xref: /aosp_15_r20/external/cronet/base/android/native_uma_recorder.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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