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