chromium/base/android/native_uma_recorder.cc

// 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, 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,
                                            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<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,
    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<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,
                                                    std::string& 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