All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.smallmind.instrument.HistogramImpl Maven / Gradle / Ivy

There is a newer version: 3.3.4
Show newest version
/*
 * Copyright (c) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 David Berkman
 * 
 * This file is part of the SmallMind Code Project.
 * 
 * The SmallMind Code Project is free software, you can redistribute
 * it and/or modify it under either, at your discretion...
 * 
 * 1) The terms of GNU Affero General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or (at
 * your option) any later version.
 * 
 * ...or...
 * 
 * 2) The terms of the Apache License, Version 2.0.
 * 
 * The SmallMind Code Project is distributed in the hope that it will
 * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License or Apache License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * and the Apache License along with the SmallMind Code Project. If not, see
 *  or .
 * 
 * Additional permission under the GNU Affero GPL version 3 section 7
 * ------------------------------------------------------------------
 * If you modify this Program, or any covered work, by linking or
 * combining it with other code, such other code is not for that reason
 * alone subject to any of the requirements of the GNU Affero GPL
 * version 3.
 */
package org.smallmind.instrument;

import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import org.smallmind.instrument.context.MetricFact;
import org.smallmind.instrument.context.MetricItem;
import org.smallmind.instrument.context.MetricSnapshot;

public class HistogramImpl extends MetricImpl implements Histogram {

  private final AtomicLong min = new AtomicLong(Long.MAX_VALUE);
  // These are for the Welford algorithm for calculating running variance without floating-point doom.
  private final AtomicReference variance = new AtomicReference(new double[] {-1, 0});
  private final AtomicLong count = new AtomicLong(0);
  private final ArrayCache arrayCache = new ArrayCache();
  private final Sample sample;
  private final AtomicLong max = new AtomicLong(Long.MIN_VALUE);
  private final AtomicLong sum = new AtomicLong(0);

  public HistogramImpl (Samples samples) {

    sample = samples.createSample();
  }

  @Override
  public Class getMetricClass () {

    return Histogram.class;
  }

  @Override
  public void clear () {

    MetricSnapshot metricSnapshot;

    sample.clear();
    count.set(0);
    sum.set(0);
    min.set(Long.MAX_VALUE);
    max.set(Long.MIN_VALUE);
    variance.set(new double[] {-1, 0});

    if ((metricSnapshot = getMetricSnapshot()) != null) {
      if (metricSnapshot.willTrace(MetricFact.COUNT)) {
        metricSnapshot.addItem(new MetricItem("count", 0L));
      }
      if (metricSnapshot.willTrace(MetricFact.SUM)) {
        metricSnapshot.addItem(new MetricItem("sum", 0L));
      }
      if (metricSnapshot.willTrace(MetricFact.MIN)) {
        metricSnapshot.addItem(new MetricItem("min", "n/a"));
      }
      if (metricSnapshot.willTrace(MetricFact.MAX)) {
        metricSnapshot.addItem(new MetricItem("max", "n/a"));
      }
      if (metricSnapshot.willTrace(MetricFact.AVG)) {
        metricSnapshot.addItem(new MetricItem("avg", 0.0));
      }
      if (metricSnapshot.willTrace(MetricFact.STD_DEV)) {
        metricSnapshot.addItem(new MetricItem("std dev", 0.0));
      }
      if (metricSnapshot.willTrace(MetricFact.MEDIAN)) {
        metricSnapshot.addItem(new MetricItem("median", 0.0));
      }
      if (metricSnapshot.willTrace(MetricFact.P75_Q)) {
        metricSnapshot.addItem(new MetricItem("75th pctl", 0.0));
      }
      if (metricSnapshot.willTrace(MetricFact.P95_Q)) {
        metricSnapshot.addItem(new MetricItem("95th pctl", 0.0));
      }
      if (metricSnapshot.willTrace(MetricFact.P98_Q)) {
        metricSnapshot.addItem(new MetricItem("98th pctl", 0.0));
      }
      if (metricSnapshot.willTrace(MetricFact.P99_Q)) {
        metricSnapshot.addItem(new MetricItem("99th pctl", 0.0));
      }
      if (metricSnapshot.willTrace(MetricFact.P999_Q)) {
        metricSnapshot.addItem(new MetricItem("999th pctl", 0.0));
      }
    }
  }

