Commit | Line | Data |
---|---|---|
50a47aa6 | 1 | /******************************************************************************* |
ed902a2b | 2 | * Copyright (c) 2014, 2015 École Polytechnique de Montréal |
50a47aa6 GB |
3 | * |
4 | * All rights reserved. This program and the accompanying materials are | |
5 | * made available under the terms of the Eclipse Public License v1.0 which | |
6 | * accompanies this distribution, and is available at | |
7 | * http://www.eclipse.org/legal/epl-v10.html | |
8 | * | |
9 | * Contributors: | |
10 | * Geneviève Bastien - Initial API and implementation | |
11 | *******************************************************************************/ | |
12 | ||
0f7a12d3 | 13 | package org.eclipse.tracecompass.analysis.os.linux.core.kernel; |
50a47aa6 GB |
14 | |
15 | import java.util.Collection; | |
16 | import java.util.Collections; | |
3fef638d | 17 | import java.util.Iterator; |
50a47aa6 GB |
18 | import java.util.List; |
19 | import java.util.Set; | |
20 | import java.util.TreeSet; | |
3fef638d AM |
21 | import java.util.function.Predicate; |
22 | import java.util.stream.Collectors; | |
50a47aa6 GB |
23 | |
24 | import org.eclipse.core.runtime.IProgressMonitor; | |
4c4e2816 | 25 | import org.eclipse.jdt.annotation.NonNull; |
50a47aa6 | 26 | import org.eclipse.jdt.annotation.Nullable; |
f69045e2 | 27 | import org.eclipse.tracecompass.internal.analysis.os.linux.core.kernel.Attributes; |
50a47aa6 GB |
28 | import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem; |
29 | import org.eclipse.tracecompass.statesystem.core.StateSystemUtils; | |
30 | import org.eclipse.tracecompass.statesystem.core.exceptions.AttributeNotFoundException; | |
31 | import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException; | |
32 | import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException; | |
33 | import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval; | |
34 | import org.eclipse.tracecompass.statesystem.core.statevalue.ITmfStateValue; | |
f20f0966 | 35 | import org.eclipse.tracecompass.statesystem.core.statevalue.ITmfStateValue.Type; |
50a47aa6 | 36 | |
3fef638d AM |
37 | import com.google.common.collect.ImmutableSet; |
38 | ||
50a47aa6 GB |
39 | /** |
40 | * Information provider utility class that retrieves thread-related information | |
41 | * from a Linux Kernel Analysis | |
42 | * | |
43 | * @author Geneviève Bastien | |
0f7a12d3 | 44 | * @since 2.0 |
50a47aa6 | 45 | */ |
e363eae1 | 46 | public final class KernelThreadInformationProvider { |
50a47aa6 | 47 | |
e363eae1 | 48 | private KernelThreadInformationProvider() { |
50a47aa6 GB |
49 | } |
50 | ||
51 | /** | |
52 | * Get the ID of the thread running on the CPU at time ts | |
53 | * | |
54 | * TODO: This method may later be replaced by an aspect, when the aspect can | |
55 | * resolve to something that is not an event | |
56 | * | |
57 | * @param module | |
1add07ef | 58 | * The kernel analysis instance to run this method on |
50a47aa6 GB |
59 | * @param cpuId |
60 | * The CPU number the process is running on | |
61 | * @param ts | |
62 | * The timestamp at which we want the running process | |
63 | * @return The TID of the thread running on CPU cpuId at time ts or | |
64 | * {@code null} if either no thread is running or we do not know. | |
65 | */ | |
6d16f5a9 | 66 | public static @Nullable Integer getThreadOnCpu(KernelAnalysisModule module, long cpuId, long ts) { |
50a47aa6 GB |
67 | ITmfStateSystem ss = module.getStateSystem(); |
68 | if (ss == null) { | |
69 | return null; | |
70 | } | |
71 | try { | |
72 | int cpuQuark = ss.getQuarkAbsolute(Attributes.CPUS, Long.toString(cpuId), Attributes.CURRENT_THREAD); | |
73 | ITmfStateInterval interval = ss.querySingleState(ts, cpuQuark); | |
74 | ITmfStateValue val = interval.getStateValue(); | |
f20f0966 | 75 | if (val.getType().equals(Type.INTEGER)) { |
50a47aa6 | 76 | return val.unboxInt(); |
50a47aa6 GB |
77 | } |
78 | } catch (AttributeNotFoundException | StateSystemDisposedException | TimeRangeException e) { | |
79 | } | |
80 | return null; | |
81 | } | |
82 | ||
3fef638d AM |
83 | /** |
84 | * The the threads that have been scheduled on the given CPU(s), for the | |
85 | * given time range. Threads with TID 0 (swapper threads) will never be | |
86 | * included. | |
87 | * | |
88 | * @param module | |
89 | * The kernel analysis module to query | |
90 | * @param cpus | |
91 | * The list of cpus | |
92 | * @param rangeStart | |
93 | * The start of the time range | |
94 | * @param rangeEnd | |
95 | * The end of the time range | |
96 | * @return A set of all the thread IDs that are run on said CPUs on the time | |
97 | * range. Empty set if there is no thread on the CPUs in this time | |
98 | * range. Null if the information is not available. | |
99 | * @since 2.2 | |
100 | */ | |
101 | public static @Nullable Set<Integer> getThreadsOfCpus(KernelAnalysisModule module, Collection<Long> cpus, long rangeStart, long rangeEnd) { | |
102 | ITmfStateSystem ss = module.getStateSystem(); | |
103 | if (ss == null) { | |
104 | return null; | |
105 | } | |
106 | ||
107 | Set<Long> uniqueCpus = (cpus instanceof Set ? (Set<Long>) cpus : ImmutableSet.copyOf(cpus)); | |
108 | ||
109 | int threadsQuark = ss.optQuarkAbsolute(Attributes.THREADS); | |
110 | if (threadsQuark == ITmfStateSystem.INVALID_ATTRIBUTE) { | |
111 | return null; | |
112 | } | |
113 | ||
114 | List<Integer> threadQuarks = ss.getSubAttributes(threadsQuark, false); | |
115 | return threadQuarks.stream() | |
116 | /* | |
117 | * Keep only the quarks of threads that are on at least one of the | |
118 | * wanted CPUs' run queue. | |
119 | */ | |
120 | .filter(threadQuark -> { | |
121 | int threadCurrentCpuQuark = ss.optQuarkRelative(threadQuark, Attributes.CURRENT_CPU_RQ); | |
122 | if (threadCurrentCpuQuark == ITmfStateSystem.INVALID_ATTRIBUTE) { | |
123 | return false; | |
124 | } | |
125 | ||
126 | Iterator<ITmfStateInterval> it = StateSystemUtils.getIteratorOverQuark(ss, threadCurrentCpuQuark, rangeStart, rangeEnd); | |
127 | while (it.hasNext()) { | |
128 | ITmfStateInterval interval = it.next(); | |
129 | ITmfStateValue value = interval.getStateValue(); | |
130 | if (!value.isNull() && uniqueCpus.contains(Long.valueOf(value.unboxLong()))) { | |
131 | return true; | |
132 | } | |
133 | } | |
134 | /* The thread was never on any of the requested CPUs. */ | |
135 | return false; | |
136 | }) | |
137 | ||
138 | /* Convert the thread quarks to their corresponding TIDs */ | |
139 | .map(ss::getAttributeName) | |
140 | /* Ignore swapper threads */ | |
141 | .filter(attribName -> !attribName.startsWith(Attributes.THREAD_0_PREFIX)) | |
142 | .map(Integer::parseInt) | |
143 | .collect(Collectors.toSet()); | |
144 | } | |
145 | ||
146 | /** | |
147 | * Predicate indicating if a thread state value is considered active or not. | |
148 | */ | |
149 | private static final Predicate<ITmfStateValue> IS_STATE_VALUE_ACTIVE = stateValue -> { | |
150 | if (stateValue.isNull() || | |
151 | stateValue.equals(StateValues.PROCESS_STATUS_UNKNOWN_VALUE) || | |
152 | stateValue.equals(StateValues.PROCESS_STATUS_WAIT_BLOCKED_VALUE) || | |
153 | stateValue.equals(StateValues.PROCESS_STATUS_WAIT_UNKNOWN_VALUE)) { | |
154 | return false; | |
155 | } | |
156 | return true; | |
157 | }; | |
158 | ||
159 | /** | |
160 | * Return all the threads that are considered active in the given time | |
161 | * range. Threads with TID 0 (swapper threads) will never be included. | |
162 | * | |
163 | * @param module | |
164 | * The kernel analysis module to query | |
165 | * @param rangeStart | |
166 | * The start of the time range | |
167 | * @param rangeEnd | |
168 | * The end of the time range | |
169 | * @return A set of all the thread IDs that are considered active in the | |
170 | * time range. Empty set if there are none. Null if the information | |
171 | * is not available. | |
172 | * @since 2.2 | |
173 | */ | |
174 | public static @Nullable Set<Integer> getActiveThreadsForRange(KernelAnalysisModule module, long rangeStart, long rangeEnd) { | |
175 | ITmfStateSystem ss = module.getStateSystem(); | |
176 | if (ss == null) { | |
177 | return null; | |
178 | } | |
179 | ||
180 | List<ITmfStateInterval> fullQueryAtStart; | |
181 | int threadsQuark; | |
182 | try { | |
183 | fullQueryAtStart = ss.queryFullState(rangeStart); | |
184 | threadsQuark = ss.getQuarkAbsolute(Attributes.THREADS); | |
185 | } catch (AttributeNotFoundException | StateSystemDisposedException e) { | |
186 | return null; | |
187 | } | |
188 | ||
189 | ||
190 | List<Integer> threadQuarks = ss.getSubAttributes(threadsQuark, false); | |
191 | return threadQuarks.stream() | |
192 | /* | |
193 | * Keep only the quarks of threads that are considered active at | |
194 | * some point in the time range. | |
195 | */ | |
196 | .filter(threadQuark -> { | |
197 | /* | |
198 | * If the thread was active at range start, we can already | |
199 | * consider it active. | |
200 | */ | |
201 | ITmfStateInterval intervalAtStart = fullQueryAtStart.get(threadQuark); | |
202 | if (IS_STATE_VALUE_ACTIVE.test(intervalAtStart.getStateValue())) { | |
203 | return true; | |
204 | } | |
205 | ||
206 | /* | |
207 | * If it was inactive, and it remains in the exact same | |
208 | * state for the whole time range, we can conclude it is | |
209 | * inactive for the whole range. | |
210 | * | |
211 | * Note this will not catch cases where the threads goes | |
212 | * from one inactive state to another, this will be found | |
213 | * with the range query below. | |
214 | */ | |
215 | if (intervalAtStart.getEndTime() >= rangeEnd) { | |
216 | return false; | |
217 | } | |
218 | ||
219 | Iterator<ITmfStateInterval> it = StateSystemUtils.getIteratorOverQuark(ss, threadQuark, rangeStart, rangeEnd); | |
220 | while (it.hasNext()) { | |
221 | ITmfStateInterval interval = it.next(); | |
222 | if (IS_STATE_VALUE_ACTIVE.test(interval.getStateValue())) { | |
223 | return true; | |
224 | } | |
225 | } | |
226 | /* We haven't found an active state value in the whole range. */ | |
227 | return false; | |
228 | }) | |
229 | ||
230 | /* Convert the thread quarks to their corresponding TIDs */ | |
231 | .map(ss::getAttributeName) | |
232 | /* Ignore swapper threads */ | |
233 | .filter(attribName -> !attribName.startsWith(Attributes.THREAD_0_PREFIX)) | |
234 | .map(Integer::parseInt) | |
235 | .collect(Collectors.toSet()); | |
236 | } | |
237 | ||
50a47aa6 GB |
238 | /** |
239 | * Get the TIDs of the threads from an analysis | |
240 | * | |
241 | * @param module | |
1add07ef | 242 | * The kernel analysis instance to run this method on |
50a47aa6 GB |
243 | * @return The set of TIDs corresponding to the threads |
244 | */ | |
6d16f5a9 | 245 | public static Collection<Integer> getThreadIds(KernelAnalysisModule module) { |
50a47aa6 GB |
246 | ITmfStateSystem ss = module.getStateSystem(); |
247 | if (ss == null) { | |
aa353506 | 248 | return Collections.EMPTY_SET; |
50a47aa6 GB |
249 | } |
250 | int threadQuark; | |
251 | try { | |
252 | threadQuark = ss.getQuarkAbsolute(Attributes.THREADS); | |
4c4e2816 | 253 | Set<@NonNull Integer> tids = new TreeSet<>(); |
50a47aa6 | 254 | for (Integer quark : ss.getSubAttributes(threadQuark, false)) { |
02b08403 MK |
255 | final @NonNull String attributeName = ss.getAttributeName(quark); |
256 | tids.add(attributeName.startsWith(Attributes.THREAD_0_PREFIX) ? 0 : Integer.parseInt(attributeName)); | |
50a47aa6 GB |
257 | } |
258 | return tids; | |
259 | } catch (AttributeNotFoundException e) { | |
260 | } | |
aa353506 | 261 | return Collections.EMPTY_SET; |
50a47aa6 GB |
262 | } |
263 | ||
264 | /** | |
265 | * Get the parent process ID of a thread | |
266 | * | |
267 | * @param module | |
1add07ef | 268 | * The kernel analysis instance to run this method on |
50a47aa6 GB |
269 | * @param threadId |
270 | * The thread ID of the process for which to get the parent | |
271 | * @param ts | |
272 | * The timestamp at which to get the parent | |
273 | * @return The parent PID or {@code null} if the PPID is not found. | |
274 | */ | |
6d16f5a9 | 275 | public static @Nullable Integer getParentPid(KernelAnalysisModule module, Integer threadId, long ts) { |
50a47aa6 GB |
276 | ITmfStateSystem ss = module.getStateSystem(); |
277 | if (ss == null) { | |
f20f0966 | 278 | return null; |
50a47aa6 GB |
279 | } |
280 | Integer ppidNode; | |
281 | try { | |
282 | ppidNode = ss.getQuarkAbsolute(Attributes.THREADS, threadId.toString(), Attributes.PPID); | |
283 | ITmfStateInterval ppidInterval = ss.querySingleState(ts, ppidNode); | |
284 | ITmfStateValue ppidValue = ppidInterval.getStateValue(); | |
285 | ||
f20f0966 MK |
286 | if (ppidValue.getType().equals(Type.INTEGER)) { |
287 | return Integer.valueOf(ppidValue.unboxInt()); | |
50a47aa6 GB |
288 | } |
289 | } catch (AttributeNotFoundException | StateSystemDisposedException | TimeRangeException e) { | |
290 | } | |
f20f0966 | 291 | return null; |
50a47aa6 GB |
292 | } |
293 | ||
294 | /** | |
295 | * Get the executable name of the thread ID. If the thread ID was used | |
296 | * multiple time or the name changed in between, it will return the last | |
297 | * name the thread has taken, or {@code null} if no name is found | |
298 | * | |
299 | * @param module | |
1add07ef | 300 | * The kernel analysis instance to run this method on |
50a47aa6 GB |
301 | * @param threadId |
302 | * The thread ID of the process for which to get the name | |
303 | * @return The last executable name of this process, or {@code null} if not | |
304 | * found | |
305 | */ | |
6d16f5a9 | 306 | public static @Nullable String getExecutableName(KernelAnalysisModule module, Integer threadId) { |
50a47aa6 GB |
307 | ITmfStateSystem ss = module.getStateSystem(); |
308 | if (ss == null) { | |
f20f0966 | 309 | return null; |
50a47aa6 GB |
310 | } |
311 | Integer execNameNode; | |
312 | try { | |
313 | execNameNode = ss.getQuarkAbsolute(Attributes.THREADS, threadId.toString(), Attributes.EXEC_NAME); | |
314 | List<ITmfStateInterval> execNameIntervals = StateSystemUtils.queryHistoryRange(ss, execNameNode, ss.getStartTime(), ss.getCurrentEndTime()); | |
315 | ||
316 | ITmfStateValue execNameValue; | |
f20f0966 | 317 | String execName = null; |
50a47aa6 GB |
318 | for (ITmfStateInterval interval : execNameIntervals) { |
319 | execNameValue = interval.getStateValue(); | |
f20f0966 | 320 | if (execNameValue.getType().equals(Type.STRING)) { |
0e4f957e | 321 | execName = execNameValue.unboxStr(); |
50a47aa6 GB |
322 | } |
323 | } | |
f20f0966 | 324 | return execName; |
50a47aa6 GB |
325 | } catch (AttributeNotFoundException | StateSystemDisposedException | TimeRangeException e) { |
326 | } | |
f20f0966 | 327 | return null; |
50a47aa6 GB |
328 | } |
329 | ||
1add07ef GB |
330 | /** |
331 | * Get the priority of this thread at time ts | |
332 | * | |
333 | * @param module | |
334 | * The kernel analysis instance to run this method on | |
335 | * @param threadId | |
336 | * The ID of the thread to query | |
337 | * @param ts | |
338 | * The timestamp at which to query | |
339 | * @return The priority of the thread or <code>-1</code> if not available | |
340 | */ | |
341 | public static int getThreadPriority(KernelAnalysisModule module, int threadId, long ts) { | |
342 | ITmfStateSystem ss = module.getStateSystem(); | |
343 | if (ss == null) { | |
344 | return -1; | |
345 | } | |
346 | int prioQuark = ss.optQuarkAbsolute(Attributes.THREADS, String.valueOf(threadId), Attributes.PRIO); | |
347 | if (prioQuark == ITmfStateSystem.INVALID_ATTRIBUTE) { | |
348 | return -1; | |
349 | } | |
350 | try { | |
351 | return ss.querySingleState(ts, prioQuark).getStateValue().unboxInt(); | |
ed48dc75 | 352 | } catch (StateSystemDisposedException e) { |
1add07ef GB |
353 | return -1; |
354 | } | |
355 | } | |
356 | ||
50a47aa6 GB |
357 | /** |
358 | * Get the status intervals for a given thread with a resolution | |
359 | * | |
360 | * @param module | |
1add07ef | 361 | * The kernel analysis instance to run this method on |
50a47aa6 GB |
362 | * @param threadId |
363 | * The ID of the thread to get the intervals for | |
364 | * @param start | |
365 | * The start time of the requested range | |
366 | * @param end | |
367 | * The end time of the requested range | |
368 | * @param resolution | |
369 | * The resolution or the minimal time between the requested | |
370 | * intervals. If interval times are smaller than resolution, only | |
371 | * the first interval is returned, the others are ignored. | |
372 | * @param monitor | |
373 | * A progress monitor for this task | |
374 | * @return The list of status intervals for this thread, an empty list is | |
375 | * returned if either the state system is {@code null} or the quark | |
376 | * is not found | |
377 | */ | |
6d16f5a9 | 378 | public static List<ITmfStateInterval> getStatusIntervalsForThread(KernelAnalysisModule module, Integer threadId, long start, long end, long resolution, IProgressMonitor monitor) { |
50a47aa6 GB |
379 | ITmfStateSystem ss = module.getStateSystem(); |
380 | if (ss == null) { | |
aa353506 | 381 | return Collections.EMPTY_LIST; |
50a47aa6 GB |
382 | } |
383 | ||
384 | try { | |
385 | int threadQuark = ss.getQuarkAbsolute(Attributes.THREADS, threadId.toString()); | |
d3cc952f | 386 | List<ITmfStateInterval> statusIntervals = StateSystemUtils.queryHistoryRange(ss, threadQuark, Math.max(start, ss.getStartTime()), Math.min(end - 1, ss.getCurrentEndTime()), resolution, monitor); |
50a47aa6 GB |
387 | return statusIntervals; |
388 | } catch (AttributeNotFoundException | StateSystemDisposedException | TimeRangeException e) { | |
389 | } | |
aa353506 | 390 | return Collections.EMPTY_LIST; |
50a47aa6 GB |
391 | } |
392 | ||
393 | } |