1 /*******************************************************************************
2 * Copyright (c) 2014, 2015 École Polytechnique de Montréal and others.
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
10 * Geneviève Bastien - Initial API and implementation
11 *******************************************************************************/
13 package org
.eclipse
.tracecompass
.analysis
.os
.linux
.core
.cpuusage
;
15 import static org
.eclipse
.tracecompass
.common
.core
.NonNullUtils
.checkNotNull
;
17 import java
.util
.HashMap
;
18 import java
.util
.HashSet
;
19 import java
.util
.List
;
21 import java
.util
.Map
.Entry
;
24 import org
.eclipse
.jdt
.annotation
.NonNull
;
25 import org
.eclipse
.jdt
.annotation
.Nullable
;
26 import org
.eclipse
.tracecompass
.analysis
.os
.linux
.core
.kernel
.KernelAnalysisModule
;
27 import org
.eclipse
.tracecompass
.analysis
.os
.linux
.core
.trace
.DefaultEventLayout
;
28 import org
.eclipse
.tracecompass
.analysis
.os
.linux
.core
.trace
.IKernelAnalysisEventLayout
;
29 import org
.eclipse
.tracecompass
.analysis
.os
.linux
.core
.trace
.IKernelTrace
;
30 import org
.eclipse
.tracecompass
.analysis
.os
.linux
.core
.trace
.KernelEventLayoutRequirement
;
31 import org
.eclipse
.tracecompass
.common
.core
.NonNullUtils
;
32 import org
.eclipse
.tracecompass
.internal
.analysis
.os
.linux
.core
.Activator
;
33 import org
.eclipse
.tracecompass
.internal
.analysis
.os
.linux
.core
.kernel
.Attributes
;
34 import org
.eclipse
.tracecompass
.statesystem
.core
.ITmfStateSystem
;
35 import org
.eclipse
.tracecompass
.statesystem
.core
.exceptions
.AttributeNotFoundException
;
36 import org
.eclipse
.tracecompass
.statesystem
.core
.exceptions
.StateSystemDisposedException
;
37 import org
.eclipse
.tracecompass
.statesystem
.core
.exceptions
.StateValueTypeException
;
38 import org
.eclipse
.tracecompass
.statesystem
.core
.exceptions
.TimeRangeException
;
39 import org
.eclipse
.tracecompass
.statesystem
.core
.interval
.ITmfStateInterval
;
40 import org
.eclipse
.tracecompass
.tmf
.core
.analysis
.IAnalysisModule
;
41 import org
.eclipse
.tracecompass
.tmf
.core
.analysis
.requirements
.TmfAnalysisRequirement
;
42 import org
.eclipse
.tracecompass
.tmf
.core
.analysis
.requirements
.TmfAnalysisRequirement
.PriorityLevel
;
43 import org
.eclipse
.tracecompass
.tmf
.core
.statesystem
.ITmfStateProvider
;
44 import org
.eclipse
.tracecompass
.tmf
.core
.statesystem
.TmfStateSystemAnalysisModule
;
45 import org
.eclipse
.tracecompass
.tmf
.core
.trace
.ITmfTrace
;
46 import org
.eclipse
.tracecompass
.tmf
.core
.trace
.TmfTraceUtils
;
48 import com
.google
.common
.collect
.HashMultimap
;
49 import com
.google
.common
.collect
.ImmutableSet
;
50 import com
.google
.common
.collect
.SetMultimap
;
53 * This analysis module computes the CPU usage of a system from a kernel trace.
54 * It requires the LTTng Kernel analysis module to have accurate CPU usage data.
56 * @author Geneviève Bastien
58 public class KernelCpuUsageAnalysis
extends TmfStateSystemAnalysisModule
{
60 /** The ID of this analysis */
61 public static final String ID
= "org.eclipse.tracecompass.analysis.os.linux.cpuusage"; //$NON-NLS-1$
63 /** Text used to identify 'total' entries in the returned maps */
64 public static final String TOTAL
= "total"; //$NON-NLS-1$
65 /** String used to separate elements in the returned maps */
66 public static final String SPLIT_STRING
= "/"; //$NON-NLS-1$
67 /** Idle process thread ID */
68 public static final String TID_ZERO
= "0"; //$NON-NLS-1$
70 /** The requirements as an immutable set */
71 private static final KernelEventLayoutRequirement LAYOUT_REQUIREMENT
;
72 private static final SetMultimap
<IKernelAnalysisEventLayout
, TmfAnalysisRequirement
> LAYOUT_REQ_MAP
= NonNullUtils
.checkNotNull(HashMultimap
.create());
75 LAYOUT_REQUIREMENT
= new KernelEventLayoutRequirement(ImmutableSet
.of((l
) -> l
.eventSchedSwitch()), PriorityLevel
.MANDATORY
);
78 private static IKernelAnalysisEventLayout
getLayout(@Nullable ITmfTrace trace
) {
79 IKernelAnalysisEventLayout layout
;
81 if (trace
instanceof IKernelTrace
) {
82 layout
= ((IKernelTrace
) trace
).getKernelEventLayout();
84 /* Fall-back to the base LttngEventLayout */
85 layout
= DefaultEventLayout
.getInstance();
91 protected ITmfStateProvider
createStateProvider() {
92 ITmfTrace trace
= checkNotNull(getTrace());
93 IKernelAnalysisEventLayout layout
= getLayout(trace
);
95 return new KernelCpuUsageStateProvider(trace
, layout
);
99 protected StateSystemBackendType
getBackendType() {
100 return StateSystemBackendType
.FULL
;
104 protected Iterable
<IAnalysisModule
> getDependentAnalyses() {
105 Set
<IAnalysisModule
> modules
= new HashSet
<>();
107 ITmfTrace trace
= getTrace();
109 throw new IllegalStateException();
112 * This analysis depends on the LTTng kernel analysis, so it's added to
115 Iterable
<KernelAnalysisModule
> kernelModules
= TmfTraceUtils
.getAnalysisModulesOfClass(trace
, KernelAnalysisModule
.class);
116 for (KernelAnalysisModule kernelModule
: kernelModules
) {
117 /* Only add the first one we find, if there is one */
118 modules
.add(kernelModule
);
125 * Gets the maximum number of cores detected
127 * @return the number of cores
130 public int getNumberOfCores() {
132 ITmfStateSystem cpuSs
= getStateSystem();
135 int cpusNode
= cpuSs
.getQuarkAbsolute(Attributes
.CPUS
);
136 final @NonNull List
<@NonNull Integer
> subAttributes
= cpuSs
.getSubAttributes(cpusNode
, false);
137 int cpus
= Integer
.MIN_VALUE
;
138 for (Integer quark
: subAttributes
) {
139 cpus
= Math
.max(Integer
.parseInt(cpuSs
.getAttributeName(quark
)), cpus
);
141 return Math
.max(subAttributes
.size(), cpus
);
142 } catch (AttributeNotFoundException e
) {
143 Activator
.getDefault().logError(e
.getMessage(), e
);
151 * Get a map of time spent on CPU by various threads during a time range.
154 * A set of the desired CPUs to get. An empty set gets all the
157 * Start time of requested range
159 * End time of requested range
160 * @return A map of TID -> time spent on CPU in the [start, end] interval
163 public Map
<String
, Long
> getCpuUsageInRange(Set
<@NonNull Integer
> cpus
, long start
, long end
) {
164 Map
<String
, Long
> map
= new HashMap
<>();
165 Map
<String
, Long
> totalMap
= new HashMap
<>();
167 ITmfTrace trace
= getTrace();
168 ITmfStateSystem cpuSs
= getStateSystem();
169 if (trace
== null || cpuSs
== null) {
172 ITmfStateSystem kernelSs
= TmfStateSystemAnalysisModule
.getStateSystem(trace
, KernelAnalysisModule
.ID
);
173 if (kernelSs
== null) {
178 * Make sure the start/end times are within the state history, so we
179 * don't get TimeRange exceptions.
181 long startTime
= Math
.max(start
, cpuSs
.getStartTime());
182 startTime
= Math
.max(startTime
, kernelSs
.getStartTime());
183 long endTime
= Math
.min(end
, cpuSs
.getCurrentEndTime());
184 endTime
= Math
.min(endTime
, kernelSs
.getCurrentEndTime());
186 if (endTime
< startTime
) {
191 /* Get the list of quarks for each CPU and CPU's TIDs */
192 int cpusNode
= cpuSs
.getQuarkAbsolute(Attributes
.CPUS
);
193 Map
<Integer
, List
<Integer
>> tidsPerCpu
= new HashMap
<>();
194 for (int cpuNode
: cpuSs
.getSubAttributes(cpusNode
, false)) {
195 final @NonNull List
<@NonNull Integer
> cpuSubAttributes
= cpuSs
.getSubAttributes(cpuNode
, false);
196 if (cpus
.isEmpty() || cpus
.contains(Integer
.parseInt(cpuSs
.getAttributeName(cpuNode
)))) {
197 tidsPerCpu
.put(cpuNode
, cpuSubAttributes
);
201 /* Query full states at start and end times */
202 List
<ITmfStateInterval
> kernelEndState
= kernelSs
.queryFullState(endTime
);
203 List
<ITmfStateInterval
> endState
= cpuSs
.queryFullState(endTime
);
204 List
<ITmfStateInterval
> kernelStartState
= kernelSs
.queryFullState(startTime
);
205 List
<ITmfStateInterval
> startState
= cpuSs
.queryFullState(startTime
);
207 long countAtStart
, countAtEnd
;
209 for (Entry
<Integer
, List
<Integer
>> entry
: tidsPerCpu
.entrySet()) {
210 int cpuNode
= entry
.getKey();
211 List
<Integer
> tidNodes
= entry
.getValue();
213 String curCpuName
= cpuSs
.getAttributeName(cpuNode
);
216 /* Get the quark of the thread running on this CPU */
217 int currentThreadQuark
= kernelSs
.getQuarkAbsolute(Attributes
.CPUS
, curCpuName
, Attributes
.CURRENT_THREAD
);
218 /* Get the currently running thread on this CPU */
219 int startThread
= kernelStartState
.get(currentThreadQuark
).getStateValue().unboxInt();
220 int endThread
= kernelEndState
.get(currentThreadQuark
).getStateValue().unboxInt();
222 for (int tidNode
: tidNodes
) {
223 String curTidName
= cpuSs
.getAttributeName(tidNode
);
224 int tid
= Integer
.parseInt(curTidName
);
226 countAtEnd
= endState
.get(tidNode
).getStateValue().unboxLong();
227 countAtStart
= startState
.get(tidNode
).getStateValue().unboxLong();
228 if (countAtStart
== -1) {
231 if (countAtEnd
== -1) {
236 * Interpolate start and end time of threads running at
239 if (tid
== startThread
|| startThread
== -1) {
240 long runningTime
= kernelStartState
.get(currentThreadQuark
).getEndTime() - kernelStartState
.get(currentThreadQuark
).getStartTime();
241 long runningEnd
= kernelStartState
.get(currentThreadQuark
).getEndTime();
243 countAtStart
= interpolateCount(countAtStart
, startTime
, runningEnd
, runningTime
);
245 if (tid
== endThread
) {
246 long runningTime
= kernelEndState
.get(currentThreadQuark
).getEndTime() - kernelEndState
.get(currentThreadQuark
).getStartTime();
247 long runningEnd
= kernelEndState
.get(currentThreadQuark
).getEndTime();
249 countAtEnd
= interpolateCount(countAtEnd
, endTime
, runningEnd
, runningTime
);
252 * If startThread is -1, we made the hypothesis that the
253 * process running at start was the current one. If the
254 * count is negative, we were wrong in this hypothesis. Also
255 * if the time at end is 0, it either means the process
256 * hasn't been on the CPU or that we still don't know who is
257 * running. In both cases, that invalidates the hypothesis.
259 if ((startThread
== -1) && ((countAtEnd
- countAtStart
< 0) || (countAtEnd
== 0))) {
263 long currentCount
= countAtEnd
- countAtStart
;
264 if (currentCount
< 0) {
265 Activator
.getDefault().logWarning(String
.format("Negative count: start %d, end %d", countAtStart
, countAtEnd
)); //$NON-NLS-1$
267 } else if (currentCount
> endTime
- startTime
) {
268 Activator
.getDefault().logWarning(String
.format("CPU Usage: Spent more time on CPU than allowed: %s spent %d when max should be %d", curTidName
, currentCount
, endTime
- startTime
)); //$NON-NLS-1$
271 cpuTotal
+= currentCount
;
272 map
.put(curCpuName
+ SPLIT_STRING
+ curTidName
, currentCount
);
273 addToMap(totalMap
, curTidName
, currentCount
);
274 totalTime
+= (currentCount
);
276 map
.put(curCpuName
, cpuTotal
);
279 /* Add the totals to the map */
280 for (Entry
<String
, Long
> entry
: totalMap
.entrySet()) {
281 map
.put(TOTAL
+ SPLIT_STRING
+ entry
.getKey(), entry
.getValue());
283 map
.put(TOTAL
, totalTime
);
285 } catch (TimeRangeException
| AttributeNotFoundException e
) {
287 * Assume there is no events or the attribute does not exist yet,
288 * nothing will be put in the map.
290 } catch (StateValueTypeException
| StateSystemDisposedException e
) {
292 * These other exception types would show a logic problem, so they
295 Activator
.getDefault().logError("Error getting CPU usage in a time range", e
); //$NON-NLS-1$
301 private static long interpolateCount(long count
, long ts
, long runningEnd
, long runningTime
) {
302 long newCount
= count
;
305 if (runningTime
> 0) {
307 long runningStart
= runningEnd
- runningTime
;
309 if (ts
< runningStart
) {
311 * This interval was not started, this can happen if the current
312 * running thread is unknown and we execute this method. It just
313 * means that this process was not the one running
317 newCount
+= (ts
- runningStart
);
323 * Add the value to the previous value in the map. If the key was not set,
326 private static void addToMap(Map
<String
, Long
> map
, String key
, Long value
) {
327 Long addTo
= map
.get(key
);
331 map
.put(key
, addTo
+ value
);
336 public Iterable
<TmfAnalysisRequirement
> getAnalysisRequirements() {
337 ITmfTrace trace
= getTrace();
338 IKernelAnalysisEventLayout layout
= getLayout(trace
);
339 Set
<TmfAnalysisRequirement
> reqs
= LAYOUT_REQ_MAP
.get(layout
);
340 if (reqs
.isEmpty()) {
341 reqs
= ImmutableSet
.of(LAYOUT_REQUIREMENT
.instanciateRequirements(layout
));
342 LAYOUT_REQ_MAP
.putAll(layout
, reqs
);