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

com.sleepycat.utilint.LatencyStat Maven / Gradle / Ivy

Go to download

Berkeley DB Java Edition is a open source, transactional storage solution for Java applications. The Direct Persistence Layer (DPL) API is faster and easier to develop, deploy, and manage than serialized object files or ORM-based Java persistence solutions. The Collections API enhances the standard java.util.collections classes allowing them to be persisted to a local file system and accessed concurrently while protected by ACID transactions. Data is stored by serializing objects and managing class and instance data separately so as not to waste space. Berkeley DB Java Edition is the reliable drop-in solution for complex, fast, and scalable storage. Source for this release is in 'je-4.0.92-sources.jar', the Javadoc is located at 'http://download.oracle.com/berkeley-db/docs/je/4.0.92/'.

There is a newer version: 5.0.73
Show newest version
/*-
 * Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
 *
 * This file was distributed by Oracle as part of a version of Oracle Berkeley
 * DB Java Edition made available at:
 *
 * http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html
 *
 * Please see the LICENSE file included in the top-level directory of the
 * appropriate version of Oracle Berkeley DB Java Edition for a copy of the
 * license and additional information.
 */

package com.sleepycat.utilint;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerArray;
import java.util.concurrent.atomic.AtomicLong;

/**
 * A stat that keeps track of latency in milliseconds and presents average,
 * min, max, 95th and 99th percentile values.
 */
public class LatencyStat implements Cloneable {

    private static final long serialVersionUID = 1L;

    /*
     * The maximum tracked latency, in milliseconds, it's also the size of the
     * configurable array which is used to save latencies.
     */
    private final int maxTrackedLatencyMillis;

    private static class Values {

        /* The number of total operations that have been tracked. */
        final AtomicInteger numOps;

        /* The number of total requests that have been tracked. */
        final AtomicInteger numRequests;

        /* The number of total nanoseconds that have been tracked. */
        final AtomicLong totalNanos;

        /*
         * Array is indexed by latency in millis and elements contain the
         * number of ops for that latency.
         */
        final AtomicIntegerArray histogram;

        /*
         * Min and max latency. They may both exceed maxTrackedLatencyMillis.
         * A volatile int rather than an AtomicInteger is used because
         * AtomicInteger has no min() or max() method, so there is no advantage
         * to using it.
         */
        volatile int minIncludingOverflow;
        volatile int maxIncludingOverflow;

        /* Number of requests whose latency exceed maxTrackedLatencyMillis. */
        final AtomicInteger requestsOverflow;

        Values(final int maxTrackedLatencyMillis) {
            histogram = new AtomicIntegerArray(maxTrackedLatencyMillis);
            numOps = new AtomicInteger();
            numRequests = new AtomicInteger();
            requestsOverflow = new AtomicInteger();
            totalNanos = new AtomicLong();
            minIncludingOverflow = Integer.MAX_VALUE;
            maxIncludingOverflow = 0;
        }
    }

    /*
     * Contains the values tracked by set() and reported by calculate().
     *
     * To clear the values, this field is assigned a new instance.  This
     * prevents uninitialized values when set() and clear() run concurrently.
     * Methods that access the values (set and calculate) should assign
     * trackedValues to a local var and perform all access using the local var,
     * so that clear() will not impact the computation.
     *
     * Concurrent access by set() and calculate() is handled differently.  The
     * numOps and numRequests fields are incremented by set() last, and are
     * checked first by calculate().  If numOps or numRequests is zero,
     * calculate() will return an empty Latency object.  If numOps and
     * numRequests are non-zero, calculate() may still return latency values
     * that are inconsistent, when set() runs concurrently.  But at least
     * calculate() won't return uninitialized latency values.  Without
     * synchronizing set(), this is the best we can do.  Synchronizing set()
     * might introduce contention during CRUD operations.
     */
    private volatile Values trackedValues;

    private int saveMin;
    private int saveMax;
    private float saveAvg;
    private int saveNumOps;
    private int saveNumRequests;
    private int save95;
    private int save99;
    private int saveRequestsOverflow;

    public LatencyStat(long maxTrackedLatencyMillis) {
        this.maxTrackedLatencyMillis = (int) maxTrackedLatencyMillis;
        clear();
    }

    public void clear() {
        clearInternal();
    }

    /**
     * Returns and clears the current stats.
     */
    private synchronized Values clearInternal() {
        final Values values = trackedValues;

        /*
         * Create a new instance to support concurrent access.  See {@link
         * #trackedValues}.
         */
        trackedValues = new Values(maxTrackedLatencyMillis);

        return values;
    }

    /**
     * Generate the min, max, avg, 95th and 99th percentile for the collected
     * measurements. Do not clear the measurement collection.
     */
    public Latency calculate() {
        return calculate(false);
    }

    /**
     * Generate the min, max, avg, 95th and 99th percentile for the collected
     * measurements, then clear the measurement collection.
     */
    public Latency calculateAndClear() {
        return calculate(true);
    }

