From 5e44940501828ad681dea07f2c897975c16fb556 Mon Sep 17 00:00:00 2001 From: brharrington Date: Wed, 25 Oct 2023 14:42:49 -0500 Subject: [PATCH] api: add helpers to wrap lambdas with a timer (#1087) Adds helpers to the Timer interface to wrap the common lambda types in `java.util.function`. This can be useful to wrap a lambda that is passed to another class in order to measure each invocation. For example: ```java Timer t = registry.timer(id); int sum = Arrays .stream(new String[] {"foo", "bar", "baz"}) .mapToInt(t.wrapToIntFunction(String::length)) .sum(); ``` Caller should ensure that the lambda does sufficient work that timing an invocatino will not be more costly than executing the lambda body. --- .../java/com/netflix/spectator/api/Timer.java | 982 ++++++++++++++++++ .../spectator/api/DefaultTimerTest.java | 165 ++- 2 files changed, 1143 insertions(+), 4 deletions(-) diff --git a/spectator-api/src/main/java/com/netflix/spectator/api/Timer.java b/spectator-api/src/main/java/com/netflix/spectator/api/Timer.java index 31f4bb741..6a4e35a2c 100644 --- a/spectator-api/src/main/java/com/netflix/spectator/api/Timer.java +++ b/spectator-api/src/main/java/com/netflix/spectator/api/Timer.java @@ -18,11 +18,48 @@ import java.time.Duration; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; +import java.util.function.BinaryOperator; import java.util.function.BooleanSupplier; +import java.util.function.Consumer; +import java.util.function.DoubleBinaryOperator; +import java.util.function.DoubleConsumer; +import java.util.function.DoublePredicate; import java.util.function.DoubleSupplier; +import java.util.function.DoubleToIntFunction; +import java.util.function.DoubleToLongFunction; +import java.util.function.DoubleUnaryOperator; +import java.util.function.Function; +import java.util.function.IntBinaryOperator; +import java.util.function.IntConsumer; +import java.util.function.IntFunction; +import java.util.function.IntPredicate; import java.util.function.IntSupplier; +import java.util.function.IntToDoubleFunction; +import java.util.function.IntToLongFunction; +import java.util.function.IntUnaryOperator; +import java.util.function.LongBinaryOperator; +import java.util.function.LongConsumer; +import java.util.function.LongFunction; +import java.util.function.LongPredicate; import java.util.function.LongSupplier; +import java.util.function.LongToDoubleFunction; +import java.util.function.LongToIntFunction; +import java.util.function.LongUnaryOperator; +import java.util.function.ObjDoubleConsumer; +import java.util.function.ObjIntConsumer; +import java.util.function.ObjLongConsumer; +import java.util.function.Predicate; import java.util.function.Supplier; +import java.util.function.ToDoubleBiFunction; +import java.util.function.ToDoubleFunction; +import java.util.function.ToIntBiFunction; +import java.util.function.ToIntFunction; +import java.util.function.ToLongBiFunction; +import java.util.function.ToLongFunction; +import java.util.function.UnaryOperator; /** * Timer intended to track a large number of short running events. Example would be something like @@ -267,6 +304,951 @@ default long recordLongSupplier(LongSupplier f) { } } + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default Callable wrapCallable(Callable f) { + return () -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.call(); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default Runnable wrapRunnable(Runnable f) { + return () -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + f.run(); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default BiConsumer wrapBiConsumer(BiConsumer f) { + return (t, u) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + f.accept(t, u); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default BiFunction wrapBiFunction(BiFunction f) { + return (t, u) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.apply(t, u); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default BinaryOperator wrapBinaryOperator(BinaryOperator f) { + return (t, u) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.apply(t, u); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default BiPredicate wrapBiPredicate(BiPredicate f) { + return (t, u) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.test(t, u); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default BooleanSupplier wrapBooleanSupplier(BooleanSupplier f) { + return () -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.getAsBoolean(); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default Consumer wrapConsumer(Consumer f) { + return (t) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + f.accept(t); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default DoubleBinaryOperator wrapDoubleBinaryOperator(DoubleBinaryOperator f) { + return (t, u) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.applyAsDouble(t, u); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default DoubleConsumer wrapDoubleConsumer(DoubleConsumer f) { + return (t) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + f.accept(t); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default java.util.function.DoubleFunction wrapDoubleFunction(java.util.function.DoubleFunction f) { + return (t) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.apply(t); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default DoublePredicate wrapDoublePredicate(DoublePredicate f) { + return (t) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.test(t); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default DoubleSupplier wrapDoubleSupplier(DoubleSupplier f) { + return () -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.getAsDouble(); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default DoubleToIntFunction wrapDoubleToIntFunction(DoubleToIntFunction f) { + return (t) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.applyAsInt(t); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default DoubleToLongFunction wrapDoubleToLongFunction(DoubleToLongFunction f) { + return (t) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.applyAsLong(t); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default DoubleUnaryOperator wrapDoubleUnaryOperator(DoubleUnaryOperator f) { + return (t) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.applyAsDouble(t); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default Function wrapFunction(Function f) { + return (t) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.apply(t); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default IntBinaryOperator wrapIntBinaryOperator(IntBinaryOperator f) { + return (t, u) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.applyAsInt(t, u); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default IntConsumer wrapIntConsumer(IntConsumer f) { + return (t) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + f.accept(t); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default IntFunction wrapIntFunction(IntFunction f) { + return (t) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.apply(t); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default IntPredicate wrapIntPredicate(IntPredicate f) { + return (t) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.test(t); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default IntSupplier wrapIntSupplier(IntSupplier f) { + return () -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.getAsInt(); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default IntToDoubleFunction wrapIntToDoubleFunction(IntToDoubleFunction f) { + return (t) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.applyAsDouble(t); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default IntToLongFunction wrapIntToLongFunction(IntToLongFunction f) { + return (t) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.applyAsLong(t); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default IntUnaryOperator wrapIntUnaryOperator(IntUnaryOperator f) { + return (t) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.applyAsInt(t); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default LongBinaryOperator wrapLongBinaryOperator(LongBinaryOperator f) { + return (t, u) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.applyAsLong(t, u); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default LongConsumer wrapLongConsumer(LongConsumer f) { + return (t) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + f.accept(t); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default LongFunction wrapLongFunction(LongFunction f) { + return (t) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.apply(t); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default LongPredicate wrapLongPredicate(LongPredicate f) { + return (t) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.test(t); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default LongSupplier wrapLongSupplier(LongSupplier f) { + return () -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.getAsLong(); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default LongToIntFunction wrapLongToIntFunction(LongToIntFunction f) { + return (t) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.applyAsInt(t); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default LongToDoubleFunction wrapLongToDoubleFunction(LongToDoubleFunction f) { + return (t) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.applyAsDouble(t); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default LongUnaryOperator wrapLongUnaryOperator(LongUnaryOperator f) { + return (t) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.applyAsLong(t); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default ObjDoubleConsumer wrapObjDoubleConsumer(ObjDoubleConsumer f) { + return (t, u) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + f.accept(t, u); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default ObjIntConsumer wrapObjIntConsumer(ObjIntConsumer f) { + return (t, u) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + f.accept(t, u); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default ObjLongConsumer wrapObjLongConsumer(ObjLongConsumer f) { + return (t, u) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + f.accept(t, u); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default Predicate wrapPredicate(Predicate f) { + return (t) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.test(t); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default Supplier wrapSupplier(Supplier f) { + return () -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.get(); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default ToDoubleBiFunction wrapToDoubleBiFunction(ToDoubleBiFunction f) { + return (t, u) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.applyAsDouble(t, u); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default ToDoubleFunction wrapToDoubleFunction(ToDoubleFunction f) { + return (t) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.applyAsDouble(t); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default ToIntBiFunction wrapToIntBiFunction(ToIntBiFunction f) { + return (t, u) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.applyAsInt(t, u); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default ToIntFunction wrapToIntFunction(ToIntFunction f) { + return (t) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.applyAsInt(t); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default ToLongBiFunction wrapToLongBiFunction(ToLongBiFunction f) { + return (t, u) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.applyAsLong(t, u); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default ToLongFunction wrapToLongFunction(ToLongFunction f) { + return (t) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.applyAsLong(t); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + + /** + * Wraps the lambda `f` to update this timer each time the wrapper is invoked. + * + * @param f + * Function to execute and measure the execution time. + * @return + * Wrapper that invokes `f` and records the time taken. + */ + default UnaryOperator wrapUnaryOperator(UnaryOperator f) { + return (t) -> { + final Clock clock = clock(); + long s = clock.monotonicTime(); + try { + return f.apply(t); + } finally { + long e = clock.monotonicTime(); + record(e - s, TimeUnit.NANOSECONDS); + } + }; + } + /** * The number of times that record has been called since this timer was last reset. * How often a timer is reset depends on the underlying registry implementation. diff --git a/spectator-api/src/test/java/com/netflix/spectator/api/DefaultTimerTest.java b/spectator-api/src/test/java/com/netflix/spectator/api/DefaultTimerTest.java index 9f94b5feb..f3055f783 100644 --- a/spectator-api/src/test/java/com/netflix/spectator/api/DefaultTimerTest.java +++ b/spectator-api/src/test/java/com/netflix/spectator/api/DefaultTimerTest.java @@ -18,8 +18,54 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import java.lang.reflect.Proxy; import java.time.Duration; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; +import java.util.function.BinaryOperator; +import java.util.function.BooleanSupplier; +import java.util.function.Consumer; +import java.util.function.DoubleBinaryOperator; +import java.util.function.DoubleConsumer; +import java.util.function.DoublePredicate; +import java.util.function.DoubleSupplier; +import java.util.function.DoubleToIntFunction; +import java.util.function.DoubleToLongFunction; +import java.util.function.DoubleUnaryOperator; +import java.util.function.Function; +import java.util.function.IntBinaryOperator; +import java.util.function.IntConsumer; +import java.util.function.IntFunction; +import java.util.function.IntPredicate; +import java.util.function.IntSupplier; +import java.util.function.IntToDoubleFunction; +import java.util.function.IntToLongFunction; +import java.util.function.IntUnaryOperator; +import java.util.function.LongBinaryOperator; +import java.util.function.LongConsumer; +import java.util.function.LongFunction; +import java.util.function.LongPredicate; +import java.util.function.LongSupplier; +import java.util.function.LongToDoubleFunction; +import java.util.function.LongToIntFunction; +import java.util.function.LongUnaryOperator; +import java.util.function.ObjDoubleConsumer; +import java.util.function.ObjIntConsumer; +import java.util.function.ObjLongConsumer; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.function.ToDoubleBiFunction; +import java.util.function.ToDoubleFunction; +import java.util.function.ToIntBiFunction; +import java.util.function.ToIntFunction; +import java.util.function.ToLongBiFunction; +import java.util.function.ToLongFunction; +import java.util.function.UnaryOperator; public class DefaultTimerTest { @@ -199,7 +245,7 @@ public void testRecordSupplierException() { } @Test - public void testRecordBooleanSupplier() throws Exception { + public void testRecordBooleanSupplier() { Timer t = new DefaultTimer(clock, NoopId.INSTANCE); clock.setMonotonicTime(100L); boolean value = t.recordBooleanSupplier(() -> { @@ -230,7 +276,7 @@ public void testRecordBooleanSupplierException() { } @Test - public void testRecordIntSupplier() throws Exception { + public void testRecordIntSupplier() { Timer t = new DefaultTimer(clock, NoopId.INSTANCE); clock.setMonotonicTime(100L); int value = t.recordIntSupplier(() -> { @@ -261,7 +307,7 @@ public void testRecordIntSupplierException() { } @Test - public void testRecordLongSupplier() throws Exception { + public void testRecordLongSupplier() { Timer t = new DefaultTimer(clock, NoopId.INSTANCE); clock.setMonotonicTime(100L); long value = t.recordLongSupplier(() -> { @@ -292,7 +338,7 @@ public void testRecordLongSupplierException() { } @Test - public void testRecordDoubleSupplier() throws Exception { + public void testRecordDoubleSupplier() { Timer t = new DefaultTimer(clock, NoopId.INSTANCE); clock.setMonotonicTime(100L); double value = t.recordDoubleSupplier(() -> { @@ -322,6 +368,117 @@ public void testRecordDoubleSupplierException() { Assertions.assertEquals(t.totalTime(), 400L); } + @SuppressWarnings("unchecked") + private void testWrapper(Class cls, BiFunction wrap, Consumer run, Object retValue) { + clock.setMonotonicTime(0L); + Timer t = new DefaultTimer(clock, NoopId.INSTANCE); + clock.setMonotonicTime(100L); + + // Success + T f = (T) Proxy.newProxyInstance( + cls.getClassLoader(), + new Class[]{cls}, + (obj, m, args) -> { + clock.setMonotonicTime(500L); + return retValue; + } + ); + T wrapped = wrap.apply(t, f); + Assertions.assertEquals(t.count(), 0L); + run.accept(wrapped); + Assertions.assertEquals(t.count(), 1L); + Assertions.assertEquals(t.totalTime(), 400L); + + // Exception + T f2 = (T) Proxy.newProxyInstance( + cls.getClassLoader(), + new Class[]{cls}, + (obj, m, args) -> { + clock.setMonotonicTime(1000L); + throw new RuntimeException("foo"); + } + ); + final T wrapped2 = wrap.apply(t, f2); + Assertions.assertThrows(RuntimeException.class, () -> run.accept(wrapped2)); + Assertions.assertEquals(t.count(), 2L); + Assertions.assertEquals(t.totalTime(), 900L); + } + + @SuppressWarnings("unchecked") + @Test + public void testWrappingLambdas() { + Consumer cc = c -> { + try { + c.call(); + } catch (Exception e) { + throw new RuntimeException(e); + } + }; + testWrapper(Callable.class, Timer::wrapCallable, cc, ""); + testWrapper(Runnable.class, Timer::wrapRunnable, Runnable::run, ""); + testWrapper(BiConsumer.class, Timer::wrapBiConsumer, c -> c.accept("", ""), ""); + testWrapper(BiFunction.class, Timer::wrapBiFunction, c -> c.apply("", ""), ""); + testWrapper(BinaryOperator.class, Timer::wrapBinaryOperator, c -> c.apply("", ""), ""); + testWrapper(BiPredicate.class, Timer::wrapBiPredicate, c -> c.test("", ""), false); + testWrapper(BooleanSupplier.class, Timer::wrapBooleanSupplier, BooleanSupplier::getAsBoolean, false); + testWrapper(Consumer.class, Timer::wrapConsumer, c -> c.accept(""), ""); + testWrapper(DoubleBinaryOperator.class, Timer::wrapDoubleBinaryOperator, c -> c.applyAsDouble(1.0, 2.0), 3.0); + testWrapper(DoubleConsumer.class, Timer::wrapDoubleConsumer, c -> c.accept(1.0), ""); + testWrapper(java.util.function.DoubleFunction.class, Timer::wrapDoubleFunction, c -> c.apply(1.0), 1.0); + testWrapper(DoublePredicate.class, Timer::wrapDoublePredicate, c -> c.test(1.0), false); + testWrapper(DoubleSupplier.class, Timer::wrapDoubleSupplier, DoubleSupplier::getAsDouble, 1.0); + testWrapper(DoubleToIntFunction.class, Timer::wrapDoubleToIntFunction, c -> c.applyAsInt(1.0), 1); + testWrapper(DoubleToLongFunction.class, Timer::wrapDoubleToLongFunction, c -> c.applyAsLong(1.0), 1L); + testWrapper(DoubleUnaryOperator.class, Timer::wrapDoubleUnaryOperator, c -> c.applyAsDouble(1.0), 1.0); + testWrapper(Function.class, Timer::wrapFunction, c -> c.apply(""), ""); + testWrapper(IntBinaryOperator.class, Timer::wrapIntBinaryOperator, c -> c.applyAsInt(1, 2), 3); + testWrapper(IntConsumer.class, Timer::wrapIntConsumer, c -> c.accept(1), ""); + testWrapper(IntFunction.class, Timer::wrapIntFunction, c -> c.apply(1), 1); + testWrapper(IntPredicate.class, Timer::wrapIntPredicate, c -> c.test(1), false); + testWrapper(IntSupplier.class, Timer::wrapIntSupplier, IntSupplier::getAsInt, 1); + testWrapper(IntToDoubleFunction.class, Timer::wrapIntToDoubleFunction, c -> c.applyAsDouble(1), 1.0); + testWrapper(IntToLongFunction.class, Timer::wrapIntToLongFunction, c -> c.applyAsLong(1), 1L); + testWrapper(IntUnaryOperator.class, Timer::wrapIntUnaryOperator, c -> c.applyAsInt(1), 1); + testWrapper(LongBinaryOperator.class, Timer::wrapLongBinaryOperator, c -> c.applyAsLong(1, 2), 3L); + testWrapper(LongConsumer.class, Timer::wrapLongConsumer, c -> c.accept(1), ""); + testWrapper(LongFunction.class, Timer::wrapLongFunction, c -> c.apply(1), 1L); + testWrapper(LongPredicate.class, Timer::wrapLongPredicate, c -> c.test(1), false); + testWrapper(LongSupplier.class, Timer::wrapLongSupplier, LongSupplier::getAsLong, 1L); + testWrapper(LongToDoubleFunction.class, Timer::wrapLongToDoubleFunction, c -> c.applyAsDouble(1), 1.0); + testWrapper(LongToIntFunction.class, Timer::wrapLongToIntFunction, c -> c.applyAsInt(1), 1); + testWrapper(LongUnaryOperator.class, Timer::wrapLongUnaryOperator, c -> c.applyAsLong(1), 1L); + testWrapper(ObjDoubleConsumer.class, Timer::wrapObjDoubleConsumer, c -> c.accept("", 1.0), ""); + testWrapper(ObjIntConsumer.class, Timer::wrapObjIntConsumer, c -> c.accept("", 1), ""); + testWrapper(ObjLongConsumer.class, Timer::wrapObjLongConsumer, c -> c.accept("", 1L), ""); + testWrapper(Predicate.class, Timer::wrapPredicate, c -> c.test(""), false); + testWrapper(Supplier.class, Timer::wrapSupplier, Supplier::get, ""); + testWrapper(ToDoubleBiFunction.class, Timer::wrapToDoubleBiFunction, c -> c.applyAsDouble("", ""), 1.0); + testWrapper(ToDoubleFunction.class, Timer::wrapToDoubleFunction, c -> c.applyAsDouble(""), 1.0); + testWrapper(ToIntBiFunction.class, Timer::wrapToIntBiFunction, c -> c.applyAsInt("", ""), 1); + testWrapper(ToIntFunction.class, Timer::wrapToIntFunction, c -> c.applyAsInt(""), 1); + testWrapper(ToLongBiFunction.class, Timer::wrapToLongBiFunction, c -> c.applyAsLong("", ""), 1L); + testWrapper(ToLongFunction.class, Timer::wrapToLongFunction, c -> c.applyAsLong(""), 1L); + testWrapper(UnaryOperator.class, Timer::wrapUnaryOperator, c -> c.apply(""), ""); + } + + @Test + public void wrapWithStreamApi() { + Timer t = new DefaultTimer(clock, NoopId.INSTANCE); + clock.setMonotonicTime(100L); + + int sum = Arrays + .stream(new int[] {1, 2, 3, 4, 5, 6}) + .map(t.wrapIntUnaryOperator(i -> i + 1)) + .sum(); + Assertions.assertEquals(27, sum); + + sum = Arrays + .stream(new String[] {"foo", "bar", "baz"}) + .mapToInt(t.wrapToIntFunction(String::length)) + .sum(); + Assertions.assertEquals(9, sum); + } + private int square(int v) { return v * v; }