Index: components/metrics/call_stack_profile_metrics_provider_unittest.cc |
diff --git a/components/metrics/call_stack_profile_metrics_provider_unittest.cc b/components/metrics/call_stack_profile_metrics_provider_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..20e53d60775903c6624d995af32634b3e6bf9274 |
--- /dev/null |
+++ b/components/metrics/call_stack_profile_metrics_provider_unittest.cc |
@@ -0,0 +1,418 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "components/metrics/call_stack_profile_metrics_provider.h" |
+ |
+#include "base/profiler/stack_sampling_profiler.h" |
+#include "base/strings/string_number_conversions.h" |
+#include "components/metrics/proto/chrome_user_metrics_extension.pb.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+using base::StackSamplingProfiler; |
+using Frame = StackSamplingProfiler::Frame; |
+using Module = StackSamplingProfiler::Module; |
+using Profile = StackSamplingProfiler::Profile; |
+using Sample = StackSamplingProfiler::Sample; |
+ |
+namespace metrics { |
+ |
+// Checks that all properties from multiple profiles are filled as expected. |
+TEST(CallStackProfileMetricsProviderTest, MultipleProfiles) { |
+ const uintptr_t module1_base_address = 0x1000; |
+ const uintptr_t module2_base_address = 0x2000; |
+ const uintptr_t module3_base_address = 0x3000; |
+ |
+ const Module profile_modules[][2] = { |
+ { |
+ Module( |
+ reinterpret_cast<const void*>(module1_base_address), |
+ "ABCD", |
+#if defined(OS_WIN) |
+ base::FilePath(L"c:\\some\\path\\to\\chrome.exe") |
+#else |
+ base::FilePath("/some/path/to/chrome") |
+#endif |
+ ), |
+ Module( |
+ reinterpret_cast<const void*>(module2_base_address), |
+ "EFGH", |
+#if defined(OS_WIN) |
+ base::FilePath(L"c:\\some\\path\\to\\third_party.dll") |
+#else |
+ base::FilePath("/some/path/to/third_party.so") |
+#endif |
+ ), |
+ }, |
+ { |
+ Module( |
+ reinterpret_cast<const void*>(module3_base_address), |
+ "MNOP", |
+#if defined(OS_WIN) |
+ base::FilePath(L"c:\\some\\path\\to\\third_party2.dll") |
+#else |
+ base::FilePath("/some/path/to/third_party2.so") |
+#endif |
+ ), |
+ Module( // Repeated from the first profile. |
+ reinterpret_cast<const void*>(module1_base_address), |
+ "ABCD", |
+#if defined(OS_WIN) |
+ base::FilePath(L"c:\\some\\path\\to\\chrome.exe") |
+#else |
+ base::FilePath("/some/path/to/chrome") |
+#endif |
+ ) |
+ } |
+ }; |
+ |
+ // Values for Windows generated with: |
+ // perl -MDigest::MD5=md5 -MEncode=encode |
+ // -e 'for(@ARGV){printf "%x\n", unpack "Q>", md5 encode "UTF-16LE", $_}' |
+ // chrome.exe third_party.dll third_party2.dll |
+ // |
+ // Values for Linux generated with: |
+ // perl -MDigest::MD5=md5 |
+ // -e 'for(@ARGV){printf "%x\n", unpack "Q>", md5 $_}' |
+ // chrome third_party.so third_party2.so |
+ const uint64 profile_expected_name_md5_prefixes[][2] = { |
+ { |
+#if defined(OS_WIN) |
+ 0x46c3e4166659ac02ULL, |
+ 0x7e2b8bfddeae1abaULL |
+#else |
+ 0x554838a8451ac36cUL, |
+ 0x843661148659c9f8UL |
+#endif |
+ }, |
+ { |
+#if defined(OS_WIN) |
+ 0x87b66f4573a4d5caULL, |
+ 0x46c3e4166659ac02ULL |
+#else |
+ 0xb4647e539fa6ec9eUL, |
+ 0x554838a8451ac36cUL |
+#endif |
+ } |
+ }; |
+ |
+ // Represents two stack samples for each of two profiles, where each stack |
+ // contains three frames. Each frame contains an instruction pointer and a |
+ // module index corresponding to the module for the profile in |
+ // profile_modules. |
+ // |
+ // So, the first stack sample below has its top frame in module 0 at an offset |
+ // of 0x10 from the module's base address, the next-to-top frame in module 1 |
+ // at an offset of 0x20 from the module's base address, and the bottom frame |
+ // in module 0 at an offset of 0x30 from the module's base address |
+ const Frame profile_sample_frames[][2][3] = { |
+ { |
+ { |
+ Frame(reinterpret_cast<const void*>(module1_base_address + 0x10), 0), |
+ Frame(reinterpret_cast<const void*>(module2_base_address + 0x20), 1), |
+ Frame(reinterpret_cast<const void*>(module1_base_address + 0x30), 0) |
+ }, |
+ { |
+ Frame(reinterpret_cast<const void*>(module2_base_address + 0x10), 1), |
+ Frame(reinterpret_cast<const void*>(module1_base_address + 0x20), 0), |
+ Frame(reinterpret_cast<const void*>(module2_base_address + 0x30), 1) |
+ } |
+ }, |
+ { |
+ { |
+ Frame(reinterpret_cast<const void*>(module3_base_address + 0x10), 0), |
+ Frame(reinterpret_cast<const void*>(module1_base_address + 0x20), 1), |
+ Frame(reinterpret_cast<const void*>(module3_base_address + 0x30), 0) |
+ }, |
+ { |
+ Frame(reinterpret_cast<const void*>(module1_base_address + 0x10), 1), |
+ Frame(reinterpret_cast<const void*>(module3_base_address + 0x20), 0), |
+ Frame(reinterpret_cast<const void*>(module1_base_address + 0x30), 1) |
+ } |
+ } |
+ }; |
+ |
+ base::TimeDelta profile_durations[2] = { |
+ base::TimeDelta::FromMilliseconds(100), |
+ base::TimeDelta::FromMilliseconds(200) |
+ }; |
+ |
+ base::TimeDelta profile_sampling_periods[2] = { |
+ base::TimeDelta::FromMilliseconds(10), |
+ base::TimeDelta::FromMilliseconds(20) |
+ }; |
+ |
+ std::vector<Profile> profiles; |
+ for (size_t i = 0; i < arraysize(profile_sample_frames); ++i) { |
+ Profile profile; |
+ profile.modules.insert( |
+ profile.modules.end(), &profile_modules[i][0], |
+ &profile_modules[i][0] + arraysize(profile_modules[i])); |
+ |
+ for (size_t j = 0; j < arraysize(profile_sample_frames[i]); ++j) { |
+ profile.samples.push_back(Sample()); |
+ Sample& sample = profile.samples.back(); |
+ sample.insert(sample.end(), &profile_sample_frames[i][j][0], |
+ &profile_sample_frames[i][j][0] + |
+ arraysize(profile_sample_frames[i][j])); |
+ } |
+ |
+ profile.profile_duration = profile_durations[i]; |
+ profile.sampling_period = profile_sampling_periods[i]; |
+ profile.preserve_sample_ordering = false; |
+ |
+ profiles.push_back(profile); |
+ } |
+ |
+ CallStackProfileMetricsProvider provider; |
+ provider.SetSourceProfilesForTesting(profiles); |
+ ChromeUserMetricsExtension uma_proto; |
+ provider.ProvideGeneralMetrics(&uma_proto); |
+ |
+ ASSERT_EQ(static_cast<int>(arraysize(profile_sample_frames)), |
+ uma_proto.sampled_profile().size()); |
+ for (size_t i = 0; i < arraysize(profile_sample_frames); ++i) { |
+ SCOPED_TRACE("profile " + base::IntToString(i)); |
+ const SampledProfile& sampled_profile = uma_proto.sampled_profile().Get(i); |
+ ASSERT_TRUE(sampled_profile.has_call_stack_profile()); |
+ const CallStackProfile& call_stack_profile = |
+ sampled_profile.call_stack_profile(); |
+ |
+ ASSERT_EQ(static_cast<int>(arraysize(profile_sample_frames[i])), |
+ call_stack_profile.sample().size()); |
+ for (size_t j = 0; j < arraysize(profile_sample_frames[i]); ++j) { |
+ SCOPED_TRACE("sample " + base::IntToString(j)); |
+ const CallStackProfile::Sample& proto_sample = |
+ call_stack_profile.sample().Get(j); |
+ ASSERT_EQ(static_cast<int>(arraysize(profile_sample_frames[i][j])), |
+ proto_sample.entry().size()); |
+ ASSERT_TRUE(proto_sample.has_count()); |
+ EXPECT_EQ(1u, proto_sample.count()); |
+ for (size_t k = 0; k < arraysize(profile_sample_frames[i][j]); ++k) { |
+ SCOPED_TRACE("frame " + base::IntToString(k)); |
+ const CallStackProfile::Entry& entry = proto_sample.entry().Get(k); |
+ ASSERT_TRUE(entry.has_address()); |
+ const char* instruction_pointer = reinterpret_cast<const char*>( |
+ profile_sample_frames[i][j][k].instruction_pointer); |
+ const char* module_base_address = reinterpret_cast<const char*>( |
+ profile_modules[i][profile_sample_frames[i][j][k].module_index] |
+ .base_address); |
+ EXPECT_EQ(static_cast<uint64>(instruction_pointer - |
+ module_base_address), entry.address()); |
+ ASSERT_TRUE(entry.has_module_id_index()); |
+ EXPECT_EQ(profile_sample_frames[i][j][k].module_index, |
+ entry.module_id_index()); |
+ } |
+ } |
+ |
+ ASSERT_EQ(static_cast<int>(arraysize(profile_modules[i])), |
+ call_stack_profile.module_id().size()); |
+ for (size_t j = 0; j < arraysize(profile_modules[i]); ++j) { |
+ SCOPED_TRACE("module " + base::IntToString(j)); |
+ const CallStackProfile::ModuleIdentifier& module_identifier = |
+ call_stack_profile.module_id().Get(j); |
+ ASSERT_TRUE(module_identifier.has_build_id()); |
+ EXPECT_EQ(profile_modules[i][j].id, module_identifier.build_id()); |
+ ASSERT_TRUE(module_identifier.has_name_md5_prefix()); |
+ EXPECT_EQ(profile_expected_name_md5_prefixes[i][j], |
+ module_identifier.name_md5_prefix()); |
+ } |
+ |
+ ASSERT_TRUE(call_stack_profile.has_profile_duration_ms()); |
+ EXPECT_EQ(profile_durations[i].InMilliseconds(), |
+ call_stack_profile.profile_duration_ms()); |
+ ASSERT_TRUE(call_stack_profile.has_sampling_period_ms()); |
+ EXPECT_EQ(profile_sampling_periods[i].InMilliseconds(), |
+ call_stack_profile.sampling_period_ms()); |
+ } |
+} |
+ |
+// Checks that all duplicate samples are collapsed with |
+// preserve_sample_ordering = false. |
+TEST(CallStackProfileMetricsProviderTest, RepeatedStacksUnordered) { |
+ const uintptr_t module_base_address = 0x1000; |
+ |
+ const Module modules[] = { |
+ Module( |
+ reinterpret_cast<const void*>(module_base_address), |
+ "ABCD", |
+#if defined(OS_WIN) |
+ base::FilePath(L"c:\\some\\path\\to\\chrome.exe") |
+#else |
+ base::FilePath("/some/path/to/chrome") |
+#endif |
+ ) |
+ }; |
+ |
+ // Duplicate samples in slots 0, 2, and 3. |
+ const Frame sample_frames[][1] = { |
+ { Frame(reinterpret_cast<const void*>(module_base_address + 0x10), 0), }, |
+ { Frame(reinterpret_cast<const void*>(module_base_address + 0x20), 0), }, |
+ { Frame(reinterpret_cast<const void*>(module_base_address + 0x10), 0), }, |
+ { Frame(reinterpret_cast<const void*>(module_base_address + 0x10), 0) } |
+ }; |
+ |
+ Profile profile; |
+ profile.modules.insert(profile.modules.end(), &modules[0], |
+ &modules[0] + arraysize(modules)); |
+ |
+ for (size_t i = 0; i < arraysize(sample_frames); ++i) { |
+ profile.samples.push_back(Sample()); |
+ Sample& sample = profile.samples.back(); |
+ sample.insert(sample.end(), &sample_frames[i][0], |
+ &sample_frames[i][0] + arraysize(sample_frames[i])); |
+ } |
+ |
+ profile.profile_duration = base::TimeDelta::FromMilliseconds(100); |
+ profile.sampling_period = base::TimeDelta::FromMilliseconds(10); |
+ profile.preserve_sample_ordering = false; |
+ |
+ CallStackProfileMetricsProvider provider; |
+ provider.SetSourceProfilesForTesting(std::vector<Profile>(1, profile)); |
+ ChromeUserMetricsExtension uma_proto; |
+ provider.ProvideGeneralMetrics(&uma_proto); |
+ |
+ ASSERT_EQ(1, uma_proto.sampled_profile().size()); |
+ const SampledProfile& sampled_profile = uma_proto.sampled_profile().Get(0); |
+ ASSERT_TRUE(sampled_profile.has_call_stack_profile()); |
+ const CallStackProfile& call_stack_profile = |
+ sampled_profile.call_stack_profile(); |
+ |
+ ASSERT_EQ(2, call_stack_profile.sample().size()); |
+ for (int i = 0; i < 2; ++i) { |
+ SCOPED_TRACE("sample " + base::IntToString(i)); |
+ const CallStackProfile::Sample& proto_sample = |
+ call_stack_profile.sample().Get(i); |
+ ASSERT_EQ(static_cast<int>(arraysize(sample_frames[i])), |
+ proto_sample.entry().size()); |
+ ASSERT_TRUE(proto_sample.has_count()); |
+ EXPECT_EQ(i == 0 ? 3u : 1u, proto_sample.count()); |
+ for (size_t j = 0; j < arraysize(sample_frames[i]); ++j) { |
+ SCOPED_TRACE("frame " + base::IntToString(j)); |
+ const CallStackProfile::Entry& entry = proto_sample.entry().Get(j); |
+ ASSERT_TRUE(entry.has_address()); |
+ const char* instruction_pointer = reinterpret_cast<const char*>( |
+ sample_frames[i][j].instruction_pointer); |
+ const char* module_base_address = reinterpret_cast<const char*>( |
+ modules[sample_frames[i][j].module_index].base_address); |
+ EXPECT_EQ(static_cast<uint64>(instruction_pointer - module_base_address), |
+ entry.address()); |
+ ASSERT_TRUE(entry.has_module_id_index()); |
+ EXPECT_EQ(sample_frames[i][j].module_index, entry.module_id_index()); |
+ } |
+ } |
+} |
+ |
+// Checks that only contiguous duplicate samples are collapsed with |
+// preserve_sample_ordering = true. |
+TEST(CallStackProfileMetricsProviderTest, RepeatedStacksOrdered) { |
+ const uintptr_t module_base_address = 0x1000; |
+ |
+ const Module modules[] = { |
+ Module( |
+ reinterpret_cast<const void*>(module_base_address), |
+ "ABCD", |
+#if defined(OS_WIN) |
+ base::FilePath(L"c:\\some\\path\\to\\chrome.exe") |
+#else |
+ base::FilePath("/some/path/to/chrome") |
+#endif |
+ ) |
+ }; |
+ |
+ // Duplicate samples in slots 0, 2, and 3. |
+ const Frame sample_frames[][1] = { |
+ { Frame(reinterpret_cast<const void*>(module_base_address + 0x10), 0), }, |
+ { Frame(reinterpret_cast<const void*>(module_base_address + 0x20), 0), }, |
+ { Frame(reinterpret_cast<const void*>(module_base_address + 0x10), 0), }, |
+ { Frame(reinterpret_cast<const void*>(module_base_address + 0x10), 0) } |
+ }; |
+ |
+ Profile profile; |
+ profile.modules.insert(profile.modules.end(), &modules[0], |
+ &modules[0] + arraysize(modules)); |
+ |
+ for (size_t i = 0; i < arraysize(sample_frames); ++i) { |
+ profile.samples.push_back(Sample()); |
+ Sample& sample = profile.samples.back(); |
+ sample.insert(sample.end(), &sample_frames[i][0], |
+ &sample_frames[i][0] + arraysize(sample_frames[i])); |
+ } |
+ |
+ profile.profile_duration = base::TimeDelta::FromMilliseconds(100); |
+ profile.sampling_period = base::TimeDelta::FromMilliseconds(10); |
+ profile.preserve_sample_ordering = true; |
+ |
+ CallStackProfileMetricsProvider provider; |
+ provider.SetSourceProfilesForTesting(std::vector<Profile>(1, profile)); |
+ ChromeUserMetricsExtension uma_proto; |
+ provider.ProvideGeneralMetrics(&uma_proto); |
+ |
+ ASSERT_EQ(1, uma_proto.sampled_profile().size()); |
+ const SampledProfile& sampled_profile = uma_proto.sampled_profile().Get(0); |
+ ASSERT_TRUE(sampled_profile.has_call_stack_profile()); |
+ const CallStackProfile& call_stack_profile = |
+ sampled_profile.call_stack_profile(); |
+ |
+ ASSERT_EQ(3, call_stack_profile.sample().size()); |
+ for (int i = 0; i < 3; ++i) { |
+ SCOPED_TRACE("sample " + base::IntToString(i)); |
+ const CallStackProfile::Sample& proto_sample = |
+ call_stack_profile.sample().Get(i); |
+ ASSERT_EQ(static_cast<int>(arraysize(sample_frames[i])), |
+ proto_sample.entry().size()); |
+ ASSERT_TRUE(proto_sample.has_count()); |
+ EXPECT_EQ(i == 2 ? 2u : 1u, proto_sample.count()); |
+ for (size_t j = 0; j < arraysize(sample_frames[i]); ++j) { |
+ SCOPED_TRACE("frame " + base::IntToString(j)); |
+ const CallStackProfile::Entry& entry = proto_sample.entry().Get(j); |
+ ASSERT_TRUE(entry.has_address()); |
+ const char* instruction_pointer = reinterpret_cast<const char*>( |
+ sample_frames[i][j].instruction_pointer); |
+ const char* module_base_address = reinterpret_cast<const char*>( |
+ modules[sample_frames[i][j].module_index].base_address); |
+ EXPECT_EQ(static_cast<uint64>(instruction_pointer - module_base_address), |
+ entry.address()); |
+ ASSERT_TRUE(entry.has_module_id_index()); |
+ EXPECT_EQ(sample_frames[i][j].module_index, entry.module_id_index()); |
+ } |
+ } |
+} |
+ |
+ |
+// Checks that unknown modules produce an empty Entry. |
+TEST(CallStackProfileMetricsProviderTest, UnknownModule) { |
+ // -1 indicates an unknown module. |
+ const Frame frame(reinterpret_cast<const void*>(0x1000), -1); |
+ |
+ Profile profile; |
+ |
+ profile.samples.push_back(Sample(1, frame)); |
+ |
+ profile.profile_duration = base::TimeDelta::FromMilliseconds(100); |
+ profile.sampling_period = base::TimeDelta::FromMilliseconds(10); |
+ profile.preserve_sample_ordering = false; |
+ |
+ CallStackProfileMetricsProvider provider; |
+ provider.SetSourceProfilesForTesting(std::vector<Profile>(1, profile)); |
+ ChromeUserMetricsExtension uma_proto; |
+ provider.ProvideGeneralMetrics(&uma_proto); |
+ |
+ ASSERT_EQ(1, uma_proto.sampled_profile().size()); |
+ const SampledProfile& sampled_profile = uma_proto.sampled_profile().Get(0); |
+ ASSERT_TRUE(sampled_profile.has_call_stack_profile()); |
+ const CallStackProfile& call_stack_profile = |
+ sampled_profile.call_stack_profile(); |
+ |
+ ASSERT_EQ(1, call_stack_profile.sample().size()); |
+ const CallStackProfile::Sample& proto_sample = |
+ call_stack_profile.sample().Get(0); |
+ ASSERT_EQ(1, proto_sample.entry().size()); |
+ ASSERT_TRUE(proto_sample.has_count()); |
+ EXPECT_EQ(1u, proto_sample.count()); |
+ const CallStackProfile::Entry& entry = proto_sample.entry().Get(0); |
+ EXPECT_FALSE(entry.has_address()); |
+ EXPECT_FALSE(entry.has_module_id_index()); |
+} |
+ |
+} // namespace metrics |