/* * Copyright 2016 Google Inc. All Rights Reserved. * * 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.cloud.pubsub; import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects.ToStringHelper; import com.google.common.base.Preconditions; import java.util.concurrent.atomic.AtomicLong; /** * Takes measurements and stores them in linear buckets from 0 to totalBuckets - 1, along with * utilities to calculate percentiles for analysis of results. */ public class Distribution { private final AtomicLong[] bucketCounts; private long count; private double mean; private double sumOfSquaredDeviation; public Distribution(int totalBuckets) { Preconditions.checkArgument(totalBuckets > 0); bucketCounts = new AtomicLong[totalBuckets]; for (int i = 0; i < totalBuckets; ++i) { bucketCounts[i] = new AtomicLong(); } } /** * Get the bucket that records values up to the given percentile. */ public long getNthPercentile(double percentile) { Preconditions.checkArgument(percentile > 0.0); Preconditions.checkArgument(percentile <= 100.0); long[] bucketCounts = getBucketCounts(); long total = 0; for (long count : bucketCounts) { total += count; } if (total == 0) { return 0; } long count = (long) Math.ceil(total * percentile / 100.0); for (int i = 0; i < bucketCounts.length; i++) { count -= bucketCounts[i]; if (count <= 0) { return i; } } return 0; } /** * Resets (sets to 0) the recorded values. */ public synchronized void reset() { for (AtomicLong element : bucketCounts) { element.set(0); } count = 0; mean = 0; sumOfSquaredDeviation = 0; } /** * Numbers of values recorded. */ public long getCount() { return count; } /** * Square deviations of the recorded values. */ public double getSumOfSquareDeviations() { return sumOfSquaredDeviation; } /** * Mean of the recorded values. */ public double getMean() { return mean; } /** * Gets the accumulated count of every bucket of the distribution. */ public long[] getBucketCounts() { long[] counts = new long[bucketCounts.length]; for (int i = 0; i < counts.length; i++) { counts[i] = bucketCounts[i].longValue(); } return counts; } /** * Make a copy of the distribution. */ public synchronized Distribution copy() { Distribution distributionCopy = new Distribution(bucketCounts.length); distributionCopy.count = count; distributionCopy.mean = mean; distributionCopy.sumOfSquaredDeviation = sumOfSquaredDeviation; System.arraycopy(bucketCounts, 0, distributionCopy.bucketCounts, 0, bucketCounts.length); return distributionCopy; } /** * Record a new value. */ public void record(int bucket) { Preconditions.checkArgument(bucket >= 0); synchronized (this) { count++; double dev = bucket - mean; mean += dev / count; sumOfSquaredDeviation += dev * (bucket - mean); } if (bucket >= bucketCounts.length) { // Account for bucket overflow, records everything that is equals or greater of the last // bucket. bucketCounts[bucketCounts.length - 1].incrementAndGet(); return; } bucketCounts[bucket].incrementAndGet(); } @Override public String toString() { ToStringHelper helper = MoreObjects.toStringHelper(Distribution.class); helper.add("bucketCounts", bucketCounts); helper.add("count", count); helper.add("mean", mean); helper.add("sumOfSquaredDeviation", sumOfSquaredDeviation); return helper.toString(); } }