02f0770444c0f3912f7350f70be19915928a21f8
[deliverable/tracecompass.git] / analysis / org.eclipse.tracecompass.analysis.os.linux.core / src / org / eclipse / tracecompass / analysis / os / linux / core / cpuusage / KernelCpuUsageAnalysis.java
1 /*******************************************************************************
2 * Copyright (c) 2014, 2015 École Polytechnique de Montréal and others.
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
13 package org.eclipse.tracecompass.analysis.os.linux.core.cpuusage;
14
15 import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
16
17 import java.util.HashMap;
18 import java.util.HashSet;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Map.Entry;
22 import java.util.Set;
23
24 import org.eclipse.jdt.annotation.NonNull;
25 import org.eclipse.tracecompass.analysis.os.linux.core.kernel.Attributes;
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.internal.analysis.os.linux.core.Activator;
31 import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem;
32 import org.eclipse.tracecompass.statesystem.core.exceptions.AttributeNotFoundException;
33 import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException;
34 import org.eclipse.tracecompass.statesystem.core.exceptions.StateValueTypeException;
35 import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException;
36 import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval;
37 import org.eclipse.tracecompass.tmf.core.analysis.IAnalysisModule;
38 import org.eclipse.tracecompass.tmf.core.statesystem.ITmfStateProvider;
39 import org.eclipse.tracecompass.tmf.core.statesystem.TmfStateSystemAnalysisModule;
40 import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
41 import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils;
42
43 /**
44 * This analysis module computes the CPU usage of a system from a kernel trace.
45 * It requires the LTTng Kernel analysis module to have accurate CPU usage data.
46 *
47 * @author Geneviève Bastien
48 */
49 public class KernelCpuUsageAnalysis extends TmfStateSystemAnalysisModule {
50
51 /** The ID of this analysis */
52 public static final String ID = "org.eclipse.tracecompass.analysis.os.linux.cpuusage"; //$NON-NLS-1$
53
54 /** Text used to identify 'total' entries in the returned maps */
55 public static final String TOTAL = "total"; //$NON-NLS-1$
56 /** String used to separate elements in the returned maps */
57 public static final String SPLIT_STRING = "/"; //$NON-NLS-1$
58 /** Idle process thread ID */
59 public static final String TID_ZERO = "0"; //$NON-NLS-1$
60
61 @Override
62 protected ITmfStateProvider createStateProvider() {
63 ITmfTrace trace = checkNotNull(getTrace());
64 IKernelAnalysisEventLayout layout;
65
66 if (trace instanceof IKernelTrace) {
67 layout = ((IKernelTrace) trace).getKernelEventLayout();
68 } else {
69 /* Fall-back to the base LttngEventLayout */
70 layout = DefaultEventLayout.getInstance();
71 }
72
73 return new KernelCpuUsageStateProvider(trace, layout);
74 }
75
76 @Override
77 protected StateSystemBackendType getBackendType() {
78 return StateSystemBackendType.FULL;
79 }
80
81 @Override
82 protected Iterable<IAnalysisModule> getDependentAnalyses() {
83 Set<IAnalysisModule> modules = new HashSet<>();
84
85 ITmfTrace trace = getTrace();
86 if (trace == null) {
87 throw new IllegalStateException();
88 }
89 /*
90 * This analysis depends on the LTTng kernel analysis, so it's added to
91 * dependent modules.
92 */
93 Iterable<KernelAnalysisModule> kernelModules = TmfTraceUtils.getAnalysisModulesOfClass(trace, KernelAnalysisModule.class);
94 for (KernelAnalysisModule kernelModule : kernelModules) {
95 /* Only add the first one we find, if there is one */
96 modules.add(kernelModule);
97 break;
98 }
99 return modules;
100 }
101
102 /**
103 * Gets the maximum number of cores detected
104 *
105 * @return the number of cores
106 * @since 2.0
107 */
108 public int getNumberOfCores() {
109
110 ITmfStateSystem cpuSs = getStateSystem();
111 if (cpuSs != null) {
112 try {
113 int cpusNode = cpuSs.getQuarkAbsolute(Attributes.CPUS);
114 final @NonNull List<@NonNull Integer> subAttributes = cpuSs.getSubAttributes(cpusNode, false);
115 int cpus = Integer.MIN_VALUE;
116 for (Integer quark : subAttributes) {
117 cpus = Math.max(Integer.parseInt(cpuSs.getAttributeName(quark)), cpus);
118 }
119 return Math.max(subAttributes.size(), cpus);
120 } catch (AttributeNotFoundException e) {
121 Activator.getDefault().logError(e.getMessage(), e);
122 }
123 }
124 return -1;
125
126 }
127
128 /**
129 * Get a map of time spent on CPU by various threads during a time range.
130 *
131 * @param cpus
132 * A set of the desired CPUs to get. An empty set gets all the
133 * cores
134 * @param start
135 * Start time of requested range
136 * @param end
137 * End time of requested range
138 * @return A map of TID -> time spent on CPU in the [start, end] interval
139 * @since 2.0
140 */
141 public Map<String, Long> getCpuUsageInRange(Set<@NonNull Integer> cpus, long start, long end) {
142 Map<String, Long> map = new HashMap<>();
143 Map<String, Long> totalMap = new HashMap<>();
144
145 ITmfTrace trace = getTrace();
146 ITmfStateSystem cpuSs = getStateSystem();
147 if (trace == null || cpuSs == null) {
148 return map;
149 }
150 ITmfStateSystem kernelSs = TmfStateSystemAnalysisModule.getStateSystem(trace, KernelAnalysisModule.ID);
151 if (kernelSs == null) {
152 return map;
153 }
154
155 /*
156 * Make sure the start/end times are within the state history, so we
157 * don't get TimeRange exceptions.
158 */
159 long startTime = Math.max(start, cpuSs.getStartTime());
160 startTime = Math.max(startTime, kernelSs.getStartTime());
161 long endTime = Math.min(end, cpuSs.getCurrentEndTime());
162 endTime = Math.min(endTime, kernelSs.getCurrentEndTime());
163 long totalTime = 0;
164 if (endTime < startTime) {
165 return map;
166 }
167
168 try {
169 /* Get the list of quarks for each CPU and CPU's TIDs */
170 int cpusNode = cpuSs.getQuarkAbsolute(Attributes.CPUS);
171 Map<Integer, List<Integer>> tidsPerCpu = new HashMap<>();
172 for (int cpuNode : cpuSs.getSubAttributes(cpusNode, false)) {
173 final @NonNull List<@NonNull Integer> cpuSubAttributes = cpuSs.getSubAttributes(cpuNode, false);
174 if (cpus.isEmpty() || cpus.contains(Integer.parseInt(cpuSs.getAttributeName(cpuNode)))) {
175 tidsPerCpu.put(cpuNode, cpuSubAttributes);
176 }
177 }
178
179 /* Query full states at start and end times */
180 List<ITmfStateInterval> kernelEndState = kernelSs.queryFullState(endTime);
181 List<ITmfStateInterval> endState = cpuSs.queryFullState(endTime);
182 List<ITmfStateInterval> kernelStartState = kernelSs.queryFullState(startTime);
183 List<ITmfStateInterval> startState = cpuSs.queryFullState(startTime);
184
185 long countAtStart, countAtEnd;
186
187 for (Entry<Integer, List<Integer>> entry : tidsPerCpu.entrySet()) {
188 int cpuNode = entry.getKey();
189 List<Integer> tidNodes = entry.getValue();
190
191 String curCpuName = cpuSs.getAttributeName(cpuNode);
192 long cpuTotal = 0;
193
194 /* Get the quark of the thread running on this CPU */
195 int currentThreadQuark = kernelSs.getQuarkAbsolute(Attributes.CPUS, curCpuName, Attributes.CURRENT_THREAD);
196 /* Get the currently running thread on this CPU */
197 int startThread = kernelStartState.get(currentThreadQuark).getStateValue().unboxInt();
198 int endThread = kernelEndState.get(currentThreadQuark).getStateValue().unboxInt();
199
200 for (int tidNode : tidNodes) {
201 String curTidName = cpuSs.getAttributeName(tidNode);
202 int tid = Integer.parseInt(curTidName);
203
204 countAtEnd = endState.get(tidNode).getStateValue().unboxLong();
205 countAtStart = startState.get(tidNode).getStateValue().unboxLong();
206 if (countAtStart == -1) {
207 countAtStart = 0;
208 }
209 if (countAtEnd == -1) {
210 countAtEnd = 0;
211 }
212
213 /*
214 * Interpolate start and end time of threads running at
215 * those times
216 */
217 if (tid == startThread || startThread == -1) {
218 long runningTime = kernelStartState.get(currentThreadQuark).getEndTime() - kernelStartState.get(currentThreadQuark).getStartTime();
219 long runningEnd = kernelStartState.get(currentThreadQuark).getEndTime();
220
221 countAtStart = interpolateCount(countAtStart, startTime, runningEnd, runningTime);
222 }
223 if (tid == endThread) {
224 long runningTime = kernelEndState.get(currentThreadQuark).getEndTime() - kernelEndState.get(currentThreadQuark).getStartTime();
225 long runningEnd = kernelEndState.get(currentThreadQuark).getEndTime();
226
227 countAtEnd = interpolateCount(countAtEnd, endTime, runningEnd, runningTime);
228 }
229 /*
230 * If startThread is -1, we made the hypothesis that the
231 * process running at start was the current one. If the
232 * count is negative, we were wrong in this hypothesis. Also
233 * if the time at end is 0, it either means the process
234 * hasn't been on the CPU or that we still don't know who is
235 * running. In both cases, that invalidates the hypothesis.
236 */
237 if ((startThread == -1) && ((countAtEnd - countAtStart < 0) || (countAtEnd == 0))) {
238 countAtStart = 0;
239 }
240
241 long currentCount = countAtEnd - countAtStart;
242 if (currentCount < 0) {
243 Activator.getDefault().logWarning(String.format("Negative count: start %d, end %d", countAtStart, countAtEnd)); //$NON-NLS-1$
244 currentCount = 0;
245 } else if (currentCount > endTime - startTime) {
246 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$
247 currentCount = 0;
248 }
249 cpuTotal += currentCount;
250 map.put(curCpuName + SPLIT_STRING + curTidName, currentCount);
251 addToMap(totalMap, curTidName, currentCount);
252 totalTime += (currentCount);
253 }
254 map.put(curCpuName, cpuTotal);
255 }
256
257 /* Add the totals to the map */
258 for (Entry<String, Long> entry : totalMap.entrySet()) {
259 map.put(TOTAL + SPLIT_STRING + entry.getKey(), entry.getValue());
260 }
261 map.put(TOTAL, totalTime);
262
263 } catch (TimeRangeException | AttributeNotFoundException e) {
264 /*
265 * Assume there is no events or the attribute does not exist yet,
266 * nothing will be put in the map.
267 */
268 } catch (StateValueTypeException | StateSystemDisposedException e) {
269 /*
270 * These other exception types would show a logic problem, so they
271 * should not happen.
272 */
273 Activator.getDefault().logError("Error getting CPU usage in a time range", e); //$NON-NLS-1$
274 }
275
276 return map;
277 }
278
279 private static long interpolateCount(long count, long ts, long runningEnd, long runningTime) {
280 long newCount = count;
281
282 /* sanity check */
283 if (runningTime > 0) {
284
285 long runningStart = runningEnd - runningTime;
286
287 if (ts < runningStart) {
288 /*
289 * This interval was not started, this can happen if the current
290 * running thread is unknown and we execute this method. It just
291 * means that this process was not the one running
292 */
293 return newCount;
294 }
295 newCount += (ts - runningStart);
296 }
297 return newCount;
298 }
299
300 /*
301 * Add the value to the previous value in the map. If the key was not set,
302 * assume 0
303 */
304 private static void addToMap(Map<String, Long> map, String key, Long value) {
305 Long addTo = map.get(key);
306 if (addTo == null) {
307 map.put(key, value);
308 } else {
309 map.put(key, addTo + value);
310 }
311 }
312
313 }
This page took 0.056981 seconds and 4 git commands to generate.