/*
* Copyright (C) 2012 Facebook, 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.facebook.stats.mx;
import com.facebook.stats.EventCounterIf;
import com.facebook.stats.MultiWindowDistribution;
import com.facebook.stats.MultiWindowRate;
import com.facebook.stats.MultiWindowSpread;
import com.facebook.stats.QuantileDigest;
import com.facebook.stats.ReadableMultiWindowCounter;
import com.facebook.stats.ReadableMultiWindowGauge;
import com.facebook.stats.ReadableMultiWindowRate;
import com.google.common.collect.ImmutableList;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.joda.time.ReadableDateTime;
import java.util.List;
import java.util.Map;
/**
* Helper methods for converting stat objects into key/value pairs that conform to standing
* naming conventions around <type>.<time_window>
*/
public class StatsUtil {
/**
* @deprecated replaced by addRateAndSumToCounters
*/
@Deprecated
public static void adddKeyToCounters(
String baseKey, ReadableMultiWindowRate rate, Map<String, Long> counterMap
) {
addRateToCounters(baseKey, rate, counterMap);
addSumToCounters(baseKey, rate, counterMap);
}
public static void addRateAndSumToCounters(
String baseKey, ReadableMultiWindowRate rate, Map<String, Long> counterMap
) {
addRateToCounters(baseKey, rate, counterMap);
addSumToCounters(baseKey, rate, counterMap);
}
public static void addRateToCounters(
String baseKey, ReadableMultiWindowRate rate, Map<String, Long> counterMap
) {
counterMap.put(baseKey + ".rate", rate.getAllTimeRate());
counterMap.put(baseKey + ".rate.3600", rate.getHourRate());
counterMap.put(baseKey + ".rate.600", rate.getTenMinuteRate());
counterMap.put(baseKey + ".rate.60", rate.getMinuteRate());
}
public static void addSumToCounters(
String baseKey, ReadableMultiWindowRate rate, Map<String, Long> counterMap
) {
counterMap.put(baseKey + ".sum", rate.getAllTimeSum());
counterMap.put(baseKey + ".sum.3600", rate.getHourSum());
counterMap.put(baseKey + ".sum.600", rate.getTenMinuteSum());
counterMap.put(baseKey + ".sum.60", rate.getMinuteSum());
}
public static void addGaugeAvgToCounters(
String baseKey, ReadableMultiWindowGauge gauge, Map<String, Long> counterMap
) {
counterMap.put(baseKey + ".avg", gauge.getAllTimeAvg());
counterMap.put(baseKey + ".avg.3600", gauge.getHourAvg());
counterMap.put(baseKey + ".avg.600", gauge.getTenMinuteAvg());
counterMap.put(baseKey + ".avg.60", gauge.getMinuteAvg());
}
public static void addGaugeSamplesToCounters(
String baseKey, ReadableMultiWindowGauge gauge, Map<String, Long> counterMap
) {
counterMap.put(baseKey + ".samples", gauge.getAllTimeSamples());
counterMap.put(baseKey + ".samples.3600", gauge.getHourSamples());
counterMap.put(baseKey + ".samples.600", gauge.getTenMinuteSamples());
counterMap.put(baseKey + ".samples.60", gauge.getMinuteSamples());
}
/**
* internal helper method
*
* @param baseKey base baseKey name, adds standard time ranges ".60", ".3600", ...
* @param counter the counter to add
* @param counterMap map of counters
*/
private static void addValueToCounters(
String baseKey, ReadableMultiWindowCounter counter, Map<String, Long> counterMap
) {
counterMap.put(baseKey, counter.getAllTimeValue());
counterMap.put(baseKey + ".3600", counter.getHourValue());
counterMap.put(baseKey + ".600", counter.getTenMinuteValue());
counterMap.put(baseKey + ".60", counter.getMinuteValue());
}
public static void addSpreadToCounters(
String baseKey, MultiWindowSpread spread, Map<String, Long> counterMap
) {
addValueToCounters(baseKey + ".min", spread.getMin(), counterMap);
addValueToCounters(baseKey + ".max", spread.getMax(), counterMap);
addGaugeAvgToCounters(baseKey, spread.getGauge(), counterMap);
addGaugeSamplesToCounters(baseKey, spread.getGauge(), counterMap);
}
public static void addQuantileToCounters(
String baseKey, MultiWindowDistribution quantiles, Map<String, Long> counterMap
) {
addQuantilesToCounters(baseKey, ".60", counterMap, quantiles.getOneMinuteQuantiles());
addQuantilesToCounters(baseKey, ".600", counterMap, quantiles.getTenMinuteQuantiles());
addQuantilesToCounters(baseKey, ".3600", counterMap, quantiles.getOneHourQuantiles());
addQuantilesToCounters(baseKey, "", counterMap, quantiles.getAllTimeQuantiles());
}
public static void addHistogramToExportedValues(
String baseKey, MultiWindowDistribution quantiles, Map<String, String> values
) {
addHistogramToExportedValues(baseKey, ".60", values, quantiles.getOneMinute());
addHistogramToExportedValues(baseKey, ".600", values, quantiles.getTenMinutes());
addHistogramToExportedValues(baseKey, ".3600", values, quantiles.getOneHour());
addHistogramToExportedValues(baseKey, "", values, quantiles.getAllTime());
}
private static void addHistogramToExportedValues(
String baseKey, String windowKey, Map<String, String> values, QuantileDigest digest
) {
values.put(baseKey + ".hist" + windowKey, serializeHistogram(digest));
}
private static String serializeHistogram(QuantileDigest digest) {
int buckets = 100;
long min = digest.getMin();
long max = digest.getMax();
long bucketSize = (max - min + buckets) / buckets;
ImmutableList.Builder<Long> boundaryBuilder = ImmutableList.builder();
for (int i = 1; i < buckets + 1; ++i) {
boundaryBuilder.add(min + bucketSize * i);
}
ImmutableList<Long> boundaries = boundaryBuilder.build();
List<QuantileDigest.Bucket> counts = digest.getHistogram(boundaries);
StringBuilder builder = new StringBuilder();
// add bogus bucket (fb303 ui ignores the first one, for whatever reason)
builder.append("-1:0:0,");
for (int i = 0; i < boundaries.size(); ++i) {
long lowBoundary = min;
if (i > 0) {
lowBoundary = boundaries.get(i - 1);
}
builder.append(lowBoundary)
.append(':')
.append(Math.round(counts.get(i).getCount()))
.append(':')
.append(Math.round(counts.get(i).getMean()))
.append(',');
}
// add a final bucket so that fb303 ui shows the max value
builder.append(max);
builder.append(":0:0");
return builder.toString();
}
private static void addQuantilesToCounters(
String baseKey,
String windowKey,
Map<String, Long> counters,
Map<MultiWindowDistribution.Quantile, Long> oneMinuteQuantiles
) {
for (Map.Entry<MultiWindowDistribution.Quantile, Long> entry : oneMinuteQuantiles.entrySet()) {
counters.put(baseKey + "." + entry.getKey().getKey() + windowKey, entry.getValue());
}
}
public static void setAllTimeSum(MultiWindowRate rate, long value) {
long old = rate.getAllTimeSum();
if(old > value) {
throw new IllegalArgumentException("MultiWindowRate counters can not decrement their allTimeSum (" + old + " > " + value + ")");
}
if(old == value) {
return;
}
long delta = value - old;
rate.add(delta);
}
public static Duration extentOf(EventCounterIf counter1, EventCounterIf counter2) {
ReadableDateTime start = counter1.getStart();
ReadableDateTime end = counter1.getEnd();
if (counter2.getStart().isBefore(start)) {
start = counter2.getStart();
}
if (counter2.getEnd().isAfter(end)) {
end = counter2.getEnd();
}
return new Duration(start, end);
}
/**
* helper method that will set the value of a counter
*/
public static long setCounterValue(StatType statType, long value, StatsCollector stats) {
long oldValue = StatsUtil.setCounterValue(statType.getKey(), value, stats);
return oldValue ;
}
/**
* helper method that will set the value of a counter
*/
public static long setCounterValue(String key, long value, StatsCollector stats) {
long oldValue = stats.resetCounter(key);
stats.incrementCounter(key, value);
return oldValue;
}
}