blob: 707e541659e108f17421e8fd821e8a23d7b66c40 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/metrics/structured/test/structured_metrics_mixin.h"
#include <memory>
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/test_timeouts.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
#include "components/metrics/log_decoder.h"
#include "components/metrics/metrics_switches.h"
#include "components/metrics/structured/structured_metrics_features.h"
#include "components/metrics/structured/structured_metrics_service.h"
#include "components/metrics/structured/test/test_event_storage.h"
#include "components/metrics/structured/test/test_key_data_provider.h"
#include "components/metrics_services_manager/metrics_services_manager.h"
namespace {
// Static hwid used for tests to populate the system profile proto.
constexpr char kHwid[] = "hwid";
class TestSystemProfileProvider : public metrics::MetricsProvider {
public:
TestSystemProfileProvider() = default;
TestSystemProfileProvider(const TestSystemProfileProvider& recorder) = delete;
TestSystemProfileProvider& operator=(
const TestSystemProfileProvider& recorder) = delete;
~TestSystemProfileProvider() override = default;
void ProvideSystemProfileMetrics(
metrics::SystemProfileProto* proto) override {
proto->mutable_hardware()->set_full_hardware_class(kHwid);
}
};
} // namespace
namespace metrics::structured {
StructuredMetricsMixin::StructuredMetricsMixin(
InProcessBrowserTestMixinHost* host,
bool setup_profile)
: InProcessBrowserTestMixin(host), setup_profile_(setup_profile) {
EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
}
StructuredMetricsMixin::~StructuredMetricsMixin() {
ChromeMetricsServiceAccessor::SetMetricsAndCrashReportingForTesting(nullptr);
}
void StructuredMetricsMixin::SetUpOnMainThread() {
InProcessBrowserTestMixin::SetUpOnMainThread();
ChromeMetricsServiceAccessor::SetMetricsAndCrashReportingForTesting(
&recording_state_);
base::FilePath device_keys_path =
temp_dir_.GetPath()
.Append(FILE_PATH_LITERAL("structured"))
.Append(FILE_PATH_LITERAL("device_keys"));
base::FilePath profile_path =
temp_dir_.GetPath().Append(FILE_PATH_LITERAL("profile"));
auto key_data_provider =
std::make_unique<TestKeyDataProvider>(device_keys_path);
if (setup_profile_) {
// Setup test profile directory immediately so that recording can happen.
key_data_provider->OnProfileAdded(profile_path);
}
// Create test key data provider and initialize key data provider.
// TODO(andrewbregger) make sure that all tests that rely on the persistent
// storage are moved.
auto recorder = base::MakeRefCounted<StructuredMetricsRecorder>(
std::move(key_data_provider), std::make_unique<TestEventStorage>());
g_browser_process->GetMetricsServicesManager()
->GetStructuredMetricsService()
->SetRecorderForTest(std::move(recorder));
}
StructuredMetricsRecorder* StructuredMetricsMixin::GetRecorder() {
return GetService()->recorder();
}
StructuredMetricsService* StructuredMetricsMixin::GetService() {
return g_browser_process->GetMetricsServicesManager()
->GetStructuredMetricsService();
}
std::optional<StructuredEventProto> StructuredMetricsMixin::FindEvent(
uint64_t project_name_hash,
uint64_t event_name_hash) {
std::vector<StructuredEventProto> events =
FindEvents(project_name_hash, event_name_hash);
if (events.size() > 0) {
return events[0];
}
return std::nullopt;
}
std::vector<StructuredEventProto> StructuredMetricsMixin::FindEvents(
uint64_t project_name_hash,
uint64_t event_name_hash) {
std::vector<StructuredEventProto> events_vector;
// TODO(andrewbregger): Create an API to allow events to be iterated over
// without copying.
const EventStorage<StructuredEventProto>* storage = GetEventStorage();
EventsProto events;
storage->CopyEvents(&events);
for (const auto& event : events.events()) {
if (event.project_name_hash() == project_name_hash &&
event.event_name_hash() == event_name_hash) {
events_vector.push_back(event);
}
}
return events_vector;
}
void StructuredMetricsMixin::WaitUntilEventRecorded(uint64_t project_name_hash,
uint64_t event_name_hash) {
// Check if event already exists.
std::optional<StructuredEventProto> event =
FindEvent(project_name_hash, event_name_hash);
if (event.has_value()) {
return;
}
// Wait for event since it does not exist yet.
record_run_loop_ = std::make_unique<base::RunLoop>();
base::RepeatingClosure callback =
base::BindLambdaForTesting([project_name_hash, event_name_hash, this]() {
std::optional<StructuredEventProto> event =
FindEvent(project_name_hash, event_name_hash);
if (event.has_value()) {
record_run_loop_->Quit();
}
});
GetRecorder()->SetEventRecordCallbackForTest(std::move(callback));
// The timeout for this is set to about 3 seconds (scaled accordingly for
// debug and sanitizer builds) because this should be ample time for the event
// to show up after Event::Record() has been called. There is normally a delay
// between flushes but this delay has been set to 0 for testing.
base::test::ScopedRunLoopTimeout shortened_timeout{
FROM_HERE, TestTimeouts::action_timeout() / 3};
record_run_loop_->Run();
}
void StructuredMetricsMixin::WaitUntilKeysReady() {
keys_run_loop_ = std::make_unique<base::RunLoop>();
base::RepeatingClosure callback =
base::BindLambdaForTesting([this]() { keys_run_loop_->Quit(); });
GetRecorder()->SetOnReadyToRecord(std::move(callback));
keys_run_loop_->Run();
}
void StructuredMetricsMixin::UpdateRecordingState(bool state) {
recording_state_ = state;
// Triggers rechecking of metrics state.
g_browser_process->GetMetricsServicesManager()->UpdateUploadPermissions();
}
std::unique_ptr<ChromeUserMetricsExtension>
StructuredMetricsMixin::GetUmaProto() {
StructuredMetricsService* service = GetService();
auto* log_store = service->reporting_service_->log_store();
if (!log_store->has_unsent_logs()) {
return nullptr;
}
if (log_store->has_staged_log()) {
// For testing purposes, we examine the content of a staged log without
// ever sending the log, so discard any previously staged log.
log_store->DiscardStagedLog();
}
log_store->StageNextLog();
if (!log_store->has_staged_log()) {
return nullptr;
}
std::unique_ptr<ChromeUserMetricsExtension> uma_proto =
std::make_unique<ChromeUserMetricsExtension>();
if (!metrics::DecodeLogDataToProto(log_store->staged_log(),
uma_proto.get())) {
return nullptr;
}
return uma_proto;
}
EventStorage<StructuredEventProto>* StructuredMetricsMixin::GetEventStorage() {
return GetRecorder()->event_storage();
}
} // namespace metrics::structured