aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFrançois Rajotte2013-02-22 14:34:38 (EST)
committerGenevieve Bastien2014-04-16 13:46:18 (EDT)
commitf43160e7b6d442580a6f87999eacf0372346a3c7 (patch)
tree25749c221bb969b831229bd7fd4aaa11e06257fd
parent783a10a8a37dc832ade3f8a81d6a9385d17ab82a (diff)
downloadorg.eclipse.linuxtools-f43160e7b6d442580a6f87999eacf0372346a3c7.zip
org.eclipse.linuxtools-f43160e7b6d442580a6f87999eacf0372346a3c7.tar.gz
org.eclipse.linuxtools-f43160e7b6d442580a6f87999eacf0372346a3c7.tar.bz2
LTTng: CPU usage analysis from the LTTng kernel tracerefs/changes/33/21633/5
This analysis creates a new state system to save the total time spent on each CPU by the different threads. It then uses it, along with the LTTng kernel state system to compute CPU usage statistics in a time range. Change-Id: Ia60e95c3bace646477aef06d58bdb7ed64c32bd4 Signed-off-by: François Rajotte <francois.rajotte@polymtl.ca> Reviewed-on: https://git.eclipse.org/r/21633 Tested-by: Hudson CI Reviewed-by: Matthew Khouzam <matthew.khouzam@ericsson.com> Tested-by: Matthew Khouzam <matthew.khouzam@ericsson.com> Reviewed-by: Genevieve Bastien <gbastien+lttng@versatic.net> Tested-by: Genevieve Bastien <gbastien+lttng@versatic.net>
-rw-r--r--lttng/org.eclipse.linuxtools.lttng2.kernel.core/META-INF/MANIFEST.MF1
-rw-r--r--lttng/org.eclipse.linuxtools.lttng2.kernel.core/plugin.properties3
-rw-r--r--lttng/org.eclipse.linuxtools.lttng2.kernel.core/plugin.xml9
-rw-r--r--lttng/org.eclipse.linuxtools.lttng2.kernel.core/src/org/eclipse/linuxtools/lttng2/kernel/core/cpuusage/LttngKernelCpuStateProvider.java141
-rw-r--r--lttng/org.eclipse.linuxtools.lttng2.kernel.core/src/org/eclipse/linuxtools/lttng2/kernel/core/cpuusage/LttngKernelCpuUsageAnalysis.java256
5 files changed, 410 insertions, 0 deletions
diff --git a/lttng/org.eclipse.linuxtools.lttng2.kernel.core/META-INF/MANIFEST.MF b/lttng/org.eclipse.linuxtools.lttng2.kernel.core/META-INF/MANIFEST.MF
index ccb2254..209d112 100644
--- a/lttng/org.eclipse.linuxtools.lttng2.kernel.core/META-INF/MANIFEST.MF
+++ b/lttng/org.eclipse.linuxtools.lttng2.kernel.core/META-INF/MANIFEST.MF
@@ -15,5 +15,6 @@ Require-Bundle: org.eclipse.core.runtime,
Export-Package: org.eclipse.linuxtools.internal.lttng2.kernel.core;x-friends:="org.eclipse.linuxtools.lttng2.kernel.ui,org.eclipse.linuxtools.lttng2.kernel.core.tests",
org.eclipse.linuxtools.internal.lttng2.kernel.core.stateprovider;x-friends:="org.eclipse.linuxtools.lttng2.kernel.ui,org.eclipse.linuxtools.lttng2.kernel.core.tests",
org.eclipse.linuxtools.lttng2.kernel.core.analysis,
+ org.eclipse.linuxtools.lttng2.kernel.core.cpuusage,
org.eclipse.linuxtools.lttng2.kernel.core.event.matching,
org.eclipse.linuxtools.lttng2.kernel.core.trace
diff --git a/lttng/org.eclipse.linuxtools.lttng2.kernel.core/plugin.properties b/lttng/org.eclipse.linuxtools.lttng2.kernel.core/plugin.properties
index 64eea0b..bfb0525 100644
--- a/lttng/org.eclipse.linuxtools.lttng2.kernel.core/plugin.properties
+++ b/lttng/org.eclipse.linuxtools.lttng2.kernel.core/plugin.properties
@@ -16,3 +16,6 @@ Bundle-Name = Linux Tools LTTng Kernel Analysis Core Plug-in
tracetype.type.kernel = LTTng Kernel Trace
analysis.lttngkernel = LTTng Kernel Analysis
+
+cpuusage.lttng.kernel = CPU usage
+
diff --git a/lttng/org.eclipse.linuxtools.lttng2.kernel.core/plugin.xml b/lttng/org.eclipse.linuxtools.lttng2.kernel.core/plugin.xml
index 278e822..cc0b84e 100644
--- a/lttng/org.eclipse.linuxtools.lttng2.kernel.core/plugin.xml
+++ b/lttng/org.eclipse.linuxtools.lttng2.kernel.core/plugin.xml
@@ -23,5 +23,14 @@
class="org.eclipse.linuxtools.lttng2.kernel.core.trace.LttngKernelTrace">
</tracetype>
</module>
+ <module
+ analysis_module="org.eclipse.linuxtools.lttng2.kernel.core.cpuusage.LttngKernelCpuUsageAnalysis"
+ id="org.eclipse.linuxtools.lttng2.kernel.core.cpuusage"
+ name="%cpuusage.lttng.kernel">
+ <tracetype
+ applies="true"
+ class="org.eclipse.linuxtools.lttng2.kernel.core.trace.LttngKernelTrace">
+ </tracetype>
+ </module>
</extension>
</plugin>
diff --git a/lttng/org.eclipse.linuxtools.lttng2.kernel.core/src/org/eclipse/linuxtools/lttng2/kernel/core/cpuusage/LttngKernelCpuStateProvider.java b/lttng/org.eclipse.linuxtools.lttng2.kernel.core/src/org/eclipse/linuxtools/lttng2/kernel/core/cpuusage/LttngKernelCpuStateProvider.java
new file mode 100644
index 0000000..8b680f1
--- /dev/null
+++ b/lttng/org.eclipse.linuxtools.lttng2.kernel.core/src/org/eclipse/linuxtools/lttng2/kernel/core/cpuusage/LttngKernelCpuStateProvider.java
@@ -0,0 +1,141 @@
+/*******************************************************************************
+ * Copyright (c) 2014 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * François Rajotte - Initial API and implementation
+ * Geneviève Bastien - Revision of the initial implementation
+ *******************************************************************************/
+
+package org.eclipse.linuxtools.lttng2.kernel.core.cpuusage;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.linuxtools.internal.lttng2.kernel.core.Activator;
+import org.eclipse.linuxtools.internal.lttng2.kernel.core.Attributes;
+import org.eclipse.linuxtools.internal.lttng2.kernel.core.LttngStrings;
+import org.eclipse.linuxtools.tmf.core.event.ITmfEvent;
+import org.eclipse.linuxtools.tmf.core.event.ITmfEventField;
+import org.eclipse.linuxtools.tmf.core.exceptions.AttributeNotFoundException;
+import org.eclipse.linuxtools.tmf.core.statesystem.AbstractTmfStateProvider;
+import org.eclipse.linuxtools.tmf.core.statevalue.ITmfStateValue;
+import org.eclipse.linuxtools.tmf.core.statevalue.TmfStateValue;
+import org.eclipse.linuxtools.tmf.core.trace.ITmfTrace;
+
+/**
+ * Creates a state system with the total time spent on CPU for each thread and
+ * for each CPU from a kernel trace.
+ *
+ * This state system in itself keeps the total time on CPU since last time the
+ * process was scheduled out. The state system queries will only be accurate
+ * when the process is not in a running state. To have exact CPU usage when
+ * running, this state system needs to be used along the LTTng Kernel analysis.
+ *
+ * It requires only the 'sched_switch' events enabled on the trace.
+ *
+ * @author François Rajotte
+ * @since 3.0
+ */
+public class LttngKernelCpuStateProvider extends AbstractTmfStateProvider {
+
+ private static final int VERSION = 1;
+
+ /* For each CPU, maps the last time a thread was scheduled in */
+ private final Map<String, Long> fLastStartTimes = new HashMap<>();
+ private final long fTraceStart;
+
+ /**
+ * Constructor
+ *
+ * @param trace
+ * The trace from which to get the CPU usage
+ */
+ public LttngKernelCpuStateProvider(ITmfTrace trace) {
+ super(trace, ITmfEvent.class, "LTTng Kernel CPU usage"); //$NON-NLS-1$
+ fTraceStart = trace.getStartTime().getValue();
+ }
+
+ // ------------------------------------------------------------------------
+ // ITmfStateProvider
+ // ------------------------------------------------------------------------
+
+ @Override
+ public int getVersion() {
+ return VERSION;
+ }
+
+ @Override
+ public LttngKernelCpuStateProvider getNewInstance() {
+ return new LttngKernelCpuStateProvider(this.getTrace());
+ }
+
+ @Override
+ protected void eventHandle(ITmfEvent event) {
+ final String eventName = event.getType().getName();
+
+ if (eventName.equals(LttngStrings.SCHED_SWITCH)) {
+ /*
+ * Fields: string prev_comm, int32 prev_tid, int32 prev_prio, int64
+ * prev_state, string next_comm, int32 next_tid, int32 next_prio
+ */
+
+ ITmfEventField content = event.getContent();
+ long ts = event.getTimestamp().getValue();
+ String cpu = event.getSource();
+
+ Long prevTid = (Long) content.getField(LttngStrings.PREV_TID).getValue();
+
+ try {
+ Integer currentCPUNode = ss.getQuarkRelativeAndAdd(getNodeCPUs(), cpu);
+
+ /*
+ * This quark contains the value of the cumulative time spent on
+ * the source CPU by the currently running thread
+ */
+ Integer cumulativeTimeQuark = ss.getQuarkRelativeAndAdd(currentCPUNode, prevTid.toString());
+ Long startTime = fLastStartTimes.get(cpu);
+ /*
+ * If start time is null, we haven't seen the start of the
+ * process, so we assume beginning of the trace
+ */
+ if (startTime == null) {
+ startTime = fTraceStart;
+ }
+
+ /*
+ * We add the time from startTime until now to the cumulative
+ * time of the thread
+ */
+ if (startTime != null) {
+ ITmfStateValue value = ss.queryOngoingState(cumulativeTimeQuark);
+
+ /*
+ * Modify cumulative time for this CPU/TID combo: The total
+ * time changes when the process is scheduled out. Nothing
+ * happens when the process is scheduled in.
+ */
+ long prevCumulativeTime = value.unboxLong();
+ long newCumulativeTime = prevCumulativeTime + (ts - startTime);
+
+ value = TmfStateValue.newValueLong(newCumulativeTime);
+ ss.modifyAttribute(ts, value, cumulativeTimeQuark);
+ fLastStartTimes.put(cpu, ts);
+ }
+ } catch (AttributeNotFoundException e) {
+ Activator.getDefault().logError("Attribute not found in LttngKernelCpuStateProvider", e); //$NON-NLS-1$
+ }
+
+ }
+ }
+
+ /* Shortcut for the "current CPU" attribute node */
+ private int getNodeCPUs() {
+ return ss.getQuarkAbsoluteAndAdd(Attributes.CPUS);
+ }
+
+}
diff --git a/lttng/org.eclipse.linuxtools.lttng2.kernel.core/src/org/eclipse/linuxtools/lttng2/kernel/core/cpuusage/LttngKernelCpuUsageAnalysis.java b/lttng/org.eclipse.linuxtools.lttng2.kernel.core/src/org/eclipse/linuxtools/lttng2/kernel/core/cpuusage/LttngKernelCpuUsageAnalysis.java
new file mode 100644
index 0000000..174b5da
--- /dev/null
+++ b/lttng/org.eclipse.linuxtools.lttng2.kernel.core/src/org/eclipse/linuxtools/lttng2/kernel/core/cpuusage/LttngKernelCpuUsageAnalysis.java
@@ -0,0 +1,256 @@
+/*******************************************************************************
+ * Copyright (c) 2014 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Geneviève Bastien - Initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.linuxtools.lttng2.kernel.core.cpuusage;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.linuxtools.internal.lttng2.kernel.core.Activator;
+import org.eclipse.linuxtools.internal.lttng2.kernel.core.Attributes;
+import org.eclipse.linuxtools.lttng2.kernel.core.analysis.LttngKernelAnalysisModule;
+import org.eclipse.linuxtools.tmf.core.exceptions.AttributeNotFoundException;
+import org.eclipse.linuxtools.tmf.core.exceptions.StateSystemDisposedException;
+import org.eclipse.linuxtools.tmf.core.exceptions.StateValueTypeException;
+import org.eclipse.linuxtools.tmf.core.exceptions.TimeRangeException;
+import org.eclipse.linuxtools.tmf.core.interval.ITmfStateInterval;
+import org.eclipse.linuxtools.tmf.core.statesystem.ITmfStateProvider;
+import org.eclipse.linuxtools.tmf.core.statesystem.ITmfStateSystem;
+import org.eclipse.linuxtools.tmf.core.statesystem.TmfStateSystemAnalysisModule;
+
+/**
+ * This analysis module computes the CPU usage of a system from a kernel trace.
+ * It requires the LTTng Kernel analysis module to have accurate CPU usage data.
+ *
+ * @author Geneviève Bastien
+ * @since 3.0
+ */
+public class LttngKernelCpuUsageAnalysis extends TmfStateSystemAnalysisModule {
+
+ /** The ID of this analysis */
+ public static final String ID = "org.eclipse.linuxtools.lttng2.kernel.core.cpuusage"; //$NON-NLS-1$
+
+ /** Text used to identify 'total' entries in the returned maps */
+ public static final String TOTAL = "total"; //$NON-NLS-1$
+ /** String used to separate elements in the returned maps */
+ public static final String SPLIT_STRING = "/"; //$NON-NLS-1$
+ /** Idle process thread ID */
+ public static final String TID_ZERO = "0"; //$NON-NLS-1$
+
+ @Override
+ protected ITmfStateProvider createStateProvider() {
+ return new LttngKernelCpuStateProvider(getTrace());
+ }
+
+ @Override
+ protected StateSystemBackendType getBackendType() {
+ return StateSystemBackendType.FULL;
+ }
+
+ @Override
+ protected boolean executeAnalysis(IProgressMonitor monitor) {
+ /*
+ * This analysis depends on the LTTng kernel analysis, so we'll start
+ * that build at the same time
+ */
+ LttngKernelAnalysisModule module = getTrace().getAnalysisModuleOfClass(LttngKernelAnalysisModule.class, LttngKernelAnalysisModule.ID);
+ if (module != null) {
+ module.schedule();
+ }
+ return super.executeAnalysis(monitor);
+ }
+
+ /**
+ * Get a map of time spent on CPU by various threads during a time range.
+ *
+ * @param start
+ * Start time of requested range
+ * @param end
+ * End time of requested range
+ * @return A map of TID -> time spent on CPU in the [start, end] interval
+ */
+ public Map<String, Long> getCpuUsageInRange(long start, long end) {
+ Map<String, Long> map = new HashMap<>();
+ Map<String, Long> totalMap = new HashMap<>();
+
+ ITmfStateSystem cpuSs = getStateSystem();
+ if (cpuSs == null) {
+ return map;
+ }
+ TmfStateSystemAnalysisModule module = getTrace().getAnalysisModuleOfClass(TmfStateSystemAnalysisModule.class, LttngKernelAnalysisModule.ID);
+ if (module == null) {
+ return map;
+ }
+ module.schedule();
+ module.waitForInitialization();
+ ITmfStateSystem kernelSs = module.getStateSystem();
+ if (kernelSs == null) {
+ return map;
+ }
+
+ /*
+ * Make sure the start/end times are within the state history, so we
+ * don't get TimeRange exceptions.
+ */
+ long startTime = Math.max(start, cpuSs.getStartTime());
+ long endTime = Math.min(end, cpuSs.getCurrentEndTime());
+ long totalTime = 0;
+ if (endTime < startTime) {
+ return map;
+ }
+
+ try {
+ /* Get the list of quarks for each CPU and CPU's TIDs */
+ int cpusNode = cpuSs.getQuarkAbsolute(Attributes.CPUS);
+ Map<Integer, List<Integer>> tidsPerCpu = new HashMap<>();
+ for (int cpuNode : cpuSs.getSubAttributes(cpusNode, false)) {
+ tidsPerCpu.put(cpuNode, cpuSs.getSubAttributes(cpuNode, false));
+ }
+
+ /* Query full states at start and end times */
+ List<ITmfStateInterval> kernelEndState = kernelSs.queryFullState(endTime);
+ List<ITmfStateInterval> endState = cpuSs.queryFullState(endTime);
+ List<ITmfStateInterval> kernelStartState = kernelSs.queryFullState(startTime);
+ List<ITmfStateInterval> startState = cpuSs.queryFullState(startTime);
+
+ long countAtStart, countAtEnd;
+
+ for (Entry<Integer, List<Integer>> entry : tidsPerCpu.entrySet()) {
+ int cpuNode = entry.getKey();
+ List<Integer> tidNodes = entry.getValue();
+
+ String curCpuName = cpuSs.getAttributeName(cpuNode);
+ long cpuTotal = 0;
+
+ /* Get the quark of the thread running on this CPU */
+ int currentThreadQuark = kernelSs.getQuarkAbsolute(Attributes.CPUS, curCpuName, Attributes.CURRENT_THREAD);
+ /* Get the currently running thread on this CPU */
+ int startThread = kernelStartState.get(currentThreadQuark).getStateValue().unboxInt();
+ int endThread = kernelEndState.get(currentThreadQuark).getStateValue().unboxInt();
+
+ for (int tidNode : tidNodes) {
+ String curTidName = cpuSs.getAttributeName(tidNode);
+ int tid = Integer.parseInt(curTidName);
+
+ countAtEnd = endState.get(tidNode).getStateValue().unboxLong();
+ countAtStart = startState.get(tidNode).getStateValue().unboxLong();
+ if (countAtStart == -1) {
+ countAtStart = 0;
+ }
+ if (countAtEnd == -1) {
+ countAtEnd = 0;
+ }
+
+ /*
+ * Interpolate start and end time of threads running at
+ * those times
+ */
+ if (tid == startThread || startThread == -1) {
+ long runningTime = kernelStartState.get(currentThreadQuark).getEndTime() - kernelStartState.get(currentThreadQuark).getStartTime();
+ long runningEnd = kernelStartState.get(currentThreadQuark).getEndTime();
+
+ countAtStart = interpolateCount(countAtStart, startTime, runningEnd, runningTime);
+ }
+ if (tid == endThread) {
+ long runningTime = kernelEndState.get(currentThreadQuark).getEndTime() - kernelEndState.get(currentThreadQuark).getStartTime();
+ long runningEnd = kernelEndState.get(currentThreadQuark).getEndTime();
+
+ countAtEnd = interpolateCount(countAtEnd, endTime, runningEnd, runningTime);
+ }
+ /*
+ * If startThread is -1, we made the hypothesis that the
+ * process running at start was the current one. If the
+ * count is negative, we were wrong in this hypothesis. Also
+ * if the time at end is 0, it either means the process
+ * hasn't been on the CPU or that we still don't know who is
+ * running. In both cases, that invalidates the hypothesis.
+ */
+ if ((startThread == -1) && ((countAtEnd - countAtStart < 0) || (countAtEnd == 0))) {
+ countAtStart = 0;
+ }
+
+ long currentCount = countAtEnd - countAtStart;
+ if (currentCount < 0) {
+ Activator.getDefault().logWarning(String.format("Negative count: start %d, end %d", countAtStart, countAtEnd)); //$NON-NLS-1$
+ currentCount = 0;
+ } else if (currentCount > endTime - startTime) {
+ 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$
+ currentCount = 0;
+ }
+ cpuTotal += currentCount;
+ map.put(curCpuName + SPLIT_STRING + curTidName, currentCount);
+ addToMap(totalMap, curTidName, currentCount);
+ totalTime += (currentCount);
+ }
+ map.put(curCpuName, cpuTotal);
+ }
+
+ /* Add the totals to the map */
+ for (Entry<String, Long> entry : totalMap.entrySet()) {
+ map.put(TOTAL + SPLIT_STRING + entry.getKey(), entry.getValue());
+ }
+ map.put(TOTAL, totalTime);
+
+ } catch (TimeRangeException | AttributeNotFoundException e) {
+ /*
+ * Assume there is no events or the attribute does not exist yet,
+ * nothing will be put in the map.
+ */
+ } catch (StateValueTypeException | StateSystemDisposedException e) {
+ /*
+ * These other exception types would show a logic problem, so they
+ * should not happen.
+ */
+ Activator.getDefault().logError("Error getting CPU usage in a time range", e); //$NON-NLS-1$
+ }
+
+ return map;
+ }
+
+ private static long interpolateCount(long count, long ts, long runningEnd, long runningTime) {
+ long newCount = count;
+
+ /* sanity check */
+ if (runningTime > 0) {
+
+ long runningStart = runningEnd - runningTime;
+
+ if (ts < runningStart) {
+ /*
+ * This interval was not started, this can happen if the current
+ * running thread is unknown and we execute this method. It just
+ * means that this process was not the one running
+ */
+ return newCount;
+ }
+ newCount += (ts - runningStart);
+ }
+ return newCount;
+ }
+
+ /*
+ * Add the value to the previous value in the map. If the key was not set,
+ * assume 0
+ */
+ private static void addToMap(Map<String, Long> map, String key, Long value) {
+ Long addTo = map.get(key);
+ if (addTo == null) {
+ map.put(key, value);
+ } else {
+ map.put(key, addTo + value);
+ }
+ }
+
+}