chromium/chrome/browser/metrics/perf/profile_provider_chromeos_unittest.cc

// 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/profile_provider_chromeos.h"

#include <stdint.h>

#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "base/test/bind.h"
#include "base/test/power_monitor_test.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "chrome/browser/metrics/perf/metric_collector.h"
#include "chrome/browser/metrics/perf/metric_provider.h"
#include "chrome/browser/metrics/perf/windowed_incognito_observer.h"
#include "chromeos/ash/components/login/login_state/login_state.h"
#include "content/public/browser/browser_task_traits.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"

namespace metrics {

namespace {

// Returns sample PerfDataProtos with custom timestamps. 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(int tstamp_sec) {
  PerfDataProto proto;
  proto.set_timestamp_sec(tstamp_sec);  // 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;
}

// Custome metric collectors to register with the profile provider for testing.
template <int TSTAMP>
class TestMetricCollector : public internal::MetricCollector {
 public:
  TestMetricCollector() : TestMetricCollector(CollectionParams()) {}
  explicit TestMetricCollector(const CollectionParams& collection_params)
      : internal::MetricCollector("UMA.CWP.TestData", collection_params) {}

  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(TSTAMP);
    // Create an incognito observer to test initialization on the UI thread.
    auto observer = WindowedIncognitoMonitor::CreateObserver();
    if (!observer->IncognitoActive()) {
      SaveSerializedPerfProto(std::move(sampled_profile),
                              perf_data_proto.SerializeAsString());
    }
  }

 private:
  base::WeakPtrFactory<TestMetricCollector> weak_factory_{this};
};

const base::TimeDelta kPeriodicCollectionInterval = base::Hours(1);
const base::TimeDelta kMaxCollectionDelay = base::Seconds(1);

// Allows access to some private methods for testing.
class TestProfileProvider : public ProfileProvider {
 public:
  TestProfileProvider() {
    // Add a couple of metric collectors. We differentiate between them using
    // different time stamps for profiles. Set sampling factors for triggers
    // to 1, so we always trigger collection.
    CollectionParams test_params;
    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;

    collectors_.clear();
    collectors_.push_back(std::make_unique<MetricProvider>(
        std::make_unique<TestMetricCollector<100>>(test_params), nullptr));
    collectors_.push_back(std::make_unique<MetricProvider>(
        std::make_unique<TestMetricCollector<200>>(test_params), nullptr));
  }

  using ProfileProvider::collectors_;
  using ProfileProvider::jank_monitor;
  using ProfileProvider::jankiness_collection_min_interval;
  using ProfileProvider::LoggedInStateChanged;
  using ProfileProvider::OnJankStarted;
  using ProfileProvider::OnSessionRestoreDone;
  using ProfileProvider::SuspendDone;

  TestProfileProvider(const TestProfileProvider&) = delete;
  TestProfileProvider& operator=(const TestProfileProvider&) = delete;
};

void ExpectTwoStoredPerfProfiles(
    const std::vector<SampledProfile>& stored_profiles,
    SampledProfile_TriggerEvent want_trigger_type,
    ThermalState want_thermal_state_type,
    int want_speed_limit) {
  ASSERT_EQ(2U, stored_profiles.size());
  // Both profiles must be of the given type and include perf data.
  const SampledProfile& profile1 = stored_profiles[0];
  const SampledProfile& profile2 = stored_profiles[1];
  EXPECT_EQ(want_trigger_type, profile1.trigger_event());
  ASSERT_TRUE(profile1.has_perf_data());
  EXPECT_EQ(want_trigger_type, profile2.trigger_event());
  ASSERT_TRUE(profile2.has_perf_data());
  // Both profiles must include the given thermal state,
  EXPECT_EQ(want_thermal_state_type, profile1.thermal_state());
  EXPECT_EQ(want_thermal_state_type, profile2.thermal_state());
  // ... and CPU speed limit.
  EXPECT_EQ(want_speed_limit, profile1.cpu_speed_limit_percent());
  EXPECT_EQ(want_speed_limit, profile2.cpu_speed_limit_percent());

  // We must have received a profile from each of the collectors.
  EXPECT_EQ(100u, profile1.perf_data().timestamp_sec());
  EXPECT_EQ(200u, profile2.perf_data().timestamp_sec());
}

}  // namespace

