Commit cb1067cf authored by Mike Wittman's avatar Mike Wittman Committed by Commit Bot

[Sampling profiler] Reland record continued_work sample metadata for Windows

Records work ids along with samples to distinguish which samples came
from which work items (i.e. tasks, user interface event handlers, etc.)
This information will be used to infer coarse work item durations and
from that information, jankiness.

Task id recording takes place while the thread is suspended so must
be implemented in a lockless fashion.

The corresponding Mac implementation will be much more involved so is
deferred to a future CL.

This is a reland of 721cd487.

Change-Id: Ife21309d469ab7ffae3cd41ea069cfafb3747dd7
Reviewed-on: https://chromium-review.googlesource.com/c/1417835
Commit-Queue: Mike Wittman <wittman@chromium.org>
Reviewed-by: default avatarFrançois Doray <fdoray@chromium.org>
Cr-Commit-Position: refs/heads/master@{#625755}
parent f701c423
......@@ -469,6 +469,8 @@ jumbo_component("base") {
"message_loop/pending_task_queue.h",
"message_loop/sequenced_task_source.h",
"message_loop/timer_slack.h",
"message_loop/work_id_provider.cc",
"message_loop/work_id_provider.h",
"metrics/bucket_ranges.cc",
"metrics/bucket_ranges.h",
"metrics/dummy_histogram.cc",
......@@ -2393,6 +2395,7 @@ test("base_unittests") {
"message_loop/message_pump_io_ios_unittest.cc",
"message_loop/message_pump_mac_unittest.mm",
"message_loop/message_pump_unittest.cc",
"message_loop/work_id_provider_unittest.cc",
"metrics/bucket_ranges_unittest.cc",
"metrics/field_trial_memory_mac_unittest.cc",
"metrics/field_trial_params_unittest.cc",
......
......@@ -215,6 +215,7 @@ void MessageLoopImpl::BindToCurrentThread(std::unique_ptr<MessagePump> pump) {
message_loop_controller_->StartScheduling();
SetThreadTaskRunnerHandle();
thread_id_ = PlatformThread::CurrentId();
work_id_provider_ = WorkIdProvider::GetForCurrentThread();
scoped_set_sequence_local_storage_map_for_current_thread_ = std::make_unique<
internal::ScopedSetSequenceLocalStorageMapForCurrentThread>(
......@@ -342,6 +343,8 @@ bool MessageLoopImpl::ProcessNextDelayedNonNestableTask() {
void MessageLoopImpl::RunTask(PendingTask* pending_task) {
DCHECK(task_execution_allowed_);
work_id_provider_->IncrementWorkId();
// Execute the task and assume the worst: It is probably not reentrant.
task_execution_allowed_ = false;
......@@ -429,6 +432,10 @@ TimeTicks MessageLoopImpl::CapAtOneDay(TimeTicks next_run_time) {
return std::min(next_run_time, recent_time_ + TimeDelta::FromDays(1));
}
void MessageLoopImpl::BeforeDoInternalWork() {
work_id_provider_->IncrementWorkId();
}
bool MessageLoopImpl::DoWork() {
if (!task_execution_allowed_)
return false;
......
......@@ -18,6 +18,7 @@
#include "base/message_loop/message_pump.h"
#include "base/message_loop/pending_task_queue.h"
#include "base/message_loop/timer_slack.h"
#include "base/message_loop/work_id_provider.h"
#include "base/observer_list.h"
#include "base/pending_task.h"
#include "base/run_loop.h"
......@@ -135,6 +136,7 @@ class BASE_EXPORT MessageLoopImpl : public MessageLoopBase,
TimeTicks CapAtOneDay(TimeTicks next_run_time);
// MessagePump::Delegate methods:
void BeforeDoInternalWork() override;
bool DoWork() override;
bool DoDelayedWork(TimeTicks* next_delayed_work_time) override;
bool DoIdleWork() override;
......@@ -200,6 +202,11 @@ class BASE_EXPORT MessageLoopImpl : public MessageLoopBase,
std::unique_ptr<internal::ScopedSetSequenceLocalStorageMapForCurrentThread>
scoped_set_sequence_local_storage_map_for_current_thread_;
// Non-null provider of id state for identifying distinct work items executed
// by the message loop (task, event, etc.). Cached on the class to avoid TLS
// lookups on task execution.
WorkIdProvider* work_id_provider_ = nullptr;
ObserverList<DestructionObserver>::Unchecked destruction_observers_;
// Verifies that calls are made on the thread on which BindToCurrentThread()
......
......@@ -6,6 +6,8 @@
namespace base {
void MessagePump::Delegate::BeforeDoInternalWork() {}
MessagePump::MessagePump() = default;
MessagePump::~MessagePump() = default;
......
......@@ -21,6 +21,9 @@ class BASE_EXPORT MessagePump {
public:
virtual ~Delegate() = default;
// Called before work performed internal to the message pump is executed.
virtual void BeforeDoInternalWork();
// Called from within Run in response to ScheduleWork or when the message
// pump would otherwise call DoDelayedWork. Returns true to indicate that
// work was done. DoDelayedWork will still be called if DoWork returns
......
......@@ -172,6 +172,7 @@ void MessagePumpForUI::DoRunLoop() {
// work, then it is a good time to consider sleeping (waiting) for more
// work.
state_->delegate->BeforeDoInternalWork();
bool more_work_is_plausible = ProcessNextWindowsMessage();
if (state_->should_quit)
break;
......
// Copyright 2019 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 "base/message_loop/work_id_provider.h"
#include <memory>
#include "base/memory/ptr_util.h"
#include "base/no_destructor.h"
#include "base/threading/thread_local.h"
namespace base {
// static
WorkIdProvider* WorkIdProvider::GetForCurrentThread() {
static NoDestructor<ThreadLocalOwnedPointer<WorkIdProvider>> instance;
if (!instance->Get())
instance->Set(WrapUnique(new WorkIdProvider));
return instance->Get();
}
// This function must support being invoked while other threads are suspended so
// must not take any locks, including indirectly through use of heap allocation,
// LOG, CHECK, or DCHECK.
unsigned int WorkIdProvider::GetWorkId() {
return work_id_.load(std::memory_order_acquire);
}
WorkIdProvider::~WorkIdProvider() = default;
void WorkIdProvider::SetCurrentWorkIdForTesting(unsigned int id) {
work_id_.store(id, std::memory_order_relaxed);
}
void WorkIdProvider::IncrementWorkIdForTesting() {
IncrementWorkId();
}
WorkIdProvider::WorkIdProvider() : work_id_(0) {}
void WorkIdProvider::IncrementWorkId() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
unsigned int next_id = work_id_.load(std::memory_order_relaxed) + 1;
// Reserve 0 to mean no work items have been executed.
if (next_id == 0)
++next_id;
// Release order ensures this state is visible to other threads prior to the
// following task/event execution.
work_id_.store(next_id, std::memory_order_release);
}
} // namespace base
// Copyright 2019 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 BASE_MESSAGE_LOOP_WORK_ID_PROVIDER_H_
#define BASE_MESSAGE_LOOP_WORK_ID_PROVIDER_H_
#include <atomic>
#include "base/base_export.h"
#include "base/threading/thread_checker.h"
namespace base {
class MessageLoopImpl;
namespace sequence_manager {
namespace internal {
class SequenceManagerImpl;
}
} // namespace sequence_manager
// WorkIdProvider associates with the current thread (via TLS) an id state
// reflecting the current work item being executed by the message loop. The item
// is accessed lock-free from other threads to provide a snapshot of the
// currently-executing work item.
//
// The expected user is the ThreadProfiler which samples the id along with the
// thread's stack to identify cases where the same task spans multiple
// samples. The state is stored in TLS rather than on the MessageLoop or the
// ThreadProfiler because the lifetime relationship between the two classes
// varies depending on which thread is being profiled, plus the fact that
// MessageLoop doesn't have a well-defined creation point/owner on some threads.
class BASE_EXPORT WorkIdProvider {
public:
// Returns the WorkIdProvider for the current thread. Allocates a
// WorkIdProvider in TLS if not already present.
static WorkIdProvider* GetForCurrentThread();
// Returns the work id for the thread to which this WorkIdProvider
// belongs. The work id is 0 before calls to IncrementWorkId() and always
// non-zero after that point. The implementation supports being invoked while
// other threads are suspended, and thus is guaranteed to take no locks,
// directly or indirectly. May be called from any thread, as long as the
// owning thread hasn't been destroyed.
unsigned int GetWorkId();
// Public to support unique_ptr<WorkIdProvider>.
~WorkIdProvider();
void SetCurrentWorkIdForTesting(unsigned int id);
void IncrementWorkIdForTesting();
WorkIdProvider(const WorkIdProvider&) = delete;
WorkIdProvider& operator=(const WorkIdProvider&) = delete;
private:
// Friended to allow use of IncrementWorkId().
friend class MessageLoopImpl;
friend class sequence_manager::internal::SequenceManagerImpl;
WorkIdProvider();
void IncrementWorkId();
std::atomic_uint work_id_;
THREAD_CHECKER(thread_checker_);
};
} // namespace base
#endif // BASE_MESSAGE_LOOP_WORK_ID_PROVIDER_H_
// Copyright 2019 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 "base/message_loop/work_id_provider.h"
#include <limits>
#include <memory>
#include <utility>
#include "base/callback.h"
#include "base/test/bind_test_util.h"
#include "base/threading/simple_thread.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
namespace {
class TestThread : public SimpleThread {
public:
TestThread(OnceCallback<void(WorkIdProvider*)> validate_func)
: SimpleThread("WorkIdProviderTestThread"),
validate_func_(std::move(validate_func)) {}
void Run() override {
std::move(validate_func_).Run(WorkIdProvider::GetForCurrentThread());
}
private:
OnceCallback<void(WorkIdProvider*)> validate_func_;
};
template <class Func>
void RunTest(const Func& func) {
TestThread thread(BindLambdaForTesting(func));
thread.Start();
thread.Join();
}
} // namespace
TEST(WorkIdProviderTest, StartsAtZero) {
RunTest(
[](WorkIdProvider* provider) { EXPECT_EQ(0u, provider->GetWorkId()); });
}
TEST(WorkIdProviderTest, Increment) {
RunTest([](WorkIdProvider* provider) {
provider->IncrementWorkIdForTesting();
EXPECT_EQ(1u, provider->GetWorkId());
provider->IncrementWorkIdForTesting();
EXPECT_EQ(2u, provider->GetWorkId());
provider->IncrementWorkIdForTesting();
EXPECT_EQ(3u, provider->GetWorkId());
});
}
TEST(WorkIdProviderTest, SkipsZeroOnOverflow) {
RunTest([](WorkIdProvider* provider) {
provider->SetCurrentWorkIdForTesting(
std::numeric_limits<unsigned int>::max());
provider->IncrementWorkIdForTesting();
EXPECT_EQ(1u, provider->GetWorkId());
});
}
} // namespace base
......@@ -218,6 +218,7 @@ void SequenceManagerImpl::BindToCurrentThread(
void SequenceManagerImpl::CompleteInitializationOnBoundThread() {
controller_->AddNestingObserver(this);
work_id_provider_ = WorkIdProvider::GetForCurrentThread();
main_thread_only().nesting_observer_registered_ = true;
if (GetMessagePump())
MessageLoopCurrent::BindToCurrentThreadInternal(this);
......@@ -438,6 +439,8 @@ Optional<PendingTask> SequenceManagerImpl::TakeTask() {
executing_task.task_queue->GetName(), "task_type",
executing_task.task_type);
work_id_provider_->IncrementWorkId();
return task;
}
......
......@@ -22,6 +22,7 @@
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/message_loop/work_id_provider.h"
#include "base/pending_task.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
......@@ -392,6 +393,11 @@ class BASE_EXPORT SequenceManagerImpl
// Whether to add the queue time to tasks.
base::subtle::Atomic32 add_queue_time_to_tasks_ = 0;
// Non-null provider of id state for identifying distinct work items executed
// by the message loop (task, event, etc.). Cached on the class to avoid TLS
// lookups on task execution.
WorkIdProvider* work_id_provider_ = nullptr;
// A check to bail out early during memory corruption.
// https://crbug.com/757940
bool Validate();
......
......@@ -10,6 +10,7 @@
#include "base/bind.h"
#include "base/command_line.h"
#include "base/lazy_instance.h"
#include "base/message_loop/work_id_provider.h"
#include "base/rand_util.h"
#include "base/threading/platform_thread.h"
#include "base/threading/sequence_local_storage_slot.h"
......@@ -65,14 +66,6 @@ CallStackProfileParams::Process GetProcess() {
return CallStackProfileParams::UNKNOWN_PROCESS;
}
std::unique_ptr<base::StackSamplingProfiler::ProfileBuilder>
CreateProfileBuilder(
const CallStackProfileParams& params,
base::OnceClosure completed_callback = base::OnceClosure()) {
return std::make_unique<CallStackProfileBuilder>(
params, std::move(completed_callback));
}
} // namespace
// The scheduler works by splitting execution time into repeated periods such
......@@ -124,7 +117,26 @@ base::TimeTicks PeriodicSamplingScheduler::Now() const {
return base::TimeTicks::Now();
}
ThreadProfiler::~ThreadProfiler() {}
// Records the current unique id for the work item being executed in the target
// thread's message loop.
class ThreadProfiler::WorkIdRecorder : public metrics::WorkIdRecorder {
public:
explicit WorkIdRecorder(base::WorkIdProvider* work_id_provider)
: work_id_provider_(work_id_provider) {}
// Invoked on the profiler thread while the target thread is suspended.
unsigned int RecordWorkId() const override {
return work_id_provider_->GetWorkId();
}
WorkIdRecorder(const WorkIdRecorder&) = delete;
WorkIdRecorder& operator=(const WorkIdRecorder&) = delete;
private:
base::WorkIdProvider* const work_id_provider_;
};
ThreadProfiler::~ThreadProfiler() = default;
// static
std::unique_ptr<ThreadProfiler> ThreadProfiler::CreateAndStartOnMainThread() {
......@@ -201,14 +213,18 @@ ThreadProfiler::ThreadProfiler(
scoped_refptr<base::SingleThreadTaskRunner> owning_thread_task_runner)
: thread_(thread),
owning_thread_task_runner_(owning_thread_task_runner),
work_id_recorder_(std::make_unique<WorkIdRecorder>(
base::WorkIdProvider::GetForCurrentThread())),
weak_factory_(this) {
if (!StackSamplingConfiguration::Get()->IsProfilerEnabledForCurrentProcess())
return;
startup_profiler_ = std::make_unique<StackSamplingProfiler>(
base::PlatformThread::CurrentId(), kSamplingParams,
CreateProfileBuilder(CallStackProfileParams(
GetProcess(), thread, CallStackProfileParams::PROCESS_STARTUP)));
std::make_unique<CallStackProfileBuilder>(
CallStackProfileParams(GetProcess(), thread,
CallStackProfileParams::PROCESS_STARTUP),
work_id_recorder_.get()));
startup_profiler_->Start();
......@@ -251,9 +267,10 @@ void ThreadProfiler::StartPeriodicSamplingCollection() {
// NB: Destroys the previous profiler as side effect.
periodic_profiler_ = std::make_unique<StackSamplingProfiler>(
base::PlatformThread::CurrentId(), kSamplingParams,
CreateProfileBuilder(
std::make_unique<CallStackProfileBuilder>(
CallStackProfileParams(GetProcess(), thread_,
CallStackProfileParams::PERIODIC_COLLECTION),
work_id_recorder_.get(),
base::BindOnce(&ThreadProfiler::OnPeriodicCollectionCompleted,
owning_thread_task_runner_,
weak_factory_.GetWeakPtr())));
......
......@@ -91,6 +91,8 @@ class ThreadProfiler {
service_manager::Connector* connector);
private:
class WorkIdRecorder;
// Creates the profiler. The task runner will be supplied for child threads
// but not for main threads.
ThreadProfiler(
......@@ -114,6 +116,8 @@ class ThreadProfiler {
scoped_refptr<base::SingleThreadTaskRunner> owning_thread_task_runner_;
std::unique_ptr<WorkIdRecorder> work_id_recorder_;
std::unique_ptr<base::StackSamplingProfiler> startup_profiler_;
std::unique_ptr<base::StackSamplingProfiler> periodic_profiler_;
......
......@@ -47,8 +47,10 @@ uint64_t HashModuleFilename(const base::FilePath& filename) {
CallStackProfileBuilder::CallStackProfileBuilder(
const CallStackProfileParams& profile_params,
const WorkIdRecorder* work_id_recorder,
base::OnceClosure completed_callback)
: profile_start_time_(base::TimeTicks::Now()) {
: work_id_recorder_(work_id_recorder),
profile_start_time_(base::TimeTicks::Now()) {
completed_callback_ = std::move(completed_callback);
sampled_profile_.set_process(
ToExecutionContextProcess(profile_params.process));
......@@ -59,7 +61,20 @@ CallStackProfileBuilder::CallStackProfileBuilder(
CallStackProfileBuilder::~CallStackProfileBuilder() = default;
// static
// This function is invoked on the profiler thread while the target thread is
// suspended so must not take any locks, including indirectly through use of
// heap allocation, LOG, CHECK, or DCHECK.
void CallStackProfileBuilder::RecordMetadata() {
if (!work_id_recorder_)
return;
unsigned int work_id = work_id_recorder_->RecordWorkId();
// A work id of 0 indicates that the message loop has not yet started.
if (work_id == 0)
return;
is_continued_work_ = (last_work_id_ == work_id);
last_work_id_ = work_id;
}
void CallStackProfileBuilder::OnSampleCompleted(
std::vector<base::StackSamplingProfiler::Frame> frames) {
// Write CallStackProfile::Stack protobuf message.
......@@ -110,6 +125,8 @@ void CallStackProfileBuilder::OnSampleCompleted(
CallStackProfile::StackSample* stack_sample_proto =
call_stack_profile->add_stack_sample();
stack_sample_proto->set_stack_index(stack_loc->second);
if (is_continued_work_)
stack_sample_proto->set_continued_work(is_continued_work_);
}
void CallStackProfileBuilder::OnProfileCompleted(
......
......@@ -5,6 +5,7 @@
#ifndef COMPONENTS_METRICS_CALL_STACK_PROFILE_BUILDER_H_
#define COMPONENTS_METRICS_CALL_STACK_PROFILE_BUILDER_H_
#include <limits>
#include <map>
#include <vector>
......@@ -18,6 +19,23 @@
namespace metrics {
// Interface that allows the CallStackProfileBuilder to provide ids for distinct
// work items. Samples with the same id are tagged as coming from the same work
// item in the recorded samples.
class WorkIdRecorder {
public:
WorkIdRecorder() = default;
virtual ~WorkIdRecorder() = default;
// This function is invoked on the profiler thread while the target thread is
// suspended so must not take any locks, including indirectly through use of
// heap allocation, LOG, CHECK, or DCHECK.
virtual unsigned int RecordWorkId() const = 0;
WorkIdRecorder(const WorkIdRecorder&) = delete;
WorkIdRecorder& operator=(const WorkIdRecorder&) = delete;
};
// 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.
//
......@@ -35,11 +53,13 @@ class CallStackProfileBuilder
// thus the callback must be callable on any thread.
explicit CallStackProfileBuilder(
const CallStackProfileParams& profile_params,
const WorkIdRecorder* work_id_recorder = nullptr,
base::OnceClosure completed_callback = base::OnceClosure());
~CallStackProfileBuilder() override;
// base::StackSamplingProfiler::ProfileBuilder:
void RecordMetadata() override;
void OnSampleCompleted(
std::vector<base::StackSamplingProfiler::Frame> frames) override;
void OnProfileCompleted(base::TimeDelta profile_duration,
......@@ -68,6 +88,10 @@ class CallStackProfileBuilder
const CallStackProfile::Stack* stack2) const;
};
unsigned int last_work_id_ = std::numeric_limits<unsigned int>::max();
bool is_continued_work_ = false;
const WorkIdRecorder* const work_id_recorder_;
// The SampledProfile protobuf message which contains the collected stack
// samples.
SampledProfile sampled_profile_;
......
......@@ -4,6 +4,8 @@
#include "components/metrics/call_stack_profile_builder.h"
#include <memory>
#include "base/files/file_path.h"
#include "base/sampling_heap_profiler/module_cache.h"
#include "base/test/bind_test_util.h"
......@@ -30,6 +32,7 @@ class TestingCallStackProfileBuilder : public CallStackProfileBuilder {
public:
TestingCallStackProfileBuilder(
const CallStackProfileParams& profile_params,
const WorkIdRecorder* work_id_recorder = nullptr,
base::OnceClosure completed_callback = base::OnceClosure());
~TestingCallStackProfileBuilder() override;
......@@ -47,8 +50,11 @@ class TestingCallStackProfileBuilder : public CallStackProfileBuilder {
TestingCallStackProfileBuilder::TestingCallStackProfileBuilder(
const CallStackProfileParams& profile_params,
const WorkIdRecorder* work_id_recorder,
base::OnceClosure completed_callback)
: CallStackProfileBuilder(profile_params, std::move(completed_callback)) {}
: CallStackProfileBuilder(profile_params,
work_id_recorder,
std::move(completed_callback)) {}
TestingCallStackProfileBuilder::~TestingCallStackProfileBuilder() = default;
......@@ -65,7 +71,7 @@ TEST(CallStackProfileBuilderTest, ProfilingCompleted) {
EXPECT_CALL(mock_closure, Run()).Times(1);
auto profile_builder = std::make_unique<TestingCallStackProfileBuilder>(
kProfileParams, mock_closure.Get());
kProfileParams, nullptr, mock_closure.Get());
#if defined(OS_WIN)
uint64_t module_md5 = 0x46C3E4166659AC02ULL;
......@@ -90,7 +96,9 @@ TEST(CallStackProfileBuilderTest, ProfilingCompleted) {
std::vector<Frame> frames1 = {frame1, frame2};
std::vector<Frame> frames2 = {frame3};
profile_builder->RecordMetadata();
profile_builder->OnSampleCompleted(frames1);
profile_builder->RecordMetadata();
profile_builder->OnSampleCompleted(frames2);
profile_builder->OnProfileCompleted(base::TimeDelta::FromMilliseconds(500),
base::TimeDelta::FromMilliseconds(100));
......@@ -133,7 +141,9 @@ TEST(CallStackProfileBuilderTest, ProfilingCompleted) {
ASSERT_EQ(2, profile.stack_sample_size());
EXPECT_EQ(0, profile.stack_sample(0).stack_index());
EXPECT_FALSE(profile.stack_sample(0).has_continued_work());
EXPECT_EQ(1, profile.stack_sample(1).stack_index());
EXPECT_FALSE(profile.stack_sample(1).has_continued_work());
ASSERT_TRUE(profile.has_profile_duration_ms());
EXPECT_EQ(500, profile.profile_duration_ms());
......@@ -163,7 +173,9 @@ TEST(CallStackProfileBuilderTest, StacksDeduped) {
// Two stacks are completed with the same frames therefore they are deduped
// to one.
profile_builder->RecordMetadata();
profile_builder->OnSampleCompleted(frames);
profile_builder->RecordMetadata();
profile_builder->OnSampleCompleted(frames);
profile_builder->OnProfileCompleted(base::TimeDelta(), base::TimeDelta());
......@@ -207,7 +219,9 @@ TEST(CallStackProfileBuilderTest, StacksNotDeduped) {
std::vector<Frame> frames2 = {frame2};
// Two stacks are completed with the different frames therefore not deduped.
profile_builder->RecordMetadata();
profile_builder->OnSampleCompleted(frames1);
profile_builder->RecordMetadata();
profile_builder->OnSampleCompleted(frames2);
profile_builder->OnProfileCompleted(base::TimeDelta(), base::TimeDelta());
......@@ -250,6 +264,7 @@ TEST(CallStackProfileBuilderTest, Modules) {
std::vector<Frame> frames = {frame1, frame2};
profile_builder->RecordMetadata();
profile_builder->OnSampleCompleted(frames);
profile_builder->OnProfileCompleted(base::TimeDelta(), base::TimeDelta());
......@@ -301,6 +316,7 @@ TEST(CallStackProfileBuilderTest, DedupModules) {
std::vector<Frame> frames = {frame1, frame2};
profile_builder->RecordMetadata();
profile_builder->OnSampleCompleted(frames);
profile_builder->OnProfileCompleted(base::TimeDelta(), base::TimeDelta());
......@@ -334,4 +350,60 @@ TEST(CallStackProfileBuilderTest, DedupModules) {
EXPECT_EQ(module_md5, profile.module_id(0).name_md5_prefix());
}
} // namespace metrics
\ No newline at end of file
TEST(CallStackProfileBuilderTest, WorkIds) {
class TestWorkIdRecorder : public WorkIdRecorder {
public:
unsigned int RecordWorkId() const override { return current_id; }
unsigned int current_id = 0;
};
TestWorkIdRecorder work_id_recorder;
auto profile_builder = std::make_unique<TestingCallStackProfileBuilder>(
kProfileParams, &work_id_recorder);
#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
Module module = {0x1000, "1", module_path};
Frame frame = {0x1000 + 0x10, module};
// Id 0 means the message loop hasn't been started yet, so the sample should
// not have continued_work set.
profile_builder->RecordMetadata();
profile_builder->OnSampleCompleted({frame});
// The second sample with the same id should have continued_work set.
work_id_recorder.current_id = 1;
profile_builder->RecordMetadata();
profile_builder->OnSampleCompleted({frame});
profile_builder->RecordMetadata();
profile_builder->OnSampleCompleted({frame});
// Ids are in general non-contiguous across multiple samples.
work_id_recorder.current_id = 10;
profile_builder->RecordMetadata();
profile_builder->OnSampleCompleted({frame});
profile_builder->RecordMetadata();
profile_builder->OnSampleCompleted({frame});
profile_builder->OnProfileCompleted(base::TimeDelta::FromMilliseconds(500),
base::TimeDelta::FromMilliseconds(100));
const SampledProfile& proto = profile_builder->test_sampled_profile();
ASSERT_TRUE(proto.has_call_stack_profile());
const CallStackProfile& profile = proto.call_stack_profile();
ASSERT_EQ(5, profile.stack_sample_size());
EXPECT_FALSE(profile.stack_sample(0).has_continued_work());
EXPECT_FALSE(profile.stack_sample(1).has_continued_work());
EXPECT_TRUE(profile.stack_sample(2).continued_work());
EXPECT_FALSE(profile.stack_sample(3).has_continued_work());
EXPECT_TRUE(profile.stack_sample(4).continued_work());
}
} // namespace metrics
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