blob: 39b3e7621d2961daba7f219057e025ee0140cd91 [file] [log] [blame]
// Copyright 2015 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/metrics/call_stack_profile_metrics_provider.h"
#include "base/metrics/field_trial.h"
#include "base/profiler/stack_sampling_profiler.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "components/metrics/proto/chrome_user_metrics_extension.pb.h"
#include "components/variations/entropy_provider.h"
#include "testing/gtest/include/gtest/gtest.h"
using base::StackSamplingProfiler;
using Frame = StackSamplingProfiler::Frame;
using Module = StackSamplingProfiler::Module;
using Profile = StackSamplingProfiler::CallStackProfile;
using Profiles = StackSamplingProfiler::CallStackProfiles;
using Sample = StackSamplingProfiler::Sample;
namespace metrics {
using Params = CallStackProfileMetricsProvider::Params;
// This test fixture enables the field trial that
// CallStackProfileMetricsProvider depends on to report profiles.
class CallStackProfileMetricsProviderTest : public testing::Test {
public:
CallStackProfileMetricsProviderTest()
: field_trial_list_(nullptr) {
base::FieldTrialList::CreateFieldTrial(
TestState::kFieldTrialName,
TestState::kReportProfilesGroupName);
TestState::ResetStaticStateForTesting();
}
~CallStackProfileMetricsProviderTest() override {}
// Utility function to append profiles to the metrics provider.
void AppendProfiles(const Params& params, const Profiles& profiles) {
CallStackProfileMetricsProvider::GetProfilerCallback(params).Run(profiles);
}
private:
// Exposes field trial/group names from the CallStackProfileMetricsProvider.
class TestState : public CallStackProfileMetricsProvider {
public:
using CallStackProfileMetricsProvider::kFieldTrialName;
using CallStackProfileMetricsProvider::kReportProfilesGroupName;
using CallStackProfileMetricsProvider::ResetStaticStateForTesting;
};
base::FieldTrialList field_trial_list_;
DISALLOW_COPY_AND_ASSIGN(CallStackProfileMetricsProviderTest);
};
// Checks that all properties from multiple profiles are filled as expected.
TEST_F(CallStackProfileMetricsProviderTest, MultipleProfiles) {
const uintptr_t module1_base_address = 0x1000;
const uintptr_t module2_base_address = 0x2000;
const uintptr_t module3_base_address = 0x3000;
const Module profile_modules[][2] = {
{
Module(
module1_base_address,
"ABCD",
#if defined(OS_WIN)
base::FilePath(L"c:\\some\\path\\to\\chrome.exe")
#else
base::FilePath("/some/path/to/chrome")
#endif
),
Module(
module2_base_address,
"EFGH",
#if defined(OS_WIN)
base::FilePath(L"c:\\some\\path\\to\\third_party.dll")
#else
base::FilePath("/some/path/to/third_party.so")
#endif
),
},
{
Module(
module3_base_address,
"MNOP",
#if defined(OS_WIN)
base::FilePath(L"c:\\some\\path\\to\\third_party2.dll")
#else
base::FilePath("/some/path/to/third_party2.so")
#endif
),
Module( // Repeated from the first profile.
module1_base_address,
"ABCD",
#if defined(OS_WIN)
base::FilePath(L"c:\\some\\path\\to\\chrome.exe")
#else
base::FilePath("/some/path/to/chrome")
#endif
)
}
};
// Values for Windows generated with:
// perl -MDigest::MD5=md5 -MEncode=encode
// -e 'for(@ARGV){printf "%x\n", unpack "Q>", md5 encode "UTF-16LE", $_}'
// chrome.exe third_party.dll third_party2.dll
//
// Values for Linux generated with:
// perl -MDigest::MD5=md5
// -e 'for(@ARGV){printf "%x\n", unpack "Q>", md5 $_}'
// chrome third_party.so third_party2.so
const uint64 profile_expected_name_md5_prefixes[][2] = {
{
#if defined(OS_WIN)
0x46c3e4166659ac02ULL,
0x7e2b8bfddeae1abaULL
#else
0x554838a8451ac36cUL,
0x843661148659c9f8UL
#endif
},
{
#if defined(OS_WIN)
0x87b66f4573a4d5caULL,
0x46c3e4166659ac02ULL
#else
0xb4647e539fa6ec9eUL,
0x554838a8451ac36cUL
#endif
}
};
// Represents two stack samples for each of two profiles, where each stack
// contains three frames. Each frame contains an instruction pointer and a
// module index corresponding to the module for the profile in
// profile_modules.
//
// So, the first stack sample below has its top frame in module 0 at an offset
// of 0x10 from the module's base address, the next-to-top frame in module 1
// at an offset of 0x20 from the module's base address, and the bottom frame
// in module 0 at an offset of 0x30 from the module's base address
const Frame profile_sample_frames[][2][3] = {
{
{
Frame(module1_base_address + 0x10, 0),
Frame(module2_base_address + 0x20, 1),
Frame(module1_base_address + 0x30, 0)
},
{
Frame(module2_base_address + 0x10, 1),
Frame(module1_base_address + 0x20, 0),
Frame(module2_base_address + 0x30, 1)
}
},
{
{
Frame(module3_base_address + 0x10, 0),
Frame(module1_base_address + 0x20, 1),
Frame(module3_base_address + 0x30, 0)
},
{
Frame(module1_base_address + 0x10, 1),
Frame(module3_base_address + 0x20, 0),
Frame(module1_base_address + 0x30, 1)
}
}
};
base::TimeDelta profile_durations[2] = {
base::TimeDelta::FromMilliseconds(100),
base::TimeDelta::FromMilliseconds(200)
};
base::TimeDelta profile_sampling_periods[2] = {
base::TimeDelta::FromMilliseconds(10),
base::TimeDelta::FromMilliseconds(20)
};
std::vector<Profile> profiles;
for (size_t i = 0; i < arraysize(profile_sample_frames); ++i) {
Profile profile;
profile.modules.insert(
profile.modules.end(), &profile_modules[i][0],
&profile_modules[i][0] + arraysize(profile_modules[i]));
for (size_t j = 0; j < arraysize(profile_sample_frames[i]); ++j) {
profile.samples.push_back(Sample());
Sample& sample = profile.samples.back();
sample.insert(sample.end(), &profile_sample_frames[i][j][0],
&profile_sample_frames[i][j][0] +
arraysize(profile_sample_frames[i][j]));
}
profile.profile_duration = profile_durations[i];
profile.sampling_period = profile_sampling_periods[i];
profiles.push_back(profile);
}
CallStackProfileMetricsProvider provider;
provider.OnRecordingEnabled();
AppendProfiles(
Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false),
profiles);
ChromeUserMetricsExtension uma_proto;
provider.ProvideGeneralMetrics(&uma_proto);
ASSERT_EQ(static_cast<int>(arraysize(profile_sample_frames)),
uma_proto.sampled_profile().size());
for (size_t i = 0; i < arraysize(profile_sample_frames); ++i) {
SCOPED_TRACE("profile " + base::SizeTToString(i));
const SampledProfile& sampled_profile = uma_proto.sampled_profile().Get(i);
ASSERT_TRUE(sampled_profile.has_call_stack_profile());
const CallStackProfile& call_stack_profile =
sampled_profile.call_stack_profile();
ASSERT_EQ(static_cast<int>(arraysize(profile_sample_frames[i])),
call_stack_profile.sample().size());
for (size_t j = 0; j < arraysize(profile_sample_frames[i]); ++j) {
SCOPED_TRACE("sample " + base::SizeTToString(j));
const CallStackProfile::Sample& proto_sample =
call_stack_profile.sample().Get(j);
ASSERT_EQ(static_cast<int>(arraysize(profile_sample_frames[i][j])),
proto_sample.entry().size());
ASSERT_TRUE(proto_sample.has_count());
EXPECT_EQ(1u, proto_sample.count());
for (size_t k = 0; k < arraysize(profile_sample_frames[i][j]); ++k) {
SCOPED_TRACE("frame " + base::SizeTToString(k));
const CallStackProfile::Entry& entry = proto_sample.entry().Get(k);
ASSERT_TRUE(entry.has_address());
const char* instruction_pointer = reinterpret_cast<const char*>(
profile_sample_frames[i][j][k].instruction_pointer);
const char* module_base_address = reinterpret_cast<const char*>(
profile_modules[i][profile_sample_frames[i][j][k].module_index]
.base_address);
EXPECT_EQ(static_cast<uint64>(instruction_pointer -
module_base_address), entry.address());
ASSERT_TRUE(entry.has_module_id_index());
EXPECT_EQ(profile_sample_frames[i][j][k].module_index,
static_cast<size_t>(entry.module_id_index()));
}
}
ASSERT_EQ(static_cast<int>(arraysize(profile_modules[i])),
call_stack_profile.module_id().size());
for (size_t j = 0; j < arraysize(profile_modules[i]); ++j) {
SCOPED_TRACE("module " + base::SizeTToString(j));
const CallStackProfile::ModuleIdentifier& module_identifier =
call_stack_profile.module_id().Get(j);
ASSERT_TRUE(module_identifier.has_build_id());
EXPECT_EQ(profile_modules[i][j].id, module_identifier.build_id());
ASSERT_TRUE(module_identifier.has_name_md5_prefix());
EXPECT_EQ(profile_expected_name_md5_prefixes[i][j],
module_identifier.name_md5_prefix());
}
ASSERT_TRUE(call_stack_profile.has_profile_duration_ms());
EXPECT_EQ(profile_durations[i].InMilliseconds(),
call_stack_profile.profile_duration_ms());
ASSERT_TRUE(call_stack_profile.has_sampling_period_ms());
EXPECT_EQ(profile_sampling_periods[i].InMilliseconds(),
call_stack_profile.sampling_period_ms());
ASSERT_TRUE(sampled_profile.has_trigger_event());
EXPECT_EQ(SampledProfile::PROCESS_STARTUP, sampled_profile.trigger_event());
}
}
// Checks that all duplicate samples are collapsed with
// preserve_sample_ordering = false.
TEST_F(CallStackProfileMetricsProviderTest, RepeatedStacksUnordered) {
const uintptr_t module_base_address = 0x1000;
const Module modules[] = {
Module(
module_base_address,
"ABCD",
#if defined(OS_WIN)
base::FilePath(L"c:\\some\\path\\to\\chrome.exe")
#else
base::FilePath("/some/path/to/chrome")
#endif
)
};
// Duplicate samples in slots 0, 2, and 3.
const Frame sample_frames[][1] = {
{ Frame(module_base_address + 0x10, 0), },
{ Frame(module_base_address + 0x20, 0), },
{ Frame(module_base_address + 0x10, 0), },
{ Frame(module_base_address + 0x10, 0) }
};
Profile profile;
profile.modules.insert(profile.modules.end(), &modules[0],
&modules[0] + arraysize(modules));
for (size_t i = 0; i < arraysize(sample_frames); ++i) {
profile.samples.push_back(Sample());
Sample& sample = profile.samples.back();
sample.insert(sample.end(), &sample_frames[i][0],
&sample_frames[i][0] + arraysize(sample_frames[i]));
}
profile.profile_duration = base::TimeDelta::FromMilliseconds(100);
profile.sampling_period = base::TimeDelta::FromMilliseconds(10);
CallStackProfileMetricsProvider provider;
provider.OnRecordingEnabled();
AppendProfiles(
Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false),
std::vector<Profile>(1, profile));
ChromeUserMetricsExtension uma_proto;
provider.ProvideGeneralMetrics(&uma_proto);
ASSERT_EQ(1, uma_proto.sampled_profile().size());
const SampledProfile& sampled_profile = uma_proto.sampled_profile().Get(0);
ASSERT_TRUE(sampled_profile.has_call_stack_profile());
const CallStackProfile& call_stack_profile =
sampled_profile.call_stack_profile();
ASSERT_EQ(2, call_stack_profile.sample().size());
for (int i = 0; i < 2; ++i) {
SCOPED_TRACE("sample " + base::IntToString(i));
const CallStackProfile::Sample& proto_sample =
call_stack_profile.sample().Get(i);
ASSERT_EQ(static_cast<int>(arraysize(sample_frames[i])),
proto_sample.entry().size());
ASSERT_TRUE(proto_sample.has_count());
EXPECT_EQ(i == 0 ? 3u : 1u, proto_sample.count());
for (size_t j = 0; j < arraysize(sample_frames[i]); ++j) {
SCOPED_TRACE("frame " + base::SizeTToString(j));
const CallStackProfile::Entry& entry = proto_sample.entry().Get(j);
ASSERT_TRUE(entry.has_address());
const char* instruction_pointer = reinterpret_cast<const char*>(
sample_frames[i][j].instruction_pointer);
const char* module_base_address = reinterpret_cast<const char*>(
modules[sample_frames[i][j].module_index].base_address);
EXPECT_EQ(static_cast<uint64>(instruction_pointer - module_base_address),
entry.address());
ASSERT_TRUE(entry.has_module_id_index());
EXPECT_EQ(sample_frames[i][j].module_index,
static_cast<size_t>(entry.module_id_index()));
}
}
}
// Checks that only contiguous duplicate samples are collapsed with
// preserve_sample_ordering = true.
TEST_F(CallStackProfileMetricsProviderTest, RepeatedStacksOrdered) {
const uintptr_t module_base_address = 0x1000;
const Module modules[] = {
Module(
module_base_address,
"ABCD",
#if defined(OS_WIN)
base::FilePath(L"c:\\some\\path\\to\\chrome.exe")
#else
base::FilePath("/some/path/to/chrome")
#endif
)
};
// Duplicate samples in slots 0, 2, and 3.
const Frame sample_frames[][1] = {
{ Frame(module_base_address + 0x10, 0), },
{ Frame(module_base_address + 0x20, 0), },
{ Frame(module_base_address + 0x10, 0), },
{ Frame(module_base_address + 0x10, 0) }
};
Profile profile;
profile.modules.insert(profile.modules.end(), &modules[0],
&modules[0] + arraysize(modules));
for (size_t i = 0; i < arraysize(sample_frames); ++i) {
profile.samples.push_back(Sample());
Sample& sample = profile.samples.back();
sample.insert(sample.end(), &sample_frames[i][0],
&sample_frames[i][0] + arraysize(sample_frames[i]));
}
profile.profile_duration = base::TimeDelta::FromMilliseconds(100);
profile.sampling_period = base::TimeDelta::FromMilliseconds(10);
CallStackProfileMetricsProvider provider;
provider.OnRecordingEnabled();
AppendProfiles(Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, true),
std::vector<Profile>(1, profile));
ChromeUserMetricsExtension uma_proto;
provider.ProvideGeneralMetrics(&uma_proto);
ASSERT_EQ(1, uma_proto.sampled_profile().size());
const SampledProfile& sampled_profile = uma_proto.sampled_profile().Get(0);
ASSERT_TRUE(sampled_profile.has_call_stack_profile());
const CallStackProfile& call_stack_profile =
sampled_profile.call_stack_profile();
ASSERT_EQ(3, call_stack_profile.sample().size());
for (int i = 0; i < 3; ++i) {
SCOPED_TRACE("sample " + base::IntToString(i));
const CallStackProfile::Sample& proto_sample =
call_stack_profile.sample().Get(i);
ASSERT_EQ(static_cast<int>(arraysize(sample_frames[i])),
proto_sample.entry().size());
ASSERT_TRUE(proto_sample.has_count());
EXPECT_EQ(i == 2 ? 2u : 1u, proto_sample.count());
for (size_t j = 0; j < arraysize(sample_frames[i]); ++j) {
SCOPED_TRACE("frame " + base::SizeTToString(j));
const CallStackProfile::Entry& entry = proto_sample.entry().Get(j);
ASSERT_TRUE(entry.has_address());
const char* instruction_pointer = reinterpret_cast<const char*>(
sample_frames[i][j].instruction_pointer);
const char* module_base_address = reinterpret_cast<const char*>(
modules[sample_frames[i][j].module_index].base_address);
EXPECT_EQ(static_cast<uint64>(instruction_pointer - module_base_address),
entry.address());
ASSERT_TRUE(entry.has_module_id_index());
EXPECT_EQ(sample_frames[i][j].module_index,
static_cast<size_t>(entry.module_id_index()));
}
}
}
// Checks that unknown modules produce an empty Entry.
TEST_F(CallStackProfileMetricsProviderTest, UnknownModule) {
const Frame frame(0x1000, Frame::kUnknownModuleIndex);
Profile profile;
profile.samples.push_back(Sample(1, frame));
profile.profile_duration = base::TimeDelta::FromMilliseconds(100);
profile.sampling_period = base::TimeDelta::FromMilliseconds(10);
CallStackProfileMetricsProvider provider;
provider.OnRecordingEnabled();
AppendProfiles(
Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false),
std::vector<Profile>(1, profile));
ChromeUserMetricsExtension uma_proto;
provider.ProvideGeneralMetrics(&uma_proto);
ASSERT_EQ(1, uma_proto.sampled_profile().size());
const SampledProfile& sampled_profile = uma_proto.sampled_profile().Get(0);
ASSERT_TRUE(sampled_profile.has_call_stack_profile());
const CallStackProfile& call_stack_profile =
sampled_profile.call_stack_profile();
ASSERT_EQ(1, call_stack_profile.sample().size());
const CallStackProfile::Sample& proto_sample =
call_stack_profile.sample().Get(0);
ASSERT_EQ(1, proto_sample.entry().size());
ASSERT_TRUE(proto_sample.has_count());
EXPECT_EQ(1u, proto_sample.count());
const CallStackProfile::Entry& entry = proto_sample.entry().Get(0);
EXPECT_FALSE(entry.has_address());
EXPECT_FALSE(entry.has_module_id_index());
}
// Checks that pending profiles are only passed back to ProvideGeneralMetrics
// once.
TEST_F(CallStackProfileMetricsProviderTest, ProfilesProvidedOnlyOnce) {
CallStackProfileMetricsProvider provider;
for (int i = 0; i < 2; ++i) {
Profile profile;
profile.samples.push_back(
Sample(1, Frame(0x1000, Frame::kUnknownModuleIndex)));
profile.profile_duration = base::TimeDelta::FromMilliseconds(100);
// Use the sampling period to distinguish the two profiles.
profile.sampling_period = base::TimeDelta::FromMilliseconds(i);
provider.OnRecordingEnabled();
AppendProfiles(
Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false),
std::vector<Profile>(1, profile));
ChromeUserMetricsExtension uma_proto;
provider.ProvideGeneralMetrics(&uma_proto);
ASSERT_EQ(1, uma_proto.sampled_profile().size());
const SampledProfile& sampled_profile = uma_proto.sampled_profile().Get(0);
ASSERT_TRUE(sampled_profile.has_call_stack_profile());
const CallStackProfile& call_stack_profile =
sampled_profile.call_stack_profile();
ASSERT_TRUE(call_stack_profile.has_sampling_period_ms());
EXPECT_EQ(i, call_stack_profile.sampling_period_ms());
}
}
// Checks that pending profiles are provided to ProvideGeneralMetrics
// when collected before CallStackProfileMetricsProvider is instantiated.
TEST_F(CallStackProfileMetricsProviderTest,
ProfilesProvidedWhenCollectedBeforeInstantiation) {
Profile profile;
profile.samples.push_back(
Sample(1, Frame(0x1000, Frame::kUnknownModuleIndex)));
profile.profile_duration = base::TimeDelta::FromMilliseconds(100);
profile.sampling_period = base::TimeDelta::FromMilliseconds(10);
AppendProfiles(
Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false),
std::vector<Profile>(1, profile));
CallStackProfileMetricsProvider provider;
provider.OnRecordingEnabled();
ChromeUserMetricsExtension uma_proto;
provider.ProvideGeneralMetrics(&uma_proto);
EXPECT_EQ(1, uma_proto.sampled_profile_size());
}
// Checks that pending profiles are not provided to ProvideGeneralMetrics
// while recording is disabled.
TEST_F(CallStackProfileMetricsProviderTest, ProfilesNotProvidedWhileDisabled) {
Profile profile;
profile.samples.push_back(
Sample(1, Frame(0x1000, Frame::kUnknownModuleIndex)));
profile.profile_duration = base::TimeDelta::FromMilliseconds(100);
profile.sampling_period = base::TimeDelta::FromMilliseconds(10);
CallStackProfileMetricsProvider provider;
provider.OnRecordingDisabled();
AppendProfiles(
Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false),
std::vector<Profile>(1, profile));
ChromeUserMetricsExtension uma_proto;
provider.ProvideGeneralMetrics(&uma_proto);
EXPECT_EQ(0, uma_proto.sampled_profile_size());
}
// Checks that pending profiles are not provided to ProvideGeneralMetrics
// if recording is disabled while profiling.
TEST_F(CallStackProfileMetricsProviderTest,
ProfilesNotProvidedAfterChangeToDisabled) {
Profile profile;
profile.samples.push_back(
Sample(1, Frame(0x1000, Frame::kUnknownModuleIndex)));
profile.profile_duration = base::TimeDelta::FromMilliseconds(100);
profile.sampling_period = base::TimeDelta::FromMilliseconds(10);
CallStackProfileMetricsProvider provider;
provider.OnRecordingEnabled();
base::StackSamplingProfiler::CompletedCallback callback =
CallStackProfileMetricsProvider::GetProfilerCallback(
Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false));
provider.OnRecordingDisabled();
callback.Run(std::vector<Profile>(1, profile));
ChromeUserMetricsExtension uma_proto;
provider.ProvideGeneralMetrics(&uma_proto);
EXPECT_EQ(0, uma_proto.sampled_profile_size());
}
// Checks that pending profiles are not provided to ProvideGeneralMetrics if
// recording is enabled, but then disabled and reenabled while profiling.
TEST_F(CallStackProfileMetricsProviderTest,
ProfilesNotProvidedAfterChangeToDisabledThenEnabled) {
Profile profile;
profile.samples.push_back(
Sample(1, Frame(0x1000, Frame::kUnknownModuleIndex)));
profile.profile_duration = base::TimeDelta::FromMilliseconds(100);
profile.sampling_period = base::TimeDelta::FromMilliseconds(10);
CallStackProfileMetricsProvider provider;
provider.OnRecordingEnabled();
base::StackSamplingProfiler::CompletedCallback callback =
CallStackProfileMetricsProvider::GetProfilerCallback(
Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false));
provider.OnRecordingDisabled();
provider.OnRecordingEnabled();
callback.Run(std::vector<Profile>(1, profile));
ChromeUserMetricsExtension uma_proto;
provider.ProvideGeneralMetrics(&uma_proto);
EXPECT_EQ(0, uma_proto.sampled_profile_size());
}
// Checks that pending profiles are not provided to ProvideGeneralMetrics
// if recording is disabled, but then enabled while profiling.
TEST_F(CallStackProfileMetricsProviderTest,
ProfilesNotProvidedAfterChangeFromDisabled) {
Profile profile;
profile.samples.push_back(
Sample(1, Frame(0x1000, Frame::kUnknownModuleIndex)));
profile.profile_duration = base::TimeDelta::FromMilliseconds(100);
profile.sampling_period = base::TimeDelta::FromMilliseconds(10);
CallStackProfileMetricsProvider provider;
provider.OnRecordingDisabled();
base::StackSamplingProfiler::CompletedCallback callback =
CallStackProfileMetricsProvider::GetProfilerCallback(
Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false));
provider.OnRecordingEnabled();
callback.Run(std::vector<Profile>(1, profile));
ChromeUserMetricsExtension uma_proto;
provider.ProvideGeneralMetrics(&uma_proto);
EXPECT_EQ(0, uma_proto.sampled_profile_size());
}
} // namespace metrics