// 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_collector.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 "content/public/browser/browser_thread.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"
#include "third_party/protobuf/src/google/protobuf/io/coded_stream.h"
#include "third_party/protobuf/src/google/protobuf/io/zero_copy_stream_impl_lite.h"
#include "third_party/protobuf/src/google/protobuf/wire_format_lite.h"
namespace metrics {
namespace internal {
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);
return proto;
}
// Creates a serialized data stream containing a string with a field tag number.
std::string SerializeStringFieldWithTag(int field, const std::string& value) {
std::string result;
google::protobuf::io::StringOutputStream string_stream(&result);
google::protobuf::io::CodedOutputStream output(&string_stream);
using google::protobuf::internal::WireFormatLite;
WireFormatLite::WriteTag(field, WireFormatLite::WIRETYPE_LENGTH_DELIMITED,
&output);
output.WriteVarint32(value.size());
output.WriteString(value);
return result;
}
// Creates a serialized uint64 with a field tag number.
std::string SerializeUint64FieldWithTag(int field, const uint64_t& value) {
std::string result;
google::protobuf::io::StringOutputStream string_stream(&result);
google::protobuf::io::CodedOutputStream output(&string_stream);
using google::protobuf::internal::WireFormatLite;
WireFormatLite::WriteTag(field, WireFormatLite::WIRETYPE_VARINT, &output);
output.WriteVarint64(value);
return result;
}
// Allows access to some private methods for testing.
class TestMetricCollector : public MetricCollector {
public:
TestMetricCollector() : TestMetricCollector(CollectionParams()) {}
explicit TestMetricCollector(const CollectionParams& collection_params)
: MetricCollector("Test", collection_params) {}
TestMetricCollector(const TestMetricCollector&) = delete;
TestMetricCollector& operator=(const TestMetricCollector&) = delete;
const char* ToolName() const override { return "Test"; }
base::WeakPtr<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());
}
using MetricCollector::collection_params;
using MetricCollector::CollectionAttemptStatus;
using MetricCollector::CurrentTimerDelay;
using MetricCollector::Init;
using MetricCollector::IsRunning;
using MetricCollector::login_time;
using MetricCollector::RecordUserLogin;
using MetricCollector::SaveSerializedPerfProto;
using MetricCollector::ScheduleIntervalCollection;
using MetricCollector::ScheduleSessionRestoreCollection;
using MetricCollector::ScheduleSuspendDoneCollection;
using MetricCollector::set_profile_done_callback;
using MetricCollector::StopTimer;
private:
base::WeakPtrFactory<TestMetricCollector> weak_factory_{this};
};
const base::TimeDelta kPeriodicCollectionInterval = base::Hours(1);
const base::TimeDelta kMaxCollectionDelay = base::Seconds(1);
} // namespace
class MetricCollectorTest : public testing::Test {
public:
MetricCollectorTest()
: task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME),
perf_data_proto_(GetExamplePerfDataProto()) {}
MetricCollectorTest(const MetricCollectorTest&) = delete;
MetricCollectorTest& operator=(const MetricCollectorTest&) = delete;
void SaveProfile(std::unique_ptr<SampledProfile> sampled_profile) {
cached_profile_data_.resize(cached_profile_data_.size() + 1);
cached_profile_data_.back().Swap(sampled_profile.get());
}
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_collector_ = std::make_unique<TestMetricCollector>(test_params);
metric_collector_->set_profile_done_callback(base::BindRepeating(
&MetricCollectorTest::SaveProfile, base::Unretained(this)));
metric_collector_->Init();
// MetricCollector requires the user to be logged in.
metric_collector_->RecordUserLogin(base::TimeTicks::Now());
}
void TearDown() override {
metric_collector_.reset();
cached_profile_data_.clear();
}
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::vector<SampledProfile> cached_profile_data_;
std::unique_ptr<TestMetricCollector> metric_collector_;
// Store sample perf data protobuf for testing.
PerfDataProto perf_data_proto_;
};
TEST_F(MetricCollectorTest, CheckSetup) {
EXPECT_GT(perf_data_proto_.ByteSize(), 0);
// Timer is active after user logs in.
EXPECT_TRUE(metric_collector_->IsRunning());
EXPECT_FALSE(metric_collector_->login_time().is_null());
}
TEST_F(MetricCollectorTest, EmptyProtosAreNotSaved) {
auto sampled_profile = std::make_unique<SampledProfile>();
sampled_profile->set_trigger_event(SampledProfile::PERIODIC_COLLECTION);
base::HistogramTester histogram_tester;
metric_collector_->SaveSerializedPerfProto(std::move(sampled_profile),
std::string());
task_environment_.RunUntilIdle();
EXPECT_TRUE(cached_profile_data_.empty());
histogram_tester.ExpectUniqueSample(
"ChromeOS.CWP.CollectTest",
TestMetricCollector::CollectionAttemptStatus::ILLEGAL_DATA_RETURNED, 1);
}
TEST_F(MetricCollectorTest, ProtosWithNoSamplesAreNotSaved) {
auto sampled_profile = std::make_unique<SampledProfile>();
sampled_profile->set_trigger_event(SampledProfile::PERIODIC_COLLECTION);
base::HistogramTester histogram_tester;
PerfDataProto proto = GetExamplePerfDataProto();
PerfDataProto_PerfEventStats* stats = proto.mutable_stats();
stats->set_num_sample_events(0);
metric_collector_->SaveSerializedPerfProto(std::move(sampled_profile),
proto.SerializeAsString());
task_environment_.RunUntilIdle();
EXPECT_TRUE(cached_profile_data_.empty());
histogram_tester.ExpectUniqueSample(
"ChromeOS.CWP.CollectTest",
TestMetricCollector::CollectionAttemptStatus::SESSION_HAS_ZERO_SAMPLES,
1);
}
TEST_F(MetricCollectorTest, PerfDataProto) {
auto sampled_profile = std::make_unique<SampledProfile>();
sampled_profile->set_trigger_event(SampledProfile::PERIODIC_COLLECTION);
base::HistogramTester histogram_tester;
metric_collector_->SaveSerializedPerfProto(
std::move(sampled_profile), perf_data_proto_.SerializeAsString());
task_environment_.RunUntilIdle();
ASSERT_EQ(1U, cached_profile_data_.size());
histogram_tester.ExpectUniqueSample(
"ChromeOS.CWP.CollectTest",
TestMetricCollector::CollectionAttemptStatus::SUCCESS, 1);
const SampledProfile& profile = cached_profile_data_[0];
EXPECT_EQ(SampledProfile::PERIODIC_COLLECTION, profile.trigger_event());
EXPECT_TRUE(profile.has_ms_after_boot());
EXPECT_TRUE(profile.has_ms_after_login());
ASSERT_TRUE(profile.has_perf_data());
EXPECT_EQ(perf_data_proto_.SerializeAsString(),
profile.perf_data().SerializeAsString());
}
TEST_F(MetricCollectorTest, PerfDataProto_UnknownFieldsDiscarded) {
// First add some unknown fields to MMapEvent, CommEvent, PerfBuildID, and
// StringAndMd5sumPrefix. The known field values don't have to make sense for
// perf data. They are just padding to avoid having an otherwise empty proto.
// The unknown field string contents don't have to make sense as serialized
// data as the test is to discard them.
// MMapEvent
PerfDataProto_PerfEvent* event1 = perf_data_proto_.add_events();
event1->mutable_header()->set_type(1);
event1->mutable_mmap_event()->set_pid(1234);
event1->mutable_mmap_event()->set_filename_md5_prefix(0xdeadbeef);
// Missing field |MMapEvent::filename| has tag=6.
*event1->mutable_mmap_event()->mutable_unknown_fields() =
SerializeStringFieldWithTag(6, "/opt/google/chrome/chrome");
// CommEvent
PerfDataProto_PerfEvent* event2 = perf_data_proto_.add_events();
event2->mutable_header()->set_type(2);
event2->mutable_comm_event()->set_pid(5678);
event2->mutable_comm_event()->set_comm_md5_prefix(0x900df00d);
// Missing field |CommEvent::comm| has tag=3.
*event2->mutable_comm_event()->mutable_unknown_fields() =
SerializeStringFieldWithTag(3, "chrome");
// PerfBuildID
PerfDataProto_PerfBuildID* build_id = perf_data_proto_.add_build_ids();
build_id->set_misc(3);
build_id->set_pid(1337);
build_id->set_filename_md5_prefix(0x9876543210);
// Missing field |PerfBuildID::filename| has tag=4.
*build_id->mutable_unknown_fields() =
SerializeStringFieldWithTag(4, "/opt/google/chrome/chrome");
// StringAndMd5sumPrefix
PerfDataProto_StringMetadata* metadata =
perf_data_proto_.mutable_string_metadata();
metadata->mutable_perf_command_line_whole()->set_value_md5_prefix(
0x123456789);
// Missing field |StringAndMd5sumPrefix::value| has tag=1.
*metadata->mutable_perf_command_line_whole()->mutable_unknown_fields() =
SerializeStringFieldWithTag(1, "perf record -a -- sleep 1");
// PerfEventType
PerfDataProto_PerfEventType* event_type = perf_data_proto_.add_event_types();
event_type->set_id(4);
event_type->set_name_md5_prefix(0xac96823403192d1f);
*event_type->mutable_unknown_fields() =
SerializeStringFieldWithTag(2, "cycles");
// PMUMappingsMetadata
PerfDataProto_PerfPMUMappingsMetadata* pmu_mapping =
perf_data_proto_.add_pmu_mappings();
pmu_mapping->set_type(5);
pmu_mapping->set_name_md5_prefix(0xd36231bfe8094177);
*pmu_mapping->mutable_unknown_fields() =
SerializeStringFieldWithTag(2, "breakpoint");
// Unknown fields at the root level
*perf_data_proto_.mutable_unknown_fields() =
SerializeUint64FieldWithTag(5, 0x123456789);
// Serialize to string and make sure it can be deserialized.
std::string perf_data_string = perf_data_proto_.SerializeAsString();
PerfDataProto temp_proto;
EXPECT_TRUE(temp_proto.ParseFromString(perf_data_string));
// Now pass it to |metric_collector_|.
auto sampled_profile = std::make_unique<SampledProfile>();
sampled_profile->set_trigger_event(SampledProfile::PERIODIC_COLLECTION);
// Perf data protos are saved from the collector task runner.
metric_collector_->SaveSerializedPerfProto(std::move(sampled_profile),
perf_data_string);
task_environment_.RunUntilIdle();
ASSERT_EQ(1U, cached_profile_data_.size());
const SampledProfile& profile = cached_profile_data_[0];
EXPECT_EQ(SampledProfile::PERIODIC_COLLECTION, profile.trigger_event());
EXPECT_TRUE(profile.has_perf_data());
// The serialized form should be different because the unknown fields have
// have been removed.
EXPECT_NE(perf_data_string, profile.perf_data().SerializeAsString());
// Check contents of stored protobuf.
const PerfDataProto& stored_proto = profile.perf_data();
ASSERT_EQ(2, stored_proto.events_size());
// MMapEvent
const PerfDataProto_PerfEvent& stored_event1 = stored_proto.events(0);
EXPECT_EQ(1U, stored_event1.header().type());
EXPECT_EQ(1234U, stored_event1.mmap_event().pid());
EXPECT_EQ(0xdeadbeef, stored_event1.mmap_event().filename_md5_prefix());
EXPECT_EQ(0U, stored_event1.mmap_event().unknown_fields().size());
// CommEvent
const PerfDataProto_PerfEvent& stored_event2 = stored_proto.events(1);
EXPECT_EQ(2U, stored_event2.header().type());
EXPECT_EQ(5678U, stored_event2.comm_event().pid());
EXPECT_EQ(0x900df00d, stored_event2.comm_event().comm_md5_prefix());
EXPECT_EQ(0U, stored_event2.comm_event().unknown_fields().size());
// PerfBuildID
ASSERT_EQ(1, stored_proto.build_ids_size());
const PerfDataProto_PerfBuildID& stored_build_id = stored_proto.build_ids(0);
EXPECT_EQ(3U, stored_build_id.misc());
EXPECT_EQ(1337U, stored_build_id.pid());
EXPECT_EQ(0x9876543210U, stored_build_id.filename_md5_prefix());
EXPECT_EQ(0U, stored_build_id.unknown_fields().size());
// StringAndMd5sumPrefix
const PerfDataProto_StringMetadata& stored_metadata =
stored_proto.string_metadata();
EXPECT_EQ(0x123456789U,
stored_metadata.perf_command_line_whole().value_md5_prefix());
EXPECT_EQ(0U,
stored_metadata.perf_command_line_whole().unknown_fields().size());
// PerfEventType
ASSERT_EQ(1, stored_proto.event_types_size());
const PerfDataProto_PerfEventType& stored_event_type =
stored_proto.event_types(0);
EXPECT_EQ(4U, stored_event_type.id());
EXPECT_EQ(0xac96823403192d1f, stored_event_type.name_md5_prefix());
EXPECT_EQ(0U, stored_event_type.unknown_fields().size());
// PMUMappingsMetadata
ASSERT_EQ(1, stored_proto.pmu_mappings_size());
const PerfDataProto_PerfPMUMappingsMetadata& stored_pmu_mapping =
stored_proto.pmu_mappings(0);
EXPECT_EQ(5U, stored_pmu_mapping.type());
EXPECT_EQ(0xd36231bfe8094177, stored_pmu_mapping.name_md5_prefix());
EXPECT_EQ(0U, stored_pmu_mapping.unknown_fields().size());
// No unknown fields in PerfDataProto
EXPECT_EQ(0U, stored_proto.unknown_fields().size());
}
// Change |sampled_profile| between calls to SaveSerializedPerfProto().
TEST_F(MetricCollectorTest, MultipleCalls) {
auto sampled_profile = std::make_unique<SampledProfile>();
sampled_profile->set_trigger_event(SampledProfile::PERIODIC_COLLECTION);
// Perf data protos are saved from the collector task runner.
metric_collector_->SaveSerializedPerfProto(
std::move(sampled_profile), perf_data_proto_.SerializeAsString());
task_environment_.RunUntilIdle();
sampled_profile = std::make_unique<SampledProfile>();
sampled_profile->set_trigger_event(SampledProfile::RESTORE_SESSION);
sampled_profile->set_ms_after_restore(3000);
metric_collector_->SaveSerializedPerfProto(
std::move(sampled_profile), perf_data_proto_.SerializeAsString());
task_environment_.RunUntilIdle();
sampled_profile = std::make_unique<SampledProfile>();
sampled_profile->set_trigger_event(SampledProfile::RESUME_FROM_SUSPEND);
sampled_profile->set_suspend_duration_ms(60000);
sampled_profile->set_ms_after_resume(1500);
metric_collector_->SaveSerializedPerfProto(
std::move(sampled_profile), perf_data_proto_.SerializeAsString());
task_environment_.RunUntilIdle();
ASSERT_EQ(3U, cached_profile_data_.size());
{
const SampledProfile& profile = cached_profile_data_[0];
EXPECT_EQ(SampledProfile::PERIODIC_COLLECTION, profile.trigger_event());
EXPECT_TRUE(profile.has_ms_after_boot());
EXPECT_TRUE(profile.has_ms_after_login());
ASSERT_TRUE(profile.has_perf_data());
EXPECT_EQ(perf_data_proto_.SerializeAsString(),
profile.perf_data().SerializeAsString());
}
{
const SampledProfile& profile = cached_profile_data_[1];
EXPECT_EQ(SampledProfile::RESTORE_SESSION, profile.trigger_event());
EXPECT_TRUE(profile.has_ms_after_boot());
EXPECT_TRUE(profile.has_ms_after_login());
EXPECT_EQ(3000, profile.ms_after_restore());
ASSERT_TRUE(profile.has_perf_data());
EXPECT_EQ(perf_data_proto_.SerializeAsString(),
profile.perf_data().SerializeAsString());
}
{
const SampledProfile& profile = cached_profile_data_[2];
EXPECT_EQ(SampledProfile::RESUME_FROM_SUSPEND, profile.trigger_event());
EXPECT_TRUE(profile.has_ms_after_boot());
EXPECT_TRUE(profile.has_ms_after_login());
EXPECT_EQ(60000, profile.suspend_duration_ms());
EXPECT_EQ(1500, profile.ms_after_resume());
ASSERT_TRUE(profile.has_perf_data());
EXPECT_EQ(perf_data_proto_.SerializeAsString(),
profile.perf_data().SerializeAsString());
}
}
TEST_F(MetricCollectorTest, StopTimer) {
auto sampled_profile = std::make_unique<SampledProfile>();
sampled_profile->set_trigger_event(SampledProfile::PERIODIC_COLLECTION);
metric_collector_->CollectProfile(std::move(sampled_profile));
task_environment_.RunUntilIdle();
EXPECT_TRUE(metric_collector_->IsRunning());
EXPECT_FALSE(metric_collector_->login_time().is_null());
// Timer is stopped by StopTimer(), but login time and cached profiles stay.
metric_collector_->StopTimer();
EXPECT_FALSE(metric_collector_->IsRunning());
EXPECT_FALSE(metric_collector_->login_time().is_null());
EXPECT_FALSE(cached_profile_data_.empty());
}
TEST_F(MetricCollectorTest, ScheduleSuspendDoneCollection) {
const auto kSuspendDuration = base::Minutes(3);
metric_collector_->ScheduleSuspendDoneCollection(kSuspendDuration);
// The timer should be running at this point.
EXPECT_TRUE(metric_collector_->IsRunning());
// Fast forward the time by the max collection delay.
task_environment_.FastForwardBy(kMaxCollectionDelay);
// Check that the SuspendDone trigger produced one profile.
ASSERT_EQ(1U, cached_profile_data_.size());
const SampledProfile& profile = cached_profile_data_[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());
// A profile collection rearms the timer for a new perodic collection.
// Check that the timer is running.
EXPECT_TRUE(metric_collector_->IsRunning());
cached_profile_data_.clear();
// Currently, any collection from another trigger event pushes the periodic
// collection interval forward by kPeriodicCollectionInterval. Since we had
// a SuspendDone collection, we should not see any new profiles during the
// next periodic collection interval, but see one in the following interval.
task_environment_.FastForwardBy(kPeriodicCollectionInterval -
kMaxCollectionDelay);
EXPECT_TRUE(cached_profile_data_.empty());
task_environment_.FastForwardBy(kPeriodicCollectionInterval);
ASSERT_EQ(1U, cached_profile_data_.size());
const SampledProfile& profile2 = cached_profile_data_[0];
EXPECT_EQ(SampledProfile::PERIODIC_COLLECTION, profile2.trigger_event());
}
TEST_F(MetricCollectorTest, ScheduleSessionRestoreCollection) {
const int kRestoredTabs = 7;
metric_collector_->ScheduleSessionRestoreCollection(kRestoredTabs);
// The timer should be running at this point.
EXPECT_TRUE(metric_collector_->IsRunning());
// Fast forward the time by the max collection delay.
task_environment_.FastForwardBy(kMaxCollectionDelay);
ASSERT_EQ(1U, cached_profile_data_.size());
const SampledProfile& profile = cached_profile_data_[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());
// Timer is rearmed for periodic collection after each collection.
// Check that the timer is running.
EXPECT_TRUE(metric_collector_->IsRunning());
cached_profile_data_.clear();
// A second SessionRestoreDone call is throttled.
metric_collector_->ScheduleSessionRestoreCollection(1);
// Fast forward the time by the max collection delay.
task_environment_.FastForwardBy(kMaxCollectionDelay);
// This should find no new session restore profiles.
EXPECT_TRUE(cached_profile_data_.empty());
// Currently, any collection from another trigger event pushes the periodic
// collection interval forward by kPeriodicCollectionInterval. Since we had
// a SessionRestore collection, we should not see any new profiles during the
// current periodic collection interval, but see one in the next interval.
task_environment_.FastForwardBy(kPeriodicCollectionInterval -
kMaxCollectionDelay * 2);
EXPECT_TRUE(cached_profile_data_.empty());
// Advance clock another collection interval. We should find a profile.
task_environment_.FastForwardBy(kPeriodicCollectionInterval);
ASSERT_EQ(1U, cached_profile_data_.size());
const SampledProfile& profile2 = cached_profile_data_[0];
EXPECT_EQ(SampledProfile::PERIODIC_COLLECTION, profile2.trigger_event());
// Advance the clock another periodic collection interval. This run should
// include a new periodic collection, but no session restore.
cached_profile_data_.clear();
task_environment_.FastForwardBy(kPeriodicCollectionInterval);
ASSERT_EQ(1U, cached_profile_data_.size());
const SampledProfile& profile3 = cached_profile_data_[0];
EXPECT_EQ(SampledProfile::PERIODIC_COLLECTION, profile3.trigger_event());
}
TEST_F(MetricCollectorTest, ScheduleIntervalCollection) {
// Timer is active after login and a periodic collection is scheduled.
EXPECT_TRUE(metric_collector_->IsRunning());
// Advance the clock by a periodic collection interval. We must have a
// periodic collection profile.
task_environment_.FastForwardBy(kPeriodicCollectionInterval);
ASSERT_EQ(1U, cached_profile_data_.size());
const SampledProfile& profile = cached_profile_data_[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());
ASSERT_TRUE(profile.has_perf_data());
EXPECT_EQ(perf_data_proto_.SerializeAsString(),
profile.perf_data().SerializeAsString());
// Make sure timer is rearmed after each collection.
EXPECT_TRUE(metric_collector_->IsRunning());
}
// Setting the sampling factors to zero should disable the triggers.
// Otherwise, it could cause a div-by-zero crash.
TEST_F(MetricCollectorTest, ZeroSamplingFactorDisablesTrigger) {
// Define params with zero sampling factors.
CollectionParams test_params;
test_params.resume_from_suspend.sampling_factor = 0;
test_params.restore_session.sampling_factor = 0;
metric_collector_ = std::make_unique<TestMetricCollector>(test_params);
metric_collector_->Init();
metric_collector_->RecordUserLogin(base::TimeTicks::Now());
// Cancel the background collection.
metric_collector_->StopTimer();
EXPECT_FALSE(metric_collector_->IsRunning())
<< "Sanity: timer should not be running.";
// Calling ScheduleSuspendDoneCollection or ScheduleSessionRestoreCollection
// should not start the timer that triggers collection.
metric_collector_->ScheduleSuspendDoneCollection(base::Minutes(10));
EXPECT_FALSE(metric_collector_->IsRunning());
metric_collector_->ScheduleSessionRestoreCollection(100);
EXPECT_FALSE(metric_collector_->IsRunning());
}
TEST_F(MetricCollectorTest, ZeroPeriodicIntervalDisablesCollection) {
// Define params with zero periodic interval.
CollectionParams test_params;
test_params.periodic_interval = base::Milliseconds(0);
metric_collector_ = std::make_unique<TestMetricCollector>(test_params);
metric_collector_->Init();
metric_collector_->RecordUserLogin(base::TimeTicks::Now());
EXPECT_FALSE(metric_collector_->IsRunning())
<< "Sanity: timer should not be running.";
// Advance the clock by 10 hours. We should have no profile and timer is not
// running.
task_environment_.FastForwardBy(base::Hours(10));
EXPECT_FALSE(metric_collector_->IsRunning())
<< "Sanity: timer should not be running.";
ASSERT_TRUE(cached_profile_data_.empty());
}
} // namespace internal
} // namespace metrics