From 17e6d74bca534e8179eeb3bcfef7944c776154e8 Mon Sep 17 00:00:00 2001 From: Patrick Strawderman Date: Fri, 11 Oct 2024 08:04:20 -0700 Subject: [PATCH] reduce memory overhead for step value types (#1158) Use AtomicLongFieldUpdater to shrink the footprint of StepLong, StepDouble, and AtomicDouble. This avoids the object overhead and pointer traversal needed when using AtomicLong internally. Note, it does change some of the public methods on StepLong and StepDouble. However, those are internal implementation classes and should not be used outside of the spectator libraries. --------- Co-authored-by: Brian Harrington --- .../netflix/spectator/impl/AtomicDouble.java | 49 +++-- .../netflix/spectator/impl/StepDouble.java | 128 ++++++++++-- .../com/netflix/spectator/impl/StepLong.java | 98 +++++++-- .../spectator/impl/AtomicDoubleTest.java | 59 +++++- .../spectator/impl/StepDoubleTest.java | 187 +++++++++++++++++- .../netflix/spectator/impl/StepLongTest.java | 139 ++++++++++++- .../netflix/spectator/atlas/AtlasCounter.java | 6 +- .../atlas/AtlasDistributionSummary.java | 35 ++-- .../spectator/atlas/AtlasMaxGauge.java | 4 +- .../netflix/spectator/atlas/AtlasTimer.java | 42 ++-- .../micrometer/MicrometerRegistry.java | 7 +- 11 files changed, 635 insertions(+), 119 deletions(-) diff --git a/spectator-api/src/main/java/com/netflix/spectator/impl/AtomicDouble.java b/spectator-api/src/main/java/com/netflix/spectator/impl/AtomicDouble.java index 99dbb9473..06fb4e4e6 100644 --- a/spectator-api/src/main/java/com/netflix/spectator/impl/AtomicDouble.java +++ b/spectator-api/src/main/java/com/netflix/spectator/impl/AtomicDouble.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2019 Netflix, Inc. + * Copyright 2014-2024 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ */ package com.netflix.spectator.impl; -import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; /** * Wrapper around AtomicLong to make working with double values easier. @@ -26,22 +26,25 @@ @SuppressWarnings("PMD.MissingSerialVersionUID") public class AtomicDouble extends Number { - private final AtomicLong value; + private volatile long value; + + private static final AtomicLongFieldUpdater VALUE_UPDATER = + AtomicLongFieldUpdater.newUpdater(AtomicDouble.class, "value"); /** Create an instance with an initial value of 0. */ public AtomicDouble() { - this(0.0); + super(); } /** Create an instance with an initial value of {@code init}. */ public AtomicDouble(double init) { super(); - value = new AtomicLong(Double.doubleToLongBits(init)); + this.value = Double.doubleToLongBits(init); } /** Return the current value. */ public double get() { - return Double.longBitsToDouble(value.get()); + return Double.longBitsToDouble(value); } /** Add {@code amount} to the value and return the new value. */ @@ -51,11 +54,11 @@ public double addAndGet(double amount) { double n; long next; do { - v = value.get(); + v = value; d = Double.longBitsToDouble(v); n = d + amount; next = Double.doubleToLongBits(n); - } while (!value.compareAndSet(v, next)); + } while (!VALUE_UPDATER.compareAndSet(this, v, next)); return n; } @@ -66,17 +69,17 @@ public double getAndAdd(double amount) { double n; long next; do { - v = value.get(); + v = value; d = Double.longBitsToDouble(v); n = d + amount; next = Double.doubleToLongBits(n); - } while (!value.compareAndSet(v, next)); + } while (!VALUE_UPDATER.compareAndSet(this, v, next)); return d; } /** Set the value to {@code amount} and return the previous value. */ public double getAndSet(double amount) { - long v = value.getAndSet(Double.doubleToLongBits(amount)); + long v = VALUE_UPDATER.getAndSet(this, Double.doubleToLongBits(amount)); return Double.longBitsToDouble(v); } @@ -87,15 +90,29 @@ public double getAndSet(double amount) { public boolean compareAndSet(double expect, double update) { long e = Double.doubleToLongBits(expect); long u = Double.doubleToLongBits(update); - return value.compareAndSet(e, u); + return VALUE_UPDATER.compareAndSet(this, e, u); } /** Set the current value to {@code amount}. */ public void set(double amount) { - value.set(Double.doubleToLongBits(amount)); + value = Double.doubleToLongBits(amount); + } + + private static boolean isLessThan(double v1, double v2) { + return v1 < v2 || Double.isNaN(v2); + } + + /** Set the current value to the maximum of the current value or the provided value. */ + public void min(double v) { + if (Double.isFinite(v)) { + double min = get(); + while (isLessThan(v, min) && !compareAndSet(min, v)) { + min = get(); + } + } } - private boolean isGreaterThan(double v1, double v2) { + private static boolean isGreaterThan(double v1, double v2) { return v1 > v2 || Double.isNaN(v2); } @@ -124,4 +141,8 @@ public void max(double v) { @Override public double doubleValue() { return get(); } + + @Override public String toString() { + return Double.toString(get()); + } } diff --git a/spectator-api/src/main/java/com/netflix/spectator/impl/StepDouble.java b/spectator-api/src/main/java/com/netflix/spectator/impl/StepDouble.java index f7ec5480d..10c7afca1 100644 --- a/spectator-api/src/main/java/com/netflix/spectator/impl/StepDouble.java +++ b/spectator-api/src/main/java/com/netflix/spectator/impl/StepDouble.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2019 Netflix, Inc. + * Copyright 2014-2024 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ import com.netflix.spectator.api.Clock; -import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; /** * Utility class for managing a set of AtomicLong instances mapped to a particular step interval. @@ -35,9 +35,15 @@ public class StepDouble implements StepValue { private final long step; private volatile double previous; - private final AtomicDouble current; + private volatile long current; - private final AtomicLong lastInitPos; + private static final AtomicLongFieldUpdater CURRENT_UPDATER = + AtomicLongFieldUpdater.newUpdater(StepDouble.class, "current"); + + private volatile long lastInitPos; + + private static final AtomicLongFieldUpdater LAST_INIT_POS_UPDATER = + AtomicLongFieldUpdater.newUpdater(StepDouble.class, "lastInitPos"); /** Create a new instance. */ public StepDouble(double init, Clock clock, long step) { @@ -45,15 +51,16 @@ public StepDouble(double init, Clock clock, long step) { this.clock = clock; this.step = step; previous = init; - current = new AtomicDouble(init); - lastInitPos = new AtomicLong(clock.wallTime() / step); + current = Double.doubleToLongBits(init); + lastInitPos = clock.wallTime() / step; } private void rollCount(long now) { final long stepTime = now / step; - final long lastInit = lastInitPos.get(); - if (lastInit < stepTime && lastInitPos.compareAndSet(lastInit, stepTime)) { - final double v = current.getAndSet(init); + final long lastInit = lastInitPos; + if (lastInit < stepTime && LAST_INIT_POS_UPDATER.compareAndSet(this, lastInit, stepTime)) { + final double v = Double.longBitsToDouble( + CURRENT_UPDATER.getAndSet(this, Double.doubleToLongBits(init))); // Need to check if there was any activity during the previous step interval. If there was // then the init position will move forward by 1, otherwise it will be older. No activity // means the previous interval should be set to the `init` value. @@ -61,15 +68,102 @@ private void rollCount(long now) { } } - /** Get the AtomicDouble for the current bucket. */ - public AtomicDouble getCurrent() { + /** Get the value for the current bucket. */ + public double getCurrent() { return getCurrent(clock.wallTime()); } - /** Get the AtomicDouble for the current bucket. */ - public AtomicDouble getCurrent(long now) { + /** Get the value for the current bucket. */ + public double getCurrent(long now) { + rollCount(now); + return Double.longBitsToDouble(current); + } + + /** Set the value for the current bucket. */ + public void setCurrent(long now, double value) { + rollCount(now); + current = Double.doubleToLongBits(value); + } + + /** Increment the current value and return the result. */ + public double addAndGet(long now, double amount) { rollCount(now); - return current; + long v; + double d; + double n; + long next; + do { + v = current; + d = Double.longBitsToDouble(v); + n = d + amount; + next = Double.doubleToLongBits(n); + } while (!CURRENT_UPDATER.compareAndSet(this, v, next)); + return n; + } + + /** Increment the current value and return the value before incrementing. */ + public double getAndAdd(long now, double amount) { + rollCount(now); + long v; + double d; + double n; + long next; + do { + v = current; + d = Double.longBitsToDouble(v); + n = d + amount; + next = Double.doubleToLongBits(n); + } while (!CURRENT_UPDATER.compareAndSet(this, v, next)); + return d; + } + + /** Set the current value and return the previous value. */ + public double getAndSet(long now, double value) { + rollCount(now); + long v = CURRENT_UPDATER.getAndSet(this, Double.doubleToLongBits(value)); + return Double.longBitsToDouble(v); + } + + private boolean compareAndSet(double expect, double update) { + long e = Double.doubleToLongBits(expect); + long u = Double.doubleToLongBits(update); + return CURRENT_UPDATER.compareAndSet(this, e, u); + } + + /** Set the current value and return the previous value. */ + public boolean compareAndSet(long now, double expect, double update) { + rollCount(now); + return compareAndSet(expect, update); + } + + private static boolean isLessThan(double v1, double v2) { + return v1 < v2 || Double.isNaN(v2); + } + + /** Set the current value to the minimum of the current value or the provided value. */ + public void min(long now, double value) { + if (Double.isFinite(value)) { + rollCount(now); + double min = Double.longBitsToDouble(current); + while (isLessThan(value, min) && !compareAndSet(min, value)) { + min = Double.longBitsToDouble(current); + } + } + } + + private static boolean isGreaterThan(double v1, double v2) { + return v1 > v2 || Double.isNaN(v2); + } + + /** Set the current value to the maximum of the current value or the provided value. */ + public void max(long now, double value) { + if (Double.isFinite(value)) { + rollCount(now); + double max = Double.longBitsToDouble(current); + while (isGreaterThan(value, max) && !compareAndSet(max, value)) { + max = Double.longBitsToDouble(current); + } + } } /** Get the value for the last completed interval. */ @@ -97,13 +191,13 @@ public double poll(long now) { /** Get the timestamp for the end of the last completed interval. */ @Override public long timestamp() { - return lastInitPos.get() * step; + return lastInitPos * step; } @Override public String toString() { return "StepDouble{init=" + init + ", previous=" + previous - + ", current=" + current.get() - + ", lastInitPos=" + lastInitPos.get() + '}'; + + ", current=" + Double.longBitsToDouble(current) + + ", lastInitPos=" + lastInitPos + '}'; } } diff --git a/spectator-api/src/main/java/com/netflix/spectator/impl/StepLong.java b/spectator-api/src/main/java/com/netflix/spectator/impl/StepLong.java index 566a7d865..5d595a4e6 100644 --- a/spectator-api/src/main/java/com/netflix/spectator/impl/StepLong.java +++ b/spectator-api/src/main/java/com/netflix/spectator/impl/StepLong.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2019 Netflix, Inc. + * Copyright 2014-2024 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ import com.netflix.spectator.api.Clock; -import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; /** * Utility class for managing a set of AtomicLong instances mapped to a particular step interval. @@ -35,9 +35,15 @@ public class StepLong implements StepValue { private final long step; private volatile long previous; - private final AtomicLong current; + private volatile long current; - private final AtomicLong lastInitPos; + private static final AtomicLongFieldUpdater CURRENT_UPDATER = + AtomicLongFieldUpdater.newUpdater(StepLong.class, "current"); + + private volatile long lastInitPos; + + private static final AtomicLongFieldUpdater LAST_INIT_POS_UPDATER = + AtomicLongFieldUpdater.newUpdater(StepLong.class, "lastInitPos"); /** Create a new instance. */ public StepLong(long init, Clock clock, long step) { @@ -45,15 +51,15 @@ public StepLong(long init, Clock clock, long step) { this.clock = clock; this.step = step; previous = init; - current = new AtomicLong(init); - lastInitPos = new AtomicLong(clock.wallTime() / step); + current = init; + lastInitPos = clock.wallTime() / step; } private void rollCount(long now) { final long stepTime = now / step; - final long lastInit = lastInitPos.get(); - if (lastInit < stepTime && lastInitPos.compareAndSet(lastInit, stepTime)) { - final long v = current.getAndSet(init); + final long lastInit = lastInitPos; + if (lastInit < stepTime && LAST_INIT_POS_UPDATER.compareAndSet(this, lastInit, stepTime)) { + final long v = CURRENT_UPDATER.getAndSet(this, init); // Need to check if there was any activity during the previous step interval. If there was // then the init position will move forward by 1, otherwise it will be older. No activity // means the previous interval should be set to the `init` value. @@ -61,17 +67,77 @@ private void rollCount(long now) { } } - /** Get the AtomicLong for the current bucket. */ - public AtomicLong getCurrent() { + /** Get the value for the current bucket. */ + public long getCurrent() { return getCurrent(clock.wallTime()); } - /** Get the AtomicLong for the current bucket. */ - public AtomicLong getCurrent(long now) { + /** Get the value for the current bucket. */ + public long getCurrent(long now) { rollCount(now); return current; } + /** Set the value for the current bucket. */ + public void setCurrent(long now, long value) { + rollCount(now); + current = value; + } + + /** Increment the current value and return the result. */ + public long incrementAndGet(long now) { + rollCount(now); + return CURRENT_UPDATER.incrementAndGet(this); + } + + /** Increment the current value and return the value before incrementing. */ + public long getAndIncrement(long now) { + rollCount(now); + return CURRENT_UPDATER.getAndIncrement(this); + } + + /** Increment the current value and return the result. */ + public long addAndGet(long now, long value) { + rollCount(now); + return CURRENT_UPDATER.addAndGet(this, value); + } + + /** Increment the current value and return the value before incrementing. */ + public long getAndAdd(long now, long value) { + rollCount(now); + return CURRENT_UPDATER.getAndAdd(this, value); + } + + /** Set the current value and return the previous value. */ + public long getAndSet(long now, long value) { + rollCount(now); + return CURRENT_UPDATER.getAndSet(this, value); + } + + /** Set the current value and return the previous value. */ + public boolean compareAndSet(long now, long expect, long update) { + rollCount(now); + return CURRENT_UPDATER.compareAndSet(this, expect, update); + } + + /** Set the current value to the minimum of the current value or the provided value. */ + public void min(long now, long value) { + rollCount(now); + long min = current; + while (value < min && !CURRENT_UPDATER.compareAndSet(this, min, value)) { + min = current; + } + } + + /** Set the current value to the maximum of the current value or the provided value. */ + public void max(long now, long value) { + rollCount(now); + long max = current; + while (value > max && !CURRENT_UPDATER.compareAndSet(this, max, value)) { + max = current; + } + } + /** Get the value for the last completed interval. */ public long poll() { return poll(clock.wallTime()); @@ -97,13 +163,13 @@ public long poll(long now) { /** Get the timestamp for the end of the last completed interval. */ @Override public long timestamp() { - return lastInitPos.get() * step; + return lastInitPos * step; } @Override public String toString() { return "StepLong{init=" + init + ", previous=" + previous - + ", current=" + current.get() - + ", lastInitPos=" + lastInitPos.get() + '}'; + + ", current=" + current + + ", lastInitPos=" + lastInitPos + '}'; } } diff --git a/spectator-api/src/test/java/com/netflix/spectator/impl/AtomicDoubleTest.java b/spectator-api/src/test/java/com/netflix/spectator/impl/AtomicDoubleTest.java index 0a1a865bf..aba11fb46 100644 --- a/spectator-api/src/test/java/com/netflix/spectator/impl/AtomicDoubleTest.java +++ b/spectator-api/src/test/java/com/netflix/spectator/impl/AtomicDoubleTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2019 Netflix, Inc. + * Copyright 2014-2024 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -74,6 +74,55 @@ public void getAndAdd() { Assertions.assertEquals(55.0, v.get(), 1e-12); } + @Test + public void minGt() { + AtomicDouble v = new AtomicDouble(0.0); + v.min(2.0); + Assertions.assertEquals(0.0, v.get(), 1e-12); + } + + @Test + public void minLt() { + AtomicDouble v = new AtomicDouble(2.0); + v.min(0.0); + Assertions.assertEquals(0.0, v.get(), 1e-12); + } + + @Test + public void minNegative() { + AtomicDouble v = new AtomicDouble(-42.0); + v.min(-41.0); + Assertions.assertEquals(-42.0, v.get(), 1e-12); + } + + @Test + public void minNaN() { + AtomicDouble v = new AtomicDouble(Double.NaN); + v.min(0.0); + Assertions.assertEquals(0.0, v.get(), 1e-12); + } + + @Test + public void minValueNaN() { + AtomicDouble v = new AtomicDouble(0.0); + v.min(Double.NaN); + Assertions.assertEquals(0.0, v.get(), 1e-12); + } + + @Test + public void minNegativeNaN() { + AtomicDouble v = new AtomicDouble(Double.NaN); + v.min(-42.0); + Assertions.assertEquals(-42.0, v.get(), 1e-12); + } + + @Test + public void minValueInfinity() { + AtomicDouble v = new AtomicDouble(0.0); + v.min(Double.NEGATIVE_INFINITY); + Assertions.assertEquals(0.0, v.get(), 1e-12); + } + @Test public void maxGt() { AtomicDouble v = new AtomicDouble(0.0); @@ -122,4 +171,12 @@ public void maxValueInfinity() { v.max(Double.POSITIVE_INFINITY); Assertions.assertEquals(0.0, v.get(), 1e-12); } + + @Test + public void testToString() { + AtomicDouble v = new AtomicDouble(0.0); + Assertions.assertEquals("0.0", v.toString()); + v.set(-100.5); + Assertions.assertEquals("-100.5", v.toString()); + } } diff --git a/spectator-api/src/test/java/com/netflix/spectator/impl/StepDoubleTest.java b/spectator-api/src/test/java/com/netflix/spectator/impl/StepDoubleTest.java index b179fd867..606b6aa10 100644 --- a/spectator-api/src/test/java/com/netflix/spectator/impl/StepDoubleTest.java +++ b/spectator-api/src/test/java/com/netflix/spectator/impl/StepDoubleTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2019 Netflix, Inc. + * Copyright 2014-2024 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,33 +32,204 @@ public void init() { @Test public void empty() { StepDouble v = new StepDouble(0.0, clock, 10L); - Assertions.assertEquals(0.0, v.getCurrent().get(), 1e-12); + Assertions.assertEquals(0.0, v.getCurrent(), 1e-12); Assertions.assertEquals(0.0, v.poll(), 1e-12); } @Test public void increment() { StepDouble v = new StepDouble(0.0, clock, 10L); - v.getCurrent().addAndGet(1.0); - Assertions.assertEquals(1.0, v.getCurrent().get(), 1e-12); + v.addAndGet(clock.wallTime(), 1.0); + Assertions.assertEquals(1.0, v.getCurrent(), 1e-12); Assertions.assertEquals(0.0, v.poll(), 1e-12); } @Test public void incrementAndCrossStepBoundary() { StepDouble v = new StepDouble(0.0, clock, 10L); - v.getCurrent().addAndGet(1.0); + v.addAndGet(clock.wallTime(), 1.0); clock.setWallTime(10L); - Assertions.assertEquals(0.0, v.getCurrent().get(), 1e-12); + Assertions.assertEquals(0.0, v.getCurrent(), 1e-12); Assertions.assertEquals(1.0, v.poll(), 1e-12); } @Test public void missedRead() { StepDouble v = new StepDouble(0.0, clock, 10L); - v.getCurrent().addAndGet(1.0); + v.addAndGet(clock.wallTime(), 1.0); clock.setWallTime(20L); - Assertions.assertEquals(0.0, v.getCurrent().get(), 1e-12); + Assertions.assertEquals(0.0, v.getCurrent(), 1e-12); Assertions.assertEquals(0.0, v.poll(), 1e-12); } + + @Test + public void initDefault() { + StepDouble v = new StepDouble(0.0, clock, 10L); + Assertions.assertEquals(0.0, v.getCurrent(), 1e-12); + } + + @Test + public void initWithValue() { + StepDouble v = new StepDouble(42.0, clock, 10L); + Assertions.assertEquals(42.0, v.getCurrent(), 1e-12); + } + + @Test + public void set() { + StepDouble v = new StepDouble(13.0, clock, 10L); + v.setCurrent(clock.wallTime(), 42.0); + Assertions.assertEquals(42.0, v.getCurrent(clock.wallTime()), 1e-12); + } + + @Test + public void getAndSet() { + final long now = clock.wallTime(); + StepDouble v = new StepDouble(13.0, clock, 10L); + Assertions.assertEquals(13.0, v.getAndSet(now, 42.0), 1e-12); + Assertions.assertEquals(42.0, v.getCurrent(now), 1e-12); + } + + @Test + public void compareAndSet() { + final long now = clock.wallTime(); + StepDouble v = new StepDouble(13.0, clock, 10L); + Assertions.assertTrue(v.compareAndSet(now, 13.0, 42.0)); + Assertions.assertEquals(42.0, v.getCurrent(now), 1e-12); + } + + @Test + public void compareAndSetFail() { + final long now = clock.wallTime(); + StepDouble v = new StepDouble(13.0, clock, 10L); + Assertions.assertFalse(v.compareAndSet(now, 12.0, 42.0)); + Assertions.assertEquals(13.0, v.getCurrent(now), 1e-12); + } + + @Test + public void addAndGet() { + final long now = clock.wallTime(); + StepDouble v = new StepDouble(13.0, clock, 10L); + Assertions.assertEquals(55.0, v.addAndGet(now, 42.0), 1e-12); + Assertions.assertEquals(55.0, v.getCurrent(now), 1e-12); + } + + @Test + public void getAndAdd() { + final long now = clock.wallTime(); + StepDouble v = new StepDouble(13.0, clock, 10L); + Assertions.assertEquals(13.0, v.getAndAdd(now, 42.0), 1e-12); + Assertions.assertEquals(55.0, v.getCurrent(now), 1e-12); + } + + @Test + public void minGt() { + final long now = clock.wallTime(); + StepDouble v = new StepDouble(0.0, clock, 10L); + v.min(now, 2.0); + Assertions.assertEquals(0.0, v.getCurrent(now), 1e-12); + } + + @Test + public void minLt() { + final long now = clock.wallTime(); + StepDouble v = new StepDouble(2.0, clock, 10L); + v.min(now, 0.0); + Assertions.assertEquals(0.0, v.getCurrent(now), 1e-12); + } + + @Test + public void minNegative() { + final long now = clock.wallTime(); + StepDouble v = new StepDouble(-42.0, clock, 10L); + v.min(now, -41.0); + Assertions.assertEquals(-42.0, v.getCurrent(now), 1e-12); + } + + @Test + public void minNaN() { + final long now = clock.wallTime(); + StepDouble v = new StepDouble(Double.NaN, clock, 10L); + v.min(now, 0.0); + Assertions.assertEquals(0.0, v.getCurrent(now), 1e-12); + } + + @Test + public void minValueNaN() { + final long now = clock.wallTime(); + StepDouble v = new StepDouble(0.0, clock, 10L); + v.min(now, Double.NaN); + Assertions.assertEquals(0.0, v.getCurrent(now), 1e-12); + } + + @Test + public void minNegativeNaN() { + final long now = clock.wallTime(); + StepDouble v = new StepDouble(Double.NaN, clock, 10L); + v.min(now, -42.0); + Assertions.assertEquals(-42.0, v.getCurrent(now), 1e-12); + } + + @Test + public void minValueInfinity() { + final long now = clock.wallTime(); + StepDouble v = new StepDouble(0.0, clock, 10L); + v.min(now, Double.NEGATIVE_INFINITY); + Assertions.assertEquals(0.0, v.getCurrent(now), 1e-12); + } + + @Test + public void maxGt() { + final long now = clock.wallTime(); + StepDouble v = new StepDouble(0.0, clock, 10L); + v.max(now, 2.0); + Assertions.assertEquals(2.0, v.getCurrent(now), 1e-12); + } + + @Test + public void maxLt() { + final long now = clock.wallTime(); + StepDouble v = new StepDouble(2.0, clock, 10L); + v.max(now, 0.0); + Assertions.assertEquals(2.0, v.getCurrent(now), 1e-12); + } + + @Test + public void maxNegative() { + final long now = clock.wallTime(); + StepDouble v = new StepDouble(-42.0, clock, 10L); + v.max(now, -41.0); + Assertions.assertEquals(-41.0, v.getCurrent(now), 1e-12); + } + + @Test + public void maxNaN() { + final long now = clock.wallTime(); + StepDouble v = new StepDouble(Double.NaN, clock, 10L); + v.max(now, 0.0); + Assertions.assertEquals(0.0, v.getCurrent(now), 1e-12); + } + + @Test + public void maxValueNaN() { + final long now = clock.wallTime(); + StepDouble v = new StepDouble(0.0, clock, 10L); + v.max(now, Double.NaN); + Assertions.assertEquals(0.0, v.getCurrent(now), 1e-12); + } + + @Test + public void maxNegativeNaN() { + final long now = clock.wallTime(); + StepDouble v = new StepDouble(Double.NaN, clock, 10L); + v.max(now, -42.0); + Assertions.assertEquals(-42.0, v.getCurrent(now), 1e-12); + } + + @Test + public void maxValueInfinity() { + final long now = clock.wallTime(); + StepDouble v = new StepDouble(0.0, clock, 10L); + v.max(now, Double.POSITIVE_INFINITY); + Assertions.assertEquals(0.0, v.getCurrent(now), 1e-12); + } } diff --git a/spectator-api/src/test/java/com/netflix/spectator/impl/StepLongTest.java b/spectator-api/src/test/java/com/netflix/spectator/impl/StepLongTest.java index 70091dc2b..194da22e5 100644 --- a/spectator-api/src/test/java/com/netflix/spectator/impl/StepLongTest.java +++ b/spectator-api/src/test/java/com/netflix/spectator/impl/StepLongTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2019 Netflix, Inc. + * Copyright 2014-2024 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,33 +32,156 @@ public void init() { @Test public void empty() { StepLong v = new StepLong(0L, clock, 10L); - Assertions.assertEquals(0L, v.getCurrent().get()); + Assertions.assertEquals(0L, v.getCurrent()); Assertions.assertEquals(0L, v.poll()); } @Test public void increment() { StepLong v = new StepLong(0L, clock, 10L); - v.getCurrent().incrementAndGet(); - Assertions.assertEquals(1L, v.getCurrent().get()); + v.incrementAndGet(clock.wallTime()); + Assertions.assertEquals(1L, v.getCurrent()); Assertions.assertEquals(0L, v.poll()); } @Test public void incrementAndCrossStepBoundary() { StepLong v = new StepLong(0L, clock, 10L); - v.getCurrent().incrementAndGet(); + v.incrementAndGet(clock.wallTime()); clock.setWallTime(10L); - Assertions.assertEquals(0L, v.getCurrent().get()); + Assertions.assertEquals(0L, v.getCurrent()); Assertions.assertEquals(1L, v.poll()); } @Test public void missedRead() { StepLong v = new StepLong(0L, clock, 10L); - v.getCurrent().incrementAndGet(); + v.incrementAndGet(clock.wallTime()); clock.setWallTime(20L); - Assertions.assertEquals(0L, v.getCurrent().get()); + Assertions.assertEquals(0L, v.getCurrent()); Assertions.assertEquals(0L, v.poll()); } + + @Test + public void initDefault() { + StepLong v = new StepLong(0L, clock, 10L); + Assertions.assertEquals(0L, v.getCurrent()); + } + + @Test + public void initWithValue() { + StepLong v = new StepLong(42L, clock, 10L); + Assertions.assertEquals(42L, v.getCurrent()); + } + + @Test + public void set() { + StepLong v = new StepLong(13L, clock, 10L); + v.setCurrent(clock.wallTime(), 42L); + Assertions.assertEquals(42L, v.getCurrent(clock.wallTime())); + } + + @Test + public void getAndSet() { + final long now = clock.wallTime(); + StepLong v = new StepLong(13L, clock, 10L); + Assertions.assertEquals(13L, v.getAndSet(now, 42L)); + Assertions.assertEquals(42L, v.getCurrent(now)); + } + + @Test + public void compareAndSet() { + final long now = clock.wallTime(); + StepLong v = new StepLong(13L, clock, 10L); + Assertions.assertTrue(v.compareAndSet(now, 13L, 42L)); + Assertions.assertEquals(42L, v.getCurrent(now)); + } + + @Test + public void compareAndSetFail() { + final long now = clock.wallTime(); + StepLong v = new StepLong(13L, clock, 10L); + Assertions.assertFalse(v.compareAndSet(now, 12L, 42L)); + Assertions.assertEquals(13L, v.getCurrent(now)); + } + + @Test + public void incrementAndGet() { + final long now = clock.wallTime(); + StepLong v = new StepLong(13L, clock, 10L); + Assertions.assertEquals(14L, v.incrementAndGet(now)); + Assertions.assertEquals(14L, v.getCurrent(now)); + } + + @Test + public void getAndIncrement() { + final long now = clock.wallTime(); + StepLong v = new StepLong(13L, clock, 10L); + Assertions.assertEquals(13L, v.getAndIncrement(now)); + Assertions.assertEquals(14L, v.getCurrent(now)); + } + + @Test + public void addAndGet() { + final long now = clock.wallTime(); + StepLong v = new StepLong(13L, clock, 10L); + Assertions.assertEquals(55L, v.addAndGet(now, 42L)); + Assertions.assertEquals(55L, v.getCurrent(now)); + } + + @Test + public void getAndAdd() { + final long now = clock.wallTime(); + StepLong v = new StepLong(13L, clock, 10L); + Assertions.assertEquals(13L, v.getAndAdd(now, 42L)); + Assertions.assertEquals(55L, v.getCurrent(now)); + } + + @Test + public void minGt() { + final long now = clock.wallTime(); + StepLong v = new StepLong(0L, clock, 10L); + v.min(now, 2L); + Assertions.assertEquals(0L, v.getCurrent(now)); + } + + @Test + public void minLt() { + final long now = clock.wallTime(); + StepLong v = new StepLong(2L, clock, 10L); + v.min(now, 0L); + Assertions.assertEquals(0L, v.getCurrent(now)); + } + + @Test + public void minNegative() { + final long now = clock.wallTime(); + StepLong v = new StepLong(-42L, clock, 10L); + v.min(now, -41L); + Assertions.assertEquals(-42L, v.getCurrent(now)); + } + + @Test + public void maxGt() { + final long now = clock.wallTime(); + StepLong v = new StepLong(0L, clock, 10L); + v.max(now, 2L); + Assertions.assertEquals(2L, v.getCurrent(now)); + } + + @Test + public void maxLt() { + final long now = clock.wallTime(); + StepLong v = new StepLong(2L, clock, 10L); + v.max(now, 0L); + Assertions.assertEquals(2L, v.getCurrent(now)); + } + + @Test + public void maxNegative() { + final long now = clock.wallTime(); + StepLong v = new StepLong(-42L, clock, 10L); + v.max(now, -41L); + Assertions.assertEquals(-41L, v.getCurrent(now)); + } } diff --git a/spectator-reg-atlas/src/main/java/com/netflix/spectator/atlas/AtlasCounter.java b/spectator-reg-atlas/src/main/java/com/netflix/spectator/atlas/AtlasCounter.java index b09ce4c4e..8f645b873 100644 --- a/spectator-reg-atlas/src/main/java/com/netflix/spectator/atlas/AtlasCounter.java +++ b/spectator-reg-atlas/src/main/java/com/netflix/spectator/atlas/AtlasCounter.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2020 Netflix, Inc. + * Copyright 2014-2024 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,8 +47,8 @@ class AtlasCounter extends AtlasMeter implements Counter { @Override public void add(double amount) { if (Double.isFinite(amount) && amount > 0.0) { - long now = clock.wallTime(); - value.getCurrent(now).addAndGet(amount); + final long now = clock.wallTime(); + value.addAndGet(now, amount); updateLastModTime(now); } } diff --git a/spectator-reg-atlas/src/main/java/com/netflix/spectator/atlas/AtlasDistributionSummary.java b/spectator-reg-atlas/src/main/java/com/netflix/spectator/atlas/AtlasDistributionSummary.java index b67ae54ad..5b916023b 100644 --- a/spectator-reg-atlas/src/main/java/com/netflix/spectator/atlas/AtlasDistributionSummary.java +++ b/spectator-reg-atlas/src/main/java/com/netflix/spectator/atlas/AtlasDistributionSummary.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2022 Netflix, Inc. + * Copyright 2014-2024 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,8 +23,6 @@ import com.netflix.spectator.impl.StepLong; import com.netflix.spectator.impl.StepValue; -import java.util.concurrent.atomic.AtomicLong; - /** * Distribution summary that reports four measurements to Atlas: * @@ -94,11 +92,11 @@ private void reportMaxMeasurement(long now, MeasurementConsumer consumer, Id mid @Override public void record(long amount) { long now = clock.wallTime(); - count.getCurrent(now).incrementAndGet(); + count.incrementAndGet(now); if (amount > 0) { - total.getCurrent(now).addAndGet(amount); - totalOfSquares.getCurrent(now).addAndGet((double) amount * amount); - updateMax(max.getCurrent(now), amount); + total.addAndGet(now, amount); + totalOfSquares.addAndGet(now, (double) amount * amount); + max.max(now, amount); } updateLastModTime(now); } @@ -121,20 +119,13 @@ private void reportMaxMeasurement(long now, MeasurementConsumer consumer, Id mid // issue updates as a batch final long now = clock.wallTime(); - count.getCurrent(now).addAndGet(limit); - total.getCurrent(now).addAndGet(accumulatedTotal); - totalOfSquares.getCurrent(now).addAndGet(accumulatedTotalOfSquares); - updateMax(max.getCurrent(now), accumulatedMax); + count.addAndGet(now, limit); + total.addAndGet(now, accumulatedTotal); + totalOfSquares.addAndGet(now, accumulatedTotalOfSquares); + max.max(now, accumulatedMax); updateLastModTime(now); } - private void updateMax(AtomicLong maxValue, long v) { - long p = maxValue.get(); - while (v > p && !maxValue.compareAndSet(p, v)) { - p = maxValue.get(); - } - } - @Override public long count() { return count.poll(); } @@ -154,9 +145,9 @@ private void updateMax(AtomicLong maxValue, long v) { */ void update(long count, long total, double totalOfSquares, long max) { long now = clock.wallTime(); - this.count.getCurrent(now).addAndGet(count); - this.total.getCurrent(now).addAndGet(total); - this.totalOfSquares.getCurrent(now).addAndGet(totalOfSquares); - updateMax(this.max.getCurrent(now), max); + this.count.addAndGet(now, count); + this.total.addAndGet(now, total); + this.totalOfSquares.addAndGet(now, totalOfSquares); + this.max.max(now, max); } } diff --git a/spectator-reg-atlas/src/main/java/com/netflix/spectator/atlas/AtlasMaxGauge.java b/spectator-reg-atlas/src/main/java/com/netflix/spectator/atlas/AtlasMaxGauge.java index 41ed24f4e..8f0dda28b 100644 --- a/spectator-reg-atlas/src/main/java/com/netflix/spectator/atlas/AtlasMaxGauge.java +++ b/spectator-reg-atlas/src/main/java/com/netflix/spectator/atlas/AtlasMaxGauge.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2020 Netflix, Inc. + * Copyright 2014-2024 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,7 +53,7 @@ class AtlasMaxGauge extends AtlasMeter implements Gauge { @Override public void set(double v) { long now = clock.wallTime(); - value.getCurrent(now).max(v); + value.max(now, v); updateLastModTime(now); } diff --git a/spectator-reg-atlas/src/main/java/com/netflix/spectator/atlas/AtlasTimer.java b/spectator-reg-atlas/src/main/java/com/netflix/spectator/atlas/AtlasTimer.java index 9c75bd84c..9baf49c5f 100644 --- a/spectator-reg-atlas/src/main/java/com/netflix/spectator/atlas/AtlasTimer.java +++ b/spectator-reg-atlas/src/main/java/com/netflix/spectator/atlas/AtlasTimer.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2023 Netflix, Inc. + * Copyright 2014-2024 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,6 @@ import java.time.Duration; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; /** * Timer that reports four measurements to Atlas: @@ -100,12 +99,12 @@ private void reportMaxMeasurement(long now, MeasurementConsumer consumer, Id mid @Override public void record(long amount, TimeUnit unit) { long now = clock.wallTime(); - count.getCurrent(now).incrementAndGet(); + count.incrementAndGet(now); if (amount > 0) { final long nanos = unit.toNanos(amount); - total.getCurrent(now).addAndGet(nanos); - totalOfSquares.getCurrent(now).addAndGet((double) nanos * nanos); - updateMax(max.getCurrent(now), nanos); + total.addAndGet(now, nanos); + totalOfSquares.addAndGet(now, (double) nanos * nanos); + max.max(now, nanos); } updateLastModTime(now); } @@ -129,10 +128,10 @@ private void reportMaxMeasurement(long now, MeasurementConsumer consumer, Id mid // issue updates as a batch final long now = clock.wallTime(); - count.getCurrent(now).addAndGet(limit); - total.getCurrent(now).addAndGet(accumulatedTotal); - totalOfSquares.getCurrent(now).addAndGet(accumulatedTotalOfSquares); - updateMax(max.getCurrent(now), accumulatedMax); + count.addAndGet(now, limit); + total.addAndGet(now, accumulatedTotal); + totalOfSquares.addAndGet(now, accumulatedTotalOfSquares); + max.max(now, accumulatedMax); updateLastModTime(now); } @@ -155,20 +154,13 @@ private void reportMaxMeasurement(long now, MeasurementConsumer consumer, Id mid // issue updates as a batch final long now = clock.wallTime(); - count.getCurrent(now).addAndGet(limit); - total.getCurrent(now).addAndGet(accumulatedTotal); - totalOfSquares.getCurrent(now).addAndGet(accumulatedTotalOfSquares); - updateMax(max.getCurrent(now), accumulatedMax); + count.addAndGet(now, limit); + total.addAndGet(now, accumulatedTotal); + totalOfSquares.addAndGet(now, accumulatedTotalOfSquares); + max.max(now, accumulatedMax); updateLastModTime(now); } - private void updateMax(AtomicLong maxValue, long v) { - long p = maxValue.get(); - while (v > p && !maxValue.compareAndSet(p, v)) { - p = maxValue.get(); - } - } - @Override public long count() { return count.poll(); } @@ -191,9 +183,9 @@ private void updateMax(AtomicLong maxValue, long v) { */ void update(long count, double total, double totalOfSquares, long max) { long now = clock.wallTime(); - this.count.getCurrent(now).addAndGet(count); - this.total.getCurrent(now).addAndGet(total); - this.totalOfSquares.getCurrent(now).addAndGet(totalOfSquares); - updateMax(this.max.getCurrent(now), max); + this.count.addAndGet(now, count); + this.total.addAndGet(now, total); + this.totalOfSquares.addAndGet(now, totalOfSquares); + this.max.max(now, max); } } diff --git a/spectator-reg-micrometer/src/main/java/com/netflix/spectator/micrometer/MicrometerRegistry.java b/spectator-reg-micrometer/src/main/java/com/netflix/spectator/micrometer/MicrometerRegistry.java index 289b13f9b..80e09f266 100644 --- a/spectator-reg-micrometer/src/main/java/com/netflix/spectator/micrometer/MicrometerRegistry.java +++ b/spectator-reg-micrometer/src/main/java/com/netflix/spectator/micrometer/MicrometerRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2019 Netflix, Inc. + * Copyright 2014-2024 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -133,12 +133,13 @@ private Meter convert(io.micrometer.core.instrument.Meter meter) { @Override public Gauge maxGauge(Id id) { // Note: micrometer doesn't support this type directly so it uses an arbitrary // window of 1m - StepDouble value = new StepDouble(Double.NaN, clock(), 60000L); + final Clock clk = clock(); + StepDouble value = new StepDouble(Double.NaN, clk, 60000L); io.micrometer.core.instrument.Gauge gauge = io.micrometer.core.instrument.Gauge .builder(id.name(), value, StepDouble::poll) .tags(convert(id.tags())) .register(impl); - return new MicrometerGauge(id, v -> value.getCurrent().max(v), gauge); + return new MicrometerGauge(id, v -> value.max(clk.wallTime(), v), gauge); } @Override public Meter get(Id id) {