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 #include "base/android/callback_android.h"
6 #include "base/android/jni_android.h"
7 #include "base/android/jni_array.h"
8 #include "base/android/jni_string.h"
9 #include "base/base_jni/NativeUmaRecorder_jni.h"
10 #include "base/format_macros.h"
11 #include "base/metrics/histogram.h"
12 #include "base/metrics/histogram_base.h"
13 #include "base/metrics/sparse_histogram.h"
14 #include "base/metrics/statistics_recorder.h"
15 #include "base/metrics/user_metrics.h"
16 #include "base/strings/stringprintf.h"
17 #include "base/time/time.h"
18
19 namespace base {
20 namespace android {
21
22 namespace {
23
24 using HistogramsSnapshot =
25 std::map<std::string, std::unique_ptr<HistogramSamples>>;
26
HistogramConstructionParamsToString(HistogramBase * histogram)27 std::string HistogramConstructionParamsToString(HistogramBase* histogram) {
28 std::string params_str = histogram->histogram_name();
29 switch (histogram->GetHistogramType()) {
30 case HISTOGRAM:
31 case LINEAR_HISTOGRAM:
32 case BOOLEAN_HISTOGRAM:
33 case CUSTOM_HISTOGRAM: {
34 Histogram* hist = static_cast<Histogram*>(histogram);
35 params_str += StringPrintf("/%d/%d/%" PRIuS, hist->declared_min(),
36 hist->declared_max(), hist->bucket_count());
37 break;
38 }
39 case SPARSE_HISTOGRAM:
40 case DUMMY_HISTOGRAM:
41 break;
42 }
43 return params_str;
44 }
45
46 // Convert a jlong |histogram_hint| from Java to a HistogramBase* via a cast.
47 // The Java side caches these in a map (see NativeUmaRecorder.java), which is
48 // safe to do since C++ Histogram objects are never freed.
HistogramFromHint(jlong j_histogram_hint)49 static HistogramBase* HistogramFromHint(jlong j_histogram_hint) {
50 return reinterpret_cast<HistogramBase*>(j_histogram_hint);
51 }
52
CheckHistogramArgs(JNIEnv * env,jstring j_histogram_name,int32_t expected_min,int32_t expected_max,size_t expected_bucket_count,HistogramBase * histogram)53 void CheckHistogramArgs(JNIEnv* env,
54 jstring j_histogram_name,
55 int32_t expected_min,
56 int32_t expected_max,
57 size_t expected_bucket_count,
58 HistogramBase* histogram) {
59 std::string histogram_name = ConvertJavaStringToUTF8(env, j_histogram_name);
60 bool valid_arguments = Histogram::InspectConstructionArguments(
61 histogram_name, &expected_min, &expected_max, &expected_bucket_count);
62 DCHECK(valid_arguments);
63 DCHECK(histogram->HasConstructionArguments(expected_min, expected_max,
64 expected_bucket_count))
65 << histogram_name << "/" << expected_min << "/" << expected_max << "/"
66 << expected_bucket_count << " vs. "
67 << HistogramConstructionParamsToString(histogram);
68 }
69
BooleanHistogram(JNIEnv * env,jstring j_histogram_name,jlong j_histogram_hint)70 HistogramBase* BooleanHistogram(JNIEnv* env,
71 jstring j_histogram_name,
72 jlong j_histogram_hint) {
73 DCHECK(j_histogram_name);
74 HistogramBase* histogram = HistogramFromHint(j_histogram_hint);
75 if (histogram)
76 return histogram;
77
78 std::string histogram_name = ConvertJavaStringToUTF8(env, j_histogram_name);
79 histogram = BooleanHistogram::FactoryGet(
80 histogram_name, HistogramBase::kUmaTargetedHistogramFlag);
81 return histogram;
82 }
83
ExponentialHistogram(JNIEnv * env,jstring j_histogram_name,jlong j_histogram_hint,jint j_min,jint j_max,jint j_num_buckets)84 HistogramBase* ExponentialHistogram(JNIEnv* env,
85 jstring j_histogram_name,
86 jlong j_histogram_hint,
87 jint j_min,
88 jint j_max,
89 jint j_num_buckets) {
90 DCHECK(j_histogram_name);
91 int32_t min = static_cast<int32_t>(j_min);
92 int32_t max = static_cast<int32_t>(j_max);
93 size_t num_buckets = static_cast<size_t>(j_num_buckets);
94 HistogramBase* histogram = HistogramFromHint(j_histogram_hint);
95 if (histogram) {
96 CheckHistogramArgs(env, j_histogram_name, min, max, num_buckets, histogram);
97 return histogram;
98 }
99
100 DCHECK_GE(min, 1) << "The min expected sample must be >= 1";
101
102 std::string histogram_name = ConvertJavaStringToUTF8(env, j_histogram_name);
103 histogram = Histogram::FactoryGet(histogram_name, min, max, num_buckets,
104 HistogramBase::kUmaTargetedHistogramFlag);
105 return histogram;
106 }
107
LinearHistogram(JNIEnv * env,jstring j_histogram_name,jlong j_histogram_hint,jint j_min,jint j_max,jint j_num_buckets)108 HistogramBase* LinearHistogram(JNIEnv* env,
109 jstring j_histogram_name,
110 jlong j_histogram_hint,
111 jint j_min,
112 jint j_max,
113 jint j_num_buckets) {
114 DCHECK(j_histogram_name);
115 int32_t min = static_cast<int32_t>(j_min);
116 int32_t max = static_cast<int32_t>(j_max);
117 size_t num_buckets = static_cast<size_t>(j_num_buckets);
118 HistogramBase* histogram = HistogramFromHint(j_histogram_hint);
119 if (histogram) {
120 CheckHistogramArgs(env, j_histogram_name, min, max, num_buckets, histogram);
121 return histogram;
122 }
123
124 std::string histogram_name = ConvertJavaStringToUTF8(env, j_histogram_name);
125 histogram =
126 LinearHistogram::FactoryGet(histogram_name, min, max, num_buckets,
127 HistogramBase::kUmaTargetedHistogramFlag);
128 return histogram;
129 }
130
SparseHistogram(JNIEnv * env,jstring j_histogram_name,jlong j_histogram_hint)131 HistogramBase* SparseHistogram(JNIEnv* env,
132 jstring j_histogram_name,
133 jlong j_histogram_hint) {
134 DCHECK(j_histogram_name);
135 HistogramBase* histogram = HistogramFromHint(j_histogram_hint);
136 if (histogram)
137 return histogram;
138
139 std::string histogram_name = ConvertJavaStringToUTF8(env, j_histogram_name);
140 histogram = SparseHistogram::FactoryGet(
141 histogram_name, HistogramBase::kUmaTargetedHistogramFlag);
142 return histogram;
143 }
144
145 struct ActionCallbackWrapper {
146 base::ActionCallback action_callback;
147 };
148
OnActionRecorded(const JavaRef<jobject> & callback,const std::string & action,TimeTicks action_time)149 static void OnActionRecorded(const JavaRef<jobject>& callback,
150 const std::string& action,
151 TimeTicks action_time) {
152 RunStringCallbackAndroid(callback, action);
153 }
154
155 } // namespace
156
JNI_NativeUmaRecorder_RecordBooleanHistogram(JNIEnv * env,const JavaParamRef<jstring> & j_histogram_name,jlong j_histogram_hint,jboolean j_sample)157 jlong JNI_NativeUmaRecorder_RecordBooleanHistogram(
158 JNIEnv* env,
159 const JavaParamRef<jstring>& j_histogram_name,
160 jlong j_histogram_hint,
161 jboolean j_sample) {
162 bool sample = static_cast<bool>(j_sample);
163 HistogramBase* histogram =
164 BooleanHistogram(env, j_histogram_name, j_histogram_hint);
165 histogram->AddBoolean(sample);
166 return reinterpret_cast<jlong>(histogram);
167 }
168
JNI_NativeUmaRecorder_RecordExponentialHistogram(JNIEnv * env,const JavaParamRef<jstring> & j_histogram_name,jlong j_histogram_hint,jint j_sample,jint j_min,jint j_max,jint j_num_buckets)169 jlong JNI_NativeUmaRecorder_RecordExponentialHistogram(
170 JNIEnv* env,
171 const JavaParamRef<jstring>& j_histogram_name,
172 jlong j_histogram_hint,
173 jint j_sample,
174 jint j_min,
175 jint j_max,
176 jint j_num_buckets) {
177 int sample = static_cast<int>(j_sample);
178 HistogramBase* histogram = ExponentialHistogram(
179 env, j_histogram_name, j_histogram_hint, j_min, j_max, j_num_buckets);
180 histogram->Add(sample);
181 return reinterpret_cast<jlong>(histogram);
182 }
183
JNI_NativeUmaRecorder_RecordLinearHistogram(JNIEnv * env,const JavaParamRef<jstring> & j_histogram_name,jlong j_histogram_hint,jint j_sample,jint j_min,jint j_max,jint j_num_buckets)184 jlong JNI_NativeUmaRecorder_RecordLinearHistogram(
185 JNIEnv* env,
186 const JavaParamRef<jstring>& j_histogram_name,
187 jlong j_histogram_hint,
188 jint j_sample,
189 jint j_min,
190 jint j_max,
191 jint j_num_buckets) {
192 int sample = static_cast<int>(j_sample);
193 HistogramBase* histogram = LinearHistogram(
194 env, j_histogram_name, j_histogram_hint, j_min, j_max, j_num_buckets);
195 histogram->Add(sample);
196 return reinterpret_cast<jlong>(histogram);
197 }
198
JNI_NativeUmaRecorder_RecordSparseHistogram(JNIEnv * env,const JavaParamRef<jstring> & j_histogram_name,jlong j_histogram_hint,jint j_sample)199 jlong JNI_NativeUmaRecorder_RecordSparseHistogram(
200 JNIEnv* env,
201 const JavaParamRef<jstring>& j_histogram_name,
202 jlong j_histogram_hint,
203 jint j_sample) {
204 int sample = static_cast<int>(j_sample);
205 HistogramBase* histogram =
206 SparseHistogram(env, j_histogram_name, j_histogram_hint);
207 histogram->Add(sample);
208 return reinterpret_cast<jlong>(histogram);
209 }
210
JNI_NativeUmaRecorder_RecordUserAction(JNIEnv * env,const JavaParamRef<jstring> & j_user_action_name,jlong j_millis_since_event)211 void JNI_NativeUmaRecorder_RecordUserAction(
212 JNIEnv* env,
213 const JavaParamRef<jstring>& j_user_action_name,
214 jlong j_millis_since_event) {
215 // Time values coming from Java need to be synchronized with TimeTick clock.
216 RecordComputedActionSince(ConvertJavaStringToUTF8(env, j_user_action_name),
217 Milliseconds(j_millis_since_event));
218 }
219
220 // This backs a Java test util for testing histograms -
221 // MetricsUtils.HistogramDelta. It should live in a test-specific file, but we
222 // currently can't have test-specific native code packaged in test-specific Java
223 // targets - see http://crbug.com/415945.
JNI_NativeUmaRecorder_GetHistogramValueCountForTesting(JNIEnv * env,const JavaParamRef<jstring> & histogram_name,jint sample,jlong snapshot_ptr)224 jint JNI_NativeUmaRecorder_GetHistogramValueCountForTesting(
225 JNIEnv* env,
226 const JavaParamRef<jstring>& histogram_name,
227 jint sample,
228 jlong snapshot_ptr) {
229 std::string name = android::ConvertJavaStringToUTF8(env, histogram_name);
230 HistogramBase* histogram = StatisticsRecorder::FindHistogram(name);
231 if (histogram == nullptr) {
232 // No samples have been recorded for this histogram (yet?).
233 return 0;
234 }
235
236 int actual_count = histogram->SnapshotSamples()->GetCount(sample);
237 if (snapshot_ptr) {
238 auto* snapshot = reinterpret_cast<HistogramsSnapshot*>(snapshot_ptr);
239 auto snapshot_data = snapshot->find(name);
240 if (snapshot_data != snapshot->end())
241 actual_count -= snapshot_data->second->GetCount(sample);
242 }
243
244 return actual_count;
245 }
246
JNI_NativeUmaRecorder_GetHistogramTotalCountForTesting(JNIEnv * env,const JavaParamRef<jstring> & histogram_name,jlong snapshot_ptr)247 jint JNI_NativeUmaRecorder_GetHistogramTotalCountForTesting(
248 JNIEnv* env,
249 const JavaParamRef<jstring>& histogram_name,
250 jlong snapshot_ptr) {
251 std::string name = android::ConvertJavaStringToUTF8(env, histogram_name);
252 HistogramBase* histogram = StatisticsRecorder::FindHistogram(name);
253 if (histogram == nullptr) {
254 // No samples have been recorded for this histogram.
255 return 0;
256 }
257
258 int actual_count = histogram->SnapshotSamples()->TotalCount();
259 if (snapshot_ptr) {
260 auto* snapshot = reinterpret_cast<HistogramsSnapshot*>(snapshot_ptr);
261 auto snapshot_data = snapshot->find(name);
262 if (snapshot_data != snapshot->end())
263 actual_count -= snapshot_data->second->TotalCount();
264 }
265 return actual_count;
266 }
267
268 // Returns an array with 3 entries for each bucket, representing (min, max,
269 // count).
270 ScopedJavaLocalRef<jlongArray>
JNI_NativeUmaRecorder_GetHistogramSamplesForTesting(JNIEnv * env,const JavaParamRef<jstring> & histogram_name)271 JNI_NativeUmaRecorder_GetHistogramSamplesForTesting(
272 JNIEnv* env,
273 const JavaParamRef<jstring>& histogram_name) {
274 std::string name = android::ConvertJavaStringToUTF8(env, histogram_name);
275 HistogramBase* histogram = StatisticsRecorder::FindHistogram(name);
276 std::vector<int64_t> buckets;
277
278 if (histogram == nullptr) {
279 // No samples have been recorded for this histogram.
280 return base::android::ToJavaLongArray(env, buckets);
281 }
282
283 std::unique_ptr<HistogramSamples> samples = histogram->SnapshotSamples();
284 for (auto sampleCountIterator = samples->Iterator();
285 !sampleCountIterator->Done(); sampleCountIterator->Next()) {
286 HistogramBase::Sample min;
287 int64_t max;
288 HistogramBase::Count count;
289 sampleCountIterator->Get(&min, &max, &count);
290 buckets.push_back(min);
291 buckets.push_back(max);
292 buckets.push_back(count);
293 }
294
295 return base::android::ToJavaLongArray(env, buckets);
296 }
297
JNI_NativeUmaRecorder_CreateHistogramSnapshotForTesting(JNIEnv * env)298 jlong JNI_NativeUmaRecorder_CreateHistogramSnapshotForTesting(JNIEnv* env) {
299 HistogramsSnapshot* snapshot = new HistogramsSnapshot();
300 for (const auto* const histogram : StatisticsRecorder::GetHistograms()) {
301 (*snapshot)[histogram->histogram_name()] = histogram->SnapshotSamples();
302 }
303 return reinterpret_cast<intptr_t>(snapshot);
304 }
305
JNI_NativeUmaRecorder_DestroyHistogramSnapshotForTesting(JNIEnv * env,jlong snapshot_ptr)306 void JNI_NativeUmaRecorder_DestroyHistogramSnapshotForTesting(
307 JNIEnv* env,
308 jlong snapshot_ptr) {
309 delete reinterpret_cast<HistogramsSnapshot*>(snapshot_ptr);
310 }
311
JNI_NativeUmaRecorder_AddActionCallbackForTesting(JNIEnv * env,const JavaParamRef<jobject> & callback)312 static jlong JNI_NativeUmaRecorder_AddActionCallbackForTesting(
313 JNIEnv* env,
314 const JavaParamRef<jobject>& callback) {
315 // Create a wrapper for the ActionCallback, so it can life on the heap until
316 // RemoveActionCallbackForTesting() is called.
317 auto* wrapper = new ActionCallbackWrapper{base::BindRepeating(
318 &OnActionRecorded, ScopedJavaGlobalRef<jobject>(env, callback))};
319 base::AddActionCallback(wrapper->action_callback);
320 return reinterpret_cast<intptr_t>(wrapper);
321 }
322
JNI_NativeUmaRecorder_RemoveActionCallbackForTesting(JNIEnv * env,jlong callback_id)323 static void JNI_NativeUmaRecorder_RemoveActionCallbackForTesting(
324 JNIEnv* env,
325 jlong callback_id) {
326 DCHECK(callback_id);
327 auto* wrapper = reinterpret_cast<ActionCallbackWrapper*>(callback_id);
328 base::RemoveActionCallback(wrapper->action_callback);
329 delete wrapper;
330 }
331
332 } // namespace android
333 } // namespace base
334