blob: 9d29a87b751541c2d9f9de0209fdca7d91be72aa [file] [log] [blame]
// 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.
package org.chromium.base.metrics;
import org.chromium.base.Callback;
import org.chromium.base.TimeUtils;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.build.annotations.MainDex;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* An implementation of {@link UmaRecorder} which forwards all calls through JNI.
*
* Note: the JNI calls are relatively costly - avoid calling these methods in performance-critical
* code.
*/
@JNINamespace("base::android")
@MainDex
/* package */ final class NativeUmaRecorder implements UmaRecorder {
/**
* Internally, histograms objects are cached on the Java side by their pointer
* values (converted to long). This is safe to do because C++ Histogram objects
* are never freed. Caching them on the Java side prevents needing to do costly
* Java String to C++ string conversions on the C++ side during lookup.
*/
private final Map<String, Long> mNativeHints =
Collections.synchronizedMap(new HashMap<String, Long>());
private Map<Callback<String>, Long> mUserActionTestingCallbackNativePtrs;
@Override
public void recordBooleanHistogram(String name, boolean sample) {
long oldHint = getNativeHint(name);
long newHint = NativeUmaRecorderJni.get().recordBooleanHistogram(name, oldHint, sample);
maybeUpdateNativeHint(name, oldHint, newHint);
}
@Override
public void recordExponentialHistogram(
String name, int sample, int min, int max, int numBuckets) {
long oldHint = getNativeHint(name);
long newHint = NativeUmaRecorderJni.get().recordExponentialHistogram(
name, oldHint, sample, min, max, numBuckets);
maybeUpdateNativeHint(name, oldHint, newHint);
}
@Override
public void recordLinearHistogram(String name, int sample, int min, int max, int numBuckets) {
long oldHint = getNativeHint(name);
long newHint = NativeUmaRecorderJni.get().recordLinearHistogram(
name, oldHint, sample, min, max, numBuckets);
maybeUpdateNativeHint(name, oldHint, newHint);
}
@Override
public void recordSparseHistogram(String name, int sample) {
long oldHint = getNativeHint(name);
long newHint = NativeUmaRecorderJni.get().recordSparseHistogram(name, oldHint, sample);
maybeUpdateNativeHint(name, oldHint, newHint);
}
@Override
public void recordUserAction(String name, long elapsedRealtimeMillis) {
// Java and native code use different clocks. We need a relative elapsed time.
long millisSinceEvent = TimeUtils.elapsedRealtimeMillis() - elapsedRealtimeMillis;
NativeUmaRecorderJni.get().recordUserAction(name, millisSinceEvent);
}
@Override
public int getHistogramValueCountForTesting(String name, int sample) {
return NativeUmaRecorderJni.get().getHistogramValueCountForTesting(name, sample, 0);
}
@Override
public int getHistogramTotalCountForTesting(String name) {
return NativeUmaRecorderJni.get().getHistogramTotalCountForTesting(name, 0);
}
@Override
public void addUserActionCallbackForTesting(Callback<String> callback) {
long ptr = NativeUmaRecorderJni.get().addActionCallbackForTesting(callback);
if (mUserActionTestingCallbackNativePtrs == null) {
mUserActionTestingCallbackNativePtrs = Collections.synchronizedMap(new HashMap<>());
}
mUserActionTestingCallbackNativePtrs.put(callback, ptr);
}
@Override
public void removeUserActionCallbackForTesting(Callback<String> callback) {
if (mUserActionTestingCallbackNativePtrs == null) {
assert false
: "Attempting to remove a user action callback without previously registering any.";
return;
}
Long ptr = mUserActionTestingCallbackNativePtrs.remove(callback);
if (ptr == null) {
assert false : "Attempting to remove a user action callback that was never previously"
+ " registered.";
return;
}
NativeUmaRecorderJni.get().removeActionCallbackForTesting(ptr);
}
private long getNativeHint(String name) {
Long hint = mNativeHints.get(name);
// Note: If key is null, we don't have it cached. In that case, pass 0
// to the native code, which gets converted to a null histogram pointer
// which will cause the native code to look up the object on the native
// side.
return (hint == null ? 0 : hint);
}
private void maybeUpdateNativeHint(String name, long oldHint, long newHint) {
if (oldHint != newHint) {
mNativeHints.put(name, newHint);
}
}
/** Natives API to record metrics. */
@NativeMethods
public interface Natives {
long recordBooleanHistogram(String name, long nativeHint, boolean sample);
long recordExponentialHistogram(
String name, long nativeHint, int sample, int min, int max, int numBuckets);
long recordLinearHistogram(
String name, long nativeHint, int sample, int min, int max, int numBuckets);
long recordSparseHistogram(String name, long nativeHint, int sample);
/**
* Records that the user performed an action. See {@code base::RecordComputedActionAt}.
* <p>
* Uses relative time, because Java and native code can use different clocks.
*
* @param name Name of the user-generated event.
* @param millisSinceEvent difference between now and the time when the event was observed.
* Should be positive.
*/
void recordUserAction(String name, long millisSinceEvent);
int getHistogramValueCountForTesting(String name, int sample, long snapshotPtr);
int getHistogramTotalCountForTesting(String name, long snapshotPtr);
long createHistogramSnapshotForTesting();
void destroyHistogramSnapshotForTesting(long snapshotPtr);
long addActionCallbackForTesting(Callback<String> callback);
void removeActionCallbackForTesting(long callbackId);
}
}