class ProfileProviderTest : public testing::Test {
 public:
  ProfileProviderTest()
      : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}

  ProfileProviderTest(const ProfileProviderTest&) = delete;
  ProfileProviderTest& operator=(const ProfileProviderTest&) = delete;

  void SetUp() override {
    // ProfileProvider requires ash::LoginState and
    // chromeos::PowerManagerClient to be initialized.
    chromeos::PowerManagerClient::InitializeFake();
    ash::LoginState::Initialize();
    test_power_monitor_source_.GenerateThermalThrottlingEvent(
        base::PowerThermalObserver::DeviceThermalState::kNominal);

    profile_provider_ = std::make_unique<TestProfileProvider>();
    profile_provider_->Init();
  }

  void TearDown() override {
    profile_provider_.reset();
    ash::LoginState::Shutdown();
    chromeos::PowerManagerClient::Shutdown();
  }

 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_;
  base::test::ScopedPowerMonitorTestSource test_power_monitor_source_;
  std::unique_ptr<TestProfileProvider> profile_provider_;
};

TEST_F(ProfileProviderTest, CheckSetup) {
  EXPECT_EQ(2U, profile_provider_->collectors_.size());

  // No profiles should be collected on start.
  std::vector<SampledProfile> stored_profiles;
  EXPECT_FALSE(profile_provider_->GetSampledProfiles(&stored_profiles));
  EXPECT_TRUE(stored_profiles.empty());
}

TEST_F(ProfileProviderTest, UserLoginLogout) {
  // No user is logged in, so no collection is scheduled to run.
  task_environment_.FastForwardBy(kPeriodicCollectionInterval);

  std::vector<SampledProfile> stored_profiles;
  EXPECT_FALSE(profile_provider_->GetSampledProfiles(&stored_profiles));
  EXPECT_TRUE(stored_profiles.empty());

  // Simulate a user log in, which should activate periodic collection for all
  // collectors.
  ash::LoginState::Get()->SetLoggedInState(
      ash::LoginState::LOGGED_IN_ACTIVE,
      ash::LoginState::LOGGED_IN_USER_REGULAR);

  // Run all pending tasks. SetLoggedInState has activated timers for periodic
  // collection causing timer based pending tasks.
  task_environment_.FastForwardBy(kPeriodicCollectionInterval);
  // We should find two profiles, one for each collector.
  EXPECT_TRUE(profile_provider_->GetSampledProfiles(&stored_profiles));
  ExpectTwoStoredPerfProfiles(
      stored_profiles, SampledProfile::PERIODIC_COLLECTION,
      THERMAL_STATE_NOMINAL, base::PowerThermalObserver::kSpeedLimitMax);

  // Periodic collection is deactivated when user logs out. Simulate a user
  // logout event.
  ash::LoginState::Get()->SetLoggedInState(
      ash::LoginState::LOGGED_IN_NONE, ash::LoginState::LOGGED_IN_USER_NONE);
  // Run all pending tasks.
  task_environment_.FastForwardBy(kPeriodicCollectionInterval);
  // We should find no new profiles.
  stored_profiles.clear();
  EXPECT_FALSE(profile_provider_->GetSampledProfiles(&stored_profiles));
  ASSERT_TRUE(stored_profiles.empty());
}

TEST_F(ProfileProviderTest, SuspendDone_NoUserLoggedIn_NoCollection) {
  // No user is logged in, so no collection is done on resume from suspend.
  profile_provider_->SuspendDone(base::Minutes(10));
  // Run all pending tasks.
  task_environment_.FastForwardBy(kMaxCollectionDelay);

  std::vector<SampledProfile> stored_profiles;
  EXPECT_FALSE(profile_provider_->GetSampledProfiles(&stored_profiles));
  EXPECT_TRUE(stored_profiles.empty());
}

TEST_F(ProfileProviderTest, CanceledSuspend_NoCollection) {
  // Set user state as logged in. This activates periodic collection, but we can
  // deactivate it for each collector.
  ash::LoginState::Get()->SetLoggedInState(
      ash::LoginState::LOGGED_IN_ACTIVE,
      ash::LoginState::LOGGED_IN_USER_REGULAR);
  for (auto& collector : profile_provider_->collectors_) {
    collector->Deactivate();
  }

  // Trigger a canceled suspend (zero sleep duration).
  profile_provider_->SuspendDone(base::Seconds(0));
  // Run all pending tasks.
  task_environment_.FastForwardBy(kMaxCollectionDelay);

  // We should find no profiles.
  std::vector<SampledProfile> stored_profiles;
  EXPECT_FALSE(profile_provider_->GetSampledProfiles(&stored_profiles));
  ASSERT_TRUE(stored_profiles.empty());
}

TEST_F(ProfileProviderTest, SuspendDone) {
  // Set user state as logged in. This activates periodic collection, but other
  // triggers like SUSPEND_DONE take precedence.
  ash::LoginState::Get()->SetLoggedInState(
      ash::LoginState::LOGGED_IN_ACTIVE,
      ash::LoginState::LOGGED_IN_USER_REGULAR);

  // Trigger a resume from suspend.
  profile_provider_->SuspendDone(base::Minutes(10));
  // Run all pending tasks.
  task_environment_.FastForwardBy(kMaxCollectionDelay);

  // We should find two profiles, one for each collector.
  std::vector<SampledProfile> stored_profiles;
  EXPECT_TRUE(profile_provider_->GetSampledProfiles(&stored_profiles));
  ExpectTwoStoredPerfProfiles(
      stored_profiles, SampledProfile::RESUME_FROM_SUSPEND,
      THERMAL_STATE_NOMINAL, base::PowerThermalObserver::kSpeedLimitMax);
}

TEST_F(ProfileProviderTest, OnSessionRestoreDone_NoUserLoggedIn_NoCollection) {
  // No user is logged in, so no collection is done on session restore.
  profile_provider_->OnSessionRestoreDone(nullptr, 10);
  // Run all pending tasks.
  task_environment_.FastForwardBy(kMaxCollectionDelay);

  std::vector<SampledProfile> stored_profiles;
  EXPECT_FALSE(profile_provider_->GetSampledProfiles(&stored_profiles));
  EXPECT_TRUE(stored_profiles.empty());
}

TEST_F(ProfileProviderTest, OnSessionRestoreDone) {
  // Set user state as logged in. This activates periodic collection, but we can
  // deactivate it for each collector.
  ash::LoginState::Get()->SetLoggedInState(
      ash::LoginState::LOGGED_IN_ACTIVE,
      ash::LoginState::LOGGED_IN_USER_REGULAR);
  for (auto& collector : profile_provider_->collectors_) {
    collector->Deactivate();
  }

  // Trigger a session restore.
  profile_provider_->OnSessionRestoreDone(nullptr, 10);
  // Run all pending tasks.
  task_environment_.FastForwardBy(kMaxCollectionDelay);

  // We should find two profiles, one for each collector.
  std::vector<SampledProfile> stored_profiles;
  EXPECT_TRUE(profile_provider_->GetSampledProfiles(&stored_profiles));
  ExpectTwoStoredPerfProfiles(stored_profiles, SampledProfile::RESTORE_SESSION,
                              THERMAL_STATE_NOMINAL,
                              base::PowerThermalObserver::kSpeedLimitMax);
}

TEST_F(ProfileProviderTest, ThermalStateChangesAreCaptured) {
  // Simulate a user log in, which should activate periodic collection for all
  // collectors.
  ash::LoginState::Get()->SetLoggedInState(
      ash::LoginState::LOGGED_IN_ACTIVE,
      ash::LoginState::LOGGED_IN_USER_REGULAR);
  test_power_monitor_source_.GenerateThermalThrottlingEvent(
      base::PowerThermalObserver::DeviceThermalState::kCritical);

  // Run all pending tasks. SetLoggedInState has activated timers for periodic
  // collection causing timer based pending tasks.
  task_environment_.FastForwardBy(kPeriodicCollectionInterval);
  // We should find two profiles, one for each collector.
  std::vector<SampledProfile> stored_profiles;
  EXPECT_TRUE(profile_provider_->GetSampledProfiles(&stored_profiles));
  ExpectTwoStoredPerfProfiles(
      stored_profiles, SampledProfile::PERIODIC_COLLECTION,
      THERMAL_STATE_CRITICAL, base::PowerThermalObserver::kSpeedLimitMax);
}

