blob: 230f4a722cb3564390ed7ead1f2b5b9453607d6f [file] [log] [blame]
/*******************************************************************************
* 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 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.statesystem.core.tests.perf.historytree;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Random;
import org.apache.commons.io.FileUtils;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.test.performance.Dimension;
import org.eclipse.test.performance.Performance;
import org.eclipse.test.performance.PerformanceMeter;
import org.eclipse.tracecompass.common.core.NonNullUtils;
import org.eclipse.tracecompass.internal.statesystem.core.backend.historytree.HistoryTreeBackend;
import org.eclipse.tracecompass.statesystem.core.ITmfStateSystemBuilder;
import org.eclipse.tracecompass.statesystem.core.StateSystemFactory;
import org.eclipse.tracecompass.statesystem.core.StateSystemUtils;
import org.eclipse.tracecompass.statesystem.core.backend.IStateHistoryBackend;
import org.eclipse.tracecompass.statesystem.core.backend.StateHistoryBackendFactory;
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.interval.ITmfStateInterval;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import com.google.common.collect.ImmutableList;
/**
* This class benchmarks writing intervals to the state system and querying them
* using a history tree backend
*
* @author Geneviève Bastien
*/
@RunWith(Parameterized.class)
public class HistoryTreeBackendBenchmark {
private static final @NonNull String TEST_PREFIX = "org.eclipse.tracecompass#History Tree Backend#";
private static final @NonNull String TEST_BUILDING_ID = "Build: ";
private static final @NonNull String TEST_SINGLE_QUERY_ID = "Single Queries: ";
private static final @NonNull String TEST_FULL_QUERY_ID = "Full Queries: ";
private static final @NonNull String TEST_QUERY_RANGE_ID = "Query History Range: ";
private static final @NonNull String TEST_2D_QUERY_ID = "2D Queries: ";
private static final @NonNull String TEST_REVERSE_2D_QUERY_ID = "Reverse 2D Queries: ";
private static final @NonNull String ROOT_NODE = "root";
private static final int QUEUE_SIZE = 10000;
private static final long SEED = 5575784704147L;
private static final int QUERY_COUNT = 100;
private static final int INTERVAL_AVG_TIME = 1000;
/* Values for the average case */
private static final int DEFAULT_NB_ATTRIB = 1500;
private static final int DEFAULT_NB_INTERVALS = 500;
private static final int DEFAULT_LOOP_COUNT = 40;
private File fTempFile;
private final String fName;
private final String fShortName;
private final int fNbAttrib;
private final int fNbAvgIntervals;
private final int fNbLoops;
private final HTBValues fValues;
private final IIntervalDistribution fDistributionMethod;
/**
* Constructor
*
* @param name
* The name of the test
* @param shortName
* A short name for this scenario (at most 40 characters,
* otherwise it will be truncated in the DB)
* @param nbAttrib
* The number of attributes
* @param nbAvgIntervals
* An idea on the number of intervals per attributes. The exact
* number will depend on the interval time distribution
* @param nbLoops
* The number of times to execute the benchmark
* @param values
* The values to put in the history tree
* @param distributionMethod
* A distribution method that will return the next interval
* duration according to an algorithm
*/
public HistoryTreeBackendBenchmark(String name, String shortName, int nbAttrib, int nbAvgIntervals, int nbLoops, HTBValues values, IIntervalDistribution distributionMethod) {
fName = name;
fShortName = shortName;
fNbAttrib = nbAttrib;
fNbAvgIntervals = nbAvgIntervals;
fNbLoops = nbLoops;
fValues = values;
fDistributionMethod = distributionMethod;
}
/* A list of values to use for the intervals */
private enum HTBValues {
INTEGERS(ImmutableList.of(1, 2, 3)),
STRINGS(ImmutableList.of("abc", "def", "wihi!")),
LONGS(ImmutableList.of(Long.MAX_VALUE, 1L, 1234567L)),
DOUBLES(ImmutableList.of(Double.MAX_VALUE, 1.0, 123.456));
private final List<Object> fValues;
private HTBValues(List<Object> values) {
fValues = values;
}
public List<Object> getValues() {
return fValues;
}
}
@FunctionalInterface
private static interface IIntervalDistribution {
long getNextEndTime(Random randomGenerator, long limit);
}
/* Generate interval duration uniformly distributed in the range [1, 2l] */
private static IIntervalDistribution UNIFORM = (r, l) -> {
long nextLong = r.nextLong();
long nextDelta = l + (nextLong % l);
return nextDelta;
};
/*
* Generates interval duration in the range [1, 2l] with 75% between [0.5l,
* 1.5l]
*/
private static IIntervalDistribution CLOSER_TO_LIMIT = (r, l) -> {
/*
* This method will return interval time closer to the specified limit
*/
double nextDouble = r.nextDouble();
int sign = (nextDouble < 0) ? -1 : 1;
long nextDelta = l + (long) (Math.sqrt(nextDouble) * l * sign);
return nextDelta;
};
/*
* Generates interval duration in the range [1, 2l] with 75% between [0.5l,
* 1.5l], but with 10% outliers between [2l, 5l]
*/
private static IIntervalDistribution CLOSER_TO_LIMIT_10_PERCENT_OUTLIERS = (r, l) -> {
long nextLong = Math.abs(r.nextLong());
/* Is this an outlier */
if ((nextLong % 100) < 10) {
/* Create an outlier that can go up to 5 times the limit */
return l + (((nextLong % 3) + 1) * l) + (nextLong % l);
}
/* Otherwise follow a distribution with more values around the limit */
double nextDouble = r.nextDouble();
int sign = (nextDouble < 0) ? -1 : 1;
long nextDelta = l + (long) (Math.sqrt(nextDouble) * l * sign);
return nextDelta;
};
/**
* @return The arrays of parameters
*/
@Parameters(name = "{index}: {0}")
public static Iterable<Object[]> getParameters() {
return Arrays.asList(new Object[][] {
{ "Average case: 1500 attributes, integers, interval duration random around limit l with 75 percent within [0.5l, 1.5l]", "Average case", DEFAULT_NB_ATTRIB, DEFAULT_NB_INTERVALS, DEFAULT_LOOP_COUNT, HTBValues.INTEGERS, CLOSER_TO_LIMIT },
{ "Vertical scaling (more attributes)", "Vertical scaling", 3500, DEFAULT_NB_INTERVALS, 5, HTBValues.INTEGERS, CLOSER_TO_LIMIT },
{ "Horizontal scaling (more intervals/attribute)", "Horizontal scaling", DEFAULT_NB_ATTRIB, 20000, 10, HTBValues.INTEGERS, CLOSER_TO_LIMIT },
{ "Interval durations uniformly distributed within [1, 2l]", "Uniform distribution of intervals", DEFAULT_NB_ATTRIB, DEFAULT_NB_INTERVALS, DEFAULT_LOOP_COUNT, HTBValues.INTEGERS, UNIFORM },
{ "Interval durations with 10 percent outliers > 2l", "Distribution with outliers", DEFAULT_NB_ATTRIB, DEFAULT_NB_INTERVALS, DEFAULT_LOOP_COUNT, HTBValues.INTEGERS, CLOSER_TO_LIMIT_10_PERCENT_OUTLIERS },
{ "Data type: strings", "Data type: strings", DEFAULT_NB_ATTRIB, DEFAULT_NB_INTERVALS, DEFAULT_LOOP_COUNT, HTBValues.STRINGS, CLOSER_TO_LIMIT },
{ "Data type: longs", "Data type: longs", DEFAULT_NB_ATTRIB, DEFAULT_NB_INTERVALS, DEFAULT_LOOP_COUNT, HTBValues.LONGS, CLOSER_TO_LIMIT },
{ "Data type: doubles", "Data type: doubles", DEFAULT_NB_ATTRIB, DEFAULT_NB_INTERVALS, DEFAULT_LOOP_COUNT, HTBValues.DOUBLES, CLOSER_TO_LIMIT },
});
}
/**
* Create the temporary file for this history tree
*/
private void createFile() {
try {
fTempFile = File.createTempFile("tmpStateSystem", null);
} catch (IOException e) {
fail(e.getMessage());
}
}
/**
* Delete the temporary history tree file after the test
*/
private void deleteFile() {
if (fTempFile != null) {
fTempFile.delete();
fTempFile = null;
}
}
private static class QuarkEvent implements Comparable<QuarkEvent> {
private final int fQuark;
private long fNextEventTime;
private final List<Object> fPossibleValues;
private int fNextValue = 0;
public QuarkEvent(int quark, long nextEventTime, List<Object> valuesList) {
fQuark = quark;
fNextEventTime = nextEventTime;
fPossibleValues = valuesList;
}
public int getQuark() {
return fQuark;
}
public long getNextEventTime() {
return fNextEventTime;
}
public void setNextEventTime(long t) {
fNextEventTime = t;
}
public Object getNextValue() {
Object value = fPossibleValues.get(fNextValue);
fNextValue++;
if (fNextValue >= fPossibleValues.size()) {
fNextValue = 0;
}
return value;
}
@Override
public int compareTo(QuarkEvent other) {
return Long.compare(fNextEventTime, other.getNextEventTime());
}
}
/**
* Benchmarks creating, single querying and full querying the state system
*/
@Test
public void testBenchmark() {
/* Check arguments */
long totalTime = this.fNbAvgIntervals * INTERVAL_AVG_TIME;
Performance perf = Performance.getDefault();
PerformanceMeter pmBuild = perf.createPerformanceMeter(TEST_PREFIX + TEST_BUILDING_ID + fName);
perf.tagAsSummary(pmBuild, TEST_BUILDING_ID + fShortName, Dimension.CPU_TIME);
PerformanceMeter pmSingleQuery = perf.createPerformanceMeter(TEST_PREFIX + TEST_SINGLE_QUERY_ID + fName);
perf.tagAsSummary(pmSingleQuery, TEST_SINGLE_QUERY_ID + fShortName, Dimension.CPU_TIME);
PerformanceMeter pmFullQuery = perf.createPerformanceMeter(TEST_PREFIX + TEST_FULL_QUERY_ID + fName);
perf.tagAsSummary(pmFullQuery, TEST_FULL_QUERY_ID + fShortName, Dimension.CPU_TIME);
PerformanceMeter pmRangeQuery = perf.createPerformanceMeter(TEST_PREFIX + TEST_QUERY_RANGE_ID + fName);
perf.tagAsSummary(pmRangeQuery, TEST_QUERY_RANGE_ID + fShortName, Dimension.CPU_TIME);
PerformanceMeter pm2DQuery = perf.createPerformanceMeter(TEST_PREFIX + TEST_2D_QUERY_ID + fName);
perf.tagAsSummary(pm2DQuery, TEST_2D_QUERY_ID + fShortName, Dimension.CPU_TIME);
PerformanceMeter pmReverse2DQuery = perf.createPerformanceMeter(TEST_PREFIX + TEST_REVERSE_2D_QUERY_ID + fName);
perf.tagAsSummary(pmReverse2DQuery, TEST_REVERSE_2D_QUERY_ID + fShortName, Dimension.CPU_TIME);
for (int i = 0; i < fNbLoops; i++) {
try {
/* Create the state system */
createFile();
IStateHistoryBackend backend = StateHistoryBackendFactory.createHistoryTreeBackendNewFile(TEST_BUILDING_ID, NonNullUtils.checkNotNull(fTempFile), 1, 1, QUEUE_SIZE);
ITmfStateSystemBuilder ss = StateSystemFactory.newStateSystem(backend);
/* Initialize the attributes */
Queue<QuarkEvent> quarkEvents = new PriorityQueue<>(fNbAttrib);
Random randomGenerator = new Random(SEED);
int rootQuark = ss.getQuarkAbsoluteAndAdd(ROOT_NODE);
/* Create all attributes before testing */
for (int j = 0; j < fNbAttrib; j++) {
int quark = ss.getQuarkRelativeAndAdd(rootQuark, String.valueOf(j));
quarkEvents.add(new QuarkEvent(quark, (Math.abs(randomGenerator.nextLong()) % INTERVAL_AVG_TIME) + 1, fValues.getValues()));
}
/* Adds random intervals to the state system */
pmBuild.start();
while (true) {
QuarkEvent quarkEvent = quarkEvents.poll();
if (quarkEvent == null) {
break;
}
long eventTime = quarkEvent.getNextEventTime();
ss.modifyAttribute(eventTime, quarkEvent.getNextValue(), quarkEvent.getQuark());
long nextDelta = fDistributionMethod.getNextEndTime(randomGenerator, INTERVAL_AVG_TIME);
long nextEndTime = eventTime + nextDelta;
if (nextEndTime <= totalTime) {
quarkEvent.setNextEventTime(nextEndTime);
quarkEvents.add(quarkEvent);
}
}
ss.closeHistory(totalTime);
pmBuild.stop();
/*
* Benchmark the single queries: for each random timestamp,
* query a random attribute
*/
List<Integer> subAttributes = ss.getSubAttributes(rootQuark, false);
pmSingleQuery.start();
for (int j = 0; j < QUERY_COUNT; j++) {
long ts = getNextRandomValue(randomGenerator, totalTime);
int attrib = (int) getNextRandomValue(randomGenerator, subAttributes.size());
ss.querySingleState(ts, attrib);
}
pmSingleQuery.stop();
/* Benchmark the history range query of 10 attributes */
pmRangeQuery.start();
List<Integer> queryAttributes = new ArrayList<>();
for (int j = 0; j < 10; j++) {
int attrib = (int) getNextRandomValue(randomGenerator, subAttributes.size());
queryAttributes.add(attrib);
StateSystemUtils.queryHistoryRange(ss, attrib, ss.getStartTime(), ss.getCurrentEndTime());
}
pmRangeQuery.stop();
/* Benchmark 2D query of the same 10 attributes */
pm2DQuery.start();
for (int j = 0; j < 10; j++) {
Iterable<@NonNull ITmfStateInterval> query2d = ss.query2D(queryAttributes, ss.getStartTime(), ss.getCurrentEndTime());
Iterator<@NonNull ITmfStateInterval> iterator = query2d.iterator();
while (iterator.hasNext()) {
iterator.next();
}
}
pm2DQuery.stop();
/* Benchmark 2D query of the same 10 attributes, in reverse order */
pmReverse2DQuery.start();
for (int j = 0; j < 10; j++) {
Iterable<@NonNull ITmfStateInterval> query2d = ss.query2D(queryAttributes, ss.getCurrentEndTime(), ss.getStartTime());
Iterator<@NonNull ITmfStateInterval> iterator = query2d.iterator();
while (iterator.hasNext()) {
iterator.next();
}
}
pmReverse2DQuery.stop();
/* Benchmark the full queries */
pmFullQuery.start();
for (int j = 0; j < QUERY_COUNT; j++) {
long ts = getNextRandomValue(randomGenerator, totalTime);
ss.queryFullState(ts);
}
pmFullQuery.stop();
/* Output some data on the file */
if (i == 0) {
if (backend instanceof HistoryTreeBackend) {
HistoryTreeBackend htBackend = (HistoryTreeBackend) backend;
System.out.println("History tree file size: " + FileUtils.byteCountToDisplaySize(htBackend.getFileSize()));
System.out.println("Average node usage: " + htBackend.getAverageNodeUsage());
}
}
deleteFile();
} catch (IOException | StateValueTypeException | AttributeNotFoundException | StateSystemDisposedException e) {
fail(e.getMessage());
} finally {
deleteFile();
}
}
pmBuild.commit();
pmSingleQuery.commit();
pmFullQuery.commit();
pmRangeQuery.commit();
pm2DQuery.commit();
pmReverse2DQuery.commit();
}
/**
* Get a next random value between 1 and a boundary.
*/
private static long getNextRandomValue(Random randomGenerator, long limit) {
long nextLong = Math.abs(randomGenerator.nextLong());
long nextDelta = (nextLong % limit) + 1;
return nextDelta;
}
}