org.eclipse.tracecompass.lttng2.lttng.kernel.core.tests.shared.vm
Import-Package: com.google.common.collect,
org.eclipse.test.performance,
- org.eclipse.tracecompass.testtraces.ctf
+ org.eclipse.tracecompass.testtraces.ctf;version="1.6.0"
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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.lttng2.kernel.core.tests.synchronization;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.tracecompass.analysis.os.linux.core.kernel.KernelAnalysisModule;
+import org.eclipse.tracecompass.analysis.os.linux.core.kernel.KernelThreadInformationProvider;
+import org.eclipse.tracecompass.lttng2.kernel.core.trace.LttngKernelTrace;
+import org.eclipse.tracecompass.testtraces.ctf.CtfTestTrace;
+import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
+import org.eclipse.tracecompass.tmf.core.exceptions.TmfTraceException;
+import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
+import org.eclipse.tracecompass.tmf.core.signal.TmfTraceOpenedSignal;
+import org.eclipse.tracecompass.tmf.core.signal.TmfTraceSelectedSignal;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfContext;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils;
+import org.eclipse.tracecompass.tmf.core.trace.experiment.TmfExperiment;
+import org.eclipse.tracecompass.tmf.ctf.core.event.CtfTmfEvent;
+import org.eclipse.tracecompass.tmf.ctf.core.tests.shared.CtfTmfTestTraceUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.rules.Timeout;
+
+/**
+ * Test that synchronization between LTTng UST and kernel traces is done
+ * correctly.
+ *
+ * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=484620
+ *
+ * @author Alexandre Montplaisir
+ */
+public class UstKernelSyncTest {
+
+ /** Time-out tests after 60 seconds */
+ @Rule public TestRule globalTimeout= new Timeout(60, TimeUnit.SECONDS);
+
+ private static final @NonNull CtfTestTrace KERNEL_TRACE = CtfTestTrace.CONTEXT_SWITCHES_KERNEL;
+ private static final @NonNull CtfTestTrace UST_TRACE = CtfTestTrace.CONTEXT_SWITCHES_UST;
+
+ private TmfExperiment fExperiment;
+ private ITmfTrace fKernelTrace;
+ private ITmfTrace fUstTrace;
+ private KernelAnalysisModule fKernelModule;
+
+ /**
+ * Test setup
+ */
+ @Before
+ public void setup() {
+ ITmfTrace ustTrace = CtfTmfTestTraceUtils.getTrace(UST_TRACE);
+
+ /*
+ * We need to initialize the kernel trace to the "LttngKernelTrace"
+ * type ourselves, so the kernel analysis can run on it.
+ */
+ String kernelTracePath = CtfTmfTestTraceUtils.getTrace(KERNEL_TRACE).getPath();
+ ITmfTrace kernelTrace = new LttngKernelTrace();
+
+ try {
+ kernelTrace.initTrace(null, kernelTracePath, CtfTmfEvent.class);
+ } catch (TmfTraceException e) {
+ fail(e.getMessage());
+ }
+
+ TmfExperiment experiment = new TmfExperiment(CtfTmfEvent.class,
+ "test-exp",
+ new ITmfTrace[] { ustTrace, kernelTrace },
+ TmfExperiment.DEFAULT_INDEX_PAGE_SIZE,
+ null);
+
+ /* Simulate experiment being opened */
+ // We have to "open" the sub-traces too, or their analyses are
+ // never initialized. Is that on purpose?
+ TmfSignalManager.dispatchSignal(new TmfTraceOpenedSignal(this, ustTrace, null));
+ TmfSignalManager.dispatchSignal(new TmfTraceOpenedSignal(this, kernelTrace, null));
+ TmfSignalManager.dispatchSignal(new TmfTraceOpenedSignal(this, experiment, null));
+ TmfSignalManager.dispatchSignal(new TmfTraceSelectedSignal(this, experiment));
+
+ KernelAnalysisModule module = TmfTraceUtils.getAnalysisModuleOfClass(experiment,
+ KernelAnalysisModule.class, KernelAnalysisModule.ID);
+ assertNotNull(module);
+ module.waitForCompletion();
+
+ fExperiment = experiment;
+ fKernelTrace = kernelTrace;
+ fUstTrace = ustTrace;
+ fKernelModule = module;
+ }
+
+ /**
+ * Test teardown
+ */
+ @After
+ public void tearDown() {
+ if (fExperiment != null) {
+ fExperiment.dispose();
+ }
+ if (fKernelTrace != null) {
+ fKernelTrace.dispose();
+ }
+
+ CtfTmfTestTraceUtils.dispose(KERNEL_TRACE);
+ CtfTmfTestTraceUtils.dispose(UST_TRACE);
+ }
+
+ /**
+ * Test that the TID given by the kernel analysis matches the one from the
+ * UST event's context for a given UST event that was known to fail.
+ *
+ * Reproduces the specific example that was pointed out in bug 484620.
+ */
+ @Test
+ public void testOneEvent() {
+ TmfExperiment experiment = fExperiment;
+ ITmfTrace ustTrace = fUstTrace;
+ KernelAnalysisModule module = fKernelModule;
+ assertNotNull(experiment);
+ assertNotNull(ustTrace);
+ assertNotNull(module);
+
+ Predicate<@NonNull ITmfEvent> eventFinder = event -> {
+ Long addr = event.getContent().getFieldValue(Long.class, "addr");
+ Long cs = event.getContent().getFieldValue(Long.class, "call_site");
+ Long ctxVtid = event.getContent().getFieldValue(Long.class, "context._vtid");
+
+ if (addr == null || cs == null || ctxVtid == null) {
+ return false;
+ }
+
+ return Objects.equals(event.getType().getName(), "lttng_ust_cyg_profile:func_entry") &&
+ Objects.equals(Long.toHexString(addr), "804af97") &&
+ Objects.equals(Long.toHexString(cs), "804ab03") &&
+ Objects.equals(ctxVtid.longValue(), 594L);
+ };
+
+ /* The event we're looking for is the second event matching the predicate */
+ CtfTmfEvent ustEvent = (CtfTmfEvent) TmfTraceUtils.getNextEventMatching(experiment, 0, eventFinder, null);
+ assertNotNull(ustEvent);
+ long rank = experiment.seekEvent(ustEvent.getTimestamp()).getRank() + 1;
+ ustEvent = (CtfTmfEvent) TmfTraceUtils.getNextEventMatching(experiment, rank, eventFinder, null);
+ assertNotNull(ustEvent);
+
+ assertEquals(ustTrace, ustEvent.getTrace());
+
+ Integer tidFromKernel = KernelThreadInformationProvider.getThreadOnCpu(module,
+ ustEvent.getCPU(), ustEvent.getTimestamp().toNanos());
+
+ assertNotNull(tidFromKernel);
+ assertEquals(594, tidFromKernel.intValue());
+ }
+
+ /**
+ * Test going through the whole UST trace, making sure the VTID context of
+ * each event corresponds to the TID given by the kernel analysis at the
+ * same timestamp.
+ */
+ @Test
+ public void testWholeUstTrace() {
+ TmfExperiment experiment = fExperiment;
+ ITmfTrace ustTrace = fUstTrace;
+ KernelAnalysisModule module = fKernelModule;
+ assertNotNull(experiment);
+ assertNotNull(ustTrace);
+ assertNotNull(module);
+
+ ITmfContext context = ustTrace.seekEvent(0L);
+ CtfTmfEvent ustEvent = (CtfTmfEvent) ustTrace.getNext(context);
+ int count = 0;
+ while (ustEvent != null) {
+ Long ustVtid = ustEvent.getContent().getFieldValue(Long.class, "context._vtid");
+ /* All events in the trace should have that context */
+ assertNotNull(ustVtid);
+
+ long ts = ustEvent.getTimestamp().toNanos();
+ long cpu = ustEvent.getCPU();
+ Integer kernelTid = KernelThreadInformationProvider.getThreadOnCpu(module, cpu, ts);
+ assertNotNull(kernelTid);
+
+ assertEquals("Wrong TID for trace event " + ustEvent.toString(), ustVtid.longValue(), kernelTid.longValue());
+
+ ustEvent = (CtfTmfEvent) ustTrace.getNext(context);
+ count++;
+ }
+
+ /* Make sure we've read all expected events */
+ assertEquals(UST_TRACE.getNbEvents(), count);
+ }
+}
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
+import java.util.Map;
import java.util.Vector;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.Path;
+import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.tracecompass.internal.tmf.core.component.TmfProviderManager;
+import org.eclipse.tracecompass.internal.tmf.core.synchronization.SyncAlgorithmFullyIncremental;
import org.eclipse.tracecompass.internal.tmf.core.trace.experiment.TmfExperimentContext;
import org.eclipse.tracecompass.internal.tmf.core.trace.experiment.TmfExperimentLocation;
import org.eclipse.tracecompass.tmf.core.analysis.IAnalysisModule;
import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
import org.eclipse.tracecompass.tmf.core.exceptions.TmfTraceException;
+import org.eclipse.tracecompass.tmf.core.project.model.ITmfPropertiesProvider;
import org.eclipse.tracecompass.tmf.core.request.ITmfEventRequest;
import org.eclipse.tracecompass.tmf.core.request.ITmfEventRequest.ExecutionType;
import org.eclipse.tracecompass.tmf.core.request.TmfEventRequest;
import org.eclipse.tracecompass.tmf.core.signal.TmfTraceOpenedSignal;
+import org.eclipse.tracecompass.tmf.core.synchronization.ITmfTimestampTransform;
+import org.eclipse.tracecompass.tmf.core.synchronization.SynchronizationAlgorithm;
+import org.eclipse.tracecompass.tmf.core.synchronization.TimestampTransformFactory;
import org.eclipse.tracecompass.tmf.core.tests.TmfCoreTestPlugin;
import org.eclipse.tracecompass.tmf.core.tests.analysis.AnalysisManagerTest;
import org.eclipse.tracecompass.tmf.core.tests.shared.TmfTestTrace;
import org.eclipse.tracecompass.tmf.tests.stubs.analysis.TestExperimentAnalysis;
import org.eclipse.tracecompass.tmf.tests.stubs.trace.TmfExperimentStub;
import org.eclipse.tracecompass.tmf.tests.stubs.trace.TmfTraceStub;
+import org.eclipse.tracecompass.tmf.tests.stubs.trace.xml.TmfXmlTraceStub;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import com.google.common.collect.ImmutableMap;
+
/**
* Test suite for the TmfExperiment class (single trace).
*/
assertTrue("isCancelled", request.isCancelled());
}
+ private static abstract class TestTrace extends TmfXmlTraceStub implements ITmfPropertiesProvider {
+
+ }
+
+ /**
+ * Tests that experiment with traces from the same host and a clock offset
+ * are well synchronized
+ */
+ @Test
+ public void testWithSingleHostClockOffset() {
+ // Data for this specific test
+ String hostId = "Test Host 1";
+ long minOffset = 2000;
+ long offset = 1000;
+ String clockOffset = "clock_offset";
+
+ ITmfTrace t1 = new TestTrace() {
+ @Override
+ public @NonNull String getHostId() {
+ return hostId;
+ }
+ @Override
+ public @NonNull Map<@NonNull String, @NonNull String> getProperties() {
+ return ImmutableMap.of(clockOffset, String.valueOf(minOffset));
+ }
+ };
+
+ ITmfTrace t2 = new TestTrace() {
+ @Override
+ public @NonNull String getHostId() {
+ return hostId;
+ }
+ @Override
+ public @NonNull Map<@NonNull String, @NonNull String> getProperties() {
+ return ImmutableMap.of(clockOffset, String.valueOf(minOffset + offset));
+ }
+ };
+
+ TmfExperiment exp = new TmfExperimentStub(EXPERIMENT, new ITmfTrace[] { t1, t2 }, BLOCK_SIZE);
+
+ try {
+ assertEquals(TimestampTransformFactory.createWithOffset(offset / 2), t1.getTimestampTransform());
+ assertEquals(TimestampTransformFactory.createWithOffset(-offset / 2), t2.getTimestampTransform());
+
+ } finally {
+ exp.dispose();
+ }
+ }
+
+ /**
+ * Tests that opening an experiment whose traces already have a
+ * synchronization formula will not eliminate that formula. This test makes
+ * the supposition that the experiment was synchronized and the
+ * synchronization added the clock offset correction to the total formula.
+ */
+ @Test
+ public void testWithMultiHostClockOffset() {
+ // Data for this specific test
+ String hostId = "Test Host 1";
+ String hostId2 = "Test Host 2";
+ long minOffset = 2000;
+ long offset = 1000;
+ String clockOffset = "clock_offset";
+
+ ITmfTimestampTransform tt1 = TimestampTransformFactory.createLinear(2.0, offset / 2);
+ ITmfTimestampTransform tt2 = TimestampTransformFactory.createLinear(2.0, -offset / 2);
+ ITmfTimestampTransform tt3 = TimestampTransformFactory.createWithOffset(offset);
+
+ ITmfTrace t1 = new TestTrace() {
+ @Override
+ public @NonNull String getHostId() {
+ return hostId;
+ }
+ @Override
+ public @NonNull Map<@NonNull String, @NonNull String> getProperties() {
+ return ImmutableMap.of(clockOffset, String.valueOf(minOffset));
+ }
+
+ };
+ t1.setTimestampTransform(tt1);
+
+ ITmfTrace t2 = new TestTrace() {
+ @Override
+ public @NonNull String getHostId() {
+ return hostId;
+ }
+ @Override
+ public @NonNull Map<@NonNull String, @NonNull String> getProperties() {
+ return ImmutableMap.of(clockOffset, String.valueOf(minOffset + offset));
+ }
+ };
+ t2.setTimestampTransform(tt2);
+
+ ITmfTrace t3 = new TmfXmlTraceStub() {
+ @Override
+ public @NonNull String getHostId() {
+ return hostId2;
+ }
+ };
+ t3.setTimestampTransform(tt3);
+
+ TmfExperiment exp = new TmfExperimentStub(EXPERIMENT, new ITmfTrace[] { t1, t2, t3 }, BLOCK_SIZE) {
+
+ @Override
+ public SynchronizationAlgorithm synchronizeTraces() {
+ return new SyncAlgorithmFullyIncremental() {
+
+ private static final long serialVersionUID = 4206172498287480153L;
+
+ @Override
+ public ITmfTimestampTransform getTimestampTransform(String h) {
+ if (hostId.equals(h)) {
+ return TimestampTransformFactory.createLinear(2.0, 0);
+ }
+ return tt3;
+ }
+ };
+ }
+ };
+
+ try {
+ assertEquals(tt1, t1.getTimestampTransform());
+ assertEquals(tt2, t2.getTimestampTransform());
+ assertEquals(tt3, t3.getTimestampTransform());
+
+ } finally {
+ exp.dispose();
+ }
+ }
+
}
package org.eclipse.tracecompass.tmf.core.trace.experiment;
+import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
+
import java.io.File;
+import java.math.BigInteger;
import java.nio.ByteBuffer;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Function;
+import java.util.stream.Collectors;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.internal.tmf.core.Activator;
+import org.eclipse.tracecompass.internal.tmf.core.synchronization.TmfTimestampTransform;
import org.eclipse.tracecompass.internal.tmf.core.trace.experiment.TmfExperimentContext;
import org.eclipse.tracecompass.internal.tmf.core.trace.experiment.TmfExperimentLocation;
import org.eclipse.tracecompass.internal.tmf.core.trace.experiment.TmfLocationArray;
import org.eclipse.tracecompass.tmf.core.TmfCommonConstants;
import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
import org.eclipse.tracecompass.tmf.core.exceptions.TmfTraceException;
+import org.eclipse.tracecompass.tmf.core.project.model.ITmfPropertiesProvider;
import org.eclipse.tracecompass.tmf.core.request.ITmfEventRequest;
import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
import org.eclipse.tracecompass.tmf.core.signal.TmfTraceOpenedSignal;
import org.eclipse.tracecompass.tmf.core.signal.TmfTraceRangeUpdatedSignal;
import org.eclipse.tracecompass.tmf.core.signal.TmfTraceSynchronizedSignal;
+import org.eclipse.tracecompass.tmf.core.synchronization.ITmfTimestampTransform;
import org.eclipse.tracecompass.tmf.core.synchronization.SynchronizationAlgorithm;
import org.eclipse.tracecompass.tmf.core.synchronization.SynchronizationManager;
+import org.eclipse.tracecompass.tmf.core.synchronization.TimestampTransformFactory;
import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp;
import org.eclipse.tracecompass.tmf.core.trace.indexer.TmfBTreeTraceIndexer;
import org.eclipse.tracecompass.tmf.core.trace.location.ITmfLocation;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+
/**
* TmfExperiment presents a time-ordered, unified view of a set of ITmfTrace:s
* that are part of a tracing experiment.
*/
public static final int DEFAULT_INDEX_PAGE_SIZE = 5000;
+ /**
+ * Property name for traces defining a clock offset.
+ */
+ private static final String CLOCK_OFFSET_PROPERTY = "clock_offset"; //$NON-NLS-1$
+
+ /**
+ * If the automatic clock offset is higher than this value, emit a warning.
+ */
+ private static final long CLOCK_OFFSET_THRESHOLD_NS = 500000;
+
// ------------------------------------------------------------------------
// Attributes
// ------------------------------------------------------------------------
setCacheSize(indexPageSize);
setStreamingInterval(0);
+ Multimap<String, ITmfTrace> tracesPerHost = HashMultimap.create();
+
// traces have to be set before super.initialize()
if (traces != null) {
// initialize
for (ITmfTrace trace : traces) {
if (trace != null) {
+ tracesPerHost.put(trace.getHostId(), trace);
addChild(trace);
}
}
}
if (resource != null) {
- this.synchronizeTraces();
+ synchronizeTraces();
+ }
+
+ /*
+ * For all traces on the same host, if two or more specify different
+ * clock offsets, adjust their clock offset by the average of all of them.
+ *
+ * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=484620
+ */
+ Function<ITmfPropertiesProvider, @Nullable Long> offsetGetter = trace -> {
+ String offset = trace.getProperties().get(CLOCK_OFFSET_PROPERTY);
+ if (offset == null) {
+ return null;
+ }
+ try {
+ return Long.parseLong(offset);
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ };
+
+ for (String host : tracesPerHost.keySet()) {
+ /*
+ * Only attempt to synchronize traces that provide a clock_offset
+ * property.
+ */
+ Collection<ITmfPropertiesProvider> tracesToSynchronize = tracesPerHost.get(host).stream()
+ .filter(trace -> trace instanceof ITmfPropertiesProvider)
+ .map(trace -> (ITmfPropertiesProvider) trace)
+ .filter(trace -> offsetGetter.apply(trace) != null)
+ .collect(Collectors.toList());
+
+ if (tracesToSynchronize.size() < 2) {
+ continue;
+ }
+
+ /* Only synchronize traces if they haven't previously been synchronized */
+ if (tracesToSynchronize.stream()
+ .map(trace -> ((ITmfTrace) trace).getTimestampTransform())
+ .anyMatch(transform -> !transform.equals(TmfTimestampTransform.IDENTITY))) {
+ continue;
+ }
+
+ /* Calculate the average of all clock offsets */
+ BigInteger sum = BigInteger.ZERO;
+ for (ITmfPropertiesProvider trace : tracesToSynchronize) {
+ long offset = checkNotNull(offsetGetter.apply(trace));
+ sum = sum.add(BigInteger.valueOf(offset));
+ }
+ long average = sum.divide(BigInteger.valueOf(tracesToSynchronize.size())).longValue();
+
+ if (average > CLOCK_OFFSET_THRESHOLD_NS) {
+ Activator.logWarning("Average clock correction (" + average + ") is higher than threshold of " + //$NON-NLS-1$ //$NON-NLS-2$
+ CLOCK_OFFSET_THRESHOLD_NS + " ns for experiment " + this.toString()); //$NON-NLS-1$
+ }
+
+ /*
+ * Apply the offset correction to all identified traces, but only if
+ * they do not already have an equivalent one (for example, closing
+ * and re-opening the same experiment should not retrigger building
+ * all supplementary files).
+ */
+ tracesToSynchronize.forEach(t -> {
+ long offset = checkNotNull(offsetGetter.apply(t));
+ long delta = average - offset;
+
+ ITmfTrace trace = (ITmfTrace) t;
+ ITmfTimestampTransform currentTransform = trace.getTimestampTransform();
+ ITmfTimestampTransform newTransform = TimestampTransformFactory.createWithOffset(delta);
+
+ if (!newTransform.equals(currentTransform)) {
+ TmfTraceManager.deleteSupplementaryFiles(trace);
+ trace.setTimestampTransform(newTransform);
+ }
+ });
}
}