blob: c3788bfaac99094c567fa13a509d7791d1b2b7da [file] [log] [blame]
// 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/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"
#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"
namespace metrics {
namespace {
// Stub module for testing.
class TestModule : public base::ModuleCache::Module {
public:
TestModule(uintptr_t base_address = 0,
const std::string& id = "",
const base::FilePath& debug_basename = base::FilePath())
: base_address_(base_address), id_(id), debug_basename_(debug_basename) {}
TestModule(const TestModule&) = delete;
TestModule& operator=(const TestModule&) = delete;
uintptr_t GetBaseAddress() const override { return base_address_; }
std::string GetId() const override { return id_; }
base::FilePath GetDebugBasename() const override { return debug_basename_; }
size_t GetSize() const override { return 0; }
bool IsNative() const override { return true; }
private:
uintptr_t base_address_;
std::string id_;
base::FilePath debug_basename_;
};
constexpr CallStackProfileParams kProfileParams = {
CallStackProfileParams::BROWSER_PROCESS,
CallStackProfileParams::MAIN_THREAD,
CallStackProfileParams::PROCESS_STARTUP};
class TestingCallStackProfileBuilder : public CallStackProfileBuilder {
public:
TestingCallStackProfileBuilder(
const CallStackProfileParams& profile_params,
const WorkIdRecorder* work_id_recorder = nullptr,
base::OnceClosure completed_callback = base::OnceClosure());
~TestingCallStackProfileBuilder() override;
const SampledProfile& test_sampled_profile() { return test_sampled_profile_; }
protected:
// Overridden for testing.
void PassProfilesToMetricsProvider(SampledProfile sampled_profile) override;
private:
// The completed profile.
SampledProfile test_sampled_profile_;
};
TestingCallStackProfileBuilder::TestingCallStackProfileBuilder(
const CallStackProfileParams& profile_params,
const WorkIdRecorder* work_id_recorder,
base::OnceClosure completed_callback)
: CallStackProfileBuilder(profile_params,
work_id_recorder,
std::move(completed_callback)) {}
TestingCallStackProfileBuilder::~TestingCallStackProfileBuilder() = default;
void TestingCallStackProfileBuilder::PassProfilesToMetricsProvider(
SampledProfile sampled_profile) {
test_sampled_profile_ = std::move(sampled_profile);
}
} // namespace
TEST(CallStackProfileBuilderTest, 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<TestingCallStackProfileBuilder>(
kProfileParams, nullptr, 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;
TestModule module1(module_base_address1, "1", module_path);
base::Frame frame1 = {module_base_address1 + 0x10, &module1};
const uintptr_t module_base_address2 = 0x1100;
TestModule module2(module_base_address2, "2", module_path);
base::Frame frame2 = {module_base_address2 + 0x10, &module2};
const uintptr_t module_base_address3 = 0x1010;
TestModule module3(module_base_address3, "3", module_path);
base::Frame frame3 = {module_base_address3 + 0x10, &module3};
std::vector<base::Frame> frames1 = {frame1, frame2};
std::vector<base::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));
const SampledProfile& proto = profile_builder->test_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.stack_size());
ASSERT_EQ(2, profile.stack(0).frame_size());
ASSERT_TRUE(profile.stack(0).frame(0).has_module_id_index());
EXPECT_EQ(0, profile.stack(0).frame(0).module_id_index());
ASSERT_TRUE(profile.stack(0).frame(1).has_module_id_index());
EXPECT_EQ(1, profile.stack(0).frame(1).module_id_index());
ASSERT_EQ(1, profile.stack(1).frame_size());
ASSERT_TRUE(profile.stack(1).frame(0).has_module_id_index());
EXPECT_EQ(2, profile.stack(1).frame(0).module_id_index());
ASSERT_EQ(3, profile.module_id().size());
ASSERT_TRUE(profile.module_id(0).has_build_id());
EXPECT_EQ("1", profile.module_id(0).build_id());
ASSERT_TRUE(profile.module_id(0).has_name_md5_prefix());
EXPECT_EQ(module_md5, profile.module_id(0).name_md5_prefix());
ASSERT_TRUE(profile.module_id(1).has_build_id());
EXPECT_EQ("2", profile.module_id(1).build_id());
ASSERT_TRUE(profile.module_id(1).has_name_md5_prefix());
EXPECT_EQ(module_md5, profile.module_id(1).name_md5_prefix());
ASSERT_TRUE(profile.module_id(2).has_build_id());
EXPECT_EQ("3", profile.module_id(2).build_id());
ASSERT_TRUE(profile.module_id(2).has_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_FALSE(profile.stack_sample(0).has_continued_work());
EXPECT_FALSE(profile.stack_sample(0).has_weight());
EXPECT_EQ(1, profile.stack_sample(1).stack_index());
EXPECT_FALSE(profile.stack_sample(1).has_continued_work());
EXPECT_FALSE(profile.stack_sample(1).has_weight());
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(CallStackProfileBuilderTest, CustomWeightsAndCounts) {
auto profile_builder =
std::make_unique<TestingCallStackProfileBuilder>(kProfileParams);
TestModule module1;
base::Frame frame1 = {0x10, &module1};
std::vector<base::Frame> frames = {frame1};
profile_builder->OnSampleCompleted(frames, 42, 3);
profile_builder->OnSampleCompleted(frames, 1, 1);
profile_builder->OnSampleCompleted(frames);
profile_builder->OnProfileCompleted(base::TimeDelta(), base::TimeDelta());
const SampledProfile& proto = profile_builder->test_sampled_profile();
ASSERT_TRUE(proto.has_call_stack_profile());
const CallStackProfile& profile = proto.call_stack_profile();
ASSERT_EQ(3, profile.stack_sample_size());
EXPECT_TRUE(profile.stack_sample(0).has_weight());
EXPECT_TRUE(profile.stack_sample(0).has_count());
EXPECT_EQ(42, profile.stack_sample(0).weight());
EXPECT_EQ(3, profile.stack_sample(0).count());
EXPECT_FALSE(profile.stack_sample(1).has_weight());
EXPECT_FALSE(profile.stack_sample(1).has_count());
EXPECT_FALSE(profile.stack_sample(2).has_weight());
EXPECT_FALSE(profile.stack_sample(2).has_count());
}
TEST(CallStackProfileBuilderTest, StacksDeduped) {
auto profile_builder =
std::make_unique<TestingCallStackProfileBuilder>(kProfileParams);
TestModule module1;
base::Frame frame1 = {0x10, &module1};
TestModule module2;
base::Frame frame2 = {0x20, &module2};
std::vector<base::Frame> frames = {frame1, frame2};
// 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());
const SampledProfile& proto = profile_builder->test_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(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, StacksNotDeduped) {
auto profile_builder =
std::make_unique<TestingCallStackProfileBuilder>(kProfileParams);
TestModule module1;
base::Frame frame1 = {0x10, &module1};
TestModule module2;
base::Frame frame2 = {0x20, &module2};
std::vector<base::Frame> frames1 = {frame1};
std::vector<base::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());
const SampledProfile& proto = profile_builder->test_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.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) {
auto profile_builder =
std::make_unique<TestingCallStackProfileBuilder>(kProfileParams);
// A frame with no module.
base::Frame frame1 = {0x1010, nullptr};
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
TestModule module2(module_base_address2, "2", module_path);
base::Frame frame2 = {module_base_address2 + 0x10, &module2};
std::vector<base::Frame> frames = {frame1, frame2};
profile_builder->RecordMetadata();
profile_builder->OnSampleCompleted(frames);
profile_builder->OnProfileCompleted(base::TimeDelta(), base::TimeDelta());
const SampledProfile& proto = profile_builder->test_sampled_profile();
ASSERT_TRUE(proto.has_call_stack_profile());
const CallStackProfile& profile = proto.call_stack_profile();
ASSERT_EQ(1, profile.stack_sample_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.stack(0).frame(0).has_module_id_index());
ASSERT_FALSE(profile.stack(0).frame(0).has_address());
ASSERT_TRUE(profile.stack(0).frame(1).has_module_id_index());
EXPECT_EQ(0, profile.stack(0).frame(1).module_id_index());
ASSERT_TRUE(profile.stack(0).frame(1).has_address());
EXPECT_EQ(0x10ULL, profile.stack(0).frame(1).address());
ASSERT_EQ(1, profile.module_id().size());
ASSERT_TRUE(profile.module_id(0).has_build_id());
EXPECT_EQ("2", profile.module_id(0).build_id());
ASSERT_TRUE(profile.module_id(0).has_name_md5_prefix());
EXPECT_EQ(module_md5, profile.module_id(0).name_md5_prefix());
}
TEST(CallStackProfileBuilderTest, DedupModules) {
auto profile_builder =
std::make_unique<TestingCallStackProfileBuilder>(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
TestModule module(module_base_address, "1", module_path);
base::Frame frame1 = {module_base_address + 0x10, &module};
base::Frame frame2 = {module_base_address + 0x20, &module};
std::vector<base::Frame> frames = {frame1, frame2};
profile_builder->RecordMetadata();
profile_builder->OnSampleCompleted(frames);
profile_builder->OnProfileCompleted(base::TimeDelta(), base::TimeDelta());
const SampledProfile& proto = profile_builder->test_sampled_profile();
ASSERT_TRUE(proto.has_call_stack_profile());
const CallStackProfile& profile = proto.call_stack_profile();
ASSERT_EQ(1, profile.stack_sample_size());
EXPECT_EQ(0, profile.stack_sample(0).stack_index());
ASSERT_EQ(1, profile.stack_size());
ASSERT_EQ(2, profile.stack(0).frame_size());
// The two frames share the same module, which should be deduped in the
// output.
ASSERT_TRUE(profile.stack(0).frame(0).has_module_id_index());
EXPECT_EQ(0, profile.stack(0).frame(0).module_id_index());
ASSERT_TRUE(profile.stack(0).frame(0).has_address());
EXPECT_EQ(0x10ULL, profile.stack(0).frame(0).address());
ASSERT_TRUE(profile.stack(0).frame(1).has_module_id_index());
EXPECT_EQ(0, profile.stack(0).frame(1).module_id_index());
ASSERT_TRUE(profile.stack(0).frame(1).has_address());
EXPECT_EQ(0x20ULL, profile.stack(0).frame(1).address());
ASSERT_EQ(1, profile.module_id().size());
ASSERT_TRUE(profile.module_id(0).has_build_id());
EXPECT_EQ("1", profile.module_id(0).build_id());
ASSERT_TRUE(profile.module_id(0).has_name_md5_prefix());
EXPECT_EQ(module_md5, profile.module_id(0).name_md5_prefix());
}
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);
TestModule module;
base::Frame frame = {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());
}
TEST(CallStackProfileBuilderTest, MetadataRecorder_NoItems) {
base::MetadataRecorder metadata_recorder;
auto profile_builder =
std::make_unique<TestingCallStackProfileBuilder>(kProfileParams, nullptr);
TestModule module;
base::Frame frame = {0x10, &module};
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(0, profile.metadata_name_hash_size());
ASSERT_EQ(1, profile.stack_sample_size());
ASSERT_EQ(0, profile.stack_sample(0).metadata_size());
}
TEST(CallStackProfileBuilderTest, MetadataRecorder_RepeatItem) {
base::MetadataRecorder metadata_recorder;
auto profile_builder =
std::make_unique<TestingCallStackProfileBuilder>(kProfileParams, nullptr);
TestModule module;
base::Frame frame = {0x10, &module};
metadata_recorder.Set(100, 10);
{
auto get_items = metadata_recorder.CreateMetadataProvider();
profile_builder->RecordMetadata(get_items.get());
}
profile_builder->OnSampleCompleted({frame});
{
auto get_items = metadata_recorder.CreateMetadataProvider();
profile_builder->RecordMetadata(get_items.get());
}
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(1, profile.metadata_name_hash_size());
EXPECT_EQ(100u, profile.metadata_name_hash(0));
ASSERT_EQ(2, profile.stack_sample_size());
ASSERT_EQ(1, profile.stack_sample(0).metadata_size());
EXPECT_EQ(0, profile.stack_sample(0).metadata(0).name_hash_index());
EXPECT_EQ(10, profile.stack_sample(0).metadata(0).value());
// The second sample shouldn't have any metadata because it's all the same as
// the last sample.
ASSERT_EQ(0, profile.stack_sample(1).metadata_size());
}
TEST(CallStackProfileBuilderTest, MetadataRecorder_ModifiedItem) {
base::MetadataRecorder metadata_recorder;
auto profile_builder =
std::make_unique<TestingCallStackProfileBuilder>(kProfileParams, nullptr);
TestModule module;
base::Frame frame = {0x10, &module};
metadata_recorder.Set(100, 10);
{
auto get_items = metadata_recorder.CreateMetadataProvider();
profile_builder->RecordMetadata(get_items.get());
}
profile_builder->OnSampleCompleted({frame});
metadata_recorder.Set(100, 11);
{
auto get_items = metadata_recorder.CreateMetadataProvider();
profile_builder->RecordMetadata(get_items.get());
}
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(1, profile.metadata_name_hash_size());
EXPECT_EQ(100u, profile.metadata_name_hash(0));
ASSERT_EQ(2, profile.stack_sample_size());
auto sample1 = profile.stack_sample(0);
ASSERT_EQ(1, sample1.metadata_size());
EXPECT_EQ(0, sample1.metadata(0).name_hash_index());
EXPECT_EQ(10, sample1.metadata(0).value());
// The second sample should have the metadata item with its new value.
auto sample2 = profile.stack_sample(1);
ASSERT_EQ(1, sample2.metadata_size());
EXPECT_EQ(0, sample2.metadata(0).name_hash_index());
EXPECT_EQ(11, sample2.metadata(0).value());
}
TEST(CallStackProfileBuilderTest, MetadataRecorder_NewItem) {
base::MetadataRecorder metadata_recorder;
auto profile_builder =
std::make_unique<TestingCallStackProfileBuilder>(kProfileParams, nullptr);
TestModule module;
base::Frame frame = {0x10, &module};
metadata_recorder.Set(100, 10);
{
auto get_items = metadata_recorder.CreateMetadataProvider();
profile_builder->RecordMetadata(get_items.get());
}
profile_builder->OnSampleCompleted({frame});
metadata_recorder.Set(100, 11);
metadata_recorder.Set(200, 20);
{
auto get_items = metadata_recorder.CreateMetadataProvider();
profile_builder->RecordMetadata(get_items.get());
}
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(2, profile.metadata_name_hash_size());
EXPECT_EQ(100u, profile.metadata_name_hash(0));
EXPECT_EQ(200u, profile.metadata_name_hash(1));
ASSERT_EQ(2, profile.stack_sample_size());
auto sample1 = profile.stack_sample(0);
ASSERT_EQ(1, sample1.metadata_size());
EXPECT_EQ(0, sample1.metadata(0).name_hash_index());
EXPECT_EQ(10, sample1.metadata(0).value());
// The second sample should have the new item in it.
auto sample2 = profile.stack_sample(1);
ASSERT_EQ(2, sample2.metadata_size());
EXPECT_EQ(0, sample2.metadata(0).name_hash_index());
EXPECT_EQ(11, sample2.metadata(0).value());
EXPECT_EQ(1, sample2.metadata(1).name_hash_index());
EXPECT_EQ(20, sample2.metadata(1).value());
}
TEST(CallStackProfileBuilderTest, MetadataRecorder_RemovedItem) {
base::MetadataRecorder metadata_recorder;
auto profile_builder =
std::make_unique<TestingCallStackProfileBuilder>(kProfileParams, nullptr);
TestModule module;
base::Frame frame = {0x10, &module};
metadata_recorder.Set(100, 10);
{
auto get_items = metadata_recorder.CreateMetadataProvider();
profile_builder->RecordMetadata(get_items.get());
}
profile_builder->OnSampleCompleted({frame});
metadata_recorder.Remove(100);
{
auto get_items = metadata_recorder.CreateMetadataProvider();
profile_builder->RecordMetadata(get_items.get());
}
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(1, profile.metadata_name_hash_size());
EXPECT_EQ(100u, profile.metadata_name_hash(0));
ASSERT_EQ(2, profile.stack_sample_size());
auto sample1 = profile.stack_sample(0);
ASSERT_EQ(1, sample1.metadata_size());
EXPECT_EQ(0, sample1.metadata(0).name_hash_index());
EXPECT_EQ(10, sample1.metadata(0).value());
// The second sample should have a metadata item with a set name hash but an
// empty value to indicate that the metadata item was removed.
auto sample2 = profile.stack_sample(1);
ASSERT_EQ(1, sample2.metadata_size());
EXPECT_EQ(0, sample2.metadata(0).name_hash_index());
EXPECT_FALSE(sample2.metadata(0).has_value());
}
} // namespace metrics