This is an automated email from the ASF dual-hosted git repository.
reschke pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git
The following commit(s) were added to refs/heads/trunk by this push:
new dd791eaa6c OAK-11260: Create replacement for Guava Stopwatch (#2218)
dd791eaa6c is described below
commit dd791eaa6c2ad255ee91c363c727f12baef5885f
Author: Julian Reschke <[email protected]>
AuthorDate: Thu Apr 10 11:28:32 2025 +0200
OAK-11260: Create replacement for Guava Stopwatch (#2218)
---
oak-commons/pom.xml | 3 +-
.../jackrabbit/oak/commons/time/Stopwatch.java | 164 +++++++++++++++++++++
.../jackrabbit/oak/commons/time/package-info.java | 28 ++++
.../commons/{StopWatch.java => TestStopWatch.java} | 2 +-
.../oak/commons/json/JsopStreamTest.java | 4 +-
.../jackrabbit/oak/commons/json/JsopTest.java | 4 +-
.../jackrabbit/oak/commons/time/StopwatchTest.java | 90 +++++++++++
7 files changed, 289 insertions(+), 6 deletions(-)
diff --git a/oak-commons/pom.xml b/oak-commons/pom.xml
index 3b7af3a58d..0f9c378232 100644
--- a/oak-commons/pom.xml
+++ b/oak-commons/pom.xml
@@ -57,7 +57,8 @@
org.apache.jackrabbit.oak.commons.sort,
org.apache.jackrabbit.oak.commons.properties,
org.apache.jackrabbit.oak.commons.jdkcompat,
- org.apache.jackrabbit.oak.commons.pio
+ org.apache.jackrabbit.oak.commons.pio,
+ org.apache.jackrabbit.oak.commons.time
</Export-Package>
</instructions>
</configuration>
diff --git
a/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/time/Stopwatch.java
b/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/time/Stopwatch.java
new file mode 100644
index 0000000000..bcf8278739
--- /dev/null
+++
b/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/time/Stopwatch.java
@@ -0,0 +1,164 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package org.apache.jackrabbit.oak.commons.time;
+
+import org.apache.jackrabbit.oak.commons.conditions.Validate;
+
+import java.time.Clock;
+import java.time.Duration;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+/**
+ * A stop watch based either on a {@link Supplier} of nanoseconds, or a {@link
Clock}.
+ * <p>
+ * The accuracy of measurements depends on the precision of the time source,
which likely depends on platform and
+ * configuration.
+ * <p>
+ * Inspired by Guava's.
+ */
+public final class Stopwatch {
+
+ private long startTime;
+ private long accumulated;
+ private boolean running;
+ private final Supplier<Long> ticker;
+
+ private Stopwatch(Supplier<Long> ticker) {
+ this.ticker = ticker;
+ this.accumulated = 0L;
+ this.startTime = ticker.get();
+ this.running = false;
+ }
+
+ /**
+ * @return a running stop watch, using {@link System#nanoTime()}.
+ */
+ public static Stopwatch createStarted() {
+ return new Stopwatch(Stopwatch::tick).start();
+ }
+
+ /**
+ * @return a running stop watch, using the supplied supplier.
+ */
+ public static Stopwatch createStarted(Supplier<Long> ticker) {
+ return new Stopwatch(ticker).start();
+ }
+
+ /**
+ * @return a running stop watch, using the supplied clock.
+ * <p>
+ * Note that only {@link Clock#millis()} will be used, thus the watch will
have ms precision at most.
+ */
+ public static Stopwatch createStarted(Clock clock) {
+ return new Stopwatch(clockAsLongSupplier(clock)).start();
+ }
+
+ /**
+ * @return a non-running stop watch, using the supplied supplier.
+ */
+ public static Stopwatch createUnstarted(Supplier<Long> ticker) {
+ return new Stopwatch(ticker);
+ }
+
+ /**
+ * @return a non-running stop watch, using {@link System#nanoTime()}.
+ */
+ public static Stopwatch createUnstarted() {
+ return new Stopwatch(Stopwatch::tick);
+ }
+
+ /**
+ * Starts the stop watch, will fail when running.
+ * @return the stop watch
+ */
+ public Stopwatch start() {
+ Validate.checkState(!this.running, "Stopwatch already running.");
+ this.startTime = this.ticker.get();
+ this.running = true;
+ return this;
+ }
+
+ /**
+ * Stops the stop watch, will fail when not running.
+ * @return the stop watch
+ */
+ public Stopwatch stop() {
+ Validate.checkState(this.running, "Stopwatch not running.");
+ this.accumulated += elapsedNanos();
+ this.startTime = 0L;
+ this.running = false;
+ return this;
+ }
+
+ /**
+ * Resets the stop watch, and puts it into stopped state.
+ * @return the stop watch
+ */
+ public Stopwatch reset() {
+ this.accumulated = 0L;
+ this.startTime = 0;
+ this.running = false;
+ return this;
+ }
+
+ /**
+ * @return whether the stop watch is running
+ */
+ public boolean isRunning() {
+ return this.running;
+ }
+
+ /**
+ * Gets elapsed time using the supplied {@link TimeUnit}.
+ * @param timeunit time unit
+ * @return elapsed time in the specified unit
+ */
+ public long elapsed(TimeUnit timeunit) {
+ return timeunit.convert(elapsedNanos(), TimeUnit.NANOSECONDS);
+ }
+
+ /**
+ * Gets elapsed time as {@link Duration}.
+ * @return elapsed time
+ */
+ public Duration elapsed() {
+ return Duration.ofNanos(elapsedNanos());
+ }
+
+ @Override
+ public String toString() {
+ return Duration.ofNanos(elapsedNanos()).toString();
+ }
+
+ // private parts
+
+ private long elapsedNanos() {
+ long delta = this.running ? this.ticker.get() - this.startTime : 0;
+ return this.accumulated + delta;
+ }
+
+ private static long tick() {
+ return System.nanoTime();
+ }
+
+ private static Supplier<Long> clockAsLongSupplier(Clock clock) {
+ return () -> TimeUnit.MILLISECONDS.toNanos(clock.millis());
+ }
+}
diff --git
a/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/time/package-info.java
b/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/time/package-info.java
new file mode 100644
index 0000000000..3e9fa9b326
--- /dev/null
+++
b/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/time/package-info.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+/**
+ * Internal ("private") utilities related to timing...
+ */
+@Internal(since = "1.0.0")
+@Version("1.0.0")
+package org.apache.jackrabbit.oak.commons.time;
+import org.apache.jackrabbit.oak.commons.annotations.Internal;
+import org.osgi.annotation.versioning.Version;
+
diff --git
a/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/StopWatch.java
b/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/TestStopWatch.java
similarity index 98%
rename from
oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/StopWatch.java
rename to
oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/TestStopWatch.java
index 7677b33c56..ed64cdf6f9 100644
--- a/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/StopWatch.java
+++
b/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/TestStopWatch.java
@@ -19,7 +19,7 @@ package org.apache.jackrabbit.oak.commons;
/**
* A utility class to time an operation.
*/
-public class StopWatch {
+public class TestStopWatch {
private static final long NANOS_PER_SECOND = 1000 * 1000 * 1000;
diff --git
a/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/json/JsopStreamTest.java
b/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/json/JsopStreamTest.java
index 622a6c112b..2437decdda 100644
---
a/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/json/JsopStreamTest.java
+++
b/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/json/JsopStreamTest.java
@@ -17,7 +17,7 @@
package org.apache.jackrabbit.oak.commons.json;
import junit.framework.TestCase;
-import org.apache.jackrabbit.oak.commons.StopWatch;
+import org.apache.jackrabbit.oak.commons.TestStopWatch;
public class JsopStreamTest extends TestCase {
@@ -25,7 +25,7 @@ public class JsopStreamTest extends TestCase {
public static void main(String... args) {
for (int k = 0; k < 5; k++) {
String s = "Hello \"World\" Hello \"World\" Hello \"World\" Hello
\"World\" Hello \"World\" Hello \"World\" ";
- StopWatch timer = new StopWatch();
+ TestStopWatch timer = new TestStopWatch();
JsopWriter w = k % 2 == 1 ? new JsopBuilder() : new JsopStream();
for (int i = 0; i < 1000000; i++) {
w.value(s);
diff --git
a/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/json/JsopTest.java
b/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/json/JsopTest.java
index 93e4e2f197..58cc316749 100644
---
a/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/json/JsopTest.java
+++
b/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/json/JsopTest.java
@@ -17,7 +17,7 @@
package org.apache.jackrabbit.oak.commons.json;
import junit.framework.TestCase;
-import org.apache.jackrabbit.oak.commons.StopWatch;
+import org.apache.jackrabbit.oak.commons.TestStopWatch;
/**
* Test the Jsop tokenizer and builder.
@@ -29,7 +29,7 @@ public class JsopTest extends TestCase {
for (int k = 0; k < 5; k++) {
// String s = "Hello World Hello World Hello World Hello World
Hello World Hello World ";
String s = "Hello \"World\" Hello \"World\" Hello \"World\" Hello
\"World\" Hello \"World\" Hello \"World\" ";
- StopWatch timer = new StopWatch();
+ TestStopWatch timer = new TestStopWatch();
int t2 = 0;
for (int i = 0; i < 1000000; i++) {
t2 += JsopBuilder.encode(s).length();
diff --git
a/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/time/StopwatchTest.java
b/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/time/StopwatchTest.java
new file mode 100644
index 0000000000..1c7eb8f740
--- /dev/null
+++
b/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/time/StopwatchTest.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package org.apache.jackrabbit.oak.commons.time;
+
+import org.junit.Test;
+
+import java.time.Clock;
+import java.time.Duration;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class StopwatchTest {
+
+ private static final long CHECK_INTERVAL_SECONDS = 3;
+ private static final long CHECK_INTERVAL_MILLIS =
TimeUnit.SECONDS.toMillis(CHECK_INTERVAL_SECONDS);
+
+ @Test
+ public void started() throws InterruptedException {
+ testStarted(Stopwatch.createStarted());
+ }
+
+ @Test
+ public void stopped() throws InterruptedException {
+ Stopwatch sw = Stopwatch.createUnstarted();
+ assertFalse(sw.isRunning());
+ testStarted(sw.start());
+ }
+
+ @Test
+ public void withClock() throws InterruptedException {
+ testStarted(Stopwatch.createStarted(Clock.systemUTC()));
+ }
+
+ @Test
+ public void specialTicker() {
+ final AtomicLong time = new AtomicLong(0);
+ Stopwatch sw = Stopwatch.createUnstarted(time::get);
+ assertEquals(0, sw.elapsed().toNanos());
+ sw.start();
+ time.set(123);
+ assertEquals(123, sw.elapsed().toNanos());
+ time.set(456);
+ sw.stop();
+ assertEquals(456, sw.elapsed().toNanos());
+
+ time.set(0);
+ sw.reset().start();
+
+ // tests internals
+ assertEquals(Duration.ofNanos(0).toString(), sw.toString());
+ }
+
+ private void testStarted(Stopwatch sw) throws InterruptedException {
+ letElapse(CHECK_INTERVAL_MILLIS);
+ sw.stop();
+ long millis = sw.elapsed().toMillis();
+ assertTrue("elapsed " + millis + "ms, expected ~" +
CHECK_INTERVAL_MILLIS,
+ millis >= CHECK_INTERVAL_MILLIS - 1000 && millis <=
CHECK_INTERVAL_MILLIS + 1000);
+ long seconds = sw.elapsed(TimeUnit.SECONDS);
+ assertTrue("elapsed " + seconds + "s, expected ~" +
CHECK_INTERVAL_SECONDS,
+ seconds >= CHECK_INTERVAL_SECONDS - 1 && seconds <=
CHECK_INTERVAL_SECONDS + 1);
+ }
+
+ private static void letElapse(long delta) throws InterruptedException {
+ long start = System.currentTimeMillis();
+ while (System.currentTimeMillis() < start + delta) {
+ Thread.sleep(1);
+ }
+ }
+}