Skip to content

Commit

Permalink
[core] Add the RandomDiscreteTimestampGenerator to generate a range of
Browse files Browse the repository at this point in the history
timestamps in a non-repeating random order.
Modify UnixEpochTimestampGenerator so that the random generator can
extend it.
  • Loading branch information
manolama committed Aug 6, 2017
1 parent cf5d2ca commit aae0a56
Show file tree
Hide file tree
Showing 3 changed files with 211 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/**
* 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,20 @@
*/
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.
Expand Down Expand Up @@ -94,7 +97,8 @@ public UnixEpochTimestampGenerator(final long interval, final TimeUnit timeUnits
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);
}

Expand All @@ -107,28 +111,35 @@ public void initalizeTimestamp(final long intervalOffset) {
switch (timeUnits) {
case NANOSECONDS:
currentTimestamp = System.nanoTime() + getOffset(intervalOffset);
startTimestamp = currentTimestamp;
break;
case MICROSECONDS:
currentTimestamp = (System.nanoTime() / 1000) + getOffset(intervalOffset);
startTimestamp = currentTimestamp;
break;
case MILLISECONDS:
currentTimestamp = System.currentTimeMillis() + getOffset(intervalOffset);
startTimestamp = currentTimestamp;
break;
case SECONDS:
currentTimestamp = (System.currentTimeMillis() / 1000) +
getOffset(intervalOffset);
startTimestamp = currentTimestamp;
break;
case MINUTES:
currentTimestamp = (System.currentTimeMillis() / 1000) +
getOffset(intervalOffset);
startTimestamp = currentTimestamp;
break;
case HOURS:
currentTimestamp = (System.currentTimeMillis() / 1000) +
getOffset(intervalOffset);
startTimestamp = currentTimestamp;
break;
case DAYS:
currentTimestamp = (System.currentTimeMillis() / 1000) +
getOffset(intervalOffset);
startTimestamp = currentTimestamp;
break;
default:
throw new IllegalArgumentException("Unhandled time unit type: " + timeUnits);
Expand Down Expand Up @@ -175,4 +186,4 @@ public long currentValue() {
return currentTimestamp;
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* 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 comments on commit aae0a56

Please sign in to comment.