Commit b2e858e5 authored by Xi Cheng's avatar Xi Cheng Committed by Commit Bot

Introduce new CallStackProfileBuilder using the new StackSample encoding

We deliberately leave out the process phase metadata information in the
StackSample encoding in this CL. Implementing that metadata support will
need to be done differently to make it extensible to general metadata.
We will implement it using follow-up CLs.

The old CallStackProfileBuilder using the legacy Sample encoding is
renamed to LegacyCallStackProfileBuilder. Related files are renamed
accordingly too.

Note that the old CallStackProfileBuilder is still used in production.

Bug: 851163, 804942
Change-Id: I235bd8cc839de4e663f1d4bd49aa24485649692a
Reviewed-on: https://chromium-review.googlesource.com/1205896Reviewed-by: default avatarFrançois Doray <fdoray@chromium.org>
Reviewed-by: default avatarIlya Sherman <isherman@chromium.org>
Reviewed-by: default avatarSiddhartha S <ssid@chromium.org>
Reviewed-by: default avatarLei Zhang <thestig@chromium.org>
Reviewed-by: default avatarAlexei Filippov <alph@chromium.org>
Reviewed-by: default avatarMike Wittman <wittman@chromium.org>
Commit-Queue: Xi Cheng <chengx@chromium.org>
Cr-Commit-Position: refs/heads/master@{#590465}
parent 2d01c9a0
...@@ -612,6 +612,8 @@ void StackSamplingProfiler::TestAPI::PerformSamplingThreadIdleShutdown( ...@@ -612,6 +612,8 @@ void StackSamplingProfiler::TestAPI::PerformSamplingThreadIdleShutdown(
SamplingThread::TestAPI::ShutdownAssumingIdle(simulate_intervening_start); SamplingThread::TestAPI::ShutdownAssumingIdle(simulate_intervening_start);
} }
void StackSamplingProfiler::ProfileBuilder::RecordAnnotations() {}
StackSamplingProfiler::StackSamplingProfiler( StackSamplingProfiler::StackSamplingProfiler(
const SamplingParams& params, const SamplingParams& params,
std::unique_ptr<ProfileBuilder> profile_builder, std::unique_ptr<ProfileBuilder> profile_builder,
......
...@@ -124,7 +124,7 @@ class BASE_EXPORT StackSamplingProfiler { ...@@ -124,7 +124,7 @@ class BASE_EXPORT StackSamplingProfiler {
// a mutex, including allocating memory (which includes LOG messages) // a mutex, including allocating memory (which includes LOG messages)
// because that mutex could be held by a stopped thread, thus resulting in // because that mutex could be held by a stopped thread, thus resulting in
// deadlock. // deadlock.
virtual void RecordAnnotations() = 0; virtual void RecordAnnotations();
// Records a new set of frames. Invoked when sampling a sample completes. // Records a new set of frames. Invoked when sampling a sample completes.
virtual void OnSampleCompleted(std::vector<Frame> frames) = 0; virtual void OnSampleCompleted(std::vector<Frame> frames) = 0;
......
...@@ -135,10 +135,10 @@ ...@@ -135,10 +135,10 @@
#include "components/language/core/browser/pref_names.h" #include "components/language/core/browser/pref_names.h"
#include "components/language/core/common/language_experiments.h" #include "components/language/core/common/language_experiments.h"
#include "components/language_usage_metrics/language_usage_metrics.h" #include "components/language_usage_metrics/language_usage_metrics.h"
#include "components/metrics/call_stack_profile_builder.h"
#include "components/metrics/call_stack_profile_metrics_provider.h" #include "components/metrics/call_stack_profile_metrics_provider.h"
#include "components/metrics/call_stack_profile_params.h" #include "components/metrics/call_stack_profile_params.h"
#include "components/metrics/expired_histogram_util.h" #include "components/metrics/expired_histogram_util.h"
#include "components/metrics/legacy_call_stack_profile_builder.h"
#include "components/metrics/metrics_reporting_default_state.h" #include "components/metrics/metrics_reporting_default_state.h"
#include "components/metrics/metrics_service.h" #include "components/metrics/metrics_service.h"
#include "components/metrics_services_manager/metrics_services_manager.h" #include "components/metrics_services_manager/metrics_services_manager.h"
...@@ -2120,8 +2120,8 @@ void ChromeBrowserMainParts::PostMainMessageLoopRun() { ...@@ -2120,8 +2120,8 @@ void ChromeBrowserMainParts::PostMainMessageLoopRun() {
} }
void ChromeBrowserMainParts::PreShutdown() { void ChromeBrowserMainParts::PreShutdown() {
metrics::CallStackProfileBuilder::SetProcessMilestone( metrics::LegacyCallStackProfileBuilder::SetProcessMilestone(
metrics::CallStackProfileBuilder::SHUTDOWN_START); metrics::LegacyCallStackProfileBuilder::SHUTDOWN_START);
} }
void ChromeBrowserMainParts::PostDestroyThreads() { void ChromeBrowserMainParts::PostDestroyThreads() {
......
...@@ -22,7 +22,7 @@ include_rules = [ ...@@ -22,7 +22,7 @@ include_rules = [
"+components/metrics/call_stack_profile_metrics_provider.h", "+components/metrics/call_stack_profile_metrics_provider.h",
"+components/metrics/call_stack_profile_params.h", "+components/metrics/call_stack_profile_params.h",
"+components/metrics/child_call_stack_profile_collector.h", "+components/metrics/child_call_stack_profile_collector.h",
"+components/metrics/call_stack_profile_builder.h", "+components/metrics/legacy_call_stack_profile_builder.h",
"+components/metrics/client_info.h", "+components/metrics/client_info.h",
"+components/metrics/metrics_pref_names.h", "+components/metrics/metrics_pref_names.h",
"+components/nacl/common", "+components/nacl/common",
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
#include "base/sampling_heap_profiler/sampling_heap_profiler.h" #include "base/sampling_heap_profiler/sampling_heap_profiler.h"
#include "base/strings/string_number_conversions.h" #include "base/strings/string_number_conversions.h"
#include "base/task/post_task.h" #include "base/task/post_task.h"
#include "components/metrics/call_stack_profile_builder.h" #include "components/metrics/legacy_call_stack_profile_builder.h"
#include "content/public/common/content_switches.h" #include "content/public/common/content_switches.h"
namespace { namespace {
...@@ -104,7 +104,7 @@ void HeapProfilerController::RetrieveAndSendSnapshot() { ...@@ -104,7 +104,7 @@ void HeapProfilerController::RetrieveAndSendSnapshot() {
metrics::CallStackProfileParams::BROWSER_PROCESS, metrics::CallStackProfileParams::BROWSER_PROCESS,
metrics::CallStackProfileParams::UNKNOWN_THREAD, metrics::CallStackProfileParams::UNKNOWN_THREAD,
metrics::CallStackProfileParams::PERIODIC_HEAP_COLLECTION); metrics::CallStackProfileParams::PERIODIC_HEAP_COLLECTION);
metrics::CallStackProfileBuilder profile_builder(params); metrics::LegacyCallStackProfileBuilder profile_builder(params);
for (const base::SamplingHeapProfiler::Sample& sample : samples) { for (const base::SamplingHeapProfiler::Sample& sample : samples) {
std::vector<base::StackSamplingProfiler::Frame> frames; std::vector<base::StackSamplingProfiler::Frame> frames;
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
#include "base/sampling_heap_profiler/poisson_allocation_sampler.h" #include "base/sampling_heap_profiler/poisson_allocation_sampler.h"
#include "base/test/bind_test_util.h" #include "base/test/bind_test_util.h"
#include "base/test/test_mock_time_task_runner.h" #include "base/test/test_mock_time_task_runner.h"
#include "components/metrics/call_stack_profile_builder.h" #include "components/metrics/legacy_call_stack_profile_builder.h"
#include "content/public/common/content_switches.h" #include "content/public/common/content_switches.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "third_party/metrics_proto/sampled_profile.pb.h" #include "third_party/metrics_proto/sampled_profile.pb.h"
...@@ -21,7 +21,7 @@ TEST(HeapProfilerControllerTest, ProfileCollectionsScheduler) { ...@@ -21,7 +21,7 @@ TEST(HeapProfilerControllerTest, ProfileCollectionsScheduler) {
command_line->AppendSwitchASCII(switches::kSamplingHeapProfiler, "1"); command_line->AppendSwitchASCII(switches::kSamplingHeapProfiler, "1");
int profiles_collected = 0; int profiles_collected = 0;
metrics::CallStackProfileBuilder::SetBrowserProcessReceiverCallback( metrics::LegacyCallStackProfileBuilder::SetBrowserProcessReceiverCallback(
base::BindLambdaForTesting( base::BindLambdaForTesting(
[&](base::TimeTicks time, metrics::SampledProfile profile) { [&](base::TimeTicks time, metrics::SampledProfile profile) {
EXPECT_EQ(metrics::SampledProfile::PERIODIC_HEAP_COLLECTION, EXPECT_EQ(metrics::SampledProfile::PERIODIC_HEAP_COLLECTION,
......
...@@ -21,8 +21,8 @@ ...@@ -21,8 +21,8 @@
#include "services/service_manager/embedder/switches.h" #include "services/service_manager/embedder/switches.h"
#include "services/service_manager/public/cpp/connector.h" #include "services/service_manager/public/cpp/connector.h"
using CallStackProfileBuilder = metrics::CallStackProfileBuilder;
using CallStackProfileParams = metrics::CallStackProfileParams; using CallStackProfileParams = metrics::CallStackProfileParams;
using LegacyCallStackProfileBuilder = metrics::LegacyCallStackProfileBuilder;
using StackSamplingProfiler = base::StackSamplingProfiler; using StackSamplingProfiler = base::StackSamplingProfiler;
namespace { namespace {
...@@ -150,7 +150,8 @@ void ThreadProfiler::StartOnChildThread(CallStackProfileParams::Thread thread) { ...@@ -150,7 +150,8 @@ void ThreadProfiler::StartOnChildThread(CallStackProfileParams::Thread thread) {
void ThreadProfiler::SetBrowserProcessReceiverCallback( void ThreadProfiler::SetBrowserProcessReceiverCallback(
const base::RepeatingCallback<void(base::TimeTicks, const base::RepeatingCallback<void(base::TimeTicks,
metrics::SampledProfile)>& callback) { metrics::SampledProfile)>& callback) {
metrics::CallStackProfileBuilder::SetBrowserProcessReceiverCallback(callback); metrics::LegacyCallStackProfileBuilder::SetBrowserProcessReceiverCallback(
callback);
} }
// static // static
...@@ -164,7 +165,7 @@ void ThreadProfiler::SetServiceManagerConnectorForChildProcess( ...@@ -164,7 +165,7 @@ void ThreadProfiler::SetServiceManagerConnectorForChildProcess(
metrics::mojom::CallStackProfileCollectorPtr browser_interface; metrics::mojom::CallStackProfileCollectorPtr browser_interface;
connector->BindInterface(content::mojom::kBrowserServiceName, connector->BindInterface(content::mojom::kBrowserServiceName,
&browser_interface); &browser_interface);
CallStackProfileBuilder::SetParentProfileCollectorForChildProcess( LegacyCallStackProfileBuilder::SetParentProfileCollectorForChildProcess(
std::move(browser_interface)); std::move(browser_interface));
} }
...@@ -197,9 +198,9 @@ ThreadProfiler::ThreadProfiler( ...@@ -197,9 +198,9 @@ ThreadProfiler::ThreadProfiler(
if (!StackSamplingConfiguration::Get()->IsProfilerEnabledForCurrentProcess()) if (!StackSamplingConfiguration::Get()->IsProfilerEnabledForCurrentProcess())
return; return;
auto profile_builder = std::make_unique<CallStackProfileBuilder>( auto profile_builder =
CallStackProfileParams(GetProcess(), thread, std::make_unique<LegacyCallStackProfileBuilder>(CallStackProfileParams(
CallStackProfileParams::PROCESS_STARTUP)); GetProcess(), thread, CallStackProfileParams::PROCESS_STARTUP));
startup_profiler_ = std::make_unique<StackSamplingProfiler>( startup_profiler_ = std::make_unique<StackSamplingProfiler>(
base::PlatformThread::CurrentId(), kSamplingParams, base::PlatformThread::CurrentId(), kSamplingParams,
...@@ -244,7 +245,7 @@ void ThreadProfiler::ScheduleNextPeriodicCollection() { ...@@ -244,7 +245,7 @@ void ThreadProfiler::ScheduleNextPeriodicCollection() {
void ThreadProfiler::StartPeriodicSamplingCollection() { void ThreadProfiler::StartPeriodicSamplingCollection() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// NB: Destroys the previous profiler as side effect. // NB: Destroys the previous profiler as side effect.
auto profile_builder = std::make_unique<CallStackProfileBuilder>( auto profile_builder = std::make_unique<LegacyCallStackProfileBuilder>(
periodic_profile_params_, periodic_profile_params_,
base::BindOnce(&ThreadProfiler::OnPeriodicCollectionCompleted, base::BindOnce(&ThreadProfiler::OnPeriodicCollectionCompleted,
owning_thread_task_runner_, weak_factory_.GetWeakPtr())); owning_thread_task_runner_, weak_factory_.GetWeakPtr()));
......
...@@ -14,8 +14,8 @@ ...@@ -14,8 +14,8 @@
#include "base/threading/thread.h" #include "base/threading/thread.h"
#include "base/threading/thread_checker.h" #include "base/threading/thread_checker.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "components/metrics/call_stack_profile_builder.h"
#include "components/metrics/call_stack_profile_params.h" #include "components/metrics/call_stack_profile_params.h"
#include "components/metrics/legacy_call_stack_profile_builder.h"
namespace service_manager { namespace service_manager {
class Connector; class Connector;
......
...@@ -276,10 +276,12 @@ source_set("child_call_stack_profile_builder") { ...@@ -276,10 +276,12 @@ source_set("child_call_stack_profile_builder") {
public = [ public = [
"call_stack_profile_builder.h", "call_stack_profile_builder.h",
"child_call_stack_profile_collector.h", "child_call_stack_profile_collector.h",
"legacy_call_stack_profile_builder.h",
] ]
sources = [ sources = [
"call_stack_profile_builder.cc", "call_stack_profile_builder.cc",
"child_call_stack_profile_collector.cc", "child_call_stack_profile_collector.cc",
"legacy_call_stack_profile_builder.cc",
] ]
public_deps = [ public_deps = [
":call_stack_profile_params", ":call_stack_profile_params",
...@@ -370,6 +372,7 @@ source_set("unit_tests") { ...@@ -370,6 +372,7 @@ source_set("unit_tests") {
"field_trials_provider_unittest.cc", "field_trials_provider_unittest.cc",
"file_metrics_provider_unittest.cc", "file_metrics_provider_unittest.cc",
"histogram_encoder_unittest.cc", "histogram_encoder_unittest.cc",
"legacy_call_stack_profile_builder_unittest.cc",
"machine_id_provider_win_unittest.cc", "machine_id_provider_win_unittest.cc",
"metrics_log_manager_unittest.cc", "metrics_log_manager_unittest.cc",
"metrics_log_store_unittest.cc", "metrics_log_store_unittest.cc",
......
...@@ -4,10 +4,10 @@ ...@@ -4,10 +4,10 @@
#include "components/metrics/call_stack_profile_builder.h" #include "components/metrics/call_stack_profile_builder.h"
#include <algorithm>
#include <string> #include <string>
#include <utility> #include <utility>
#include "base/atomicops.h"
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/lazy_instance.h" #include "base/lazy_instance.h"
#include "base/logging.h" #include "base/logging.h"
...@@ -32,51 +32,7 @@ GetBrowserProcessReceiverCallbackInstance() { ...@@ -32,51 +32,7 @@ GetBrowserProcessReceiverCallbackInstance() {
return *instance; return *instance;
} }
// Identifies an unknown module. // Convert |filename| to its MD5 hash.
const size_t kUnknownModuleIndex = static_cast<size_t>(-1);
// This global variables holds the current system state and is recorded with
// every captured sample, done on a separate thread which is why updates to
// this must be atomic. A PostTask to move the the updates to that thread
// would skew the timing and a lock could result in deadlock if the thread
// making a change was also being profiled and got stopped.
static base::subtle::Atomic32 g_process_milestones = 0;
void ChangeAtomicFlags(base::subtle::Atomic32* flags,
base::subtle::Atomic32 set,
base::subtle::Atomic32 clear) {
DCHECK(set != 0 || clear != 0);
DCHECK_EQ(0, set & clear);
base::subtle::Atomic32 bits = base::subtle::NoBarrier_Load(flags);
while (true) {
base::subtle::Atomic32 existing = base::subtle::NoBarrier_CompareAndSwap(
flags, bits, (bits | set) & ~clear);
if (existing == bits)
break;
bits = existing;
}
}
// Provide a mapping from the C++ "enum" definition of various process mile-
// stones to the equivalent protobuf "enum" definition. This table-lookup
// conversion allows for the implementation to evolve and still be compatible
// with the protobuf -- even if there are ever more than 32 defined proto
// values, though never more than 32 could be in-use in a given C++ version
// of the code.
const ProcessPhase kProtoPhases[CallStackProfileBuilder::MILESTONES_MAX_VALUE] =
{
ProcessPhase::MAIN_LOOP_START,
ProcessPhase::MAIN_NAVIGATION_START,
ProcessPhase::MAIN_NAVIGATION_FINISHED,
ProcessPhase::FIRST_NONEMPTY_PAINT,
ProcessPhase::SHUTDOWN_START,
};
// These functions are used to encode protobufs. --------------------------
// The protobuf expects the MD5 checksum prefix of the module name.
uint64_t HashModuleFilename(const base::FilePath& filename) { uint64_t HashModuleFilename(const base::FilePath& filename) {
const base::FilePath::StringType basename = filename.BaseName().value(); const base::FilePath::StringType basename = filename.BaseName().value();
// Copy the bytes in basename into a string buffer. // Copy the bytes in basename into a string buffer.
...@@ -87,198 +43,111 @@ uint64_t HashModuleFilename(const base::FilePath& filename) { ...@@ -87,198 +43,111 @@ uint64_t HashModuleFilename(const base::FilePath& filename) {
return base::HashMetricName(name_bytes); return base::HashMetricName(name_bytes);
} }
// Transcode |sample| into |proto_sample|, using base addresses in |modules| to
// compute module instruction pointer offsets.
void CopySampleToProto(const CallStackProfileBuilder::Sample& sample,
const std::vector<base::ModuleCache::Module>& modules,
CallStackProfile::Sample* proto_sample) {
for (const auto& frame : sample.frames) {
CallStackProfile::Location* location = proto_sample->add_frame();
// 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 == kUnknownModuleIndex)
continue;
int64_t module_offset =
reinterpret_cast<const char*>(frame.instruction_pointer) -
reinterpret_cast<const char*>(modules[frame.module_index].base_address);
DCHECK_GE(module_offset, 0);
location->set_address(static_cast<uint64_t>(module_offset));
location->set_module_id_index(frame.module_index);
}
}
// Transcode Sample annotations into protobuf fields. The C++ code uses a
// bit- field with each bit corresponding to an entry in an enumeration
// while the protobuf uses a repeated field of individual values. Conversion
// tables allow for arbitrary mapping, though no more than 32 in any given
// version of the code.
void CopyAnnotationsToProto(uint32_t new_milestones,
CallStackProfile::Sample* sample_proto) {
for (size_t bit = 0; new_milestones != 0 && bit < sizeof(new_milestones) * 8;
++bit) {
const uint32_t flag = 1U << bit;
if (new_milestones & flag) {
if (bit >= base::size(kProtoPhases)) {
NOTREACHED();
continue;
}
sample_proto->add_process_phase(kProtoPhases[bit]);
new_milestones ^= flag; // Bit is set so XOR will clear it.
}
}
}
} // namespace } // namespace
// CallStackProfileBuilder::Frame ---------------------------------------------
CallStackProfileBuilder::Frame::Frame(uintptr_t instruction_pointer,
size_t module_index)
: instruction_pointer(instruction_pointer), module_index(module_index) {}
CallStackProfileBuilder::Frame::~Frame() = default;
CallStackProfileBuilder::Frame::Frame()
: instruction_pointer(0), module_index(kUnknownModuleIndex) {}
// CallStackProfileBuilder::Sample --------------------------------------------
CallStackProfileBuilder::Sample::Sample() = default;
CallStackProfileBuilder::Sample::Sample(const Sample& sample) = default;
CallStackProfileBuilder::Sample::~Sample() = default;
CallStackProfileBuilder::Sample::Sample(const Frame& frame) {
frames.push_back(std::move(frame));
}
CallStackProfileBuilder::Sample::Sample(const std::vector<Frame>& frames)
: frames(frames) {}
CallStackProfileBuilder::CallStackProfileBuilder( CallStackProfileBuilder::CallStackProfileBuilder(
const CallStackProfileParams& profile_params, const CallStackProfileParams& profile_params,
base::OnceClosure completed_callback) base::OnceClosure completed_callback)
: profile_params_(profile_params), : profile_start_time_(base::TimeTicks::Now()) {
profile_start_time_(base::TimeTicks::Now()) {
completed_callback_ = std::move(completed_callback); completed_callback_ = std::move(completed_callback);
sampled_profile_.set_process(
ToExecutionContextProcess(profile_params.process));
sampled_profile_.set_thread(ToExecutionContextThread(profile_params.thread));
sampled_profile_.set_trigger_event(
ToSampledProfileTriggerEvent(profile_params.trigger));
} }
CallStackProfileBuilder::~CallStackProfileBuilder() = default; CallStackProfileBuilder::~CallStackProfileBuilder() = default;
void CallStackProfileBuilder::RecordAnnotations() { // static
// The code inside this method must not do anything that could acquire a
// mutex, including allocating memory (which includes LOG messages) because
// that mutex could be held by a stopped thread, thus resulting in deadlock.
sample_.process_milestones =
base::subtle::NoBarrier_Load(&g_process_milestones);
}
void CallStackProfileBuilder::OnSampleCompleted( void CallStackProfileBuilder::OnSampleCompleted(
std::vector<base::StackSamplingProfiler::Frame> frames) { std::vector<base::StackSamplingProfiler::Frame> frames) {
OnSampleCompleted(std::move(frames), 1); OnSampleCompleted(std::move(frames), 1);
} }
// TODO(chengx): record |count| as per-Stacksample metadata in the new proto
// format.
void CallStackProfileBuilder::OnSampleCompleted( void CallStackProfileBuilder::OnSampleCompleted(
std::vector<base::StackSamplingProfiler::Frame> frames, std::vector<base::StackSamplingProfiler::Frame> frames,
size_t count) { size_t /*count*/) {
// Assemble sample_ from |frames| first. // Write CallStackProfile::Stack protobuf message.
CallStackProfile::Stack stack;
for (const auto& frame : frames) { for (const auto& frame : frames) {
const base::ModuleCache::Module& module(frame.module); // keep the frame information even if its module is invalid so we have
if (!module.is_valid) { // visibility into how often this issue is happening on the server.
sample_.frames.emplace_back(frame.instruction_pointer, CallStackProfile::Location* location = stack.add_frame();
kUnknownModuleIndex); if (!frame.module.is_valid)
continue; continue;
}
// Dedup modules and cache them in modules_. // Dedup modules.
auto loc = module_index_.find(module.base_address); const base::ModuleCache::Module& module = frame.module;
if (loc == module_index_.end()) { auto module_loc = module_index_.find(module.base_address);
if (module_loc == module_index_.end()) {
modules_.push_back(module); modules_.push_back(module);
size_t index = modules_.size() - 1; size_t index = modules_.size() - 1;
loc = module_index_.insert(std::make_pair(module.base_address, index)) module_loc = module_index_.emplace(module.base_address, index).first;
.first;
} }
sample_.frames.emplace_back(frame.instruction_pointer, loc->second);
}
// Write CallStackProfile::Sample protocol buffer message based on sample_. // Write CallStackProfile::Location protobuf message.
int existing_sample_index = -1; ptrdiff_t module_offset =
auto location = sample_index_.find(sample_); reinterpret_cast<const char*>(frame.instruction_pointer) -
if (location != sample_index_.end()) reinterpret_cast<const char*>(module.base_address);
existing_sample_index = location->second; DCHECK_GE(module_offset, 0);
location->set_address(static_cast<uint64_t>(module_offset));
if (existing_sample_index != -1) { location->set_module_id_index(module_loc->second);
CallStackProfile::Sample* sample_proto =
proto_profile_.mutable_deprecated_sample(existing_sample_index);
sample_proto->set_count(sample_proto->count() + count);
return;
} }
CallStackProfile::Sample* sample_proto = CallStackProfile* call_stack_profile =
proto_profile_.add_deprecated_sample(); sampled_profile_.mutable_call_stack_profile();
CopySampleToProto(sample_, modules_, sample_proto);
sample_proto->set_count(count); // Dedup Stacks.
CopyAnnotationsToProto(sample_.process_milestones & ~milestones_, auto stack_loc = stack_index_.find(&stack);
sample_proto); if (stack_loc == stack_index_.end()) {
milestones_ = sample_.process_milestones; *call_stack_profile->add_stack() = std::move(stack);
int stack_index = call_stack_profile->stack_size() - 1;
sample_index_.insert(std::make_pair( // It is safe to store the Stack pointer because the repeated message
std::move(sample_), // representation ensures pointer stability.
static_cast<int>(proto_profile_.deprecated_sample_size()) - 1)); stack_loc = stack_index_
.emplace(call_stack_profile->mutable_stack(stack_index),
stack_index)
.first;
}
sample_ = Sample(); // Write CallStackProfile::StackSample protobuf message.
CallStackProfile::StackSample* stack_sample_proto =
call_stack_profile->add_stack_sample();
stack_sample_proto->set_stack_index(stack_loc->second);
} }
// Build a SampledProfile in the protocol buffer message format from the
// collected sampling data. The message is then passed to
// CallStackProfileMetricsProvider or ChildCallStackProfileCollector.
// A SampledProfile message (third_party/metrics_proto/sampled_profile.proto)
// contains a CallStackProfile message
// (third_party/metrics_proto/call_stack_profile.proto) and associated profile
// parameters (process/thread/trigger event). A CallStackProfile message
// contains a set of Sample messages and ModuleIdentifier messages, and other
// sampling information. One Sample corresponds to a single recorded stack, and
// the ModuleIdentifiers record those modules associated with the recorded stack
// frames.
void CallStackProfileBuilder::OnProfileCompleted( void CallStackProfileBuilder::OnProfileCompleted(
base::TimeDelta profile_duration, base::TimeDelta profile_duration,
base::TimeDelta sampling_period) { base::TimeDelta sampling_period) {
proto_profile_.set_profile_duration_ms(profile_duration.InMilliseconds()); // Build the SampledProfile protobuf message.
proto_profile_.set_sampling_period_ms(sampling_period.InMilliseconds()); CallStackProfile* call_stack_profile =
sampled_profile_.mutable_call_stack_profile();
call_stack_profile->set_profile_duration_ms(
profile_duration.InMilliseconds());
call_stack_profile->set_sampling_period_ms(sampling_period.InMilliseconds());
// Write CallStackProfile::ModuleIdentifier protobuf message.
for (const auto& module : modules_) { for (const auto& module : modules_) {
CallStackProfile::ModuleIdentifier* module_id = CallStackProfile::ModuleIdentifier* module_id =
proto_profile_.add_module_id(); call_stack_profile->add_module_id();
module_id->set_build_id(module.id); module_id->set_build_id(module.id);
module_id->set_name_md5_prefix(HashModuleFilename(module.filename)); module_id->set_name_md5_prefix(HashModuleFilename(module.filename));
} }
// Clear the caches etc. PassProfilesToMetricsProvider(std::move(sampled_profile_));
modules_.clear();
module_index_.clear();
sample_index_.clear();
// Assemble the SampledProfile protocol buffer message and run the associated
// callback to pass it.
SampledProfile sampled_profile;
CallStackProfile* proto_profile =
sampled_profile.mutable_call_stack_profile();
*proto_profile = std::move(proto_profile_);
sampled_profile.set_process(
ToExecutionContextProcess(profile_params_.process));
sampled_profile.set_thread(ToExecutionContextThread(profile_params_.thread));
sampled_profile.set_trigger_event(
ToSampledProfileTriggerEvent(profile_params_.trigger));
PassProfilesToMetricsProvider(std::move(sampled_profile));
// Run the completed callback if there is one. // Run the completed callback if there is one.
if (!completed_callback_.is_null()) if (!completed_callback_.is_null())
std::move(completed_callback_).Run(); std::move(completed_callback_).Run();
// Clear the caches.
stack_index_.clear();
module_index_.clear();
modules_.clear();
} }
// static // static
...@@ -288,15 +157,6 @@ void CallStackProfileBuilder::SetBrowserProcessReceiverCallback( ...@@ -288,15 +157,6 @@ void CallStackProfileBuilder::SetBrowserProcessReceiverCallback(
GetBrowserProcessReceiverCallbackInstance() = callback; GetBrowserProcessReceiverCallbackInstance() = callback;
} }
// static
void CallStackProfileBuilder::SetProcessMilestone(int milestone) {
DCHECK_LE(0, milestone);
DCHECK_GT(static_cast<int>(sizeof(g_process_milestones) * 8), milestone);
DCHECK_EQ(0, base::subtle::NoBarrier_Load(&g_process_milestones) &
(1 << milestone));
ChangeAtomicFlags(&g_process_milestones, 1 << milestone, 0);
}
// static // static
void CallStackProfileBuilder::SetParentProfileCollectorForChildProcess( void CallStackProfileBuilder::SetParentProfileCollectorForChildProcess(
metrics::mojom::CallStackProfileCollectorPtr browser_interface) { metrics::mojom::CallStackProfileCollectorPtr browser_interface) {
...@@ -306,7 +166,7 @@ void CallStackProfileBuilder::SetParentProfileCollectorForChildProcess( ...@@ -306,7 +166,7 @@ void CallStackProfileBuilder::SetParentProfileCollectorForChildProcess(
void CallStackProfileBuilder::PassProfilesToMetricsProvider( void CallStackProfileBuilder::PassProfilesToMetricsProvider(
SampledProfile sampled_profile) { SampledProfile sampled_profile) {
if (profile_params_.process == CallStackProfileParams::BROWSER_PROCESS) { if (sampled_profile.process() == BROWSER_PROCESS) {
GetBrowserProcessReceiverCallbackInstance().Run(profile_start_time_, GetBrowserProcessReceiverCallbackInstance().Run(profile_start_time_,
std::move(sampled_profile)); std::move(sampled_profile));
} else { } else {
...@@ -316,38 +176,17 @@ void CallStackProfileBuilder::PassProfilesToMetricsProvider( ...@@ -316,38 +176,17 @@ void CallStackProfileBuilder::PassProfilesToMetricsProvider(
} }
} }
// These operators permit types to be compared and used in a map of Samples. bool CallStackProfileBuilder::StackComparer::operator()(
const CallStackProfile::Stack* stack1,
bool operator==(const CallStackProfileBuilder::Sample& a, const CallStackProfile::Stack* stack2) const {
const CallStackProfileBuilder::Sample& b) { return std::lexicographical_compare(
return a.process_milestones == b.process_milestones && a.frames == b.frames; stack1->frame().begin(), stack1->frame().end(), stack2->frame().begin(),
} stack2->frame().end(),
[](const CallStackProfile::Location& loc1,
bool operator!=(const CallStackProfileBuilder::Sample& a, const CallStackProfile::Location& loc2) {
const CallStackProfileBuilder::Sample& b) { return std::make_pair(loc1.address(), loc1.module_id_index()) <
return !(a == b); std::make_pair(loc2.address(), loc2.module_id_index());
} });
bool operator<(const CallStackProfileBuilder::Sample& a,
const CallStackProfileBuilder::Sample& b) {
if (a.process_milestones != b.process_milestones)
return a.process_milestones < b.process_milestones;
return a.frames < b.frames;
}
bool operator==(const CallStackProfileBuilder::Frame& a,
const CallStackProfileBuilder::Frame& b) {
return a.instruction_pointer == b.instruction_pointer &&
a.module_index == b.module_index;
}
bool operator<(const CallStackProfileBuilder::Frame& a,
const CallStackProfileBuilder::Frame& b) {
if (a.module_index != b.module_index)
return a.module_index < b.module_index;
return a.instruction_pointer < b.instruction_pointer;
} }
} // namespace metrics } // namespace metrics
...@@ -18,66 +18,14 @@ ...@@ -18,66 +18,14 @@
namespace metrics { namespace metrics {
class SampledProfile;
// An instance of the class is meant to be passed to base::StackSamplingProfiler // An instance of the class is meant to be passed to base::StackSamplingProfiler
// to collect profiles. The profiles collected are uploaded via the metrics log. // to collect profiles. The profiles collected are uploaded via the metrics log.
//
// This uses the new StackSample encoding rather than the legacy Sample
// encoding.
class CallStackProfileBuilder class CallStackProfileBuilder
: public base::StackSamplingProfiler::ProfileBuilder { : public base::StackSamplingProfiler::ProfileBuilder {
public: public:
// Frame represents an individual sampled stack frame with module information.
struct Frame {
Frame(uintptr_t instruction_pointer, size_t module_index);
~Frame();
// Default constructor to satisfy IPC macros. Do not use explicitly.
Frame();
// The sampled instruction pointer within the function.
uintptr_t instruction_pointer;
// Index of the module in the associated vector of mofules. We don't
// represent module state directly here to save space.
size_t module_index;
};
// Sample represents a set of stack frames with some extra information.
struct Sample {
Sample();
Sample(const Sample& sample);
~Sample();
// These constructors are used only during testing.
Sample(const Frame& frame);
Sample(const std::vector<Frame>& frames);
// The entire stack frame when the sample is taken.
std::vector<Frame> frames;
// A bit-field indicating which process milestones have passed. This can be
// used to tell where in the process lifetime the samples are taken. Just
// as a "lifetime" can only move forward, these bits mark the milestones of
// the processes life as they occur. Bits can be set but never reset. The
// actual definition of the individual bits is left to the user of this
// module.
uint32_t process_milestones = 0;
};
// These milestones of a process lifetime can be passed as process "mile-
// stones" to CallStackProfileBuilder::SetProcessMilestone(). Be sure to
// update the translation constants at the top of the .cc file when this is
// changed.
enum Milestones : int {
MAIN_LOOP_START,
MAIN_NAVIGATION_START,
MAIN_NAVIGATION_FINISHED,
FIRST_NONEMPTY_PAINT,
SHUTDOWN_START,
MILESTONES_MAX_VALUE
};
// |completed_callback| is made when sampling a profile completes. Other // |completed_callback| is made when sampling a profile completes. Other
// threads, including the UI thread, may block on callback completion so this // threads, including the UI thread, may block on callback completion so this
// should run as quickly as possible. // should run as quickly as possible.
...@@ -92,7 +40,6 @@ class CallStackProfileBuilder ...@@ -92,7 +40,6 @@ class CallStackProfileBuilder
~CallStackProfileBuilder() override; ~CallStackProfileBuilder() override;
// base::StackSamplingProfiler::ProfileBuilder: // base::StackSamplingProfiler::ProfileBuilder:
void RecordAnnotations() override;
void OnSampleCompleted( void OnSampleCompleted(
std::vector<base::StackSamplingProfiler::Frame> frames) override; std::vector<base::StackSamplingProfiler::Frame> frames) override;
void OnProfileCompleted(base::TimeDelta profile_duration, void OnProfileCompleted(base::TimeDelta profile_duration,
...@@ -110,14 +57,6 @@ class CallStackProfileBuilder ...@@ -110,14 +57,6 @@ class CallStackProfileBuilder
const base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>& const base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>&
callback); callback);
// Sets the current system state that is recorded with each captured stack
// frame. This is thread-safe so can be called from anywhere. The parameter
// value should be from an enumeration of the appropriate type with values
// ranging from 0 to 31, inclusive. This sets bits within Sample field of
// |process_milestones|. The actual meanings of these bits are defined
// (globally) by the caller(s).
static void SetProcessMilestone(int milestone);
// Sets the CallStackProfileCollector interface from |browser_interface|. // Sets the CallStackProfileCollector interface from |browser_interface|.
// This function must be called within child processes. // This function must be called within child processes.
static void SetParentProfileCollectorForChildProcess( static void SetParentProfileCollectorForChildProcess(
...@@ -128,14 +67,18 @@ class CallStackProfileBuilder ...@@ -128,14 +67,18 @@ class CallStackProfileBuilder
virtual void PassProfilesToMetricsProvider(SampledProfile sampled_profile); virtual void PassProfilesToMetricsProvider(SampledProfile sampled_profile);
private: private:
// The collected stack samples in proto buffer message format. // The functor for Stack comparison.
CallStackProfile proto_profile_; struct StackComparer {
bool operator()(const CallStackProfile::Stack* stack1,
const CallStackProfile::Stack* stack2) const;
};
// The current sample being recorded. // The SampledProfile protobuf message which contains the collected stack
Sample sample_; // samples.
SampledProfile sampled_profile_;
// The indexes of samples, indexed by the sample. // The indexes of stacks, indexed by stack's address.
std::map<Sample, int> sample_index_; std::map<const CallStackProfile::Stack*, int, StackComparer> stack_index_;
// The indexes of modules, indexed by module's base_address. // The indexes of modules, indexed by module's base_address.
std::map<uintptr_t, size_t> module_index_; std::map<uintptr_t, size_t> module_index_;
...@@ -143,15 +86,9 @@ class CallStackProfileBuilder ...@@ -143,15 +86,9 @@ class CallStackProfileBuilder
// The distinct modules in the current profile. // The distinct modules in the current profile.
std::vector<base::ModuleCache::Module> modules_; std::vector<base::ModuleCache::Module> modules_;
// The process milestones of a previous sample.
uint32_t milestones_ = 0;
// Callback made when sampling a profile completes. // Callback made when sampling a profile completes.
base::OnceClosure completed_callback_; base::OnceClosure completed_callback_;
// The parameters associated with the sampled profile.
const CallStackProfileParams profile_params_;
// The start time of a profile collection. // The start time of a profile collection.
const base::TimeTicks profile_start_time_; const base::TimeTicks profile_start_time_;
......
...@@ -34,7 +34,7 @@ class TestingCallStackProfileBuilder : public CallStackProfileBuilder { ...@@ -34,7 +34,7 @@ class TestingCallStackProfileBuilder : public CallStackProfileBuilder {
~TestingCallStackProfileBuilder() override; ~TestingCallStackProfileBuilder() override;
const SampledProfile& sampled_profile() { return sampled_profile_; } const SampledProfile& test_sampled_profile() { return test_sampled_profile_; }
protected: protected:
// Overridden for testing. // Overridden for testing.
...@@ -42,7 +42,7 @@ class TestingCallStackProfileBuilder : public CallStackProfileBuilder { ...@@ -42,7 +42,7 @@ class TestingCallStackProfileBuilder : public CallStackProfileBuilder {
private: private:
// The completed profile. // The completed profile.
SampledProfile sampled_profile_; SampledProfile test_sampled_profile_;
}; };
TestingCallStackProfileBuilder::TestingCallStackProfileBuilder( TestingCallStackProfileBuilder::TestingCallStackProfileBuilder(
...@@ -54,45 +54,11 @@ TestingCallStackProfileBuilder::~TestingCallStackProfileBuilder() = default; ...@@ -54,45 +54,11 @@ TestingCallStackProfileBuilder::~TestingCallStackProfileBuilder() = default;
void TestingCallStackProfileBuilder::PassProfilesToMetricsProvider( void TestingCallStackProfileBuilder::PassProfilesToMetricsProvider(
SampledProfile sampled_profile) { SampledProfile sampled_profile) {
sampled_profile_ = std::move(sampled_profile); test_sampled_profile_ = std::move(sampled_profile);
} }
} // namespace } // namespace
TEST(CallStackProfileBuilderTest, SetProcessMilestone) {
auto profile_builder =
std::make_unique<TestingCallStackProfileBuilder>(kProfileParams);
// The default milestone is 0.
profile_builder->RecordAnnotations();
profile_builder->OnSampleCompleted(std::vector<Frame>());
CallStackProfileBuilder::SetProcessMilestone(1);
profile_builder->RecordAnnotations();
profile_builder->OnSampleCompleted(std::vector<Frame>());
profile_builder->OnProfileCompleted(base::TimeDelta(), base::TimeDelta());
const SampledProfile& proto = profile_builder->sampled_profile();
ASSERT_TRUE(proto.has_call_stack_profile());
const CallStackProfile& profile = proto.call_stack_profile();
ASSERT_EQ(2, profile.deprecated_sample_size());
uint32_t process_milestones = 0;
for (int i = 0; i < profile.deprecated_sample(0).process_phase().size(); ++i)
process_milestones |=
1U << profile.deprecated_sample(0).process_phase().Get(i);
EXPECT_EQ(0U, process_milestones);
process_milestones = 0;
for (int i = 0; i < profile.deprecated_sample(1).process_phase().size(); ++i)
process_milestones |=
1U << profile.deprecated_sample(1).process_phase().Get(i);
EXPECT_EQ(1U << 1, process_milestones);
}
TEST(CallStackProfileBuilderTest, ProfilingCompleted) { TEST(CallStackProfileBuilderTest, ProfilingCompleted) {
// Set up a mock completed callback which will be run once. // Set up a mock completed callback which will be run once.
base::MockCallback<base::OnceClosure> mock_closure; base::MockCallback<base::OnceClosure> mock_closure;
...@@ -129,7 +95,7 @@ TEST(CallStackProfileBuilderTest, ProfilingCompleted) { ...@@ -129,7 +95,7 @@ TEST(CallStackProfileBuilderTest, ProfilingCompleted) {
profile_builder->OnProfileCompleted(base::TimeDelta::FromMilliseconds(500), profile_builder->OnProfileCompleted(base::TimeDelta::FromMilliseconds(500),
base::TimeDelta::FromMilliseconds(100)); base::TimeDelta::FromMilliseconds(100));
const SampledProfile& proto = profile_builder->sampled_profile(); const SampledProfile& proto = profile_builder->test_sampled_profile();
ASSERT_TRUE(proto.has_process()); ASSERT_TRUE(proto.has_process());
ASSERT_EQ(BROWSER_PROCESS, proto.process()); ASSERT_EQ(BROWSER_PROCESS, proto.process());
...@@ -141,29 +107,33 @@ TEST(CallStackProfileBuilderTest, ProfilingCompleted) { ...@@ -141,29 +107,33 @@ TEST(CallStackProfileBuilderTest, ProfilingCompleted) {
ASSERT_TRUE(proto.has_call_stack_profile()); ASSERT_TRUE(proto.has_call_stack_profile());
const CallStackProfile& profile = proto.call_stack_profile(); const CallStackProfile& profile = proto.call_stack_profile();
ASSERT_EQ(2, profile.deprecated_sample_size()); ASSERT_EQ(2, profile.stack_size());
ASSERT_EQ(2, profile.deprecated_sample(0).frame_size()); ASSERT_EQ(2, profile.stack(0).frame_size());
ASSERT_TRUE(profile.deprecated_sample(0).frame(0).has_module_id_index()); ASSERT_TRUE(profile.stack(0).frame(0).has_module_id_index());
EXPECT_EQ(0, profile.deprecated_sample(0).frame(0).module_id_index()); EXPECT_EQ(0, profile.stack(0).frame(0).module_id_index());
ASSERT_TRUE(profile.deprecated_sample(0).frame(1).has_module_id_index()); ASSERT_TRUE(profile.stack(0).frame(1).has_module_id_index());
EXPECT_EQ(1, profile.deprecated_sample(0).frame(1).module_id_index()); EXPECT_EQ(1, profile.stack(0).frame(1).module_id_index());
ASSERT_EQ(1, profile.deprecated_sample(1).frame_size()); ASSERT_EQ(1, profile.stack(1).frame_size());
ASSERT_TRUE(profile.deprecated_sample(1).frame(0).has_module_id_index()); ASSERT_TRUE(profile.stack(1).frame(0).has_module_id_index());
EXPECT_EQ(2, profile.deprecated_sample(1).frame(0).module_id_index()); EXPECT_EQ(2, profile.stack(1).frame(0).module_id_index());
ASSERT_EQ(3, profile.module_id().size()); ASSERT_EQ(3, profile.module_id().size());
ASSERT_TRUE(profile.module_id(0).has_build_id()); ASSERT_TRUE(profile.module_id(0).has_build_id());
ASSERT_EQ("1", profile.module_id(0).build_id()); EXPECT_EQ("1", profile.module_id(0).build_id());
ASSERT_TRUE(profile.module_id(0).has_name_md5_prefix()); ASSERT_TRUE(profile.module_id(0).has_name_md5_prefix());
ASSERT_EQ(module_md5, profile.module_id(0).name_md5_prefix()); EXPECT_EQ(module_md5, profile.module_id(0).name_md5_prefix());
ASSERT_TRUE(profile.module_id(1).has_build_id()); ASSERT_TRUE(profile.module_id(1).has_build_id());
ASSERT_EQ("2", profile.module_id(1).build_id()); EXPECT_EQ("2", profile.module_id(1).build_id());
ASSERT_TRUE(profile.module_id(1).has_name_md5_prefix()); ASSERT_TRUE(profile.module_id(1).has_name_md5_prefix());
ASSERT_EQ(module_md5, profile.module_id(1).name_md5_prefix()); EXPECT_EQ(module_md5, profile.module_id(1).name_md5_prefix());
ASSERT_TRUE(profile.module_id(2).has_build_id()); ASSERT_TRUE(profile.module_id(2).has_build_id());
ASSERT_EQ("3", profile.module_id(2).build_id()); EXPECT_EQ("3", profile.module_id(2).build_id());
ASSERT_TRUE(profile.module_id(2).has_name_md5_prefix()); ASSERT_TRUE(profile.module_id(2).has_name_md5_prefix());
ASSERT_EQ(module_md5, profile.module_id(2).name_md5_prefix()); EXPECT_EQ(module_md5, profile.module_id(2).name_md5_prefix());
ASSERT_EQ(2, profile.stack_sample_size());
EXPECT_EQ(0, profile.stack_sample(0).stack_index());
EXPECT_EQ(1, profile.stack_sample(1).stack_index());
ASSERT_TRUE(profile.has_profile_duration_ms()); ASSERT_TRUE(profile.has_profile_duration_ms());
EXPECT_EQ(500, profile.profile_duration_ms()); EXPECT_EQ(500, profile.profile_duration_ms());
...@@ -171,7 +141,7 @@ TEST(CallStackProfileBuilderTest, ProfilingCompleted) { ...@@ -171,7 +141,7 @@ TEST(CallStackProfileBuilderTest, ProfilingCompleted) {
EXPECT_EQ(100, profile.sampling_period_ms()); EXPECT_EQ(100, profile.sampling_period_ms());
} }
TEST(CallStackProfileBuilderTest, SamplesDeduped) { TEST(CallStackProfileBuilderTest, StacksDeduped) {
auto profile_builder = auto profile_builder =
std::make_unique<TestingCallStackProfileBuilder>(kProfileParams); std::make_unique<TestingCallStackProfileBuilder>(kProfileParams);
...@@ -191,19 +161,14 @@ TEST(CallStackProfileBuilderTest, SamplesDeduped) { ...@@ -191,19 +161,14 @@ TEST(CallStackProfileBuilderTest, SamplesDeduped) {
std::vector<Frame> frames = {frame1, frame2}; std::vector<Frame> frames = {frame1, frame2};
// Two samples are completed with the same frames. They also have the same // Two stacks are completed with the same frames therefore they are deduped
// process milestone therefore they are deduped to one. // to one.
CallStackProfileBuilder::SetProcessMilestone(0); profile_builder->OnSampleCompleted(frames);
profile_builder->RecordAnnotations();
profile_builder->OnSampleCompleted(frames, 42);
profile_builder->RecordAnnotations();
profile_builder->OnSampleCompleted(frames); profile_builder->OnSampleCompleted(frames);
profile_builder->OnProfileCompleted(base::TimeDelta(), base::TimeDelta()); profile_builder->OnProfileCompleted(base::TimeDelta(), base::TimeDelta());
const SampledProfile& proto = profile_builder->sampled_profile(); const SampledProfile& proto = profile_builder->test_sampled_profile();
ASSERT_TRUE(proto.has_process()); ASSERT_TRUE(proto.has_process());
ASSERT_EQ(BROWSER_PROCESS, proto.process()); ASSERT_EQ(BROWSER_PROCESS, proto.process());
...@@ -213,11 +178,14 @@ TEST(CallStackProfileBuilderTest, SamplesDeduped) { ...@@ -213,11 +178,14 @@ TEST(CallStackProfileBuilderTest, SamplesDeduped) {
ASSERT_EQ(SampledProfile::PROCESS_STARTUP, proto.trigger_event()); ASSERT_EQ(SampledProfile::PROCESS_STARTUP, proto.trigger_event());
ASSERT_TRUE(proto.has_call_stack_profile()); ASSERT_TRUE(proto.has_call_stack_profile());
ASSERT_EQ(1, proto.call_stack_profile().deprecated_sample_size()); const CallStackProfile& profile = proto.call_stack_profile();
ASSERT_EQ(43, proto.call_stack_profile().deprecated_sample(0).count()); ASSERT_EQ(1, profile.stack_size());
ASSERT_EQ(2, profile.stack_sample_size());
EXPECT_EQ(0, profile.stack_sample(0).stack_index());
EXPECT_EQ(0, profile.stack_sample(1).stack_index());
} }
TEST(CallStackProfileBuilderTest, SamplesNotDeduped) { TEST(CallStackProfileBuilderTest, StacksNotDeduped) {
auto profile_builder = auto profile_builder =
std::make_unique<TestingCallStackProfileBuilder>(kProfileParams); std::make_unique<TestingCallStackProfileBuilder>(kProfileParams);
...@@ -235,21 +203,16 @@ TEST(CallStackProfileBuilderTest, SamplesNotDeduped) { ...@@ -235,21 +203,16 @@ TEST(CallStackProfileBuilderTest, SamplesNotDeduped) {
Module module2 = {module_base_address2, "2", module_path}; Module module2 = {module_base_address2, "2", module_path};
Frame frame2 = {module_base_address2 + 0x10, module2}; Frame frame2 = {module_base_address2 + 0x10, module2};
std::vector<Frame> frames = {frame1, frame2}; std::vector<Frame> frames1 = {frame1};
std::vector<Frame> frames2 = {frame2};
// Two samples are completed with the same frames but different process
// milestones. They are considered as different samples threfore not deduped.
CallStackProfileBuilder::SetProcessMilestone(2);
profile_builder->RecordAnnotations();
profile_builder->OnSampleCompleted(frames);
CallStackProfileBuilder::SetProcessMilestone(4); // Two stacks are completed with the different frames therefore not deduped.
profile_builder->RecordAnnotations(); profile_builder->OnSampleCompleted(frames1);
profile_builder->OnSampleCompleted(frames); profile_builder->OnSampleCompleted(frames2);
profile_builder->OnProfileCompleted(base::TimeDelta(), base::TimeDelta()); profile_builder->OnProfileCompleted(base::TimeDelta(), base::TimeDelta());
const SampledProfile& proto = profile_builder->sampled_profile(); const SampledProfile& proto = profile_builder->test_sampled_profile();
ASSERT_TRUE(proto.has_process()); ASSERT_TRUE(proto.has_process());
ASSERT_EQ(BROWSER_PROCESS, proto.process()); ASSERT_EQ(BROWSER_PROCESS, proto.process());
...@@ -259,7 +222,11 @@ TEST(CallStackProfileBuilderTest, SamplesNotDeduped) { ...@@ -259,7 +222,11 @@ TEST(CallStackProfileBuilderTest, SamplesNotDeduped) {
ASSERT_EQ(SampledProfile::PROCESS_STARTUP, proto.trigger_event()); ASSERT_EQ(SampledProfile::PROCESS_STARTUP, proto.trigger_event());
ASSERT_TRUE(proto.has_call_stack_profile()); ASSERT_TRUE(proto.has_call_stack_profile());
ASSERT_EQ(2, proto.call_stack_profile().deprecated_sample_size()); const CallStackProfile& profile = proto.call_stack_profile();
ASSERT_EQ(2, profile.stack_size());
ASSERT_EQ(2, profile.stack_sample_size());
EXPECT_EQ(0, profile.stack_sample(0).stack_index());
EXPECT_EQ(1, profile.stack_sample(1).stack_index());
} }
TEST(CallStackProfileBuilderTest, Modules) { TEST(CallStackProfileBuilderTest, Modules) {
...@@ -286,27 +253,30 @@ TEST(CallStackProfileBuilderTest, Modules) { ...@@ -286,27 +253,30 @@ TEST(CallStackProfileBuilderTest, Modules) {
profile_builder->OnSampleCompleted(frames); profile_builder->OnSampleCompleted(frames);
profile_builder->OnProfileCompleted(base::TimeDelta(), base::TimeDelta()); profile_builder->OnProfileCompleted(base::TimeDelta(), base::TimeDelta());
const SampledProfile& proto = profile_builder->sampled_profile(); const SampledProfile& proto = profile_builder->test_sampled_profile();
ASSERT_TRUE(proto.has_call_stack_profile()); ASSERT_TRUE(proto.has_call_stack_profile());
const CallStackProfile& profile = proto.call_stack_profile(); const CallStackProfile& profile = proto.call_stack_profile();
ASSERT_EQ(1, profile.deprecated_sample_size()); ASSERT_EQ(1, profile.stack_sample_size());
ASSERT_EQ(2, profile.deprecated_sample(0).frame_size()); EXPECT_EQ(0, profile.stack_sample(0).stack_index());
ASSERT_EQ(1, profile.stack_size());
ASSERT_EQ(2, profile.stack(0).frame_size());
ASSERT_FALSE(profile.deprecated_sample(0).frame(0).has_module_id_index()); ASSERT_FALSE(profile.stack(0).frame(0).has_module_id_index());
ASSERT_FALSE(profile.deprecated_sample(0).frame(0).has_address()); ASSERT_FALSE(profile.stack(0).frame(0).has_address());
ASSERT_TRUE(profile.deprecated_sample(0).frame(1).has_module_id_index()); ASSERT_TRUE(profile.stack(0).frame(1).has_module_id_index());
EXPECT_EQ(0, profile.deprecated_sample(0).frame(1).module_id_index()); EXPECT_EQ(0, profile.stack(0).frame(1).module_id_index());
ASSERT_TRUE(profile.deprecated_sample(0).frame(1).has_address()); ASSERT_TRUE(profile.stack(0).frame(1).has_address());
EXPECT_EQ(0x10ULL, profile.deprecated_sample(0).frame(1).address()); EXPECT_EQ(0x10ULL, profile.stack(0).frame(1).address());
ASSERT_EQ(1, profile.module_id().size()); ASSERT_EQ(1, profile.module_id().size());
ASSERT_TRUE(profile.module_id(0).has_build_id()); ASSERT_TRUE(profile.module_id(0).has_build_id());
ASSERT_EQ("2", profile.module_id(0).build_id()); EXPECT_EQ("2", profile.module_id(0).build_id());
ASSERT_TRUE(profile.module_id(0).has_name_md5_prefix()); ASSERT_TRUE(profile.module_id(0).has_name_md5_prefix());
ASSERT_EQ(module_md5, profile.module_id(0).name_md5_prefix()); EXPECT_EQ(module_md5, profile.module_id(0).name_md5_prefix());
} }
TEST(CallStackProfileBuilderTest, DedupModules) { TEST(CallStackProfileBuilderTest, DedupModules) {
...@@ -334,31 +304,34 @@ TEST(CallStackProfileBuilderTest, DedupModules) { ...@@ -334,31 +304,34 @@ TEST(CallStackProfileBuilderTest, DedupModules) {
profile_builder->OnSampleCompleted(frames); profile_builder->OnSampleCompleted(frames);
profile_builder->OnProfileCompleted(base::TimeDelta(), base::TimeDelta()); profile_builder->OnProfileCompleted(base::TimeDelta(), base::TimeDelta());
const SampledProfile& proto = profile_builder->sampled_profile(); const SampledProfile& proto = profile_builder->test_sampled_profile();
ASSERT_TRUE(proto.has_call_stack_profile()); ASSERT_TRUE(proto.has_call_stack_profile());
const CallStackProfile& profile = proto.call_stack_profile(); const CallStackProfile& profile = proto.call_stack_profile();
ASSERT_EQ(1, profile.deprecated_sample_size()); ASSERT_EQ(1, profile.stack_sample_size());
ASSERT_EQ(2, profile.deprecated_sample(0).frame_size()); EXPECT_EQ(0, profile.stack_sample(0).stack_index());
ASSERT_EQ(1, profile.stack_size());
ASSERT_EQ(2, profile.stack(0).frame_size());
// Since module1 and module2 have the same base address, they are considered // Since module1 and module2 have the same base address, they are considered
// the same module and therefore deduped. // the same module and therefore deduped.
ASSERT_TRUE(profile.deprecated_sample(0).frame(0).has_module_id_index()); ASSERT_TRUE(profile.stack(0).frame(0).has_module_id_index());
EXPECT_EQ(0, profile.deprecated_sample(0).frame(0).module_id_index()); EXPECT_EQ(0, profile.stack(0).frame(0).module_id_index());
ASSERT_TRUE(profile.deprecated_sample(0).frame(0).has_address()); ASSERT_TRUE(profile.stack(0).frame(0).has_address());
EXPECT_EQ(0x10ULL, profile.deprecated_sample(0).frame(0).address()); EXPECT_EQ(0x10ULL, profile.stack(0).frame(0).address());
ASSERT_TRUE(profile.deprecated_sample(0).frame(1).has_module_id_index()); ASSERT_TRUE(profile.stack(0).frame(1).has_module_id_index());
EXPECT_EQ(0, profile.deprecated_sample(0).frame(1).module_id_index()); EXPECT_EQ(0, profile.stack(0).frame(1).module_id_index());
ASSERT_TRUE(profile.deprecated_sample(0).frame(1).has_address()); ASSERT_TRUE(profile.stack(0).frame(1).has_address());
EXPECT_EQ(0x20ULL, profile.deprecated_sample(0).frame(1).address()); EXPECT_EQ(0x20ULL, profile.stack(0).frame(1).address());
ASSERT_EQ(1, profile.module_id().size()); ASSERT_EQ(1, profile.module_id().size());
ASSERT_TRUE(profile.module_id(0).has_build_id()); ASSERT_TRUE(profile.module_id(0).has_build_id());
ASSERT_EQ("1", profile.module_id(0).build_id()); EXPECT_EQ("1", profile.module_id(0).build_id());
ASSERT_TRUE(profile.module_id(0).has_name_md5_prefix()); ASSERT_TRUE(profile.module_id(0).has_name_md5_prefix());
ASSERT_EQ(module_md5, profile.module_id(0).name_md5_prefix()); EXPECT_EQ(module_md5, profile.module_id(0).name_md5_prefix());
} }
} // namespace metrics } // namespace metrics
\ No newline at end of file
// Copyright 2018 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/legacy_call_stack_profile_builder.h"
#include <string>
#include <utility>
#include "base/atomicops.h"
#include "base/files/file_path.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/metrics/metrics_hashes.h"
#include "base/no_destructor.h"
#include "base/stl_util.h"
#include "components/metrics/call_stack_profile_encoding.h"
namespace metrics {
namespace {
// Only used by child processes.
base::LazyInstance<ChildCallStackProfileCollector>::Leaky
g_child_call_stack_profile_collector = LAZY_INSTANCE_INITIALIZER;
base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>&
GetBrowserProcessReceiverCallbackInstance() {
static base::NoDestructor<
base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>>
instance;
return *instance;
}
// Identifies an unknown module.
const size_t kUnknownModuleIndex = static_cast<size_t>(-1);
// This global variables holds the current system state and is recorded with
// every captured sample, done on a separate thread which is why updates to
// this must be atomic. A PostTask to move the the updates to that thread
// would skew the timing and a lock could result in deadlock if the thread
// making a change was also being profiled and got stopped.
static base::subtle::Atomic32 g_process_milestones = 0;
void ChangeAtomicFlags(base::subtle::Atomic32* flags,
base::subtle::Atomic32 set,
base::subtle::Atomic32 clear) {
DCHECK(set != 0 || clear != 0);
DCHECK_EQ(0, set & clear);
base::subtle::Atomic32 bits = base::subtle::NoBarrier_Load(flags);
while (true) {
base::subtle::Atomic32 existing = base::subtle::NoBarrier_CompareAndSwap(
flags, bits, (bits | set) & ~clear);
if (existing == bits)
break;
bits = existing;
}
}
// Provide a mapping from the C++ "enum" definition of various process mile-
// stones to the equivalent protobuf "enum" definition. This table-lookup
// conversion allows for the implementation to evolve and still be compatible
// with the protobuf -- even if there are ever more than 32 defined proto
// values, though never more than 32 could be in-use in a given C++ version
// of the code.
const ProcessPhase
kProtoPhases[LegacyCallStackProfileBuilder::MILESTONES_MAX_VALUE] = {
ProcessPhase::MAIN_LOOP_START,
ProcessPhase::MAIN_NAVIGATION_START,
ProcessPhase::MAIN_NAVIGATION_FINISHED,
ProcessPhase::FIRST_NONEMPTY_PAINT,
ProcessPhase::SHUTDOWN_START,
};
// These functions are used to encode protobufs. --------------------------
// The protobuf expects the MD5 checksum prefix of the module name.
uint64_t 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 base::HashMetricName(name_bytes);
}
// Transcode |sample| into |proto_sample|, using base addresses in |modules| to
// compute module instruction pointer offsets.
void CopySampleToProto(const LegacyCallStackProfileBuilder::Sample& sample,
const std::vector<base::ModuleCache::Module>& modules,
CallStackProfile::Sample* proto_sample) {
for (const auto& frame : sample.frames) {
CallStackProfile::Location* location = proto_sample->add_frame();
// 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 == kUnknownModuleIndex)
continue;
int64_t module_offset =
reinterpret_cast<const char*>(frame.instruction_pointer) -
reinterpret_cast<const char*>(modules[frame.module_index].base_address);
DCHECK_GE(module_offset, 0);
location->set_address(static_cast<uint64_t>(module_offset));
location->set_module_id_index(frame.module_index);
}
}
// Transcode Sample annotations into protobuf fields. The C++ code uses a
// bit- field with each bit corresponding to an entry in an enumeration
// while the protobuf uses a repeated field of individual values. Conversion
// tables allow for arbitrary mapping, though no more than 32 in any given
// version of the code.
void CopyAnnotationsToProto(uint32_t new_milestones,
CallStackProfile::Sample* sample_proto) {
for (size_t bit = 0; new_milestones != 0 && bit < sizeof(new_milestones) * 8;
++bit) {
const uint32_t flag = 1U << bit;
if (new_milestones & flag) {
if (bit >= base::size(kProtoPhases)) {
NOTREACHED();
continue;
}
sample_proto->add_process_phase(kProtoPhases[bit]);
new_milestones ^= flag; // Bit is set so XOR will clear it.
}
}
}
} // namespace
// LegacyCallStackProfileBuilder::Frame
// ---------------------------------------------
LegacyCallStackProfileBuilder::Frame::Frame(uintptr_t instruction_pointer,
size_t module_index)
: instruction_pointer(instruction_pointer), module_index(module_index) {}
LegacyCallStackProfileBuilder::Frame::~Frame() = default;
LegacyCallStackProfileBuilder::Frame::Frame()
: instruction_pointer(0), module_index(kUnknownModuleIndex) {}
// LegacyCallStackProfileBuilder::Sample
// --------------------------------------------
LegacyCallStackProfileBuilder::Sample::Sample() = default;
LegacyCallStackProfileBuilder::Sample::Sample(const Sample& sample) = default;
LegacyCallStackProfileBuilder::Sample::~Sample() = default;
LegacyCallStackProfileBuilder::Sample::Sample(const Frame& frame) {
frames.push_back(std::move(frame));
}
LegacyCallStackProfileBuilder::Sample::Sample(const std::vector<Frame>& frames)
: frames(frames) {}
LegacyCallStackProfileBuilder::LegacyCallStackProfileBuilder(
const CallStackProfileParams& profile_params,
base::OnceClosure completed_callback)
: profile_params_(profile_params),
profile_start_time_(base::TimeTicks::Now()) {
completed_callback_ = std::move(completed_callback);
}
LegacyCallStackProfileBuilder::~LegacyCallStackProfileBuilder() = default;
void LegacyCallStackProfileBuilder::RecordAnnotations() {
// The code inside this method must not do anything that could acquire a
// mutex, including allocating memory (which includes LOG messages) because
// that mutex could be held by a stopped thread, thus resulting in deadlock.
sample_.process_milestones =
base::subtle::NoBarrier_Load(&g_process_milestones);
}
void LegacyCallStackProfileBuilder::OnSampleCompleted(
std::vector<base::StackSamplingProfiler::Frame> frames) {
OnSampleCompleted(std::move(frames), 1);
}
void LegacyCallStackProfileBuilder::OnSampleCompleted(
std::vector<base::StackSamplingProfiler::Frame> frames,
size_t count) {
// Assemble sample_ from |frames| first.
for (const auto& frame : frames) {
const base::ModuleCache::Module& module(frame.module);
if (!module.is_valid) {
sample_.frames.emplace_back(frame.instruction_pointer,
kUnknownModuleIndex);
continue;
}
// Dedup modules and cache them in modules_.
auto loc = module_index_.find(module.base_address);
if (loc == module_index_.end()) {
modules_.push_back(module);
size_t index = modules_.size() - 1;
loc = module_index_.insert(std::make_pair(module.base_address, index))
.first;
}
sample_.frames.emplace_back(frame.instruction_pointer, loc->second);
}
// Write CallStackProfile::Sample protocol buffer message based on sample_.
int existing_sample_index = -1;
auto location = sample_index_.find(sample_);
if (location != sample_index_.end())
existing_sample_index = location->second;
if (existing_sample_index != -1) {
CallStackProfile::Sample* sample_proto =
proto_profile_.mutable_deprecated_sample(existing_sample_index);
sample_proto->set_count(sample_proto->count() + count);
return;
}
CallStackProfile::Sample* sample_proto =
proto_profile_.add_deprecated_sample();
CopySampleToProto(sample_, modules_, sample_proto);
sample_proto->set_count(count);
CopyAnnotationsToProto(sample_.process_milestones & ~milestones_,
sample_proto);
milestones_ = sample_.process_milestones;
sample_index_.insert(std::make_pair(
std::move(sample_),
static_cast<int>(proto_profile_.deprecated_sample_size()) - 1));
sample_ = Sample();
}
// Build a SampledProfile in the protocol buffer message format from the
// collected sampling data. The message is then passed to
// CallStackProfileMetricsProvider or ChildCallStackProfileCollector.
// A SampledProfile message (third_party/metrics_proto/sampled_profile.proto)
// contains a CallStackProfile message
// (third_party/metrics_proto/call_stack_profile.proto) and associated profile
// parameters (process/thread/trigger event). A CallStackProfile message
// contains a set of Sample messages and ModuleIdentifier messages, and other
// sampling information. One Sample corresponds to a single recorded stack, and
// the ModuleIdentifiers record those modules associated with the recorded stack
// frames.
void LegacyCallStackProfileBuilder::OnProfileCompleted(
base::TimeDelta profile_duration,
base::TimeDelta sampling_period) {
proto_profile_.set_profile_duration_ms(profile_duration.InMilliseconds());
proto_profile_.set_sampling_period_ms(sampling_period.InMilliseconds());
for (const auto& module : 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));
}
// Clear the caches etc.
modules_.clear();
module_index_.clear();
sample_index_.clear();
// Assemble the SampledProfile protocol buffer message and run the associated
// callback to pass it.
SampledProfile sampled_profile;
CallStackProfile* proto_profile =
sampled_profile.mutable_call_stack_profile();
*proto_profile = std::move(proto_profile_);
sampled_profile.set_process(
ToExecutionContextProcess(profile_params_.process));
sampled_profile.set_thread(ToExecutionContextThread(profile_params_.thread));
sampled_profile.set_trigger_event(
ToSampledProfileTriggerEvent(profile_params_.trigger));
PassProfilesToMetricsProvider(std::move(sampled_profile));
// Run the completed callback if there is one.
if (!completed_callback_.is_null())
std::move(completed_callback_).Run();
}
// static
void LegacyCallStackProfileBuilder::SetBrowserProcessReceiverCallback(
const base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>&
callback) {
GetBrowserProcessReceiverCallbackInstance() = callback;
}
void LegacyCallStackProfileBuilder::PassProfilesToMetricsProvider(
SampledProfile sampled_profile) {
if (profile_params_.process == CallStackProfileParams::BROWSER_PROCESS) {
GetBrowserProcessReceiverCallbackInstance().Run(profile_start_time_,
std::move(sampled_profile));
} else {
g_child_call_stack_profile_collector.Get()
.ChildCallStackProfileCollector::Collect(profile_start_time_,
std::move(sampled_profile));
}
}
// static
void LegacyCallStackProfileBuilder::SetProcessMilestone(int milestone) {
DCHECK_LE(0, milestone);
DCHECK_GT(static_cast<int>(sizeof(g_process_milestones) * 8), milestone);
DCHECK_EQ(0, base::subtle::NoBarrier_Load(&g_process_milestones) &
(1 << milestone));
ChangeAtomicFlags(&g_process_milestones, 1 << milestone, 0);
}
// static
void LegacyCallStackProfileBuilder::SetParentProfileCollectorForChildProcess(
metrics::mojom::CallStackProfileCollectorPtr browser_interface) {
g_child_call_stack_profile_collector.Get().SetParentProfileCollector(
std::move(browser_interface));
}
// These operators permit types to be compared and used in a map of Samples.
bool operator==(const LegacyCallStackProfileBuilder::Sample& a,
const LegacyCallStackProfileBuilder::Sample& b) {
return a.process_milestones == b.process_milestones && a.frames == b.frames;
}
bool operator!=(const LegacyCallStackProfileBuilder::Sample& a,
const LegacyCallStackProfileBuilder::Sample& b) {
return !(a == b);
}
bool operator<(const LegacyCallStackProfileBuilder::Sample& a,
const LegacyCallStackProfileBuilder::Sample& b) {
if (a.process_milestones != b.process_milestones)
return a.process_milestones < b.process_milestones;
return a.frames < b.frames;
}
bool operator==(const LegacyCallStackProfileBuilder::Frame& a,
const LegacyCallStackProfileBuilder::Frame& b) {
return a.instruction_pointer == b.instruction_pointer &&
a.module_index == b.module_index;
}
bool operator<(const LegacyCallStackProfileBuilder::Frame& a,
const LegacyCallStackProfileBuilder::Frame& b) {
if (a.module_index != b.module_index)
return a.module_index < b.module_index;
return a.instruction_pointer < b.instruction_pointer;
}
} // namespace metrics
// Copyright 2018 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_LEGACY_CALL_STACK_PROFILE_BUILDER_H_
#define COMPONENTS_METRICS_LEGACY_CALL_STACK_PROFILE_BUILDER_H_
#include <map>
#include <vector>
#include "base/callback.h"
#include "base/profiler/stack_sampling_profiler.h"
#include "base/sampling_heap_profiler/module_cache.h"
#include "base/time/time.h"
#include "components/metrics/call_stack_profile_params.h"
#include "components/metrics/child_call_stack_profile_collector.h"
#include "third_party/metrics_proto/sampled_profile.pb.h"
namespace metrics {
// An instance of the class is meant to be passed to base::StackSamplingProfiler
// to collect profiles. The profiles collected are uploaded via the metrics log.
//
// This uses the legacy Sample encoding rather than the new StackSample
// encoding.
class LegacyCallStackProfileBuilder
: public base::StackSamplingProfiler::ProfileBuilder {
public:
// Frame represents an individual sampled stack frame with module information.
struct Frame {
Frame(uintptr_t instruction_pointer, size_t module_index);
~Frame();
// Default constructor to satisfy IPC macros. Do not use explicitly.
Frame();
// The sampled instruction pointer within the function.
uintptr_t instruction_pointer;
// Index of the module in the associated vector of mofules. We don't
// represent module state directly here to save space.
size_t module_index;
};
// Sample represents a set of stack frames with some extra information.
struct Sample {
Sample();
Sample(const Sample& sample);
~Sample();
// These constructors are used only during testing.
Sample(const Frame& frame);
Sample(const std::vector<Frame>& frames);
// The entire stack frame when the sample is taken.
std::vector<Frame> frames;
// A bit-field indicating which process milestones have passed. This can be
// used to tell where in the process lifetime the samples are taken. Just
// as a "lifetime" can only move forward, these bits mark the milestones of
// the processes life as they occur. Bits can be set but never reset. The
// actual definition of the individual bits is left to the user of this
// module.
uint32_t process_milestones = 0;
};
// These milestones of a process lifetime can be passed as process "mile-
// stones" to LegacyCallStackProfileBuilder::SetProcessMilestone(). Be sure to
// update the translation constants at the top of the .cc file when this is
// changed.
enum Milestones : int {
MAIN_LOOP_START,
MAIN_NAVIGATION_START,
MAIN_NAVIGATION_FINISHED,
FIRST_NONEMPTY_PAINT,
SHUTDOWN_START,
MILESTONES_MAX_VALUE
};
// |completed_callback| is made when sampling a profile completes. Other
// threads, including the UI thread, may block on callback completion so this
// should run as quickly as possible.
//
// IMPORTANT NOTE: The callback is invoked on a thread the profiler
// constructs, rather than on the thread used to construct the profiler, and
// thus the callback must be callable on any thread.
explicit LegacyCallStackProfileBuilder(
const CallStackProfileParams& profile_params,
base::OnceClosure completed_callback = base::OnceClosure());
~LegacyCallStackProfileBuilder() override;
// base::StackSamplingProfiler::ProfileBuilder:
void RecordAnnotations() override;
void OnSampleCompleted(
std::vector<base::StackSamplingProfiler::Frame> frames) override;
void OnProfileCompleted(base::TimeDelta profile_duration,
base::TimeDelta sampling_period) override;
// The function is used by sampling heap profiler. Its samples already come
// with different counts.
void OnSampleCompleted(std::vector<base::StackSamplingProfiler::Frame> frames,
size_t count);
// Sets the callback to use for reporting browser process profiles. This
// indirection is required to avoid a dependency on unnecessary metrics code
// in child processes.
static void SetBrowserProcessReceiverCallback(
const base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>&
callback);
// Sets the current system state that is recorded with each captured stack
// frame. This is thread-safe so can be called from anywhere. The parameter
// value should be from an enumeration of the appropriate type with values
// ranging from 0 to 31, inclusive. This sets bits within Sample field of
// |process_milestones|. The actual meanings of these bits are defined
// (globally) by the caller(s).
static void SetProcessMilestone(int milestone);
// Sets the CallStackProfileCollector interface from |browser_interface|.
// This function must be called within child processes.
static void SetParentProfileCollectorForChildProcess(
metrics::mojom::CallStackProfileCollectorPtr browser_interface);
protected:
// Test seam.
virtual void PassProfilesToMetricsProvider(SampledProfile sampled_profile);
private:
// The collected stack samples in proto buffer message format.
CallStackProfile proto_profile_;
// The current sample being recorded.
Sample sample_;
// The indexes of samples, indexed by the sample.
std::map<Sample, int> sample_index_;
// The indexes of modules, indexed by module's base_address.
std::map<uintptr_t, size_t> module_index_;
// The distinct modules in the current profile.
std::vector<base::ModuleCache::Module> modules_;
// The process milestones of a previous sample.
uint32_t milestones_ = 0;
// Callback made when sampling a profile completes.
base::OnceClosure completed_callback_;
// The parameters associated with the sampled profile.
const CallStackProfileParams profile_params_;
// The start time of a profile collection.
const base::TimeTicks profile_start_time_;
DISALLOW_COPY_AND_ASSIGN(LegacyCallStackProfileBuilder);
};
} // namespace metrics
#endif // COMPONENTS_METRICS_LEGACY_CALL_STACK_PROFILE_BUILDER_H_
// Copyright 2018 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/legacy_call_stack_profile_builder.h"
#include "base/files/file_path.h"
#include "base/sampling_heap_profiler/module_cache.h"
#include "base/test/bind_test_util.h"
#include "base/test/mock_callback.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "components/metrics/call_stack_profile_params.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/metrics_proto/sampled_profile.pb.h"
using Frame = base::StackSamplingProfiler::Frame;
using Module = base::ModuleCache::Module;
namespace metrics {
namespace {
constexpr CallStackProfileParams kProfileParams = {
CallStackProfileParams::BROWSER_PROCESS,
CallStackProfileParams::MAIN_THREAD,
CallStackProfileParams::PROCESS_STARTUP};
class TestingLegacyCallStackProfileBuilder
: public LegacyCallStackProfileBuilder {
public:
TestingLegacyCallStackProfileBuilder(
const CallStackProfileParams& profile_params,
base::OnceClosure completed_callback = base::OnceClosure());
~TestingLegacyCallStackProfileBuilder() override;
const SampledProfile& sampled_profile() { return sampled_profile_; }
protected:
// Overridden for testing.
void PassProfilesToMetricsProvider(SampledProfile sampled_profile) override;
private:
// The completed profile.
SampledProfile sampled_profile_;
};
TestingLegacyCallStackProfileBuilder::TestingLegacyCallStackProfileBuilder(
const CallStackProfileParams& profile_params,
base::OnceClosure completed_callback)
: LegacyCallStackProfileBuilder(profile_params,
std::move(completed_callback)) {}
TestingLegacyCallStackProfileBuilder::~TestingLegacyCallStackProfileBuilder() =
default;
void TestingLegacyCallStackProfileBuilder::PassProfilesToMetricsProvider(
SampledProfile sampled_profile) {
sampled_profile_ = std::move(sampled_profile);
}
} // namespace
TEST(LegacyCallStackProfileBuilderTest, SetProcessMilestone) {
auto profile_builder =
std::make_unique<TestingLegacyCallStackProfileBuilder>(kProfileParams);
// The default milestone is 0.
profile_builder->RecordAnnotations();
profile_builder->OnSampleCompleted(std::vector<Frame>());
LegacyCallStackProfileBuilder::SetProcessMilestone(1);
profile_builder->RecordAnnotations();
profile_builder->OnSampleCompleted(std::vector<Frame>());
profile_builder->OnProfileCompleted(base::TimeDelta(), base::TimeDelta());
const SampledProfile& proto = profile_builder->sampled_profile();
ASSERT_TRUE(proto.has_call_stack_profile());
const CallStackProfile& profile = proto.call_stack_profile();
ASSERT_EQ(2, profile.deprecated_sample_size());
uint32_t process_milestones = 0;
for (int i = 0; i < profile.deprecated_sample(0).process_phase().size(); ++i)
process_milestones |=
1U << profile.deprecated_sample(0).process_phase().Get(i);
EXPECT_EQ(0U, process_milestones);
process_milestones = 0;
for (int i = 0; i < profile.deprecated_sample(1).process_phase().size(); ++i)
process_milestones |=
1U << profile.deprecated_sample(1).process_phase().Get(i);
EXPECT_EQ(1U << 1, process_milestones);
}
TEST(LegacyCallStackProfileBuilderTest, ProfilingCompleted) {
// Set up a mock completed callback which will be run once.
base::MockCallback<base::OnceClosure> mock_closure;
EXPECT_CALL(mock_closure, Run()).Times(1);
auto profile_builder = std::make_unique<TestingLegacyCallStackProfileBuilder>(
kProfileParams, mock_closure.Get());
#if defined(OS_WIN)
uint64_t module_md5 = 0x46C3E4166659AC02ULL;
base::FilePath module_path(L"c:\\some\\path\\to\\chrome.exe");
#else
uint64_t module_md5 = 0x554838A8451AC36CULL;
base::FilePath module_path("/some/path/to/chrome");
#endif
const uintptr_t module_base_address1 = 0x1000;
Module module1 = {module_base_address1, "1", module_path};
Frame frame1 = {module_base_address1 + 0x10, module1};
const uintptr_t module_base_address2 = 0x1100;
Module module2 = {module_base_address2, "2", module_path};
Frame frame2 = {module_base_address2 + 0x10, module2};
const uintptr_t module_base_address3 = 0x1010;
Module module3 = {module_base_address3, "3", module_path};
Frame frame3 = {module_base_address3 + 0x10, module3};
std::vector<Frame> frames1 = {frame1, frame2};
std::vector<Frame> frames2 = {frame3};
profile_builder->OnSampleCompleted(frames1);
profile_builder->OnSampleCompleted(frames2);
profile_builder->OnProfileCompleted(base::TimeDelta::FromMilliseconds(500),
base::TimeDelta::FromMilliseconds(100));
const SampledProfile& proto = profile_builder->sampled_profile();
ASSERT_TRUE(proto.has_process());
ASSERT_EQ(BROWSER_PROCESS, proto.process());
ASSERT_TRUE(proto.has_thread());
ASSERT_EQ(MAIN_THREAD, proto.thread());
ASSERT_TRUE(proto.has_trigger_event());
ASSERT_EQ(SampledProfile::PROCESS_STARTUP, proto.trigger_event());
ASSERT_TRUE(proto.has_call_stack_profile());
const CallStackProfile& profile = proto.call_stack_profile();
ASSERT_EQ(2, profile.deprecated_sample_size());
ASSERT_EQ(2, profile.deprecated_sample(0).frame_size());
ASSERT_TRUE(profile.deprecated_sample(0).frame(0).has_module_id_index());
EXPECT_EQ(0, profile.deprecated_sample(0).frame(0).module_id_index());
ASSERT_TRUE(profile.deprecated_sample(0).frame(1).has_module_id_index());
EXPECT_EQ(1, profile.deprecated_sample(0).frame(1).module_id_index());
ASSERT_EQ(1, profile.deprecated_sample(1).frame_size());
ASSERT_TRUE(profile.deprecated_sample(1).frame(0).has_module_id_index());
EXPECT_EQ(2, profile.deprecated_sample(1).frame(0).module_id_index());
ASSERT_EQ(3, profile.module_id().size());
ASSERT_TRUE(profile.module_id(0).has_build_id());
ASSERT_EQ("1", profile.module_id(0).build_id());
ASSERT_TRUE(profile.module_id(0).has_name_md5_prefix());
ASSERT_EQ(module_md5, profile.module_id(0).name_md5_prefix());
ASSERT_TRUE(profile.module_id(1).has_build_id());
ASSERT_EQ("2", profile.module_id(1).build_id());
ASSERT_TRUE(profile.module_id(1).has_name_md5_prefix());
ASSERT_EQ(module_md5, profile.module_id(1).name_md5_prefix());
ASSERT_TRUE(profile.module_id(2).has_build_id());
ASSERT_EQ("3", profile.module_id(2).build_id());
ASSERT_TRUE(profile.module_id(2).has_name_md5_prefix());
ASSERT_EQ(module_md5, profile.module_id(2).name_md5_prefix());
ASSERT_TRUE(profile.has_profile_duration_ms());
EXPECT_EQ(500, profile.profile_duration_ms());
ASSERT_TRUE(profile.has_sampling_period_ms());
EXPECT_EQ(100, profile.sampling_period_ms());
}
TEST(LegacyCallStackProfileBuilderTest, SamplesDeduped) {
auto profile_builder =
std::make_unique<TestingLegacyCallStackProfileBuilder>(kProfileParams);
#if defined(OS_WIN)
base::FilePath module_path(L"c:\\some\\path\\to\\chrome.exe");
#else
base::FilePath module_path("/some/path/to/chrome");
#endif
const uintptr_t module_base_address1 = 0x1000;
Module module1 = {module_base_address1, "1", module_path};
Frame frame1 = {module_base_address1 + 0x10, module1};
const uintptr_t module_base_address2 = 0x1100;
Module module2 = {module_base_address2, "2", module_path};
Frame frame2 = {module_base_address2 + 0x10, module2};
std::vector<Frame> frames = {frame1, frame2};
// Two samples are completed with the same frames. They also have the same
// process milestone therefore they are deduped to one.
LegacyCallStackProfileBuilder::SetProcessMilestone(0);
profile_builder->RecordAnnotations();
profile_builder->OnSampleCompleted(frames, 42);
profile_builder->RecordAnnotations();
profile_builder->OnSampleCompleted(frames);
profile_builder->OnProfileCompleted(base::TimeDelta(), base::TimeDelta());
const SampledProfile& proto = profile_builder->sampled_profile();
ASSERT_TRUE(proto.has_process());
ASSERT_EQ(BROWSER_PROCESS, proto.process());
ASSERT_TRUE(proto.has_thread());
ASSERT_EQ(MAIN_THREAD, proto.thread());
ASSERT_TRUE(proto.has_trigger_event());
ASSERT_EQ(SampledProfile::PROCESS_STARTUP, proto.trigger_event());
ASSERT_TRUE(proto.has_call_stack_profile());
ASSERT_EQ(1, proto.call_stack_profile().deprecated_sample_size());
ASSERT_EQ(43, proto.call_stack_profile().deprecated_sample(0).count());
}
TEST(LegacyCallStackProfileBuilderTest, SamplesNotDeduped) {
auto profile_builder =
std::make_unique<TestingLegacyCallStackProfileBuilder>(kProfileParams);
#if defined(OS_WIN)
base::FilePath module_path(L"c:\\some\\path\\to\\chrome.exe");
#else
base::FilePath module_path("/some/path/to/chrome");
#endif
const uintptr_t module_base_address1 = 0x1000;
Module module1 = {module_base_address1, "1", module_path};
Frame frame1 = {module_base_address1 + 0x10, module1};
const uintptr_t module_base_address2 = 0x1100;
Module module2 = {module_base_address2, "2", module_path};
Frame frame2 = {module_base_address2 + 0x10, module2};
std::vector<Frame> frames = {frame1, frame2};
// Two samples are completed with the same frames but different process
// milestones. They are considered as different samples threfore not deduped.
LegacyCallStackProfileBuilder::SetProcessMilestone(2);
profile_builder->RecordAnnotations();
profile_builder->OnSampleCompleted(frames);
LegacyCallStackProfileBuilder::SetProcessMilestone(4);
profile_builder->RecordAnnotations();
profile_builder->OnSampleCompleted(frames);
profile_builder->OnProfileCompleted(base::TimeDelta(), base::TimeDelta());
const SampledProfile& proto = profile_builder->sampled_profile();
ASSERT_TRUE(proto.has_process());
ASSERT_EQ(BROWSER_PROCESS, proto.process());
ASSERT_TRUE(proto.has_thread());
ASSERT_EQ(MAIN_THREAD, proto.thread());
ASSERT_TRUE(proto.has_trigger_event());
ASSERT_EQ(SampledProfile::PROCESS_STARTUP, proto.trigger_event());
ASSERT_TRUE(proto.has_call_stack_profile());
ASSERT_EQ(2, proto.call_stack_profile().deprecated_sample_size());
}
TEST(LegacyCallStackProfileBuilderTest, Modules) {
auto profile_builder =
std::make_unique<TestingLegacyCallStackProfileBuilder>(kProfileParams);
const uintptr_t module_base_address1 = 0x1000;
Module module1; // module1 has no information hence invalid.
Frame frame1 = {module_base_address1 + 0x10, module1};
const uintptr_t module_base_address2 = 0x1100;
#if defined(OS_WIN)
uint64_t module_md5 = 0x46C3E4166659AC02ULL;
base::FilePath module_path(L"c:\\some\\path\\to\\chrome.exe");
#else
uint64_t module_md5 = 0x554838A8451AC36CULL;
base::FilePath module_path("/some/path/to/chrome");
#endif
Module module2 = {module_base_address2, "2", module_path};
Frame frame2 = {module_base_address2 + 0x10, module2};
std::vector<Frame> frames = {frame1, frame2};
profile_builder->OnSampleCompleted(frames);
profile_builder->OnProfileCompleted(base::TimeDelta(), base::TimeDelta());
const SampledProfile& proto = profile_builder->sampled_profile();
ASSERT_TRUE(proto.has_call_stack_profile());
const CallStackProfile& profile = proto.call_stack_profile();
ASSERT_EQ(1, profile.deprecated_sample_size());
ASSERT_EQ(2, profile.deprecated_sample(0).frame_size());
ASSERT_FALSE(profile.deprecated_sample(0).frame(0).has_module_id_index());
ASSERT_FALSE(profile.deprecated_sample(0).frame(0).has_address());
ASSERT_TRUE(profile.deprecated_sample(0).frame(1).has_module_id_index());
EXPECT_EQ(0, profile.deprecated_sample(0).frame(1).module_id_index());
ASSERT_TRUE(profile.deprecated_sample(0).frame(1).has_address());
EXPECT_EQ(0x10ULL, profile.deprecated_sample(0).frame(1).address());
ASSERT_EQ(1, profile.module_id().size());
ASSERT_TRUE(profile.module_id(0).has_build_id());
ASSERT_EQ("2", profile.module_id(0).build_id());
ASSERT_TRUE(profile.module_id(0).has_name_md5_prefix());
ASSERT_EQ(module_md5, profile.module_id(0).name_md5_prefix());
}
TEST(LegacyCallStackProfileBuilderTest, DedupModules) {
auto profile_builder =
std::make_unique<TestingLegacyCallStackProfileBuilder>(kProfileParams);
const uintptr_t module_base_address = 0x1000;
#if defined(OS_WIN)
uint64_t module_md5 = 0x46C3E4166659AC02ULL;
base::FilePath module_path(L"c:\\some\\path\\to\\chrome.exe");
#else
uint64_t module_md5 = 0x554838A8451AC36CULL;
base::FilePath module_path("/some/path/to/chrome");
#endif
Module module1 = {module_base_address, "1", module_path};
Frame frame1 = {module_base_address + 0x10, module1};
Module module2 = {module_base_address, "1", module_path};
Frame frame2 = {module_base_address + 0x20, module2};
std::vector<Frame> frames = {frame1, frame2};
profile_builder->OnSampleCompleted(frames);
profile_builder->OnProfileCompleted(base::TimeDelta(), base::TimeDelta());
const SampledProfile& proto = profile_builder->sampled_profile();
ASSERT_TRUE(proto.has_call_stack_profile());
const CallStackProfile& profile = proto.call_stack_profile();
ASSERT_EQ(1, profile.deprecated_sample_size());
ASSERT_EQ(2, profile.deprecated_sample(0).frame_size());
// Since module1 and module2 have the same base address, they are considered
// the same module and therefore deduped.
ASSERT_TRUE(profile.deprecated_sample(0).frame(0).has_module_id_index());
EXPECT_EQ(0, profile.deprecated_sample(0).frame(0).module_id_index());
ASSERT_TRUE(profile.deprecated_sample(0).frame(0).has_address());
EXPECT_EQ(0x10ULL, profile.deprecated_sample(0).frame(0).address());
ASSERT_TRUE(profile.deprecated_sample(0).frame(1).has_module_id_index());
EXPECT_EQ(0, profile.deprecated_sample(0).frame(1).module_id_index());
ASSERT_TRUE(profile.deprecated_sample(0).frame(1).has_address());
EXPECT_EQ(0x20ULL, profile.deprecated_sample(0).frame(1).address());
ASSERT_EQ(1, profile.module_id().size());
ASSERT_TRUE(profile.module_id(0).has_build_id());
ASSERT_EQ("1", profile.module_id(0).build_id());
ASSERT_TRUE(profile.module_id(0).has_name_md5_prefix());
ASSERT_EQ(module_md5, profile.module_id(0).name_md5_prefix());
}
} // namespace metrics
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
#include "base/threading/platform_thread.h" #include "base/threading/platform_thread.h"
#include "base/trace_event/trace_event.h" #include "base/trace_event/trace_event.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "components/metrics/call_stack_profile_builder.h" #include "components/metrics/legacy_call_stack_profile_builder.h"
#include "components/prefs/pref_registry_simple.h" #include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h" #include "components/prefs/pref_service.h"
#include "components/startup_metric_utils/browser/pref_names.h" #include "components/startup_metric_utils/browser/pref_names.h"
...@@ -579,8 +579,8 @@ void RecordBrowserMainMessageLoopStart(base::TimeTicks ticks, ...@@ -579,8 +579,8 @@ void RecordBrowserMainMessageLoopStart(base::TimeTicks ticks,
RecordHardFaultHistogram(); RecordHardFaultHistogram();
// Record timing of the browser message-loop start time. // Record timing of the browser message-loop start time.
metrics::CallStackProfileBuilder::SetProcessMilestone( metrics::LegacyCallStackProfileBuilder::SetProcessMilestone(
metrics::CallStackProfileBuilder::MAIN_LOOP_START); metrics::LegacyCallStackProfileBuilder::MAIN_LOOP_START);
if (!is_first_run && !g_process_creation_ticks.is_null()) { if (!is_first_run && !g_process_creation_ticks.is_null()) {
UMA_HISTOGRAM_AND_TRACE_WITH_TEMPERATURE_AND_SAME_VERSION_COUNT( UMA_HISTOGRAM_AND_TRACE_WITH_TEMPERATURE_AND_SAME_VERSION_COUNT(
UMA_HISTOGRAM_LONG_TIMES_100, "Startup.BrowserMessageLoopStartTime", UMA_HISTOGRAM_LONG_TIMES_100, "Startup.BrowserMessageLoopStartTime",
...@@ -702,8 +702,8 @@ void RecordFirstWebContentsNonEmptyPaint( ...@@ -702,8 +702,8 @@ void RecordFirstWebContentsNonEmptyPaint(
if (!ShouldLogStartupHistogram()) if (!ShouldLogStartupHistogram())
return; return;
metrics::CallStackProfileBuilder::SetProcessMilestone( metrics::LegacyCallStackProfileBuilder::SetProcessMilestone(
metrics::CallStackProfileBuilder::FIRST_NONEMPTY_PAINT); metrics::LegacyCallStackProfileBuilder::FIRST_NONEMPTY_PAINT);
UMA_HISTOGRAM_AND_TRACE_WITH_TEMPERATURE_AND_SAME_VERSION_COUNT( UMA_HISTOGRAM_AND_TRACE_WITH_TEMPERATURE_AND_SAME_VERSION_COUNT(
UMA_HISTOGRAM_LONG_TIMES_100, "Startup.FirstWebContents.NonEmptyPaint2", UMA_HISTOGRAM_LONG_TIMES_100, "Startup.FirstWebContents.NonEmptyPaint2",
g_process_creation_ticks, now); g_process_creation_ticks, now);
...@@ -727,8 +727,8 @@ void RecordFirstWebContentsMainNavigationStart(base::TimeTicks ticks, ...@@ -727,8 +727,8 @@ void RecordFirstWebContentsMainNavigationStart(base::TimeTicks ticks,
if (!ShouldLogStartupHistogram()) if (!ShouldLogStartupHistogram())
return; return;
metrics::CallStackProfileBuilder::SetProcessMilestone( metrics::LegacyCallStackProfileBuilder::SetProcessMilestone(
metrics::CallStackProfileBuilder::MAIN_NAVIGATION_START); metrics::LegacyCallStackProfileBuilder::MAIN_NAVIGATION_START);
UMA_HISTOGRAM_AND_TRACE_WITH_TEMPERATURE_AND_SAME_VERSION_COUNT( UMA_HISTOGRAM_AND_TRACE_WITH_TEMPERATURE_AND_SAME_VERSION_COUNT(
UMA_HISTOGRAM_LONG_TIMES_100, UMA_HISTOGRAM_LONG_TIMES_100,
"Startup.FirstWebContents.MainNavigationStart", g_process_creation_ticks, "Startup.FirstWebContents.MainNavigationStart", g_process_creation_ticks,
...@@ -758,8 +758,8 @@ void RecordFirstWebContentsMainNavigationFinished(base::TimeTicks ticks) { ...@@ -758,8 +758,8 @@ void RecordFirstWebContentsMainNavigationFinished(base::TimeTicks ticks) {
if (!ShouldLogStartupHistogram()) if (!ShouldLogStartupHistogram())
return; return;
metrics::CallStackProfileBuilder::SetProcessMilestone( metrics::LegacyCallStackProfileBuilder::SetProcessMilestone(
metrics::CallStackProfileBuilder::MAIN_NAVIGATION_FINISHED); metrics::LegacyCallStackProfileBuilder::MAIN_NAVIGATION_FINISHED);
UMA_HISTOGRAM_AND_TRACE_WITH_TEMPERATURE_AND_SAME_VERSION_COUNT( UMA_HISTOGRAM_AND_TRACE_WITH_TEMPERATURE_AND_SAME_VERSION_COUNT(
UMA_HISTOGRAM_LONG_TIMES_100, UMA_HISTOGRAM_LONG_TIMES_100,
"Startup.FirstWebContents.MainNavigationFinished", "Startup.FirstWebContents.MainNavigationFinished",
......
...@@ -22,8 +22,6 @@ namespace { ...@@ -22,8 +22,6 @@ namespace {
class TracingProfileBuilder class TracingProfileBuilder
: public base::StackSamplingProfiler::ProfileBuilder { : public base::StackSamplingProfiler::ProfileBuilder {
public: public:
void RecordAnnotations() override {}
void OnSampleCompleted( void OnSampleCompleted(
std::vector<base::StackSamplingProfiler::Frame> frames) override { std::vector<base::StackSamplingProfiler::Frame> frames) override {
if (frames.empty()) if (frames.empty())
......
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