/******************************************************************************* * Copyright (c) 2016 É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 *******************************************************************************/ package org.eclipse.tracecompass.internal.lttng2.kernel.ui.views.vm.vcpuview; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.core.runtime.IProgressMonitor; 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.kernel.KernelThreadInformationProvider; import org.eclipse.tracecompass.common.core.NonNullUtils; import org.eclipse.tracecompass.internal.lttng2.kernel.core.analysis.vm.VmAttributes; import org.eclipse.tracecompass.internal.lttng2.kernel.core.analysis.vm.module.VirtualMachineCpuAnalysis; import org.eclipse.tracecompass.internal.lttng2.kernel.core.analysis.vm.trace.VirtualMachineExperiment; import org.eclipse.tracecompass.internal.lttng2.kernel.ui.Activator; import org.eclipse.tracecompass.internal.lttng2.kernel.ui.views.vm.vcpuview.VirtualMachineCommon.Type; 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.StateValueTypeException; import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException; import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval; 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.core.trace.experiment.TmfExperimentUtils; import org.eclipse.tracecompass.tmf.ui.views.timegraph.AbstractTimeGraphView; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeEvent; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeGraphEntry; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.NullTimeEvent; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.TimeEvent; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.TimeGraphEntry; import com.google.common.collect.Multimap; /** * Main implementation for the Virtual Machine view * * @author Mohamad Gebai */ public class VirtualMachineView extends AbstractTimeGraphView { /** View ID. */ public static final String ID = "org.eclipse.tracecompass.lttng2.analysis.vm.ui.vmview"; //$NON-NLS-1$ private static final String[] COLUMN_NAMES = new String[] { Messages.VmView_stateTypeName }; private static final String[] FILTER_COLUMN_NAMES = new String[] { Messages.VmView_stateTypeName }; // Timeout between updates in the build thread in ms private static final long BUILD_UPDATE_TIMEOUT = 500; private static final int[] DEFAULT_WEIGHT = { 1, 3 }; private Comparator<ITimeGraphEntry> fComparator = VirtualMachineViewEntry.getComparator(); // ------------------------------------------------------------------------ // Constructors // ------------------------------------------------------------------------ /** * Default constructor */ public VirtualMachineView() { super(ID, new VirtualMachinePresentationProvider()); setFilterColumns(FILTER_COLUMN_NAMES); setTreeColumns(COLUMN_NAMES); setTreeLabelProvider(new VmViewTreeLabelProvider()); setWeight(DEFAULT_WEIGHT); setAutoExpandLevel(2); } @Override protected @Nullable String getNextText() { return Messages.VmView_nextResourceActionNameText; } @Override protected @Nullable String getNextTooltip() { return Messages.VmView_nextResourceActionToolTipText; } @Override protected @Nullable String getPrevText() { return Messages.VmView_previousResourceActionNameText; } @Override protected @Nullable String getPrevTooltip() { return Messages.VmView_previousResourceActionToolTipText; } private static class VmViewTreeLabelProvider extends TreeLabelProvider { @Override public String getColumnText(@Nullable Object element, int columnIndex) { if (!(element instanceof VirtualMachineViewEntry)) { return ""; //$NON-NLS-1$ } VirtualMachineViewEntry entry = (VirtualMachineViewEntry) element; if (COLUMN_NAMES[columnIndex].equals(Messages.VmView_stateTypeName)) { String name = entry.getName(); return (name == null) ? "" : name; //$NON-NLS-1$ } return ""; //$NON-NLS-1$ } } // ------------------------------------------------------------------------ // Internal // ------------------------------------------------------------------------ @Override protected void buildEntryList(ITmfTrace trace, ITmfTrace parentTrace, IProgressMonitor monitor) { setStartTime(Long.MAX_VALUE); setEndTime(Long.MIN_VALUE); if (!(parentTrace instanceof VirtualMachineExperiment)) { return; } ITmfStateSystem ssq = TmfStateSystemAnalysisModule.getStateSystem(parentTrace, VirtualMachineCpuAnalysis.ID); if (ssq == null) { return; } VirtualMachineExperiment vmExperiment = (VirtualMachineExperiment) parentTrace; long startTime = ssq.getStartTime(); ArrayList<VirtualMachineViewEntry> entryList = new ArrayList<>(); Map<String, VirtualMachineViewEntry> entryMap = new HashMap<>(); boolean complete = false; VirtualMachineViewEntry groupEntry = new VirtualMachineViewEntry.VmEntryBuilder(vmExperiment.getName(), startTime, startTime, vmExperiment).build(); entryList.add(groupEntry); putEntryList(parentTrace, new ArrayList<TimeGraphEntry>(entryList)); while (!complete) { if (monitor.isCanceled()) { return; } complete = ssq.waitUntilBuilt(BUILD_UPDATE_TIMEOUT); if (ssq.isCancelled()) { return; } long endTime = ssq.getCurrentEndTime() + 1; groupEntry.updateEndTime(endTime); setStartTime(Math.min(getStartTime(), startTime)); setEndTime(Math.max(getEndTime(), endTime)); /* * Create the entries for the VMs in this experiment and their * respective threads */ buildEntries(ssq, startTime, endTime, groupEntry, entryMap, vmExperiment); if (parentTrace.equals(getTrace())) { refresh(); } /* Build event lists for each entry */ buildEntryEventLists(entryList, ssq, startTime, endTime, monitor); } } private void buildEntries(ITmfStateSystem ssq, long startTime, long endTime, VirtualMachineViewEntry groupEntry, Map<@NonNull String, @NonNull VirtualMachineViewEntry> entryMap, VirtualMachineExperiment vmExperiment) { try { List<Integer> vmQuarks = ssq.getQuarks(VmAttributes.VIRTUAL_MACHINES, "*"); //$NON-NLS-1$ /* For each virtual machine in the analysis */ for (Integer vmQuark : vmQuarks) { /* Display an entry for the virtual machine */ String vmHostId = ssq.getAttributeName(vmQuark); ITmfStateInterval vmNameInterval = StateSystemUtils.queryUntilNonNullValue(ssq, vmQuark, startTime, endTime); String vmName = vmHostId; if (vmNameInterval != null) { vmName = vmNameInterval.getStateValue().unboxStr(); } VirtualMachineViewEntry vmEntry = entryMap.get(vmHostId); if (vmEntry == null) { vmEntry = new VirtualMachineViewEntry.VmEntryBuilder(vmName, startTime, endTime, vmExperiment).setId(vmHostId).setVmName(vmName).setNumericId(vmQuark).setType(Type.VM).build(); vmEntry.sortChildren(fComparator); groupEntry.addChild(vmEntry); entryMap.put(vmHostId, vmEntry); } else { vmEntry.updateEndTime(endTime); } /* Display an entry for each of its CPUs */ for (Integer vCpuQuark : ssq.getSubAttributes(vmQuark, false)) { String vcpuId = ssq.getAttributeName(vCpuQuark); VirtualMachineViewEntry vcpuEntry = entryMap.get(vmHostId + vcpuId); if (vcpuEntry == null) { vcpuEntry = new VirtualMachineViewEntry.VmEntryBuilder(vcpuId, startTime, endTime, vmExperiment).setId(vcpuId).setVmName(vmName).setNumericId(vCpuQuark).setType(Type.VCPU).build(); vmEntry.addChild(vcpuEntry); entryMap.put(vmHostId + vcpuId, vcpuEntry); } else { vcpuEntry.updateEndTime(endTime); } } /* Add the entries for the threads */ buildThreadEntries(vmEntry, entryMap, startTime, endTime); } } catch (TimeRangeException | StateValueTypeException e) { Activator.getDefault().logError("VirtualMachineView: error building event list", e); //$NON-NLS-1$ } } private static void buildThreadEntries(VirtualMachineViewEntry vmEntry, Map<String, VirtualMachineViewEntry> entryMap, long startTime, long endTime) { String vmHostId = vmEntry.getId(); VirtualMachineExperiment vmExperiment = vmEntry.getExperiment(); /* * Get the LTTng Kernel analysis module from the corresponding trace */ KernelAnalysisModule kernelModule = TmfExperimentUtils.getAnalysisModuleOfClassForHost(vmExperiment, vmHostId, KernelAnalysisModule.class); if (kernelModule == null) { return; } VirtualMachineViewEntry threadEntry = entryMap.get(vmHostId + NonNullUtils.nullToEmptyString(Messages.VmView_threads)); if (threadEntry == null) { threadEntry = new VirtualMachineViewEntry.VmEntryBuilder(NonNullUtils.nullToEmptyString(Messages.VmView_threads), startTime, endTime, vmExperiment).build(); entryMap.put(vmHostId + NonNullUtils.nullToEmptyString(Messages.VmView_threads), threadEntry); vmEntry.addChild(threadEntry); } else { threadEntry.updateEndTime(endTime); } String vmName = vmEntry.getVmName(); if (vmName == null) { return; } /* * Display an entry for each thread. * * For each interval that is in a running status, intersect with the * status of the virtual CPU it is currently running on */ Collection<Integer> threadIds = KernelThreadInformationProvider.getThreadIds(kernelModule); for (Integer threadId : threadIds) { if (threadId == -1) { continue; } VirtualMachineViewEntry oneThreadEntry = entryMap.get(vmHostId + ':' + threadId); if (oneThreadEntry != null) { oneThreadEntry.updateEndTime(endTime); continue; } /* * FIXME: Only add threads that are active during the trace */ String threadName = KernelThreadInformationProvider.getExecutableName(kernelModule, threadId); String tidString = threadId.toString(); threadName = (threadName != null) ? tidString + ':' + ' ' + threadName : tidString; oneThreadEntry = new VirtualMachineViewEntry.VmEntryBuilder(threadName, startTime, endTime, vmExperiment).setId(threadName).setVmName(vmName).setNumericId(threadId).setType(Type.THREAD).build(); threadEntry.addChild(oneThreadEntry); entryMap.put(vmHostId + ':' + threadId, oneThreadEntry); } } private void buildEntryEventLists(List<@NonNull VirtualMachineViewEntry> entryList, ITmfStateSystem ssq, long startTime, long endTime, IProgressMonitor monitor) { for (VirtualMachineViewEntry entry : entryList) { if (monitor.isCanceled()) { return; } buildEntryEventList(entry, ssq, startTime, endTime, monitor); } } private void buildEntryEventList(TimeGraphEntry entry, ITmfStateSystem ssq, long start, long end, IProgressMonitor monitor) { if (start < entry.getEndTime() && end > entry.getStartTime()) { long startTime = Math.max(start, entry.getStartTime()); long endTime = Math.min(end + 1, entry.getEndTime()); long resolution = Math.max(1, (end - ssq.getStartTime()) / getDisplayWidth()); List<ITimeEvent> eventList = getEventList(entry, startTime, endTime, resolution, monitor); entry.setEventList(eventList); redraw(); for (TimeGraphEntry child : entry.getChildren()) { if (monitor.isCanceled()) { return; } buildEntryEventList(child, ssq, start, end, monitor); } } } @Override protected @Nullable List<ITimeEvent> getEventList(TimeGraphEntry entry, long startTime, long endTime, long resolution, IProgressMonitor monitor) { if (!(entry instanceof VirtualMachineViewEntry)) { return null; } if (monitor.isCanceled()) { return null; } VirtualMachineViewEntry vmEntry = (VirtualMachineViewEntry) entry; switch (vmEntry.getType()) { case THREAD: { return getThreadEventList(vmEntry, endTime, monitor); } case VCPU: { return getVcpuEventList(vmEntry, startTime, endTime, resolution, monitor); } case VM: { VirtualMachineExperiment experiment = vmEntry.getExperiment(); VirtualMachineCpuAnalysis vmAnalysis = TmfTraceUtils.getAnalysisModuleOfClass(experiment, VirtualMachineCpuAnalysis.class, VirtualMachineCpuAnalysis.ID); if (vmAnalysis == null) { break; } Multimap<Integer, ITmfStateInterval> updatedThreadIntervals = vmAnalysis.getUpdatedThreadIntervals(vmEntry.getNumericId(), startTime, endTime, resolution, monitor); vmEntry.setThreadIntervals(updatedThreadIntervals); } break; case NULL: /* These entry types are not used in this view */ break; default: break; } return null; } private static @Nullable List<@NonNull ITimeEvent> getVcpuEventList(VirtualMachineViewEntry vmEntry, long startTime, long endTime, long resolution, IProgressMonitor monitor) { List<ITimeEvent> eventList = null; try { int quark = vmEntry.getNumericId(); ITmfStateSystem ssq = TmfStateSystemAnalysisModule.getStateSystem(vmEntry.getExperiment(), VirtualMachineCpuAnalysis.ID); if (ssq == null) { return Collections.EMPTY_LIST; } final long realStart = Math.max(startTime, ssq.getStartTime()); final long realEnd = Math.min(endTime, ssq.getCurrentEndTime() + 1); if (realEnd <= realStart) { return Collections.EMPTY_LIST; } quark = ssq.getQuarkRelative(quark, VmAttributes.STATUS); List<ITmfStateInterval> statusIntervals = StateSystemUtils.queryHistoryRange(ssq, quark, realStart, realEnd - 1, resolution, monitor); eventList = parseIntervalsForEvents(vmEntry, statusIntervals, endTime, monitor); } catch (AttributeNotFoundException | TimeRangeException | StateValueTypeException e) { Activator.getDefault().logError("Error getting event list", e); //$NON-NLS-1$ } catch (StateSystemDisposedException e) { /* Ignored */ } return eventList; } private static @Nullable List<@NonNull ITimeEvent> getThreadEventList(VirtualMachineViewEntry vmEntry, long endTime, IProgressMonitor monitor) { List<ITimeEvent> eventList = null; Collection<ITmfStateInterval> threadIntervals = getThreadIntervalsForEntry(vmEntry); if (threadIntervals != null) { /* * FIXME: I think the key for the alpha bug when alpha overlaps * multiple events is around here * * Hint by Patrick: "The problem is that the thread intervals * are sorted by start time, and drawn in that order. * * Given the intervals: Blue [0,10] Alpha [5,15] Red [10,20] * * Blue is drawn, then Alpha makes DarkBlue from [5,10] and * DarkBackground from [10,15], then Red is drawn over [10,20], * overwriting the DarkBackground. There is no DarkRed. * * For this to work you would have to draw all real states * first, then all alpha states second. * * I think this would also have the side-effect that the find * item used for tool tips would always find the real event and * never the alpha event. This might be what we want. Right now * the tool tip has State: (multiple). * * But using the Next Event button, we would skip to the next * real event and not at the preemption event. Maybe not what we * want. * * Maybe what we need is separate thread interval events: * * Blue [0,5] Preempted Blue [5,10] Preempted Red [10,15] Red * [15,20]... * * The preempted events would have the real state value, but * with a flag for alpha to be used in the postDrawEvent." */ eventList = parseIntervalsForEvents(vmEntry, threadIntervals, endTime, monitor); } return eventList; } private static @Nullable List<@NonNull ITimeEvent> parseIntervalsForEvents(VirtualMachineViewEntry vmEntry, Collection<@NonNull ITmfStateInterval> intervals, long endTime, IProgressMonitor monitor) { List<ITimeEvent> eventList = new ArrayList<>(intervals.size()); long lastEndTime = -1; for (ITmfStateInterval interval : intervals) { if (monitor.isCanceled()) { return null; } long time = interval.getStartTime(); long duration = interval.getEndTime() - time + 1; if (!interval.getStateValue().isNull()) { int status = interval.getStateValue().unboxInt(); if (lastEndTime != time && lastEndTime != -1) { eventList.add(new TimeEvent(vmEntry, lastEndTime, time - lastEndTime)); } eventList.add(new TimeEvent(vmEntry, time, duration, status)); } else if (lastEndTime == -1 || time + duration >= endTime) { /* add null event if it intersects the start or end time */ eventList.add(new NullTimeEvent(vmEntry, time, duration)); } lastEndTime = time + duration; } return eventList; } private static @Nullable Collection<@NonNull ITmfStateInterval> getThreadIntervalsForEntry(VirtualMachineViewEntry vmEntry) { Collection<ITmfStateInterval> threadIntervals = null; /* * The parent VM entry will contain the thread intervals for all * threads. Just take the list from there */ TimeGraphEntry parent = vmEntry.getParent(); while (threadIntervals == null && parent != null) { if (parent instanceof VirtualMachineViewEntry) { threadIntervals = ((VirtualMachineViewEntry) parent).getThreadIntervals(vmEntry.getNumericId()); } parent = parent.getParent(); } return threadIntervals; } @Override protected Iterable<ITmfTrace> getTracesToBuild(@Nullable ITmfTrace trace) { if (trace == null) { return Collections.EMPTY_SET; } return Collections.singleton(trace); } }