/******************************************************************************* * Copyright (c) 2014, 2017 É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.tracecompass.internal.analysis.os.linux.ui.views.cpuusage; import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeSet; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerComparator; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.widgets.Composite; import org.eclipse.tracecompass.analysis.os.linux.core.cpuusage.KernelCpuUsageAnalysis; import org.eclipse.tracecompass.analysis.os.linux.core.kernel.KernelAnalysisModule; import org.eclipse.tracecompass.internal.analysis.os.linux.core.kernel.Attributes; import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem; import org.eclipse.tracecompass.statesystem.core.StateSystemUtils; import org.eclipse.tracecompass.statesystem.core.exceptions.AttributeNotFoundException; import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException; import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException; import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval; import org.eclipse.tracecompass.statesystem.core.statevalue.ITmfStateValue; import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler; import org.eclipse.tracecompass.tmf.core.signal.TmfTraceOpenedSignal; import org.eclipse.tracecompass.tmf.core.signal.TmfTraceSelectedSignal; import org.eclipse.tracecompass.tmf.core.statesystem.TmfStateSystemAnalysisModule; import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace; import org.eclipse.tracecompass.tmf.core.trace.TmfTraceContext; import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager; import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils; import org.eclipse.tracecompass.tmf.ui.viewers.tree.AbstractTmfTreeViewer; import org.eclipse.tracecompass.tmf.ui.viewers.tree.ITmfTreeColumnDataProvider; import org.eclipse.tracecompass.tmf.ui.viewers.tree.ITmfTreeViewerEntry; import org.eclipse.tracecompass.tmf.ui.viewers.tree.TmfTreeColumnData; import org.eclipse.tracecompass.tmf.ui.viewers.tree.TmfTreeColumnData.ITmfColumnPercentageProvider; import org.eclipse.tracecompass.tmf.ui.viewers.tree.TmfTreeViewerEntry; /** * Tree viewer to display CPU usage information in a specified time range. It * shows the process's TID, its name, the time spent on the CPU during that * range, in % and absolute value. * * @author Geneviève Bastien */ public class CpuUsageComposite extends AbstractTmfTreeViewer { // Timeout between to wait for in the updateElements method private static final long BUILD_UPDATE_TIMEOUT = 500; private KernelCpuUsageAnalysis fModule = null; private String fSelectedThread = null; private static final String[] COLUMN_NAMES = new String[] { Messages.CpuUsageComposite_ColumnTID, Messages.CpuUsageComposite_ColumnProcess, Messages.CpuUsageComposite_ColumnPercent, Messages.CpuUsageComposite_ColumnTime }; /* A map that saves the mapping of a thread ID to its executable name */ private final Map<String, String> fProcessNameMap = new HashMap<>(); private final @NonNull Set<@NonNull Integer> fCpus = new TreeSet<>(); /** Provides label for the CPU usage tree viewer cells */ protected static class CpuLabelProvider extends TreeLabelProvider { @Override public String getColumnText(Object element, int columnIndex) { CpuUsageEntry obj = (CpuUsageEntry) element; if (columnIndex == 0) { return obj.getTid(); } else if (columnIndex == 1) { return obj.getProcessName(); } else if (columnIndex == 2) { return String.format(Messages.CpuUsageComposite_TextPercent, obj.getPercent()); } else if (columnIndex == 3) { return NLS.bind(Messages.CpuUsageComposite_TextTime, obj.getTime()); } return element.toString(); } } /** * Constructor * * @param parent * The parent composite that holds this viewer */ public CpuUsageComposite(Composite parent) { super(parent, false); setLabelProvider(new CpuLabelProvider()); } @Override protected ITmfTreeColumnDataProvider getColumnDataProvider() { return new ITmfTreeColumnDataProvider() { @Override public List<TmfTreeColumnData> getColumnData() { /* All columns are sortable */ List<TmfTreeColumnData> columns = new ArrayList<>(); TmfTreeColumnData column = new TmfTreeColumnData(COLUMN_NAMES[0]); column.setComparator(new ViewerComparator() { @Override public int compare(Viewer viewer, Object e1, Object e2) { CpuUsageEntry n1 = (CpuUsageEntry) e1; CpuUsageEntry n2 = (CpuUsageEntry) e2; return n1.getTid().compareTo(n2.getTid()); } }); columns.add(column); column = new TmfTreeColumnData(COLUMN_NAMES[1]); column.setComparator(new ViewerComparator() { @Override public int compare(Viewer viewer, Object e1, Object e2) { CpuUsageEntry n1 = (CpuUsageEntry) e1; CpuUsageEntry n2 = (CpuUsageEntry) e2; return n1.getProcessName().compareTo(n2.getProcessName()); } }); columns.add(column); column = new TmfTreeColumnData(COLUMN_NAMES[2]); column.setComparator(new ViewerComparator() { @Override public int compare(Viewer viewer, Object e1, Object e2) { CpuUsageEntry n1 = (CpuUsageEntry) e1; CpuUsageEntry n2 = (CpuUsageEntry) e2; return n1.getPercent().compareTo(n2.getPercent()); } }); column.setPercentageProvider(new ITmfColumnPercentageProvider() { @Override public double getPercentage(Object data) { CpuUsageEntry parent = (CpuUsageEntry) data; return parent.getPercent() / 100; } }); columns.add(column); column = new TmfTreeColumnData(COLUMN_NAMES[3]); column.setComparator(new ViewerComparator() { @Override public int compare(Viewer viewer, Object e1, Object e2) { CpuUsageEntry n1 = (CpuUsageEntry) e1; CpuUsageEntry n2 = (CpuUsageEntry) e2; return n1.getTime().compareTo(n2.getTime()); } }); columns.add(column); return columns; } }; } @Override protected ITmfTrace getTrace() { return super.getTrace(); } // ------------------------------------------------------------------------ // Operations // ------------------------------------------------------------------------ @Override protected void contentChanged(ITmfTreeViewerEntry rootEntry) { String selectedThread = fSelectedThread; if (selectedThread != null) { /* Find the selected thread among the inputs */ for (ITmfTreeViewerEntry entry : rootEntry.getChildren()) { if (entry instanceof CpuUsageEntry) { if (selectedThread.equals(((CpuUsageEntry) entry).getTid())) { List<ITmfTreeViewerEntry> list = Collections.singletonList(entry); super.setSelection(list); return; } } } } } @Override public void initializeDataSource() { /* Should not be called while trace is still null */ ITmfTrace trace = checkNotNull(getTrace()); fModule = TmfTraceUtils.getAnalysisModuleOfClass(trace, KernelCpuUsageAnalysis.class, KernelCpuUsageAnalysis.ID); if (fModule == null) { return; } fModule.schedule(); fModule.waitForInitialization(); fProcessNameMap.clear(); } @Override protected ITmfTreeViewerEntry updateElements(long start, long end, boolean isSelection) { if (isSelection || (start == end)) { return null; } if (getTrace() == null || fModule == null) { return null; } fModule.waitForInitialization(); ITmfStateSystem ss = fModule.getStateSystem(); if (ss == null) { return null; } boolean complete = false; long currentEnd = Math.max(start, ss.getStartTime()); while (!complete && currentEnd < end) { complete = ss.waitUntilBuilt(BUILD_UPDATE_TIMEOUT); currentEnd = ss.getCurrentEndTime(); } /* Initialize the data */ Map<String, Long> cpuUsageMap = fModule.getCpuUsageInRange(fCpus, Math.max(start, getStartTime()), Math.min(end, getEndTime())); TmfTreeViewerEntry root = new TmfTreeViewerEntry(""); //$NON-NLS-1$ List<ITmfTreeViewerEntry> entryList = root.getChildren(); for (Entry<String, Long> entry : cpuUsageMap.entrySet()) { /* * Process only entries representing the total of all CPUs and that * have time on CPU */ if (entry.getValue() == 0) { continue; } if (!entry.getKey().startsWith(KernelCpuUsageAnalysis.TOTAL)) { continue; } String[] strings = entry.getKey().split(KernelCpuUsageAnalysis.SPLIT_STRING, 2); if ((strings.length > 1) && !(strings[1].equals(KernelCpuUsageAnalysis.TID_ZERO))) { CpuUsageEntry obj = new CpuUsageEntry(strings[1], getProcessName(strings[1]), (double) entry.getValue() / (double) (end - start) * 100, entry.getValue()); entryList.add(obj); } } return root; } /* * Get the process name from its TID by using the LTTng kernel analysis * module */ private String getProcessName(String tid) { String execName = fProcessNameMap.get(tid); if (execName != null) { return execName; } ITmfTrace trace = getTrace(); if (trace == null) { return tid; } ITmfStateSystem kernelSs = TmfStateSystemAnalysisModule.getStateSystem(trace, KernelAnalysisModule.ID); if (kernelSs == null) { return tid; } try { int cpusNode = kernelSs.getQuarkAbsolute(Attributes.THREADS); /* Get the quarks for each cpu */ List<Integer> cpuNodes = kernelSs.getSubAttributes(cpusNode, false); for (Integer tidQuark : cpuNodes) { if (kernelSs.getAttributeName(tidQuark).equals(tid)) { int execNameQuark; List<ITmfStateInterval> execNameIntervals; try { execNameQuark = kernelSs.getQuarkRelative(tidQuark, Attributes.EXEC_NAME); execNameIntervals = StateSystemUtils.queryHistoryRange(kernelSs, execNameQuark, getStartTime(), getEndTime()); } catch (TimeRangeException | AttributeNotFoundException e) { /* * No information on this thread (yet?), skip it for now */ continue; } catch (StateSystemDisposedException e) { /* State system is closing down, no point continuing */ break; } for (ITmfStateInterval execNameInterval : execNameIntervals) { if (!execNameInterval.getStateValue().isNull() && execNameInterval.getStateValue().getType() == ITmfStateValue.Type.STRING) { execName = execNameInterval.getStateValue().unboxStr(); fProcessNameMap.put(tid, execName); return execName; } } } } } catch (AttributeNotFoundException e) { /* can't find the process name, just return the tid instead */ } return tid; } /** * Set the currently selected thread ID * * @param tid * The selected thread ID */ public void setSelectedThread(String tid) { fSelectedThread = tid; } /** * Add a core * * @param core * the core to add * @since 2.0 */ public void addCpu(int core) { fCpus.add(core); updateContent(getWindowStartTime(), getWindowEndTime(), false); } /** * Remove a core * * @param core * the core to remove * @since 2.0 */ public void removeCpu(int core) { fCpus.remove(core); updateContent(getWindowStartTime(), getWindowEndTime(), false); } /** * Clears the cores * * @since 2.0 * */ public void clearCpu() { fCpus.clear(); updateContent(getWindowStartTime(), getWindowEndTime(), false); } @Override @TmfSignalHandler public void traceSelected(TmfTraceSelectedSignal signal) { initSelection(); initCPU(); super.traceSelected(signal); } @Override @TmfSignalHandler public void traceOpened(TmfTraceOpenedSignal signal) { initSelection(); initCPU(); super.traceOpened(signal); } private void initSelection() { TmfTraceContext ctx = TmfTraceManager.getInstance().getCurrentTraceContext(); String thread = (String) ctx.getData(CpuUsageView.CPU_USAGE_SELECTED_THREAD); setSelectedThread(thread); } private void initCPU() { clearCpu(); TmfTraceContext ctx = TmfTraceManager.getInstance().getCurrentTraceContext(); Object data = ctx.getData(CpuUsageView.CPU_USAGE_FOLLOW_CPU); if (data instanceof Set<?>) { Set<?> set = (Set<?>) data; for (Object coreObject : set) { if (coreObject instanceof Integer) { Integer core = (Integer) coreObject; if (core >= 0) { addCpu(core); } } } } } }