| /******************************************************************************* |
| * Copyright (c) 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 2.0 which |
| * accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| *******************************************************************************/ |
| |
| package org.eclipse.tracecompass.incubator.internal.perf.profiling.core.callgraph; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Optional; |
| |
| import org.eclipse.jdt.annotation.NonNull; |
| import org.eclipse.jdt.annotation.Nullable; |
| import org.eclipse.tracecompass.incubator.analysis.core.concepts.AggregatedCallSite; |
| import org.eclipse.tracecompass.incubator.analysis.core.concepts.ISamplingDataProvider; |
| import org.eclipse.tracecompass.incubator.analysis.core.weighted.tree.IWeightedTreeGroupDescriptor; |
| import org.eclipse.tracecompass.incubator.callstack.core.base.CallStackElement; |
| import org.eclipse.tracecompass.incubator.callstack.core.base.CallStackGroupDescriptor; |
| import org.eclipse.tracecompass.incubator.callstack.core.base.ICallStackElement; |
| import org.eclipse.tracecompass.incubator.callstack.core.sampled.callgraph.ProfilingCallGraphAnalysisModule; |
| import org.eclipse.tracecompass.tmf.core.event.ITmfEvent; |
| import org.eclipse.tracecompass.tmf.core.event.ITmfEventField; |
| import org.eclipse.tracecompass.tmf.core.event.TmfEvent; |
| import org.eclipse.tracecompass.tmf.core.request.ITmfEventRequest; |
| import org.eclipse.tracecompass.tmf.core.request.TmfEventRequest; |
| import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange; |
| import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp; |
| import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace; |
| import org.eclipse.tracecompass.tmf.core.trace.experiment.TmfExperiment; |
| import org.eclipse.tracecompass.tmf.core.util.Pair; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| |
| /** |
| * An analysis module for the sampled callchains from a perf trace. It |
| * aggregates the data from the sampling events per pid/tid. |
| * |
| * @author Geneviève Bastien |
| */ |
| public class PerfCallchainAnalysisModule extends ProfilingCallGraphAnalysisModule implements ISamplingDataProvider { |
| |
| /** |
| * ID of this analysis |
| */ |
| public static final String ID = "org.eclipse.tracecompass.extension.perf.profiling.core.callchain"; //$NON-NLS-1$ |
| private static final String EVENT_SAMPLING = "cycles"; //$NON-NLS-1$ |
| private static final String FIELD_PERF_CALLCHAIN = "perf_callchain"; //$NON-NLS-1$ |
| private static final String FIELD_PERF_PID = "perf_pid"; //$NON-NLS-1$ |
| private static final String FIELD_PERF_TID = "perf_tid"; //$NON-NLS-1$ |
| |
| private final CallStackGroupDescriptor fThreadDescriptor; |
| private final CallStackGroupDescriptor fProcessDescriptor; |
| // private final ProfilingGroup fGroupNode = new ProfilingGroup("Data", |
| // CallGraphAllGroupDescriptor.getInstance()); |
| |
| /** |
| * Constructor |
| */ |
| public PerfCallchainAnalysisModule() { |
| // Create group descriptors |
| fThreadDescriptor = new CallStackGroupDescriptor("Threads", null, false); //$NON-NLS-1$ |
| fProcessDescriptor = new CallStackGroupDescriptor("Process", fThreadDescriptor, true); //$NON-NLS-1$ |
| } |
| |
| @Override |
| protected @Nullable Pair<ICallStackElement, AggregatedCallSite> getProfiledStackTrace(@NonNull ITmfEvent event) { |
| if (!event.getName().startsWith(EVENT_SAMPLING)) { |
| return null; |
| } |
| // Get the callchain if available |
| ITmfEventField field = event.getContent().getField(FIELD_PERF_CALLCHAIN); |
| if (field == null) { |
| return null; |
| } |
| long[] value = (long[]) field.getValue(); |
| int size = value.length; |
| long tmp; |
| // Reverse the stack so that element at position 0 is the bottom |
| for (int i = 0, mid = size >> 1, j = size - 1; i < mid; i++, j--) { |
| tmp = value[i]; |
| value[i] = value[j]; |
| value[j] = tmp; |
| } |
| ICallStackElement element = getElement(event); |
| return new Pair<>(element, getCallSite(element, value, event.getTimestamp().getValue())); |
| } |
| |
| /** |
| * @param event |
| */ |
| private ICallStackElement getElement(ITmfEvent event) { |
| // Find a root elements with the same PID |
| Collection<ICallStackElement> rootElements = getRootElements(); |
| Long pidField = event.getContent().getFieldValue(Long.class, FIELD_PERF_PID); |
| Long pid = pidField == null ? -1 : pidField; |
| Long tidField = event.getContent().getFieldValue(Long.class, FIELD_PERF_TID); |
| Long tid = tidField == null ? -1 : tidField; |
| Optional<ICallStackElement> process = rootElements.stream() |
| .filter(e -> e.getName().equals(String.valueOf(pid))) |
| .findFirst(); |
| if (!process.isPresent()) { |
| // Process is null, create both process and thread elements and return |
| ICallStackElement processEl = new CallStackElement(String.valueOf(pid), fProcessDescriptor, fThreadDescriptor, null) { |
| |
| @Override |
| protected int retrieveSymbolKeyAt(long time) { |
| return pid.intValue(); |
| } |
| |
| }; |
| ICallStackElement threadEl = new CallStackElement(String.valueOf(tid), fThreadDescriptor, null, processEl); |
| processEl.setSymbolKeyElement(processEl); |
| threadEl.setSymbolKeyElement(processEl); |
| processEl.addChild(threadEl); |
| addRootElement(processEl); |
| return threadEl; |
| } |
| ICallStackElement processEl = process.get(); |
| |
| // Process exists, find a thread element under it or create it |
| Optional<ICallStackElement> thread = processEl.getChildrenElements().stream() |
| .filter(e -> e.getName().equals(String.valueOf(tid))) |
| .findFirst(); |
| |
| if (thread.isPresent()) { |
| return thread.get(); |
| } |
| ICallStackElement threadEl = new CallStackElement(String.valueOf(tid), fThreadDescriptor, null, processEl); |
| processEl.addChild(threadEl); |
| return threadEl; |
| |
| } |
| |
| @Override |
| public Collection<IWeightedTreeGroupDescriptor> getGroupDescriptors() { |
| return ImmutableList.of(fProcessDescriptor); |
| } |
| |
| @Override |
| public Map<String, Collection<Object>> getCallStack(@NonNull ITmfEvent event) { |
| ITmfEventField field = event.getContent().getField(FIELD_PERF_CALLCHAIN); |
| if (field == null) { |
| return Collections.emptyMap(); |
| } |
| Object value = field.getValue(); |
| if (!(value instanceof long[])) { |
| return Collections.emptyMap(); |
| } |
| long[] callstack = (long[]) value; |
| List<Object> longList = new ArrayList<>(); |
| for (long callsite : callstack) { |
| longList.add(callsite); |
| } |
| Collections.reverse(longList); |
| return ImmutableMap.of("Callchain", longList); //$NON-NLS-1$ |
| |
| } |
| |
| @Override |
| public Collection<AggregatedCallSite> getSamplingData(int tid, long start, long end) { |
| ITmfTrace trace = getTrace(); |
| if (trace == null) { |
| return Collections.emptyList(); |
| } |
| List<AggregatedCallSite> callsites = new ArrayList<>(); |
| TmfEventRequest request = new PerfProfilingEventRequest(trace, start, end, tid, callsites); |
| trace.sendRequest(request); |
| try { |
| request.waitForCompletion(); |
| } catch (InterruptedException e) { |
| |
| } |
| return callsites; |
| } |
| |
| @Override |
| public Collection<String> getHostIds() { |
| ITmfTrace trace = getTrace(); |
| if (trace == null) { |
| return Collections.emptySet(); |
| } |
| return Collections.singleton(trace.getHostId()); |
| } |
| |
| private class PerfProfilingEventRequest extends TmfEventRequest { |
| |
| private final int fTid; |
| private final ITmfTrace fTrace; |
| private final List<AggregatedCallSite> fSites; |
| |
| /** |
| * Constructor |
| * |
| * @param trace |
| * The trace |
| * @param start |
| * The start time of the request |
| * @param end |
| * The end time of the request |
| * @param tid |
| * The tid for which to get the samples |
| * @param callsites |
| * A list of callsites to fill |
| */ |
| public PerfProfilingEventRequest(ITmfTrace trace, long start, long end, int tid, List<AggregatedCallSite> callsites) { |
| super(TmfEvent.class, |
| new TmfTimeRange(TmfTimestamp.fromNanos(start), TmfTimestamp.fromNanos(end)), |
| 0, |
| ITmfEventRequest.ALL_DATA, |
| ITmfEventRequest.ExecutionType.BACKGROUND); |
| fTid = tid; |
| fTrace = trace; |
| fSites = callsites; |
| } |
| |
| @Override |
| public void handleData(final ITmfEvent event) { |
| super.handleData(event); |
| if (event.getTrace() == fTrace) { |
| handleEvent(event); |
| } else if (fTrace instanceof TmfExperiment) { |
| /* |
| * If the request is for an experiment, check if the event is |
| * from one of the child trace |
| */ |
| for (ITmfTrace childTrace : ((TmfExperiment) fTrace).getTraces()) { |
| if (childTrace == event.getTrace()) { |
| handleEvent(event); |
| } |
| } |
| } |
| } |
| |
| private void handleEvent(ITmfEvent event) { |
| Long tidField = event.getContent().getFieldValue(Long.class, FIELD_PERF_TID); |
| Long tid = tidField == null ? -1 : tidField; |
| if (tid.intValue() != fTid) { |
| return; |
| } |
| Pair<ICallStackElement, AggregatedCallSite> stackTrace = getProfiledStackTrace(event); |
| if (stackTrace == null) { |
| return; |
| } |
| AggregatedCallSite perfCallSite = stackTrace.getSecond(); |
| for (AggregatedCallSite site : fSites) { |
| if (site.getObject().equals(perfCallSite.getObject())) { |
| site.merge(perfCallSite); |
| return; |
| } |
| } |
| fSites.add(perfCallSite); |
| } |
| } |
| |
| } |