| // 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 <string_view> |
| |
| #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, |
| std::string_view 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.com/40238818): Give more insight here (e.g. "independent log |
| // generated from pma file"). |
| return "Reason: Independent log"; |
| case MetricsLogsEventManager::CreateReason::kOutOfBand: |
| return "Reason: Manually triggered by client"; |
| } |
| } |
| |
| } // 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( |
| std::string_view log_hash, |
| std::string_view log_data, |
| std::string_view 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> log = std::make_unique<Log>(); |
| 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, |
| std::string_view log_hash, |
| std::string_view 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<MetricsLog::LogType> 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>& 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<int>(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( |
| std::string_view log_hash) { |
| auto it = indexed_logs_.find(log_hash); |
| return it != indexed_logs_.end() ? it->second : nullptr; |
| } |
| |
| } // namespace metrics |