blob: 269ff089d067d17e8d940da3b0c853e46e066a31 [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 "chromecast/base/metrics/cast_metrics_helper.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/json/json_string_value_serializer.h"
#include "base/location.h"
#include "base/metrics/histogram.h"
#include "base/metrics/user_metrics.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chromecast/base/metrics/cast_histograms.h"
#include "chromecast/base/metrics/grouped_histogram.h"
namespace chromecast {
namespace metrics {
// A useful macro to make sure current member function runs on the valid thread.
#define MAKE_SURE_THREAD(callback, ...) \
if (!task_runner_->BelongsToCurrentThread()) { \
task_runner_->PostTask( \
FROM_HERE, base::BindOnce(&CastMetricsHelper::callback, \
base::Unretained(this), ##__VA_ARGS__)); \
return; \
}
namespace {
CastMetricsHelper* g_instance = NULL;
const char kMetricsNameAppInfoDelimiter = '#';
std::unique_ptr<std::string> SerializeToJson(const base::Value& value) {
std::unique_ptr<std::string> json_str(new std::string());
JSONStringValueSerializer serializer(json_str.get());
if (!serializer.Serialize(value))
json_str.reset(nullptr);
return json_str;
}
std::unique_ptr<base::DictionaryValue> CreateEventBase(
const std::string& name) {
std::unique_ptr<base::DictionaryValue> cast_event(
new base::DictionaryValue());
cast_event->SetString("name", name);
cast_event->SetDouble("time", base::TimeTicks::Now().ToInternalValue());
return cast_event;
}
} // namespace
// static
// NOTE(gfhuang): This is a hacky way to encode/decode app infos into a
// string. Mainly because it's hard to add another metrics serialization type
// into components/metrics/serialization/.
// static
bool CastMetricsHelper::DecodeAppInfoFromMetricsName(
const std::string& metrics_name,
std::string* action_name,
std::string* app_id,
std::string* session_id,
std::string* sdk_version) {
DCHECK(action_name);
DCHECK(app_id);
DCHECK(session_id);
DCHECK(sdk_version);
if (metrics_name.find(kMetricsNameAppInfoDelimiter) == std::string::npos)
return false;
std::vector<std::string> tokens = base::SplitString(
metrics_name, std::string(1, kMetricsNameAppInfoDelimiter),
base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
DCHECK_EQ(tokens.size(), 4u);
// The order of tokens should match EncodeAppInfoIntoMetricsName().
*action_name = tokens[0];
*app_id = tokens[1];
*session_id = tokens[2];
*sdk_version = tokens[3];
return true;
}
// static
std::string CastMetricsHelper::EncodeAppInfoIntoMetricsName(
const std::string& action_name,
const std::string& app_id,
const std::string& session_id,
const std::string& sdk_version) {
std::string result(action_name);
result.push_back(kMetricsNameAppInfoDelimiter);
result.append(app_id);
result.push_back(kMetricsNameAppInfoDelimiter);
result.append(session_id);
result.push_back(kMetricsNameAppInfoDelimiter);
result.append(sdk_version);
return result;
}
// static
CastMetricsHelper* CastMetricsHelper::GetInstance() {
DCHECK(g_instance);
return g_instance;
}
CastMetricsHelper::CastMetricsHelper(
scoped_refptr<base::SingleThreadTaskRunner> task_runner)
: task_runner_(task_runner),
metrics_sink_(NULL),
logged_first_audio_(false),
record_action_callback_(
base::BindRepeating(&base::RecordComputedAction)) {
DCHECK(task_runner_.get());
DCHECK(!g_instance);
g_instance = this;
}
CastMetricsHelper::CastMetricsHelper()
: metrics_sink_(NULL), logged_first_audio_(false) {
DCHECK(!g_instance);
g_instance = this;
}
CastMetricsHelper::~CastMetricsHelper() {
DCHECK_EQ(g_instance, this);
g_instance = NULL;
}
void CastMetricsHelper::DidStartLoad(const std::string& app_id) {
loading_app_id_ = app_id;
app_load_start_time_ = base::TimeTicks::Now();
}
void CastMetricsHelper::DidCompleteLoad(const std::string& app_id,
const std::string& session_id) {
MAKE_SURE_THREAD(DidCompleteLoad, app_id, session_id);
DCHECK_EQ(app_id, loading_app_id_);
loading_app_id_ = std::string();
app_id_ = app_id;
app_start_time_ = app_load_start_time_;
session_id_ = session_id;
logged_first_audio_ = false;
TagAppStartForGroupedHistograms(app_id_);
sdk_version_.clear();
}
void CastMetricsHelper::UpdateSDKInfo(const std::string& sdk_version) {
MAKE_SURE_THREAD(UpdateSDKInfo, sdk_version);
sdk_version_ = sdk_version;
}
void CastMetricsHelper::LogMediaPlay() {
MAKE_SURE_THREAD(LogMediaPlay);
RecordSimpleAction(EncodeAppInfoIntoMetricsName(
"MediaPlay",
app_id_,
session_id_,
sdk_version_));
}
void CastMetricsHelper::LogMediaPause() {
MAKE_SURE_THREAD(LogMediaPause);
RecordSimpleAction(EncodeAppInfoIntoMetricsName(
"MediaPause",
app_id_,
session_id_,
sdk_version_));
}
void CastMetricsHelper::LogTimeToFirstPaint() {
MAKE_SURE_THREAD(LogTimeToFirstPaint);
if (app_id_.empty())
return;
base::TimeDelta launch_time = base::TimeTicks::Now() - app_start_time_;
const std::string uma_name(GetMetricsNameWithAppName("Startup",
"TimeToFirstPaint"));
LogMediumTimeHistogramEvent(uma_name, launch_time);
LOG(INFO) << uma_name << " is " << launch_time.InSecondsF() << " seconds.";
}
void CastMetricsHelper::LogTimeToFirstAudio() {
MAKE_SURE_THREAD(LogTimeToFirstAudio);
if (logged_first_audio_)
return;
if (app_id_.empty())
return;
base::TimeDelta time_to_first_audio =
base::TimeTicks::Now() - app_start_time_;
const std::string uma_name(
GetMetricsNameWithAppName("Startup", "TimeToFirstAudio"));
LogMediumTimeHistogramEvent(uma_name, time_to_first_audio);
LOG(INFO) << uma_name << " is " << time_to_first_audio.InSecondsF()
<< " seconds.";
logged_first_audio_ = true;
}
void CastMetricsHelper::LogTimeToBufferAv(BufferingType buffering_type,
base::TimeDelta time) {
MAKE_SURE_THREAD(LogTimeToBufferAv, buffering_type, time);
if (time < base::TimeDelta::FromSeconds(0)) {
LOG(WARNING) << "Negative time";
return;
}
const std::string uma_name(GetMetricsNameWithAppName(
"Media",
(buffering_type == kInitialBuffering ? "TimeToBufferAv" :
buffering_type == kBufferingAfterUnderrun ?
"TimeToBufferAvAfterUnderrun" :
buffering_type == kAbortedBuffering ? "TimeToBufferAvAfterAbort" : "")));
// Histogram from 250ms to 30s with 50 buckets.
// The ratio between 2 consecutive buckets is:
// exp( (ln(30000) - ln(250)) / 50 ) = 1.1
LogTimeHistogramEvent(
uma_name,
time,
base::TimeDelta::FromMilliseconds(250),
base::TimeDelta::FromMilliseconds(30000),
50);
}
std::string CastMetricsHelper::GetMetricsNameWithAppName(
const std::string& prefix,
const std::string& suffix) const {
DCHECK(task_runner_->BelongsToCurrentThread());
std::string metrics_name(prefix);
if (!app_id_.empty()) {
if (!metrics_name.empty())
metrics_name.push_back('.');
metrics_name.append(app_id_);
}
if (!suffix.empty()) {
if (!metrics_name.empty())
metrics_name.push_back('.');
metrics_name.append(suffix);
}
return metrics_name;
}
void CastMetricsHelper::SetMetricsSink(MetricsSink* delegate) {
MAKE_SURE_THREAD(SetMetricsSink, delegate);
metrics_sink_ = delegate;
}
void CastMetricsHelper::SetRecordActionCallback(RecordActionCallback callback) {
DCHECK(task_runner_->BelongsToCurrentThread());
record_action_callback_ = std::move(callback);
}
void CastMetricsHelper::SetDummySessionIdForTesting() {
DCHECK(task_runner_->BelongsToCurrentThread());
session_id_ = "00000000-0000-0000-0000-000000000000";
}
void CastMetricsHelper::RecordSimpleAction(const std::string& action) {
MAKE_SURE_THREAD(RecordSimpleAction, action);
if (metrics_sink_) {
metrics_sink_->OnAction(action);
} else {
record_action_callback_.Run(action);
}
}
void CastMetricsHelper::LogEnumerationHistogramEvent(
const std::string& name, int value, int num_buckets) {
MAKE_SURE_THREAD(LogEnumerationHistogramEvent, name, value, num_buckets);
if (metrics_sink_) {
metrics_sink_->OnEnumerationEvent(name, value, num_buckets);
} else {
UMA_HISTOGRAM_ENUMERATION_NO_CACHE(name, value, num_buckets);
}
}
void CastMetricsHelper::LogTimeHistogramEvent(const std::string& name,
const base::TimeDelta& value,
const base::TimeDelta& min,
const base::TimeDelta& max,
int num_buckets) {
MAKE_SURE_THREAD(LogTimeHistogramEvent, name, value, min, max, num_buckets);
if (metrics_sink_) {
metrics_sink_->OnTimeEvent(name, value, min, max, num_buckets);
} else {
UMA_HISTOGRAM_CUSTOM_TIMES_NO_CACHE(name, value, min, max, num_buckets);
}
}
void CastMetricsHelper::LogMediumTimeHistogramEvent(
const std::string& name,
const base::TimeDelta& value) {
// Follow UMA_HISTOGRAM_MEDIUM_TIMES definition.
LogTimeHistogramEvent(name, value,
base::TimeDelta::FromMilliseconds(10),
base::TimeDelta::FromMinutes(3),
50);
}
void CastMetricsHelper::RecordEventWithValue(const std::string& event,
int value) {
std::unique_ptr<base::DictionaryValue> cast_event(CreateEventBase(event));
cast_event->SetInteger("value", value);
const std::string message = *SerializeToJson(*cast_event);
RecordSimpleAction(message);
}
void CastMetricsHelper::RecordApplicationEvent(const std::string& event) {
std::unique_ptr<base::DictionaryValue> cast_event(CreateEventBase(event));
cast_event->SetString("app_id", app_id_);
cast_event->SetString("session_id", session_id_);
cast_event->SetString("sdk_version", sdk_version_);
const std::string message = *SerializeToJson(*cast_event);
RecordSimpleAction(message);
}
void CastMetricsHelper::RecordApplicationEventWithValue(
const std::string& event,
int value) {
std::unique_ptr<base::DictionaryValue> cast_event(CreateEventBase(event));
cast_event->SetString("app_id", app_id_);
cast_event->SetString("session_id", session_id_);
cast_event->SetString("sdk_version", sdk_version_);
cast_event->SetInteger("value", value);
const std::string message = *SerializeToJson(*cast_event);
RecordSimpleAction(message);
}
} // namespace metrics
} // namespace chromecast