+/*******************************************************************************
+ * Copyright (c) 2014, 2015 É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.interval.ITmfStateInterval;
+import org.eclipse.tracecompass.statesystem.core.statevalue.ITmfStateValue;
+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 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;
+ }
+
+ };
+ }
+
+ // ------------------------------------------------------------------------
+ // 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 = start;
+
+ 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 (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);
+ }
+
+}