blob: aefcaae0718136b69beb33263b0b372f4e53dda5 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.components.background_task_scheduler;
import android.content.SharedPreferences;
import org.chromium.base.ContextUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.library_loader.LibraryLoader;
import org.chromium.base.metrics.RecordHistogram;
import java.util.HashSet;
import java.util.Set;
class BackgroundTaskSchedulerUma {
// BackgroundTaskId defined in tools/metrics/histograms/enums.xml
static final int BACKGROUND_TASK_TEST = 0;
static final int BACKGROUND_TASK_OMAHA = 1;
static final int BACKGROUND_TASK_GCM = 2;
static final int BACKGROUND_TASK_NOTIFICATIONS = 3;
static final int BACKGROUND_TASK_WEBVIEW_MINIDUMP = 4;
static final int BACKGROUND_TASK_CHROME_MINIDUMP = 5;
static final int BACKGROUND_TASK_OFFLINE_PAGES = 6;
static final int BACKGROUND_TASK_OFFLINE_PREFETCH = 7;
static final int BACKGROUND_TASK_DOWNLOAD_SERVICE = 8;
static final int BACKGROUND_TASK_DOWNLOAD_CLEANUP = 9;
static final int BACKGROUND_TASK_WEBVIEW_VARIATIONS = 10;
static final int BACKGROUND_TASK_OFFLINE_CONTENT_NOTIFICATION = 11;
static final int BACKGROUND_TASK_WEBAPK_UPDATE = 12;
static final int BACKGROUND_TASK_DOWNLOAD_RESUMPTION = 13;
static final int BACKGROUND_TASK_FEED_REFRESH = 14;
static final int BACKGROUND_TASK_COMPONENT_UPDATE = 15;
static final int BACKGROUND_TASK_EXPLORE_SITES_REFRESH = 16;
// Keep this one at the end and increment appropriately when adding new tasks.
static final int BACKGROUND_TASK_COUNT = 17;
static final String KEY_CACHED_UMA = "bts_cached_uma";
private static BackgroundTaskSchedulerUma sInstance;
private static class CachedUmaEntry {
private static final String SEPARATOR = ":";
private String mEvent;
private int mValue;
private int mCount;
/**
* Parses a cached UMA entry from a string.
*
* @param entry A serialized entry from preferences store.
* @return A parsed CachedUmaEntry object, or <c>null</c> if parsing failed.
*/
public static CachedUmaEntry parseEntry(String entry) {
if (entry == null) return null;
String[] entryParts = entry.split(SEPARATOR);
if (entryParts.length != 3 || entryParts[0].isEmpty() || entryParts[1].isEmpty()
|| entryParts[2].isEmpty()) {
return null;
}
int value = -1;
int count = -1;
try {
value = Integer.parseInt(entryParts[1]);
count = Integer.parseInt(entryParts[2]);
} catch (NumberFormatException e) {
return null;
}
return new CachedUmaEntry(entryParts[0], value, count);
}
/** Returns a string for partial matching of the prefs entry. */
public static String getStringForPartialMatching(String event, int value) {
return event + SEPARATOR + value + SEPARATOR;
}
public CachedUmaEntry(String event, int value, int count) {
mEvent = event;
mValue = value;
mCount = count;
}
/** Converts cached UMA entry to a string in format: EVENT:VALUE:COUNT. */
@Override
public String toString() {
return mEvent + SEPARATOR + mValue + SEPARATOR + mCount;
}
/** Gets the name of the event (UMA). */
public String getEvent() {
return mEvent;
}
/** Gets the value of the event (concrete value of the enum). */
public int getValue() {
return mValue;
}
/** Gets the count of events that happened. */
public int getCount() {
return mCount;
}
/** Increments the count of the event. */
public void increment() {
mCount++;
}
}
public static BackgroundTaskSchedulerUma getInstance() {
if (sInstance == null) {
sInstance = new BackgroundTaskSchedulerUma();
}
return sInstance;
}
@VisibleForTesting
public static void setInstanceForTesting(BackgroundTaskSchedulerUma instance) {
sInstance = instance;
}
/** Reports metrics for task scheduling and whether it was successful. */
public void reportTaskScheduled(int taskId, boolean success) {
if (success) {
cacheEvent("Android.BackgroundTaskScheduler.TaskScheduled.Success",
toUmaEnumValueFromTaskId(taskId));
} else {
cacheEvent("Android.BackgroundTaskScheduler.TaskScheduled.Failure",
toUmaEnumValueFromTaskId(taskId));
}
}
/** Reports metrics for task canceling. */
public void reportTaskCanceled(int taskId) {
cacheEvent(
"Android.BackgroundTaskScheduler.TaskCanceled", toUmaEnumValueFromTaskId(taskId));
}
/** Reports metrics for starting a task. */
public void reportTaskStarted(int taskId) {
cacheEvent("Android.BackgroundTaskScheduler.TaskStarted", toUmaEnumValueFromTaskId(taskId));
}
/** Reports metrics for stopping a task. */
public void reportTaskStopped(int taskId) {
cacheEvent("Android.BackgroundTaskScheduler.TaskStopped", toUmaEnumValueFromTaskId(taskId));
}
/** Reports metrics for when a NativeBackgroundTask loads the native library. */
public void reportTaskStartedNative(int taskId) {
cacheEvent("Android.BackgroundTaskScheduler.TaskLoadedNative",
toUmaEnumValueFromTaskId(taskId));
}
/** Method that actually invokes histogram recording. Extracted for testing. */
@VisibleForTesting
void recordEnumeratedHistogram(String histogram, int value, int maxCount) {
RecordHistogram.recordEnumeratedHistogram(histogram, value, maxCount);
}
/** Records histograms for cached stats. Should only be called when native is initialized. */
public void flushStats() {
assertNativeIsLoaded();
ThreadUtils.assertOnUiThread();
Set<String> cachedUmaStrings = getCachedUmaEntries(ContextUtils.getAppSharedPreferences());
for (String cachedUmaString : cachedUmaStrings) {
CachedUmaEntry entry = CachedUmaEntry.parseEntry(cachedUmaString);
if (entry == null) continue;
for (int i = 0; i < entry.getCount(); i++) {
recordEnumeratedHistogram(
entry.getEvent(), entry.getValue(), BACKGROUND_TASK_COUNT);
}
}
// Once all metrics are reported, we can simply remove the shared preference key.
removeCachedStats();
}
/** Removes all of the cached stats without reporting. */
public void removeCachedStats() {
ThreadUtils.assertOnUiThread();
ContextUtils.getAppSharedPreferences().edit().remove(KEY_CACHED_UMA).apply();
}
/** Caches the event to be reported through UMA in shared preferences. */
@VisibleForTesting
void cacheEvent(String event, int value) {
SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
Set<String> cachedUmaStrings = getCachedUmaEntries(prefs);
String partialMatch = CachedUmaEntry.getStringForPartialMatching(event, value);
String existingEntry = null;
for (String cachedUmaString : cachedUmaStrings) {
if (cachedUmaString.startsWith(partialMatch)) {
existingEntry = cachedUmaString;
break;
}
}
Set<String> setToWriteBack = new HashSet<>(cachedUmaStrings);
CachedUmaEntry entry = null;
if (existingEntry != null) {
entry = CachedUmaEntry.parseEntry(existingEntry);
if (entry == null) {
entry = new CachedUmaEntry(event, value, 1);
}
setToWriteBack.remove(existingEntry);
entry.increment();
} else {
entry = new CachedUmaEntry(event, value, 1);
}
setToWriteBack.add(entry.toString());
updateCachedUma(prefs, setToWriteBack);
}
@VisibleForTesting
static int toUmaEnumValueFromTaskId(int taskId) {
switch (taskId) {
case TaskIds.TEST:
return BACKGROUND_TASK_TEST;
case TaskIds.OMAHA_JOB_ID:
return BACKGROUND_TASK_OMAHA;
case TaskIds.GCM_BACKGROUND_TASK_JOB_ID:
return BACKGROUND_TASK_GCM;
case TaskIds.NOTIFICATION_SERVICE_JOB_ID:
return BACKGROUND_TASK_NOTIFICATIONS;
case TaskIds.WEBVIEW_MINIDUMP_UPLOADING_JOB_ID:
return BACKGROUND_TASK_WEBVIEW_MINIDUMP;
case TaskIds.CHROME_MINIDUMP_UPLOADING_JOB_ID:
return BACKGROUND_TASK_CHROME_MINIDUMP;
case TaskIds.OFFLINE_PAGES_BACKGROUND_JOB_ID:
return BACKGROUND_TASK_OFFLINE_PAGES;
case TaskIds.OFFLINE_PAGES_PREFETCH_JOB_ID:
return BACKGROUND_TASK_OFFLINE_PREFETCH;
case TaskIds.DOWNLOAD_SERVICE_JOB_ID:
return BACKGROUND_TASK_DOWNLOAD_SERVICE;
case TaskIds.DOWNLOAD_CLEANUP_JOB_ID:
return BACKGROUND_TASK_DOWNLOAD_CLEANUP;
case TaskIds.WEBVIEW_VARIATIONS_SEED_FETCH_JOB_ID:
return BACKGROUND_TASK_WEBVIEW_VARIATIONS;
case TaskIds.OFFLINE_PAGES_PREFETCH_NOTIFICATION_JOB_ID:
return BACKGROUND_TASK_OFFLINE_CONTENT_NOTIFICATION;
case TaskIds.WEBAPK_UPDATE_JOB_ID:
return BACKGROUND_TASK_WEBAPK_UPDATE;
case TaskIds.DOWNLOAD_RESUMPTION_JOB_ID:
return BACKGROUND_TASK_DOWNLOAD_RESUMPTION;
case TaskIds.FEED_REFRESH_JOB_ID:
return BACKGROUND_TASK_FEED_REFRESH;
case TaskIds.COMPONENT_UPDATE_JOB_ID:
return BACKGROUND_TASK_COMPONENT_UPDATE;
case TaskIds.EXPLORE_SITES_REFRESH_JOB_ID:
return BACKGROUND_TASK_EXPLORE_SITES_REFRESH;
default:
assert false;
}
// Returning a value that is not expected to ever be reported.
return BACKGROUND_TASK_TEST;
}
@VisibleForTesting
static Set<String> getCachedUmaEntries(SharedPreferences prefs) {
return prefs.getStringSet(KEY_CACHED_UMA, new HashSet<String>(1));
}
@VisibleForTesting
static void updateCachedUma(SharedPreferences prefs, Set<String> cachedUma) {
ThreadUtils.assertOnUiThread();
SharedPreferences.Editor editor = prefs.edit();
editor.putStringSet(KEY_CACHED_UMA, cachedUma);
editor.apply();
}
void assertNativeIsLoaded() {
assert LibraryLoader.getInstance().isInitialized();
}
}