blob: 05f942b48a53fa886c528abc7822717c24beac28 [file] [log] [blame]
// Copyright (C) 2022 The Android Open Source Project
//
// 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.
package com.googlesource.gerrit.plugins.task.statistics;
import com.google.common.base.Stopwatch;
import com.googlesource.gerrit.plugins.task.util.SamTryWrapper;
import java.util.concurrent.TimeUnit;
import java.util.function.LongConsumer;
/**
* StopWatches with APIs designed to make it easy to disable, and to make robust in the face of
* exceptions.
*
* <p>The Stopwatch class from google commons is used by placing start() and stop() calls around
* code sections which need to be timed. This approach can be problematic since the code being timed
* could throw an exception and if the stop() is not in a finally clause, then it will likely never
* get called, potentially causing bad timings, or worse programatic issues elsewhere due to double
* calls to start(). The need for a finally clause to make things safe is an obvious hint that using
* an AutoCloseable approach is likely going to be safer. With that in mind, the two API approaches
* provided by these StopWatch classes are:
*
* <ol>
* <li>The timed try-with-resource API. Use the StopWatch.Enabled class for this API.
* <li>The timed SAM evaluation API. Use the StopWatch.Runner.Enabled class for these APIs.
* </ol>
*
* <p>Finally, the commons stopwatch API also does not provide an easy way to disable timings at
* runtime when they are not desired, so the DISABLED classes can be used for this with both
* approaches above, and thus provide low cost runtime substitutes for either the StopWatch.Enabled
* or StopWatch.Runner.Enabled classes.
*/
public interface StopWatch extends AutoCloseable {
/** Designed for the greatest simplicity to time SAM executions. */
public abstract static class Runner extends SamTryWrapper<AutoCloseable> {
public static class Enabled extends Runner {
protected LongConsumer nanosConsumer = EMPTY_LONG_CONSUMER;
@Override
protected AutoCloseable getAutoCloseable() {
return new StopWatch.Enabled().setNanosConsumer(nanosConsumer);
}
@Override
public Runner setNanosConsumer(LongConsumer nanosConsumer) {
this.nanosConsumer = nanosConsumer;
return this;
}
}
/** May be used anywhere that Enabled can be used */
public static final Runner DISABLED =
new Runner() {
@Override
protected AutoCloseable getAutoCloseable() {
return () -> {};
}
};
public Runner setNanosConsumer(LongConsumer nanosConsumer) {
return this;
}
}
/** Should be created and used only within a try-with-resource */
public static class Enabled implements StopWatch {
protected LongConsumer nanosConsumer = EMPTY_LONG_CONSUMER;
protected Stopwatch stopwatch = Stopwatch.createStarted();
@Override
public StopWatch setNanosConsumer(LongConsumer nanosConsumer) {
this.nanosConsumer = nanosConsumer;
return this;
}
@Override
public void close() {
stopwatch.stop();
nanosConsumer.accept(stopwatch.elapsed(TimeUnit.NANOSECONDS));
}
}
/**
* A easy way to build a timer which needes to be enabled/disabled based on a runtime boolean.
*
* <p>Example Usage:
*
* <p><code>
* try (StopWatch stopWatch =
* StopWatch.builder().enabled(myBoolean).build().setNanosConsumer(myConsumer)) {
* // Code to be timed here...
* }
* </code>
*/
public static class Builder {
protected static class Enabled extends Builder {
@Override
public StopWatch build() {
return new StopWatch.Enabled();
}
}
protected static final Builder ENABLED = new Enabled();
protected static final Builder DISABLED = new Builder();
public Builder enabled(boolean enabled) {
return enabled ? ENABLED : this;
}
public StopWatch build() {
return StopWatch.DISABLED;
}
}
/** May be used anywhere that Enabled can be used */
public static final StopWatch DISABLED = new StopWatch() {};
public static final LongConsumer EMPTY_LONG_CONSUMER = l -> {};
public static Builder builder() {
return Builder.DISABLED;
}
default StopWatch setNanosConsumer(LongConsumer nanosConsumer) {
return this;
}
default void close() {}
}