/******************************************************************************* * Copyright (c) 2014, 2015 École Polytechnique de Montréal and others. * * 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.tracecompass.analysis.os.linux.core.cpuusage; import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.tracecompass.analysis.os.linux.core.kernel.KernelAnalysisModule; import org.eclipse.tracecompass.analysis.os.linux.core.trace.DefaultEventLayout; import org.eclipse.tracecompass.analysis.os.linux.core.trace.IKernelAnalysisEventLayout; import org.eclipse.tracecompass.analysis.os.linux.core.trace.IKernelTrace; import org.eclipse.tracecompass.analysis.os.linux.core.trace.KernelEventLayoutRequirement; import org.eclipse.tracecompass.common.core.NonNullUtils; import org.eclipse.tracecompass.internal.analysis.os.linux.core.Activator; import org.eclipse.tracecompass.internal.analysis.os.linux.core.kernel.Attributes; import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem; import org.eclipse.tracecompass.statesystem.core.exceptions.AttributeNotFoundException; import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException; import org.eclipse.tracecompass.statesystem.core.exceptions.StateValueTypeException; import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException; import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval; import org.eclipse.tracecompass.tmf.core.analysis.IAnalysisModule; import org.eclipse.tracecompass.tmf.core.analysis.requirements.TmfAbstractAnalysisRequirement; import org.eclipse.tracecompass.tmf.core.analysis.requirements.TmfAbstractAnalysisRequirement.PriorityLevel; import org.eclipse.tracecompass.tmf.core.statesystem.ITmfStateProvider; import org.eclipse.tracecompass.tmf.core.statesystem.TmfStateSystemAnalysisModule; import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace; import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.SetMultimap; /** * 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 */ public class KernelCpuUsageAnalysis extends TmfStateSystemAnalysisModule { /** The ID of this analysis */ public static final String ID = "org.eclipse.tracecompass.analysis.os.linux.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$ /** The requirements as an immutable set */ private static final KernelEventLayoutRequirement LAYOUT_REQUIREMENT; private static final SetMultimap<IKernelAnalysisEventLayout, TmfAbstractAnalysisRequirement> LAYOUT_REQ_MAP = NonNullUtils.checkNotNull(HashMultimap.create()); static { LAYOUT_REQUIREMENT = new KernelEventLayoutRequirement(ImmutableSet.of((l) -> l.eventSchedSwitch()), PriorityLevel.MANDATORY); } private static IKernelAnalysisEventLayout getLayout(@Nullable ITmfTrace trace) { IKernelAnalysisEventLayout layout; if (trace instanceof IKernelTrace) { layout = ((IKernelTrace) trace).getKernelEventLayout(); } else { /* Fall-back to the base LttngEventLayout */ layout = DefaultEventLayout.getInstance(); } return layout; } @Override protected ITmfStateProvider createStateProvider() { ITmfTrace trace = checkNotNull(getTrace()); IKernelAnalysisEventLayout layout = getLayout(trace); return new KernelCpuUsageStateProvider(trace, layout); } @Override protected StateSystemBackendType getBackendType() { return StateSystemBackendType.FULL; } @Override protected Iterable<IAnalysisModule> getDependentAnalyses() { Set<IAnalysisModule> modules = new HashSet<>(); ITmfTrace trace = getTrace(); if (trace == null) { throw new IllegalStateException("Analysis requires a trace"); //$NON-NLS-1$ } /* * This analysis depends on the LTTng kernel analysis, so it's added to * dependent modules. */ Iterable<KernelAnalysisModule> kernelModules = TmfTraceUtils.getAnalysisModulesOfClass(trace, KernelAnalysisModule.class); for (KernelAnalysisModule kernelModule : kernelModules) { /* Only add the first one we find, if there is one */ modules.add(kernelModule); break; } return modules; } /** * Gets the maximum number of cores detected * * @return the number of cores * @since 2.0 */ public int getNumberOfCores() { ITmfStateSystem cpuSs = getStateSystem(); if (cpuSs != null) { try { int cpusNode = cpuSs.getQuarkAbsolute(Attributes.CPUS); final @NonNull List<@NonNull Integer> subAttributes = cpuSs.getSubAttributes(cpusNode, false); int cpus = Integer.MIN_VALUE; for (Integer quark : subAttributes) { cpus = Math.max(Integer.parseInt(cpuSs.getAttributeName(quark)), cpus); } return Math.max(subAttributes.size(), cpus); } catch (AttributeNotFoundException e) { Activator.getDefault().logError(e.getMessage(), e); } } return -1; } /** * Get a map of time spent on CPU by various threads during a time range. * * @param cpus * A set of the desired CPUs to get. An empty set gets all the * cores * @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 * @since 2.0 */ public Map<String, Long> getCpuUsageInRange(Set<@NonNull Integer> cpus, long start, long end) { Map<String, Long> map = new HashMap<>(); Map<String, Long> totalMap = new HashMap<>(); ITmfTrace trace = getTrace(); ITmfStateSystem cpuSs = getStateSystem(); if (trace == null || cpuSs == null) { return map; } ITmfStateSystem kernelSs = TmfStateSystemAnalysisModule.getStateSystem(trace, KernelAnalysisModule.ID); 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()); startTime = Math.max(startTime, kernelSs.getStartTime()); long endTime = Math.min(end, cpuSs.getCurrentEndTime()); endTime = Math.min(endTime, kernelSs.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)) { final @NonNull List<@NonNull Integer> cpuSubAttributes = cpuSs.getSubAttributes(cpuNode, false); if (cpus.isEmpty() || cpus.contains(Integer.parseInt(cpuSs.getAttributeName(cpuNode)))) { tidsPerCpu.put(cpuNode, cpuSubAttributes); } } /* 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); } } @Override public Iterable<TmfAbstractAnalysisRequirement> getAnalysisRequirements() { ITmfTrace trace = getTrace(); IKernelAnalysisEventLayout layout = getLayout(trace); Set<TmfAbstractAnalysisRequirement> reqs = LAYOUT_REQ_MAP.get(layout); if (reqs.isEmpty()) { reqs= ImmutableSet.of(LAYOUT_REQUIREMENT.instanciateRequirements(layout)); LAYOUT_REQ_MAP.putAll(layout, reqs); } return reqs; } }