blob: 10a7622295b58b4ce030cd2136f066aab523b39f [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/arc/metrics/arc_metrics_service.h"
#include <algorithm>
#include <array>
#include <map>
#include <utility>
#include <vector>
#include "ash/public/cpp/app_types.h"
#include "base/metrics/histogram_samples.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/metrics/histogram_tester.h"
#include "chromeos/dbus/session_manager/fake_session_manager_client.h"
#include "components/arc/arc_prefs.h"
#include "components/arc/arc_service_manager.h"
#include "components/arc/metrics/arc_metrics_constants.h"
#include "components/arc/metrics/stability_metrics_manager.h"
#include "components/arc/test/test_browser_context.h"
#include "components/prefs/testing_pref_service.h"
#include "components/session_manager/core/session_manager.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/test/test_windows.h"
#include "ui/aura/window.h"
namespace arc {
namespace {
// The event names the container sends to Chrome.
constexpr std::array<const char*, 11> kBootEvents{
"boot_progress_start",
"boot_progress_preload_start",
"boot_progress_preload_end",
"boot_progress_system_run",
"boot_progress_pms_start",
"boot_progress_pms_system_scan_start",
"boot_progress_pms_data_scan_start",
"boot_progress_pms_scan_end",
"boot_progress_pms_ready",
"boot_progress_ams_ready",
"boot_progress_enable_screen"};
constexpr const char kBootProgressArcUpgraded[] = "boot_progress_arc_upgraded";
constexpr char kAppTypeArcAppLauncher[] = "ArcAppLauncher";
constexpr char kAppTypeArcOther[] = "ArcOther";
constexpr char kAppTypeFirstParty[] = "FirstParty";
constexpr char kAppTypeGmsCore[] = "GmsCore";
constexpr char kAppTypePlayStore[] = "PlayStore";
constexpr char kAppTypeSystemServer[] = "SystemServer";
constexpr char kAppTypeSystem[] = "SystemApp";
constexpr char kAppTypeOther[] = "Other";
constexpr char kAppOverall[] = "Overall";
constexpr std::array<const char*, 9> kAppTypes{
kAppTypeArcAppLauncher, kAppTypeArcOther, kAppTypeFirstParty,
kAppTypeGmsCore, kAppTypePlayStore, kAppTypeSystemServer,
kAppTypeSystem, kAppTypeOther, kAppOverall,
};
std::string CreateAnrKey(const std::string& app_type, mojom::AnrType type) {
std::stringstream output;
output << app_type << "/" << type;
return output.str();
}
mojom::AnrPtr GetAnr(mojom::AnrSource source, mojom::AnrType type) {
return mojom::Anr::New(type, source);
}
void VerifyAnr(const base::HistogramTester& tester,
const std::map<std::string, int>& expectation) {
std::map<std::string, int> current;
for (const char* app_type : kAppTypes) {
const std::vector<base::Bucket> buckets =
tester.GetAllSamples("Arc.Anr." + std::string(app_type));
for (const auto& bucket : buckets) {
current[CreateAnrKey(app_type, static_cast<mojom::AnrType>(bucket.min))] =
bucket.count;
}
}
EXPECT_EQ(expectation, current);
}
class ArcMetricsServiceTest : public testing::Test {
protected:
ArcMetricsServiceTest() {
prefs::RegisterLocalStatePrefs(local_state_.registry());
StabilityMetricsManager::Initialize(&local_state_);
chromeos::PowerManagerClient::InitializeFake();
chromeos::SessionManagerClient::InitializeFakeInMemory();
chromeos::FakeSessionManagerClient::Get()->set_arc_available(true);
arc_service_manager_ = std::make_unique<ArcServiceManager>();
context_ = std::make_unique<TestBrowserContext>();
prefs::RegisterProfilePrefs(context_->pref_registry());
service_ =
ArcMetricsService::GetForBrowserContextForTesting(context_.get());
CreateFakeWindows();
}
~ArcMetricsServiceTest() override {
fake_non_arc_window_.reset();
fake_arc_window_.reset();
context_.reset();
arc_service_manager_.reset();
chromeos::SessionManagerClient::Shutdown();
chromeos::PowerManagerClient::Shutdown();
StabilityMetricsManager::Shutdown();
}
ArcMetricsService* service() { return service_; }
void SetArcStartTimeInMs(uint64_t arc_start_time_in_ms) {
const base::TimeTicks arc_start_time =
base::TimeDelta::FromMilliseconds(arc_start_time_in_ms) +
base::TimeTicks();
chromeos::FakeSessionManagerClient::Get()->set_arc_start_time(
arc_start_time);
}
std::vector<mojom::BootProgressEventPtr> GetBootProgressEvents(
uint64_t start_in_ms,
uint64_t step_in_ms) {
std::vector<mojom::BootProgressEventPtr> events;
for (size_t i = 0; i < kBootEvents.size(); ++i) {
events.emplace_back(mojom::BootProgressEvent::New(
kBootEvents[i], start_in_ms + (step_in_ms * i)));
}
return events;
}
aura::Window* fake_arc_window() { return fake_arc_window_.get(); }
aura::Window* fake_non_arc_window() { return fake_non_arc_window_.get(); }
private:
void CreateFakeWindows() {
fake_arc_window_.reset(aura::test::CreateTestWindowWithId(
/*id=*/0, nullptr));
fake_arc_window_->SetProperty(aura::client::kAppType,
static_cast<int>(ash::AppType::ARC_APP));
fake_non_arc_window_.reset(aura::test::CreateTestWindowWithId(
/*id=*/1, nullptr));
}
content::BrowserTaskEnvironment task_environment_;
TestingPrefServiceSimple local_state_;
session_manager::SessionManager session_manager_;
std::unique_ptr<ArcServiceManager> arc_service_manager_;
std::unique_ptr<TestBrowserContext> context_;
ArcMetricsService* service_;
std::unique_ptr<aura::Window> fake_arc_window_;
std::unique_ptr<aura::Window> fake_non_arc_window_;
DISALLOW_COPY_AND_ASSIGN(ArcMetricsServiceTest);
};
// Tests that ReportBootProgress() actually records UMA stats.
TEST_F(ArcMetricsServiceTest, ReportBootProgress_FirstBoot) {
// Start the full ARC container at t=10. Also set boot_progress_start to 10,
// boot_progress_preload_start to 11, and so on.
constexpr uint64_t kArcStartTimeMs = 10;
SetArcStartTimeInMs(kArcStartTimeMs);
std::vector<mojom::BootProgressEventPtr> events(
GetBootProgressEvents(kArcStartTimeMs, 1 /* step_in_ms */));
// Call ReportBootProgress() and then confirm that
// Arc.boot_progress_start.FirstBoot is recorded with 0 (ms),
// Arc.boot_progress_preload_start.FirstBoot is with 1 (ms), etc.
base::HistogramTester tester;
service()->ReportBootProgress(std::move(events), mojom::BootType::FIRST_BOOT);
base::RunLoop().RunUntilIdle();
for (size_t i = 0; i < kBootEvents.size(); ++i) {
tester.ExpectUniqueSample(
std::string("Arc.") + kBootEvents[i] + ".FirstBoot", i,
1 /* count of the sample */);
}
// Confirm that Arc.AndroidBootTime.FirstBoot is also recorded, and has the
// same value as "Arc.boot_progress_enable_screen.FirstBoot".
std::unique_ptr<base::HistogramSamples> samples =
tester.GetHistogramSamplesSinceCreation(
"Arc." + std::string(kBootEvents.back()) + ".FirstBoot");
ASSERT_TRUE(samples.get());
tester.ExpectUniqueSample("Arc.AndroidBootTime.FirstBoot", samples->sum(), 1);
}
// Does the same but with negative values and FIRST_BOOT_AFTER_UPDATE.
TEST_F(ArcMetricsServiceTest, ReportBootProgress_FirstBootAfterUpdate) {
// Start the full ARC container at t=10. Also set boot_progress_start to 5,
// boot_progress_preload_start to 7, and so on. This can actually happen
// because the mini container can finish up to boot_progress_preload_end
// before the full container is started.
constexpr uint64_t kArcStartTimeMs = 10;
SetArcStartTimeInMs(kArcStartTimeMs);
std::vector<mojom::BootProgressEventPtr> events(
GetBootProgressEvents(kArcStartTimeMs - 5, 2 /* step_in_ms */));
// Call ReportBootProgress() and then confirm that
// Arc.boot_progress_start.FirstBoot is recorded with 0 (ms),
// Arc.boot_progress_preload_start.FirstBoot is with 0 (ms), etc. Unlike our
// performance dashboard where negative performance numbers are treated as-is,
// UMA treats them as zeros.
base::HistogramTester tester;
// This time, use mojom::BootType::FIRST_BOOT_AFTER_UPDATE.
service()->ReportBootProgress(std::move(events),
mojom::BootType::FIRST_BOOT_AFTER_UPDATE);
base::RunLoop().RunUntilIdle();
for (size_t i = 0; i < kBootEvents.size(); ++i) {
const int expected = std::max<int>(0, i * 2 - 5);
tester.ExpectUniqueSample(
std::string("Arc.") + kBootEvents[i] + ".FirstBootAfterUpdate",
expected, 1);
}
std::unique_ptr<base::HistogramSamples> samples =
tester.GetHistogramSamplesSinceCreation(
"Arc." + std::string(kBootEvents.back()) + ".FirstBootAfterUpdate");
ASSERT_TRUE(samples.get());
tester.ExpectUniqueSample("Arc.AndroidBootTime.FirstBootAfterUpdate",
samples->sum(), 1);
}
// Does the same but with REGULAR_BOOT.
TEST_F(ArcMetricsServiceTest, ReportBootProgress_RegularBoot) {
constexpr uint64_t kArcStartTimeMs = 10;
SetArcStartTimeInMs(kArcStartTimeMs);
std::vector<mojom::BootProgressEventPtr> events(
GetBootProgressEvents(kArcStartTimeMs - 5, 2 /* step_in_ms */));
base::HistogramTester tester;
service()->ReportBootProgress(std::move(events),
mojom::BootType::REGULAR_BOOT);
base::RunLoop().RunUntilIdle();
for (size_t i = 0; i < kBootEvents.size(); ++i) {
const int expected = std::max<int>(0, i * 2 - 5);
tester.ExpectUniqueSample(
std::string("Arc.") + kBootEvents[i] + ".RegularBoot", expected, 1);
}
std::unique_ptr<base::HistogramSamples> samples =
tester.GetHistogramSamplesSinceCreation(
"Arc." + std::string(kBootEvents.back()) + ".RegularBoot");
ASSERT_TRUE(samples.get());
tester.ExpectUniqueSample("Arc.AndroidBootTime.RegularBoot", samples->sum(),
1);
}
// Tests that no UMA is recorded when nothing is reported.
TEST_F(ArcMetricsServiceTest, ReportBootProgress_EmptyResults) {
SetArcStartTimeInMs(100);
std::vector<mojom::BootProgressEventPtr> events; // empty
base::HistogramTester tester;
service()->ReportBootProgress(std::move(events), mojom::BootType::FIRST_BOOT);
base::RunLoop().RunUntilIdle();
for (size_t i = 0; i < kBootEvents.size(); ++i) {
tester.ExpectTotalCount(std::string("Arc.") + kBootEvents[i] + ".FirstBoot",
0);
}
tester.ExpectTotalCount("Arc.AndroidBootTime.FirstBoot", 0);
}
// Tests that no UMA is recorded when BootType is invalid.
TEST_F(ArcMetricsServiceTest, ReportBootProgress_InvalidBootType) {
SetArcStartTimeInMs(100);
std::vector<mojom::BootProgressEventPtr> events(
GetBootProgressEvents(123, 456));
base::HistogramTester tester;
service()->ReportBootProgress(std::move(events), mojom::BootType::UNKNOWN);
base::RunLoop().RunUntilIdle();
for (const std::string& suffix :
{".FirstBoot", ".FirstBootAfterUpdate", ".RegularBoot"}) {
tester.ExpectTotalCount("Arc." + (kBootEvents.front() + suffix), 0);
tester.ExpectTotalCount("Arc." + (kBootEvents.back() + suffix), 0);
tester.ExpectTotalCount("Arc.AndroidBootTime" + suffix, 0);
}
}
TEST_F(ArcMetricsServiceTest, ReportNativeBridge) {
// SetArcNativeBridgeType should be called once ArcMetricsService is
// constructed.
EXPECT_EQ(StabilityMetricsManager::Get()->GetArcNativeBridgeType(),
NativeBridgeType::UNKNOWN);
service()->ReportNativeBridge(mojom::NativeBridgeType::NONE);
EXPECT_EQ(StabilityMetricsManager::Get()->GetArcNativeBridgeType(),
NativeBridgeType::NONE);
service()->ReportNativeBridge(mojom::NativeBridgeType::HOUDINI);
EXPECT_EQ(StabilityMetricsManager::Get()->GetArcNativeBridgeType(),
NativeBridgeType::HOUDINI);
service()->ReportNativeBridge(mojom::NativeBridgeType::NDK_TRANSLATION);
EXPECT_EQ(StabilityMetricsManager::Get()->GetArcNativeBridgeType(),
NativeBridgeType::NDK_TRANSLATION);
}
TEST_F(ArcMetricsServiceTest, RecordArcWindowFocusAction) {
base::HistogramTester tester;
service()->OnWindowActivated(
wm::ActivationChangeObserver::ActivationReason::INPUT_EVENT,
fake_arc_window(), nullptr);
tester.ExpectBucketCount(
"Arc.UserInteraction",
static_cast<int>(UserInteractionType::APP_CONTENT_WINDOW_INTERACTION), 1);
}
TEST_F(ArcMetricsServiceTest, RecordNothingNonArcWindowFocusAction) {
base::HistogramTester tester;
// Focus an ARC window once so that the histogram is created.
service()->OnWindowActivated(
wm::ActivationChangeObserver::ActivationReason::INPUT_EVENT,
fake_arc_window(), nullptr);
tester.ExpectBucketCount(
"Arc.UserInteraction",
static_cast<int>(UserInteractionType::APP_CONTENT_WINDOW_INTERACTION), 1);
// Focusing a non-ARC window should not increase the bucket count.
service()->OnWindowActivated(
wm::ActivationChangeObserver::ActivationReason::INPUT_EVENT,
fake_non_arc_window(), nullptr);
tester.ExpectBucketCount(
"Arc.UserInteraction",
static_cast<int>(UserInteractionType::APP_CONTENT_WINDOW_INTERACTION), 1);
}
TEST_F(ArcMetricsServiceTest, GetArcStartTimeFromEvents) {
constexpr uint64_t kArcStartTimeMs = 10;
std::vector<mojom::BootProgressEventPtr> events(
GetBootProgressEvents(kArcStartTimeMs, 1 /* step_in_ms */));
events.emplace_back(
mojom::BootProgressEvent::New(kBootProgressArcUpgraded, kArcStartTimeMs));
absl::optional<base::TimeTicks> arc_start_time =
service()->GetArcStartTimeFromEvents(events);
EXPECT_TRUE(arc_start_time.has_value());
EXPECT_EQ(*arc_start_time,
base::TimeDelta::FromMilliseconds(10) + base::TimeTicks());
// Check that the upgrade event was removed from events.
EXPECT_TRUE(std::none_of(
events.begin(), events.end(), [](const mojom::BootProgressEventPtr& ev) {
return ev->event.compare(kBootProgressArcUpgraded) == 0;
}));
}
TEST_F(ArcMetricsServiceTest, GetArcStartTimeFromEvents_NoArcUpgradedEvent) {
constexpr uint64_t kArcStartTimeMs = 10;
std::vector<mojom::BootProgressEventPtr> events(
GetBootProgressEvents(kArcStartTimeMs, 1 /* step_in_ms */));
absl::optional<base::TimeTicks> arc_start_time =
service()->GetArcStartTimeFromEvents(events);
EXPECT_FALSE(arc_start_time.has_value());
}
TEST_F(ArcMetricsServiceTest, UserInteractionObserver) {
class Observer : public ArcMetricsService::UserInteractionObserver {
public:
void OnUserInteraction(UserInteractionType type) override {
this->type = type;
}
absl::optional<UserInteractionType> type;
} observer;
service()->AddUserInteractionObserver(&observer);
// This calls RecordArcUserInteraction() with APP_CONTENT_WINDOW_INTERACTION.
service()->OnWindowActivated(
wm::ActivationChangeObserver::ActivationReason::INPUT_EVENT,
fake_arc_window(), nullptr);
ASSERT_TRUE(observer.type);
EXPECT_EQ(UserInteractionType::APP_CONTENT_WINDOW_INTERACTION,
*observer.type);
service()->RemoveUserInteractionObserver(&observer);
}
TEST_F(ArcMetricsServiceTest, ArcAnr) {
base::HistogramTester tester;
std::map<std::string, int> expectation;
service()->ReportAnr(
GetAnr(mojom::AnrSource::OTHER, mojom::AnrType::UNKNOWN));
expectation[CreateAnrKey(kAppOverall, mojom::AnrType::UNKNOWN)] = 1;
expectation[CreateAnrKey(kAppTypeOther, mojom::AnrType::UNKNOWN)] = 1;
VerifyAnr(tester, expectation);
service()->ReportAnr(
GetAnr(mojom::AnrSource::SYSTEM_SERVER, mojom::AnrType::INPUT));
expectation[CreateAnrKey(kAppOverall, mojom::AnrType::INPUT)] = 1;
expectation[CreateAnrKey(kAppTypeSystemServer, mojom::AnrType::INPUT)] = 1;
VerifyAnr(tester, expectation);
service()->ReportAnr(
GetAnr(mojom::AnrSource::SYSTEM_SERVER, mojom::AnrType::SERVICE));
expectation[CreateAnrKey(kAppOverall, mojom::AnrType::SERVICE)] = 1;
expectation[CreateAnrKey(kAppTypeSystemServer, mojom::AnrType::SERVICE)] = 1;
VerifyAnr(tester, expectation);
service()->ReportAnr(
GetAnr(mojom::AnrSource::GMS_CORE, mojom::AnrType::BROADCAST));
expectation[CreateAnrKey(kAppOverall, mojom::AnrType::BROADCAST)] = 1;
expectation[CreateAnrKey(kAppTypeGmsCore, mojom::AnrType::BROADCAST)] = 1;
VerifyAnr(tester, expectation);
service()->ReportAnr(
GetAnr(mojom::AnrSource::PLAY_STORE, mojom::AnrType::CONTENT_PROVIDER));
expectation[CreateAnrKey(kAppOverall, mojom::AnrType::CONTENT_PROVIDER)] = 1;
expectation[CreateAnrKey(kAppTypePlayStore,
mojom::AnrType::CONTENT_PROVIDER)] = 1;
VerifyAnr(tester, expectation);
service()->ReportAnr(
GetAnr(mojom::AnrSource::FIRST_PARTY, mojom::AnrType::APP_REQUESTED));
expectation[CreateAnrKey(kAppOverall, mojom::AnrType::APP_REQUESTED)] = 1;
expectation[CreateAnrKey(kAppTypeFirstParty, mojom::AnrType::APP_REQUESTED)] =
1;
VerifyAnr(tester, expectation);
service()->ReportAnr(
GetAnr(mojom::AnrSource::ARC_OTHER, mojom::AnrType::INPUT));
expectation[CreateAnrKey(kAppOverall, mojom::AnrType::INPUT)] = 2;
expectation[CreateAnrKey(kAppTypeArcOther, mojom::AnrType::INPUT)] = 1;
VerifyAnr(tester, expectation);
service()->ReportAnr(
GetAnr(mojom::AnrSource::ARC_APP_LAUNCHER, mojom::AnrType::SERVICE));
expectation[CreateAnrKey(kAppOverall, mojom::AnrType::SERVICE)] = 2;
expectation[CreateAnrKey(kAppTypeArcAppLauncher, mojom::AnrType::SERVICE)] =
1;
VerifyAnr(tester, expectation);
}
} // namespace
} // namespace arc