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