001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 package org.apache.hadoop.metrics2.lib; 020 021 import static org.apache.hadoop.metrics2.lib.Interns.info; 022 023 import java.io.IOException; 024 import java.util.Map; 025 import java.util.concurrent.Executors; 026 import java.util.concurrent.ScheduledExecutorService; 027 import java.util.concurrent.TimeUnit; 028 029 import org.apache.commons.lang.StringUtils; 030 import org.apache.hadoop.classification.InterfaceAudience; 031 import org.apache.hadoop.classification.InterfaceStability; 032 import org.apache.hadoop.metrics2.MetricsInfo; 033 import org.apache.hadoop.metrics2.MetricsRecordBuilder; 034 import org.apache.hadoop.metrics2.util.Quantile; 035 import org.apache.hadoop.metrics2.util.SampleQuantiles; 036 037 import com.google.common.annotations.VisibleForTesting; 038 039 /** 040 * Watches a stream of long values, maintaining online estimates of specific 041 * quantiles with provably low error bounds. This is particularly useful for 042 * accurate high-percentile (e.g. 95th, 99th) latency metrics. 043 */ 044 @InterfaceAudience.Public 045 @InterfaceStability.Evolving 046 public class MutableQuantiles extends MutableMetric { 047 048 @VisibleForTesting 049 public static final Quantile[] quantiles = { new Quantile(0.50, 0.050), 050 new Quantile(0.75, 0.025), new Quantile(0.90, 0.010), 051 new Quantile(0.95, 0.005), new Quantile(0.99, 0.001) }; 052 053 private final MetricsInfo numInfo; 054 private final MetricsInfo[] quantileInfos; 055 private final int interval; 056 057 private SampleQuantiles estimator; 058 private long previousCount = 0; 059 060 @VisibleForTesting 061 protected Map<Quantile, Long> previousSnapshot = null; 062 063 private final ScheduledExecutorService scheduler = Executors 064 .newScheduledThreadPool(1); 065 066 /** 067 * Instantiates a new {@link MutableQuantiles} for a metric that rolls itself 068 * over on the specified time interval. 069 * 070 * @param name 071 * of the metric 072 * @param description 073 * long-form textual description of the metric 074 * @param sampleName 075 * type of items in the stream (e.g., "Ops") 076 * @param valueName 077 * type of the values 078 * @param interval 079 * rollover interval (in seconds) of the estimator 080 */ 081 public MutableQuantiles(String name, String description, String sampleName, 082 String valueName, int interval) { 083 String ucName = StringUtils.capitalize(name); 084 String usName = StringUtils.capitalize(sampleName); 085 String uvName = StringUtils.capitalize(valueName); 086 String desc = StringUtils.uncapitalize(description); 087 String lsName = StringUtils.uncapitalize(sampleName); 088 String lvName = StringUtils.uncapitalize(valueName); 089 090 numInfo = info(ucName + "Num" + usName, String.format( 091 "Number of %s for %s with %ds interval", lsName, desc, interval)); 092 // Construct the MetricsInfos for the quantiles, converting to percentiles 093 quantileInfos = new MetricsInfo[quantiles.length]; 094 String nameTemplate = ucName + "%dthPercentile" + uvName; 095 String descTemplate = "%d percentile " + lvName + " with " + interval 096 + " second interval for " + desc; 097 for (int i = 0; i < quantiles.length; i++) { 098 int percentile = (int) (100 * quantiles[i].quantile); 099 quantileInfos[i] = info(String.format(nameTemplate, percentile), 100 String.format(descTemplate, percentile)); 101 } 102 103 estimator = new SampleQuantiles(quantiles); 104 105 this.interval = interval; 106 scheduler.scheduleAtFixedRate(new RolloverSample(this), interval, interval, 107 TimeUnit.SECONDS); 108 } 109 110 @Override 111 public synchronized void snapshot(MetricsRecordBuilder builder, boolean all) { 112 if (all || changed()) { 113 builder.addGauge(numInfo, previousCount); 114 for (int i = 0; i < quantiles.length; i++) { 115 long newValue = 0; 116 // If snapshot is null, we failed to update since the window was empty 117 if (previousSnapshot != null) { 118 newValue = previousSnapshot.get(quantiles[i]); 119 } 120 builder.addGauge(quantileInfos[i], newValue); 121 } 122 if (changed()) { 123 clearChanged(); 124 } 125 } 126 } 127 128 public synchronized void add(long value) { 129 estimator.insert(value); 130 } 131 132 public int getInterval() { 133 return interval; 134 } 135 136 /** 137 * Runnable used to periodically roll over the internal 138 * {@link SampleQuantiles} every interval. 139 */ 140 private static class RolloverSample implements Runnable { 141 142 MutableQuantiles parent; 143 144 public RolloverSample(MutableQuantiles parent) { 145 this.parent = parent; 146 } 147 148 @Override 149 public void run() { 150 synchronized (parent) { 151 try { 152 parent.previousCount = parent.estimator.getCount(); 153 parent.previousSnapshot = parent.estimator.snapshot(); 154 } catch (IOException e) { 155 // Couldn't get a new snapshot because the window was empty 156 parent.previousCount = 0; 157 parent.previousSnapshot = null; 158 } 159 parent.estimator.clear(); 160 } 161 parent.setChanged(); 162 } 163 164 } 165 }