blob: 03267dc7dd76da91e920af80a18dca166bc6aa26 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.base.metrics;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.MainDex;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* Java API for recording UMA histograms.
*
* Internally, histograms objects are cached on the Java side by their pointer
* values (converted to long). This is safe to do because C++ Histogram objects
* are never freed. Caching them on the Java side prevents needing to do costly
* Java String to C++ string conversions on the C++ side during lookup.
*
* Note: the JNI calls are relatively costly - avoid calling these methods in performance-critical
* code.
*/
@JNINamespace("base::android")
@MainDex
public class RecordHistogram {
private static Throwable sDisabledBy;
private static Map<String, Long> sCache =
Collections.synchronizedMap(new HashMap<String, Long>());
/**
* Tests may not have native initialized, so they may need to disable metrics. The value should
* be reset after the test done, to avoid carrying over state to unrelated tests.
*
* In JUnit tests this can be done automatically using
* {@link org.chromium.chrome.browser.DisableHistogramsRule}
*/
@VisibleForTesting
public static void setDisabledForTests(boolean disabled) {
if (disabled && sDisabledBy != null) {
throw new IllegalStateException("Histograms are already disabled.", sDisabledBy);
}
sDisabledBy = disabled ? new Throwable() : null;
}
private static long getCachedHistogramKey(String name) {
Long key = sCache.get(name);
// Note: If key is null, we don't have it cached. In that case, pass 0
// to the native code, which gets converted to a null histogram pointer
// which will cause the native code to look up the object on the native
// side.
return (key == null ? 0 : key);
}
/**
* Records a sample in a boolean UMA histogram of the given name. Boolean histogram has two
* buckets, corresponding to success (true) and failure (false). This is the Java equivalent of
* the UMA_HISTOGRAM_BOOLEAN C++ macro.
* @param name name of the histogram
* @param sample sample to be recorded, either true or false
*/
public static void recordBooleanHistogram(String name, boolean sample) {
if (sDisabledBy != null) return;
long key = getCachedHistogramKey(name);
long result = nativeRecordBooleanHistogram(name, key, sample);
if (result != key) sCache.put(name, result);
}
/**
* Records a sample in an enumerated histogram of the given name and boundary. Note that
* |boundary| identifies the histogram - it should be the same at every invocation. This is the
* Java equivalent of the UMA_HISTOGRAM_ENUMERATION C++ macro.
* @param name name of the histogram
* @param sample sample to be recorded, at least 0 and at most |boundary| - 1
* @param boundary upper bound for legal sample values - all sample values have to be strictly
* lower than |boundary|
*/
public static void recordEnumeratedHistogram(String name, int sample, int boundary) {
if (sDisabledBy != null) return;
long key = getCachedHistogramKey(name);
long result = nativeRecordEnumeratedHistogram(name, key, sample, boundary);
if (result != key) sCache.put(name, result);
}
/**
* Records a sample in a count histogram. This is the Java equivalent of the
* UMA_HISTOGRAM_COUNTS_1M C++ macro.
* @param name name of the histogram
* @param sample sample to be recorded, at least 1 and at most 999999
*/
public static void recordCountHistogram(String name, int sample) {
recordCustomCountHistogram(name, sample, 1, 1000000, 50);
}
/**
* Records a sample in a count histogram. This is the Java equivalent of the
* UMA_HISTOGRAM_COUNTS_100 C++ macro.
* @param name name of the histogram
* @param sample sample to be recorded, at least 1 and at most 99
*/
public static void recordCount100Histogram(String name, int sample) {
recordCustomCountHistogram(name, sample, 1, 100, 50);
}
/**
* Records a sample in a count histogram. This is the Java equivalent of the
* UMA_HISTOGRAM_COUNTS_1000 C++ macro.
* @param name name of the histogram
* @param sample sample to be recorded, at least 1 and at most 999
*/
public static void recordCount1000Histogram(String name, int sample) {
recordCustomCountHistogram(name, sample, 1, 1000, 50);
}
/**
* Records a sample in a count histogram. This is the Java equivalent of the
* UMA_HISTOGRAM_CUSTOM_COUNTS C++ macro.
* @param name name of the histogram
* @param sample sample to be recorded, at least |min| and at most |max| - 1
* @param min lower bound for expected sample values. It must be >= 1
* @param max upper bounds for expected sample values
* @param numBuckets the number of buckets
*/
public static void recordCustomCountHistogram(
String name, int sample, int min, int max, int numBuckets) {
if (sDisabledBy != null) return;
long key = getCachedHistogramKey(name);
long result = nativeRecordCustomCountHistogram(name, key, sample, min, max, numBuckets);
if (result != key) sCache.put(name, result);
}
/**
* Records a sample in a linear histogram. This is the Java equivalent for using
* base::LinearHistogram.
* @param name name of the histogram
* @param sample sample to be recorded, at least |min| and at most |max| - 1.
* @param min lower bound for expected sample values, should be at least 1.
* @param max upper bounds for expected sample values
* @param numBuckets the number of buckets
*/
public static void recordLinearCountHistogram(
String name, int sample, int min, int max, int numBuckets) {
if (sDisabledBy != null) return;
long key = getCachedHistogramKey(name);
long result = nativeRecordLinearCountHistogram(name, key, sample, min, max, numBuckets);
if (result != key) sCache.put(name, result);
}
/**
* Records a sample in a percentage histogram. This is the Java equivalent of the
* UMA_HISTOGRAM_PERCENTAGE C++ macro.
* @param name name of the histogram
* @param sample sample to be recorded, at least 0 and at most 100.
*/
public static void recordPercentageHistogram(String name, int sample) {
if (sDisabledBy != null) return;
long key = getCachedHistogramKey(name);
long result = nativeRecordEnumeratedHistogram(name, key, sample, 101);
if (result != key) sCache.put(name, result);
}
/**
* Records a sparse histogram. This is the Java equivalent of UmaHistogramSparse.
* @param name name of the histogram
* @param sample sample to be recorded. All values of |sample| are valid, including negative
* values.
*/
public static void recordSparseSlowlyHistogram(String name, int sample) {
if (sDisabledBy != null) return;
long key = getCachedHistogramKey(name);
long result = nativeRecordSparseHistogram(name, key, sample);
if (result != key) sCache.put(name, result);
}
/**
* Records a sample in a histogram of times. Useful for recording short durations. This is the
* Java equivalent of the UMA_HISTOGRAM_TIMES C++ macro.
* Note that histogram samples will always be converted to milliseconds when logged.
* @param name name of the histogram
* @param duration duration to be recorded
* @param timeUnit the unit of the duration argument (must be >= MILLISECONDS)
*/
public static void recordTimesHistogram(String name, long duration, TimeUnit timeUnit) {
assertTimesHistogramSupportsUnit(timeUnit);
recordCustomTimesHistogramMilliseconds(
name, timeUnit.toMillis(duration), 1, TimeUnit.SECONDS.toMillis(10), 50);
}
/**
* Records a sample in a histogram of times. Useful for recording medium durations. This is the
* Java equivalent of the UMA_HISTOGRAM_MEDIUM_TIMES C++ macro.
* Note that histogram samples will always be converted to milliseconds when logged.
* @param name name of the histogram
* @param duration duration to be recorded
* @param timeUnit the unit of the duration argument (must be >= MILLISECONDS)
*/
public static void recordMediumTimesHistogram(String name, long duration, TimeUnit timeUnit) {
assertTimesHistogramSupportsUnit(timeUnit);
recordCustomTimesHistogramMilliseconds(
name, timeUnit.toMillis(duration), 10, TimeUnit.MINUTES.toMillis(3), 50);
}
/**
* Records a sample in a histogram of times. Useful for recording long durations. This is the
* Java equivalent of the UMA_HISTOGRAM_LONG_TIMES C++ macro.
* Note that histogram samples will always be converted to milliseconds when logged.
* @param name name of the histogram
* @param duration duration to be recorded
* @param timeUnit the unit of the duration argument (must be >= MILLISECONDS)
*/
public static void recordLongTimesHistogram(String name, long duration, TimeUnit timeUnit) {
assertTimesHistogramSupportsUnit(timeUnit);
recordCustomTimesHistogramMilliseconds(
name, timeUnit.toMillis(duration), 1, TimeUnit.HOURS.toMillis(1), 50);
}
/**
* Records a sample in a histogram of times. Useful for recording long durations. This is the
* Java equivalent of the UMA_HISTOGRAM_LONG_TIMES_100 C++ macro.
* Note that histogram samples will always be converted to milliseconds when logged.
* @param name name of the histogram
* @param duration duration to be recorded
* @param timeUnit the unit of the duration argument (must be >= MILLISECONDS)
*/
public static void recordLongTimesHistogram100(String name, long duration, TimeUnit timeUnit) {
assertTimesHistogramSupportsUnit(timeUnit);
recordCustomTimesHistogramMilliseconds(
name, timeUnit.toMillis(duration), 1, TimeUnit.HOURS.toMillis(1), 100);
}
/**
* Records a sample in a histogram of times with custom buckets. This is the Java equivalent of
* the UMA_HISTOGRAM_CUSTOM_TIMES C++ macro.
* Note that histogram samples will always be converted to milliseconds when logged.
* @param name name of the histogram
* @param duration duration to be recorded
* @param min the minimum bucket value
* @param max the maximum bucket value
* @param timeUnit the unit of the duration, min, and max arguments (must be >= MILLISECONDS)
* @param numBuckets the number of buckets
*/
public static void recordCustomTimesHistogram(
String name, long duration, long min, long max, TimeUnit timeUnit, int numBuckets) {
assertTimesHistogramSupportsUnit(timeUnit);
recordCustomTimesHistogramMilliseconds(name, timeUnit.toMillis(duration),
timeUnit.toMillis(min), timeUnit.toMillis(max), numBuckets);
}
/**
* Records a sample in a histogram of sizes in KB. This is the Java equivalent of the
* UMA_HISTOGRAM_MEMORY_KB C++ macro.
*
* Good for sizes up to about 500MB.
*
* @param name name of the histogram.
* @param sizeInkB Sample to record in KB.
*/
public static void recordMemoryKBHistogram(String name, int sizeInKB) {
recordCustomCountHistogram(name, sizeInKB, 1000, 500000, 50);
}
/**
* Asserts that the time unit is supported by TimesHistogram.
* @param timeUnit the unit, must be >= MILLISECONDS
*/
/* package */ static void assertTimesHistogramSupportsUnit(TimeUnit timeUnit) {
// Use extra variable, or else 'git cl format' produces weird results.
boolean supported = timeUnit != TimeUnit.NANOSECONDS && timeUnit != TimeUnit.MICROSECONDS;
assert supported : "TimesHistogram doesn't support MICROSECOND and NANOSECONDS time units. "
+ "Consider using CountHistogram instead.";
}
private static int clampToInt(long value) {
if (value > Integer.MAX_VALUE) return Integer.MAX_VALUE;
// Note: Clamping to MIN_VALUE rather than 0, to let base/ histograms code
// do its own handling of negative values in the future.
if (value < Integer.MIN_VALUE) return Integer.MIN_VALUE;
return (int) value;
}
private static void recordCustomTimesHistogramMilliseconds(
String name, long duration, long min, long max, int numBuckets) {
if (sDisabledBy != null) return;
long key = getCachedHistogramKey(name);
// Note: Duration, min and max are clamped to int here because that's what's expected by
// the native histograms API. Callers of these functions still pass longs because that's
// the types returned by TimeUnit and System.currentTimeMillis() APIs, from which these
// values come.
assert max == clampToInt(max);
long result = nativeRecordCustomTimesHistogramMilliseconds(
name, key, clampToInt(duration), clampToInt(min), clampToInt(max), numBuckets);
if (result != key) sCache.put(name, result);
}
/**
* Returns the number of samples recorded in the given bucket of the given histogram.
* @param name name of the histogram to look up
* @param sample the bucket containing this sample value will be looked up
*/
@VisibleForTesting
public static int getHistogramValueCountForTesting(String name, int sample) {
return nativeGetHistogramValueCountForTesting(name, sample);
}
/**
* Returns the number of samples recorded for the given histogram.
* @param name name of the histogram to look up.
*/
@VisibleForTesting
public static int getHistogramTotalCountForTesting(String name) {
return nativeGetHistogramTotalCountForTesting(name);
}
private static native long nativeRecordCustomTimesHistogramMilliseconds(
String name, long key, int duration, int min, int max, int numBuckets);
private static native long nativeRecordBooleanHistogram(String name, long key, boolean sample);
private static native long nativeRecordEnumeratedHistogram(
String name, long key, int sample, int boundary);
private static native long nativeRecordCustomCountHistogram(
String name, long key, int sample, int min, int max, int numBuckets);
private static native long nativeRecordLinearCountHistogram(
String name, long key, int sample, int min, int max, int numBuckets);
private static native long nativeRecordSparseHistogram(String name, long key, int sample);
private static native int nativeGetHistogramValueCountForTesting(String name, int sample);
private static native int nativeGetHistogramTotalCountForTesting(String name);
}