requirements: Clean up the API of TmfAnalysisRequirement
[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.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;
47
48 import com.google.common.collect.HashMultimap;
49 import com.google.common.collect.ImmutableSet;
50 import com.google.common.collect.SetMultimap;
51
52 /**
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.
55 *
56 * @author Geneviève Bastien
57 */
58 public class KernelCpuUsageAnalysis extends TmfStateSystemAnalysisModule {
59
60 /** The ID of this analysis */
61 public static final String ID = "org.eclipse.tracecompass.analysis.os.linux.cpuusage"; //$NON-NLS-1$
62
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$
69
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());
73
74 static {
75 LAYOUT_REQUIREMENT = new KernelEventLayoutRequirement(ImmutableSet.of((l) -> l.eventSchedSwitch()), PriorityLevel.MANDATORY);
76 }
77
78 private static IKernelAnalysisEventLayout getLayout(@Nullable ITmfTrace trace) {
79 IKernelAnalysisEventLayout layout;
80
81 if (trace instanceof IKernelTrace) {
82 layout = ((IKernelTrace) trace).getKernelEventLayout();
83 } else {
84 /* Fall-back to the base LttngEventLayout */
85 layout = DefaultEventLayout.getInstance();
86 }
87 return layout;
88 }
89
90 @Override
91 protected ITmfStateProvider createStateProvider() {
92 ITmfTrace trace = checkNotNull(getTrace());
93 IKernelAnalysisEventLayout layout = getLayout(trace);
94
95 return new KernelCpuUsageStateProvider(trace, layout);
96 }
97
98 @Override
99 protected StateSystemBackendType getBackendType() {
100 return StateSystemBackendType.FULL;
101 }
102
103 @Override
104 protected Iterable<IAnalysisModule> getDependentAnalyses() {
105 Set<IAnalysisModule> modules = new HashSet<>();
106
107 ITmfTrace trace = getTrace();
108 if (trace == null) {
109 throw new IllegalStateException();
110 }
111 /*
112 * This analysis depends on the LTTng kernel analysis, so it's added to
113 * dependent modules.
114 */
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);
119 break;
120 }
121 return modules;
122 }
123
124 /**
125 * Gets the maximum number of cores detected
126 *
127 * @return the number of cores
128 * @since 2.0
129 */
130 public int getNumberOfCores() {
131
132 ITmfStateSystem cpuSs = getStateSystem();
133 if (cpuSs != null) {
134 try {
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);
140 }
141 return Math.max(subAttributes.size(), cpus);
142 } catch (AttributeNotFoundException e) {
143 Activator.getDefault().logError(e.getMessage(), e);
144 }
145 }
146 return -1;
147
148 }
149
150 /**
151 * Get a map of time spent on CPU by various threads during a time range.
152 *
153 * @param cpus
154 * A set of the desired CPUs to get. An empty set gets all the
155 * cores
156 * @param start
157 * Start time of requested range
158 * @param end
159 * End time of requested range
160 * @return A map of TID -> time spent on CPU in the [start, end] interval
161 * @since 2.0
162 */
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<>();
166
167 ITmfTrace trace = getTrace();
168 ITmfStateSystem cpuSs = getStateSystem();
169 if (trace == null || cpuSs == null) {
170 return map;
171 }
172 ITmfStateSystem kernelSs = TmfStateSystemAnalysisModule.getStateSystem(trace, KernelAnalysisModule.ID);
173 if (kernelSs == null) {
174 return map;
175 }
176
177 /*
178 * Make sure the start/end times are within the state history, so we
179 * don't get TimeRange exceptions.
180 */
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());
185 long totalTime = 0;
186 if (endTime < startTime) {
187 return map;
188 }
189
190 try {
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);
198 }
199 }
200
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);
206
207 long countAtStart, countAtEnd;
208
209 for (Entry<Integer, List<Integer>> entry : tidsPerCpu.entrySet()) {
210 int cpuNode = entry.getKey();
211 List<Integer> tidNodes = entry.getValue();
212
213 String curCpuName = cpuSs.getAttributeName(cpuNode);
214 long cpuTotal = 0;
215
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();
221
222 for (int tidNode : tidNodes) {
223 String curTidName = cpuSs.getAttributeName(tidNode);
224 int tid = Integer.parseInt(curTidName);
225
226 countAtEnd = endState.get(tidNode).getStateValue().unboxLong();
227 countAtStart = startState.get(tidNode).getStateValue().unboxLong();
228 if (countAtStart == -1) {
229 countAtStart = 0;
230 }
231 if (countAtEnd == -1) {
232 countAtEnd = 0;
233 }
234
235 /*
236 * Interpolate start and end time of threads running at
237 * those times
238 */
239 if (tid == startThread || startThread == -1) {
240 long runningTime = kernelStartState.get(currentThreadQuark).getEndTime() - kernelStartState.get(currentThreadQuark).getStartTime();
241 long runningEnd = kernelStartState.get(currentThreadQuark).getEndTime();
242
243 countAtStart = interpolateCount(countAtStart, startTime, runningEnd, runningTime);
244 }
245 if (tid == endThread) {
246 long runningTime = kernelEndState.get(currentThreadQuark).getEndTime() - kernelEndState.get(currentThreadQuark).getStartTime();
247 long runningEnd = kernelEndState.get(currentThreadQuark).getEndTime();
248
249 countAtEnd = interpolateCount(countAtEnd, endTime, runningEnd, runningTime);
250 }
251 /*
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.
258 */
259 if ((startThread == -1) && ((countAtEnd - countAtStart < 0) || (countAtEnd == 0))) {
260 countAtStart = 0;
261 }
262
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$
266 currentCount = 0;
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$
269 currentCount = 0;
270 }
271 cpuTotal += currentCount;
272 map.put(curCpuName + SPLIT_STRING + curTidName, currentCount);
273 addToMap(totalMap, curTidName, currentCount);
274 totalTime += (currentCount);
275 }
276 map.put(curCpuName, cpuTotal);
277 }
278
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());
282 }
283 map.put(TOTAL, totalTime);
284
285 } catch (TimeRangeException | AttributeNotFoundException e) {
286 /*
287 * Assume there is no events or the attribute does not exist yet,
288 * nothing will be put in the map.
289 */
290 } catch (StateValueTypeException | StateSystemDisposedException e) {
291 /*
292 * These other exception types would show a logic problem, so they
293 * should not happen.
294 */
295 Activator.getDefault().logError("Error getting CPU usage in a time range", e); //$NON-NLS-1$
296 }
297
298 return map;
299 }
300
301 private static long interpolateCount(long count, long ts, long runningEnd, long runningTime) {
302 long newCount = count;
303
304 /* sanity check */
305 if (runningTime > 0) {
306
307 long runningStart = runningEnd - runningTime;
308
309 if (ts < runningStart) {
310 /*
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
314 */
315 return newCount;
316 }
317 newCount += (ts - runningStart);
318 }
319 return newCount;
320 }
321
322 /*
323 * Add the value to the previous value in the map. If the key was not set,
324 * assume 0
325 */
326 private static void addToMap(Map<String, Long> map, String key, Long value) {
327 Long addTo = map.get(key);
328 if (addTo == null) {
329 map.put(key, value);
330 } else {
331 map.put(key, addTo + value);
332 }
333 }
334
335 @Override
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);
343 }
344 return reqs;
345 }
346
347 }
This page took 0.03869 seconds and 5 git commands to generate.