| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 org.apache.hadoop.metrics2.lib; |
| |
| import static org.apache.hadoop.metrics2.lib.Interns.info; |
| |
| import java.util.Map; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.ScheduledExecutorService; |
| import java.util.concurrent.TimeUnit; |
| |
| import org.apache.commons.lang.StringUtils; |
| import org.apache.hadoop.classification.InterfaceAudience; |
| import org.apache.hadoop.classification.InterfaceStability; |
| import org.apache.hadoop.metrics2.MetricsInfo; |
| import org.apache.hadoop.metrics2.MetricsRecordBuilder; |
| import org.apache.hadoop.metrics2.util.Quantile; |
| import org.apache.hadoop.metrics2.util.SampleQuantiles; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.util.concurrent.ThreadFactoryBuilder; |
| |
| /** |
| * Watches a stream of long values, maintaining online estimates of specific |
| * quantiles with provably low error bounds. This is particularly useful for |
| * accurate high-percentile (e.g. 95th, 99th) latency metrics. |
| */ |
| @InterfaceAudience.Public |
| @InterfaceStability.Evolving |
| public class MutableQuantiles extends MutableMetric { |
| |
| @VisibleForTesting |
| public static final Quantile[] quantiles = { new Quantile(0.50, 0.050), |
| new Quantile(0.75, 0.025), new Quantile(0.90, 0.010), |
| new Quantile(0.95, 0.005), new Quantile(0.99, 0.001) }; |
| |
| private final MetricsInfo numInfo; |
| private final MetricsInfo[] quantileInfos; |
| private final int interval; |
| |
| private SampleQuantiles estimator; |
| private long previousCount = 0; |
| |
| @VisibleForTesting |
| protected Map<Quantile, Long> previousSnapshot = null; |
| |
| private static final ScheduledExecutorService scheduler = Executors |
| .newScheduledThreadPool(1, new ThreadFactoryBuilder().setDaemon(true) |
| .setNameFormat("MutableQuantiles-%d").build()); |
| |
| /** |
| * Instantiates a new {@link MutableQuantiles} for a metric that rolls itself |
| * over on the specified time interval. |
| * |
| * @param name |
| * of the metric |
| * @param description |
| * long-form textual description of the metric |
| * @param sampleName |
| * type of items in the stream (e.g., "Ops") |
| * @param valueName |
| * type of the values |
| * @param interval |
| * rollover interval (in seconds) of the estimator |
| */ |
| public MutableQuantiles(String name, String description, String sampleName, |
| String valueName, int interval) { |
| String ucName = StringUtils.capitalize(name); |
| String usName = StringUtils.capitalize(sampleName); |
| String uvName = StringUtils.capitalize(valueName); |
| String desc = StringUtils.uncapitalize(description); |
| String lsName = StringUtils.uncapitalize(sampleName); |
| String lvName = StringUtils.uncapitalize(valueName); |
| |
| numInfo = info(ucName + "Num" + usName, String.format( |
| "Number of %s for %s with %ds interval", lsName, desc, interval)); |
| // Construct the MetricsInfos for the quantiles, converting to percentiles |
| quantileInfos = new MetricsInfo[quantiles.length]; |
| String nameTemplate = ucName + "%dthPercentile" + uvName; |
| String descTemplate = "%d percentile " + lvName + " with " + interval |
| + " second interval for " + desc; |
| for (int i = 0; i < quantiles.length; i++) { |
| int percentile = (int) (100 * quantiles[i].quantile); |
| quantileInfos[i] = info(String.format(nameTemplate, percentile), |
| String.format(descTemplate, percentile)); |
| } |
| |
| estimator = new SampleQuantiles(quantiles); |
| |
| this.interval = interval; |
| scheduler.scheduleAtFixedRate(new RolloverSample(this), interval, interval, |
| TimeUnit.SECONDS); |
| } |
| |
| @Override |
| public synchronized void snapshot(MetricsRecordBuilder builder, boolean all) { |
| if (all || changed()) { |
| builder.addGauge(numInfo, previousCount); |
| for (int i = 0; i < quantiles.length; i++) { |
| long newValue = 0; |
| // If snapshot is null, we failed to update since the window was empty |
| if (previousSnapshot != null) { |
| newValue = previousSnapshot.get(quantiles[i]); |
| } |
| builder.addGauge(quantileInfos[i], newValue); |
| } |
| if (changed()) { |
| clearChanged(); |
| } |
| } |
| } |
| |
| public synchronized void add(long value) { |
| estimator.insert(value); |
| } |
| |
| public int getInterval() { |
| return interval; |
| } |
| |
| /** |
| * Runnable used to periodically roll over the internal |
| * {@link SampleQuantiles} every interval. |
| */ |
| private static class RolloverSample implements Runnable { |
| |
| MutableQuantiles parent; |
| |
| public RolloverSample(MutableQuantiles parent) { |
| this.parent = parent; |
| } |
| |
| @Override |
| public void run() { |
| synchronized (parent) { |
| parent.previousCount = parent.estimator.getCount(); |
| parent.previousSnapshot = parent.estimator.snapshot(); |
| parent.estimator.clear(); |
| } |
| parent.setChanged(); |
| } |
| |
| } |
| } |