1 /*******************************************************************************
2 * Copyright (c) 2016 École Polytechnique de Montréal
4 * All rights reserved. This program and the accompanying materials are
5 * made available under the terms of the Eclipse Public License v1.0 which
6 * accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
8 *******************************************************************************/
10 package org
.eclipse
.tracecompass
.statesystem
.core
.tests
.perf
.historytree
;
12 import static org
.junit
.Assert
.fail
;
15 import java
.io
.IOException
;
16 import java
.util
.Arrays
;
17 import java
.util
.List
;
18 import java
.util
.PriorityQueue
;
19 import java
.util
.Queue
;
20 import java
.util
.Random
;
22 import org
.apache
.commons
.io
.FileUtils
;
23 import org
.eclipse
.jdt
.annotation
.NonNull
;
24 import org
.eclipse
.test
.performance
.Dimension
;
25 import org
.eclipse
.test
.performance
.Performance
;
26 import org
.eclipse
.test
.performance
.PerformanceMeter
;
27 import org
.eclipse
.tracecompass
.common
.core
.NonNullUtils
;
28 import org
.eclipse
.tracecompass
.internal
.statesystem
.core
.backend
.historytree
.HistoryTreeBackend
;
29 import org
.eclipse
.tracecompass
.statesystem
.core
.ITmfStateSystemBuilder
;
30 import org
.eclipse
.tracecompass
.statesystem
.core
.StateSystemFactory
;
31 import org
.eclipse
.tracecompass
.statesystem
.core
.StateSystemUtils
;
32 import org
.eclipse
.tracecompass
.statesystem
.core
.backend
.IStateHistoryBackend
;
33 import org
.eclipse
.tracecompass
.statesystem
.core
.backend
.StateHistoryBackendFactory
;
34 import org
.eclipse
.tracecompass
.statesystem
.core
.exceptions
.AttributeNotFoundException
;
35 import org
.eclipse
.tracecompass
.statesystem
.core
.exceptions
.StateSystemDisposedException
;
36 import org
.eclipse
.tracecompass
.statesystem
.core
.exceptions
.StateValueTypeException
;
37 import org
.eclipse
.tracecompass
.statesystem
.core
.statevalue
.ITmfStateValue
;
38 import org
.eclipse
.tracecompass
.statesystem
.core
.statevalue
.TmfStateValue
;
39 import org
.junit
.Test
;
40 import org
.junit
.runner
.RunWith
;
41 import org
.junit
.runners
.Parameterized
;
42 import org
.junit
.runners
.Parameterized
.Parameters
;
44 import com
.google
.common
.collect
.ImmutableList
;
47 * This class benchmarks writing intervals to the state system and querying them
48 * using a history tree backend
50 * @author Geneviève Bastien
52 @RunWith(Parameterized
.class)
53 public class HistoryTreeBackendBenchmark
{
55 private static final @NonNull String TEST_PREFIX
= "org.eclipse.tracecompass#History Tree Backend#";
56 private static final @NonNull String TEST_BUILDING_ID
= "Build: ";
57 private static final @NonNull String TEST_SINGLE_QUERY_ID
= "Single Queries: ";
58 private static final @NonNull String TEST_FULL_QUERY_ID
= "Full Queries: ";
59 private static final @NonNull String TEST_QUERY_RANGE_ID
= "Query History Range: ";
60 private static final @NonNull String ROOT_NODE
= "root";
61 private static final int QUEUE_SIZE
= 10000;
62 private static final long SEED
= 5575784704147L;
63 private static final int QUERY_COUNT
= 100;
64 private static final int INTERVAL_AVG_TIME
= 1000;
66 /* Values for the average case */
67 private static final int DEFAULT_NB_ATTRIB
= 1500;
68 private static final int DEFAULT_NB_INTERVALS
= 500;
69 private static final int DEFAULT_LOOP_COUNT
= 40;
71 private File fTempFile
;
72 private final String fName
;
73 private final String fShortName
;
74 private final int fNbAttrib
;
75 private final int fNbAvgIntervals
;
76 private final int fNbLoops
;
77 private final HTBValues fValues
;
78 private final IIntervalDistribution fDistributionMethod
;
84 * The name of the test
86 * A short name for this scenario (at most 40 characters,
87 * otherwise it will be truncated in the DB)
89 * The number of attributes
90 * @param nbAvgIntervals
91 * An idea on the number of intervals per attributes. The exact
92 * number will depend on the interval time distribution
94 * The number of times to execute the benchmark
96 * The values to put in the history tree
97 * @param distributionMethod
98 * A distribution method that will return the next interval
99 * duration according to an algorithm
101 public HistoryTreeBackendBenchmark(String name
, String shortName
, int nbAttrib
, int nbAvgIntervals
, int nbLoops
, HTBValues values
, IIntervalDistribution distributionMethod
) {
103 fShortName
= shortName
;
104 fNbAttrib
= nbAttrib
;
105 fNbAvgIntervals
= nbAvgIntervals
;
108 fDistributionMethod
= distributionMethod
;
111 /* A list of values to use for the intervals */
112 private enum HTBValues
{
113 INTEGERS(ImmutableList
.of(TmfStateValue
.newValueInt(1), TmfStateValue
.newValueInt(2), TmfStateValue
.newValueInt(3))),
114 STRINGS(ImmutableList
.of(TmfStateValue
.newValueString("abc"), TmfStateValue
.newValueString("def"), TmfStateValue
.newValueString("wihi!"))),
115 LONGS(ImmutableList
.of(TmfStateValue
.newValueLong(Long
.MAX_VALUE
), TmfStateValue
.newValueLong(1L), TmfStateValue
.newValueLong(1234567L))),
116 DOUBLES(ImmutableList
.of(TmfStateValue
.newValueDouble(Double
.MAX_VALUE
), TmfStateValue
.newValueDouble(1.0), TmfStateValue
.newValueDouble(123.456)));
118 private final List
<ITmfStateValue
> fValues
;
120 private HTBValues(List
<ITmfStateValue
> values
) {
124 public List
<ITmfStateValue
> getValues() {
130 private static interface IIntervalDistribution
{
131 long getNextEndTime(Random randomGenerator
, long limit
);
134 /* Generate interval duration uniformly distributed in the range [1, 2l] */
135 private static IIntervalDistribution UNIFORM
= (r
, l
) -> {
136 long nextLong
= r
.nextLong();
137 long nextDelta
= l
+ (nextLong
% l
);
142 * Generates interval duration in the range [1, 2l] with 75% between [0.5l,
145 private static IIntervalDistribution CLOSER_TO_LIMIT
= (r
, l
) -> {
147 * This method will return interval time closer to the specified limit
149 double nextDouble
= r
.nextDouble();
150 int sign
= (nextDouble
< 0) ?
-1 : 1;
151 long nextDelta
= l
+ (long) (Math
.sqrt(nextDouble
) * l
* sign
);
156 * Generates interval duration in the range [1, 2l] with 75% between [0.5l,
157 * 1.5l], but with 10% outliers between [2l, 5l]
159 private static IIntervalDistribution CLOSER_TO_LIMIT_10_PERCENT_OUTLIERS
= (r
, l
) -> {
160 long nextLong
= Math
.abs(r
.nextLong());
161 /* Is this an outlier */
162 if ((nextLong
% 100) < 10) {
163 /* Create an outlier that can go up to 5 times the limit */
164 return l
+ (((nextLong
% 3) + 1) * l
) + (nextLong
% l
);
166 /* Otherwise follow a distribution with more values around the limit */
167 double nextDouble
= r
.nextDouble();
168 int sign
= (nextDouble
< 0) ?
-1 : 1;
169 long nextDelta
= l
+ (long) (Math
.sqrt(nextDouble
) * l
* sign
);
174 * @return The arrays of parameters
176 @Parameters(name
= "{index}: {0}")
177 public static Iterable
<Object
[]> getParameters() {
178 return Arrays
.asList(new Object
[][] {
179 { "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
},
180 { "Vertical scaling (more attributes)", "Vertical scaling", 3500, DEFAULT_NB_INTERVALS
, 5, HTBValues
.INTEGERS
, CLOSER_TO_LIMIT
},
181 { "Horizontal scaling (more intervals/attribute)", "Horizontal scaling", DEFAULT_NB_ATTRIB
, 20000, 10, HTBValues
.INTEGERS
, CLOSER_TO_LIMIT
},
182 { "Interval durations uniformly distributed within [1, 2l]", "Uniform distribution of intervals", DEFAULT_NB_ATTRIB
, DEFAULT_NB_INTERVALS
, DEFAULT_LOOP_COUNT
, HTBValues
.INTEGERS
, UNIFORM
},
183 { "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
},
184 { "Data type: strings", "Data type: strings", DEFAULT_NB_ATTRIB
, DEFAULT_NB_INTERVALS
, DEFAULT_LOOP_COUNT
, HTBValues
.STRINGS
, CLOSER_TO_LIMIT
},
185 { "Data type: longs", "Data type: longs", DEFAULT_NB_ATTRIB
, DEFAULT_NB_INTERVALS
, DEFAULT_LOOP_COUNT
, HTBValues
.LONGS
, CLOSER_TO_LIMIT
},
186 { "Data type: doubles", "Data type: doubles", DEFAULT_NB_ATTRIB
, DEFAULT_NB_INTERVALS
, DEFAULT_LOOP_COUNT
, HTBValues
.DOUBLES
, CLOSER_TO_LIMIT
},
191 * Create the temporary file for this history tree
193 private void createFile() {
195 fTempFile
= File
.createTempFile("tmpStateSystem", null);
196 } catch (IOException e
) {
197 fail(e
.getMessage());
202 * Delete the temporary history tree file after the test
204 private void deleteFile() {
205 if (fTempFile
!= null) {
211 private static class QuarkEvent
implements Comparable
<QuarkEvent
> {
212 private final int fQuark
;
213 private long fNextEventTime
;
214 private final List
<ITmfStateValue
> fPossibleValues
;
215 private int fNextValue
= 0;
217 public QuarkEvent(int quark
, long nextEventTime
, List
<ITmfStateValue
> valuesList
) {
219 fNextEventTime
= nextEventTime
;
220 fPossibleValues
= valuesList
;
223 public int getQuark() {
227 public long getNextEventTime() {
228 return fNextEventTime
;
231 public void setNextEventTime(long t
) {
235 public ITmfStateValue
getNextValue() {
236 ITmfStateValue value
= fPossibleValues
.get(fNextValue
);
238 if (fNextValue
>= fPossibleValues
.size()) {
245 public int compareTo(QuarkEvent other
) {
246 return Long
.compare(fNextEventTime
, other
.getNextEventTime());
251 * Benchmarks creating, single querying and full querying the state system
254 public void testBenchmark() {
255 /* Check arguments */
256 long totalTime
= this.fNbAvgIntervals
* INTERVAL_AVG_TIME
;
258 Performance perf
= Performance
.getDefault();
259 PerformanceMeter pmBuild
= perf
.createPerformanceMeter(TEST_PREFIX
+ TEST_BUILDING_ID
+ fName
);
260 perf
.tagAsSummary(pmBuild
, TEST_BUILDING_ID
+ fShortName
, Dimension
.CPU_TIME
);
262 PerformanceMeter pmSingleQuery
= perf
.createPerformanceMeter(TEST_PREFIX
+ TEST_SINGLE_QUERY_ID
+ fName
);
263 perf
.tagAsSummary(pmSingleQuery
, TEST_SINGLE_QUERY_ID
+ fShortName
, Dimension
.CPU_TIME
);
265 PerformanceMeter pmFullQuery
= perf
.createPerformanceMeter(TEST_PREFIX
+ TEST_FULL_QUERY_ID
+ fName
);
266 perf
.tagAsSummary(pmFullQuery
, TEST_FULL_QUERY_ID
+ fShortName
, Dimension
.CPU_TIME
);
268 PerformanceMeter pmRangeQuery
= perf
.createPerformanceMeter(TEST_PREFIX
+ TEST_QUERY_RANGE_ID
+ fName
);
269 perf
.tagAsSummary(pmRangeQuery
, TEST_QUERY_RANGE_ID
+ fShortName
, Dimension
.CPU_TIME
);
271 for (int i
= 0; i
< fNbLoops
; i
++) {
273 /* Create the state system */
275 IStateHistoryBackend backend
= StateHistoryBackendFactory
.createHistoryTreeBackendNewFile(TEST_BUILDING_ID
, NonNullUtils
.checkNotNull(fTempFile
), 1, 1, QUEUE_SIZE
);
276 ITmfStateSystemBuilder ss
= StateSystemFactory
.newStateSystem(backend
);
278 /* Initialize the attributes */
279 Queue
<QuarkEvent
> quarkEvents
= new PriorityQueue
<>(fNbAttrib
);
280 Random randomGenerator
= new Random(SEED
);
281 int rootQuark
= ss
.getQuarkAbsoluteAndAdd(ROOT_NODE
);
283 /* Create all attributes before testing */
284 for (int j
= 0; j
< fNbAttrib
; j
++) {
285 int quark
= ss
.getQuarkRelativeAndAdd(rootQuark
, String
.valueOf(j
));
286 quarkEvents
.add(new QuarkEvent(quark
, (Math
.abs(randomGenerator
.nextLong()) % INTERVAL_AVG_TIME
) + 1, fValues
.getValues()));
289 /* Adds random intervals to the state system */
292 QuarkEvent quarkEvent
= quarkEvents
.poll();
293 if (quarkEvent
== null) {
296 long eventTime
= quarkEvent
.getNextEventTime();
297 ss
.modifyAttribute(eventTime
, quarkEvent
.getNextValue(), quarkEvent
.getQuark());
298 long nextDelta
= fDistributionMethod
.getNextEndTime(randomGenerator
, INTERVAL_AVG_TIME
);
299 long nextEndTime
= eventTime
+ nextDelta
;
300 if (nextEndTime
<= totalTime
) {
301 quarkEvent
.setNextEventTime(nextEndTime
);
302 quarkEvents
.add(quarkEvent
);
305 ss
.closeHistory(totalTime
);
309 * Benchmark the single queries: for each random timestamp,
310 * query a random attribute
312 List
<Integer
> subAttributes
= ss
.getSubAttributes(rootQuark
, false);
313 pmSingleQuery
.start();
314 for (int j
= 0; j
< QUERY_COUNT
; j
++) {
315 long ts
= getNextRandomValue(randomGenerator
, totalTime
);
316 int attrib
= (int) getNextRandomValue(randomGenerator
, subAttributes
.size());
317 ss
.querySingleState(ts
, attrib
);
319 pmSingleQuery
.stop();
321 /* Benchmark the history range query of 10 attributes */
322 pmRangeQuery
.start();
323 for (int j
= 0; j
< 10; j
++) {
324 int attrib
= (int) getNextRandomValue(randomGenerator
, subAttributes
.size());
325 StateSystemUtils
.queryHistoryRange(ss
, attrib
, ss
.getStartTime(), ss
.getCurrentEndTime());
329 /* Benchmark the full queries */
331 for (int j
= 0; j
< QUERY_COUNT
; j
++) {
332 long ts
= getNextRandomValue(randomGenerator
, totalTime
);
333 ss
.queryFullState(ts
);
337 /* Output some data on the file */
339 if (backend
instanceof HistoryTreeBackend
) {
340 HistoryTreeBackend htBackend
= (HistoryTreeBackend
) backend
;
341 System
.out
.println("History tree file size: " + FileUtils
.byteCountToDisplaySize(htBackend
.getFileSize()));
342 System
.out
.println("Average node usage: " + htBackend
.getAverageNodeUsage());
346 } catch (IOException
| StateValueTypeException
| AttributeNotFoundException
| StateSystemDisposedException e
) {
347 fail(e
.getMessage());
353 pmSingleQuery
.commit();
354 pmFullQuery
.commit();
355 pmRangeQuery
.commit();
359 * Get a next random value between 1 and a boundary.
361 private static long getNextRandomValue(Random randomGenerator
, long limit
) {
362 long nextLong
= Math
.abs(randomGenerator
.nextLong());
363 long nextDelta
= (nextLong
% limit
) + 1;