// Copyright 2018 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/perf/metric_provider.h"
#include <stdint.h>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/memory/scoped_refptr.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/sync/sync_service_factory.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "chromeos/ash/components/browser_context_helper/browser_context_types.h"
#include "components/sync/base/user_selectable_type.h"
#include "components/sync/test/test_sync_service.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/metrics_proto/sampled_profile.pb.h"
namespace metrics {
namespace {
// Returns an example PerfDataProto. The contents don't have to make sense. They
// just need to constitute a semantically valid protobuf.
// |proto| is an output parameter that will contain the created protobuf.
PerfDataProto GetExamplePerfDataProto() {
PerfDataProto proto;
proto.set_timestamp_sec(1435604013); // Time since epoch in seconds.
PerfDataProto_PerfFileAttr* file_attr = proto.add_file_attrs();
file_attr->add_ids(61);
file_attr->add_ids(62);
file_attr->add_ids(63);
PerfDataProto_PerfEventAttr* attr = file_attr->mutable_attr();
attr->set_type(1);
attr->set_size(2);
attr->set_config(3);
attr->set_sample_period(4);
attr->set_sample_freq(5);
PerfDataProto_PerfEventStats* stats = proto.mutable_stats();
stats->set_num_events_read(100);
stats->set_num_sample_events(200);
stats->set_num_mmap_events(300);
stats->set_num_fork_events(400);
stats->set_num_exit_events(500);
// MMapEvent.
PerfDataProto_PerfEvent* event1 = proto.add_events();
event1->mutable_header()->set_type(1);
event1->mutable_mmap_event()->set_pid(1234);
event1->mutable_mmap_event()->set_filename_md5_prefix(0xabcdefab);
// CommEvent.
PerfDataProto_PerfEvent* event2 = proto.add_events();
event2->mutable_header()->set_type(2);
event2->mutable_comm_event()->set_pid(5678);
event2->mutable_comm_event()->set_comm_md5_prefix(0x0123456abcd);
return proto;
}
// Converts a protobuf to serialized format as a byte vector.
std::vector<uint8_t> SerializeMessageToVector(
const google::protobuf::MessageLite& message) {
std::vector<uint8_t> result(message.ByteSize());
message.SerializeToArray(result.data(), result.size());
return result;
}
using TestSyncService = syncer::TestSyncService;
std::unique_ptr<KeyedService> TestingSyncFactoryFunction(
content::BrowserContext* context) {
return std::make_unique<TestSyncService>();
}
// Allow access to some private methods for testing.
class TestMetricProvider : public MetricProvider {
public:
using MetricProvider::MetricProvider;
~TestMetricProvider() override = default;
using MetricProvider::RecordAttemptStatus;
};
// Allows access to some private methods for testing.
class TestMetricCollector : public internal::MetricCollector {
public:
TestMetricCollector() : TestMetricCollector(CollectionParams()) {}
explicit TestMetricCollector(const CollectionParams& collection_params)
: internal::MetricCollector("UMA.CWP.TestData", collection_params),
weak_factory_(this) {}
TestMetricCollector(const TestMetricCollector&) = delete;
TestMetricCollector& operator=(const TestMetricCollector&) = delete;
const char* ToolName() const override { return "Test"; }
base::WeakPtr<internal::MetricCollector> GetWeakPtr() override {
return weak_factory_.GetWeakPtr();
}
void CollectProfile(
std::unique_ptr<SampledProfile> sampled_profile) override {
PerfDataProto perf_data_proto = GetExamplePerfDataProto();
SaveSerializedPerfProto(std::move(sampled_profile),
perf_data_proto.SerializeAsString());
}
private:
base::WeakPtrFactory<TestMetricCollector> weak_factory_;
};
const base::TimeDelta kPeriodicCollectionInterval = base::Hours(1);
const base::TimeDelta kMaxCollectionDelay = base::Seconds(1);
const uint64_t kRedactedCommMd5Prefix = 0xee1f021828a1fcbc;
} // namespace
class MetricProviderTest : public testing::Test {
public:
MetricProviderTest()
: task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
MetricProviderTest(const MetricProviderTest&) = delete;
MetricProviderTest& operator=(const MetricProviderTest&) = delete;
void SetUp() override {
CollectionParams test_params;
// Set the sampling factors for the triggers to 1, so we always trigger
// collection, and set the collection delays to well known quantities, so
// we can fast forward the time.
test_params.resume_from_suspend.sampling_factor = 1;
test_params.resume_from_suspend.max_collection_delay = kMaxCollectionDelay;
test_params.restore_session.sampling_factor = 1;
test_params.restore_session.max_collection_delay = kMaxCollectionDelay;
test_params.periodic_interval = kPeriodicCollectionInterval;
metric_provider_ = std::make_unique<TestMetricProvider>(
std::make_unique<TestMetricCollector>(test_params), nullptr);
metric_provider_->Init();
task_environment_.RunUntilIdle();
}
void TearDown() override { metric_provider_.reset(); }
protected:
// task_environment_ must be the first member (or at least before
// any member that cares about tasks) to be initialized first and destroyed
// last.
content::BrowserTaskEnvironment task_environment_;
std::unique_ptr<TestMetricProvider> metric_provider_;
};
TEST_F(MetricProviderTest, CheckSetup) {
// There are no cached profiles at start.
std::vector<SampledProfile> stored_profiles;
EXPECT_FALSE(metric_provider_->GetSampledProfiles(&stored_profiles));
EXPECT_TRUE(stored_profiles.empty());
}
TEST_F(MetricProviderTest, DisabledBeforeLogin) {
task_environment_.FastForwardBy(kPeriodicCollectionInterval);
// There are no cached profiles after a profiling interval.
std::vector<SampledProfile> stored_profiles;
EXPECT_FALSE(metric_provider_->GetSampledProfiles(&stored_profiles));
EXPECT_TRUE(stored_profiles.empty());
}
TEST_F(MetricProviderTest, EnabledOnLogin) {
metric_provider_->OnUserLoggedIn();
task_environment_.FastForwardBy(kPeriodicCollectionInterval);
// We should find a cached PERIODIC_COLLECTION profile after a profiling
// interval.
std::vector<SampledProfile> stored_profiles;
EXPECT_TRUE(metric_provider_->GetSampledProfiles(&stored_profiles));
EXPECT_EQ(stored_profiles.size(), 1u);
const SampledProfile& profile = stored_profiles[0];
EXPECT_EQ(SampledProfile::PERIODIC_COLLECTION, profile.trigger_event());
EXPECT_FALSE(profile.has_suspend_duration_ms());
EXPECT_FALSE(profile.has_ms_after_resume());
EXPECT_TRUE(profile.has_ms_after_login());
EXPECT_TRUE(profile.has_ms_after_boot());
}
TEST_F(MetricProviderTest, DisabledOnDeactivate) {
metric_provider_->OnUserLoggedIn();
task_environment_.RunUntilIdle();
metric_provider_->Deactivate();
task_environment_.FastForwardBy(kPeriodicCollectionInterval);
// There are no cached profiles after a profiling interval.
std::vector<SampledProfile> stored_profiles;
EXPECT_FALSE(metric_provider_->GetSampledProfiles(&stored_profiles));
EXPECT_TRUE(stored_profiles.empty());
}
TEST_F(MetricProviderTest, SuspendDone) {
metric_provider_->OnUserLoggedIn();
task_environment_.RunUntilIdle();
const auto kSuspendDuration = base::Minutes(3);
metric_provider_->SuspendDone(kSuspendDuration);
// Fast forward the time by the max collection delay.
task_environment_.FastForwardBy(kMaxCollectionDelay);
// Check that the SuspendDone trigger produced one profile.
std::vector<SampledProfile> stored_profiles;
EXPECT_TRUE(metric_provider_->GetSampledProfiles(&stored_profiles));
ASSERT_EQ(1U, stored_profiles.size());
const SampledProfile& profile = stored_profiles[0];
EXPECT_EQ(SampledProfile::RESUME_FROM_SUSPEND, profile.trigger_event());
EXPECT_EQ(kSuspendDuration.InMilliseconds(), profile.suspend_duration_ms());
EXPECT_TRUE(profile.has_ms_after_resume());
EXPECT_TRUE(profile.has_ms_after_login());
EXPECT_TRUE(profile.has_ms_after_boot());
}
TEST_F(MetricProviderTest, OnSessionRestoreDone) {
metric_provider_->OnUserLoggedIn();
task_environment_.RunUntilIdle();
const int kRestoredTabs = 7;
metric_provider_->OnSessionRestoreDone(kRestoredTabs);
// Fast forward the time by the max collection delay.
task_environment_.FastForwardBy(kMaxCollectionDelay);
std::vector<SampledProfile> stored_profiles;
EXPECT_TRUE(metric_provider_->GetSampledProfiles(&stored_profiles));
ASSERT_EQ(1U, stored_profiles.size());
const SampledProfile& profile = stored_profiles[0];
EXPECT_EQ(SampledProfile::RESTORE_SESSION, profile.trigger_event());
EXPECT_EQ(kRestoredTabs, profile.num_tabs_restored());
EXPECT_FALSE(profile.has_ms_after_resume());
EXPECT_TRUE(profile.has_ms_after_login());
EXPECT_TRUE(profile.has_ms_after_boot());
}
TEST_F(MetricProviderTest, ThermalStateRecordedInProfile) {
metric_provider_->OnUserLoggedIn();
metric_provider_->SetThermalState(
base::PowerThermalObserver::DeviceThermalState::kSerious);
task_environment_.FastForwardBy(kPeriodicCollectionInterval);
// We should find a cached PERIODIC_COLLECTION profile after a profiling
// interval.
std::vector<SampledProfile> stored_profiles;
EXPECT_TRUE(metric_provider_->GetSampledProfiles(&stored_profiles));
EXPECT_EQ(stored_profiles.size(), 1u);
const SampledProfile& profile = stored_profiles[0];
EXPECT_EQ(SampledProfile::PERIODIC_COLLECTION, profile.trigger_event());
EXPECT_EQ(THERMAL_STATE_SERIOUS, profile.thermal_state());
}
TEST_F(MetricProviderTest, DisableRecording) {
base::HistogramTester histogram_tester;
metric_provider_->OnUserLoggedIn();
// Upon disabling recording, we would expect no cached profiles after a
// profiling interval.
metric_provider_->DisableRecording();
task_environment_.FastForwardBy(kPeriodicCollectionInterval);
std::vector<SampledProfile> stored_profiles;
EXPECT_FALSE(metric_provider_->GetSampledProfiles(&stored_profiles));
EXPECT_TRUE(stored_profiles.empty());
histogram_tester.ExpectUniqueSample(
"ChromeOS.CWP.RecordTest",
TestMetricProvider::RecordAttemptStatus::kRecordingDisabled, 1);
}
TEST_F(MetricProviderTest, EnableRecording) {
base::HistogramTester histogram_tester;
metric_provider_->OnUserLoggedIn();
// Upon enabling recording, we would find a cached PERIODIC_COLLECTION profile
// after a profiling interval.
metric_provider_->EnableRecording();
task_environment_.FastForwardBy(kPeriodicCollectionInterval);
std::vector<SampledProfile> stored_profiles;
EXPECT_TRUE(metric_provider_->GetSampledProfiles(&stored_profiles));
EXPECT_EQ(stored_profiles.size(), 1u);
EXPECT_EQ(SampledProfile::PERIODIC_COLLECTION,
stored_profiles[0].trigger_event());
// When initializing the MetricProvider, we set the ProfileManager to nullptr.
histogram_tester.ExpectUniqueSample(
"ChromeOS.CWP.RecordTest",
TestMetricProvider::RecordAttemptStatus::kProfileManagerUnset, 1);
}
class MetricProviderSyncSettingsTest : public testing::Test {
public:
MetricProviderSyncSettingsTest()
: task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
MetricProviderSyncSettingsTest(const MetricProviderSyncSettingsTest&) =
delete;
MetricProviderSyncSettingsTest& operator=(
const MetricProviderSyncSettingsTest&) = delete;
void SetUp() override {
CollectionParams test_params;
test_params.periodic_interval = kPeriodicCollectionInterval;
testing_profile_manager_ = std::make_unique<TestingProfileManager>(
TestingBrowserProcess::GetGlobal());
ASSERT_TRUE(testing_profile_manager_->SetUp());
// A Default profile is always loaded before all other user profiles are
// initialized on Chrome OS. So creating the Default profile here to reflect
// this. The Default profile is skipped when getting the sync settings from
// user profile(s).
testing_profile_manager_->CreateTestingProfile(
ash::kSigninBrowserContextBaseName);
// Also add two non-regular profiles that might appear on ChromeOS. They
// always disable sync and are skipped when getting sync settings.
testing_profile_manager_->CreateTestingProfile(
ash::kLockScreenAppBrowserContextBaseName);
testing_profile_manager_->CreateTestingProfile(
ash::kLockScreenBrowserContextBaseName);
metric_provider_ = std::make_unique<TestMetricProvider>(
std::make_unique<TestMetricCollector>(test_params),
testing_profile_manager_->profile_manager());
metric_provider_->Init();
task_environment_.RunUntilIdle();
perf_data_unchanged_ = GetExamplePerfDataProto();
// Perf data whose COMM event has its comm_md5_prefix redacted.
perf_data_redacted_ = GetExamplePerfDataProto();
perf_data_redacted_.mutable_events(1)
->mutable_comm_event()
->set_comm_md5_prefix(kRedactedCommMd5Prefix);
}
void TearDown() override {
metric_provider_.reset();
testing_profile_manager_.reset();
}
protected:
TestSyncService* GetSyncService(TestingProfile* profile) {
TestSyncService* sync_service = static_cast<TestSyncService*>(
SyncServiceFactory::GetInstance()->SetTestingFactoryAndUse(
profile, base::BindRepeating(&TestingSyncFactoryFunction)));
sync_service->SetInitialSyncFeatureSetupComplete(true);
return sync_service;
}
void EnableOSAppSync(TestSyncService* sync_service) {
sync_service->GetUserSettings()->SetSelectedOsTypes(
/*sync_all_os_types=*/false, {syncer::UserSelectableOsType::kOsApps});
}
void DisableOSAppSync(TestSyncService* sync_service) {
sync_service->GetUserSettings()->SetSelectedOsTypes(
/*sync_all_os_types=*/false, {});
}
void EnableAppSync(TestSyncService* sync_service) {
sync_service->GetUserSettings()->SetSelectedTypes(
/*sync_everything=*/false, {syncer::UserSelectableType::kApps});
}
void DisableAppSync(TestSyncService* sync_service) {
sync_service->GetUserSettings()->SetSelectedTypes(/*sync_everything=*/false,
{});
}
content::BrowserTaskEnvironment task_environment_;
std::unique_ptr<TestingProfileManager> testing_profile_manager_;
std::unique_ptr<TestMetricProvider> metric_provider_;
PerfDataProto perf_data_unchanged_;
PerfDataProto perf_data_redacted_;
};
TEST_F(MetricProviderSyncSettingsTest, NoLoadedUserProfile) {
// The Default profile is skipped and there is no other user profile
// initialized. So we would expect the perf data to be redacted and a
// histogram count of kNoLoadedProfile.
base::HistogramTester histogram_tester;
std::vector<SampledProfile> stored_profiles;
metric_provider_->OnUserLoggedIn();
task_environment_.FastForwardBy(kPeriodicCollectionInterval);
EXPECT_TRUE(metric_provider_->GetSampledProfiles(&stored_profiles));
EXPECT_EQ(stored_profiles.size(), 1u);
const SampledProfile& profile = stored_profiles[0];
EXPECT_EQ(SampledProfile::PERIODIC_COLLECTION, profile.trigger_event());
EXPECT_EQ(SerializeMessageToVector(perf_data_redacted_),
SerializeMessageToVector(profile.perf_data()));
histogram_tester.ExpectUniqueSample(
"ChromeOS.CWP.RecordTest",
TestMetricProvider::RecordAttemptStatus::kNoLoadedProfile, 1);
}
TEST_F(MetricProviderSyncSettingsTest, SyncFeatureDisabled) {
base::HistogramTester histogram_tester;
std::vector<SampledProfile> stored_profiles;
metric_provider_->OnUserLoggedIn();
// The first testing profile has both sync-the-feature and App sync enabled.
TestSyncService* sync_service1 =
GetSyncService(testing_profile_manager_->CreateTestingProfile("user1"));
EnableOSAppSync(sync_service1);
// The second testing profile has kOsApps type enabled, but sync-the-feature
// is turned off by marking FirstSetupComplete as false.
TestSyncService* sync_service2 =
GetSyncService(testing_profile_manager_->CreateTestingProfile("user2"));
EnableOSAppSync(sync_service2);
sync_service2->SetInitialSyncFeatureSetupComplete(false);
task_environment_.FastForwardBy(kPeriodicCollectionInterval);
EXPECT_TRUE(metric_provider_->GetSampledProfiles(&stored_profiles));
EXPECT_EQ(stored_profiles.size(), 1u);
const SampledProfile& profile = stored_profiles[0];
EXPECT_EQ(SampledProfile::PERIODIC_COLLECTION, profile.trigger_event());
EXPECT_EQ(SerializeMessageToVector(perf_data_redacted_),
SerializeMessageToVector(profile.perf_data()));
histogram_tester.ExpectUniqueSample(
"ChromeOS.CWP.RecordTest",
TestMetricProvider::RecordAttemptStatus::kChromeSyncFeatureDisabled, 1);
}
TEST_F(MetricProviderSyncSettingsTest, AppSyncEnabled) {
base::HistogramTester histogram_tester;
std::vector<SampledProfile> stored_profiles;
metric_provider_->OnUserLoggedIn();
// Set up two testing profiles, both with OS App Sync enabled. The Default
// profile has OS App Sync disabled but is skipped.
TestSyncService* sync_service1 =
GetSyncService(testing_profile_manager_->CreateTestingProfile("user1"));
TestSyncService* sync_service2 =
GetSyncService(testing_profile_manager_->CreateTestingProfile("user2"));
EnableOSAppSync(sync_service1);
EnableOSAppSync(sync_service2);
task_environment_.FastForwardBy(kPeriodicCollectionInterval);
EXPECT_TRUE(metric_provider_->GetSampledProfiles(&stored_profiles));
EXPECT_EQ(stored_profiles.size(), 1u);
const SampledProfile& profile = stored_profiles[0];
EXPECT_EQ(SampledProfile::PERIODIC_COLLECTION, profile.trigger_event());
EXPECT_EQ(SerializeMessageToVector(perf_data_unchanged_),
SerializeMessageToVector(profile.perf_data()));
histogram_tester.ExpectUniqueSample(
"ChromeOS.CWP.RecordTest",
TestMetricProvider::RecordAttemptStatus::kAppSyncEnabled, 1);
}
TEST_F(MetricProviderSyncSettingsTest, AppSyncDisabled) {
base::HistogramTester histogram_tester;
std::vector<SampledProfile> stored_profiles;
metric_provider_->OnUserLoggedIn();
// Set up two testing profiles, one with OS App Sync enabled and the other
// disabled.
TestSyncService* sync_service1 =
GetSyncService(testing_profile_manager_->CreateTestingProfile("user1"));
TestSyncService* sync_service2 =
GetSyncService(testing_profile_manager_->CreateTestingProfile("user2"));
EnableOSAppSync(sync_service1);
DisableOSAppSync(sync_service2);
task_environment_.FastForwardBy(kPeriodicCollectionInterval);
EXPECT_TRUE(metric_provider_->GetSampledProfiles(&stored_profiles));
EXPECT_EQ(stored_profiles.size(), 1u);
const SampledProfile& profile = stored_profiles[0];
EXPECT_EQ(SampledProfile::PERIODIC_COLLECTION, profile.trigger_event());
EXPECT_EQ(SerializeMessageToVector(perf_data_redacted_),
SerializeMessageToVector(profile.perf_data()));
histogram_tester.ExpectUniqueSample(
"ChromeOS.CWP.RecordTest",
TestMetricProvider::RecordAttemptStatus::kOSAppSyncDisabled, 1);
}
} // namespace metrics