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

com.couchbase.client.core.deps.org.LatencyUtils.LatencyStats Maven / Gradle / Ivy

There is a newer version: 2.7.0
Show newest version
/**
 * Written by Gil Tene of Azul Systems, and released to the public domain,
 * as explained at http://creativecommons.org/publicdomain/zero/1.0/
 */

package com.couchbase.client.core.deps.org.LatencyUtils;

import com.couchbase.client.core.deps.org.HdrHistogram.Histogram;
import com.couchbase.client.core.deps.org.HdrHistogram.AtomicHistogram;
import com.couchbase.client.core.deps.org.HdrHistogram.WriterReaderPhaser;

import java.lang.ref.WeakReference;

/**
 * LatencyStats objects are used to track and report on the behavior of latencies across measurements.
 * recorded into a a given LatencyStats instance. Latencies are recorded using
 * {@link #recordLatency}, which provides a thread safe, wait free, and lossless recording method.
 * The accumulated behavior across the recorded latencies in a given LatencyStats instance can be
 * examined in detail using interval and accumulated HdrHistogram histograms
 * (see {@link com.couchbase.client.core.deps.org.HdrHistogram.Histogram}).
 * 

* LatencyStats instances maintain internal histogram data that track all recoded latencies. Interval * histogram data can be sampled with the {@link #getIntervalHistogram}, * {@link #getIntervalHistogramInto}, or {@link #addIntervalHistogramTo} calls. *

* Recorded latencies are auto-corrected for experienced pauses by leveraging pause detectors and * moving window average interval estimators, compensating for coordinated omission. While typical * histogram use deals with corrected data, LatencyStats instances do keep track of the raw, * uncorrected records, which can be accessed via the {@link #getLatestUncorrectedIntervalHistogram} * and {@link #getLatestUncorrectedIntervalHistogramInto} calls. *

* LatencyStats objects can be instantiated either directly via the provided constructors, or by * using the fluent API builder supported by {@link com.couchbase.client.core.deps.org.LatencyUtils.LatencyStats.Builder}. * *

Correction Technique

* In addition to tracking the raw latency recordings provided via {@link #recordLatency}, each * LatencyStats instance maintains an internal interval estimator that tracks the expected * interval between latency recordings. Whenever a stall in measurement is detected by a given * pause detector, each LatencyStats instances that uses that pause detector will be notified, * and will generate correcting latency entries (that are separately tracked internally). * Correcting latency entries are computed to "fill in" detected measurement pauses by projecting * the observed recording rate into the pause gap, and creating a linearly diminishing latency * measurement for each missed recording interval. *

* Pause detection and interval estimation are both configurable, and each LatencyStats instance * can operate with potentially independent pause detector and interval estimator settings. *

* A configurable default pause detector is (by default) shared between LatencyStats instances * that are not provided with a specific pause detector at instantiation. If the default pause * detector is not explicitly set, it will itself default to creating (and starting) a single * instance of {@link com.couchbase.client.core.deps.org.LatencyUtils.SimplePauseDetector}, which uses consensus observation * of a pause across multiple observing threads as a detection technique. *

* Custom pause detectors can be provided (by subclassing {@link com.couchbase.client.core.deps.org.LatencyUtils.PauseDetector}). * E.g. a pause detector that pauses GC log output rather than directly measuring observations * can be constructed. A custom pause detector can be especially useful in situations where a * stall in the operation and latency measurement of an application's is known and detectable * by the application level, but would not be detectable as a process-wide stall in execution * (which {@link com.couchbase.client.core.deps.org.LatencyUtils.SimplePauseDetector} is built to detect). *

* Interval estimation is done by using a time-capped moving window average estimator, with * the expected interval computed to be the average of measurement intervals within the window * (with the window being capped by both count and time). See * {@link TimeCappedMovingAverageIntervalEstimator} for more details. The estimator window * length and time cap can both be configured when instantiating a LatencyStats object * (defaults are 1024, and 10 seconds). * */ public class LatencyStats { private static Builder defaultBuilder = new Builder(); private static final TimeServices.ScheduledExecutor latencyStatsScheduledExecutor = new TimeServices.ScheduledExecutor(); private static PauseDetector defaultPauseDetector; // All times and time units are in nanoseconds private final long lowestTrackableLatency; private final long highestTrackableLatency; private final int numberOfSignificantValueDigits; private volatile AtomicHistogram activeRecordingHistogram; private Histogram activePauseCorrectionsHistogram; private AtomicHistogram inactiveRawDataHistogram; private Histogram inactivePauseCorrectionsHistogram; private final WriterReaderPhaser recordingPhaser = new WriterReaderPhaser(); private final PauseTracker pauseTracker; private final IntervalEstimator intervalEstimator; private final PauseDetector pauseDetector; /** * Set the default pause detector for the LatencyStats class. Used by constructors that do * not explicitly provide a pause detector. * * @param pauseDetector the pause detector to use as a default when no explicit pause detector is provided */ static public void setDefaultPauseDetector(PauseDetector pauseDetector) { defaultPauseDetector = pauseDetector; } /** * Get the current default pause detector which will be used by newly constructed LatencyStats * instances when the constructor is not explicitly provided with one. * @return the current default pause detector */ static public PauseDetector getDefaultPauseDetector() { return defaultPauseDetector; } /** * Create a LatencyStats object with default settings.
*

    *
  • use the default pause detector (supplied separately set using {@link #setDefaultPauseDetector}, which * will itself default to a {@link SimplePauseDetector} if not explicitly supplied)
  • *
  • use a default histogram update interval (1 sec)
  • *
  • use a default histogram range and accuracy (1 usec to 1hr, 2 decimal points of accuracy)
  • *
  • and use a default moving window estimator (1024 entry moving window, time capped at * 10 seconds)
  • *
*/ public LatencyStats() { this( defaultBuilder.lowestTrackableLatency, defaultBuilder.highestTrackableLatency, defaultBuilder.numberOfSignificantValueDigits, defaultBuilder.intervalEstimatorWindowLength, defaultBuilder.intervalEstimatorTimeCap, defaultBuilder.pauseDetector ); } /** * * @param lowestTrackableLatency Lowest trackable latency in latency histograms * @param highestTrackableLatency Highest trackable latency in latency histograms * @param numberOfSignificantValueDigits Number of significant [decimal] digits of accuracy in latency histograms * @param intervalEstimatorWindowLength Length of window in moving window interval estimator * @param intervalEstimatorTimeCap Time cap (in nanoseconds) of window in moving window interval estimator * @param pauseDetector The pause detector to use for identifying and correcting for pauses */ public LatencyStats(final long lowestTrackableLatency, final long highestTrackableLatency, final int numberOfSignificantValueDigits, final int intervalEstimatorWindowLength, final long intervalEstimatorTimeCap, final PauseDetector pauseDetector) { if (pauseDetector == null) { if (defaultPauseDetector == null) { // There is no pause detector supplied, and no default set. Set the default to a default // simple pause detector instance. [User feedback seems to be that this is preferable to // throwing an exception and forcing people to set the default themselves...] synchronized (LatencyStats.class) { if (defaultPauseDetector == null) { defaultPauseDetector = new SimplePauseDetector(); } } } this.pauseDetector = defaultPauseDetector; } else { this.pauseDetector = pauseDetector; } this.lowestTrackableLatency = lowestTrackableLatency; this.highestTrackableLatency = highestTrackableLatency; this.numberOfSignificantValueDigits = numberOfSignificantValueDigits; // Create alternating recording histograms: activeRecordingHistogram = new AtomicHistogram(lowestTrackableLatency, highestTrackableLatency, numberOfSignificantValueDigits); inactiveRawDataHistogram = new AtomicHistogram(lowestTrackableLatency, highestTrackableLatency, numberOfSignificantValueDigits); // Create alternating pause correction histograms: activePauseCorrectionsHistogram = new Histogram(lowestTrackableLatency, highestTrackableLatency, numberOfSignificantValueDigits); inactivePauseCorrectionsHistogram = new Histogram(lowestTrackableLatency, highestTrackableLatency, numberOfSignificantValueDigits); // Create interval estimator: intervalEstimator = new TimeCappedMovingAverageIntervalEstimator(intervalEstimatorWindowLength, intervalEstimatorTimeCap, this.pauseDetector); // Create PauseTracker and register with pauseDetector: pauseTracker = new PauseTracker(this.pauseDetector, this); long now = System.currentTimeMillis(); activeRecordingHistogram.setStartTimeStamp(now); activePauseCorrectionsHistogram.setStartTimeStamp(now); } /** * Record a latency value in the LatencyStats object * @param latency latency value (in nanoseconds) to record */ public void recordLatency(long latency) { long criticalValueAtEnter = recordingPhaser.writerCriticalSectionEnter(); try { trackRecordingInterval(); activeRecordingHistogram.recordValue(latency); } finally { recordingPhaser.writerCriticalSectionExit(criticalValueAtEnter); } } // Interval Histogram access: /** * Get a new interval histogram which will include the value counts accumulated since the last * interval histogram was taken. *

* Calling {@link #getIntervalHistogram}() will reset * the interval value counts, and start accumulating value counts for the next interval. * * @return a copy of the latest interval latency histogram */ public synchronized Histogram getIntervalHistogram() { Histogram intervalHistogram = new Histogram(lowestTrackableLatency, highestTrackableLatency, numberOfSignificantValueDigits); getIntervalHistogramInto(intervalHistogram); return intervalHistogram; } /** * Place a copy of the value counts accumulated since the last interval histogram * was taken into {@code targetHistogram}. * * Calling {@link #getIntervalHistogramInto}() will reset * the interval value counts, and start accumulating value counts for the next interval. * * @param targetHistogram the histogram into which the interval histogram's data should be copied */ public synchronized void getIntervalHistogramInto(Histogram targetHistogram) { try { recordingPhaser.readerLock(); updateHistograms(); inactiveRawDataHistogram.copyInto(targetHistogram); targetHistogram.add(inactivePauseCorrectionsHistogram); } finally { recordingPhaser.readerUnlock(); } } /** * Add the values value counts accumulated since the last interval histogram was taken * into {@code toHistogram}. * * Calling {@link #addIntervalHistogramTo}() will reset * the interval value counts, and start accumulating value counts for the next interval. * * @param toHistogram the histogram into which the interval histogram's data should be added */ public synchronized void addIntervalHistogramTo(Histogram toHistogram) { try { recordingPhaser.readerLock(); updateHistograms(); toHistogram.add(inactiveRawDataHistogram); toHistogram.add(inactivePauseCorrectionsHistogram); } finally { recordingPhaser.readerUnlock(); } } /** * Get a copy of the uncorrected latest interval latency histogram. Values will not include * corrections for detected pauses. The interval histogram copies will include all values points * captured up to the latest call to call to one of {@link #getIntervalHistogram}, * {@link #getIntervalHistogramInto}, or {@link #addIntervalHistogramTo}. * * @return a copy of the latest uncorrected interval latency histogram */ public synchronized Histogram getLatestUncorrectedIntervalHistogram() { try { recordingPhaser.readerLock(); Histogram intervalHistogram = new Histogram(lowestTrackableLatency, highestTrackableLatency, numberOfSignificantValueDigits); getLatestUncorrectedIntervalHistogramInto(intervalHistogram); return intervalHistogram; } finally { recordingPhaser.readerUnlock(); } } /** * Place a copy of the values of the latest uncorrected interval latency histogram. Values will not include * corrections for detected pauses. The interval histogram copies will include all values points * captured up to the latest call to call to one of {@link #getIntervalHistogram}, * {@link #getIntervalHistogramInto}, or {@link #addIntervalHistogramTo}. * * @param targetHistogram the histogram into which the interval histogram's data should be copied */ public synchronized void getLatestUncorrectedIntervalHistogramInto(Histogram targetHistogram) { try { recordingPhaser.readerLock(); inactiveRawDataHistogram.copyInto(targetHistogram); } finally { recordingPhaser.readerUnlock(); } } /** * Stop operation of this LatencyStats object, removing it from the pause detector's notification list */ public synchronized void stop() { pauseTracker.stop(); latencyStatsScheduledExecutor.shutdown(); } /** * get the IntervalEstimator used by this LatencyStats object * @return the IntervalEstimator used by this LatencyStats object */ public IntervalEstimator getIntervalEstimator() { return intervalEstimator; } /** * get the PauseDetector used by this LatencyStats object * @return the PauseDetector used by this LatencyStats object */ public PauseDetector getPauseDetector() { return pauseDetector; } /** * A fluent API builder class for creating LatencyStats objects. *
Uses the following defaults: *

    *
  • lowestTrackableLatency: 1000 (1 usec)
  • *
  • highestTrackableLatency: 3600000000000L (1 hour)
  • *
  • numberOfSignificantValueDigits: 2
  • *
  • intervalEstimatorWindowLength: 1024
  • *
  • intervalEstimatorTimeCap: 10000000000L (10 sec)
  • *
  • pauseDetector: (use LatencyStats default)
  • *
*/ public static class Builder { private long lowestTrackableLatency = 1000L; /* 1 usec */ private long highestTrackableLatency = 3600000000000L; /* 1 hr */ private int numberOfSignificantValueDigits = 2; private int intervalEstimatorWindowLength = 1024; private long intervalEstimatorTimeCap = 10000000000L; /* 10 sec */ private PauseDetector pauseDetector = null; public static Builder create() { return new Builder(); } public Builder() { } public Builder lowestTrackableLatency(long lowestTrackableLatency) { this.lowestTrackableLatency = lowestTrackableLatency; return this; } public Builder highestTrackableLatency(long highestTrackableLatency) { this.highestTrackableLatency = highestTrackableLatency; return this; } public Builder numberOfSignificantValueDigits(int numberOfSignificantValueDigits) { this.numberOfSignificantValueDigits = numberOfSignificantValueDigits; return this; } public Builder intervalEstimatorWindowLength(int intervalEstimatorWindowLength) { this.intervalEstimatorWindowLength = intervalEstimatorWindowLength; return this; } public Builder intervalEstimatorTimeCap(long intervalEstimatorTimeCap) { this.intervalEstimatorTimeCap = intervalEstimatorTimeCap; return this; } public Builder pauseDetector(PauseDetector pauseDetector) { this.pauseDetector = pauseDetector; return this; } public LatencyStats build() { return new LatencyStats(lowestTrackableLatency, highestTrackableLatency, numberOfSignificantValueDigits, intervalEstimatorWindowLength, intervalEstimatorTimeCap, pauseDetector); } } private synchronized void recordDetectedPause(long pauseLength, long pauseEndTime) { long criticalValueAtEnter = recordingPhaser.writerCriticalSectionEnter(); try { long estimatedInterval = intervalEstimator.getEstimatedInterval(pauseEndTime); long observedLatencyMinbar = pauseLength - estimatedInterval; if (observedLatencyMinbar >= estimatedInterval) { activePauseCorrectionsHistogram.recordValueWithExpectedInterval( observedLatencyMinbar, estimatedInterval ); } } finally { recordingPhaser.writerCriticalSectionExit(criticalValueAtEnter); } } private void trackRecordingInterval() { long now = TimeServices.nanoTime(); intervalEstimator.recordInterval(now); } private void swapRecordingHistograms() { final AtomicHistogram tempHistogram = inactiveRawDataHistogram; inactiveRawDataHistogram = activeRecordingHistogram; activeRecordingHistogram = tempHistogram; } private void swapPauseCorrectionHistograms() { final Histogram tempHistogram = inactivePauseCorrectionsHistogram; inactivePauseCorrectionsHistogram = activePauseCorrectionsHistogram; activePauseCorrectionsHistogram = tempHistogram; } private synchronized void swapHistograms() { swapRecordingHistograms(); swapPauseCorrectionHistograms(); } private synchronized void updateHistograms() { try { recordingPhaser.readerLock(); inactiveRawDataHistogram.reset(); inactivePauseCorrectionsHistogram.reset(); swapHistograms(); long now = System.currentTimeMillis(); activeRecordingHistogram.setStartTimeStamp(now); activePauseCorrectionsHistogram.setStartTimeStamp(now); inactiveRawDataHistogram.setEndTimeStamp(now); inactivePauseCorrectionsHistogram.setEndTimeStamp(now); // Make sure we are not in the middle of recording a value on the previously current recording histogram: // Flip phase on epochs to make sure no in-flight recordings are active on pre-flip phase: recordingPhaser.flipPhase(); } finally { recordingPhaser.readerUnlock(); } } /** * PauseTracker is used to feed pause correction histograms whenever a pause is reported: */ private static class PauseTracker extends WeakReference implements PauseDetectorListener { final PauseDetector pauseDetector; PauseTracker(final PauseDetector pauseDetector, final LatencyStats latencyStats) { super(latencyStats); this.pauseDetector = pauseDetector; // Register as listener: pauseDetector.addListener(this); } public void stop() { pauseDetector.removeListener(this); } public void handlePauseEvent(final long pauseLength, final long pauseEndTime) { final LatencyStats latencyStats = this.get(); if (latencyStats != null) { latencyStats.recordDetectedPause(pauseLength, pauseEndTime); } else { // Remove listener: stop(); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy