• 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.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