1// Copyright 2014 The Chromium Authors. All rights reserved.
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/record_histogram.h"
6
7#include <stdint.h>
8
9#include <map>
10#include <string>
11
12#include "base/android/jni_android.h"
13#include "base/android/jni_string.h"
14#include "base/lazy_instance.h"
15#include "base/macros.h"
16#include "base/metrics/histogram.h"
17#include "base/metrics/sparse_histogram.h"
18#include "base/metrics/statistics_recorder.h"
19#include "base/strings/stringprintf.h"
20#include "base/synchronization/lock.h"
21#include "base/time/time.h"
22#include "jni/RecordHistogram_jni.h"
23
24namespace base {
25namespace android {
26namespace {
27
28// Simple thread-safe wrapper for caching histograms. This avoids
29// relatively expensive JNI string translation for each recording.
30class HistogramCache {
31 public:
32  HistogramCache() {}
33
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/%d", hist->declared_min(),
43                                   hist->declared_max(), hist->bucket_count());
44        break;
45      }
46      case SPARSE_HISTOGRAM:
47        break;
48    }
49    return params_str;
50  }
51
52  void CheckHistogramArgs(JNIEnv* env,
53                          jstring j_histogram_name,
54                          int32_t expected_min,
55                          int32_t expected_max,
56                          uint32_t expected_bucket_count,
57                          HistogramBase* histogram) {
58    std::string histogram_name = ConvertJavaStringToUTF8(env, j_histogram_name);
59    bool valid_arguments = Histogram::InspectConstructionArguments(
60        histogram_name, &expected_min, &expected_max, &expected_bucket_count);
61    DCHECK(valid_arguments);
62    DCHECK(histogram->HasConstructionArguments(expected_min, expected_max,
63                                               expected_bucket_count))
64        << histogram_name << "/" << expected_min << "/" << expected_max << "/"
65        << expected_bucket_count << " vs. "
66        << HistogramConstructionParamsToString(histogram);
67  }
68
69  HistogramBase* BooleanHistogram(JNIEnv* env,
70                                  jstring j_histogram_name,
71                                  jlong j_histogram_key) {
72    DCHECK(j_histogram_name);
73    HistogramBase* histogram = HistogramFromKey(j_histogram_key);
74    if (histogram)
75      return histogram;
76
77    std::string histogram_name = ConvertJavaStringToUTF8(env, j_histogram_name);
78    histogram = BooleanHistogram::FactoryGet(
79        histogram_name, HistogramBase::kUmaTargetedHistogramFlag);
80    return histogram;
81  }
82
83  HistogramBase* EnumeratedHistogram(JNIEnv* env,
84                                     jstring j_histogram_name,
85                                     jlong j_histogram_key,
86                                     jint j_boundary) {
87    DCHECK(j_histogram_name);
88    HistogramBase* histogram = HistogramFromKey(j_histogram_key);
89    int32_t boundary = static_cast<int32_t>(j_boundary);
90    if (histogram) {
91      CheckHistogramArgs(env, j_histogram_name, 1, boundary, boundary + 1,
92                         histogram);
93      return histogram;
94    }
95
96    std::string histogram_name = ConvertJavaStringToUTF8(env, j_histogram_name);
97    histogram =
98        LinearHistogram::FactoryGet(histogram_name, 1, boundary, boundary + 1,
99                                    HistogramBase::kUmaTargetedHistogramFlag);
100    return histogram;
101  }
102
103  HistogramBase* CustomCountHistogram(JNIEnv* env,
104                                      jstring j_histogram_name,
105                                      jlong j_histogram_key,
106                                      jint j_min,
107                                      jint j_max,
108                                      jint j_num_buckets) {
109    DCHECK(j_histogram_name);
110    int32_t min = static_cast<int32_t>(j_min);
111    int32_t max = static_cast<int32_t>(j_max);
112    int32_t num_buckets = static_cast<int32_t>(j_num_buckets);
113    HistogramBase* histogram = HistogramFromKey(j_histogram_key);
114    if (histogram) {
115      CheckHistogramArgs(env, j_histogram_name, min, max, num_buckets,
116                         histogram);
117      return histogram;
118    }
119
120    DCHECK_GE(min, 1) << "The min expected sample must be >= 1";
121
122    std::string histogram_name = ConvertJavaStringToUTF8(env, j_histogram_name);
123    histogram =
124        Histogram::FactoryGet(histogram_name, min, max, num_buckets,
125                              HistogramBase::kUmaTargetedHistogramFlag);
126    return histogram;
127  }
128
129  HistogramBase* LinearCountHistogram(JNIEnv* env,
130                                      jstring j_histogram_name,
131                                      jlong j_histogram_key,
132                                      jint j_min,
133                                      jint j_max,
134                                      jint j_num_buckets) {
135    DCHECK(j_histogram_name);
136    int32_t min = static_cast<int32_t>(j_min);
137    int32_t max = static_cast<int32_t>(j_max);
138    int32_t num_buckets = static_cast<int32_t>(j_num_buckets);
139    HistogramBase* histogram = HistogramFromKey(j_histogram_key);
140    if (histogram) {
141      CheckHistogramArgs(env, j_histogram_name, min, max, num_buckets,
142                         histogram);
143      return histogram;
144    }
145
146    std::string histogram_name = ConvertJavaStringToUTF8(env, j_histogram_name);
147    histogram =
148        LinearHistogram::FactoryGet(histogram_name, min, max, num_buckets,
149                                    HistogramBase::kUmaTargetedHistogramFlag);
150    return histogram;
151  }
152
153  HistogramBase* SparseHistogram(JNIEnv* env,
154                                 jstring j_histogram_name,
155                                 jlong j_histogram_key) {
156    DCHECK(j_histogram_name);
157    HistogramBase* histogram = HistogramFromKey(j_histogram_key);
158    if (histogram)
159      return histogram;
160
161    std::string histogram_name = ConvertJavaStringToUTF8(env, j_histogram_name);
162    histogram = SparseHistogram::FactoryGet(
163        histogram_name, HistogramBase::kUmaTargetedHistogramFlag);
164    return histogram;
165  }
166
167  HistogramBase* CustomTimesHistogram(JNIEnv* env,
168                                      jstring j_histogram_name,
169                                      jlong j_histogram_key,
170                                      jint j_min,
171                                      jint j_max,
172                                      jint j_bucket_count) {
173    DCHECK(j_histogram_name);
174    HistogramBase* histogram = HistogramFromKey(j_histogram_key);
175    int32_t min = static_cast<int32_t>(j_min);
176    int32_t max = static_cast<int32_t>(j_max);
177    int32_t bucket_count = static_cast<int32_t>(j_bucket_count);
178    if (histogram) {
179      CheckHistogramArgs(env, j_histogram_name, min, max, bucket_count,
180                         histogram);
181      return histogram;
182    }
183
184    std::string histogram_name = ConvertJavaStringToUTF8(env, j_histogram_name);
185    // This intentionally uses FactoryGet and not FactoryTimeGet. FactoryTimeGet
186    // is just a convenience for constructing the underlying Histogram with
187    // TimeDelta arguments.
188    histogram = Histogram::FactoryGet(histogram_name, min, max, bucket_count,
189                                      HistogramBase::kUmaTargetedHistogramFlag);
190    return histogram;
191  }
192
193 private:
194  // Convert a jlong |histogram_key| from Java to a HistogramBase* via a cast.
195  // The Java side caches these in a map (see RecordHistogram.java), which is
196  // safe to do since C++ Histogram objects are never freed.
197  static HistogramBase* HistogramFromKey(jlong j_histogram_key) {
198    return reinterpret_cast<HistogramBase*>(j_histogram_key);
199  }
200
201  DISALLOW_COPY_AND_ASSIGN(HistogramCache);
202};
203
204LazyInstance<HistogramCache>::Leaky g_histograms;
205
206}  // namespace
207
208jlong RecordBooleanHistogram(JNIEnv* env,
209                             const JavaParamRef<jclass>& clazz,
210                             const JavaParamRef<jstring>& j_histogram_name,
211                             jlong j_histogram_key,
212                             jboolean j_sample) {
213  bool sample = static_cast<bool>(j_sample);
214  HistogramBase* histogram = g_histograms.Get().BooleanHistogram(
215      env, j_histogram_name, j_histogram_key);
216  histogram->AddBoolean(sample);
217  return reinterpret_cast<jlong>(histogram);
218}
219
220jlong RecordEnumeratedHistogram(JNIEnv* env,
221                                const JavaParamRef<jclass>& clazz,
222                                const JavaParamRef<jstring>& j_histogram_name,
223                                jlong j_histogram_key,
224                                jint j_sample,
225                                jint j_boundary) {
226  int sample = static_cast<int>(j_sample);
227
228  HistogramBase* histogram = g_histograms.Get().EnumeratedHistogram(
229      env, j_histogram_name, j_histogram_key, j_boundary);
230  histogram->Add(sample);
231  return reinterpret_cast<jlong>(histogram);
232}
233
234jlong RecordCustomCountHistogram(JNIEnv* env,
235                                 const JavaParamRef<jclass>& clazz,
236                                 const JavaParamRef<jstring>& j_histogram_name,
237                                 jlong j_histogram_key,
238                                 jint j_sample,
239                                 jint j_min,
240                                 jint j_max,
241                                 jint j_num_buckets) {
242  int sample = static_cast<int>(j_sample);
243
244  HistogramBase* histogram = g_histograms.Get().CustomCountHistogram(
245      env, j_histogram_name, j_histogram_key, j_min, j_max, j_num_buckets);
246  histogram->Add(sample);
247  return reinterpret_cast<jlong>(histogram);
248}
249
250jlong RecordLinearCountHistogram(JNIEnv* env,
251                                 const JavaParamRef<jclass>& clazz,
252                                 const JavaParamRef<jstring>& j_histogram_name,
253                                 jlong j_histogram_key,
254                                 jint j_sample,
255                                 jint j_min,
256                                 jint j_max,
257                                 jint j_num_buckets) {
258  int sample = static_cast<int>(j_sample);
259
260  HistogramBase* histogram = g_histograms.Get().LinearCountHistogram(
261      env, j_histogram_name, j_histogram_key, j_min, j_max, j_num_buckets);
262  histogram->Add(sample);
263  return reinterpret_cast<jlong>(histogram);
264}
265
266jlong RecordSparseHistogram(JNIEnv* env,
267                            const JavaParamRef<jclass>& clazz,
268                            const JavaParamRef<jstring>& j_histogram_name,
269                            jlong j_histogram_key,
270                            jint j_sample) {
271  int sample = static_cast<int>(j_sample);
272  HistogramBase* histogram = g_histograms.Get().SparseHistogram(
273      env, j_histogram_name, j_histogram_key);
274  histogram->Add(sample);
275  return reinterpret_cast<jlong>(histogram);
276}
277
278jlong RecordCustomTimesHistogramMilliseconds(
279    JNIEnv* env,
280    const JavaParamRef<jclass>& clazz,
281    const JavaParamRef<jstring>& j_histogram_name,
282    jlong j_histogram_key,
283    jint j_duration,
284    jint j_min,
285    jint j_max,
286    jint j_num_buckets) {
287  HistogramBase* histogram = g_histograms.Get().CustomTimesHistogram(
288      env, j_histogram_name, j_histogram_key, j_min, j_max, j_num_buckets);
289  histogram->AddTime(
290      TimeDelta::FromMilliseconds(static_cast<int64_t>(j_duration)));
291  return reinterpret_cast<jlong>(histogram);
292}
293
294void Initialize(JNIEnv* env, const JavaParamRef<jclass>&) {
295  StatisticsRecorder::Initialize();
296}
297
298// This backs a Java test util for testing histograms -
299// MetricsUtils.HistogramDelta. It should live in a test-specific file, but we
300// currently can't have test-specific native code packaged in test-specific Java
301// targets - see http://crbug.com/415945.
302jint GetHistogramValueCountForTesting(
303    JNIEnv* env,
304    const JavaParamRef<jclass>& clazz,
305    const JavaParamRef<jstring>& histogram_name,
306    jint sample) {
307  HistogramBase* histogram = StatisticsRecorder::FindHistogram(
308      android::ConvertJavaStringToUTF8(env, histogram_name));
309  if (histogram == nullptr) {
310    // No samples have been recorded for this histogram (yet?).
311    return 0;
312  }
313
314  std::unique_ptr<HistogramSamples> samples = histogram->SnapshotSamples();
315  return samples->GetCount(static_cast<int>(sample));
316}
317
318bool RegisterRecordHistogram(JNIEnv* env) {
319  return RegisterNativesImpl(env);
320}
321
322}  // namespace android
323}  // namespace base
324