1 // Copyright 2019 The Chromium Authors 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.Callback; 8 import org.chromium.base.TimeUtils; 9 import org.chromium.base.annotations.JNINamespace; 10 import org.chromium.base.annotations.NativeMethods; 11 import org.chromium.build.annotations.MainDex; 12 13 import java.util.ArrayList; 14 import java.util.Collections; 15 import java.util.HashMap; 16 import java.util.List; 17 import java.util.Map; 18 19 /** 20 * An implementation of {@link UmaRecorder} which forwards all calls through JNI. 21 * 22 * Note: the JNI calls are relatively costly - avoid calling these methods in performance-critical 23 * code. 24 */ 25 @JNINamespace("base::android") 26 @MainDex 27 /* package */ final class NativeUmaRecorder implements UmaRecorder { 28 /** 29 * Internally, histograms objects are cached on the Java side by their pointer 30 * values (converted to long). This is safe to do because C++ Histogram objects 31 * are never freed. Caching them on the Java side prevents needing to do costly 32 * Java String to C++ string conversions on the C++ side during lookup. 33 */ 34 private final Map<String, Long> mNativeHints = 35 Collections.synchronizedMap(new HashMap<String, Long>()); 36 private Map<Callback<String>, Long> mUserActionTestingCallbackNativePtrs; 37 38 @Override recordBooleanHistogram(String name, boolean sample)39 public void recordBooleanHistogram(String name, boolean sample) { 40 long oldHint = getNativeHint(name); 41 long newHint = NativeUmaRecorderJni.get().recordBooleanHistogram(name, oldHint, sample); 42 maybeUpdateNativeHint(name, oldHint, newHint); 43 } 44 45 @Override recordExponentialHistogram( String name, int sample, int min, int max, int numBuckets)46 public void recordExponentialHistogram( 47 String name, int sample, int min, int max, int numBuckets) { 48 long oldHint = getNativeHint(name); 49 long newHint = NativeUmaRecorderJni.get().recordExponentialHistogram( 50 name, oldHint, sample, min, max, numBuckets); 51 maybeUpdateNativeHint(name, oldHint, newHint); 52 } 53 54 @Override recordLinearHistogram(String name, int sample, int min, int max, int numBuckets)55 public void recordLinearHistogram(String name, int sample, int min, int max, int numBuckets) { 56 long oldHint = getNativeHint(name); 57 long newHint = NativeUmaRecorderJni.get().recordLinearHistogram( 58 name, oldHint, sample, min, max, numBuckets); 59 maybeUpdateNativeHint(name, oldHint, newHint); 60 } 61 62 @Override recordSparseHistogram(String name, int sample)63 public void recordSparseHistogram(String name, int sample) { 64 long oldHint = getNativeHint(name); 65 long newHint = NativeUmaRecorderJni.get().recordSparseHistogram(name, oldHint, sample); 66 maybeUpdateNativeHint(name, oldHint, newHint); 67 } 68 69 @Override recordUserAction(String name, long elapsedRealtimeMillis)70 public void recordUserAction(String name, long elapsedRealtimeMillis) { 71 // Java and native code use different clocks. We need a relative elapsed time. 72 long millisSinceEvent = TimeUtils.elapsedRealtimeMillis() - elapsedRealtimeMillis; 73 NativeUmaRecorderJni.get().recordUserAction(name, millisSinceEvent); 74 } 75 76 @Override getHistogramValueCountForTesting(String name, int sample)77 public int getHistogramValueCountForTesting(String name, int sample) { 78 return NativeUmaRecorderJni.get().getHistogramValueCountForTesting(name, sample, 0); 79 } 80 81 @Override getHistogramTotalCountForTesting(String name)82 public int getHistogramTotalCountForTesting(String name) { 83 return NativeUmaRecorderJni.get().getHistogramTotalCountForTesting(name, 0); 84 } 85 86 @Override getHistogramSamplesForTesting(String name)87 public List<HistogramBucket> getHistogramSamplesForTesting(String name) { 88 long[] samplesArray = NativeUmaRecorderJni.get().getHistogramSamplesForTesting(name); 89 List<HistogramBucket> buckets = new ArrayList<>(samplesArray.length); 90 for (int i = 0; i < samplesArray.length; i += 3) { 91 int min = (int) samplesArray[i]; 92 long max = samplesArray[i + 1]; 93 int count = (int) samplesArray[i + 2]; 94 buckets.add(new HistogramBucket(min, max, count)); 95 } 96 return buckets; 97 } 98 99 @Override addUserActionCallbackForTesting(Callback<String> callback)100 public void addUserActionCallbackForTesting(Callback<String> callback) { 101 long ptr = NativeUmaRecorderJni.get().addActionCallbackForTesting(callback); 102 if (mUserActionTestingCallbackNativePtrs == null) { 103 mUserActionTestingCallbackNativePtrs = Collections.synchronizedMap(new HashMap<>()); 104 } 105 mUserActionTestingCallbackNativePtrs.put(callback, ptr); 106 } 107 108 @Override removeUserActionCallbackForTesting(Callback<String> callback)109 public void removeUserActionCallbackForTesting(Callback<String> callback) { 110 if (mUserActionTestingCallbackNativePtrs == null) { 111 assert false 112 : "Attempting to remove a user action callback without previously registering any."; 113 return; 114 } 115 Long ptr = mUserActionTestingCallbackNativePtrs.remove(callback); 116 if (ptr == null) { 117 assert false : "Attempting to remove a user action callback that was never previously" 118 + " registered."; 119 return; 120 } 121 NativeUmaRecorderJni.get().removeActionCallbackForTesting(ptr); 122 } 123 getNativeHint(String name)124 private long getNativeHint(String name) { 125 Long hint = mNativeHints.get(name); 126 // Note: If key is null, we don't have it cached. In that case, pass 0 127 // to the native code, which gets converted to a null histogram pointer 128 // which will cause the native code to look up the object on the native 129 // side. 130 return (hint == null ? 0 : hint); 131 } 132 maybeUpdateNativeHint(String name, long oldHint, long newHint)133 private void maybeUpdateNativeHint(String name, long oldHint, long newHint) { 134 if (oldHint != newHint) { 135 mNativeHints.put(name, newHint); 136 } 137 } 138 139 /** Natives API to record metrics. */ 140 @NativeMethods 141 public interface Natives { recordBooleanHistogram(String name, long nativeHint, boolean sample)142 long recordBooleanHistogram(String name, long nativeHint, boolean sample); recordExponentialHistogram( String name, long nativeHint, int sample, int min, int max, int numBuckets)143 long recordExponentialHistogram( 144 String name, long nativeHint, int sample, int min, int max, int numBuckets); recordLinearHistogram( String name, long nativeHint, int sample, int min, int max, int numBuckets)145 long recordLinearHistogram( 146 String name, long nativeHint, int sample, int min, int max, int numBuckets); recordSparseHistogram(String name, long nativeHint, int sample)147 long recordSparseHistogram(String name, long nativeHint, int sample); 148 149 /** 150 * Records that the user performed an action. See {@code base::RecordComputedActionAt}. 151 * <p> 152 * Uses relative time, because Java and native code can use different clocks. 153 * 154 * @param name Name of the user-generated event. 155 * @param millisSinceEvent difference between now and the time when the event was observed. 156 * Should be positive. 157 */ recordUserAction(String name, long millisSinceEvent)158 void recordUserAction(String name, long millisSinceEvent); 159 getHistogramValueCountForTesting(String name, int sample, long snapshotPtr)160 int getHistogramValueCountForTesting(String name, int sample, long snapshotPtr); getHistogramTotalCountForTesting(String name, long snapshotPtr)161 int getHistogramTotalCountForTesting(String name, long snapshotPtr); getHistogramSamplesForTesting(String name)162 long[] getHistogramSamplesForTesting(String name); 163 createHistogramSnapshotForTesting()164 long createHistogramSnapshotForTesting(); destroyHistogramSnapshotForTesting(long snapshotPtr)165 void destroyHistogramSnapshotForTesting(long snapshotPtr); 166 addActionCallbackForTesting(Callback<String> callback)167 long addActionCallbackForTesting(Callback<String> callback); removeActionCallbackForTesting(long callbackId)168 void removeActionCallbackForTesting(long callbackId); 169 } 170 } 171