Home | History | Annotate | Download | only in metrics
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 package org.chromium.base.metrics;
      6 
      7 import org.chromium.base.VisibleForTesting;
      8 import org.chromium.base.annotations.JNINamespace;
      9 
     10 import java.util.Collections;
     11 import java.util.HashMap;
     12 import java.util.Map;
     13 import java.util.concurrent.TimeUnit;
     14 
     15 /**
     16  * Java API for recording UMA histograms.
     17  *
     18  * Internally, histograms objects are cached on the Java side by their pointer
     19  * values (converted to long). This is safe to do because C++ Histogram objects
     20  * are never freed. Caching them on the Java side prevents needing to do costly
     21  * Java String to C++ string conversions on the C++ side during lookup.
     22  *
     23  * Note: the JNI calls are relatively costly - avoid calling these methods in performance-critical
     24  * code.
     25  */
     26 @JNINamespace("base::android")
     27 public class RecordHistogram {
     28     private static Throwable sDisabledBy;
     29     private static Map<String, Long> sCache =
     30             Collections.synchronizedMap(new HashMap<String, Long>());
     31 
     32     /**
     33      * Tests may not have native initialized, so they may need to disable metrics. The value should
     34      * be reset after the test done, to avoid carrying over state to unrelated tests.
     35      *
     36      * In JUnit tests this can be done automatically using
     37      * {@link org.chromium.chrome.browser.DisableHistogramsRule}
     38      */
     39     @VisibleForTesting
     40     public static void setDisabledForTests(boolean disabled) {
     41         if (disabled && sDisabledBy != null) {
     42             throw new IllegalStateException("Histograms are already disabled.", sDisabledBy);
     43         }
     44         sDisabledBy = disabled ? new Throwable() : null;
     45     }
     46 
     47     private static long getCachedHistogramKey(String name) {
     48         Long key = sCache.get(name);
     49         // Note: If key is null, we don't have it cached. In that case, pass 0
     50         // to the native code, which gets converted to a null histogram pointer
     51         // which will cause the native code to look up the object on the native
     52         // side.
     53         return (key == null ? 0 : key);
     54     }
     55 
     56     /**
     57      * Records a sample in a boolean UMA histogram of the given name. Boolean histogram has two
     58      * buckets, corresponding to success (true) and failure (false). This is the Java equivalent of
     59      * the UMA_HISTOGRAM_BOOLEAN C++ macro.
     60      * @param name name of the histogram
     61      * @param sample sample to be recorded, either true or false
     62      */
     63     public static void recordBooleanHistogram(String name, boolean sample) {
     64         if (sDisabledBy != null) return;
     65         long key = getCachedHistogramKey(name);
     66         long result = nativeRecordBooleanHistogram(name, key, sample);
     67         if (result != key) sCache.put(name, result);
     68     }
     69 
     70     /**
     71      * Records a sample in an enumerated histogram of the given name and boundary. Note that
     72      * |boundary| identifies the histogram - it should be the same at every invocation. This is the
     73      * Java equivalent of the UMA_HISTOGRAM_ENUMERATION C++ macro.
     74      * @param name name of the histogram
     75      * @param sample sample to be recorded, at least 0 and at most |boundary| - 1
     76      * @param boundary upper bound for legal sample values - all sample values have to be strictly
     77      *        lower than |boundary|
     78      */
     79     public static void recordEnumeratedHistogram(String name, int sample, int boundary) {
     80         if (sDisabledBy != null) return;
     81         long key = getCachedHistogramKey(name);
     82         long result = nativeRecordEnumeratedHistogram(name, key, sample, boundary);
     83         if (result != key) sCache.put(name, result);
     84     }
     85 
     86     /**
     87      * Records a sample in a count histogram. This is the Java equivalent of the
     88      * UMA_HISTOGRAM_COUNTS C++ macro.
     89      * @param name name of the histogram
     90      * @param sample sample to be recorded, at least 1 and at most 999999
     91      */
     92     public static void recordCountHistogram(String name, int sample) {
     93         recordCustomCountHistogram(name, sample, 1, 1000000, 50);
     94     }
     95 
     96     /**
     97      * Records a sample in a count histogram. This is the Java equivalent of the
     98      * UMA_HISTOGRAM_COUNTS_100 C++ macro.
     99      * @param name name of the histogram
    100      * @param sample sample to be recorded, at least 1 and at most 99
    101      */
    102     public static void recordCount100Histogram(String name, int sample) {
    103         recordCustomCountHistogram(name, sample, 1, 100, 50);
    104     }
    105 
    106     /**
    107      * Records a sample in a count histogram. This is the Java equivalent of the
    108      * UMA_HISTOGRAM_COUNTS_1000 C++ macro.
    109      * @param name name of the histogram
    110      * @param sample sample to be recorded, at least 1 and at most 999
    111      */
    112     public static void recordCount1000Histogram(String name, int sample) {
    113         recordCustomCountHistogram(name, sample, 1, 1000, 50);
    114     }
    115 
    116     /**
    117      * Records a sample in a count histogram. This is the Java equivalent of the
    118      * UMA_HISTOGRAM_CUSTOM_COUNTS C++ macro.
    119      * @param name name of the histogram
    120      * @param sample sample to be recorded, at least |min| and at most |max| - 1
    121      * @param min lower bound for expected sample values. It must be >= 1
    122      * @param max upper bounds for expected sample values
    123      * @param numBuckets the number of buckets
    124      */
    125     public static void recordCustomCountHistogram(
    126             String name, int sample, int min, int max, int numBuckets) {
    127         if (sDisabledBy != null) return;
    128         long key = getCachedHistogramKey(name);
    129         long result = nativeRecordCustomCountHistogram(name, key, sample, min, max, numBuckets);
    130         if (result != key) sCache.put(name, result);
    131     }
    132 
    133     /**
    134      * Records a sample in a linear histogram. This is the Java equivalent for using
    135      * base::LinearHistogram.
    136      * @param name name of the histogram
    137      * @param sample sample to be recorded, at least |min| and at most |max| - 1.
    138      * @param min lower bound for expected sample values, should be at least 1.
    139      * @param max upper bounds for expected sample values
    140      * @param numBuckets the number of buckets
    141      */
    142     public static void recordLinearCountHistogram(
    143             String name, int sample, int min, int max, int numBuckets) {
    144         if (sDisabledBy != null) return;
    145         long key = getCachedHistogramKey(name);
    146         long result = nativeRecordLinearCountHistogram(name, key, sample, min, max, numBuckets);
    147         if (result != key) sCache.put(name, result);
    148     }
    149 
    150     /**
    151      * Records a sample in a percentage histogram. This is the Java equivalent of the
    152      * UMA_HISTOGRAM_PERCENTAGE C++ macro.
    153      * @param name name of the histogram
    154      * @param sample sample to be recorded, at least 0 and at most 100.
    155      */
    156     public static void recordPercentageHistogram(String name, int sample) {
    157         if (sDisabledBy != null) return;
    158         long key = getCachedHistogramKey(name);
    159         long result = nativeRecordEnumeratedHistogram(name, key, sample, 101);
    160         if (result != key) sCache.put(name, result);
    161     }
    162 
    163     /**
    164     * Records a sparse histogram. This is the Java equivalent of UMA_HISTOGRAM_SPARSE_SLOWLY.
    165     * @param name name of the histogram
    166     * @param sample sample to be recorded. All values of |sample| are valid, including negative
    167     *        values.
    168     */
    169     public static void recordSparseSlowlyHistogram(String name, int sample) {
    170         if (sDisabledBy != null) return;
    171         long key = getCachedHistogramKey(name);
    172         long result = nativeRecordSparseHistogram(name, key, sample);
    173         if (result != key) sCache.put(name, result);
    174     }
    175 
    176     /**
    177      * Records a sample in a histogram of times. Useful for recording short durations. This is the
    178      * Java equivalent of the UMA_HISTOGRAM_TIMES C++ macro.
    179      * @param name name of the histogram
    180      * @param duration duration to be recorded
    181      * @param timeUnit the unit of the duration argument
    182      */
    183     public static void recordTimesHistogram(String name, long duration, TimeUnit timeUnit) {
    184         recordCustomTimesHistogramMilliseconds(
    185                 name, timeUnit.toMillis(duration), 1, TimeUnit.SECONDS.toMillis(10), 50);
    186     }
    187 
    188     /**
    189      * Records a sample in a histogram of times. Useful for recording medium durations. This is the
    190      * Java equivalent of the UMA_HISTOGRAM_MEDIUM_TIMES C++ macro.
    191      * @param name name of the histogram
    192      * @param duration duration to be recorded
    193      * @param timeUnit the unit of the duration argument
    194      */
    195     public static void recordMediumTimesHistogram(String name, long duration, TimeUnit timeUnit) {
    196         recordCustomTimesHistogramMilliseconds(
    197                 name, timeUnit.toMillis(duration), 10, TimeUnit.MINUTES.toMillis(3), 50);
    198     }
    199 
    200     /**
    201      * Records a sample in a histogram of times. Useful for recording long durations. This is the
    202      * Java equivalent of the UMA_HISTOGRAM_LONG_TIMES C++ macro.
    203      * @param name name of the histogram
    204      * @param duration duration to be recorded
    205      * @param timeUnit the unit of the duration argument
    206      */
    207     public static void recordLongTimesHistogram(String name, long duration, TimeUnit timeUnit) {
    208         recordCustomTimesHistogramMilliseconds(
    209                 name, timeUnit.toMillis(duration), 1, TimeUnit.HOURS.toMillis(1), 50);
    210     }
    211 
    212     /**
    213      * Records a sample in a histogram of times with custom buckets. This is the Java equivalent of
    214      * the UMA_HISTOGRAM_CUSTOM_TIMES C++ macro.
    215      * @param name name of the histogram
    216      * @param duration duration to be recorded
    217      * @param min the minimum bucket value
    218      * @param max the maximum bucket value
    219      * @param timeUnit the unit of the duration, min, and max arguments
    220      * @param numBuckets the number of buckets
    221      */
    222     public static void recordCustomTimesHistogram(
    223             String name, long duration, long min, long max, TimeUnit timeUnit, int numBuckets) {
    224         recordCustomTimesHistogramMilliseconds(name, timeUnit.toMillis(duration),
    225                 timeUnit.toMillis(min), timeUnit.toMillis(max), numBuckets);
    226     }
    227 
    228     /**
    229      * Records a sample in a histogram of sizes in KB. This is the Java equivalent of the
    230      * UMA_HISTOGRAM_MEMORY_KB C++ macro.
    231      *
    232      * Good for sizes up to about 500MB.
    233      *
    234      * @param name name of the histogram.
    235      * @param sizeInkB Sample to record in KB.
    236      */
    237     public static void recordMemoryKBHistogram(String name, int sizeInKB) {
    238         recordCustomCountHistogram(name, sizeInKB, 1000, 500000, 50);
    239     }
    240 
    241     private static int clampToInt(long value) {
    242         if (value > Integer.MAX_VALUE) return Integer.MAX_VALUE;
    243         // Note: Clamping to MIN_VALUE rather than 0, to let base/ histograms code
    244         // do its own handling of negative values in the future.
    245         if (value < Integer.MIN_VALUE) return Integer.MIN_VALUE;
    246         return (int) value;
    247     }
    248 
    249     private static void recordCustomTimesHistogramMilliseconds(
    250             String name, long duration, long min, long max, int numBuckets) {
    251         if (sDisabledBy != null) return;
    252         long key = getCachedHistogramKey(name);
    253         // Note: Duration, min and max are clamped to int here because that's what's expected by
    254         // the native histograms API. Callers of these functions still pass longs because that's
    255         // the types returned by TimeUnit and System.currentTimeMillis() APIs, from which these
    256         // values come.
    257         long result = nativeRecordCustomTimesHistogramMilliseconds(
    258                 name, key, clampToInt(duration), clampToInt(min), clampToInt(max), numBuckets);
    259         if (result != key) sCache.put(name, result);
    260     }
    261 
    262     /**
    263      * Returns the number of samples recorded in the given bucket of the given histogram.
    264      * @param name name of the histogram to look up
    265      * @param sample the bucket containing this sample value will be looked up
    266      */
    267     @VisibleForTesting
    268     public static int getHistogramValueCountForTesting(String name, int sample) {
    269         return nativeGetHistogramValueCountForTesting(name, sample);
    270     }
    271 
    272     /**
    273      * Initializes the metrics system.
    274      */
    275     public static void initialize() {
    276         if (sDisabledBy != null) return;
    277         nativeInitialize();
    278     }
    279 
    280     private static native long nativeRecordCustomTimesHistogramMilliseconds(
    281             String name, long key, int duration, int min, int max, int numBuckets);
    282 
    283     private static native long nativeRecordBooleanHistogram(String name, long key, boolean sample);
    284     private static native long nativeRecordEnumeratedHistogram(
    285             String name, long key, int sample, int boundary);
    286     private static native long nativeRecordCustomCountHistogram(
    287             String name, long key, int sample, int min, int max, int numBuckets);
    288     private static native long nativeRecordLinearCountHistogram(
    289             String name, long key, int sample, int min, int max, int numBuckets);
    290     private static native long nativeRecordSparseHistogram(String name, long key, int sample);
    291 
    292     private static native int nativeGetHistogramValueCountForTesting(String name, int sample);
    293     private static native void nativeInitialize();
    294 }
    295