Skip to content
Snippets Groups Projects
Commit b945c660 authored by Chris Larsen's avatar Chris Larsen
Browse files

[core] Add the RandomDiscreteTimestampGenerator to generate a range of

timestamps in a non-repeating random order.
Modify UnixEpochTimestampGenerator so that the random generator can
extend it.

Signed-off-by: default avatarChris Larsen <clarsen@yahoo-inc.com>
parent b1d4ac62
No related branches found
No related tags found
No related merge requests found
/**
* Copyright (c) 2017 YCSB contributors. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you
* may not use this file except in compliance with the License. You
* may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License. See accompanying
* LICENSE file.
*/
package com.yahoo.ycsb.generator;
import java.util.concurrent.TimeUnit;
import com.yahoo.ycsb.Utils;
/**
* A generator that picks from a discrete set of offsets from a base Unix Epoch
* timestamp that returns timestamps in a random order with the guarantee that
* each timestamp is only returned once.
* <p>
* TODO - It would be best to implement some kind of psuedo non-repeating random
* generator for this as it's likely OK that some small percentage of values are
* repeated. For now we just generate all of the offsets in an array, shuffle
* it and then iterate over the array.
* <p>
* Note that {@link #MAX_INTERVALS} defines a hard limit on the size of the
* offset array so that we don't completely blow out the heap.
* <p>
* The constructor parameter {@code intervals} determines how many values will be
* returned by the generator. For example, if the {@code interval} is 60 and the
* {@code timeUnits} are set to {@link TimeUnit#SECONDS} and {@code intervals}
* is set to 60, then the consumer can call {@link #nextValue()} 60 times for
* timestamps within an hour.
*/
public class RandomDiscreteTimestampGenerator extends UnixEpochTimestampGenerator {
/** A hard limit on the size of the offsets array to a void using too much heap. */
public static final int MAX_INTERVALS = 16777216;
/** The total number of intervals for this generator. */
private final int intervals;
// can't be primitives due to the generic params on the sort function :(
/** The array of generated offsets from the base time. */
private final Integer[] offsets;
/** The current index into the offsets array. */
private int offsetIndex;
/**
* Ctor that uses the current system time as current.
* @param interval The interval between timestamps.
* @param timeUnits The time units of the returned Unix Epoch timestamp (as well
* as the units for the interval).
* @param intervals The total number of intervals for the generator.
* @throws IllegalArgumentException if the intervals is larger than {@link #MAX_INTERVALS}
*/
public RandomDiscreteTimestampGenerator(final long interval, final TimeUnit timeUnits,
final int intervals) {
super(interval, timeUnits);
this.intervals = intervals;
offsets = new Integer[intervals];
setup();
}
/**
* Ctor for supplying a starting timestamp.
* The interval between timestamps.
* @param timeUnits The time units of the returned Unix Epoch timestamp (as well
* as the units for the interval).
* @param startTimestamp The start timestamp to use.
* NOTE that this must match the time units used for the interval.
* If the units are in nanoseconds, provide a nanosecond timestamp {@code System.nanoTime()}
* or in microseconds, {@code System.nanoTime() / 1000}
* or in millis, {@code System.currentTimeMillis()}
* @param intervals The total number of intervals for the generator.
* @throws IllegalArgumentException if the intervals is larger than {@link #MAX_INTERVALS}
*/
public RandomDiscreteTimestampGenerator(final long interval, final TimeUnit timeUnits,
final long startTimestamp, final int intervals) {
super(interval, timeUnits, startTimestamp);
this.intervals = intervals;
offsets = new Integer[intervals];
setup();
}
/**
* Generates the offsets and shuffles the array.
*/
private void setup() {
if (intervals > MAX_INTERVALS) {
throw new IllegalArgumentException("Too many intervals for the in-memory "
+ "array. The limit is " + MAX_INTERVALS + ".");
}
offsetIndex = 0;
for (int i = 0; i < intervals; i++) {
offsets[i] = i;
}
Utils.shuffleArray(offsets);
}
@Override
public Long nextValue() {
if (offsetIndex >= offsets.length) {
throw new IllegalStateException("Reached the end of the random timestamp "
+ "intervals: " + offsetIndex);
}
lastTimestamp = currentTimestamp;
currentTimestamp = startTimestamp + (offsets[offsetIndex++] * getOffset(1));
return currentTimestamp;
}
}
\ No newline at end of file
......@@ -43,17 +43,20 @@ import java.util.concurrent.TimeUnit;
*/
public class UnixEpochTimestampGenerator extends Generator<Long> {
/** The base timestamp used as a starting reference. */
protected long startTimestamp;
/** The current timestamp that will be incremented. */
private long currentTimestamp;
protected long currentTimestamp;
/** The last used timestamp. Should always be one interval behind current. */
private long lastTimestamp;
protected long lastTimestamp;
/** The interval to increment by. Multiplied by {@link #timeUnits}. */
private long interval;
protected long interval;
/** The units of time the interval represents. */
private TimeUnit timeUnits;
protected TimeUnit timeUnits;
/**
* Default ctor with the current system time and a 60 second interval.
......@@ -94,7 +97,8 @@ public class UnixEpochTimestampGenerator extends Generator<Long> {
this.timeUnits = timeUnits;
// move the first timestamp by 1 interval so that the first call to nextValue
// returns this timestamp
this.currentTimestamp = startTimestamp - getOffset(1);
currentTimestamp = startTimestamp - getOffset(1);
this.startTimestamp = currentTimestamp;
lastTimestamp = currentTimestamp - getOffset(1);
}
......@@ -133,6 +137,7 @@ public class UnixEpochTimestampGenerator extends Generator<Long> {
default:
throw new IllegalArgumentException("Unhandled time unit type: " + timeUnits);
}
startTimestamp = currentTimestamp;
}
@Override
......@@ -175,4 +180,4 @@ public class UnixEpochTimestampGenerator extends Generator<Long> {
return currentTimestamp;
}
}
}
\ No newline at end of file
/**
* Copyright (c) 2017 YCSB contributors. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you
* may not use this file except in compliance with the License. You
* may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License. See accompanying
* LICENSE file.
*/
package com.yahoo.ycsb.generator;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.fail;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.testng.annotations.Test;
import org.testng.collections.Lists;
public class TestRandomDiscreteTimestampGenerator {
@Test
public void systemTime() throws Exception {
final RandomDiscreteTimestampGenerator generator =
new RandomDiscreteTimestampGenerator(60, TimeUnit.SECONDS, 60);
List<Long> generated = Lists.newArrayList();
for (int i = 0; i < 60; i++) {
generated.add(generator.nextValue());
}
assertEquals(generated.size(), 60);
try {
generator.nextValue();
fail("Expected IllegalStateException");
} catch (IllegalStateException e) { }
}
@Test
public void withStartTime() throws Exception {
final RandomDiscreteTimestampGenerator generator =
new RandomDiscreteTimestampGenerator(60, TimeUnit.SECONDS, 1072915200L, 60);
List<Long> generated = Lists.newArrayList();
for (int i = 0; i < 60; i++) {
generated.add(generator.nextValue());
}
assertEquals(generated.size(), 60);
Collections.sort(generated);
long ts = 1072915200L - 60; // starts 1 interval in the past
for (final long t : generated) {
assertEquals(t, ts);
ts += 60;
}
try {
generator.nextValue();
fail("Expected IllegalStateException");
} catch (IllegalStateException e) { }
}
@Test (expectedExceptions = IllegalArgumentException.class)
public void tooLarge() throws Exception {
new RandomDiscreteTimestampGenerator(60, TimeUnit.SECONDS,
RandomDiscreteTimestampGenerator.MAX_INTERVALS + 1);
}
//TODO - With PowerMockito we could UT the initializeTimestamp(long) call.
// Otherwise it would involve creating more functions and that would get ugly.
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment