• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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