From 6a23a22b3b950d93b6e9e86952a22010a6cb676c Mon Sep 17 00:00:00 2001 From: Alexandre Montplaisir Date: Fri, 7 Jul 2017 16:59:06 -0400 Subject: [PATCH] ctf: Add tests for CTF trace trimming Attempt to trim all existing CTF test traces. Change-Id: I4fe15265dfe36a2aa561ed2c19365b0879e90342 Signed-off-by: Alexandre Montplaisir --- .../META-INF/MANIFEST.MF | 7 +- .../tests/trim/CtfTmfTraceTrimmingTest.java | 330 ++++++++++++++++++ 2 files changed, 335 insertions(+), 2 deletions(-) create mode 100644 ctf/org.eclipse.tracecompass.tmf.ctf.core.tests/src/org/eclipse/tracecompass/tmf/ctf/core/tests/trim/CtfTmfTraceTrimmingTest.java diff --git a/ctf/org.eclipse.tracecompass.tmf.ctf.core.tests/META-INF/MANIFEST.MF b/ctf/org.eclipse.tracecompass.tmf.ctf.core.tests/META-INF/MANIFEST.MF index 861236752d..656d2c5972 100644 --- a/ctf/org.eclipse.tracecompass.tmf.ctf.core.tests/META-INF/MANIFEST.MF +++ b/ctf/org.eclipse.tracecompass.tmf.ctf.core.tests/META-INF/MANIFEST.MF @@ -27,7 +27,10 @@ Export-Package: org.eclipse.tracecompass.tmf.ctf.core.tests.context, org.eclipse.tracecompass.tmf.ctf.core.tests.temp.statistics;x-internal:=true, org.eclipse.tracecompass.tmf.ctf.core.tests.temp.tracemanager;x-internal:=true, org.eclipse.tracecompass.tmf.ctf.core.tests.trace, - org.eclipse.tracecompass.tmf.ctf.core.tests.trace.indexer -Import-Package: com.google.common.collect, + org.eclipse.tracecompass.tmf.ctf.core.tests.trace.indexer, + org.eclipse.tracecompass.tmf.ctf.core.tests.trim +Import-Package: com.google.common.base, + com.google.common.collect, + org.apache.commons.io, org.eclipse.test.performance, org.eclipse.tracecompass.testtraces.ctf;version="1.0.0" diff --git a/ctf/org.eclipse.tracecompass.tmf.ctf.core.tests/src/org/eclipse/tracecompass/tmf/ctf/core/tests/trim/CtfTmfTraceTrimmingTest.java b/ctf/org.eclipse.tracecompass.tmf.ctf.core.tests/src/org/eclipse/tracecompass/tmf/ctf/core/tests/trim/CtfTmfTraceTrimmingTest.java new file mode 100644 index 0000000000..abe45c5496 --- /dev/null +++ b/ctf/org.eclipse.tracecompass.tmf.ctf.core.tests/src/org/eclipse/tracecompass/tmf/ctf/core/tests/trim/CtfTmfTraceTrimmingTest.java @@ -0,0 +1,330 @@ +/******************************************************************************* + * 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.tmf.ctf.core.tests.trim; + +import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import org.apache.commons.io.FileUtils; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.tracecompass.testtraces.ctf.CtfTestTrace; +import org.eclipse.tracecompass.tmf.core.event.TmfEvent; +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.timestamp.TmfTimeRange; +import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp; +import org.eclipse.tracecompass.tmf.core.trace.ITmfContext; +import org.eclipse.tracecompass.tmf.ctf.core.event.CtfTmfEvent; +import org.eclipse.tracecompass.tmf.ctf.core.tests.shared.CtfTmfTestTraceUtils; +import org.eclipse.tracecompass.tmf.ctf.core.trace.CtfTmfTrace; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import com.google.common.collect.ImmutableSet; + +/** + * Tests related to the trimming feature of CTF traces + * ({@link CtfTmfTrace#trim}). + * + * @author Alexandre Montplaisir + */ +@Ignore("Trim command requires Babeltrace 2.0, which is not installed on most CIs. Test can be run manually.") +@RunWith(Parameterized.class) +public class CtfTmfTraceTrimmingTest { + + /** Test timeout */ + @Rule public TestRule globalTimeout= new Timeout(5, TimeUnit.MINUTES); + + private final @NonNull CtfTestTrace fTestTrace; + + private CtfTmfTrace fOriginalTrace; + private long fRequestedTraceCutStart; + private long fRequestedTraceCutEnd; + + private CtfTmfTrace fNewTrace; + private Path fNewTracePath; + + // ------------------------------------------------------------------------ + // Test suite definition + // ------------------------------------------------------------------------ + + @SuppressWarnings("deprecation") + private static final Set BLACKLISTED_TRACES = ImmutableSet.of( + /* + * Babeltrace 2 currently fails reading the following traces. This is a reader + * bug, unrelated to the trace trimming operation. + * + * See https://bugs.lttng.org/issues/1121 . + * + * TODO Un-ignore these once BT2 is fixed. + */ + CtfTestTrace.FUNKY_TRACE, + CtfTestTrace.DYNSCOPE, + + /* + * Ignore the deprecated DEBUG_INFO traces. Those were not generated by a + * release version of lttng-ust. + */ + CtfTestTrace.DEBUG_INFO, + CtfTestTrace.DEBUG_INFO3, + + /* + * Ignore hello-lost, most of the trace range is lost events, so cutting would + * give an empty trace. + */ + CtfTestTrace.HELLO_LOST, + + /* Trimming doesn't work on experiments at the moment */ + CtfTestTrace.TRACE_EXPERIMENT + ); + + /** + * Test parameter generator + * + * @return The list of constructor parameters, one for each test instance. + */ + @Parameters(name = "{index}: {0}") + public static Iterable getTestTraces() { + CtfTestTrace[] testTraces = CtfTestTrace.values(); + return Arrays.stream(testTraces) + .filter(testTrace -> !BLACKLISTED_TRACES.contains(testTrace)) + .map(testTrace -> new Object[] { testTrace }) + .collect(Collectors.toList()); + } + + /** + * Constructor. Receives parameters defined in {@link #getTestTraces()}. + * + * @param testTrace + * The test trace to use for this test instance. + */ + public CtfTmfTraceTrimmingTest(@NonNull CtfTestTrace testTrace) { + fTestTrace = testTrace; + } + + // ------------------------------------------------------------------------ + // Test instance maintenance + // ------------------------------------------------------------------------ + + /** + * Test setup + */ + @Before + public void setup() { + fOriginalTrace = CtfTmfTestTraceUtils.getTrace(fTestTrace); + openTrace(fOriginalTrace); + + fRequestedTraceCutStart = getTraceCutStart(fOriginalTrace); + fRequestedTraceCutEnd = getTraceCutEnd(fOriginalTrace); + + assertTrue(fRequestedTraceCutStart >= fOriginalTrace.getStartTime().toNanos()); + assertTrue(fRequestedTraceCutEnd <= fOriginalTrace.getEndTime().toNanos()); + assertTrue(fRequestedTraceCutStart < fRequestedTraceCutEnd); + + TmfTimeRange range = new TmfTimeRange( + TmfTimestamp.fromNanos(fRequestedTraceCutStart), + TmfTimestamp.fromNanos(fRequestedTraceCutEnd)); + try { + /* Perform the trim to create the new trace */ + fNewTracePath = checkNotNull(Files.createTempDirectory("trimmed-trace-test")); + fOriginalTrace.trim(range, fNewTracePath, new NullProgressMonitor()); + + /* Initialize the new trace */ + fNewTrace = new CtfTmfTrace(); + fNewTrace.initTrace(null, fNewTracePath.toString(), CtfTmfEvent.class); + openTrace(fNewTrace); + + } catch (IOException | TmfTraceException e) { + fail(e.getMessage()); + } catch (CoreException e) { + /* + * CoreException are more or less useless, all the interesting stuff + * is in their "status" objects. + */ + String msg; + IStatus status = e.getStatus(); + IStatus[] children = status.getChildren(); + if (children == null) { + msg = status.getMessage(); + } else { + msg = Arrays.stream(children) + .map(IStatus::getMessage) + .collect(Collectors.joining("\n")); + } + fail(msg); + } + } + + /** + * Test teardown + */ + @After + public void tearDown() { + if (fOriginalTrace != null) { + fOriginalTrace.dispose(); + } + CtfTmfTestTraceUtils.dispose(fTestTrace); + + if (fNewTrace != null) { + fNewTrace.dispose(); + } + + if (fNewTracePath != null) { + FileUtils.deleteQuietly(fNewTracePath.toFile()); + } + } + + /** Simulate a trace being opened */ + private static void openTrace(CtfTmfTrace trace) { + trace.indexTrace(true); + TmfSignalManager.dispatchSignal(new TmfTraceOpenedSignal(CtfTmfTraceTrimmingTest.class, trace, null)); + } + + /** + * Get the timestamp at which we should start cutting the trace. It should + * be roughly 1/4 into the trace. + */ + private static long getTraceCutStart(CtfTmfTrace trace) { + long start = trace.getStartTime().toNanos(); + long end = trace.getEndTime().toNanos(); + + return ((end - start) / 4) + start; + } + + /** + * Get the timestamp at which we should end the trace cutting. It should be + * roughly at half the trace. + */ + private static long getTraceCutEnd(CtfTmfTrace trace) { + long start = trace.getStartTime().toNanos(); + long end = trace.getEndTime().toNanos(); + + return ((end - start) / 2) + start; + } + + // ------------------------------------------------------------------------ + // Test methods and helpers + // ------------------------------------------------------------------------ + + /** + * Test that all expected events are present in the new trace. + */ + @Test + public void testTrimEvents() { + CtfTmfTrace initialTrace = fOriginalTrace; + CtfTmfTrace trimmedTrace = fNewTrace; + Path newTracePath = fNewTracePath; + assertNotNull(initialTrace); + assertNotNull(trimmedTrace); + assertNotNull(newTracePath); + + /* + * Verify the bounds of the new trace are fine. The actual trace can be + * smaller than what was requested if there are no events exactly at the + * bounds, but should not contain events outside of the requested range. + */ + final long newTraceStartTime = trimmedTrace.getStartTime().toNanos(); + final long newTraceEndTime = trimmedTrace.getEndTime().toNanos(); + + assertTrue("Cut trace start time " + newTraceStartTime + + " is earlier than the requested " + fRequestedTraceCutStart, + newTraceStartTime >= fRequestedTraceCutStart); + + assertTrue("Cut trace end time " + newTraceEndTime + + " is later than the requested " + fRequestedTraceCutEnd, + newTraceEndTime <= fRequestedTraceCutEnd); + + /* + * Verify that each trace event from the original trace in the given + * time range is present in the new one. + */ + ITmfContext context1 = initialTrace.seekEvent(TmfTimestamp.fromNanos(newTraceStartTime)); + CtfTmfEvent event1 = initialTrace.getNext(context1); + ITmfContext context2 = trimmedTrace.seekEvent(0L); + CtfTmfEvent event2 = trimmedTrace.getNext(context2); + + int count = 0; + while (event1.getTimestamp().toNanos() <= fRequestedTraceCutEnd) { + assertNotNull(event1); + assertNotNull("Expected event not present in trimmed trace: " + eventToString(event1), event2); + + if (!eventsEquals(event1, event2)) { + /* + * Skip the test for different events of the exact same timestamp. The library + * does not guarantee in which order events of the same timestamp are read. + */ + if (Objects.equals(event1.getTimestamp(), event2.getTimestamp())) { + System.err.println("The following events have the exact same timestamp, and may be read in any order:"); + System.err.println(eventToString(event1)); + System.err.println(eventToString(event2)); + } else { + fail("The following events are not the same: \n " + + eventToString(event1) + '\n' + + eventToString(event2)); + } + } + + event1 = initialTrace.getNext(context1); + event2 = trimmedTrace.getNext(context2); + count++; + } + + assertEquals(trimmedTrace.getNbEvents(), count); + } + + /** + * {@link TmfEvent#equals} checks the container trace, among other things. + * Here we want to compare events from different traces, so we have to + * implement our own equals(). + */ + private static boolean eventsEquals(CtfTmfEvent event1, CtfTmfEvent event2) { + return Objects.equals(event1.getTimestamp(), event2.getTimestamp()) + && Objects.equals(event1.getType(), event2.getType()) + && Objects.equals(event1.getContent(), event2.getContent()) + && Objects.equals(event1.getCPU(), event2.getCPU()); + + // FIXME This currently gets renamed, but eventually won't. +// && Objects.equals(event1.getChannel(), event2.getChannel()); + } + + private static String eventToString(CtfTmfEvent event) { + return com.google.common.base.Objects.toStringHelper(event) + .add("Timestamp", event.getTimestamp()) + .add("Type", event.getType()) + .add("Content", event.getContent()) + .add("CPU", event.getCPU()) + .add("Channel", event.getChannel()) + .toString(); + } +} -- 2.34.1