diff --git a/core/src/main/java/com/yahoo/ycsb/generator/UnixEpochTimestampGenerator.java b/core/src/main/java/com/yahoo/ycsb/generator/UnixEpochTimestampGenerator.java new file mode 100644 index 00000000..914bb7fe --- /dev/null +++ b/core/src/main/java/com/yahoo/ycsb/generator/UnixEpochTimestampGenerator.java @@ -0,0 +1,178 @@ +/** + * Copyright (c) 2016 YCSB contributors. All rights reserved. + * + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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; + +/** + * A generator that produces Unix epoch timestamps in seconds, milli, micro or + * nanoseconds and increments the stamp a given interval each time + * {@link #nextValue()} is called. The result is emitted as a long in the same + * way calls to {@code System.currentTimeMillis()} and + * {@code System.nanoTime()} behave. + *

+ * By default, the current system time of the host is used as the starting + * timestamp. Calling {@link #initalizeTimestamp(long)} can adjust the timestamp + * back or forward in time. For example, if a workload will generate an hour of + * data at 1 minute intervals, then to set the start timestamp an hour in the past + * from the current run, use: + *

{@code
+ * UnixEpochTimestampGenerator generator = new UnixEpochTimestampGenerator();
+ * generator.initalizeTimestamp(-60);
+ * }
+ * A constructor is also present for setting an explicit start time. + * Negative intervals are supported as well for iterating back in time. + *

+ * WARNING: This generator is not thread safe and should not called from multiple + * threads. + */ +public class UnixEpochTimestampGenerator extends Generator { + + /** The current timestamp that will be incremented. */ + private long currentTimestamp; + + /** The last used timestamp. Should always be one interval behind current. */ + private long lastTimestamp; + + /** The interval to increment by. Multiplied by {@link #timeUnits}. */ + private long interval; + + /** The units of time the interval represents. */ + private TimeUnit timeUnits; + + /** + * Default ctor with the current system time and a 60 second interval. + */ + public UnixEpochTimestampGenerator() { + this(60, TimeUnit.SECONDS); + } + + /** + * Ctor that uses the current system time as current. + * @param interval The interval for incrementing the timestamp. + * @param timeUnits The units of time the increment represents. + */ + public UnixEpochTimestampGenerator(final long interval, final TimeUnit timeUnits) { + this.interval = interval; + this.timeUnits = timeUnits; + // move the first timestamp by 1 interval so that the first call to nextValue + // returns this timestamp + initalizeTimestamp(-1); + currentTimestamp -= getOffset(1); + lastTimestamp = currentTimestamp; + } + + /** + * Ctor for supplying a starting timestamp. + * @param interval The interval for incrementing the timestamp. + * @param timeUnits The units of time the increment represents. + * @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()} + * or seconds and any interval above, {@code System.currentTimeMillis() / 1000} + */ + public UnixEpochTimestampGenerator(final long interval, final TimeUnit timeUnits, + final long startTimestamp) { + this.interval = interval; + 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); + lastTimestamp = currentTimestamp - getOffset(1); + } + + /** + * Sets the starting timestamp to the current system time plus the interval offset. + * E.g. to set the time an hour in the past, supply a value of {@code -60}. + * @param intervalOffset The interval to increment or decrement by. + */ + public void initalizeTimestamp(final long intervalOffset) { + switch (timeUnits) { + case NANOSECONDS: + currentTimestamp = System.nanoTime() + getOffset(intervalOffset); + break; + case MICROSECONDS: + currentTimestamp = (System.nanoTime() / 1000) + getOffset(intervalOffset); + break; + case MILLISECONDS: + currentTimestamp = System.currentTimeMillis() + getOffset(intervalOffset); + break; + case SECONDS: + currentTimestamp = (System.currentTimeMillis() / 1000) + + getOffset(intervalOffset); + break; + case MINUTES: + currentTimestamp = (System.currentTimeMillis() / 1000) + + getOffset(intervalOffset); + break; + case HOURS: + currentTimestamp = (System.currentTimeMillis() / 1000) + + getOffset(intervalOffset); + break; + case DAYS: + currentTimestamp = (System.currentTimeMillis() / 1000) + + getOffset(intervalOffset); + break; + default: + throw new IllegalArgumentException("Unhandled time unit type: " + timeUnits); + } + } + + @Override + public Long nextValue() { + lastTimestamp = currentTimestamp; + currentTimestamp += getOffset(1); + return currentTimestamp; + } + + /** + * Returns the proper increment offset to use given the interval and timeunits. + * @param intervalOffset The amount of offset to multiply by. + * @return An offset value to adjust the timestamp by. + */ + public long getOffset(final long intervalOffset) { + switch (timeUnits) { + case NANOSECONDS: + case MICROSECONDS: + case MILLISECONDS: + case SECONDS: + return intervalOffset * interval; + case MINUTES: + return intervalOffset * interval * (long) 60; + case HOURS: + return intervalOffset * interval * (long) (60 * 60); + case DAYS: + return intervalOffset * interval * (long) (60 * 60 * 24); + default: + throw new IllegalArgumentException("Unhandled time unit type: " + timeUnits); + } + } + + @Override + public Long lastValue() { + return lastTimestamp; + } + + /** @return The current timestamp as set by the last call to {@link #nextValue()} */ + public long currentValue() { + return currentTimestamp; + } + +} diff --git a/core/src/test/java/com/yahoo/ycsb/generator/TestUnixEpochTimestampGenerator.java b/core/src/test/java/com/yahoo/ycsb/generator/TestUnixEpochTimestampGenerator.java new file mode 100644 index 00000000..f92ec0f6 --- /dev/null +++ b/core/src/test/java/com/yahoo/ycsb/generator/TestUnixEpochTimestampGenerator.java @@ -0,0 +1,122 @@ +/** + * Copyright (c) 2016 YCSB contributors. All rights reserved. + * + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 java.util.concurrent.TimeUnit; +import org.testng.annotations.Test; + +public class TestUnixEpochTimestampGenerator { + + @Test + public void defaultCtor() throws Exception { + final UnixEpochTimestampGenerator generator = + new UnixEpochTimestampGenerator(); + final long startTime = generator.currentValue(); + assertEquals((long) generator.nextValue(), startTime + 60); + assertEquals((long) generator.lastValue(), startTime); + assertEquals((long) generator.nextValue(), startTime + 120); + assertEquals((long) generator.lastValue(), startTime + 60); + assertEquals((long) generator.nextValue(), startTime + 180); + } + + @Test + public void ctorWithIntervalAndUnits() throws Exception { + final UnixEpochTimestampGenerator generator = + new UnixEpochTimestampGenerator(120, TimeUnit.SECONDS); + final long startTime = generator.currentValue(); + assertEquals((long) generator.nextValue(), startTime + 120); + assertEquals((long) generator.lastValue(), startTime); + assertEquals((long) generator.nextValue(), startTime + 240); + assertEquals((long) generator.lastValue(), startTime + 120); + } + + @Test + public void ctorWithIntervalAndUnitsAndStart() throws Exception { + final UnixEpochTimestampGenerator generator = + new UnixEpochTimestampGenerator(120, TimeUnit.SECONDS, 1072915200L); + assertEquals((long) generator.nextValue(), 1072915200L); + assertEquals((long) generator.lastValue(), 1072915200L - 120); + assertEquals((long) generator.nextValue(), 1072915200L + 120); + assertEquals((long) generator.lastValue(), 1072915200L); + } + + @Test + public void variousIntervalsAndUnits() throws Exception { + // negatives could happen, just start and roll back in time + UnixEpochTimestampGenerator generator = + new UnixEpochTimestampGenerator(-60, TimeUnit.SECONDS); + long startTime = generator.currentValue(); + assertEquals((long) generator.nextValue(), startTime - 60); + assertEquals((long) generator.lastValue(), startTime); + assertEquals((long) generator.nextValue(), startTime - 120); + assertEquals((long) generator.lastValue(), startTime - 60); + + generator = new UnixEpochTimestampGenerator(100, TimeUnit.NANOSECONDS); + startTime = generator.currentValue(); + assertEquals((long) generator.nextValue(), startTime + 100); + assertEquals((long) generator.lastValue(), startTime); + assertEquals((long) generator.nextValue(), startTime + 200); + assertEquals((long) generator.lastValue(), startTime + 100); + + generator = new UnixEpochTimestampGenerator(100, TimeUnit.MICROSECONDS); + startTime = generator.currentValue(); + assertEquals((long) generator.nextValue(), startTime + 100); + assertEquals((long) generator.lastValue(), startTime); + assertEquals((long) generator.nextValue(), startTime + 200); + assertEquals((long) generator.lastValue(), startTime + 100); + + generator = new UnixEpochTimestampGenerator(100, TimeUnit.MILLISECONDS); + startTime = generator.currentValue(); + assertEquals((long) generator.nextValue(), startTime + 100); + assertEquals((long) generator.lastValue(), startTime); + assertEquals((long) generator.nextValue(), startTime + 200); + assertEquals((long) generator.lastValue(), startTime + 100); + + generator = new UnixEpochTimestampGenerator(100, TimeUnit.SECONDS); + startTime = generator.currentValue(); + assertEquals((long) generator.nextValue(), startTime + 100); + assertEquals((long) generator.lastValue(), startTime); + assertEquals((long) generator.nextValue(), startTime + 200); + assertEquals((long) generator.lastValue(), startTime + 100); + + generator = new UnixEpochTimestampGenerator(1, TimeUnit.MINUTES); + startTime = generator.currentValue(); + assertEquals((long) generator.nextValue(), startTime + (1 * 60)); + assertEquals((long) generator.lastValue(), startTime); + assertEquals((long) generator.nextValue(), startTime + (2 * 60)); + assertEquals((long) generator.lastValue(), startTime + (1 * 60)); + + generator = new UnixEpochTimestampGenerator(1, TimeUnit.HOURS); + startTime = generator.currentValue(); + assertEquals((long) generator.nextValue(), startTime + (1 * 60 * 60)); + assertEquals((long) generator.lastValue(), startTime); + assertEquals((long) generator.nextValue(), startTime + (2 * 60 * 60)); + assertEquals((long) generator.lastValue(), startTime + (1 * 60 * 60)); + + generator = new UnixEpochTimestampGenerator(1, TimeUnit.DAYS); + startTime = generator.currentValue(); + assertEquals((long) generator.nextValue(), startTime + (1 * 60 * 60 * 24)); + assertEquals((long) generator.lastValue(), startTime); + assertEquals((long) generator.nextValue(), startTime + (2 * 60 * 60 * 24)); + assertEquals((long) generator.lastValue(), startTime + (1 * 60 * 60 * 24)); + } + + // TODO - With PowerMockito we could UT the initializeTimestamp(long) call. + // Otherwise it would involve creating more functions and that would get ugly. +}