blob: 9f77049431dbd45617c151f92d0477315d9bf408 [file] [log] [blame]
// Copyright 2014 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.
#include "content/browser/service_worker/service_worker_metrics.h"
#include <limits>
#include <string>
#include "base/bind.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/task/post_task.h"
#include "base/time/time.h"
#include "content/browser/service_worker/embedded_worker_status.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/content_client.h"
namespace content {
namespace {
const char* StartSituationToSuffix(
ServiceWorkerMetrics::StartSituation situation) {
// Don't change these returned strings. They are written (in hashed form) into
// logs.
switch (situation) {
case ServiceWorkerMetrics::StartSituation::UNKNOWN:
NOTREACHED();
return ".Unknown";
case ServiceWorkerMetrics::StartSituation::DURING_STARTUP:
return ".DuringStartup";
case ServiceWorkerMetrics::StartSituation::NEW_PROCESS:
return ".NewProcess";
case ServiceWorkerMetrics::StartSituation::EXISTING_UNREADY_PROCESS:
return ".ExistingUnreadyProcess";
case ServiceWorkerMetrics::StartSituation::EXISTING_READY_PROCESS:
return ".ExistingReadyProcess";
}
NOTREACHED() << static_cast<int>(situation);
return ".Unknown";
}
// TODO(falken): Remove this when the associated UMA are removed.
const char* StartSituationToDeprecatedSuffix(
ServiceWorkerMetrics::StartSituation situation) {
// Don't change this returned string. It is written (in hashed form) into
// logs.
switch (situation) {
case ServiceWorkerMetrics::StartSituation::UNKNOWN:
NOTREACHED();
return "_Unknown";
case ServiceWorkerMetrics::StartSituation::DURING_STARTUP:
return "_DuringStartup";
case ServiceWorkerMetrics::StartSituation::NEW_PROCESS:
return "_NewProcess";
case ServiceWorkerMetrics::StartSituation::EXISTING_UNREADY_PROCESS:
return "_ExistingUnreadyProcess";
case ServiceWorkerMetrics::StartSituation::EXISTING_READY_PROCESS:
return "_ExistingReadyProcess";
}
NOTREACHED() << static_cast<int>(situation);
return "_Unknown";
}
const char* EventTypeToSuffix(ServiceWorkerMetrics::EventType event_type) {
// Don't change these returned strings. They are written (in hashed form) into
// logs.
switch (event_type) {
case ServiceWorkerMetrics::EventType::ACTIVATE:
return "_ACTIVATE";
case ServiceWorkerMetrics::EventType::INSTALL:
return "_INSTALL";
case ServiceWorkerMetrics::EventType::SYNC:
return "_SYNC";
case ServiceWorkerMetrics::EventType::NOTIFICATION_CLICK:
return "_NOTIFICATION_CLICK";
case ServiceWorkerMetrics::EventType::PUSH:
return "_PUSH";
case ServiceWorkerMetrics::EventType::MESSAGE:
return "_MESSAGE";
case ServiceWorkerMetrics::EventType::NOTIFICATION_CLOSE:
return "_NOTIFICATION_CLOSE";
case ServiceWorkerMetrics::EventType::FETCH_MAIN_FRAME:
return "_FETCH_MAIN_FRAME";
case ServiceWorkerMetrics::EventType::FETCH_SUB_FRAME:
return "_FETCH_SUB_FRAME";
case ServiceWorkerMetrics::EventType::FETCH_SHARED_WORKER:
return "_FETCH_SHARED_WORKER";
case ServiceWorkerMetrics::EventType::FETCH_SUB_RESOURCE:
return "_FETCH_SUB_RESOURCE";
case ServiceWorkerMetrics::EventType::UNKNOWN:
return "_UNKNOWN";
case ServiceWorkerMetrics::EventType::FETCH_WAITUNTIL:
return "_FETCH_WAITUNTIL";
case ServiceWorkerMetrics::EventType::EXTERNAL_REQUEST:
return "_EXTERNAL_REQUEST";
case ServiceWorkerMetrics::EventType::PAYMENT_REQUEST:
return "_PAYMENT_REQUEST";
case ServiceWorkerMetrics::EventType::BACKGROUND_FETCH_ABORT:
return "_BACKGROUND_FETCH_ABORT";
case ServiceWorkerMetrics::EventType::BACKGROUND_FETCH_CLICK:
return "_BACKGROUND_FETCH_CLICK";
case ServiceWorkerMetrics::EventType::BACKGROUND_FETCH_FAIL:
return "_BACKGROUND_FETCH_FAIL";
case ServiceWorkerMetrics::EventType::NAVIGATION_HINT:
return "_NAVIGATION_HINT";
case ServiceWorkerMetrics::EventType::CAN_MAKE_PAYMENT:
return "_CAN_MAKE_PAYMENT";
case ServiceWorkerMetrics::EventType::ABORT_PAYMENT:
return "_ABORT_PAYMENT";
case ServiceWorkerMetrics::EventType::COOKIE_CHANGE:
return "_COOKIE_CHANGE";
case ServiceWorkerMetrics::EventType::BACKGROUND_FETCH_SUCCESS:
return "_BACKGROUND_FETCH_SUCCESS";
case ServiceWorkerMetrics::EventType::PERIODIC_SYNC:
return "_PERIODIC_SYNC";
case ServiceWorkerMetrics::EventType::CONTENT_DELETE:
return "_CONTENT_DELETE";
case ServiceWorkerMetrics::EventType::PUSH_SUBSCRIPTION_CHANGE:
return "_PUSH_SUBSCRIPTION_CHANGE";
}
return "_UNKNOWN";
}
} // namespace
const char* ServiceWorkerMetrics::EventTypeToString(EventType event_type) {
switch (event_type) {
case EventType::ACTIVATE:
return "Activate";
case EventType::INSTALL:
return "Install";
case EventType::SYNC:
return "Sync";
case EventType::NOTIFICATION_CLICK:
return "Notification Click";
case EventType::NOTIFICATION_CLOSE:
return "Notification Close";
case EventType::PUSH:
return "Push";
case EventType::MESSAGE:
return "Message";
case EventType::FETCH_MAIN_FRAME:
return "Fetch Main Frame";
case EventType::FETCH_SUB_FRAME:
return "Fetch Sub Frame";
case EventType::FETCH_SHARED_WORKER:
return "Fetch Shared Worker";
case EventType::FETCH_SUB_RESOURCE:
return "Fetch Subresource";
case EventType::UNKNOWN:
return "Unknown";
case EventType::FETCH_WAITUNTIL:
return "Fetch WaitUntil";
case EventType::EXTERNAL_REQUEST:
return "External Request";
case EventType::PAYMENT_REQUEST:
return "Payment Request";
case EventType::BACKGROUND_FETCH_ABORT:
return "Background Fetch Abort";
case EventType::BACKGROUND_FETCH_CLICK:
return "Background Fetch Click";
case EventType::BACKGROUND_FETCH_FAIL:
return "Background Fetch Fail";
case EventType::NAVIGATION_HINT:
return "Navigation Hint";
case EventType::CAN_MAKE_PAYMENT:
return "Can Make Payment";
case EventType::ABORT_PAYMENT:
return "Abort Payment";
case EventType::COOKIE_CHANGE:
return "Cookie Change";
case EventType::BACKGROUND_FETCH_SUCCESS:
return "Background Fetch Success";
case EventType::PERIODIC_SYNC:
return "Periodic Sync";
case EventType::CONTENT_DELETE:
return "Content Delete";
case EventType::PUSH_SUBSCRIPTION_CHANGE:
return "Push Subscription Change";
}
NOTREACHED() << "Got unexpected event type: " << static_cast<int>(event_type);
return "error";
}
const char* ServiceWorkerMetrics::StartSituationToString(
StartSituation start_situation) {
switch (start_situation) {
case StartSituation::UNKNOWN:
return "Unknown";
case StartSituation::DURING_STARTUP:
return "During startup";
case StartSituation::NEW_PROCESS:
return "New process";
case StartSituation::EXISTING_UNREADY_PROCESS:
return "Existing unready process";
case StartSituation::EXISTING_READY_PROCESS:
return "Existing ready process";
}
NOTREACHED() << "Got unexpected start situation: "
<< static_cast<int>(start_situation);
return "error";
}
ServiceWorkerMetrics::Site ServiceWorkerMetrics::SiteFromURL(const GURL& url) {
// TODO(falken): Plumb through ContentBrowserClient::GetMetricSuffixForURL or
// figure out a way to remove ServiceWorkerMetrics::Site entirely instead of
// hardcoding sites in //content.
// This inaccurately matches google.example.com, see the TODO above.
static const char google_like_scope_prefix[] = "https://www.google.";
static const char ntp_scope_path[] = "/_/chrome/";
if (base::StartsWith(url.spec(), google_like_scope_prefix,
base::CompareCase::INSENSITIVE_ASCII) &&
base::StartsWith(url.path(), ntp_scope_path,
base::CompareCase::SENSITIVE)) {
return ServiceWorkerMetrics::Site::NEW_TAB_PAGE;
}
const base::StringPiece host = url.host_piece();
if (host == "plus.google.com")
return ServiceWorkerMetrics::Site::PLUS;
if (host == "inbox.google.com")
return ServiceWorkerMetrics::Site::INBOX;
if (host == "docs.google.com")
return ServiceWorkerMetrics::Site::DOCS;
if (host == "drive.google.com") {
// TODO(falken): This should not be DOCS but historically we logged them
// together.
return ServiceWorkerMetrics::Site::DOCS;
}
return ServiceWorkerMetrics::Site::OTHER;
}
void ServiceWorkerMetrics::CountReadResponseResult(
ServiceWorkerMetrics::ReadResponseResult result) {
UMA_HISTOGRAM_ENUMERATION("ServiceWorker.DiskCache.ReadResponseResult",
result, NUM_READ_RESPONSE_RESULT_TYPES);
}
void ServiceWorkerMetrics::CountWriteResponseResult(
ServiceWorkerMetrics::WriteResponseResult result) {
UMA_HISTOGRAM_ENUMERATION("ServiceWorker.DiskCache.WriteResponseResult",
result, NUM_WRITE_RESPONSE_RESULT_TYPES);
}
void ServiceWorkerMetrics::CountControlledPageLoad(Site site,
bool is_main_frame_load) {
DCHECK_NE(site, Site::OTHER);
UMA_HISTOGRAM_ENUMERATION("ServiceWorker.PageLoad", site);
if (is_main_frame_load) {
UMA_HISTOGRAM_ENUMERATION("ServiceWorker.MainFramePageLoad", site);
}
}
void ServiceWorkerMetrics::RecordStartInstalledWorkerStatus(
blink::ServiceWorkerStatusCode status,
EventType purpose) {
UMA_HISTOGRAM_ENUMERATION("ServiceWorker.StartWorker.Status", status);
base::UmaHistogramEnumeration(
base::StrCat({"ServiceWorker.StartWorker.StatusByPurpose",
EventTypeToSuffix(purpose)}),
status);
UMA_HISTOGRAM_ENUMERATION("ServiceWorker.StartWorker.Purpose", purpose);
if (status == blink::ServiceWorkerStatusCode::kErrorTimeout) {
UMA_HISTOGRAM_ENUMERATION("ServiceWorker.StartWorker.Timeout.StartPurpose",
purpose);
}
}
void ServiceWorkerMetrics::RecordStartWorkerTime(base::TimeDelta time,
bool is_installed,
StartSituation start_situation,
EventType purpose) {
if (is_installed) {
UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.StartWorker.Time", time);
base::UmaHistogramMediumTimes(
base::StrCat({"ServiceWorker.StartWorker.Time",
StartSituationToDeprecatedSuffix(start_situation)}),
time);
base::UmaHistogramMediumTimes(
base::StrCat({"ServiceWorker.StartWorker.Time",
StartSituationToDeprecatedSuffix(start_situation),
EventTypeToSuffix(purpose)}),
time);
} else {
UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.StartNewWorker.Time", time);
}
}
void ServiceWorkerMetrics::RecordWorkerStopped(StopStatus status) {
UMA_HISTOGRAM_ENUMERATION("ServiceWorker.WorkerStopped", status);
}
void ServiceWorkerMetrics::RecordStopWorkerTime(base::TimeDelta time) {
UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.StopWorker.Time", time);
}
void ServiceWorkerMetrics::RecordActivateEventStatus(
blink::ServiceWorkerStatusCode status,
bool is_shutdown) {
UMA_HISTOGRAM_ENUMERATION("ServiceWorker.ActivateEventStatus", status);
if (is_shutdown) {
UMA_HISTOGRAM_ENUMERATION("ServiceWorker.ActivateEventStatus_InShutdown",
status);
} else {
UMA_HISTOGRAM_ENUMERATION("ServiceWorker.ActivateEventStatus_NotInShutdown",
status);
}
}
void ServiceWorkerMetrics::RecordInstallEventStatus(
blink::ServiceWorkerStatusCode status,
uint32_t fetch_count) {
base::UmaHistogramEnumeration("ServiceWorker.InstallEvent.All.Status",
status);
base::UmaHistogramCounts1000("ServiceWorker.InstallEvent.All.FetchCount",
fetch_count);
if (fetch_count > 0) {
base::UmaHistogramEnumeration("ServiceWorker.InstallEvent.WithFetch.Status",
status);
}
}
void ServiceWorkerMetrics::RecordEventDuration(EventType event,
base::TimeDelta time,
bool was_handled,
uint32_t fetch_count) {
switch (event) {
case EventType::ACTIVATE:
UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.ActivateEvent.Time", time);
break;
case EventType::INSTALL:
base::UmaHistogramMediumTimes("ServiceWorker.InstallEvent.All.Time",
time);
if (fetch_count) {
base::UmaHistogramMediumTimes(
"ServiceWorker.InstallEvent.WithFetch.Time", time);
}
break;
case EventType::FETCH_MAIN_FRAME:
case EventType::FETCH_SUB_FRAME:
case EventType::FETCH_SHARED_WORKER:
case EventType::FETCH_SUB_RESOURCE:
if (was_handled) {
UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.FetchEvent.HasResponse.Time",
time);
} else {
UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.FetchEvent.Fallback.Time",
time);
}
break;
case EventType::FETCH_WAITUNTIL:
UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.FetchEvent.WaitUntil.Time",
time);
break;
case EventType::SYNC:
UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.BackgroundSyncEvent.Time",
time);
break;
case EventType::NOTIFICATION_CLICK:
UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.NotificationClickEvent.Time",
time);
break;
case EventType::NOTIFICATION_CLOSE:
UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.NotificationCloseEvent.Time",
time);
break;
case EventType::PUSH:
UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.PushEvent.Time", time);
break;
case EventType::MESSAGE:
UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.ExtendableMessageEvent.Time",
time);
break;
case EventType::EXTERNAL_REQUEST:
UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.ExternalRequest.Time", time);
break;
case EventType::PAYMENT_REQUEST:
UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.PaymentRequestEvent.Time",
time);
break;
case EventType::BACKGROUND_FETCH_ABORT:
UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.BackgroundFetchAbortEvent.Time",
time);
break;
case EventType::BACKGROUND_FETCH_CLICK:
UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.BackgroundFetchClickEvent.Time",
time);
break;
case EventType::BACKGROUND_FETCH_FAIL:
UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.BackgroundFetchFailEvent.Time",
time);
break;
case EventType::BACKGROUND_FETCH_SUCCESS:
UMA_HISTOGRAM_MEDIUM_TIMES(
"ServiceWorker.BackgroundFetchSuccessEvent.Time", time);
break;
case EventType::CAN_MAKE_PAYMENT:
UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.CanMakePaymentEvent.Time",
time);
break;
case EventType::ABORT_PAYMENT:
UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.AbortPaymentEvent.Time", time);
break;
case EventType::COOKIE_CHANGE:
UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.CookieChangeEvent.Time", time);
break;
case EventType::PERIODIC_SYNC:
UMA_HISTOGRAM_MEDIUM_TIMES(
"ServiceWorker.PeriodicBackgroundSyncEvent.Time", time);
break;
case EventType::CONTENT_DELETE:
UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.ContentDeleteEvent.Time", time);
break;
case EventType::PUSH_SUBSCRIPTION_CHANGE:
UMA_HISTOGRAM_MEDIUM_TIMES(
"ServiceWorker.PushSubscriptionChangeEvent.Time", time);
break;
case EventType::NAVIGATION_HINT:
// The navigation hint should not be sent as an event.
case EventType::UNKNOWN:
NOTREACHED() << "Invalid event type";
break;
}
}
void ServiceWorkerMetrics::RecordFetchEventStatus(
bool is_main_resource,
blink::ServiceWorkerStatusCode status) {
if (is_main_resource) {
UMA_HISTOGRAM_ENUMERATION("ServiceWorker.FetchEvent.MainResource.Status",
status);
} else {
UMA_HISTOGRAM_ENUMERATION("ServiceWorker.FetchEvent.Subresource.Status",
status);
}
}
void ServiceWorkerMetrics::RecordStartWorkerTiming(const StartTimes& times,
StartSituation situation) {
// Bail if the timings across processes weren't consistent.
if (!base::TimeTicks::IsHighResolution() ||
!base::TimeTicks::IsConsistentAcrossProcesses()) {
RecordStartWorkerTimingClockConsistency(
CrossProcessTimeDelta::INACCURATE_CLOCK);
return;
}
if (times.remote_start_worker_received < times.local_start_worker_sent ||
times.local_end < times.remote_script_evaluation_end) {
RecordStartWorkerTimingClockConsistency(CrossProcessTimeDelta::NEGATIVE);
return;
}
RecordStartWorkerTimingClockConsistency(CrossProcessTimeDelta::NORMAL);
// Total duration.
UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.StartTiming.Duration",
times.local_end - times.local_start);
base::UmaHistogramMediumTimes(
base::StrCat({"ServiceWorker.StartTiming.Duration",
StartSituationToSuffix(situation)}),
times.local_end - times.local_start);
// SentStartWorker milestone.
UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.StartTiming.StartToSentStartWorker",
times.local_start_worker_sent - times.local_start);
// ReceivedStartWorker milestone.
UMA_HISTOGRAM_MEDIUM_TIMES(
"ServiceWorker.StartTiming.StartToReceivedStartWorker",
times.remote_start_worker_received - times.local_start);
UMA_HISTOGRAM_MEDIUM_TIMES(
"ServiceWorker.StartTiming.SentStartWorkerToReceivedStartWorker",
times.remote_start_worker_received - times.local_start_worker_sent);
// ScriptEvaluationStart milestone.
UMA_HISTOGRAM_MEDIUM_TIMES(
"ServiceWorker.StartTiming.StartToScriptEvaluationStart",
times.remote_script_evaluation_start - times.local_start);
UMA_HISTOGRAM_MEDIUM_TIMES(
"ServiceWorker.StartTiming.ReceivedStartWorkerToScriptEvaluationStart",
times.remote_script_evaluation_start -
times.remote_start_worker_received);
// ScriptEvaluationEnd milestone.
UMA_HISTOGRAM_MEDIUM_TIMES(
"ServiceWorker.StartTiming.StartToScriptEvaluationEnd",
times.remote_script_evaluation_end - times.local_start);
UMA_HISTOGRAM_MEDIUM_TIMES(
"ServiceWorker.StartTiming.ScriptEvaluationStartToScriptEvaluationEnd",
times.remote_script_evaluation_end -
times.remote_script_evaluation_start);
// End milestone.
UMA_HISTOGRAM_MEDIUM_TIMES(
"ServiceWorker.StartTiming.ScriptEvaluationEndToEnd",
times.local_end - times.remote_script_evaluation_end);
}
void ServiceWorkerMetrics::RecordStartWorkerTimingClockConsistency(
CrossProcessTimeDelta type) {
UMA_HISTOGRAM_ENUMERATION("ServiceWorker.StartTiming.ClockConsistency", type);
}
void ServiceWorkerMetrics::RecordStartStatusAfterFailure(
int failure_count,
blink::ServiceWorkerStatusCode status) {
DCHECK_GT(failure_count, 0);
if (status == blink::ServiceWorkerStatusCode::kOk) {
UMA_HISTOGRAM_COUNTS_1000("ServiceWorker.StartWorker.FailureStreakEnded",
failure_count);
} else if (failure_count < std::numeric_limits<int>::max()) {
UMA_HISTOGRAM_COUNTS_1000("ServiceWorker.StartWorker.FailureStreak",
failure_count + 1);
}
if (failure_count == 1) {
UMA_HISTOGRAM_ENUMERATION("ServiceWorker.StartWorker.AfterFailureStreak_1",
status);
} else if (failure_count == 2) {
UMA_HISTOGRAM_ENUMERATION("ServiceWorker.StartWorker.AfterFailureStreak_2",
status);
} else if (failure_count == 3) {
UMA_HISTOGRAM_ENUMERATION("ServiceWorker.StartWorker.AfterFailureStreak_3",
status);
}
}
void ServiceWorkerMetrics::RecordRuntime(base::TimeDelta time) {
// Start at 1 second since we expect service worker to last at least this
// long: the update timer and idle timeout timer run on the order of seconds.
constexpr base::TimeDelta kMin = base::TimeDelta::FromSeconds(1);
// End at 1 day since service workers can conceivably run as long as the the
// browser is open; we have to cap somewhere.
constexpr base::TimeDelta kMax = base::TimeDelta::FromDays(1);
// Set the bucket count to 50 since that is the recommended value for all
// histograms.
const int kBucketCount = 50;
UMA_HISTOGRAM_CUSTOM_TIMES("ServiceWorker.Runtime", time, kMin, kMax,
kBucketCount);
}
void ServiceWorkerMetrics::RecordStartServiceWorkerForNavigationHintResult(
StartServiceWorkerForNavigationHintResult result) {
UMA_HISTOGRAM_ENUMERATION("ServiceWorker.StartForNavigationHint.Result",
result);
}
void ServiceWorkerMetrics::RecordLookupRegistrationTime(
blink::ServiceWorkerStatusCode status,
base::TimeDelta duration) {
if (status == blink::ServiceWorkerStatusCode::kOk) {
UMA_HISTOGRAM_TIMES(
"ServiceWorker.LookupRegistration.MainResource.Time.Exists", duration);
} else if (status == blink::ServiceWorkerStatusCode::kErrorNotFound) {
UMA_HISTOGRAM_TIMES(
"ServiceWorker.LookupRegistration.MainResource.Time.DoesNotExist",
duration);
} else {
UMA_HISTOGRAM_TIMES(
"ServiceWorker.LookupRegistration.MainResource.Time.Error", duration);
}
}
} // namespace content