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

com.yammer.metrics.core.BurstRateTrackingCounter Maven / Gradle / Ivy

There is a newer version: 2023-22.3
Show newest version
package com.yammer.metrics.core;

import com.wavefront.common.EvictingRingBuffer;
import com.wavefront.common.NamedThreadFactory;
import com.wavefront.common.SynchronizedEvictingRingBuffer;
import com.yammer.metrics.Metrics;

import javax.annotation.Nullable;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
 * A counter that accurately tracks burst rate, 1-minute rate and 5-minute rate, with
 * customizable precision (defined by {@code granularityMillis} parameter, which controls
 * sample collection interval).
 *
 * @author [email protected]
 */
public class BurstRateTrackingCounter extends Counter implements Metric {
  private static final MetricsRegistry LOCAL_REGISTRY = new MetricsRegistry();
  private static final ScheduledExecutorService EXECUTOR = Executors.newScheduledThreadPool(1,
      new NamedThreadFactory("burst-tracking-counter"));

  private final Counter delegate;
  private final int granularityMillis;
  private final Histogram burstRateHistogram;
  private final AtomicLong validSampleCount = new AtomicLong();
  private volatile long previousCount = 0;
  private volatile long currentRate = 0;

  private final EvictingRingBuffer perPeriodStats;

  /**
   * @param metricName        metric name for the counter.
   * @param metricsRegistry   metrics registry for the counter. if null, default registry is used.
   * @param granularityMillis stats collection interval in milliseconds. lower granularity usually
   *                          means better precision, but it always requires more memory (we need
   *                          to track 5 minutes worth of data, so, for example, if granularity
   *                          is set to 100ms, we have to keep 3000 long values).
   */
  public BurstRateTrackingCounter(MetricName metricName,
                                  @Nullable MetricsRegistry metricsRegistry,
                                  int granularityMillis) {
    this.delegate = (metricsRegistry == null ? Metrics.defaultRegistry() : metricsRegistry).
        newCounter(metricName);
    this.granularityMillis = granularityMillis;
    this.perPeriodStats = new SynchronizedEvictingRingBuffer<>(5 * 60 * 1000 / granularityMillis,
        false, 0L);
    this.burstRateHistogram = LOCAL_REGISTRY.newHistogram(BurstRateTrackingCounter.class,
        metricName.getGroup() + "-max-burst-rate");
    EXECUTOR.scheduleAtFixedRate(() -> {
      long currentCount = this.delegate.count();
      this.currentRate = currentCount - this.previousCount;
      this.burstRateHistogram.update(this.currentRate);
      this.previousCount = currentCount;
      this.perPeriodStats.add(this.currentRate);
      if (currentCount > 0) {
        validSampleCount.incrementAndGet();
      }
    }, granularityMillis, granularityMillis, TimeUnit.MILLISECONDS);
  }

  /**
   * Get histogram of 1s burst rates.
   *
   * @return burst rate histogram
   */
  public Histogram getBurstRateHistogram() {
    return burstRateHistogram;
  }

  /**
   * Get most recent 1-second rate.
   *
   * @return rate
   */
  public long getCurrentRate() {
    return currentRate * 1000 / granularityMillis;
  }

  /**
   * Get highest burst rate and reset the histogram.
   * .
   * @return burst rate
   */
  public Double getMaxBurstRateAndClear() {
    Double maxValue = burstRateHistogram.max() * 1000 / granularityMillis;
    burstRateHistogram.clear();
    return maxValue;
  }

  /**
   * Get 1-minute rate in human-readable form.
   *
   * @return 1-minute rate as string
   */
  public String getOneMinutePrintableRate() {
    return getPrintableRate(getOneMinuteCount());
  }

  /**
   * Get 4-minute rate in human-readable form.
   *
   * @return 4-minute rate as string
   */
  public String getFiveMinutePrintableRate() {
    return getPrintableRate(getFiveMinuteCount() / 5);
  }

  /**
   * Get delta value of the counter between now and 1 minute ago.
   *
   * @return 1-minute delta value
   */
  public long getOneMinuteCount() {
    return perPeriodStats.toList().subList(4 * 60 * 1000 / granularityMillis,
        5 * 60 * 1000 / granularityMillis).stream().mapToLong(i -> i).sum();
  }

  /**
   * Get delta value of the counter between now and 5 minutes ago.
   *
   * @return 5-minute delta value
   */
  public long getFiveMinuteCount() {
    return perPeriodStats.toList().stream().mapToLong(i -> i).sum();
  }

  /**
   * Get the number of samples since the counter received its first non-zero sample.
   *
   * @return number of samples.
   */
  public long getSampleCount() {
    return validSampleCount.get();
  }

  /**
   * Convert a per minute count to human-readable per second rate.
   *
   * @param count counter value.
   * @return human readable string
   */
  public static String getPrintableRate(long count) {
    // round rate to the nearest integer, unless it's < 1
    return count < 60 && count > 0 ? "<1" : String.valueOf((count + 30) / 60);
  }

  @Override
  public void inc() {
    delegate.inc();
  }

  @Override
  public void inc(long n) {
    delegate.inc(n);
  }

  @Override
  public void dec() {
    delegate.dec();
  }

  @Override
  public void dec(long n) {
    delegate.dec(n);
  }

  @Override
  public long count() {
    return delegate.count();
  }

  @Override
  public void clear() {
    delegate.clear();
  }

  @Override
  public  void processWith(MetricProcessor processor, MetricName name, T context)
      throws Exception {
    delegate.processWith(processor, name, context);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy