Commit d19f5200 authored by wittman's avatar wittman Committed by Commit bot

Metrics provider for statistical stack profiler

Provides a metrics provider that uploads statistical stack profiling
state via UMA.

This CL builds on top of the statistical profiler implementation in
https://crrev.com/1016563004, which is under review.

BUG=464929

Review URL: https://codereview.chromium.org/981143006

Cr-Commit-Position: refs/heads/master@{#321928}
parent 45816be8
...@@ -65,6 +65,10 @@ void PendingProfiles::GetProfiles( ...@@ -65,6 +65,10 @@ void PendingProfiles::GetProfiles(
} // namespace } // namespace
StackSamplingProfiler::Module::Module() : base_address(nullptr) {} StackSamplingProfiler::Module::Module() : base_address(nullptr) {}
StackSamplingProfiler::Module::Module(const void* base_address,
const std::string& id,
const FilePath& filename)
: base_address(base_address), id(id), filename(filename) {}
StackSamplingProfiler::Module::~Module() {} StackSamplingProfiler::Module::~Module() {}
...@@ -72,6 +76,11 @@ StackSamplingProfiler::Frame::Frame() ...@@ -72,6 +76,11 @@ StackSamplingProfiler::Frame::Frame()
: instruction_pointer(nullptr), : instruction_pointer(nullptr),
module_index(-1) {} module_index(-1) {}
StackSamplingProfiler::Frame::Frame(const void* instruction_pointer,
int module_index)
: instruction_pointer(instruction_pointer),
module_index(module_index) {}
StackSamplingProfiler::Frame::~Frame() {} StackSamplingProfiler::Frame::~Frame() {}
StackSamplingProfiler::Profile::Profile() : preserve_sample_ordering(false) {} StackSamplingProfiler::Profile::Profile() : preserve_sample_ordering(false) {}
......
...@@ -53,6 +53,8 @@ class BASE_EXPORT StackSamplingProfiler { ...@@ -53,6 +53,8 @@ class BASE_EXPORT StackSamplingProfiler {
// Module represents the module (DLL or exe) corresponding to a stack frame. // Module represents the module (DLL or exe) corresponding to a stack frame.
struct BASE_EXPORT Module { struct BASE_EXPORT Module {
Module(); Module();
Module(const void* base_address, const std::string& id,
const FilePath& filename);
~Module(); ~Module();
// Points to the base address of the module. // Points to the base address of the module.
...@@ -72,6 +74,7 @@ class BASE_EXPORT StackSamplingProfiler { ...@@ -72,6 +74,7 @@ class BASE_EXPORT StackSamplingProfiler {
// Frame represents an individual sampled stack frame with module information. // Frame represents an individual sampled stack frame with module information.
struct BASE_EXPORT Frame { struct BASE_EXPORT Frame {
Frame(); Frame();
Frame(const void* instruction_pointer, int module_index);
~Frame(); ~Frame();
// The sampled instruction pointer within the function. // The sampled instruction pointer within the function.
......
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
#include "chrome/common/crash_keys.h" #include "chrome/common/crash_keys.h"
#include "chrome/common/pref_names.h" #include "chrome/common/pref_names.h"
#include "chrome/common/render_messages.h" #include "chrome/common/render_messages.h"
#include "components/metrics/call_stack_profile_metrics_provider.h"
#include "components/metrics/gpu/gpu_metrics_provider.h" #include "components/metrics/gpu/gpu_metrics_provider.h"
#include "components/metrics/metrics_service.h" #include "components/metrics/metrics_service.h"
#include "components/metrics/net/net_metrics_log_uploader.h" #include "components/metrics/net/net_metrics_log_uploader.h"
...@@ -334,6 +335,10 @@ void ChromeMetricsServiceClient::Initialize() { ...@@ -334,6 +335,10 @@ void ChromeMetricsServiceClient::Initialize() {
metrics_service_->RegisterMetricsProvider( metrics_service_->RegisterMetricsProvider(
scoped_ptr<metrics::MetricsProvider>(profiler_metrics_provider_)); scoped_ptr<metrics::MetricsProvider>(profiler_metrics_provider_));
metrics_service_->RegisterMetricsProvider(
scoped_ptr<metrics::MetricsProvider>(
new metrics::CallStackProfileMetricsProvider));
#if defined(OS_ANDROID) #if defined(OS_ANDROID)
metrics_service_->RegisterMetricsProvider( metrics_service_->RegisterMetricsProvider(
scoped_ptr<metrics::MetricsProvider>( scoped_ptr<metrics::MetricsProvider>(
......
...@@ -243,6 +243,7 @@ ...@@ -243,6 +243,7 @@
'login/screens/screen_context_unittest.cc', 'login/screens/screen_context_unittest.cc',
], ],
'metrics_unittest_sources': [ 'metrics_unittest_sources': [
'metrics/call_stack_profile_metrics_provider_unittest.cc',
'metrics/compression_utils_unittest.cc', 'metrics/compression_utils_unittest.cc',
'metrics/daily_event_unittest.cc', 'metrics/daily_event_unittest.cc',
'metrics/gpu/gpu_metrics_provider_unittest.cc', 'metrics/gpu/gpu_metrics_provider_unittest.cc',
......
...@@ -23,6 +23,8 @@ ...@@ -23,6 +23,8 @@
'component_metrics_proto', 'component_metrics_proto',
], ],
'sources': [ 'sources': [
'metrics/call_stack_profile_metrics_provider.cc',
'metrics/call_stack_profile_metrics_provider.h',
'metrics/clean_exit_beacon.cc', 'metrics/clean_exit_beacon.cc',
'metrics/clean_exit_beacon.h', 'metrics/clean_exit_beacon.h',
'metrics/client_info.cc', 'metrics/client_info.cc',
...@@ -158,6 +160,7 @@ ...@@ -158,6 +160,7 @@
'target_name': 'component_metrics_proto', 'target_name': 'component_metrics_proto',
'type': 'static_library', 'type': 'static_library',
'sources': [ 'sources': [
'metrics/proto/call_stack_profile.proto',
'metrics/proto/cast_logs.proto', 'metrics/proto/cast_logs.proto',
'metrics/proto/chrome_user_metrics_extension.proto', 'metrics/proto/chrome_user_metrics_extension.proto',
'metrics/proto/histogram_event.proto', 'metrics/proto/histogram_event.proto',
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
# GYP version: components/metrics.gypi:metrics # GYP version: components/metrics.gypi:metrics
source_set("metrics") { source_set("metrics") {
sources = [ sources = [
"call_stack_profile_metrics_provider.cc",
"call_stack_profile_metrics_provider.h",
"clean_exit_beacon.cc", "clean_exit_beacon.cc",
"clean_exit_beacon.h", "clean_exit_beacon.h",
"client_info.cc", "client_info.cc",
......
// 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 <cstring>
#include <map>
#include <utility>
#include "base/logging.h"
#include "base/macros.h"
#include "base/profiler/stack_sampling_profiler.h"
#include "components/metrics/metrics_hashes.h"
#include "components/metrics/proto/chrome_user_metrics_extension.pb.h"
using base::StackSamplingProfiler;
namespace metrics {
namespace {
// The protobuf expects the MD5 checksum prefix of the module name.
uint64 HashModuleFilename(const base::FilePath& filename) {
const base::FilePath::StringType basename = filename.BaseName().value();
// Copy the bytes in basename into a string buffer.
size_t basename_length_in_bytes =
basename.size() * sizeof(base::FilePath::CharType);
std::string name_bytes(basename_length_in_bytes, '\0');
memcpy(&name_bytes[0], &basename[0], basename_length_in_bytes);
return HashMetricName(name_bytes);
}
// Transcode |sample| into |proto_sample|, using base addresses in |modules| to
// compute module instruction pointer offsets.
void CopySampleToProto(
const StackSamplingProfiler::Sample& sample,
const std::vector<StackSamplingProfiler::Module>& modules,
CallStackProfile::Sample* proto_sample) {
for (const StackSamplingProfiler::Frame& frame : sample) {
CallStackProfile::Entry* entry = proto_sample->add_entry();
// A frame may not have a valid module. If so, we can't compute the
// instruction pointer offset, and we don't want to send bare pointers, so
// leave call_stack_entry empty.
if (frame.module_index < 0)
continue;
int64 module_offset =
reinterpret_cast<const char*>(frame.instruction_pointer) -
reinterpret_cast<const char*>(modules[frame.module_index].base_address);
DCHECK_GE(module_offset, 0);
entry->set_address(static_cast<uint64>(module_offset));
entry->set_module_id_index(frame.module_index);
}
}
// Transcode |profile| into |proto_profile|.
void CopyProfileToProto(
const StackSamplingProfiler::Profile& profile,
CallStackProfile* proto_profile) {
if (profile.samples.empty())
return;
if (profile.preserve_sample_ordering) {
// Collapse only consecutive repeated samples together.
CallStackProfile::Sample* current_sample_proto = nullptr;
for (auto it = profile.samples.begin(); it != profile.samples.end(); ++it) {
if (!current_sample_proto || *it != *(it - 1)) {
current_sample_proto = proto_profile->add_sample();
CopySampleToProto(*it, profile.modules, current_sample_proto);
current_sample_proto->set_count(1);
} else {
current_sample_proto->set_count(current_sample_proto->count() + 1);
}
}
} else {
// Collapse all repeated samples together.
std::map<StackSamplingProfiler::Sample, int> sample_index;
for (auto it = profile.samples.begin(); it != profile.samples.end(); ++it) {
auto location = sample_index.find(*it);
if (location == sample_index.end()) {
CallStackProfile::Sample* sample_proto = proto_profile->add_sample();
CopySampleToProto(*it, profile.modules, sample_proto);
sample_proto->set_count(1);
sample_index.insert(
std::make_pair(
*it, static_cast<int>(proto_profile->sample().size()) - 1));
} else {
CallStackProfile::Sample* sample_proto =
proto_profile->mutable_sample()->Mutable(location->second);
sample_proto->set_count(sample_proto->count() + 1);
}
}
}
for (const StackSamplingProfiler::Module& module : profile.modules) {
CallStackProfile::ModuleIdentifier* module_id =
proto_profile->add_module_id();
module_id->set_build_id(module.id);
module_id->set_name_md5_prefix(HashModuleFilename(module.filename));
}
proto_profile->set_profile_duration_ms(
profile.profile_duration.InMilliseconds());
proto_profile->set_sampling_period_ms(
profile.sampling_period.InMilliseconds());
}
} // namespace
CallStackProfileMetricsProvider::CallStackProfileMetricsProvider() {}
CallStackProfileMetricsProvider::~CallStackProfileMetricsProvider() {}
void CallStackProfileMetricsProvider::ProvideGeneralMetrics(
ChromeUserMetricsExtension* uma_proto) {
std::vector<StackSamplingProfiler::Profile> profiles;
if (!source_profiles_for_test_.empty())
profiles.swap(source_profiles_for_test_);
else
StackSamplingProfiler::GetPendingProfiles(&profiles);
for (const StackSamplingProfiler::Profile& profile : profiles) {
CallStackProfile* call_stack_profile =
uma_proto->add_sampled_profile()->mutable_call_stack_profile();
CopyProfileToProto(profile, call_stack_profile);
}
}
void CallStackProfileMetricsProvider::SetSourceProfilesForTesting(
const std::vector<StackSamplingProfiler::Profile>& profiles) {
source_profiles_for_test_ = profiles;
}
} // namespace metrics
// 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.
#ifndef COMPONENTS_METRICS_CALL_STACK_PROFILE_METRICS_PROVIDER_H_
#define COMPONENTS_METRICS_CALL_STACK_PROFILE_METRICS_PROVIDER_H_
#include <vector>
#include "base/profiler/stack_sampling_profiler.h"
#include "components/metrics/metrics_provider.h"
namespace metrics {
class ChromeUserMetricsExtension;
// Performs metrics logging for the stack sampling profiler.
class CallStackProfileMetricsProvider : public MetricsProvider {
public:
CallStackProfileMetricsProvider();
~CallStackProfileMetricsProvider() override;
// MetricsProvider:
void ProvideGeneralMetrics(ChromeUserMetricsExtension* uma_proto) override;
// Uses |profiles| as the source data for the next invocation of
// ProvideGeneralMetrics, rather than sourcing them from the
// StackSamplingProfiler.
void SetSourceProfilesForTesting(
const std::vector<base::StackSamplingProfiler::Profile>& profiles);
private:
std::vector<base::StackSamplingProfiler::Profile> source_profiles_for_test_;
DISALLOW_COPY_AND_ASSIGN(CallStackProfileMetricsProvider);
};
} // namespace metrics
#endif // COMPONENTS_METRICS_CALL_STACK_PROFILE_METRICS_PROVIDER_H_
// 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/profiler/stack_sampling_profiler.h"
#include "base/strings/string_number_conversions.h"
#include "components/metrics/proto/chrome_user_metrics_extension.pb.h"
#include "testing/gtest/include/gtest/gtest.h"
using base::StackSamplingProfiler;
using Frame = StackSamplingProfiler::Frame;
using Module = StackSamplingProfiler::Module;
using Profile = StackSamplingProfiler::Profile;
using Sample = StackSamplingProfiler::Sample;
namespace metrics {
// Checks that all properties from multiple profiles are filled as expected.
TEST(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(
reinterpret_cast<const void*>(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(
reinterpret_cast<const void*>(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(
reinterpret_cast<const void*>(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.
reinterpret_cast<const void*>(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(reinterpret_cast<const void*>(module1_base_address + 0x10), 0),
Frame(reinterpret_cast<const void*>(module2_base_address + 0x20), 1),
Frame(reinterpret_cast<const void*>(module1_base_address + 0x30), 0)
},
{
Frame(reinterpret_cast<const void*>(module2_base_address + 0x10), 1),
Frame(reinterpret_cast<const void*>(module1_base_address + 0x20), 0),
Frame(reinterpret_cast<const void*>(module2_base_address + 0x30), 1)
}
},
{
{
Frame(reinterpret_cast<const void*>(module3_base_address + 0x10), 0),
Frame(reinterpret_cast<const void*>(module1_base_address + 0x20), 1),
Frame(reinterpret_cast<const void*>(module3_base_address + 0x30), 0)
},
{
Frame(reinterpret_cast<const void*>(module1_base_address + 0x10), 1),
Frame(reinterpret_cast<const void*>(module3_base_address + 0x20), 0),
Frame(reinterpret_cast<const void*>(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];
profile.preserve_sample_ordering = false;
profiles.push_back(profile);
}
CallStackProfileMetricsProvider provider;
provider.SetSourceProfilesForTesting(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::IntToString(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::IntToString(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::IntToString(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,
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::IntToString(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());
}
}
// Checks that all duplicate samples are collapsed with
// preserve_sample_ordering = false.
TEST(CallStackProfileMetricsProviderTest, RepeatedStacksUnordered) {
const uintptr_t module_base_address = 0x1000;
const Module modules[] = {
Module(
reinterpret_cast<const void*>(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(reinterpret_cast<const void*>(module_base_address + 0x10), 0), },
{ Frame(reinterpret_cast<const void*>(module_base_address + 0x20), 0), },
{ Frame(reinterpret_cast<const void*>(module_base_address + 0x10), 0), },
{ Frame(reinterpret_cast<const void*>(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);
profile.preserve_sample_ordering = false;
CallStackProfileMetricsProvider provider;
provider.SetSourceProfilesForTesting(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::IntToString(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, entry.module_id_index());
}
}
}
// Checks that only contiguous duplicate samples are collapsed with
// preserve_sample_ordering = true.
TEST(CallStackProfileMetricsProviderTest, RepeatedStacksOrdered) {
const uintptr_t module_base_address = 0x1000;
const Module modules[] = {
Module(
reinterpret_cast<const void*>(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(reinterpret_cast<const void*>(module_base_address + 0x10), 0), },
{ Frame(reinterpret_cast<const void*>(module_base_address + 0x20), 0), },
{ Frame(reinterpret_cast<const void*>(module_base_address + 0x10), 0), },
{ Frame(reinterpret_cast<const void*>(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);
profile.preserve_sample_ordering = true;
CallStackProfileMetricsProvider provider;
provider.SetSourceProfilesForTesting(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::IntToString(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, entry.module_id_index());
}
}
}
// Checks that unknown modules produce an empty Entry.
TEST(CallStackProfileMetricsProviderTest, UnknownModule) {
// -1 indicates an unknown module.
const Frame frame(reinterpret_cast<const void*>(0x1000), -1);
Profile profile;
profile.samples.push_back(Sample(1, frame));
profile.profile_duration = base::TimeDelta::FromMilliseconds(100);
profile.sampling_period = base::TimeDelta::FromMilliseconds(10);
profile.preserve_sample_ordering = false;
CallStackProfileMetricsProvider provider;
provider.SetSourceProfilesForTesting(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());
}
} // namespace metrics
...@@ -7,6 +7,7 @@ import("//third_party/protobuf/proto_library.gni") ...@@ -7,6 +7,7 @@ import("//third_party/protobuf/proto_library.gni")
# GYP version: components/ # GYP version: components/
proto_library("proto") { proto_library("proto") {
sources = [ sources = [
"call_stack_profile.proto",
"cast_logs.proto", "cast_logs.proto",
"chrome_user_metrics_extension.proto", "chrome_user_metrics_extension.proto",
"histogram_event.proto", "histogram_event.proto",
......
// 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.
// Call stack sample data for a given profiling session.
syntax = "proto2";
option optimize_for = LITE_RUNTIME;
package metrics;
// Next tag: 5
message CallStackProfile {
// Describes an entry in the callstack.
message Entry {
// Instruction pointer subtracted by module base.
optional uint64 address = 1;
// Index to the module identifier in |module_ids| of CallStackProfile.
optional int32 module_id_index = 2;
}
// A sample consisting of one or more callstacks with the same stack frames
// and instruction pointers.
message Sample {
// The callstack. Sample.entries[0] represents the call on the top of the
// stack.
repeated Entry entry = 1;
// Number of times this stack signature occurs.
optional int64 count = 2;
}
// Uniquely identifies a module.
message ModuleIdentifier {
// A hash that uniquely identifies a particular program version with high
// probability. This is parsed from headers of the loaded module.
// For binaries generated by GNU tools:
// Contents of the .note.gnu.build-id field.
// On Windows:
// GUID + AGE in the debug image headers of a module.
optional bytes build_id = 1;
// MD5Sum Prefix of the module name. This is the same hashing scheme as used
// to hash UMA histogram names.
optional fixed64 name_md5_prefix = 2;
}
// The callstack and counts.
repeated Sample sample = 1;
// List of module ids found in this sample.
repeated ModuleIdentifier module_id = 2;
// Duration of this profile.
optional int32 profile_duration_ms = 3;
// Time between samples.
optional int32 sampling_period_ms = 4;
}
...@@ -8,12 +8,13 @@ option optimize_for = LITE_RUNTIME; ...@@ -8,12 +8,13 @@ option optimize_for = LITE_RUNTIME;
package metrics; package metrics;
import "call_stack_profile.proto";
import "perf_data.proto"; import "perf_data.proto";
// Protocol buffer for collected sample-based profiling data. // Protocol buffer for collected sample-based profiling data.
// Contains the parameters and data from a single profile collection event. // Contains the parameters and data from a single profile collection event.
// Next tag: 9 // Next tag: 10
message SampledProfile { message SampledProfile {
// Indicates the event that triggered this collection. // Indicates the event that triggered this collection.
enum TriggerEvent { enum TriggerEvent {
...@@ -59,6 +60,9 @@ message SampledProfile { ...@@ -59,6 +60,9 @@ message SampledProfile {
// collected. Only set when |trigger_event| is RESTORE_SESSION. // collected. Only set when |trigger_event| is RESTORE_SESSION.
optional int64 ms_after_restore = 8; optional int64 ms_after_restore = 8;
// The actual perf data that was collected. // Sampled profile data collected from Linux perf tool.
optional PerfDataProto perf_data = 4; optional PerfDataProto perf_data = 4;
// Sampled profile data collected by periodic sampling of call stacks.
optional CallStackProfile call_stack_profile = 9;
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment