// Copyright 2019 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/android/callback_android.h" #include "base/android/jni_android.h" #include "base/android/jni_array.h" #include "base/android/jni_string.h" #include "base/format_macros.h" #include "base/metrics/histogram.h" #include "base/metrics/histogram_base.h" #include "base/metrics/sparse_histogram.h" #include "base/metrics/statistics_recorder.h" #include "base/metrics/user_metrics.h" #include "base/strings/stringprintf.h" #include "base/time/time.h" #include "build/robolectric_buildflags.h" // Must come after all headers that specialize FromJniType() / ToJniType(). #if BUILDFLAG(IS_ROBOLECTRIC) #include "base/base_robolectric_jni/NativeUmaRecorder_jni.h" // nogncheck #else #include "base/metrics_jni/NativeUmaRecorder_jni.h" #endif namespace base { namespace android { namespace { using HistogramsSnapshot = std::map>; std::string HistogramConstructionParamsToString(HistogramBase* histogram) { std::string params_str = histogram->histogram_name(); switch (histogram->GetHistogramType()) { case HISTOGRAM: case LINEAR_HISTOGRAM: case BOOLEAN_HISTOGRAM: case CUSTOM_HISTOGRAM: { Histogram* hist = static_cast(histogram); params_str += StringPrintf("/%d/%d/%" PRIuS, hist->declared_min(), hist->declared_max(), hist->bucket_count()); break; } case SPARSE_HISTOGRAM: case DUMMY_HISTOGRAM: break; } return params_str; } // Convert a jlong |histogram_hint| from Java to a HistogramBase* via a cast. // The Java side caches these in a map (see NativeUmaRecorder.java), which is // safe to do since C++ Histogram objects are never freed. static HistogramBase* HistogramFromHint(jlong j_histogram_hint) { return reinterpret_cast(j_histogram_hint); } void CheckHistogramArgs(JNIEnv* env, jstring j_histogram_name, int32_t expected_min, int32_t expected_max, size_t expected_bucket_count, HistogramBase* histogram) { std::string histogram_name = ConvertJavaStringToUTF8(env, j_histogram_name); bool valid_arguments = Histogram::InspectConstructionArguments( histogram_name, &expected_min, &expected_max, &expected_bucket_count); DCHECK(valid_arguments); DCHECK(histogram->HasConstructionArguments(expected_min, expected_max, expected_bucket_count)) << histogram_name << "/" << expected_min << "/" << expected_max << "/" << expected_bucket_count << " vs. " << HistogramConstructionParamsToString(histogram); } HistogramBase* BooleanHistogram(JNIEnv* env, jstring j_histogram_name, jlong j_histogram_hint) { DCHECK(j_histogram_name); HistogramBase* histogram = HistogramFromHint(j_histogram_hint); if (histogram) return histogram; std::string histogram_name = ConvertJavaStringToUTF8(env, j_histogram_name); histogram = BooleanHistogram::FactoryGet( histogram_name, HistogramBase::kUmaTargetedHistogramFlag); return histogram; } HistogramBase* ExponentialHistogram(JNIEnv* env, jstring j_histogram_name, jlong j_histogram_hint, jint j_min, jint j_max, jint j_num_buckets) { DCHECK(j_histogram_name); int32_t min = static_cast(j_min); int32_t max = static_cast(j_max); size_t num_buckets = static_cast(j_num_buckets); HistogramBase* histogram = HistogramFromHint(j_histogram_hint); if (histogram) { CheckHistogramArgs(env, j_histogram_name, min, max, num_buckets, histogram); return histogram; } DCHECK_GE(min, 1) << "The min expected sample must be >= 1"; std::string histogram_name = ConvertJavaStringToUTF8(env, j_histogram_name); histogram = Histogram::FactoryGet(histogram_name, min, max, num_buckets, HistogramBase::kUmaTargetedHistogramFlag); return histogram; } HistogramBase* LinearHistogram(JNIEnv* env, jstring j_histogram_name, jlong j_histogram_hint, jint j_min, jint j_max, jint j_num_buckets) { DCHECK(j_histogram_name); int32_t min = static_cast(j_min); int32_t max = static_cast(j_max); size_t num_buckets = static_cast(j_num_buckets); HistogramBase* histogram = HistogramFromHint(j_histogram_hint); if (histogram) { CheckHistogramArgs(env, j_histogram_name, min, max, num_buckets, histogram); return histogram; } std::string histogram_name = ConvertJavaStringToUTF8(env, j_histogram_name); histogram = LinearHistogram::FactoryGet(histogram_name, min, max, num_buckets, HistogramBase::kUmaTargetedHistogramFlag); return histogram; } HistogramBase* SparseHistogram(JNIEnv* env, jstring j_histogram_name, jlong j_histogram_hint) { DCHECK(j_histogram_name); HistogramBase* histogram = HistogramFromHint(j_histogram_hint); if (histogram) return histogram; std::string histogram_name = ConvertJavaStringToUTF8(env, j_histogram_name); histogram = SparseHistogram::FactoryGet( histogram_name, HistogramBase::kUmaTargetedHistogramFlag); return histogram; } struct ActionCallbackWrapper { base::ActionCallback action_callback; }; static void OnActionRecorded(const JavaRef& callback, const std::string& action, TimeTicks action_time) { RunStringCallbackAndroid(callback, action); } } // namespace jlong JNI_NativeUmaRecorder_RecordBooleanHistogram( JNIEnv* env, const JavaParamRef& j_histogram_name, jlong j_histogram_hint, jboolean j_sample) { bool sample = static_cast(j_sample); HistogramBase* histogram = BooleanHistogram(env, j_histogram_name, j_histogram_hint); histogram->AddBoolean(sample); return reinterpret_cast(histogram); } jlong JNI_NativeUmaRecorder_RecordExponentialHistogram( JNIEnv* env, const JavaParamRef& j_histogram_name, jlong j_histogram_hint, jint j_sample, jint j_min, jint j_max, jint j_num_buckets) { int sample = static_cast(j_sample); HistogramBase* histogram = ExponentialHistogram( env, j_histogram_name, j_histogram_hint, j_min, j_max, j_num_buckets); histogram->Add(sample); return reinterpret_cast(histogram); } jlong JNI_NativeUmaRecorder_RecordLinearHistogram( JNIEnv* env, const JavaParamRef& j_histogram_name, jlong j_histogram_hint, jint j_sample, jint j_min, jint j_max, jint j_num_buckets) { int sample = static_cast(j_sample); HistogramBase* histogram = LinearHistogram( env, j_histogram_name, j_histogram_hint, j_min, j_max, j_num_buckets); histogram->Add(sample); return reinterpret_cast(histogram); } jlong JNI_NativeUmaRecorder_RecordSparseHistogram( JNIEnv* env, const JavaParamRef& j_histogram_name, jlong j_histogram_hint, jint j_sample) { int sample = static_cast(j_sample); HistogramBase* histogram = SparseHistogram(env, j_histogram_name, j_histogram_hint); histogram->Add(sample); return reinterpret_cast(histogram); } void JNI_NativeUmaRecorder_RecordUserAction(JNIEnv* env, std::string& user_action_name, jlong j_millis_since_event) { // Time values coming from Java need to be synchronized with TimeTick clock. RecordComputedActionSince(user_action_name, Milliseconds(j_millis_since_event)); } // This backs a Java test util for testing histograms - // MetricsUtils.HistogramDelta. It should live in a test-specific file, but we // currently can't have test-specific native code packaged in test-specific Java // targets - see http://crbug.com/415945. jint JNI_NativeUmaRecorder_GetHistogramValueCountForTesting( JNIEnv* env, std::string& name, jint sample, jlong snapshot_ptr) { HistogramBase* histogram = StatisticsRecorder::FindHistogram(name); if (histogram == nullptr) { // No samples have been recorded for this histogram (yet?). return 0; } int actual_count = histogram->SnapshotSamples()->GetCount(sample); if (snapshot_ptr) { auto* snapshot = reinterpret_cast(snapshot_ptr); auto snapshot_data = snapshot->find(name); if (snapshot_data != snapshot->end()) actual_count -= snapshot_data->second->GetCount(sample); } return actual_count; } jint JNI_NativeUmaRecorder_GetHistogramTotalCountForTesting( JNIEnv* env, std::string& name, jlong snapshot_ptr) { HistogramBase* histogram = StatisticsRecorder::FindHistogram(name); if (histogram == nullptr) { // No samples have been recorded for this histogram. return 0; } int actual_count = histogram->SnapshotSamples()->TotalCount(); if (snapshot_ptr) { auto* snapshot = reinterpret_cast(snapshot_ptr); auto snapshot_data = snapshot->find(name); if (snapshot_data != snapshot->end()) actual_count -= snapshot_data->second->TotalCount(); } return actual_count; } // Returns an array with 3 entries for each bucket, representing (min, max, // count). ScopedJavaLocalRef JNI_NativeUmaRecorder_GetHistogramSamplesForTesting(JNIEnv* env, std::string& name) { HistogramBase* histogram = StatisticsRecorder::FindHistogram(name); std::vector buckets; if (histogram == nullptr) { // No samples have been recorded for this histogram. return base::android::ToJavaLongArray(env, buckets); } std::unique_ptr samples = histogram->SnapshotSamples(); for (auto sampleCountIterator = samples->Iterator(); !sampleCountIterator->Done(); sampleCountIterator->Next()) { HistogramBase::Sample min; int64_t max; HistogramBase::Count count; sampleCountIterator->Get(&min, &max, &count); buckets.push_back(min); buckets.push_back(max); buckets.push_back(count); } return base::android::ToJavaLongArray(env, buckets); } jlong JNI_NativeUmaRecorder_CreateHistogramSnapshotForTesting(JNIEnv* env) { HistogramsSnapshot* snapshot = new HistogramsSnapshot(); for (const auto* const histogram : StatisticsRecorder::GetHistograms()) { (*snapshot)[histogram->histogram_name()] = histogram->SnapshotSamples(); } return reinterpret_cast(snapshot); } void JNI_NativeUmaRecorder_DestroyHistogramSnapshotForTesting( JNIEnv* env, jlong snapshot_ptr) { delete reinterpret_cast(snapshot_ptr); } static jlong JNI_NativeUmaRecorder_AddActionCallbackForTesting( JNIEnv* env, const JavaParamRef& callback) { // Create a wrapper for the ActionCallback, so it can life on the heap until // RemoveActionCallbackForTesting() is called. auto* wrapper = new ActionCallbackWrapper{base::BindRepeating( &OnActionRecorded, ScopedJavaGlobalRef(env, callback))}; base::AddActionCallback(wrapper->action_callback); return reinterpret_cast(wrapper); } static void JNI_NativeUmaRecorder_RemoveActionCallbackForTesting( JNIEnv* env, jlong callback_id) { DCHECK(callback_id); auto* wrapper = reinterpret_cast(callback_id); base::RemoveActionCallback(wrapper->action_callback); delete wrapper; } } // namespace android } // namespace base