From 62c083a6ea5a5b0d904affc1afc3be9bb012f94c Mon Sep 17 00:00:00 2001 From: rbygrave Date: Tue, 29 Jul 2014 23:48:38 +1200 Subject: [PATCH] Add LongMaxUpdater, change CQueryPlanStats to use LongMaxUpdater --- .../server/query/CQueryPlanStats.java | 35 +++- .../server/util/LongMaxUpdater.java | 184 ++++++++++++++++++ 2 files changed, 209 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/avaje/ebeaninternal/server/util/LongMaxUpdater.java diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlanStats.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlanStats.java index 1a9af2362f..075f59d08e 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlanStats.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlanStats.java @@ -1,4 +1,3 @@ - package com.avaje.ebeaninternal.server.query; import java.util.ArrayList; @@ -12,6 +11,7 @@ import com.avaje.ebean.meta.MetaQueryPlanOriginCount; import com.avaje.ebean.meta.MetaQueryPlanStatistic; import com.avaje.ebeaninternal.server.util.LongAdder; +import com.avaje.ebeaninternal.server.util.LongMaxUpdater; /** * Statistics for a specific query plan that can accumulate. @@ -26,7 +26,7 @@ public final class CQueryPlanStats { private final LongAdder totalBeans = new LongAdder(); - private final AtomicLong maxTime = new AtomicLong(); + private final LongMaxUpdater maxTime = new LongMaxUpdater(); private final AtomicLong startTime = new AtomicLong(System.currentTimeMillis()); @@ -34,19 +34,25 @@ public final class CQueryPlanStats { private final ConcurrentHashMap origins; + /** + * Construct for a given query plan. + */ public CQueryPlanStats(CQueryPlan queryPlan, boolean collectQueryOrigins) { + this.queryPlan = queryPlan; this.origins = !collectQueryOrigins ? null : new ConcurrentHashMap(); } + /** + * Add a query execution to the statistics. + */ public void add(long loadedBeanCount, long timeMicros, ObjectGraphNode objectGraphNode) { + count.increment(); totalBeans.add(loadedBeanCount); totalTime.add(timeMicros); - if (timeMicros > maxTime.get()) { - // effectively a high water mark - maxTime.set(timeMicros); - } + maxTime.update(timeMicros); + // not safe but should be atomic lastQueryTime = System.currentTimeMillis(); @@ -64,12 +70,16 @@ public void add(long loadedBeanCount, long timeMicros, ObjectGraphNode objectGra } } + /** + * Reset the internal statistics counters. + */ public void reset() { + // Racey but near enough for our purposes as we don't want locks count.reset(); totalBeans.reset(); totalTime.reset(); - maxTime.set(0); + maxTime.reset(); startTime.set(System.currentTimeMillis()); if (origins != null) { @@ -77,13 +87,18 @@ public void reset() { counter.reset(); } } - } + /** + * Return the last time this query was executed. + */ public long getLastQueryTime() { return lastQueryTime; } + /** + * Return a Snapshot of the query execution statistics potentially resetting the internal counters. + */ public Snapshot getSnapshot(boolean reset) { List origins = getOrigins(reset); @@ -91,9 +106,9 @@ public Snapshot getSnapshot(boolean reset) { // not guaranteed to be consistent due to time gaps between getting each value out of LongAdders but can live with that // relative to the cost of making sure count and totalTime etc are all guaranteed to be consistent if (reset) { - return new Snapshot(queryPlan, count.sumThenReset(), totalTime.sumThenReset(), totalBeans.sumThenReset(), maxTime.getAndSet(0), startTime.getAndSet(System.currentTimeMillis()), lastQueryTime, origins); + return new Snapshot(queryPlan, count.sumThenReset(), totalTime.sumThenReset(), totalBeans.sumThenReset(), maxTime.maxThenReset(), startTime.getAndSet(System.currentTimeMillis()), lastQueryTime, origins); } - return new Snapshot(queryPlan, count.sum(), totalTime.sum(), totalBeans.sum(), maxTime.get(), startTime.get(), lastQueryTime, origins); + return new Snapshot(queryPlan, count.sum(), totalTime.sum(), totalBeans.sum(), maxTime.max(), startTime.get(), lastQueryTime, origins); } /** diff --git a/src/main/java/com/avaje/ebeaninternal/server/util/LongMaxUpdater.java b/src/main/java/com/avaje/ebeaninternal/server/util/LongMaxUpdater.java new file mode 100644 index 0000000000..b702ff2629 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/util/LongMaxUpdater.java @@ -0,0 +1,184 @@ +package com.avaje.ebeaninternal.server.util; + +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import java.io.Serializable; + +/** + * One or more variables that together maintain a running {@code long} + * maximum with initial value {@code Long.MIN_VALUE}. When updates + * (method {@link #update}) are contended across threads, the set of + * variables may grow dynamically to reduce contention. Method {@link + * #max} (or, equivalently, {@link #longValue}) returns the current + * maximum across the variables maintaining updates. + * + *

This class extends {@link Number}, but does not define + * methods such as {@code equals}, {@code hashCode} and {@code + * compareTo} because instances are expected to be mutated, and so are + * not useful as collection keys. + * + *

jsr166e note: This class is targeted to be placed in + * java.util.concurrent.atomic. + * + * @since 1.8 + * @author Doug Lea + */ +public class LongMaxUpdater extends Striped64 implements Serializable { + private static final long serialVersionUID = 7249069246863182397L; + + /** + * Version of max for use in retryUpdate + */ + final long fn(long v, long x) { return v > x ? v : x; } + + /** + * Creates a new instance with initial maximum of {@code + * Long.MIN_VALUE}. + */ + public LongMaxUpdater() { + base = Long.MIN_VALUE; + } + + /** + * Updates the maximum to be at least the given value. + * + * @param x the value to update + */ + public void update(long x) { + Cell[] as; long b, v; HashCode hc; Cell a; int n; + if ((as = cells) != null || + (b = base) < x && !casBase(b, x)) { + boolean uncontended = true; + int h = (hc = threadHashCode.get()).code; + if (as == null || (n = as.length) < 1 || + (a = as[(n - 1) & h]) == null || + ((v = a.value) < x && !(uncontended = a.cas(v, x)))) + retryUpdate(x, hc, uncontended); + } + } + + /** + * Returns the current maximum. The returned value is + * NOT an atomic snapshot; invocation in the absence of + * concurrent updates returns an accurate result, but concurrent + * updates that occur while the value is being calculated might + * not be incorporated. + * + * @return the maximum + */ + public long max() { + Cell[] as = cells; + long max = base; + if (as != null) { + int n = as.length; + long v; + for (int i = 0; i < n; ++i) { + Cell a = as[i]; + if (a != null && (v = a.value) > max) + max = v; + } + } + return max; + } + + /** + * Resets variables maintaining updates to {@code Long.MIN_VALUE}. + * This method may be a useful alternative to creating a new + * updater, but is only effective if there are no concurrent + * updates. Because this method is intrinsically racy, it should + * only be used when it is known that no threads are concurrently + * updating. + */ + public void reset() { + internalReset(Long.MIN_VALUE); + } + + /** + * Equivalent in effect to {@link #max} followed by {@link + * #reset}. This method may apply for example during quiescent + * points between multithreaded computations. If there are + * updates concurrent with this method, the returned value is + * not guaranteed to be the final value occurring before + * the reset. + * + * @return the maximum + */ + public long maxThenReset() { + Cell[] as = cells; + long max = base; + base = Long.MIN_VALUE; + if (as != null) { + int n = as.length; + for (int i = 0; i < n; ++i) { + Cell a = as[i]; + if (a != null) { + long v = a.value; + a.value = Long.MIN_VALUE; + if (v > max) + max = v; + } + } + } + return max; + } + + /** + * Returns the String representation of the {@link #max}. + * @return the String representation of the {@link #max} + */ + public String toString() { + return Long.toString(max()); + } + + /** + * Equivalent to {@link #max}. + * + * @return the maximum + */ + public long longValue() { + return max(); + } + + /** + * Returns the {@link #max} as an {@code int} after a narrowing + * primitive conversion. + */ + public int intValue() { + return (int)max(); + } + + /** + * Returns the {@link #max} as a {@code float} + * after a widening primitive conversion. + */ + public float floatValue() { + return (float)max(); + } + + /** + * Returns the {@link #max} as a {@code double} after a widening + * primitive conversion. + */ + public double doubleValue() { + return (double)max(); + } + + private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException { + s.defaultWriteObject(); + s.writeLong(max()); + } + + private void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + s.defaultReadObject(); + busy = 0; + cells = null; + base = s.readLong(); + } + +}