oracle.kv.impl.util.sklogger.PerfQuantile Maven / Gradle / Ivy
/*-
* Copyright (C) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
*
* This file was distributed by Oracle as part of a version of Oracle NoSQL
* Database made available at:
*
* http://www.oracle.com/technetwork/database/database-technologies/nosqldb/downloads/index.html
*
* Please see the LICENSE file included in the top-level directory of the
* appropriate version of Oracle NoSQL Database for a copy of the license and
* additional information.
*/
package oracle.kv.impl.util.sklogger;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicLongArray;
import com.sleepycat.utilint.Latency;
/**
* A PerfQuantile as one kind of {@link RateMetric} is to calculate
* avg/95th/99th of observed values. As we need set a maxTracked value for
* PerfQuantile, and we create a histogram array that length is the maxTracked
* value, so the maxTracked can't be too big or unknown. And the observed value
* need be positive integer.
* The most common usage scenario is for latency in millisecond.
*
* Usage example:
* {@code
* PerfQuantile requestLatency = new PerfQuantile("requestLatency", 5000,
* "opType");
* add one observed read request latency.
* requestLatency.label("read").observe(latency);
*
* add one observed write request latency.
* requestLatency.label("write").observe(latency);
*
* get read request avg/95th/99th latency since last call for watcherName.
* requestCount.label("read").rateSinceLastTime("watcherName")
* }
*
*/
public class PerfQuantile
extends RateMetric {
/*
* Ceiling of tracked value
*/
private final int maxTracked;
/**
*
* @param name is metric name that will be used when metric is registered
* and/or processed.
* @param maxTracked set the ceiling of tracked value in a histogram array
* which length is maxTracked value.
* @param labelNames to set metric tag names that is optional for Metric
*/
public PerfQuantile(final String name,
int maxTracked,
String... labelNames) {
super(name, labelNames);
if (maxTracked <= 0) {
throw new IllegalArgumentException(
"maxTracked value must be positive.");
}
this.maxTracked = maxTracked;
initializeNoLabelsElement();
}
/**
* {@link Metric.Element} for {@link PerfQuantile}.
*/
public static final class Element extends RateMetric.Element {
/*
* The min, max, 95% and 99% values will be 0 if if there is no
* observed value.
*/
private static final int DEFAULT_VAL = 0;
private final int maxTracked;
//TODO change to LongAdder if this package move to JAVA 8.
private final AtomicLong totalRequests;
private final AtomicLong overflowRequests;
private final AtomicLongArray cumulativeCounts;
private final Max overallMax;
private final ConcurrentHashMap watchers;
private Element(int maxTracked) {
this.maxTracked = maxTracked;
totalRequests = new AtomicLong();
overflowRequests = new AtomicLong();
cumulativeCounts = new AtomicLongArray(maxTracked + 1);
overallMax = new Max();
watchers = new ConcurrentHashMap();
}
/**
* Round Latency from nanosecond to millisecond and observe millisecond
* value
* TODO move it to an utility class as a static method?
*/
public void observeNanoLatency(long nanoLatency, int times) {
if (nanoLatency < 0) {
return;
}
/* Round the latency to determine where to mark the histogram. */
final int millisRounded =
(int) ((nanoLatency + (1000000L / 2)) / 1000000L);
observe(millisRounded, times);
}
/**
* Observe the given value.
*/
public void observe(int val) {
observe(val, 1);
}
/**
* Observe the given value with N times.
*/
public void observe(int val, int times) {
if (val < 0) {
return;
}
totalRequests.incrementAndGet();
if (val < maxTracked) {
cumulativeCounts.addAndGet(val, times);
} else {
cumulativeCounts.addAndGet(maxTracked, times);
overflowRequests.incrementAndGet();
}
updateMax(val);
}
/*
* Update overall max and each watcher interval max.
*/
private void updateMax(int val) {
overallMax.updateMax(val);
for (HistoryItem item : watchers.values()) {
item.max.updateMax(val);
}
}
/*
* Calculate 95th/99th from histogramCounts
*/
private RateResult calculate(long duration,
long requestCount,
long overflowCount,
long operationCount,
long[] histogramCounts,
int max) {
int percent95 = DEFAULT_VAL;
int percent99 = DEFAULT_VAL;
int min = -1;
final long percent95Count;
final long percent99Count;
if (operationCount == 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) (operationCount * .95);
percent99Count = (int) (operationCount * .99);
}
long numRequestsSeen = 0;
long totalLatency = 0;
for (int latency = 0; latency < histogramCounts.length; latency++) {
if (histogramCounts[latency] == 0) {
continue;
}
if (numRequestsSeen < percent95Count) {
percent95 = latency;
}
if (numRequestsSeen < percent99Count) {
percent99 = latency;
}
if (min == -1) {
min = latency;
}
while (max < latency) {
max = latency;
}
numRequestsSeen += histogramCounts[latency];
totalLatency += histogramCounts[latency] * latency;
}
if (min == -1) {
min = DEFAULT_VAL;
}
return new RateResult(maxTracked, duration, requestCount,
operationCount, overflowCount,
min, max, percent95, percent99, totalLatency);
}
/**
* {@inheritDoc}
* Updating different fields are not an atomic operation, so they will
* be a bit inconsistent when calculating rate and observing new value
* concurrently. This tradeoff is made in order to avoid the cost of
* synchronization.
*/
@Override
public RateResult rate() {
final long[] intervalCounts = new long[maxTracked + 1];
long intervalTotalCount = 0;
for (int i = 0; i < intervalCounts.length; i++) {
intervalCounts[i] = cumulativeCounts.get(i);
intervalTotalCount += intervalCounts[i];
}
return calculate(System.nanoTime() - initialTime,
totalRequests.get(), overflowRequests.get(),
intervalTotalCount, intervalCounts,
overallMax.val);
}
/**
* {@inheritDoc}
* Updating different fields are not an atomic operation, so they will
* be a bit inconsistent when calculating rate and observing new value
* concurrently. This tradeoff is made in order to avoid the cost of
* synchronization.
*/
@Override
public RateResult rateSinceLastTime(String watcherName) {
final long currentTime = System.nanoTime();
final long currentTotalRequests = totalRequests.get();
final long currentOverflowRequests = overflowRequests.get();
long lastTime = initialTime;
long lastRequestCount = 0;
long lastOverflowCount = 0;
long[] lastHistogramCounts;
int max = DEFAULT_VAL;
final HistoryItem historyItem = watchers.get(watcherName);
if (historyItem != null) {
lastTime = historyItem.lastTime;
lastHistogramCounts = historyItem.lastHistogramCounts;
lastRequestCount = historyItem.lastRequestCount;
lastOverflowCount = historyItem.lastOverflowCount;
max = historyItem.max.val;
historyItem.lastTime = currentTime;
historyItem.lastRequestCount = currentTotalRequests;
historyItem.lastOverflowCount = currentOverflowRequests;
historyItem.max.reset();
} else {
lastHistogramCounts = new long[maxTracked + 1];
watchers.put(watcherName,
new HistoryItem(currentTime,
currentTotalRequests,
currentOverflowRequests,
lastHistogramCounts));
}
final long[] intervalCounts = new long[maxTracked + 1];
long cumulativeICount = 0;
long intervalTotalCount = 0;
for (int i = 0; i < intervalCounts.length; i++) {
cumulativeICount = cumulativeCounts.get(i);
intervalCounts[i] = cumulativeICount - lastHistogramCounts[i];
lastHistogramCounts[i] = cumulativeICount;
intervalTotalCount += intervalCounts[i];
}
return calculate(currentTime - lastTime,
currentTotalRequests - lastRequestCount,
currentOverflowRequests - lastOverflowCount,
intervalTotalCount, intervalCounts,
max);
}
/* A structure to record PerfQuantile history item for each watcher */
private static final class HistoryItem {
private long lastTime;
private long lastRequestCount;
private long lastOverflowCount;
private long[] lastHistogramCounts;
private Max max;
private HistoryItem(long time,
long requestCount,
long overflowCount,
long[] histogramCounts) {
lastTime = time;
lastRequestCount = requestCount;
lastOverflowCount = overflowCount;
lastHistogramCounts = histogramCounts;
max = new Max();
}
}
private static final class Max {
private volatile int val;
private Max() {
reset();
}
private void reset() {
val = DEFAULT_VAL;
}
private void updateMax(int next) {
/*
* Update the max if necessary. This is not atomic, so we loop
* to account for lost updates.
*/
while (val < next) {
val = next;
}
}
}
}
/**
* {@inheritDoc}
*/
@Override
protected Element newElement() {
return new Element(maxTracked);
}
/**
* Saving collected {@link PerfQuantile} rate change result.
*/
public static class RateResult extends RateMetric.RateResult {
private static final long serialVersionUID = 1L;
private final int maxTracked;
private final long requestCount;
private final long operationCount;
private final long overflowCount;
private final int min;
private final int max;
private final int percent95;
private final int percent99;
private final long totalLatency;
public RateResult(int maxTracked,
long duration,
long requestCount,
long operationCount,
long overflowCount,
int min,
int max,
int percent95,
int percent99,
long totalLatency) {
super(duration);
this.maxTracked = maxTracked;
this.requestCount = requestCount;
this.operationCount = operationCount;
this.overflowCount = overflowCount;
this.min = min;
this.max = max;
this.percent95 = percent95;
this.percent99 = percent99;
this.totalLatency = totalLatency;
}
public long getRequestCount() {
return requestCount;
}
public long getOperationCount() {
return operationCount;
}
public long getOverflowCount() {
return overflowCount;
}
public int getMin() {
return min;
}
public int getMax() {
return max;
}
public int get95th() {
return percent95;
}
public int get99th() {
return percent99;
}
public float getAvgMs() {
if (operationCount == 0) {
return 0;
}
return totalLatency / (float) operationCount;
}
public Latency getLatency() {
final Latency latency = new Latency(maxTracked, min, max,
getAvgMs(),
(int) operationCount,
(int) requestCount,
percent95, percent99,
(int) overflowCount);
return latency;
}
@Override
public Map toMap() {
final Map map = super.toMap();
map.put(REQUEST_COUNT_NAME, requestCount);
map.put(OPERATION_COUNT_NAME, operationCount);
map.put(OVERFLOW_COUNT_NAME, overflowCount);
map.put(OPERATION_MIN_NAME, min);
map.put(OPERATION_MAX_NAME, max);
map.put(OPERATION_AVG_NAME, getAvgMs());
map.put(QUANTILE_NAME + DELIMITER + "0.95", percent95);
map.put(QUANTILE_NAME + DELIMITER + "0.99", percent99);
return map;
}
}
// Convenience methods.
/**
* {@link Element#observe(int)}
* Note: as a convenience method, it only works for no label PerfQuantile.
*/
public void observe(int val) {
noLabelsElement.observe(val);
}
/**
* {@link Element#observe(int, int)}
* Note: as a convenience method, it only works for no label PerfQuantile.
*/
public void observe(int val, int times) {
noLabelsElement.observe(val, times);
}
/**
* {@link Element#observeNanoLatency(long, int)}
* Note: as a convenience method, it only works for no label PerfQuantile.
*/
public void observeNanoLatency(long nanoLatency, int times) {
noLabelsElement.observeNanoLatency(nanoLatency, times);
}
/**
* {@inheritDoc}
*/
@Override
public MetricFamilySamples collect() {
return collect(Type.PERF_QUANTILE);
}
/**
* {@inheritDoc}
*/
@Override
public MetricFamilySamples
collectSinceLastTime(String watcherName) {
return collectSinceLastTime(Type.PERF_QUANTILE, watcherName);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy