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