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