| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chromecast/cast_core/runtime/browser/cast_runtime_metrics_recorder.h" |
| |
| #include <cfloat> |
| #include <cmath> |
| |
| #include "base/json/json_string_value_serializer.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_base.h" |
| #include "base/metrics/sparse_histogram.h" |
| #include "base/metrics/user_metrics.h" |
| #include "base/notreached.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/time/time.h" |
| #include "base/values.h" |
| #include "chromecast/base/metrics/cast_histograms.h" |
| #include "chromecast/base/metrics/cast_metrics_helper.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "third_party/metrics_proto/cast_logs.pb.h" |
| |
| namespace chromecast { |
| namespace { |
| |
| // The following are used by cast events created from a JSON format string. |
| constexpr char kEventName[] = "name"; |
| constexpr char kEventAppId[] = "app_id"; |
| constexpr char kEventSessionId[] = "session_id"; |
| constexpr char kEventSdkVersion[] = "sdk_version"; |
| constexpr char kEventTime[] = "time"; |
| constexpr char kEventValue[] = "value"; |
| constexpr char kEventEventsPair[] = "events"; |
| constexpr char kEventAuditReport[] = "audit_report"; |
| |
| constexpr int64_t kInt64DoubleMax = 1LL << DBL_MANT_DIG; |
| |
| constexpr size_t kCastEventLimit = 1000000; |
| |
| void PopulateEventBuilder(CastEventBuilder* event_builder, |
| double event_time, |
| const std::string& app_id, |
| const std::string& sdk_version, |
| const std::string& session_id) { |
| event_builder->SetTime(base::TimeTicks() + base::Microseconds(event_time)); |
| if (!app_id.empty()) |
| event_builder->SetAppId(app_id); |
| if (!sdk_version.empty()) |
| event_builder->SetSdkVersion(sdk_version); |
| if (!session_id.empty()) |
| event_builder->SetSessionId(session_id); |
| } |
| |
| } // namespace |
| |
| CastRuntimeMetricsRecorder::EventBuilderFactory::~EventBuilderFactory() = |
| default; |
| |
| // NOTE: This is the same as MetricsRecorderImpl but it seems improper to mix |
| // MetricsRecorderImpl's static functions when this is the actual |
| // MetricsRecorder instance that is live. |
| // static |
| void CastRuntimeMetricsRecorder::RecordSimpleActionWithValue( |
| const std::string& action, |
| int64_t value) { |
| MetricsRecorder* recorder = MetricsRecorder::GetInstance(); |
| std::unique_ptr<CastEventBuilder> event_builder( |
| recorder->CreateEventBuilder(action)); |
| event_builder->SetExtraValue(value); |
| recorder->RecordCastEvent(std::move(event_builder)); |
| } |
| |
| CastRuntimeMetricsRecorder::CastRuntimeMetricsRecorder( |
| EventBuilderFactory* event_builder_factory) |
| : task_runner_(base::SequencedTaskRunner::GetCurrentDefault()), |
| event_builder_factory_(event_builder_factory) { |
| DCHECK(event_builder_factory_); |
| |
| DCHECK(!MetricsRecorder::GetInstance()); |
| MetricsRecorder::SetInstance(this); |
| metrics::CastMetricsHelper::GetInstance()->SetRecordActionCallback( |
| base::BindRepeating( |
| &CastRuntimeMetricsRecorder::CastMetricsHelperRecordActionCallback, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| CastRuntimeMetricsRecorder::~CastRuntimeMetricsRecorder() { |
| DCHECK(MetricsRecorder::GetInstance() == this); |
| metrics::CastMetricsHelper::GetInstance()->SetRecordActionCallback( |
| base::BindRepeating(&base::RecordComputedAction)); |
| MetricsRecorder::SetInstance(nullptr); |
| } |
| |
| std::vector<cast::metrics::Event> CastRuntimeMetricsRecorder::TakeEvents() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return std::move(events_); |
| } |
| |
| std::unique_ptr<CastEventBuilder> |
| CastRuntimeMetricsRecorder::CreateEventBuilder(const std::string& name) { |
| auto builder = event_builder_factory_->CreateEventBuilder(); |
| builder->SetName(name); |
| return builder; |
| } |
| |
| void CastRuntimeMetricsRecorder::AddActiveConnection( |
| const std::string& transport_connection_id, |
| const std::string& virtual_connection_id, |
| const base::Value& sender_info, |
| const net::IPAddressBytes& sender_ip) { |
| NOTIMPLEMENTED(); |
| } |
| |
| void CastRuntimeMetricsRecorder::RemoveActiveConnection( |
| const std::string& connection_id) { |
| NOTIMPLEMENTED(); |
| } |
| |
| void CastRuntimeMetricsRecorder::RecordCastEvent( |
| std::unique_ptr<CastEventBuilder> event_builder) { |
| if (task_runner_->RunsTasksInCurrentSequence()) { |
| DVLOG(1) << "RecordCastEvent direct"; |
| RecordCastEventOnSequence(std::move(event_builder)); |
| } else { |
| DVLOG(1) << "RecordCastEvent bounce"; |
| task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&CastRuntimeMetricsRecorder::RecordCastEventOnSequence, |
| weak_factory_.GetWeakPtr(), std::move(event_builder))); |
| } |
| } |
| |
| void CastRuntimeMetricsRecorder::RecordCastEventOnSequence( |
| std::unique_ptr<CastEventBuilder> event_builder) { |
| DVLOG(1) << "RecordCastEventOnSequence"; |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (events_.size() >= kCastEventLimit) { |
| static bool logged_once = false; |
| if (!logged_once) { |
| LOG(ERROR) << "Too many events queued, dropping..."; |
| logged_once = true; |
| } |
| return; |
| } |
| std::unique_ptr<::metrics::CastLogsProto_CastEventProto> event_proto( |
| event_builder->Build()); |
| std::string str; |
| if (!event_proto->SerializeToString(&str)) { |
| LOG(ERROR) << "Failed to serialize CastEventProto"; |
| return; |
| } |
| |
| cast::metrics::Event e; |
| e.mutable_impl_defined_event()->set_name(event_builder->GetName()); |
| e.mutable_impl_defined_event()->set_data(str); |
| events_.push_back(std::move(e)); |
| } |
| |
| void CastRuntimeMetricsRecorder::RecordHistogramTime(const std::string& name, |
| int sample, |
| int min, |
| int max, |
| int num_buckets) { |
| UMA_HISTOGRAM_CUSTOM_TIMES_NO_CACHE(name, base::Milliseconds(sample), |
| base::Milliseconds(min), |
| base::Milliseconds(max), num_buckets); |
| } |
| |
| void CastRuntimeMetricsRecorder::RecordHistogramCount(const std::string& name, |
| int sample, |
| int min, |
| int max, |
| int num_buckets) { |
| UMA_HISTOGRAM_CUSTOM_COUNTS_NO_CACHE(name, sample, min, max, num_buckets, |
| /* count */ 1); |
| } |
| |
| void CastRuntimeMetricsRecorder::RecordHistogramCountRepeated( |
| const std::string& name, |
| int sample, |
| int min, |
| int max, |
| int num_buckets, |
| int count) { |
| UMA_HISTOGRAM_CUSTOM_COUNTS_NO_CACHE(name, sample, min, max, num_buckets, |
| count); |
| } |
| |
| void CastRuntimeMetricsRecorder::RecordHistogramEnum(const std::string& name, |
| int sample, |
| int boundary) { |
| UMA_HISTOGRAM_ENUMERATION_NO_CACHE(name, sample, boundary); |
| } |
| |
| void CastRuntimeMetricsRecorder::RecordHistogramSparse(const std::string& name, |
| int sample) { |
| base::HistogramBase* counter = base::SparseHistogram::FactoryGet( |
| name, base::HistogramBase::kUmaTargetedHistogramFlag); |
| counter->Add(sample); |
| } |
| |
| void CastRuntimeMetricsRecorder::CastMetricsHelperRecordActionCallback( |
| const std::string& action) { |
| DVLOG(1) << "Record action via CastMetricsHelper: " << action; |
| std::string action_name; |
| std::string app_id; |
| std::string session_id; |
| std::string sdk_version; |
| if (metrics::CastMetricsHelper::DecodeAppInfoFromMetricsName( |
| action, &action_name, &app_id, &session_id, &sdk_version)) { |
| if (app_id.empty() && session_id.empty()) { |
| LOG(ERROR) << "Missing app and session ID"; |
| return; |
| } |
| std::unique_ptr<CastEventBuilder> event_builder( |
| CreateEventBuilder(action_name)); |
| event_builder->SetAppId(app_id) |
| .SetSessionId(session_id) |
| .SetSdkVersion(sdk_version); |
| RecordCastEvent(std::move(event_builder)); |
| return; |
| } |
| |
| // Tries to parse and record event string as JSON event. |
| if (RecordJsonCastEvent(action)) { |
| return; |
| } |
| |
| // This is normal non-JSON user action event. |
| RecordCastEvent(CreateEventBuilder(action)); |
| } |
| |
| bool CastRuntimeMetricsRecorder::RecordJsonCastEvent(const std::string& event) { |
| std::unique_ptr<const base::Value> value( |
| JSONStringValueDeserializer(event).Deserialize(nullptr, nullptr)); |
| if (!value) { |
| LOG(ERROR) << "This is not a JSON format event: " << event; |
| return false; |
| } |
| |
| if (!value->is_dict()) { |
| LOG(ERROR) << "This is not a dictionary JSON format event: " << event; |
| return false; |
| } |
| |
| const base::Value::Dict& value_dict = value->GetDict(); |
| const std::string* name = value_dict.FindString(kEventName); |
| if (!name) { |
| LOG(ERROR) << "Missing field:" << kEventName; |
| return false; |
| } |
| |
| // Gets event creation time. If unavailable use now. |
| absl::optional<double> maybe_event_time = value_dict.FindDouble(kEventTime); |
| double event_time = 0; |
| if (maybe_event_time && maybe_event_time.value() > 0) { |
| event_time = maybe_event_time.value(); |
| } else { |
| event_time = (base::TimeTicks::Now() - base::TimeTicks()).InMicroseconds(); |
| } |
| // Gets App Id. |
| const std::string* maybe_app_id = value_dict.FindString(kEventAppId); |
| std::string app_id; |
| if (maybe_app_id) { |
| app_id = *maybe_app_id; |
| } |
| // Gets session Id. |
| const std::string* maybe_session_id = value_dict.FindString(kEventSessionId); |
| std::string session_id; |
| if (maybe_session_id) { |
| session_id = *maybe_session_id; |
| } |
| // Gets SDK version. |
| const std::string* maybe_sdk_version = |
| value_dict.FindString(kEventSdkVersion); |
| std::string sdk_version; |
| if (maybe_sdk_version) { |
| sdk_version = *maybe_sdk_version; |
| } |
| |
| const base::Value::Dict* multiple_events = |
| value_dict.FindDict(kEventEventsPair); |
| if (!multiple_events) { |
| std::unique_ptr<CastEventBuilder> event_builder(CreateEventBuilder(*name)); |
| PopulateEventBuilder(event_builder.get(), event_time, app_id, sdk_version, |
| session_id); |
| absl::optional<double> maybe_event_value = |
| value_dict.FindDouble(kEventValue); |
| if (maybe_event_value) { |
| double event_value = maybe_event_value.value(); |
| DCHECK_EQ(event_value, std::nearbyint(event_value)); |
| DCHECK_LE(std::abs(event_value), kInt64DoubleMax); |
| event_builder->SetExtraValue(static_cast<int64_t>(event_value)); |
| } |
| |
| const std::string* audit_report = value_dict.FindString(kEventAuditReport); |
| if (audit_report) { |
| event_builder->SetAuditReport(*audit_report); |
| } |
| |
| RecordCastEvent(std::move(event_builder)); |
| return true; |
| } |
| |
| for (auto kv : *multiple_events) { |
| absl::optional<double> maybe_event_value = kv.second.GetIfDouble(); |
| if (!maybe_event_value) { |
| continue; |
| } |
| |
| double event_value = maybe_event_value.value(); |
| DCHECK_EQ(event_value, std::nearbyint(event_value)); |
| DCHECK_LE(std::abs(event_value), kInt64DoubleMax); |
| |
| std::unique_ptr<CastEventBuilder> event_builder( |
| CreateEventBuilder(*name + "." + kv.first)); |
| event_builder->SetExtraValue(static_cast<int64_t>(event_value)); |
| PopulateEventBuilder(event_builder.get(), event_time, app_id, sdk_version, |
| session_id); |
| RecordCastEvent(std::move(event_builder)); |
| } |
| return true; |
| } |
| |
| } // namespace chromecast |