TEST_F(ProfileProviderTest, CpuSpeedChangesAreCaptured) {
  // Simulate a user log in, which should activate periodic collection for all
  // collectors.
  ash::LoginState::Get()->SetLoggedInState(
      ash::LoginState::LOGGED_IN_ACTIVE,
      ash::LoginState::LOGGED_IN_USER_REGULAR);
  test_power_monitor_source_.GenerateSpeedLimitEvent(50);

  // Run all pending tasks. SetLoggedInState has activated timers for periodic
  // collection causing timer based pending tasks.
  task_environment_.FastForwardBy(kPeriodicCollectionInterval);
  // We should find two profiles, one for each collector.
  std::vector<SampledProfile> stored_profiles;
  EXPECT_TRUE(profile_provider_->GetSampledProfiles(&stored_profiles));
  ExpectTwoStoredPerfProfiles(stored_profiles,
                              SampledProfile::PERIODIC_COLLECTION,
                              THERMAL_STATE_NOMINAL, 50);
}

// Test profile collection triggered when a jank starts.
TEST_F(ProfileProviderTest, JankMonitorCallbacks) {
  // Jankiness collection requires that the user is logged in.
  ash::LoginState::Get()->SetLoggedInState(
      ash::LoginState::LOGGED_IN_ACTIVE,
      ash::LoginState::LOGGED_IN_USER_REGULAR);

  // Trigger a jankiness collection.
  profile_provider_->OnJankStarted();
  task_environment_.RunUntilIdle();

  // We should find two profiles, one for each collector.
  std::vector<SampledProfile> stored_profiles;
  EXPECT_TRUE(profile_provider_->GetSampledProfiles(&stored_profiles));

  EXPECT_EQ(2U, stored_profiles.size());
  ExpectTwoStoredPerfProfiles(stored_profiles, SampledProfile::JANKY_TASK,
                              THERMAL_STATE_NOMINAL,
                              base::PowerThermalObserver::kSpeedLimitMax);
}

// Test throttling of JANKY_TASK collections: no consecutive collections within
// jankiness_collection_min_interval().
TEST_F(ProfileProviderTest, JankinessCollectionThrottled) {
  // Jankiness collection requires that the user is logged in.
  ash::LoginState::Get()->SetLoggedInState(
      ash::LoginState::LOGGED_IN_ACTIVE,
      ash::LoginState::LOGGED_IN_USER_REGULAR);

  // The first JANKY_TASK collection should succeed.
  profile_provider_->OnJankStarted();
  task_environment_.RunUntilIdle();

  std::vector<SampledProfile> stored_profiles;

  EXPECT_TRUE(profile_provider_->GetSampledProfiles(&stored_profiles));
  EXPECT_EQ(2U, stored_profiles.size());
  ExpectTwoStoredPerfProfiles(stored_profiles, SampledProfile::JANKY_TASK,
                              THERMAL_STATE_NOMINAL,
                              base::PowerThermalObserver::kSpeedLimitMax);

  stored_profiles.clear();

  // We are about to fast forward the clock a lot. Deactivate all collectors
  // to disable periodic collections.
  for (auto& collector : profile_provider_->collectors_) {
    collector->Deactivate();
  }

  // Fast forward time to 1 second before the throttling duration is over.
  task_environment_.FastForwardBy(
      profile_provider_->jankiness_collection_min_interval() -
      base::Seconds(1));

  // This collection within the minimum interval should be throttled.
  profile_provider_->OnJankStarted();
  task_environment_.RunUntilIdle();

  EXPECT_FALSE(profile_provider_->GetSampledProfiles(&stored_profiles));
  stored_profiles.clear();

  // Move the clock forward past the throttling duration. The next JANKY_TASK
  // collection should succeed.
  task_environment_.FastForwardBy(base::Seconds(1));

  profile_provider_->OnJankStarted();
  task_environment_.RunUntilIdle();

  EXPECT_TRUE(profile_provider_->GetSampledProfiles(&stored_profiles));
  EXPECT_EQ(2U, stored_profiles.size());
  ExpectTwoStoredPerfProfiles(stored_profiles, SampledProfile::JANKY_TASK,
                              THERMAL_STATE_NOMINAL,
                              base::PowerThermalObserver::kSpeedLimitMax);
}