  @Override
  public void update (long value) {

    MetricSnapshot metricSnapshot;
    double[] currentValues;
    long currentCount;
    long currentSum;
    long currentMin;
    long currentMax;

    currentCount = count.incrementAndGet();
    currentSum = sum.getAndAdd(value);
    currentMin = setMin(value);
    currentMax = setMax(value);
    currentValues = updateVariance(value);
    sample.update(value);

    if ((metricSnapshot = getMetricSnapshot()) != null) {

      Statistics currentStatistics = sample.getStatistics();

      if (metricSnapshot.willTrace(MetricFact.COUNT)) {
        metricSnapshot.addItem(new MetricItem("count", currentCount));
      }
      if (metricSnapshot.willTrace(MetricFact.SUM)) {
        metricSnapshot.addItem(new MetricItem("sum", currentSum));
      }
      if (metricSnapshot.willTrace(MetricFact.MIN)) {
        metricSnapshot.addItem(new MetricItem("min", currentMin));
      }
      if (metricSnapshot.willTrace(MetricFact.MAX)) {
        metricSnapshot.addItem(new MetricItem("max", currentMax));
      }
      if (metricSnapshot.willTrace(MetricFact.AVG)) {
        metricSnapshot.addItem(new MetricItem("avg", (currentCount > 0) ? currentSum / (double)currentCount : 0.0));
      }
      if (metricSnapshot.willTrace(MetricFact.STD_DEV)) {
        metricSnapshot.addItem(new MetricItem("std dev", (currentCount > 0) ? Math.sqrt((currentCount <= 1) ? 0.0 : currentValues[1] / (currentCount - 1)) : 0.0));
      }
      if (metricSnapshot.willTrace(MetricFact.MEDIAN)) {
        metricSnapshot.addItem(new MetricItem("median", currentStatistics.getMedian()));
      }
      if (metricSnapshot.willTrace(MetricFact.P75_Q)) {
        metricSnapshot.addItem(new MetricItem("75th pctl", currentStatistics.get75thPercentile()));
      }
      if (metricSnapshot.willTrace(MetricFact.P95_Q)) {
        metricSnapshot.addItem(new MetricItem("95th pctl", currentStatistics.get95thPercentile()));
      }
      if (metricSnapshot.willTrace(MetricFact.P98_Q)) {
        metricSnapshot.addItem(new MetricItem("98th pctl", currentStatistics.get98thPercentile()));
      }
      if (metricSnapshot.willTrace(MetricFact.P99_Q)) {
        metricSnapshot.addItem(new MetricItem("99th pctl", currentStatistics.get99thPercentile()));
      }
      if (metricSnapshot.willTrace(MetricFact.P999_Q)) {
        metricSnapshot.addItem(new MetricItem("999th pctl", currentStatistics.get999thPercentile()));
      }
    }
  }

  @Override
  public String getSampleType () {

    return sample.getType().name();
  }

  @Override
  public long getCount () {

    return count.get();
  }

  @Override
  public double getMax () {

    return (getCount() > 0) ? max.get() : 0.0;
  }

  private long setMax (long potentialMax) {

    boolean replaced = false;
    long currentMax;

    do {
      currentMax = max.get();
    } while (!(currentMax >= potentialMax || (replaced = max.compareAndSet(currentMax, potentialMax))));

    return replaced ? potentialMax : currentMax;
  }

  @Override
  public double getMin () {

    return (getCount() > 0) ? min.get() : 0.0;
  }

  private long setMin (long potentialMin) {

    boolean replaced = false;
    long currentMin;

    do {
      currentMin = min.get();
    } while (!(currentMin <= potentialMin || (replaced = min.compareAndSet(currentMin, potentialMin))));

    return replaced ? potentialMin : currentMin;
  }

  @Override
  public double getAverage () {

    return (getCount() > 0) ? sum.get() / (double)getCount() : 0.0;
  }

  @Override
  public double getStdDev () {

    return (getCount() > 0) ? Math.sqrt(getVariance()) : 0.0;
  }

  @Override
  public double getSum () {

    return (double)sum.get();
  }

  private double getVariance () {

    return (getCount() <= 1) ? 0.0 : variance.get()[1] / (getCount() - 1);
  }

  @Override
  public Statistics getStatistics () {

    return sample.getStatistics();
  }

  private double[] updateVariance (long value) {

    boolean done;
    double[] newValues;

    do {

      double[] oldValues = variance.get();

      newValues = arrayCache.get();

      if (oldValues[0] == -1) {
        newValues[0] = value;
        newValues[1] = 0;
      }
      else {

        double oldM = oldValues[0];
        double oldS = oldValues[1];
        double newM = oldM + ((value - oldM) / getCount());
        double newS = oldS + ((value - oldM) * (value - newM));

        newValues[0] = newM;
        newValues[1] = newS;
      }

      if (done = variance.compareAndSet(oldValues, newValues)) {
        // recycle the old array into the cache
        arrayCache.set(oldValues);
      }
    } while (!done);

    return newValues;
  }

  private static final class ArrayCache extends ThreadLocal {

    @Override
    protected double[] initialValue () {

      return new double[2];
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy