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