// This class enables the jank monitor to test collections triggered by jank
// callbacks from the jank monitor.
class ProfileProviderJankinessTest : public ProfileProviderTest {
 public:
  void SetUp() override {
    ProfileProviderTest::SetUp();
    // Jankiness collection requires that the user is logged in.
    ash::LoginState::Get()->SetLoggedInState(
        ash::LoginState::LOGGED_IN_ACTIVE,
        ash::LoginState::LOGGED_IN_USER_REGULAR);
    // Deactivate each collectors to disable periodic collections.
    for (auto& collector : profile_provider_->collectors_) {
      collector->Deactivate();
    }
  }
};

// Test profile collection triggered by a UI thread jank.
TEST_F(ProfileProviderJankinessTest, JankMonitor_UI) {
  EXPECT_TRUE(profile_provider_->jank_monitor());
  // Post a janky task to the UI thread.
  content::GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE, base::BindLambdaForTesting([&]() {
        // This is a janky task that runs for 2 seconds.
        task_environment_.FastForwardBy(base::Seconds(2));
      }));
  task_environment_.RunUntilIdle();

  std::vector<SampledProfile> stored_profiles;
  EXPECT_TRUE(profile_provider_->GetSampledProfiles(&stored_profiles));

  EXPECT_EQ(2U, stored_profiles.size());
  ExpectTwoStoredPerfProfiles(stored_profiles, SampledProfile::JANKY_TASK,
                              THERMAL_STATE_NOMINAL,
                              base::PowerThermalObserver::kSpeedLimitMax);
}

// Test profile collection triggered by an IO thread jank.
TEST_F(ProfileProviderJankinessTest, JankMonitor_IO) {
  EXPECT_TRUE(profile_provider_->jank_monitor());
  // Post a janky task to the IO thread.
  content::GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE, base::BindLambdaForTesting([&]() {
        // This is a janky task that runs for 2 seconds.
        task_environment_.FastForwardBy(base::Seconds(2));
      }));
  task_environment_.RunUntilIdle();

  std::vector<SampledProfile> stored_profiles;
  EXPECT_TRUE(profile_provider_->GetSampledProfiles(&stored_profiles));

  EXPECT_EQ(2U, stored_profiles.size());
  ExpectTwoStoredPerfProfiles(stored_profiles, SampledProfile::JANKY_TASK,
                              THERMAL_STATE_NOMINAL,
                              base::PowerThermalObserver::kSpeedLimitMax);
}

namespace {

class TestStockProfileProvider : public ProfileProvider {
 public:
  TestStockProfileProvider() = default;

  using ProfileProvider::collectors_;

  TestStockProfileProvider(const TestStockProfileProvider&) = delete;
  TestStockProfileProvider& operator=(const TestStockProfileProvider&) = delete;
};

}  // namespace

class ProfileProviderStockTest : public testing::Test {
 public:
  ProfileProviderStockTest() = default;

  ProfileProviderStockTest(const ProfileProviderStockTest&) = delete;
  ProfileProviderStockTest& operator=(const ProfileProviderStockTest&) = delete;

  void SetUp() override {
    // ProfileProvider requires ash::LoginState and
    // chromeos::PowerManagerClient to be initialized.
    chromeos::PowerManagerClient::InitializeFake();
    ash::LoginState::Initialize();
  }

  void TearDown() override {
    ash::LoginState::Shutdown();
    chromeos::PowerManagerClient::Shutdown();
  }

 protected:
  content::BrowserTaskEnvironment task_environment_;
};

TEST_F(ProfileProviderStockTest, CheckSetup) {
  TestStockProfileProvider profile_provider;
  // We should have one collector registered.
  EXPECT_EQ(1u, profile_provider.collectors_.size());

  // After initialization, we should still have a single collector.
  profile_provider.Init();
  EXPECT_EQ(1u, profile_provider.collectors_.size());
}

}  // namespace metrics