aurora-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From zma...@apache.org
Subject [24/37] aurora git commit: Import of Twitter Commons.
Date Tue, 25 Aug 2015 18:19:38 GMT
http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/stats/RecordingStatImpl.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/stats/RecordingStatImpl.java b/commons/src/main/java/com/twitter/common/stats/RecordingStatImpl.java
new file mode 100644
index 0000000..aa635e4
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/stats/RecordingStatImpl.java
@@ -0,0 +1,49 @@
+// =================================================================================================
+// Copyright 2011 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.stats;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * A convenience class to wrap a {@link RecordingStat}.
+ *
+ * @author William Farner
+ */
+class RecordingStatImpl<T extends Number> implements RecordingStat<T> {
+  private final Stat<T> recorded;
+  private final String name;
+
+  public RecordingStatImpl(Stat<T> recorded) {
+    this.recorded = Preconditions.checkNotNull(recorded);
+    this.name = Stats.validateName(recorded.getName());
+  }
+
+  @Override
+  public String getName() {
+    return name;
+  }
+
+  @Override
+  public T sample() {
+    return read();
+  }
+
+  @Override
+  public T read() {
+    return recorded.read();
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/stats/RequestStats.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/stats/RequestStats.java b/commons/src/main/java/com/twitter/common/stats/RequestStats.java
new file mode 100644
index 0000000..c2454d3
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/stats/RequestStats.java
@@ -0,0 +1,145 @@
+// =================================================================================================
+// Copyright 2011 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.stats;
+
+import com.twitter.common.stats.StatsProvider.RequestTimer;
+
+import javax.annotation.Nullable;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * A class to represent the statistics associated with a client connection to an external service.
+ * Tracks request latency/rate, and error rate.
+ *
+ * @author William Farner
+ */
+public class RequestStats implements RequestTimer {
+
+  private static final float DEFAULT_SAMPLE_PERCENT = 10;
+  private static final double[] DEFAULT_PERCENTILES = {10, 50, 90, 99, 99.9, 99.99};
+
+  private final SlidingStats requests;
+  private final Percentile<Long> percentile;
+
+  private final AtomicLong errors;
+  private final AtomicLong reconnects;
+  private final AtomicLong timeouts;
+
+  /**
+   * Creates a new request statistics object, using the default percentiles and sampling rate.
+   *
+   * @param name The unique name for this request type.
+   */
+  public RequestStats(String name) {
+    this(name, new Percentile<Long>(name, DEFAULT_SAMPLE_PERCENT, DEFAULT_PERCENTILES));
+  }
+
+  /**
+   * Creates a new request statistics object using a custom percentile tracker.
+   *
+   * @param name The unique name for this request type.
+   * @param percentile The percentile tracker, or {@code null} to disable percentile tracking.
+   */
+  public RequestStats(String name, @Nullable Percentile<Long> percentile) {
+    requests = new SlidingStats(name + "_requests", "micros");
+    this.percentile = percentile;
+    errors = Stats.exportLong(name + "_errors");
+    reconnects = Stats.exportLong(name + "_reconnects");
+    timeouts = Stats.exportLong(name + "_timeouts");
+    Rate<AtomicLong> requestsPerSec =
+        Rate.of(name + "_requests_per_sec", requests.getEventCounter()).build();
+    Stats.export(Ratio.of(name + "_error_rate",
+        Rate.of(name + "_errors_per_sec", errors).build(), requestsPerSec));
+    Rate<AtomicLong> timeoutsPerSec = Rate.of(name + "_timeouts_per_sec", timeouts).build();
+    Stats.export(timeoutsPerSec);
+    Stats.export(Ratio.of(name + "_timeout_rate", timeoutsPerSec, requestsPerSec));
+  }
+
+  public SlidingStats getSlidingStats() {
+    return requests;
+  }
+
+  public AtomicLong getErrorCounter() {
+    return errors;
+  }
+
+  public AtomicLong getReconnectCounter() {
+    return reconnects;
+  }
+
+  public AtomicLong getTimeoutCounter() {
+    return timeouts;
+  }
+
+  public Percentile<Long> getPercentile() {
+    return percentile;
+  }
+
+  /**
+   * Accumulates a request and its latency.
+   *
+   * @param latencyMicros The elapsed time required to complete the request.
+   */
+  public void requestComplete(long latencyMicros) {
+    requests.accumulate(latencyMicros);
+    if (percentile != null) percentile.record(latencyMicros);
+  }
+
+  /**
+   * Accumulates the error counter and the request counter.
+   */
+  public void incErrors() {
+    requestComplete(0);
+    errors.incrementAndGet();
+  }
+
+  /**
+   * Accumulates the error counter, the request counter and the request latency.
+   *
+   * @param latencyMicros The elapsed time before the error occurred.
+   */
+  public void incErrors(long latencyMicros) {
+    requestComplete(latencyMicros);
+    errors.incrementAndGet();
+  }
+
+  /**
+   * Accumulates the reconnect counter.
+   */
+  public void incReconnects() {
+    reconnects.incrementAndGet();
+  }
+
+  /**
+   * Accumulates the timtout counter.
+   */
+  public void incTimeouts() {
+    timeouts.incrementAndGet();
+  }
+
+  public long getErrorCount() {
+    return errors.get();
+  }
+
+  public long getReconnectCount() {
+    return reconnects.get();
+  }
+
+  public long getTimeoutCount() {
+    return timeouts.get();
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/stats/ReservoirSampler.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/stats/ReservoirSampler.java b/commons/src/main/java/com/twitter/common/stats/ReservoirSampler.java
new file mode 100644
index 0000000..061181a
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/stats/ReservoirSampler.java
@@ -0,0 +1,84 @@
+package com.twitter.common.stats;
+
+import java.util.Vector;
+
+import com.google.common.base.Preconditions;
+
+import com.twitter.common.util.Random;
+
+/**
+ * An in memory implementation of Reservoir Sampling for sampling from
+ * a population.
+ * <p>Several optimizations can be done.
+ * Especially, one can avoid rolling the dice as many times as the
+ * size of the population with an involved trick.
+ * See "Random Sampling with a Reservoir", Vitter, 1985</p>
+ * <p>TODO (delip): Fix this when the problem arises</p>
+ *
+ * @param <T> Type of the sample
+ * @author Delip Rao
+ */
+public class ReservoirSampler<T> {
+  private final Vector<T> reservoir = new Vector<T>();
+  private final int numSamples;
+
+  private final Random random;
+  private int numItemsSeen = 0;
+
+  /**
+   * Create a new sampler with a certain reservoir size using
+   * a supplied random number generator.
+   *
+   * @param numSamples Maximum number of samples to
+   *                   retain in the reservoir. Must be non-negative.
+   * @param random Instance of the random number generator
+   *               to use for sampling
+   */
+  public ReservoirSampler(int numSamples, Random random) {
+    Preconditions.checkArgument(numSamples > 0,
+        "numSamples should be positive");
+    Preconditions.checkNotNull(random);
+    this.numSamples = numSamples;
+    this.random = random;
+  }
+
+  /**
+   * Create a new sampler with a certain reservoir size using
+   * the default random number generator.
+   *
+   * @param numSamples Maximum number of samples to
+   *        retain in the reservoir. Must be non-negative.
+   */
+  public ReservoirSampler(int numSamples) {
+    this(numSamples, Random.Util.newDefaultRandom());
+  }
+
+  /**
+   * Sample an item and store in the reservoir if needed.
+   *
+   * @param item The item to sample - may not be null.
+   */
+  public void sample(T item) {
+    Preconditions.checkNotNull(item);
+    if (reservoir.size() < numSamples) {
+      // reservoir not yet full, just append
+      reservoir.add(item);
+    } else {
+      // find a sample to replace
+      int rIndex = random.nextInt(numItemsSeen + 1);
+      if (rIndex < numSamples) {
+        reservoir.set(rIndex, item);
+      }
+    }
+    numItemsSeen++;
+  }
+
+  /**
+   * Get samples collected in the reservoir.
+   *
+   * @return A sequence of the samples. No guarantee is provided on the order of the samples.
+   */
+  public Iterable<T> getSamples() {
+    return reservoir;
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/stats/SampledStat.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/stats/SampledStat.java b/commons/src/main/java/com/twitter/common/stats/SampledStat.java
new file mode 100644
index 0000000..74e503e
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/stats/SampledStat.java
@@ -0,0 +1,46 @@
+// =================================================================================================
+// Copyright 2011 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.stats;
+
+/**
+ * A convenience class to perform the basic tasks needed for a {@link RecordingStat} except the
+ * actual value calculation.
+ *
+ * @author William Farner
+ */
+public abstract class SampledStat<T extends Number> extends StatImpl<T> implements RecordingStat<T> {
+
+  private volatile T prevValue;
+
+  public SampledStat(String name, T defaultValue) {
+    super(name);
+    this.prevValue = defaultValue; /* Don't forbid null. */
+  }
+
+  public abstract T doSample();
+
+  @Override
+  public final T sample() {
+    prevValue = doSample();
+    return prevValue;
+  }
+
+  @Override
+  public T read() {
+    return prevValue;
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/stats/Significance.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/stats/Significance.java b/commons/src/main/java/com/twitter/common/stats/Significance.java
new file mode 100644
index 0000000..8356e50
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/stats/Significance.java
@@ -0,0 +1,62 @@
+// =================================================================================================
+// Copyright 2011 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.stats;
+
+/**
+ * Calculate significance scores between an observed amount and an expected amount.
+ *
+ * @author Gilad Mishne
+ */
+public class Significance {
+
+  /**
+   * @param observed The observed amount.
+   * @param expected The expected amount.
+   * @return [(observed - expected) ** 2 / expected] * sign(observed - expected)
+   */
+  public static double chiSqrScore(double observed, double expected) {
+    double score = Math.pow((observed - expected), 2) / expected;
+    if (observed < expected) {
+      score *= -1;
+    }
+    return score;
+  }
+
+  /**
+   * @param observed The observed amount.
+   * @param expected The expected amount.
+   * @return -2 * expected * log(observed / expected) * sign(observed - expected)
+   */
+  public static double logLikelihood(double observed, double expected) {
+    if (observed == 0) {
+      return -expected;
+    }
+    if (expected == 0) {
+      return observed;
+    }
+    double score = -2 * observed * Math.log(observed / expected);
+    if (observed < expected) {
+      score *= -1;
+    }
+    return score;
+  }
+
+  private Significance() {
+    // prevent instantiation
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/stats/SlidingStats.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/stats/SlidingStats.java b/commons/src/main/java/com/twitter/common/stats/SlidingStats.java
new file mode 100644
index 0000000..b676a57
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/stats/SlidingStats.java
@@ -0,0 +1,96 @@
+// =================================================================================================
+// Copyright 2011 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.stats;
+
+import com.twitter.common.base.MorePreconditions;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Tracks event statistics over a sliding window of time. An event is something that has a
+ * frequency and associated total.
+ *
+ * @author William Farner
+ */
+public class SlidingStats {
+
+  private static final int DEFAULT_WINDOW_SIZE = 1;
+
+  private final AtomicLong total;
+  private final AtomicLong events;
+  private final Stat<Double> perEventLatency;
+
+  /**
+   * Creates a new sliding statistic with the given name
+   *
+   * @param name Name for this stat collection.
+   * @param totalUnitDisplay String to display for the total counter unit.
+   */
+  public SlidingStats(String name, String totalUnitDisplay) {
+    this(name, totalUnitDisplay, DEFAULT_WINDOW_SIZE);
+  }
+
+  /**
+   * Creates a new sliding statistic with the given name
+   *
+   * @param name Name for this stat collection.
+   * @param totalUnitDisplay String to display for the total counter unit.
+   * @param windowSize The window size for the per second Rate and Ratio stats.
+   */
+  public SlidingStats(String name, String totalUnitDisplay, int windowSize) {
+    MorePreconditions.checkNotBlank(name);
+
+    String totalDisplay = name + "_" + totalUnitDisplay + "_total";
+    String eventDisplay = name + "_events";
+    total = Stats.exportLong(totalDisplay);
+    events = Stats.exportLong(eventDisplay);
+    perEventLatency = Stats.export(Ratio.of(name + "_" + totalUnitDisplay + "_per_event",
+        Rate.of(totalDisplay + "_per_sec", total).withWindowSize(windowSize).build(),
+        Rate.of(eventDisplay + "_per_sec", events).withWindowSize(windowSize).build()));
+  }
+
+  public AtomicLong getTotalCounter() {
+    return total;
+  }
+
+  public AtomicLong getEventCounter() {
+    return events;
+  }
+
+  public Stat<Double> getPerEventLatency() {
+    return perEventLatency;
+  }
+
+  /**
+   * Accumulates counter by an offset.  This is is useful for tracking things like
+   * latency of operations.
+   *
+   * TODO(William Farner): Implement a wrapper to SlidingStats that expects to accumulate time, and can
+   *    convert between time units.
+   *
+   * @param value The value to accumulate.
+   */
+  public void accumulate(long value) {
+    total.addAndGet(value);
+    events.incrementAndGet();
+  }
+
+  @Override
+  public String toString() {
+    return total + " " + events;
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/stats/Stat.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/stats/Stat.java b/commons/src/main/java/com/twitter/common/stats/Stat.java
new file mode 100644
index 0000000..586b3bb
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/stats/Stat.java
@@ -0,0 +1,40 @@
+// =================================================================================================
+// Copyright 2011 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.stats;
+
+/**
+ * A stat that may only be read, no method calls will cause any internal state changes.
+ *
+ * @author William Farner
+ */
+public interface Stat<T> {
+
+  /**
+   * Gets the name of this stat. For sake of convention, variable names should be alphanumeric, and
+   * use underscores.
+   *
+   * @return The variable name.
+   */
+  String getName();
+
+  /**
+   * Retrieves the most recent value of the stat.
+   *
+   * @return The most recent value.
+   */
+  T read();
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/stats/StatImpl.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/stats/StatImpl.java b/commons/src/main/java/com/twitter/common/stats/StatImpl.java
new file mode 100644
index 0000000..78713db
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/stats/StatImpl.java
@@ -0,0 +1,38 @@
+// =================================================================================================
+// Copyright 2011 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.stats;
+
+import com.twitter.common.base.MorePreconditions;
+
+/**
+ * A convenience class to not require stat implementers to implement {@link #getName()}.
+ *
+ * @author William Farner
+ */
+public abstract class StatImpl<T> implements Stat<T> {
+
+  private final String name;
+
+  public StatImpl(String name) {
+    this.name = MorePreconditions.checkNotBlank(name);
+  }
+
+  @Override
+  public String getName() {
+    return name;
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/stats/StatRegistry.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/stats/StatRegistry.java b/commons/src/main/java/com/twitter/common/stats/StatRegistry.java
new file mode 100644
index 0000000..9994aa3
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/stats/StatRegistry.java
@@ -0,0 +1,32 @@
+// =================================================================================================
+// Copyright 2012 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.stats;
+
+/**
+ * A registry of stats.
+ *
+ * @author William Farner
+ */
+public interface StatRegistry {
+
+  /**
+   * Gets all stats in the registry.
+   *
+   * @return All registered stats.
+   */
+  Iterable<RecordingStat<? extends Number>> getStats();
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/stats/Statistics.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/stats/Statistics.java b/commons/src/main/java/com/twitter/common/stats/Statistics.java
new file mode 100644
index 0000000..2d051e8
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/stats/Statistics.java
@@ -0,0 +1,96 @@
+// =================================================================================================
+// Copyright 2011 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.stats;
+
+/**
+ * A simple class to keep running statistics that require O(1) storage.
+ *
+ * @author William Farner
+ */
+public class Statistics implements StatisticsInterface {
+  private long populationSize;
+  private long sum;
+  private double accumulatedVariance;
+  private double runningMean;
+
+  private long minValue;
+  private long maxValue;
+
+  public Statistics() {
+    clear();
+  }
+
+  public void accumulate(long value) {
+    populationSize++;
+    sum += value;
+    double delta = value - runningMean;
+    runningMean += delta / populationSize;
+    accumulatedVariance += delta * (value - runningMean);
+
+    // Update max/min.
+    minValue = value < minValue ? value : minValue;
+    maxValue = value > maxValue ? value : maxValue;
+  }
+
+  public void clear() {
+    populationSize = 0;
+    sum = 0;
+    accumulatedVariance = 0;
+    runningMean = 0;
+    minValue = Long.MAX_VALUE;
+    maxValue = Long.MIN_VALUE;
+  }
+
+  public double variance() {
+    return accumulatedVariance / populationSize;
+  }
+
+  public double standardDeviation() {
+    return Math.sqrt(variance());
+  }
+
+  public double mean() {
+    return runningMean;
+  }
+
+  public long min() {
+    return minValue;
+  }
+
+  public long max() {
+    return maxValue;
+  }
+
+  public long range() {
+    return maxValue - minValue;
+  }
+
+  public long sum() {
+    return sum;
+  }
+
+  public long populationSize() {
+    return populationSize;
+  }
+
+  @Override
+  public String toString() {
+    return String.format("Mean: %f, Min: %d, Max: %d, Range: %d, Stddev: %f, Variance: %f, " +
+        "Population: %d, Sum: %d", mean(), min(), max(), range(), standardDeviation(),
+        variance(), populationSize(), sum());
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/stats/StatisticsInterface.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/stats/StatisticsInterface.java b/commons/src/main/java/com/twitter/common/stats/StatisticsInterface.java
new file mode 100644
index 0000000..9be533b
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/stats/StatisticsInterface.java
@@ -0,0 +1,73 @@
+// =================================================================================================
+// Copyright 2011 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.stats;
+
+/**
+ * Interface representing statistics of a set of (long) elements.
+ */
+public interface StatisticsInterface {
+  /**
+   * Add a value in the Statistics object.
+   * @param value value that you want to accumulate.
+   */
+  void accumulate(long value);
+
+  /**
+   * Clear the Statistics instance (equivalent to recreate a new one).
+   */
+  void clear();
+
+  /**
+   * Return the variance of the inserted elements.
+   */
+  double variance();
+
+  /**
+   * Return the standard deviation of the inserted elements.
+   */
+  double standardDeviation();
+
+  /**
+   * Return the mean of the inserted elements.
+   */
+  double mean();
+
+  /**
+   * Return the min of the inserted elements.
+   */
+  long min();
+
+  /**
+   * Return the max of the inserted elements.
+   */
+  long max();
+
+  /**
+   * Return the range of the inserted elements.
+   */
+  long range();
+
+  /**
+   * Return the sum of the inserted elements.
+   */
+  long sum();
+
+  /**
+   * Return the number of the inserted elements.
+   */
+  long populationSize();
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/stats/Stats.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/stats/Stats.java b/commons/src/main/java/com/twitter/common/stats/Stats.java
new file mode 100644
index 0000000..93bc82d
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/stats/Stats.java
@@ -0,0 +1,411 @@
+// =================================================================================================
+// Copyright 2011 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.stats;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Logger;
+import java.util.regex.Pattern;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Supplier;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.MapMaker;
+import com.google.common.util.concurrent.AtomicDouble;
+
+import com.twitter.common.base.MorePreconditions;
+
+/**
+ * Manages {@link Stat}s that should be exported for monitoring.
+ *
+ * Statistic names may only contain {@code [A-Za-z0-9_]},
+ * all other chars will be logged as a warning and replaced with underscore on export.
+ *
+ * @author John Sirois
+ */
+public class Stats {
+
+  private static final Logger LOG = Logger.getLogger(Stats.class.getName());
+  private static final Pattern NOT_NAME_CHAR = Pattern.compile("[^A-Za-z0-9_]");
+
+  private static final ConcurrentMap<String, Stat<?>> VAR_MAP = new MapMaker().makeMap();
+
+  // Store stats in the order they were registered, so that derived variables are
+  // sampled after their inputs.
+  private static final Collection<RecordingStat<? extends Number>> ORDERED_NUMERIC_STATS =
+      new ConcurrentLinkedQueue<RecordingStat<? extends Number>>();
+
+  private static final Cache<String, RecordingStat<? extends Number>> NUMERIC_STATS =
+      CacheBuilder.newBuilder().build();
+
+  public static String normalizeName(String name) {
+    return NOT_NAME_CHAR.matcher(name).replaceAll("_");
+  }
+
+  static String validateName(String name) {
+    String normalized = normalizeName(name);
+    if (!name.equals(normalized)) {
+      LOG.warning("Invalid stat name " + name + " exported as " + normalized);
+    }
+    return normalized;
+  }
+
+  /**
+   * A {@link StatsProvider} that exports gauge-style stats to the global {@link Stat}s repository
+   * for time series tracking.
+   */
+  public static final StatsProvider STATS_PROVIDER = new StatsProvider() {
+    private final StatsProvider untracked = new StatsProvider() {
+      @Override public AtomicLong makeCounter(String name) {
+        final AtomicLong longVar = new AtomicLong();
+        Stats.exportStatic(new StatImpl<Long>(name) {
+          @Override public Long read() {
+            return longVar.get();
+          }
+        });
+        return longVar;
+      }
+
+      @Override public <T extends Number> Stat<T> makeGauge(String name, final Supplier<T> gauge) {
+        return Stats.exportStatic(new StatImpl<T>(name) {
+          @Override public T read() {
+            return gauge.get();
+          }
+        });
+      }
+
+      @Override public StatsProvider untracked() {
+        return this;
+      }
+
+      @Override public RequestTimer makeRequestTimer(String name) {
+        // TODO(William Farner): Add support for this once a caller shows interest in using it.
+        throw new UnsupportedOperationException();
+      }
+    };
+
+    @Override public <T extends Number> Stat<T> makeGauge(String name, final Supplier<T> gauge) {
+      return Stats.export(new StatImpl<T>(name) {
+        @Override public T read() {
+          return gauge.get();
+        }
+      });
+    }
+
+    @Override public AtomicLong makeCounter(String name) {
+      return Stats.exportLong(name);
+    }
+
+    @Override public StatsProvider untracked() {
+      return untracked;
+    }
+
+    @Override public RequestTimer makeRequestTimer(String name) {
+      return new RequestStats(name);
+    }
+  };
+
+  /**
+   * A {@link StatRegistry} that provides stats registered with the global {@link Stat}s repository.
+   */
+  public static final StatRegistry STAT_REGISTRY = new StatRegistry() {
+    @Override public Iterable<RecordingStat<? extends Number>> getStats() {
+      return Stats.getNumericVariables();
+    }
+  };
+
+  private static class ExportStat implements Callable<RecordingStat<? extends Number>> {
+    private final AtomicBoolean called = new AtomicBoolean(false);
+
+    private final RecordingStat<? extends Number> stat;
+    private final String name;
+
+    private <T extends Number> ExportStat(String name, Stat<T> stat) {
+      this.name = name;
+      this.stat = (stat instanceof RecordingStat)
+          ? (RecordingStat<? extends Number>) stat
+          : new RecordingStatImpl<T>(stat);
+    }
+
+    @Override
+    public RecordingStat<? extends Number> call() {
+      try {
+        exportStaticInternal(name, stat);
+        ORDERED_NUMERIC_STATS.add(stat);
+        return stat;
+      } finally {
+        called.set(true);
+      }
+    }
+  }
+
+  /**
+   * Exports a stat for tracking.
+   * if the stat provided implements the internal {@link RecordingStat} interface, it will be
+   * registered for time series collection and returned.  If a {@link RecordingStat} with the same
+   * name as the provided stat has already been exported, the previously-exported stat will be
+   * returned and no additional registration will be performed.
+   *
+   * @param var The variable to export.
+   * @param <T> The value exported by the variable.
+   * @return A reference to the stat that was stored.  The stat returned may not be equal to the
+   *    stat provided.  If a variable was already returned with the same
+   */
+  public static <T extends Number> Stat<T> export(Stat<T> var) {
+    String validatedName = validateName(MorePreconditions.checkNotBlank(var.getName()));
+    ExportStat exportStat = new ExportStat(validatedName, var);
+    try {
+      @SuppressWarnings("unchecked")
+      Stat<T> exported = (Stat<T>) NUMERIC_STATS.get(validatedName, exportStat);
+      return exported;
+    } catch (ExecutionException e) {
+      throw new IllegalStateException(
+          "Unexpected error exporting stat " + validatedName, e.getCause());
+    } finally {
+      if (!exportStat.called.get()) {
+        LOG.warning("Re-using already registered variable for key " + validatedName);
+      }
+    }
+  }
+
+  /**
+   * Exports a string stat.
+   * String-based statistics will not be registered for time series collection.
+   *
+   * @param var Stat to export.
+   * @return A reference back to {@code var}, or the variable that was already registered under the
+   *    same name as {@code var}.
+   */
+  public static Stat<String> exportString(Stat<String> var) {
+    return exportStatic(var);
+  }
+
+  /**
+   * Adds a collection of stats for export.
+   *
+   * @param vars The variables to add.
+   */
+  public static void exportAll(Iterable<Stat<? extends Number>> vars) {
+    for (Stat<? extends Number> var : vars) {
+      export(var);
+    }
+  }
+
+  /**
+   * Exports an {@link AtomicInteger}, which will be included in time series tracking.
+   *
+   * @param name The name to export the stat with.
+   * @param intVar The variable to export.
+   * @return A reference to the {@link AtomicInteger} provided.
+   */
+  public static AtomicInteger export(final String name, final AtomicInteger intVar) {
+    export(new SampledStat<Integer>(name, 0) {
+      @Override public Integer doSample() { return intVar.get(); }
+    });
+
+    return intVar;
+  }
+
+  /**
+   * Creates and exports an {@link AtomicInteger}.
+   *
+   * @param name The name to export the stat with.
+   * @return A reference to the {@link AtomicInteger} created.
+   */
+  public static AtomicInteger exportInt(String name) {
+    return exportInt(name, 0);
+  }
+
+  /**
+   * Creates and exports an {@link AtomicInteger} with initial value.
+   *
+   * @param name The name to export the stat with.
+   * @param initialValue The initial stat value.
+   * @return A reference to the {@link AtomicInteger} created.
+   */
+  public static AtomicInteger exportInt(String name, int initialValue) {
+    return export(name, new AtomicInteger(initialValue));
+  }
+
+  /**
+   * Exports an {@link AtomicLong}, which will be included in time series tracking.
+   *
+   * @param name The name to export the stat with.
+   * @param longVar The variable to export.
+   * @return A reference to the {@link AtomicLong} provided.
+   */
+  public static AtomicLong export(String name, final AtomicLong longVar) {
+    export(new StatImpl<Long>(name) {
+      @Override public Long read() { return longVar.get(); }
+    });
+
+    return longVar;
+  }
+
+  /**
+   * Creates and exports an {@link AtomicLong}.
+   *
+   * @param name The name to export the stat with.
+   * @return A reference to the {@link AtomicLong} created.
+   */
+  public static AtomicLong exportLong(String name) {
+    return exportLong(name, 0L);
+  }
+
+  /**
+   * Creates and exports an {@link AtomicLong} with initial value.
+   *
+   * @param name The name to export the stat with.
+   * @param initialValue The initial stat value.
+   * @return A reference to the {@link AtomicLong} created.
+   */
+  public static AtomicLong exportLong(String name, long initialValue) {
+    return export(name, new AtomicLong(initialValue));
+  }
+
+  /**
+   * Exports an {@link AtomicDouble}, which will be included in time series tracking.
+   *
+   * @param name The name to export the stat with.
+   * @param doubleVar The variable to export.
+   * @return A reference to the {@link AtomicDouble} provided.
+   */
+  public static AtomicDouble export(String name, final AtomicDouble doubleVar) {
+    export(new StatImpl<Double>(name) {
+      @Override public Double read() { return doubleVar.doubleValue(); }
+    });
+
+    return doubleVar;
+  }
+
+  /**
+   * Creates and exports an {@link AtomicDouble}.
+   *
+   * @param name The name to export the stat with.
+   * @return A reference to the {@link AtomicDouble} created.
+   */
+  public static AtomicDouble exportDouble(String name) {
+    return exportDouble(name, 0.0);
+  }
+
+  /**
+   * Creates and exports an {@link AtomicDouble} with initial value.
+   *
+   * @param name The name to export the stat with.
+   * @param initialValue The initial stat value.
+   * @return A reference to the {@link AtomicDouble} created.
+   */
+  public static AtomicDouble exportDouble(String name, double initialValue) {
+    return export(name, new AtomicDouble(initialValue));
+  }
+
+  /**
+   * Exports a metric that tracks the size of a collection.
+   *
+   * @param name Name of the stat to export.
+   * @param collection Collection whose size should be tracked.
+   */
+  public static void exportSize(String name, final Collection<?> collection) {
+    export(new StatImpl<Integer>(name) {
+      @Override public Integer read() {
+        return collection.size();
+      }
+    });
+  }
+
+  /**
+   * Exports a metric that tracks the size of a map.
+   *
+   * @param name Name of the stat to export.
+   * @param map Map whose size should be tracked.
+   */
+  public static void exportSize(String name, final Map<?, ?> map) {
+    export(new StatImpl<Integer>(name) {
+      @Override public Integer read() {
+        return map.size();
+      }
+    });
+  }
+
+  /**
+   * Exports a metric that tracks the size of a cache.
+   *
+   * @param name Name of the stat to export.
+   * @param cache Cache whose size should be tracked.
+   */
+  public static void exportSize(String name, final Cache<?, ?> cache) {
+    export(new StatImpl<Long>(name) {
+      @Override public Long read() {
+        return cache.size();
+      }
+    });
+  }
+
+  /**
+   * Exports a 'static' statistic, which will not be registered for time series tracking.
+   *
+   * @param var Variable to statically export.
+   * @return A reference back to the provided {@link Stat}.
+   */
+  public static <T> Stat<T> exportStatic(Stat<T> var) {
+    String validatedName = validateName(MorePreconditions.checkNotBlank(var.getName()));
+    exportStaticInternal(validatedName, var);
+    return var;
+  }
+
+  private static void exportStaticInternal(String name, Stat<?> stat) {
+    if (VAR_MAP.put(name, stat) != null) {
+      LOG.warning("Warning - exported variable collision on " + name);
+    }
+  }
+
+  /**
+   * Fetches all registered stat.
+   *
+   * @return An iterable of all registered stats.
+   */
+  public static Iterable<Stat<?>> getVariables() {
+    return ImmutableList.copyOf(VAR_MAP.values());
+  }
+
+  static Iterable<RecordingStat<? extends Number>> getNumericVariables() {
+    return ImmutableList.copyOf(ORDERED_NUMERIC_STATS);
+  }
+
+  @VisibleForTesting
+  public static void flush() {
+    VAR_MAP.clear();
+    ORDERED_NUMERIC_STATS.clear();
+    NUMERIC_STATS.invalidateAll();
+  }
+
+  public static <T> Stat<T> getVariable(String name) {
+    MorePreconditions.checkNotBlank(name);
+    @SuppressWarnings("unchecked")
+    Stat<T> stat = (Stat<T>) VAR_MAP.get(name);
+    return stat;
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/stats/StatsProvider.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/stats/StatsProvider.java b/commons/src/main/java/com/twitter/common/stats/StatsProvider.java
new file mode 100644
index 0000000..347d0d5
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/stats/StatsProvider.java
@@ -0,0 +1,91 @@
+// =================================================================================================
+// Copyright 2011 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.stats;
+
+import com.google.common.base.Supplier;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * A minimal interface to a Stats repository.
+ *
+ * @author John Sirois
+ */
+public interface StatsProvider {
+
+  /**
+   * Creates and exports a counter for tracking.
+   *
+   * @param name The name to export the stat with.
+   * @return A reference to the counter that will be tracked for incrementing.
+   */
+  AtomicLong makeCounter(String name);
+
+  /**
+   * Exports a read-only value for tracking.
+   *
+   * @param name The name of the variable to export.
+   * @param gauge The supplier of the instantaneous values to export.
+   * @param <T> The type of number exported by the variable.
+   * @return A reference to the stat that was stored.
+   */
+  <T extends Number> Stat<T> makeGauge(String name, Supplier<T> gauge);
+
+  /**
+   * Gets a stats provider that does not track stats in an internal time series repository.
+   * The stored variables will only be available as instantaneous values.
+   *
+   * @return A stats provider that creates untracked stats.
+   */
+  StatsProvider untracked();
+
+  /**
+   * A stat for tracking service requests.
+   */
+  interface RequestTimer {
+
+    /**
+     * Accumulates a request and its latency.
+     *
+     * @param latencyMicros The elapsed time required to complete the request.
+     */
+    void requestComplete(long latencyMicros);
+
+    /**
+     * Accumulates the error counter and the request counter.
+     */
+    void incErrors();
+
+    /**
+     * Accumulates the reconnect counter.
+     */
+    void incReconnects();
+
+    /**
+     * Accumulates the timeout counter.
+     */
+    void incTimeouts();
+  }
+
+  /**
+   * Creates and exports a sets of stats that allows for typical rROC request tracking.
+   *
+   * @param name The name to export the stat with.
+   * @return A reference to the request timer that can be used to track RPCs.
+   */
+  RequestTimer makeRequestTimer(String name);
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/stats/TimeSeries.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/stats/TimeSeries.java b/commons/src/main/java/com/twitter/common/stats/TimeSeries.java
new file mode 100644
index 0000000..57573fa
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/stats/TimeSeries.java
@@ -0,0 +1,41 @@
+// =================================================================================================
+// Copyright 2011 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.stats;
+
+import java.util.Calendar;
+
+/**
+ * A time series of values.
+ *
+ * @author William Farner
+ */
+public interface TimeSeries {
+
+  /**
+   * A name describing this time series.
+   *
+   * @return The name of this time series data.
+   */
+  public String getName();
+
+  /**
+   * A series of numbers representing regular samples of a variable.
+   *
+   * @return The time series of sample values.
+   */
+  public Iterable<Number> getSamples();
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/stats/TimeSeriesRepository.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/stats/TimeSeriesRepository.java b/commons/src/main/java/com/twitter/common/stats/TimeSeriesRepository.java
new file mode 100644
index 0000000..d439219
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/stats/TimeSeriesRepository.java
@@ -0,0 +1,60 @@
+// =================================================================================================
+// Copyright 2011 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.stats;
+
+import java.util.Set;
+
+import com.twitter.common.application.ShutdownRegistry;
+
+/**
+ * A repository for time series data.
+ *
+ * @author William Farner
+ */
+public interface TimeSeriesRepository {
+
+  /**
+   * Starts the time series sampler.
+   *
+   * @param shutdownRegistry An action registry that the repository can use to register a shutdown
+   *    for the sampler.
+   */
+  public void start(ShutdownRegistry shutdownRegistry);
+
+  /**
+   * Fetches the names of all available time series.
+   *
+   * @return Available time series, which can then be obtained by calling {@link #get(String)}.
+   */
+  public Set<String> getAvailableSeries();
+
+  /**
+   * Fetches a time series by name.
+   *
+   * @param name The name of the time series to fetch.
+   * @return The time series registered with the given name, or {@code null} if no such time series
+   *     has been registered.
+   */
+  public TimeSeries get(String name);
+
+  /**
+   * Gets an ordered iterable of the timestamps that all timeseries were sampled at.
+   *
+   * @return All current timestamps.
+   */
+  public Iterable<Number> getTimestamps();
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/stats/TimeSeriesRepositoryImpl.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/stats/TimeSeriesRepositoryImpl.java b/commons/src/main/java/com/twitter/common/stats/TimeSeriesRepositoryImpl.java
new file mode 100644
index 0000000..bfd0fdb
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/stats/TimeSeriesRepositoryImpl.java
@@ -0,0 +1,200 @@
+// =================================================================================================
+// Copyright 2011 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.stats;
+
+import java.util.Set;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+
+import com.twitter.common.application.ShutdownRegistry;
+import com.twitter.common.base.Command;
+import com.twitter.common.collections.BoundedQueue;
+import com.twitter.common.quantity.Amount;
+import com.twitter.common.quantity.Time;
+import com.twitter.common.util.Clock;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A simple in-memory repository for exported variables.
+ *
+ * @author John Sirois
+ */
+public class TimeSeriesRepositoryImpl implements TimeSeriesRepository {
+
+  private static final Logger LOG = Logger.getLogger(TimeSeriesRepositoryImpl.class.getName());
+
+  /**
+   * {@literal @Named} binding key for the sampling period.
+   */
+  public static final String SAMPLE_PERIOD =
+      "com.twitter.common.stats.TimeSeriesRepositoryImpl.SAMPLE_PERIOD";
+
+  /**
+   * {@literal @Named} binding key for the maximum number of retained samples.
+   */
+  public static final String SAMPLE_RETENTION_PERIOD =
+      "com.twitter.common.stats.TimeSeriesRepositoryImpl.SAMPLE_RETENTION_PERIOD";
+
+  private final SlidingStats scrapeDuration = new SlidingStats("variable_scrape", "micros");
+
+  // We store TimeSeriesImpl, which allows us to add samples.
+  private final LoadingCache<String, TimeSeriesImpl> timeSeries;
+  private final BoundedQueue<Number> timestamps;
+
+  private final StatRegistry statRegistry;
+  private final Amount<Long, Time> samplePeriod;
+  private final int retainedSampleLimit;
+
+  @Inject
+  public TimeSeriesRepositoryImpl(
+      StatRegistry statRegistry,
+      @Named(SAMPLE_PERIOD) Amount<Long, Time> samplePeriod,
+      @Named(SAMPLE_RETENTION_PERIOD) final Amount<Long, Time> retentionPeriod) {
+    this.statRegistry = checkNotNull(statRegistry);
+    this.samplePeriod = checkNotNull(samplePeriod);
+    Preconditions.checkArgument(samplePeriod.getValue() > 0, "Sample period must be positive.");
+    checkNotNull(retentionPeriod);
+    Preconditions.checkArgument(retentionPeriod.getValue() > 0,
+        "Sample retention period must be positive.");
+
+    retainedSampleLimit = (int) (retentionPeriod.as(Time.SECONDS) / samplePeriod.as(Time.SECONDS));
+    Preconditions.checkArgument(retainedSampleLimit > 0,
+        "Sample retention period must be greater than sample period.");
+
+    timeSeries = CacheBuilder.newBuilder().build(
+        new CacheLoader<String, TimeSeriesImpl>() {
+          @Override public TimeSeriesImpl load(final String name) {
+            TimeSeriesImpl timeSeries = new TimeSeriesImpl(name);
+
+            // Backfill so we have data for pre-accumulated timestamps.
+            int numTimestamps = timestamps.size();
+            if (numTimestamps != 0) {
+              for (int i = 1; i < numTimestamps; i++) {
+                timeSeries.addSample(0L);
+              }
+            }
+
+            return timeSeries;
+          }
+        });
+
+    timestamps = new BoundedQueue<Number>(retainedSampleLimit);
+  }
+
+  /**
+   * Starts the variable sampler, which will fetch variables {@link Stats} on the given period.
+   *
+   */
+  @Override
+  public void start(ShutdownRegistry shutdownRegistry) {
+    checkNotNull(shutdownRegistry);
+    checkNotNull(samplePeriod);
+    Preconditions.checkArgument(samplePeriod.getValue() > 0);
+
+    final ScheduledExecutorService executor = new ScheduledThreadPoolExecutor(1 /* One thread. */,
+        new ThreadFactoryBuilder().setNameFormat("VariableSampler-%d").setDaemon(true).build());
+
+    final AtomicBoolean shouldSample = new AtomicBoolean(true);
+    final Runnable sampler = new Runnable() {
+      @Override public void run() {
+        if (shouldSample.get()) {
+          try {
+            runSampler(Clock.SYSTEM_CLOCK);
+          } catch (Exception e) {
+            LOG.log(Level.SEVERE, "ignoring runSampler failure", e);
+          }
+        }
+      }
+    };
+
+    executor.scheduleAtFixedRate(sampler, samplePeriod.getValue(), samplePeriod.getValue(),
+        samplePeriod.getUnit().getTimeUnit());
+    shutdownRegistry.addAction(new Command() {
+      @Override
+      public void execute() throws RuntimeException {
+        shouldSample.set(false);
+        executor.shutdown();
+        LOG.info("Variable sampler shut down");
+      }
+    });
+  }
+
+  @VisibleForTesting
+  synchronized void runSampler(Clock clock) {
+    timestamps.add(clock.nowMillis());
+
+    long startNanos = clock.nowNanos();
+    for (RecordingStat<? extends Number> var : statRegistry.getStats()) {
+      timeSeries.getUnchecked(var.getName()).addSample(var.sample());
+    }
+    scrapeDuration.accumulate(
+        Amount.of(clock.nowNanos() - startNanos, Time.NANOSECONDS).as(Time.MICROSECONDS));
+  }
+
+  @Override
+  public synchronized Set<String> getAvailableSeries() {
+    return ImmutableSet.copyOf(timeSeries.asMap().keySet());
+  }
+
+  @Override
+  public synchronized TimeSeries get(String name) {
+    if (!timeSeries.asMap().containsKey(name)) return null;
+    return timeSeries.getUnchecked(name);
+  }
+
+  @Override
+  public synchronized Iterable<Number> getTimestamps() {
+    return Iterables.unmodifiableIterable(timestamps);
+  }
+
+  private class TimeSeriesImpl implements TimeSeries {
+    private final String name;
+    private final BoundedQueue<Number> samples;
+
+    TimeSeriesImpl(String name) {
+      this.name = name;
+      samples = new BoundedQueue<Number>(retainedSampleLimit);
+    }
+
+    @Override public String getName() {
+      return name;
+    }
+
+    void addSample(Number value) {
+      samples.add(value);
+    }
+
+    @Override public Iterable<Number> getSamples() {
+      return Iterables.unmodifiableIterable(samples);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/stats/Windowed.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/stats/Windowed.java b/commons/src/main/java/com/twitter/common/stats/Windowed.java
new file mode 100644
index 0000000..5531e99
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/stats/Windowed.java
@@ -0,0 +1,139 @@
+// =================================================================================================
+// Copyright 2013 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.stats;
+
+import java.lang.reflect.Array;
+
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Supplier;
+
+import com.twitter.common.quantity.Amount;
+import com.twitter.common.quantity.Time;
+import com.twitter.common.util.Clock;
+
+/**
+ * Windowed is an abstraction that let you span a class across a sliding window.
+ * It creates a ring buffer of T and reuse the buffer after clearing it or use a new one (via
+ * the {@code clearer} function).
+ *
+ * <pre>
+ *          tenured instances
+ *  ++++++++++++++++++++++++++++++++++
+ * [----A-----][-----B----][-----C----][-----D----]
+ *                                      ++++++++++
+ *                                    current instance
+ * </pre>
+ *
+ * The schema above represents the valid instances over time
+ * (A,B,C) are the tenured ones
+ * D is the current instance
+ */
+public abstract class Windowed<T> {
+  private Class<T> clazz;
+  protected final T[] buffers;
+  private final long sliceDuration;
+  private final Clock clock;
+  private long index = -1L;
+  private Function<T, T> clearer;
+
+  /**
+   * @param clazz the type of the underlying element T
+   * @param window the length of the window
+   * @param slices the number of slices (the window will be divided into {@code slices} slices)
+   * @param sliceProvider the supplier of element
+   * @param clearer the function that clear (or re-create) an element
+   * @param clock  the clock used for to select the appropriate histogram
+   */
+  public Windowed(Class<T> clazz, Amount<Long, Time> window, int slices,
+      Supplier<T> sliceProvider, Function<T, T> clearer, Clock clock) {
+    Preconditions.checkNotNull(window);
+    // Ensure that we have at least 1ms per slice
+    Preconditions.checkArgument(window.as(Time.MILLISECONDS) > (slices + 1));
+    Preconditions.checkArgument(window.as(Time.MILLISECONDS) > (slices + 1));
+    Preconditions.checkArgument(0 < slices);
+    Preconditions.checkNotNull(sliceProvider);
+    Preconditions.checkNotNull(clock);
+
+    this.clazz = clazz;
+    this.sliceDuration = window.as(Time.MILLISECONDS) / slices;
+    @SuppressWarnings("unchecked") // safe because we have the clazz proof of type H
+    T[] bufs = (T[]) Array.newInstance(clazz, slices + 1);
+    for (int i = 0; i < bufs.length; i++) {
+      bufs[i] = sliceProvider.get();
+    }
+    this.buffers = bufs;
+    this.clearer = clearer;
+    this.clock = clock;
+  }
+
+  /**
+   * Return the index of the latest Histogram.
+   * You have to modulo it with buffer.length before accessing the array with this number.
+   */
+  protected int getCurrentIndex() {
+    long now = clock.nowMillis();
+    return (int) (now / sliceDuration);
+  }
+
+  /**
+   * Check for expired elements and return the current one.
+   */
+  protected T getCurrent() {
+    sync(getCurrentIndex());
+    return buffers[(int) (index % buffers.length)];
+  }
+
+  /**
+   * Check for expired elements and return all the tenured (old) ones.
+   */
+  protected T[] getTenured() {
+    long currentIndex = getCurrentIndex();
+    sync(currentIndex);
+    @SuppressWarnings("unchecked") // safe because we have the clazz proof of type T
+    T[] tmp = (T[]) Array.newInstance(clazz, buffers.length - 1);
+    for (int i = 0; i < tmp.length; i++) {
+      int idx = (int) ((currentIndex + 1 + i) % buffers.length);
+      tmp[i] = buffers[idx];
+    }
+    return tmp;
+  }
+
+  /**
+   * Clear all the elements.
+   */
+  public void clear() {
+    for (int i = 0; i <= buffers.length; i++) {
+      buffers[i] = clearer.apply(buffers[i]);
+    }
+  }
+
+  /**
+   * Synchronize elements with a point in time.
+   * i.e. Check for expired ones and clear them, and update the index variable.
+   */
+  protected void sync(long currentIndex) {
+    if (index < currentIndex) {
+      long from = Math.max(index + 1, currentIndex - buffers.length + 1);
+      for (long i = from; i <= currentIndex; i++) {
+        int idx = (int) (i % buffers.length);
+        buffers[idx] = clearer.apply(buffers[idx]);
+      }
+      index = currentIndex;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/stats/WindowedApproxHistogram.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/stats/WindowedApproxHistogram.java b/commons/src/main/java/com/twitter/common/stats/WindowedApproxHistogram.java
new file mode 100644
index 0000000..f010b32
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/stats/WindowedApproxHistogram.java
@@ -0,0 +1,156 @@
+// =================================================================================================
+// Copyright 2013 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.stats;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Supplier;
+
+import com.twitter.common.quantity.Amount;
+import com.twitter.common.quantity.Data;
+import com.twitter.common.quantity.Time;
+import com.twitter.common.util.Clock;
+
+/**
+ * WindowedApproxHistogram is an implementation of WindowedHistogram with an
+ * ApproximateHistogram as the underlying storing histogram.
+ */
+public class WindowedApproxHistogram extends WindowedHistogram<ApproximateHistogram> {
+  @VisibleForTesting public static final int DEFAULT_SLICES = 3;
+  @VisibleForTesting public static final Amount<Long, Time> DEFAULT_WINDOW =
+      Amount.of(1L, Time.MINUTES);
+  @VisibleForTesting public static final Amount<Long, Data> DEFAULT_MAX_MEMORY = Amount.of(
+      (DEFAULT_SLICES + 1) * ApproximateHistogram.DEFAULT_MAX_MEMORY.as(Data.BYTES), Data.BYTES);
+
+  /**
+   * Create a {@code WindowedApproxHistogram } with a window duration of {@code window} and
+   * decomposed in {@code slices} Histograms. Those Histograms will individually take less than
+   * {@code maxMemory / (slices + 1)}. The clock will be used to find the correct index in the
+   * ring buffer.
+   *
+   * @param window duration of the window
+   * @param slices number of slices in the window
+   * @param maxMemory maximum memory used by the whole histogram
+   */
+  public WindowedApproxHistogram(Amount<Long, Time> window, final int slices,
+      final Amount<Long, Data> maxMemory, Clock clock) {
+    super(ApproximateHistogram.class, window, slices,
+        new Supplier<ApproximateHistogram>() {
+          private Amount<Long, Data> perHistogramMemory = Amount.of(
+              maxMemory.as(Data.BYTES) / (slices + 1), Data.BYTES);
+          @Override
+          public ApproximateHistogram get() {
+            return new ApproximateHistogram(perHistogramMemory);
+          }
+        },
+        new Function<ApproximateHistogram[], Histogram>() {
+          @Override
+          public Histogram apply(ApproximateHistogram[] histograms) {
+            return ApproximateHistogram.merge(histograms);
+          }
+        }, clock);
+  }
+
+  /**
+   * Create a {@code WindowedApproxHistogram } with a window duration of {@code window} and
+   * decomposed in {@code slices} Histograms. Those Histograms will individually have a
+   * precision of {@code precision / (slices + 1)}. The ticker will be used to measure elapsed
+   * time in the WindowedHistogram.
+   *
+   * @param window duration of the window
+   * @param slices number of slices in the window
+   * @param precision precision of the whole histogram
+   */
+  public WindowedApproxHistogram(Amount<Long, Time> window, final int slices,
+      final Precision precision, Clock clock) {
+    super(ApproximateHistogram.class, window, slices,
+        new Supplier<ApproximateHistogram>() {
+          private Precision perHistogramPrecision = new Precision(
+              precision.getEpsilon(), precision.getN() / (slices + 1));
+          @Override
+          public ApproximateHistogram get() {
+            return new ApproximateHistogram(perHistogramPrecision);
+          }
+        },
+        new Function<ApproximateHistogram[], Histogram>() {
+          @Override
+          public Histogram apply(ApproximateHistogram[] histograms) {
+            return ApproximateHistogram.merge(histograms);
+          }
+        }, clock);
+  }
+
+  /**
+   * Equivalent to calling
+   * {@link #WindowedApproxHistogram(Amount, int, Amount, Clock)}
+   * with the System clock.
+   */
+  public WindowedApproxHistogram(Amount<Long, Time> window, int slices,
+      Amount<Long, Data> maxMemory) {
+    this(window, slices, maxMemory, Clock.SYSTEM_CLOCK);
+  }
+
+  /**
+   * Equivalent to calling
+   * {@link #WindowedApproxHistogram(Amount, int, Amount)}
+   * with default window and slices.
+   */
+  public WindowedApproxHistogram(Amount<Long, Data> maxMemory) {
+    this(DEFAULT_WINDOW, DEFAULT_SLICES, maxMemory);
+  }
+
+  /**
+   * Equivalent to calling
+   * {@link #WindowedApproxHistogram(Amount, int, Precision, Clock)}
+   * with the System clock.
+   */
+  public WindowedApproxHistogram(Amount<Long, Time> window, int slices, Precision precision) {
+    this(window, slices, precision, Clock.SYSTEM_CLOCK);
+  }
+
+  /**
+   * Equivalent to calling
+   * {@link #WindowedApproxHistogram(Amount, int, Precision)}
+   * with default window and slices.
+   */
+  public WindowedApproxHistogram(Precision precision) {
+    this(DEFAULT_WINDOW, DEFAULT_SLICES, precision);
+  }
+
+  /**
+   * Equivalent to calling
+   * {@link #WindowedApproxHistogram(Amount, int, Amount, Clock)}
+   * with the default maxMemory parameter and System clock.
+   */
+  public WindowedApproxHistogram(Amount<Long, Time> window, int slices) {
+    this(window, slices, DEFAULT_MAX_MEMORY, Clock.SYSTEM_CLOCK);
+  }
+
+  /**
+   * WindowedApproxHistogram constructor with default values.
+   */
+  public WindowedApproxHistogram() {
+    this(DEFAULT_WINDOW, DEFAULT_SLICES, DEFAULT_MAX_MEMORY, Clock.SYSTEM_CLOCK);
+  }
+
+  /**
+   * WindowedApproxHistogram constructor with custom Clock (for testing purposes only).
+   */
+  @VisibleForTesting public WindowedApproxHistogram(Clock clock) {
+    this(DEFAULT_WINDOW, DEFAULT_SLICES, DEFAULT_MAX_MEMORY, clock);
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/stats/WindowedHistogram.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/stats/WindowedHistogram.java b/commons/src/main/java/com/twitter/common/stats/WindowedHistogram.java
new file mode 100644
index 0000000..bdc7347
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/stats/WindowedHistogram.java
@@ -0,0 +1,113 @@
+// =================================================================================================
+// Copyright 2013 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.stats;
+
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Supplier;
+
+import com.twitter.common.quantity.Amount;
+import com.twitter.common.quantity.Time;
+import com.twitter.common.util.Clock;
+
+/**
+ * Histogram windowed over time.
+ * <p>
+ * This histogram is composed of a series of ({@code slices} + 1) histograms representing a window
+ * of {@code range} duration. We only update the latest one, and we query the oldest ones (i.e. all
+ * histograms except the head).
+ * </p>
+ * <pre>
+ *      range
+ * <------------->
+ * [AAA][BBB][CCC][DDD]   here slices = 3
+ * --------------------->
+ *                t1  t2
+ *
+ *  For t in [t1,t2) we:
+ *  insert elements in DDD
+ *  query quantile over [AAA][BBB][CCC]
+ * </pre>
+ * <p>
+ * When {@code t} is in {@code [t1, t2)} we insert value into the latest histogram (DDD here),
+ * when we query the histogram, we 'merge' all other histograms (all except the latest) and query
+ * it. when {@code t > t2} the oldest histogram become the newest (like in a ring buffer) and
+ * so on ...
+ * </p>
+ * <p>
+ * Note: We use MergedHistogram class to represent a merged histogram without actually
+ * merging the underlying histograms.
+ * </p>
+ */
+public class WindowedHistogram<H extends Histogram> extends Windowed<H> implements Histogram {
+
+  private long mergedHistIndex = -1L;
+  private Function<H[], Histogram> merger;
+  private Histogram mergedHistogram = null;
+
+  /**
+   * Create a WindowedHistogram of {@code slices + 1} elements over a time {@code window}.
+   * This code is independent from the implementation of Histogram, you just need to provide
+   * a {@code Supplier<H>} to create the histograms and a {@code Function<H[], Histogram>} to
+   * merge them.
+   *
+   * @param clazz the type of the underlying Histogram H
+   * @param window the length of the window
+   * @param slices the number of slices (the window will be divided into {@code slices} slices)
+   * @param sliceProvider the supplier of histogram
+   * @param merger the function that merge an array of histogram H[] into a single Histogram
+   * @param clock the clock used for to select the appropriate histogram
+   */
+  public WindowedHistogram(Class<H> clazz, Amount<Long, Time> window, int slices,
+        Supplier<H> sliceProvider, Function<H[], Histogram> merger, Clock clock) {
+    super(clazz, window, slices, sliceProvider, new Function<H, H>() {
+      @Override
+      public H apply(H h) { h.clear(); return h; }
+    }, clock);
+    Preconditions.checkNotNull(merger);
+
+    this.merger = merger;
+  }
+
+  @Override
+  public synchronized void add(long x) {
+    getCurrent().add(x);
+  }
+
+  @Override
+  public synchronized void clear() {
+    for (Histogram h: buffers) {
+      h.clear();
+    }
+  }
+
+  @Override
+  public synchronized long getQuantile(double quantile) {
+    long currentIndex = getCurrentIndex();
+    if (mergedHistIndex < currentIndex) {
+      H[] tmp = getTenured();
+      mergedHistogram = merger.apply(tmp);
+      mergedHistIndex = currentIndex;
+    }
+    return mergedHistogram.getQuantile(quantile);
+  }
+
+  @Override
+  public synchronized long[] getQuantiles(double[] quantiles) {
+    return Histograms.extractQuantiles(this, quantiles);
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/stats/WindowedStatistics.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/stats/WindowedStatistics.java b/commons/src/main/java/com/twitter/common/stats/WindowedStatistics.java
new file mode 100644
index 0000000..96480ea
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/stats/WindowedStatistics.java
@@ -0,0 +1,160 @@
+package com.twitter.common.stats;
+
+import com.google.common.base.Supplier;
+import com.google.common.base.Function;
+
+import com.twitter.common.quantity.Amount;
+import com.twitter.common.quantity.Time;
+import com.twitter.common.util.Clock;
+
+/**
+ * Keep track of statistics over a set of value in a sliding window.
+ * WARNING: The computation of the statistics needs to be explicitly requested with
+ * {@code refresh()} before reading any statistics.
+ *
+ * @see Windowed class for more details about how the window is parametrized.
+ */
+public class WindowedStatistics extends Windowed<Statistics> implements StatisticsInterface {
+  private int lastIndex = -1;
+  private double variance = 0.0;
+  private double mean = 0.0;
+  private long sum = 0L;
+  private long min = Long.MAX_VALUE;
+  private long max = Long.MIN_VALUE;
+  private long populationSize = 0L;
+
+  public WindowedStatistics(Amount<Long, Time> window, int slices, Clock clock) {
+    super(Statistics.class, window, slices,
+        new Supplier<Statistics>() {
+          @Override public Statistics get() { return new Statistics(); }
+        },
+        new Function<Statistics, Statistics>() {
+          @Override public Statistics apply(Statistics s) { s.clear(); return s; }
+        },
+        clock);
+  }
+
+  /**
+   * Construct a Statistics sliced over time in {@code slices + 1} windows.
+   * The {@code window} parameter represents the total window, that will be sliced into
+   * {@code slices + 1} parts.
+   *
+   * Ex: WindowedStatistics(Amount.of(1L, Time.MINUTES), 3) will be sliced like this:
+   * <pre>
+   *        20s         20s         20s         20s
+   *   [----A-----][-----B----][-----C----][-----D----]
+   * </pre>
+   * The current window is 'D' (the one you insert elements into) and the tenured windows
+   * are 'A', 'B', 'C' (the ones you read elements from).
+   */
+  public WindowedStatistics(Amount<Long, Time> window, int slices) {
+    this(window, slices, Clock.SYSTEM_CLOCK);
+  }
+
+  /**
+   * Equivalent to calling {@link #WindowedStatistics(Amount, int)} with a 1 minute window
+   * and 3 slices.
+   */
+  public WindowedStatistics() {
+    this(Amount.of(1L, Time.MINUTES), 3, Clock.SYSTEM_CLOCK);
+  }
+
+  public void accumulate(long value) {
+    getCurrent().accumulate(value);
+  }
+
+  /**
+   * Compute all the statistics in one pass.
+   */
+  public void refresh() {
+    int currentIndex = getCurrentIndex();
+    if (lastIndex != currentIndex) {
+      lastIndex = currentIndex;
+      double x = 0.0;
+      variance = 0.0;
+      mean = 0.0;
+      sum = 0L;
+      populationSize = 0L;
+      min = Long.MAX_VALUE;
+      max = Long.MIN_VALUE;
+      for (Statistics s : getTenured()) {
+        if (s.populationSize() == 0) {
+          continue;
+        }
+        x += s.populationSize() * (s.variance() + s.mean() * s.mean());
+        sum += s.sum();
+        populationSize += s.populationSize();
+        min = Math.min(min, s.min());
+        max = Math.max(max, s.max());
+      }
+      if (populationSize != 0) {
+        mean = ((double) sum) / populationSize;
+        variance = x / populationSize - mean * mean;
+      }
+    }
+  }
+
+  /**
+   * WARNING: You need to call refresh() to recompute the variance
+   * @return the variance of the aggregated windows
+   */
+  public double variance() {
+    return variance;
+  }
+
+  /**
+   * WARNING: You need to call refresh() to recompute the variance
+   * @return the standard deviation of the aggregated windows
+   */
+  public double standardDeviation() {
+    return Math.sqrt(variance());
+  }
+
+  /**
+   * WARNING: You need to call refresh() to recompute the variance
+   * @return the mean of the aggregated windows
+   */
+  public double mean() {
+    return mean;
+  }
+
+  /**
+   * WARNING: You need to call refresh() to recompute the variance
+   * @return the sum of the aggregated windows
+   */
+  public long sum() {
+    return sum;
+  }
+
+  /**
+   * WARNING: You need to call refresh() to recompute the variance
+   * @return the min of the aggregated windows
+   */
+  public long min() {
+    return min;
+  }
+
+  /**
+   * WARNING: You need to call refresh() to recompute the variance
+   * @return the max of the aggregated windows
+   */
+  public long max() {
+    return max;
+  }
+
+  /**
+   * WARNING: You need to call refresh() to recompute the variance
+   * @return the range of the aggregated windows
+   */
+  public long range() {
+    return max - min;
+  }
+
+  /**
+   * WARNING: You need to call refresh() to recompute the variance
+   * @return the population size of the aggregated windows
+   */
+  public long populationSize() {
+    return populationSize;
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/stats/testing/RealHistogram.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/stats/testing/RealHistogram.java b/commons/src/main/java/com/twitter/common/stats/testing/RealHistogram.java
new file mode 100644
index 0000000..ff1e84a
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/stats/testing/RealHistogram.java
@@ -0,0 +1,45 @@
+// =================================================================================================
+// Copyright 2013 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.stats.testing;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import com.twitter.common.stats.Histogram;
+import com.twitter.common.stats.Histograms;
+
+public class RealHistogram implements Histogram {
+  private final List<Long> buffer = new ArrayList<Long>();
+
+  @Override public void add(long x) {
+    buffer.add(x);
+  }
+
+  @Override public void clear() {
+    buffer.clear();
+  }
+
+  @Override public long getQuantile(double quantile) {
+    Collections.sort(buffer);
+    return buffer.get((int) (quantile * buffer.size()));
+  }
+
+  @Override public long[] getQuantiles(double[] quantiles) {
+    return Histograms.extractQuantiles(this, quantiles);
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/testing/TearDownRegistry.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/testing/TearDownRegistry.java b/commons/src/main/java/com/twitter/common/testing/TearDownRegistry.java
new file mode 100644
index 0000000..6061dda
--- /dev/null
+++ b/commons/src/main/java/com/twitter/common/testing/TearDownRegistry.java
@@ -0,0 +1,54 @@
+// =================================================================================================
+// Copyright 2011 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.testing;
+
+import com.google.common.base.Preconditions;
+import com.google.common.testing.TearDown;
+import com.google.common.testing.TearDownAccepter;
+
+import com.twitter.common.application.ShutdownRegistry;
+import com.twitter.common.base.ExceptionalCommand;
+
+/**
+ * An action registry suitable for use as a shutdownRegistry in tests that extend
+ * {@link com.google.common.testing.junit4.TearDownTestCase}.
+ *
+ * @author John Sirois
+ */
+public class TearDownRegistry implements ShutdownRegistry {
+  private final TearDownAccepter tearDownAccepter;
+
+  /**
+   * Creates a new tear down registry that delegates execution of shutdown actions to a
+   * {@code tearDownAccepter}.
+   *
+   * @param tearDownAccepter A tear down accepter that will be used to register shutdown actions
+   *     with.
+   */
+  public TearDownRegistry(TearDownAccepter tearDownAccepter) {
+    this.tearDownAccepter = Preconditions.checkNotNull(tearDownAccepter);
+  }
+
+  @Override
+  public <E extends Exception, T extends ExceptionalCommand<E>> void addAction(final T action) {
+    tearDownAccepter.addTearDown(new TearDown() {
+      @Override public void tearDown() throws Exception {
+        action.execute();
+      }
+    });
+  }
+}


Mime
View raw message