// Copyright 2022 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "components/metrics/metrics_service_observer.h" #include "base/base64.h" #include "base/callback_list.h" #include "base/files/file_util.h" #include "base/json/json_string_value_serializer.h" #include "base/logging.h" #include "base/strings/string_number_conversions.h" #include "base/time/time.h" #include "base/values.h" #include "components/metrics/metrics_logs_event_manager.h" namespace metrics { namespace { MetricsServiceObserver::Log::Event CreateEventStruct( MetricsLogsEventManager::LogEvent event, base::StringPiece message) { MetricsServiceObserver::Log::Event event_struct; event_struct.event = event; event_struct.timestampMs = base::Time::Now().InMillisecondsFSinceUnixEpochIgnoringNull(); if (!message.empty()) { event_struct.message = std::string(message); } return event_struct; } std::string LogTypeToString(MetricsLog::LogType log_type) { switch (log_type) { case MetricsLog::LogType::INDEPENDENT_LOG: return "Independent"; case MetricsLog::LogType::INITIAL_STABILITY_LOG: return "Stability"; case MetricsLog::LogType::ONGOING_LOG: return "Ongoing"; } NOTREACHED(); } std::string EventToString(MetricsLogsEventManager::LogEvent event) { switch (event) { case MetricsLogsEventManager::LogEvent::kLogStaged: return "Staged"; case MetricsLogsEventManager::LogEvent::kLogDiscarded: return "Discarded"; case MetricsLogsEventManager::LogEvent::kLogTrimmed: return "Trimmed"; case MetricsLogsEventManager::LogEvent::kLogUploading: return "Uploading"; case MetricsLogsEventManager::LogEvent::kLogUploaded: return "Uploaded"; case MetricsLogsEventManager::LogEvent::kLogCreated: return "Created"; } NOTREACHED(); } std::string CreateReasonToString( metrics::MetricsLogsEventManager::CreateReason reason) { switch (reason) { case MetricsLogsEventManager::CreateReason::kUnknown: return std::string(); case MetricsLogsEventManager::CreateReason::kPeriodic: return "Reason: Periodic log creation"; case MetricsLogsEventManager::CreateReason::kServiceShutdown: return "Reason: Shutting down"; case MetricsLogsEventManager::CreateReason::kLoadFromPreviousSession: return "Reason: Loaded from previous session"; case MetricsLogsEventManager::CreateReason::kBackgrounded: return "Reason: Browser backgrounded"; case MetricsLogsEventManager::CreateReason::kForegrounded: return "Reason: Browser foregrounded"; case MetricsLogsEventManager::CreateReason::kAlternateOngoingLogStoreSet: return "Reason: Alternate ongoing log store set"; case MetricsLogsEventManager::CreateReason::kAlternateOngoingLogStoreUnset: return "Reason: Alternate ongoing log store unset"; case MetricsLogsEventManager::CreateReason::kStability: return "Reason: Stability metrics from previous session"; case MetricsLogsEventManager::CreateReason::kIndependent: // TODO(crbug/1363747): Give more insight here (e.g. "independent log // generated from pma file"). return "Reason: Independent log"; } } } // namespace MetricsServiceObserver::MetricsServiceObserver(MetricsServiceType service_type) : service_type_(service_type) {} MetricsServiceObserver::~MetricsServiceObserver() = default; MetricsServiceObserver::Log::Log() = default; MetricsServiceObserver::Log::Log(const Log&) = default; MetricsServiceObserver::Log& MetricsServiceObserver::Log::operator=( const Log&) = default; MetricsServiceObserver::Log::~Log() = default; MetricsServiceObserver::Log::Event::Event() = default; MetricsServiceObserver::Log::Event::Event(const Event&) = default; MetricsServiceObserver::Log::Event& MetricsServiceObserver::Log::Event::operator=(const Event&) = default; MetricsServiceObserver::Log::Event::~Event() = default; void MetricsServiceObserver::OnLogCreated( base::StringPiece log_hash, base::StringPiece log_data, base::StringPiece log_timestamp, metrics::MetricsLogsEventManager::CreateReason reason) { DCHECK(!GetLogFromHash(log_hash)); // Insert a new log into |logs_| with the given |log_hash| to indicate that // this observer is now aware and keeping track of this log. std::unique_ptr log = std::make_unique(); log->hash = std::string(log_hash); log->timestamp = std::string(log_timestamp); log->data = std::string(log_data); if (uma_log_type_.has_value()) { DCHECK_EQ(service_type_, MetricsServiceType::UMA); log->type = uma_log_type_; } // Immediately create a |kLogCreated| log event, along with the reason why the // log was created. log->events.push_back( CreateEventStruct(MetricsLogsEventManager::LogEvent::kLogCreated, CreateReasonToString(reason))); indexed_logs_.emplace(log->hash, log.get()); logs_.push_back(std::move(log)); // Call all registered callbacks. notified_callbacks_.Notify(); } void MetricsServiceObserver::OnLogEvent(MetricsLogsEventManager::LogEvent event, base::StringPiece log_hash, base::StringPiece message) { Log* log = GetLogFromHash(log_hash); // If this observer is not aware of any logs with the given |log_hash|, do // nothing. This may happen if this observer started observing after a log // was already created. if (!log) return; log->events.push_back(CreateEventStruct(event, message)); // Call all registered callbacks. notified_callbacks_.Notify(); } void MetricsServiceObserver::OnLogType( std::optional log_type) { uma_log_type_ = log_type; } bool MetricsServiceObserver::ExportLogsAsJson(bool include_log_proto_data, std::string* json_output) { base::Value::List logs_list; // Create and append to |logs_list| a base::Value for each log in |logs_|. for (const std::unique_ptr& log : logs_) { base::Value::Dict log_dict; if (log->type.has_value()) { DCHECK_EQ(service_type_, MetricsServiceType::UMA); log_dict.Set("type", LogTypeToString(log->type.value())); } log_dict.Set("hash", base::HexEncode(log->hash)); log_dict.Set("timestamp", log->timestamp); if (include_log_proto_data) { log_dict.Set("data", base::Base64Encode(log->data)); } log_dict.Set("size", static_cast(log->data.length())); base::Value::List log_events_list; for (const Log::Event& event : log->events) { base::Value::Dict log_event_dict; log_event_dict.Set("event", EventToString(event.event)); log_event_dict.Set("timestampMs", event.timestampMs); if (event.message.has_value()) log_event_dict.Set("message", event.message.value()); log_events_list.Append(std::move(log_event_dict)); } log_dict.Set("events", std::move(log_events_list)); logs_list.Append(std::move(log_dict)); } // Create a last |dict| that contains all the logs and |service_type_|, // convert it to a JSON string, and write it to |json_output|. base::Value::Dict dict; dict.Set("logType", service_type_ == MetricsServiceType::UMA ? "UMA" : "UKM"); dict.Set("logs", std::move(logs_list)); JSONStringValueSerializer serializer(json_output); return serializer.Serialize(dict); } void MetricsServiceObserver::ExportLogsToFile(const base::FilePath& path) { std::string logs_data; bool success = ExportLogsAsJson(/*include_log_proto_data=*/true, &logs_data); DCHECK(success); if (!base::WriteFile(path, logs_data)) { LOG(ERROR) << "Failed to export logs to " << path << ": " << logs_data; } } base::CallbackListSubscription MetricsServiceObserver::AddNotifiedCallback( base::RepeatingClosure callback) { return notified_callbacks_.Add(callback); } MetricsServiceObserver::Log* MetricsServiceObserver::GetLogFromHash( base::StringPiece log_hash) { auto it = indexed_logs_.find(log_hash); return it != indexed_logs_.end() ? it->second : nullptr; } } // namespace metrics