    /**
     * Calculate may be called on a stat that is concurrently updating, so
     * while it has to be thread safe, it's a bit inaccurate when there's
     * concurrent activity. That tradeoff is made in order to avoid the cost of
     * synchronization during the set() method.  See {@link #trackedValues}.
     */
    private synchronized Latency calculate(boolean clear) {

        /*
         * Use a local var to support concurrent access.  See {@link
         * #trackedValues}.
         */
        final Values values = clear ? clearInternal() : trackedValues;

        /*
         * Check numOps and numReqests first and return an empty Latency if
         * either one is zero.  This ensures that we don't report partially
         * computed values when they are zero.  This works because the other
         * values are calculated first by set(), and numOps and numRequests are
         * incremented last.
         */
        final int totalOps = values.numOps.get();
        final int totalRequests = values.numRequests.get();
        if (totalOps == 0 || totalRequests == 0) {
            return new Latency(maxTrackedLatencyMillis);
        }

        final long totalNanos = values.totalNanos.get();
        final int nOverflow = values.requestsOverflow.get();
        final int maxIncludingOverflow = values.maxIncludingOverflow;
        final int minIncludingOverflow = values.minIncludingOverflow;

        final float avgMs = (float) ((totalNanos * 1e-6) / totalRequests);

        /*
         * The 95% and 99% values will be -1 if there are no recorded latencies
         * in the histogram.
         */
        int percent95 = -1;
        int percent99 = -1;

        /*
         * Min/max can be inaccurate because of concurrent set() calls, i.e.,
         * values may be from a mixture of different set() calls.  Bound the
         * min/max to the average, so they are sensible.
         */
        final int avgMsInt = Math.round(avgMs);
        int max = Math.max(avgMsInt, maxIncludingOverflow);
        int min = Math.min(avgMsInt, minIncludingOverflow);

        final int percent95Count;
        final int percent99Count;
        final int nTrackedRequests = totalRequests - nOverflow;
        if (nTrackedRequests == 1) {
            /* For one request, always include it in the 95% and 99%. */
            percent95Count = 1;
            percent99Count = 1;
        } else {
            /* Otherwise truncate: never include the last/highest request. */
            percent95Count = (int) (nTrackedRequests * .95);
            percent99Count = (int) (nTrackedRequests * .99);
        }

        final int histogramLength = values.histogram.length();
        int numRequestsSeen = 0;
        for (int latency = 0; latency < histogramLength; latency++) {

            final int count = values.histogram.get(latency);

            if (count == 0) {
                continue;
            }

            if (min > latency) {
                min = latency;
            }

            if (max < latency) {
                max = latency;
            }

            if (numRequestsSeen < percent95Count) {
                percent95 = latency;
            }

            if (numRequestsSeen < percent99Count) {
                percent99 = latency;
            }

            numRequestsSeen += count;
        }

        saveMax = max;
        saveMin = min;
        saveAvg = avgMs;
        saveNumOps = totalOps;
        saveNumRequests = totalRequests;
        save95 = percent95;
        save99 = percent99;
        saveRequestsOverflow = nOverflow;

        return new Latency(maxTrackedLatencyMillis, saveMin, saveMax, saveAvg,
                           saveNumOps, saveNumRequests, save95, save99,
                           saveRequestsOverflow);
    }

    /**
     * Record a single operation that took place in a request of "nanolatency"
     * nanos.
     */
    public void set(long nanoLatency) {
        set(1, nanoLatency);
    }

    /**
     * Record "numRecordedOps" (one or more) operations that took place in a
     * single request of "nanoLatency" nanos.
     */
    public void set(int numRecordedOps, long nanoLatency) {

        /* ignore negative values [#22466] */
        if (nanoLatency < 0) {
            return;
        }

        /*
         * Use a local var to support concurrent access.  See {@link
         * #trackedValues}.
         */
        final Values values = trackedValues;

        /* Round the latency to determine where to mark the histogram. */
        final int millisRounded =
            (int) ((nanoLatency + (1000000l / 2)) / 1000000l);

        /* Record this latency. */
        if (millisRounded >= maxTrackedLatencyMillis) {
            values.requestsOverflow.incrementAndGet();
        } else {
            values.histogram.incrementAndGet(millisRounded);
        }

        /*
         * Update the min/max latency if necessary.  This is not atomic, so we
         * loop to account for lost updates.
         */
        while (values.maxIncludingOverflow < millisRounded) {
            values.maxIncludingOverflow = millisRounded;
        }
        while (values.minIncludingOverflow > millisRounded) {
            values.minIncludingOverflow = millisRounded;
        }

        /*
         * Keep a count of latency that is precise enough to record sub
         * millisecond values.
         */
        values.totalNanos.addAndGet(nanoLatency);

        /*
         * Increment numOps and numRequests last so that calculate() won't use
         * other uninitialized values when numOps or numRequests is zero.
         */
        values.numOps.addAndGet(numRecordedOps);
        values.numRequests.incrementAndGet();
    }

    public boolean isEmpty() {
        return (trackedValues.numOps.get() == 0) ||
               (trackedValues.numRequests.get() == 0);
    }

    @Override
    public String toString() {
        final Latency results =
            new Latency(maxTrackedLatencyMillis, saveMin, saveMax, saveAvg,
                        saveNumRequests, saveNumOps, save95, save99,
                        saveRequestsOverflow);
        return results.toString();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy