diff options
Diffstat (limited to 'chromium/base/android/native_uma_recorder.cc')
-rw-r--r-- | chromium/base/android/native_uma_recorder.cc | 333 |
1 files changed, 333 insertions, 0 deletions
diff --git a/chromium/base/android/native_uma_recorder.cc b/chromium/base/android/native_uma_recorder.cc new file mode 100644 index 00000000000..4714fd94c5c --- /dev/null +++ b/chromium/base/android/native_uma_recorder.cc @@ -0,0 +1,333 @@ +// 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/base_jni_headers/NativeUmaRecorder_jni.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" + +namespace base { +namespace android { + +namespace { + +using HistogramsSnapshot = + std::map<std::string, std::unique_ptr<HistogramSamples>>; + +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*>(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<HistogramBase*>(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<int32_t>(j_min); + int32_t max = static_cast<int32_t>(j_max); + size_t num_buckets = static_cast<size_t>(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<int32_t>(j_min); + int32_t max = static_cast<int32_t>(j_max); + size_t num_buckets = static_cast<size_t>(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<jobject>& callback, + const std::string& action, + TimeTicks action_time) { + RunStringCallbackAndroid(callback, action); +} + +} // namespace + +jlong JNI_NativeUmaRecorder_RecordBooleanHistogram( + JNIEnv* env, + const JavaParamRef<jstring>& j_histogram_name, + jlong j_histogram_hint, + jboolean j_sample) { + bool sample = static_cast<bool>(j_sample); + HistogramBase* histogram = + BooleanHistogram(env, j_histogram_name, j_histogram_hint); + histogram->AddBoolean(sample); + return reinterpret_cast<jlong>(histogram); +} + +jlong 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) { + int sample = static_cast<int>(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<jlong>(histogram); +} + +jlong 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) { + int sample = static_cast<int>(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<jlong>(histogram); +} + +jlong JNI_NativeUmaRecorder_RecordSparseHistogram( + JNIEnv* env, + const JavaParamRef<jstring>& j_histogram_name, + jlong j_histogram_hint, + jint j_sample) { + int sample = static_cast<int>(j_sample); + HistogramBase* histogram = + SparseHistogram(env, j_histogram_name, j_histogram_hint); + histogram->Add(sample); + return reinterpret_cast<jlong>(histogram); +} + +void JNI_NativeUmaRecorder_RecordUserAction( + JNIEnv* env, + const JavaParamRef<jstring>& j_user_action_name, + jlong j_millis_since_event) { + // Time values coming from Java need to be synchronized with TimeTick clock. + RecordComputedActionSince(ConvertJavaStringToUTF8(env, j_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, + const JavaParamRef<jstring>& histogram_name, + jint sample, + jlong snapshot_ptr) { + std::string name = android::ConvertJavaStringToUTF8(env, histogram_name); + 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<HistogramsSnapshot*>(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, + const JavaParamRef<jstring>& histogram_name, + jlong snapshot_ptr) { + std::string name = android::ConvertJavaStringToUTF8(env, histogram_name); + 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<HistogramsSnapshot*>(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<jlongArray> +JNI_NativeUmaRecorder_GetHistogramSamplesForTesting( + JNIEnv* env, + const JavaParamRef<jstring>& histogram_name) { + std::string name = android::ConvertJavaStringToUTF8(env, histogram_name); + HistogramBase* histogram = StatisticsRecorder::FindHistogram(name); + std::vector<int64_t> buckets; + + if (histogram == nullptr) { + // No samples have been recorded for this histogram. + return base::android::ToJavaLongArray(env, buckets); + } + + std::unique_ptr<HistogramSamples> 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<intptr_t>(snapshot); +} + +void JNI_NativeUmaRecorder_DestroyHistogramSnapshotForTesting( + JNIEnv* env, + jlong snapshot_ptr) { + delete reinterpret_cast<HistogramsSnapshot*>(snapshot_ptr); +} + +static jlong JNI_NativeUmaRecorder_AddActionCallbackForTesting( + JNIEnv* env, + const JavaParamRef<jobject>& 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<jobject>(env, callback))}; + base::AddActionCallback(wrapper->action_callback); + return reinterpret_cast<intptr_t>(wrapper); +} + +static void JNI_NativeUmaRecorder_RemoveActionCallbackForTesting( + JNIEnv* env, + jlong callback_id) { + DCHECK(callback_id); + auto* wrapper = reinterpret_cast<ActionCallbackWrapper*>(callback_id); + base::RemoveActionCallback(wrapper->action_callback); + delete wrapper; +} + +} // namespace android +} // namespace base |