Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 92 additions & 2 deletions src/main/java/org/apache/commons/lang3/time/StopWatch.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@

import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
Expand All @@ -27,6 +30,7 @@
import org.apache.commons.lang3.function.FailableConsumer;
import org.apache.commons.lang3.function.FailableRunnable;
import org.apache.commons.lang3.function.FailableSupplier;
import org.apache.commons.lang3.tuple.ImmutablePair;

/**
* {@link StopWatch} provides a convenient API for timings.
Expand Down Expand Up @@ -248,6 +252,11 @@ public static StopWatch createStarted() {
*/
private long stopTimeNanos;

/**
* The split list.
*/
private final List<Split> splits = new ArrayList<>();

/**
* Constructs a new instance.
*/
Expand Down Expand Up @@ -326,6 +335,16 @@ public String getMessage() {
return message;
}

/**
* Gets the split list.
*
* @return the list of splits.
* @since 3.20.0
*/
public List<Split> getSplits() {
return Collections.unmodifiableList(splits);
}

/**
* Gets the <em>elapsed</em> time in nanoseconds.
*
Expand Down Expand Up @@ -382,7 +401,7 @@ public long getSplitNanoTime() {
if (splitState != SplitState.SPLIT) {
throw new IllegalStateException("Stopwatch must be split to get the split time.");
}
return stopTimeNanos - startTimeNanos;
return splits.get(splits.size() - 1).getRight().toNanos();
}

/**
Expand Down Expand Up @@ -557,6 +576,7 @@ private long nanosToMillis(final long nanos) {
public void reset() {
runningState = State.UNSTARTED;
splitState = SplitState.UNSPLIT;
splits.clear();
}

/**
Expand Down Expand Up @@ -624,6 +644,28 @@ public void split() {
}
stopTimeNanos = System.nanoTime();
splitState = SplitState.SPLIT;
splits.add(new Split(String.valueOf(splits.size()), Duration.ofNanos(stopTimeNanos - startTimeNanos)));
}

/**
* Splits the time with a label.
*
* <p>
* This method sets the stop time of the watch to allow a time to be extracted. The start time is unaffected, enabling {@link #unsplit()} to continue the
* timing from the original start point.
* </p>
*
* @param label A message for string presentation.
* @throws IllegalStateException if the StopWatch is not running.
* @since 3.20.0
*/
public void split(final String label) {
if (runningState != State.RUNNING) {
throw new IllegalStateException("Stopwatch is not running.");
}
stopTimeNanos = System.nanoTime();
splitState = SplitState.SPLIT;
splits.add(new Split(label, Duration.ofNanos(stopTimeNanos - startTimeNanos)));
}

/**
Expand All @@ -645,6 +687,7 @@ public void start() {
startTimeNanos = System.nanoTime();
startInstant = Instant.now();
runningState = State.RUNNING;
splits.clear();
}

/**
Expand Down Expand Up @@ -697,7 +740,7 @@ public void suspend() {
}

/**
* Gets a summary of the split time that this StopWatch recorded as a string.
* Gets a summary of the last split time that this StopWatch recorded as a string.
*
* <p>
* The format used is ISO 8601-like, [<em>message</em> ]<em>hours</em>:<em>minutes</em>:<em>seconds</em>.<em>milliseconds</em>.
Expand Down Expand Up @@ -744,6 +787,53 @@ public void unsplit() {
throw new IllegalStateException("Stopwatch has not been split.");
}
splitState = SplitState.UNSPLIT;
splits.remove(splits.size() - 1);
}

/**
* Stores a split as a label and duration.
*
* @since 3.20.0
*/
public static final class Split extends ImmutablePair<String, Duration> {

/**
* Constructs a Split object with label and duration.
*
* @param label Label for this split.
* @param duration Duration for this split.
*/
public Split(String label, Duration duration) {
super(label, duration);
}

/**
* Gets the label of this split.
*
* @return The label of this split.
*/
public String getLabel() {
return getLeft();
}

/**
* Gets the duration of this split.
*
* @return The duration of this split..
*/
public Duration getDuration() {
return getRight();
}

/**
* Converts this instance to a string.
*
* @return this instance to a string.
*/
@Override
public String toString() {
return String.format("Split [%s, %s])", getLabel(), getDuration());
}
}

}
52 changes: 52 additions & 0 deletions src/test/java/org/apache/commons/lang3/time/StopWatchTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

Expand Down Expand Up @@ -69,8 +71,11 @@ private StopWatch createMockStopWatch(final long nanos) {
private StopWatch set(final StopWatch watch, final long nanos) {
try {
final long currentNanos = System.nanoTime();
final List<StopWatch.Split> splits = new ArrayList<>();
splits.add(new StopWatch.Split(String.valueOf(0), Duration.ofNanos(nanos)));
FieldUtils.writeField(watch, "startTimeNanos", currentNanos - nanos, true);
FieldUtils.writeField(watch, "stopTimeNanos", currentNanos, true);
FieldUtils.writeField(watch, "splits", splits, true);
} catch (final IllegalAccessException e) {
return null;
}
Expand Down Expand Up @@ -215,6 +220,23 @@ void testGetSplitDuration() {
assertEquals(Duration.ofNanos(123456), watch.getSplitDuration());
}

@Test
void testGetSplits() {
final StopWatch stopWatch = StopWatch.create();
assertTrue(stopWatch.getSplits().isEmpty());
stopWatch.start();
testGetSplits(stopWatch);
testGetSplits(StopWatch.createStarted());
}

private void testGetSplits(final StopWatch watch) {
assertTrue(watch.getSplits().isEmpty());
watch.split();
assertEquals(1, watch.getSplits().size());
watch.unsplit();
assertTrue(watch.getSplits().isEmpty());
}

@Test
void testGetStartInstant() {
final long beforeStopWatchMillis = System.currentTimeMillis();
Expand Down Expand Up @@ -500,6 +522,36 @@ void testToStringWithMessage() throws InterruptedException {
assertEquals(SPLIT_CLOCK_STR_LEN + MESSAGE.length() + 1, splitStr.length(), "Formatted split string not the correct length");
}

@Test
void testSplitsWithStringLabels() {
final StopWatch watch = new StopWatch();
final String firstLabel = "one";
final String secondLabel = "two";
final String thirdLabel = "three";
watch.start();
// starting splits
watch.split(firstLabel);
watch.split(secondLabel);
watch.split(thirdLabel);
watch.stop();
// getting splits
final List<StopWatch.Split> splits = watch.getSplits();
// check size
assertEquals(3, splits.size());
// check labels
assertEquals(firstLabel, splits.get(0).getLabel());
assertEquals(secondLabel, splits.get(1).getLabel());
assertEquals(thirdLabel, splits.get(2).getLabel());
// check time in nanos
assertTrue(splits.get(0).getDuration().toNanos() > 0);
assertTrue(splits.get(1).getDuration().toNanos() > 0);
assertTrue(splits.get(2).getDuration().toNanos() > 0);
// We can only unsplit once
watch.unsplit();
assertEquals(2, watch.getSplits().size());
assertThrows(IllegalStateException.class, watch::unsplit);
}

private int throwIOException() throws IOException {
throw new IOException("A");
}
Expand Down