View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */ 
18  
19  package org.apache.hadoop.hbase.metrics.histogram;
20  
21  import java.util.concurrent.atomic.AtomicLong;
22  import java.util.concurrent.atomic.AtomicReference;
23  
24  import org.apache.hadoop.metrics.MetricsRecord;
25  import org.apache.hadoop.metrics.util.MetricsBase;
26  import org.apache.hadoop.metrics.util.MetricsRegistry;
27  
28  import com.yammer.metrics.stats.Sample;
29  import com.yammer.metrics.stats.Snapshot;
30  import com.yammer.metrics.stats.UniformSample;
31  import com.yammer.metrics.stats.ExponentiallyDecayingSample;
32  
33  public class MetricsHistogram extends MetricsBase {
34    
35    // 1028 items implies 99.9% CI w/ 5% margin of error 
36    // (assuming a normal distribution on the underlying data)
37    private static final int DEFAULT_SAMPLE_SIZE = 1028;
38  
39    // the bias towards sampling from more recent data. 
40    // Per Cormode et al. an alpha of 0.015 strongly biases to the last 5 minutes
41    private static final double DEFAULT_ALPHA = 0.015;
42    public static final String NUM_OPS_METRIC_NAME = "_num_ops";
43    public static final String MIN_METRIC_NAME = "_min";
44    public static final String MAX_METRIC_NAME = "_max";
45    public static final String MEAN_METRIC_NAME = "_mean";
46    public static final String STD_DEV_METRIC_NAME = "_std_dev";
47    public static final String MEDIAN_METRIC_NAME = "_median";
48    public static final String SEVENTY_FIFTH_PERCENTILE_METRIC_NAME = "_75th_percentile";
49    public static final String NINETY_FIFTH_PERCENTILE_METRIC_NAME = "_95th_percentile";
50    public static final String NINETY_NINETH_PERCENTILE_METRIC_NAME = "_99th_percentile";
51  
52    /**
53     * Constructor to create a new histogram metric
54     * @param nam           the name to publish the metric under
55     * @param registry      where the metrics object will be registered
56     * @param description   the metric's description
57     * @param forwardBiased true if you want this histogram to give more 
58     *                      weight to recent data, 
59     *                      false if you want all data to have uniform weight
60     */
61    public MetricsHistogram(final String nam, final MetricsRegistry registry, 
62        final String description, boolean forwardBiased) {
63      super(nam, description);
64  
65      this.min = new AtomicLong();
66      this.max = new AtomicLong();
67      this.sum = new AtomicLong();
68      this.sample = forwardBiased ? 
69          new ExponentiallyDecayingSample(DEFAULT_SAMPLE_SIZE, DEFAULT_ALPHA) 
70      : new UniformSample(DEFAULT_SAMPLE_SIZE);
71  
72      this.variance =  new AtomicReference<double[]>(new double[]{-1, 0});
73      this.count = new AtomicLong();
74  
75      this.clear();
76  
77      if (registry != null) {
78        registry.add(nam, this);      
79      }
80    }
81  
82    /**
83     * Constructor create a new (forward biased) histogram metric
84     * @param nam         the name to publish the metric under
85     * @param registry    where the metrics object will be registered
86     * @param description the metric's description
87     */
88    public MetricsHistogram(final String nam, MetricsRegistry registry, 
89        final String description) {
90      this(nam, registry, NO_DESCRIPTION, true);
91    }
92      
93    /**
94     * Constructor - create a new (forward biased) histogram metric
95     * @param nam the name of the metrics to be used to publish the metric
96     * @param registry - where the metrics object will be registered
97     */
98    public MetricsHistogram(final String nam, MetricsRegistry registry) {
99      this(nam, registry, NO_DESCRIPTION);
100   }
101 
102   private final Sample sample;
103   private final AtomicLong min;
104   private final AtomicLong max;
105   private final AtomicLong sum;
106 
107   // these are for computing a running-variance, 
108   // without letting floating point errors accumulate via Welford's algorithm
109   private final AtomicReference<double[]> variance;
110   private final AtomicLong count;
111 
112   /**
113    * Clears all recorded values.
114    */
115   public void clear() {
116     this.sample.clear();
117     this.count.set(0);
118     this.max.set(Long.MIN_VALUE);
119     this.min.set(Long.MAX_VALUE);
120     this.sum.set(0);
121     variance.set(new double[]{-1, 0});
122   }
123 
124   public void update(int val) {
125     update((long) val);
126   }
127 
128   public void update(final long val) {
129     count.incrementAndGet();
130     sample.update(val);
131     setMax(val);
132     setMin(val);
133     sum.getAndAdd(val);
134     updateVariance(val);
135   }
136 
137   private void setMax(final long potentialMax) {
138     boolean done = false;
139     while (!done) {
140       final long currentMax = max.get();
141       done = currentMax >= potentialMax 
142           || max.compareAndSet(currentMax, potentialMax);
143     }
144   }
145 
146   private void setMin(long potentialMin) {
147     boolean done = false;
148     while (!done) {
149       final long currentMin = min.get();
150       done = currentMin <= potentialMin 
151           || min.compareAndSet(currentMin, potentialMin);
152     }
153   }
154 
155   private void updateVariance(long value) {
156     boolean done = false;
157     while (!done) {
158       final double[] oldValues = variance.get();
159       final double[] newValues = new double[2];
160       if (oldValues[0] == -1) {
161         newValues[0] = value;
162         newValues[1] = 0;
163       } else {
164         final double oldM = oldValues[0];
165         final double oldS = oldValues[1];
166 
167         final double newM = oldM + ((value - oldM) / getCount());
168         final double newS = oldS + ((value - oldM) * (value - newM));
169 
170         newValues[0] = newM;
171         newValues[1] = newS;
172       }
173       done = variance.compareAndSet(oldValues, newValues);
174     }
175   }
176 
177 
178   public long getCount() {
179     return count.get();
180   }
181 
182   public long getMax() {
183     if (getCount() > 0) {
184       return max.get();
185     }
186     return 0L;
187   }
188 
189   public long getMin() {
190     if (getCount() > 0) {
191       return min.get();
192     }
193     return 0L;
194   }
195 
196   public double getMean() {
197     if (getCount() > 0) {
198       return sum.get() / (double) getCount();
199     }
200     return 0.0;
201   }
202 
203   public double getStdDev() {
204     if (getCount() > 0) {
205       return Math.sqrt(getVariance());
206     }
207     return 0.0;
208   }
209 
210   public Snapshot getSnapshot() {
211     return sample.getSnapshot();
212   }
213 
214   private double getVariance() {
215     if (getCount() <= 1) {
216       return 0.0;
217     }
218     return variance.get()[1] / (getCount() - 1);
219   }
220 
221   @Override
222   public void pushMetric(MetricsRecord mr) {
223     final Snapshot s = this.getSnapshot();
224     mr.setMetric(getName() + NUM_OPS_METRIC_NAME, this.getCount());
225     mr.setMetric(getName() + MIN_METRIC_NAME, this.getMin());
226     mr.setMetric(getName() + MAX_METRIC_NAME, this.getMax());
227 
228     mr.setMetric(getName() + MEAN_METRIC_NAME, (float) this.getMean());
229     mr.setMetric(getName() + STD_DEV_METRIC_NAME, (float) this.getStdDev());
230 
231     mr.setMetric(getName() + MEDIAN_METRIC_NAME, (float) s.getMedian());
232     mr.setMetric(getName() + SEVENTY_FIFTH_PERCENTILE_METRIC_NAME,
233         (float) s.get75thPercentile());
234     mr.setMetric(getName() + NINETY_FIFTH_PERCENTILE_METRIC_NAME,
235         (float) s.get95thPercentile());
236     mr.setMetric(getName() + NINETY_NINETH_PERCENTILE_METRIC_NAME,
237         (float) s.get99thPercentile());
238   }
239 }