// Copyright 2016 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package com.google.pubsub.flic.common;
import com.google.common.base.Preconditions;
import java.util.Arrays;
import java.util.List;
import java.util.stream.LongStream;
import org.apache.commons.lang3.ArrayUtils;
/**
* Takes latency measurements and stores them in buckets for more efficient storage, along with
* utilities to calculate percentiles for analysis of results.
*/
public class LatencyDistribution {
public static final double[] LATENCY_BUCKETS = {
0.0,
1.0,
5.0,
10.0,
20.0,
40.0,
60.0,
80.0,
100.0,
150.0,
200.0,
250.0,
300.0,
400.0,
500.0,
600.0,
700.0,
800.0,
900.0,
1000.0,
1500.0,
2000.0,
5000.0,
10000.0,
50000.0,
100000.0,
500000.0,
1000000.0,
10000000.0,
100000000.0,
1000000000.0,
Integer.MAX_VALUE
};
private final long[] bucketValues = new long[LATENCY_BUCKETS.length];
private long count = 0;
private double mean = 0;
private double sumOfSquaredDeviation = 0;
public LatencyDistribution() {
}
private static int getNthPercentileIndex(long[] bucketValues, double percentile) {
Preconditions.checkArgument(percentile > 0.0);
Preconditions.checkArgument(percentile < 100.0);
long total = LongStream.of(bucketValues).sum();
if (total == 0) {
return 0;
}
long count = (long) (total * percentile / 100.0);
for (int i = LATENCY_BUCKETS.length - 1; i > 0; i--) {
total -= bucketValues[i];
if (total < count) {
return i;
}
}
return -1;
}
public static double getNthPercentileUpperBound(long[] bucketValues, double percentile) {
return LATENCY_BUCKETS[Math.max(0, getNthPercentileIndex(bucketValues, percentile))];
}
public static String getNthPercentile(long[] bucketValues, double percentile) {
int index = getNthPercentileIndex(bucketValues, percentile);
if (index < 1) {
return "N/A";
}
return LATENCY_BUCKETS[index - 1] + " - " + LATENCY_BUCKETS[index];
}
public static String getNthPercentileMidpoint(long[] bucketValues, double percentile) {
int index = getNthPercentileIndex(bucketValues, percentile);
if (index < 1) {
return "N/A";
}
return Double.toString((LATENCY_BUCKETS[index - 1] + LATENCY_BUCKETS[index]) / 2);
}
public synchronized void reset() {
for (int i = 0; i < LATENCY_BUCKETS.length; i++) {
bucketValues[i] = 0;
}
count = 0;
mean = 0;
sumOfSquaredDeviation = 0;
}
public long getCount() {
return count;
}
public double getSumOfSquareDeviations() {
return sumOfSquaredDeviation;
}
public double getMean() {
return mean;
}
long[] getBucketValues() {
return bucketValues;
}
public synchronized LatencyDistribution copy() {
LatencyDistribution latencyDistribution = new LatencyDistribution();
latencyDistribution.count = count;
latencyDistribution.mean = mean;
latencyDistribution.sumOfSquaredDeviation = sumOfSquaredDeviation;
System.arraycopy(bucketValues, 0, latencyDistribution.bucketValues, 0, LATENCY_BUCKETS.length);
return latencyDistribution;
}
public List<Long> getBucketValuesAsList() {
return Arrays.asList(ArrayUtils.toObject(bucketValues));
}
public void recordLatency(long latencyMs) {
synchronized (this) {
count++;
double dev = latencyMs - mean;
mean += dev / count;
sumOfSquaredDeviation += dev * (latencyMs - mean);
}
for (int i = 0; i < LATENCY_BUCKETS.length; i++) {
double bucket = LATENCY_BUCKETS[i];
if (latencyMs < bucket) {
synchronized (this) {
bucketValues[i]++;
}
return;
}
}
synchronized (this) {
bucketValues[LATENCY_BUCKETS.length - 1]++;
}
}
public void recordBatchLatency(long latencyMs, int batchSize) {
if (batchSize < 1) {
return;
}
synchronized (this) {
double dev = latencyMs - mean;
mean = (mean * count + latencyMs * batchSize) / (count + batchSize);
count += batchSize;
sumOfSquaredDeviation += dev * (latencyMs - mean) * batchSize;
}
for (int i = 0; i < LATENCY_BUCKETS.length; i++) {
double bucket = LATENCY_BUCKETS[i];
if (latencyMs < bucket) {
synchronized (this) {
bucketValues[i] += batchSize;
}
return;
}
}
synchronized (this) {
bucketValues[LATENCY_BUCKETS.length - 1] += batchSize;
}
}
}