1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/metrics/call_stack_profile_metrics_provider.h"
7 #include "base/metrics/field_trial.h"
8 #include "base/profiler/stack_sampling_profiler.h"
9 #include "base/strings/string_number_conversions.h"
10 #include "components/metrics/proto/chrome_user_metrics_extension.pb.h"
11 #include "components/variations/entropy_provider.h"
12 #include "testing/gtest/include/gtest/gtest.h"
14 using base::StackSamplingProfiler
;
15 using Frame
= StackSamplingProfiler::Frame
;
16 using Module
= StackSamplingProfiler::Module
;
17 using Profile
= StackSamplingProfiler::CallStackProfile
;
18 using Sample
= StackSamplingProfiler::Sample
;
22 // This test fixture enables the field trial that
23 // CallStackProfileMetricsProvider depends on to report profiles.
24 class CallStackProfileMetricsProviderTest
: public testing::Test
{
26 CallStackProfileMetricsProviderTest()
27 : field_trial_list_(new base::FieldTrialList(
28 new metrics::SHA1EntropyProvider("foo"))) {
29 base::FieldTrialList::CreateFieldTrial(
30 FieldTrialState::kFieldTrialName
,
31 FieldTrialState::kReportProfilesGroupName
);
34 ~CallStackProfileMetricsProviderTest() override
{}
37 // Exposes field trial/group names from the CallStackProfileMetricsProvider.
38 class FieldTrialState
: public CallStackProfileMetricsProvider
{
40 using CallStackProfileMetricsProvider::kFieldTrialName
;
41 using CallStackProfileMetricsProvider::kReportProfilesGroupName
;
44 const scoped_ptr
<base::FieldTrialList
> field_trial_list_
;
47 // Checks that all properties from multiple profiles are filled as expected.
48 TEST_F(CallStackProfileMetricsProviderTest
, MultipleProfiles
) {
49 const uintptr_t module1_base_address
= 0x1000;
50 const uintptr_t module2_base_address
= 0x2000;
51 const uintptr_t module3_base_address
= 0x3000;
53 const Module profile_modules
[][2] = {
56 reinterpret_cast<const void*>(module1_base_address
),
59 base::FilePath(L
"c:\\some\\path\\to\\chrome.exe")
61 base::FilePath("/some/path/to/chrome")
65 reinterpret_cast<const void*>(module2_base_address
),
68 base::FilePath(L
"c:\\some\\path\\to\\third_party.dll")
70 base::FilePath("/some/path/to/third_party.so")
76 reinterpret_cast<const void*>(module3_base_address
),
79 base::FilePath(L
"c:\\some\\path\\to\\third_party2.dll")
81 base::FilePath("/some/path/to/third_party2.so")
84 Module( // Repeated from the first profile.
85 reinterpret_cast<const void*>(module1_base_address
),
88 base::FilePath(L
"c:\\some\\path\\to\\chrome.exe")
90 base::FilePath("/some/path/to/chrome")
96 // Values for Windows generated with:
97 // perl -MDigest::MD5=md5 -MEncode=encode
98 // -e 'for(@ARGV){printf "%x\n", unpack "Q>", md5 encode "UTF-16LE", $_}'
99 // chrome.exe third_party.dll third_party2.dll
101 // Values for Linux generated with:
102 // perl -MDigest::MD5=md5
103 // -e 'for(@ARGV){printf "%x\n", unpack "Q>", md5 $_}'
104 // chrome third_party.so third_party2.so
105 const uint64 profile_expected_name_md5_prefixes
[][2] = {
108 0x46c3e4166659ac02ULL
,
109 0x7e2b8bfddeae1abaULL
111 0x554838a8451ac36cUL
,
117 0x87b66f4573a4d5caULL
,
118 0x46c3e4166659ac02ULL
120 0xb4647e539fa6ec9eUL
,
126 // Represents two stack samples for each of two profiles, where each stack
127 // contains three frames. Each frame contains an instruction pointer and a
128 // module index corresponding to the module for the profile in
131 // So, the first stack sample below has its top frame in module 0 at an offset
132 // of 0x10 from the module's base address, the next-to-top frame in module 1
133 // at an offset of 0x20 from the module's base address, and the bottom frame
134 // in module 0 at an offset of 0x30 from the module's base address
135 const Frame profile_sample_frames
[][2][3] = {
138 Frame(reinterpret_cast<const void*>(module1_base_address
+ 0x10), 0),
139 Frame(reinterpret_cast<const void*>(module2_base_address
+ 0x20), 1),
140 Frame(reinterpret_cast<const void*>(module1_base_address
+ 0x30), 0)
143 Frame(reinterpret_cast<const void*>(module2_base_address
+ 0x10), 1),
144 Frame(reinterpret_cast<const void*>(module1_base_address
+ 0x20), 0),
145 Frame(reinterpret_cast<const void*>(module2_base_address
+ 0x30), 1)
150 Frame(reinterpret_cast<const void*>(module3_base_address
+ 0x10), 0),
151 Frame(reinterpret_cast<const void*>(module1_base_address
+ 0x20), 1),
152 Frame(reinterpret_cast<const void*>(module3_base_address
+ 0x30), 0)
155 Frame(reinterpret_cast<const void*>(module1_base_address
+ 0x10), 1),
156 Frame(reinterpret_cast<const void*>(module3_base_address
+ 0x20), 0),
157 Frame(reinterpret_cast<const void*>(module1_base_address
+ 0x30), 1)
162 base::TimeDelta profile_durations
[2] = {
163 base::TimeDelta::FromMilliseconds(100),
164 base::TimeDelta::FromMilliseconds(200)
167 base::TimeDelta profile_sampling_periods
[2] = {
168 base::TimeDelta::FromMilliseconds(10),
169 base::TimeDelta::FromMilliseconds(20)
172 std::vector
<Profile
> profiles
;
173 for (size_t i
= 0; i
< arraysize(profile_sample_frames
); ++i
) {
175 profile
.modules
.insert(
176 profile
.modules
.end(), &profile_modules
[i
][0],
177 &profile_modules
[i
][0] + arraysize(profile_modules
[i
]));
179 for (size_t j
= 0; j
< arraysize(profile_sample_frames
[i
]); ++j
) {
180 profile
.samples
.push_back(Sample());
181 Sample
& sample
= profile
.samples
.back();
182 sample
.insert(sample
.end(), &profile_sample_frames
[i
][j
][0],
183 &profile_sample_frames
[i
][j
][0] +
184 arraysize(profile_sample_frames
[i
][j
]));
187 profile
.profile_duration
= profile_durations
[i
];
188 profile
.sampling_period
= profile_sampling_periods
[i
];
189 profile
.preserve_sample_ordering
= false;
190 profile
.user_data
= CallStackProfileMetricsProvider::PROCESS_STARTUP
;
192 profiles
.push_back(profile
);
195 CallStackProfileMetricsProvider provider
;
196 provider
.AppendSourceProfilesForTesting(profiles
);
197 ChromeUserMetricsExtension uma_proto
;
198 provider
.ProvideGeneralMetrics(&uma_proto
);
200 ASSERT_EQ(static_cast<int>(arraysize(profile_sample_frames
)),
201 uma_proto
.sampled_profile().size());
202 for (size_t i
= 0; i
< arraysize(profile_sample_frames
); ++i
) {
203 SCOPED_TRACE("profile " + base::IntToString(i
));
204 const SampledProfile
& sampled_profile
= uma_proto
.sampled_profile().Get(i
);
205 ASSERT_TRUE(sampled_profile
.has_call_stack_profile());
206 const CallStackProfile
& call_stack_profile
=
207 sampled_profile
.call_stack_profile();
209 ASSERT_EQ(static_cast<int>(arraysize(profile_sample_frames
[i
])),
210 call_stack_profile
.sample().size());
211 for (size_t j
= 0; j
< arraysize(profile_sample_frames
[i
]); ++j
) {
212 SCOPED_TRACE("sample " + base::IntToString(j
));
213 const CallStackProfile::Sample
& proto_sample
=
214 call_stack_profile
.sample().Get(j
);
215 ASSERT_EQ(static_cast<int>(arraysize(profile_sample_frames
[i
][j
])),
216 proto_sample
.entry().size());
217 ASSERT_TRUE(proto_sample
.has_count());
218 EXPECT_EQ(1u, proto_sample
.count());
219 for (size_t k
= 0; k
< arraysize(profile_sample_frames
[i
][j
]); ++k
) {
220 SCOPED_TRACE("frame " + base::IntToString(k
));
221 const CallStackProfile::Entry
& entry
= proto_sample
.entry().Get(k
);
222 ASSERT_TRUE(entry
.has_address());
223 const char* instruction_pointer
= reinterpret_cast<const char*>(
224 profile_sample_frames
[i
][j
][k
].instruction_pointer
);
225 const char* module_base_address
= reinterpret_cast<const char*>(
226 profile_modules
[i
][profile_sample_frames
[i
][j
][k
].module_index
]
228 EXPECT_EQ(static_cast<uint64
>(instruction_pointer
-
229 module_base_address
), entry
.address());
230 ASSERT_TRUE(entry
.has_module_id_index());
231 EXPECT_EQ(profile_sample_frames
[i
][j
][k
].module_index
,
232 static_cast<size_t>(entry
.module_id_index()));
236 ASSERT_EQ(static_cast<int>(arraysize(profile_modules
[i
])),
237 call_stack_profile
.module_id().size());
238 for (size_t j
= 0; j
< arraysize(profile_modules
[i
]); ++j
) {
239 SCOPED_TRACE("module " + base::IntToString(j
));
240 const CallStackProfile::ModuleIdentifier
& module_identifier
=
241 call_stack_profile
.module_id().Get(j
);
242 ASSERT_TRUE(module_identifier
.has_build_id());
243 EXPECT_EQ(profile_modules
[i
][j
].id
, module_identifier
.build_id());
244 ASSERT_TRUE(module_identifier
.has_name_md5_prefix());
245 EXPECT_EQ(profile_expected_name_md5_prefixes
[i
][j
],
246 module_identifier
.name_md5_prefix());
249 ASSERT_TRUE(call_stack_profile
.has_profile_duration_ms());
250 EXPECT_EQ(profile_durations
[i
].InMilliseconds(),
251 call_stack_profile
.profile_duration_ms());
252 ASSERT_TRUE(call_stack_profile
.has_sampling_period_ms());
253 EXPECT_EQ(profile_sampling_periods
[i
].InMilliseconds(),
254 call_stack_profile
.sampling_period_ms());
255 ASSERT_TRUE(sampled_profile
.has_trigger_event());
256 EXPECT_EQ(SampledProfile::PROCESS_STARTUP
, sampled_profile
.trigger_event());
260 // Checks that all duplicate samples are collapsed with
261 // preserve_sample_ordering = false.
262 TEST_F(CallStackProfileMetricsProviderTest
, RepeatedStacksUnordered
) {
263 const uintptr_t module_base_address
= 0x1000;
265 const Module modules
[] = {
267 reinterpret_cast<const void*>(module_base_address
),
270 base::FilePath(L
"c:\\some\\path\\to\\chrome.exe")
272 base::FilePath("/some/path/to/chrome")
277 // Duplicate samples in slots 0, 2, and 3.
278 const Frame sample_frames
[][1] = {
279 { Frame(reinterpret_cast<const void*>(module_base_address
+ 0x10), 0), },
280 { Frame(reinterpret_cast<const void*>(module_base_address
+ 0x20), 0), },
281 { Frame(reinterpret_cast<const void*>(module_base_address
+ 0x10), 0), },
282 { Frame(reinterpret_cast<const void*>(module_base_address
+ 0x10), 0) }
286 profile
.modules
.insert(profile
.modules
.end(), &modules
[0],
287 &modules
[0] + arraysize(modules
));
289 for (size_t i
= 0; i
< arraysize(sample_frames
); ++i
) {
290 profile
.samples
.push_back(Sample());
291 Sample
& sample
= profile
.samples
.back();
292 sample
.insert(sample
.end(), &sample_frames
[i
][0],
293 &sample_frames
[i
][0] + arraysize(sample_frames
[i
]));
296 profile
.profile_duration
= base::TimeDelta::FromMilliseconds(100);
297 profile
.sampling_period
= base::TimeDelta::FromMilliseconds(10);
298 profile
.preserve_sample_ordering
= false;
300 CallStackProfileMetricsProvider provider
;
301 provider
.AppendSourceProfilesForTesting(std::vector
<Profile
>(1, profile
));
302 ChromeUserMetricsExtension uma_proto
;
303 provider
.ProvideGeneralMetrics(&uma_proto
);
305 ASSERT_EQ(1, uma_proto
.sampled_profile().size());
306 const SampledProfile
& sampled_profile
= uma_proto
.sampled_profile().Get(0);
307 ASSERT_TRUE(sampled_profile
.has_call_stack_profile());
308 const CallStackProfile
& call_stack_profile
=
309 sampled_profile
.call_stack_profile();
311 ASSERT_EQ(2, call_stack_profile
.sample().size());
312 for (int i
= 0; i
< 2; ++i
) {
313 SCOPED_TRACE("sample " + base::IntToString(i
));
314 const CallStackProfile::Sample
& proto_sample
=
315 call_stack_profile
.sample().Get(i
);
316 ASSERT_EQ(static_cast<int>(arraysize(sample_frames
[i
])),
317 proto_sample
.entry().size());
318 ASSERT_TRUE(proto_sample
.has_count());
319 EXPECT_EQ(i
== 0 ? 3u : 1u, proto_sample
.count());
320 for (size_t j
= 0; j
< arraysize(sample_frames
[i
]); ++j
) {
321 SCOPED_TRACE("frame " + base::IntToString(j
));
322 const CallStackProfile::Entry
& entry
= proto_sample
.entry().Get(j
);
323 ASSERT_TRUE(entry
.has_address());
324 const char* instruction_pointer
= reinterpret_cast<const char*>(
325 sample_frames
[i
][j
].instruction_pointer
);
326 const char* module_base_address
= reinterpret_cast<const char*>(
327 modules
[sample_frames
[i
][j
].module_index
].base_address
);
328 EXPECT_EQ(static_cast<uint64
>(instruction_pointer
- module_base_address
),
330 ASSERT_TRUE(entry
.has_module_id_index());
331 EXPECT_EQ(sample_frames
[i
][j
].module_index
,
332 static_cast<size_t>(entry
.module_id_index()));
337 // Checks that only contiguous duplicate samples are collapsed with
338 // preserve_sample_ordering = true.
339 TEST_F(CallStackProfileMetricsProviderTest
, RepeatedStacksOrdered
) {
340 const uintptr_t module_base_address
= 0x1000;
342 const Module modules
[] = {
344 reinterpret_cast<const void*>(module_base_address
),
347 base::FilePath(L
"c:\\some\\path\\to\\chrome.exe")
349 base::FilePath("/some/path/to/chrome")
354 // Duplicate samples in slots 0, 2, and 3.
355 const Frame sample_frames
[][1] = {
356 { Frame(reinterpret_cast<const void*>(module_base_address
+ 0x10), 0), },
357 { Frame(reinterpret_cast<const void*>(module_base_address
+ 0x20), 0), },
358 { Frame(reinterpret_cast<const void*>(module_base_address
+ 0x10), 0), },
359 { Frame(reinterpret_cast<const void*>(module_base_address
+ 0x10), 0) }
363 profile
.modules
.insert(profile
.modules
.end(), &modules
[0],
364 &modules
[0] + arraysize(modules
));
366 for (size_t i
= 0; i
< arraysize(sample_frames
); ++i
) {
367 profile
.samples
.push_back(Sample());
368 Sample
& sample
= profile
.samples
.back();
369 sample
.insert(sample
.end(), &sample_frames
[i
][0],
370 &sample_frames
[i
][0] + arraysize(sample_frames
[i
]));
373 profile
.profile_duration
= base::TimeDelta::FromMilliseconds(100);
374 profile
.sampling_period
= base::TimeDelta::FromMilliseconds(10);
375 profile
.preserve_sample_ordering
= true;
377 CallStackProfileMetricsProvider provider
;
378 provider
.AppendSourceProfilesForTesting(std::vector
<Profile
>(1, profile
));
379 ChromeUserMetricsExtension uma_proto
;
380 provider
.ProvideGeneralMetrics(&uma_proto
);
382 ASSERT_EQ(1, uma_proto
.sampled_profile().size());
383 const SampledProfile
& sampled_profile
= uma_proto
.sampled_profile().Get(0);
384 ASSERT_TRUE(sampled_profile
.has_call_stack_profile());
385 const CallStackProfile
& call_stack_profile
=
386 sampled_profile
.call_stack_profile();
388 ASSERT_EQ(3, call_stack_profile
.sample().size());
389 for (int i
= 0; i
< 3; ++i
) {
390 SCOPED_TRACE("sample " + base::IntToString(i
));
391 const CallStackProfile::Sample
& proto_sample
=
392 call_stack_profile
.sample().Get(i
);
393 ASSERT_EQ(static_cast<int>(arraysize(sample_frames
[i
])),
394 proto_sample
.entry().size());
395 ASSERT_TRUE(proto_sample
.has_count());
396 EXPECT_EQ(i
== 2 ? 2u : 1u, proto_sample
.count());
397 for (size_t j
= 0; j
< arraysize(sample_frames
[i
]); ++j
) {
398 SCOPED_TRACE("frame " + base::IntToString(j
));
399 const CallStackProfile::Entry
& entry
= proto_sample
.entry().Get(j
);
400 ASSERT_TRUE(entry
.has_address());
401 const char* instruction_pointer
= reinterpret_cast<const char*>(
402 sample_frames
[i
][j
].instruction_pointer
);
403 const char* module_base_address
= reinterpret_cast<const char*>(
404 modules
[sample_frames
[i
][j
].module_index
].base_address
);
405 EXPECT_EQ(static_cast<uint64
>(instruction_pointer
- module_base_address
),
407 ASSERT_TRUE(entry
.has_module_id_index());
408 EXPECT_EQ(sample_frames
[i
][j
].module_index
,
409 static_cast<size_t>(entry
.module_id_index()));
414 // Checks that unknown modules produce an empty Entry.
415 TEST_F(CallStackProfileMetricsProviderTest
, UnknownModule
) {
416 const Frame
frame(reinterpret_cast<const void*>(0x1000),
417 Frame::kUnknownModuleIndex
);
421 profile
.samples
.push_back(Sample(1, frame
));
423 profile
.profile_duration
= base::TimeDelta::FromMilliseconds(100);
424 profile
.sampling_period
= base::TimeDelta::FromMilliseconds(10);
425 profile
.preserve_sample_ordering
= false;
427 CallStackProfileMetricsProvider provider
;
428 provider
.AppendSourceProfilesForTesting(std::vector
<Profile
>(1, profile
));
429 ChromeUserMetricsExtension uma_proto
;
430 provider
.ProvideGeneralMetrics(&uma_proto
);
432 ASSERT_EQ(1, uma_proto
.sampled_profile().size());
433 const SampledProfile
& sampled_profile
= uma_proto
.sampled_profile().Get(0);
434 ASSERT_TRUE(sampled_profile
.has_call_stack_profile());
435 const CallStackProfile
& call_stack_profile
=
436 sampled_profile
.call_stack_profile();
438 ASSERT_EQ(1, call_stack_profile
.sample().size());
439 const CallStackProfile::Sample
& proto_sample
=
440 call_stack_profile
.sample().Get(0);
441 ASSERT_EQ(1, proto_sample
.entry().size());
442 ASSERT_TRUE(proto_sample
.has_count());
443 EXPECT_EQ(1u, proto_sample
.count());
444 const CallStackProfile::Entry
& entry
= proto_sample
.entry().Get(0);
445 EXPECT_FALSE(entry
.has_address());
446 EXPECT_FALSE(entry
.has_module_id_index());
449 // Checks that pending profiles are only passed back to ProvideGeneralMetrics
451 TEST_F(CallStackProfileMetricsProviderTest
, ProfilesProvidedOnlyOnce
) {
452 CallStackProfileMetricsProvider provider
;
453 for (int i
= 0; i
< 2; ++i
) {
455 profile
.samples
.push_back(Sample(1, Frame(
456 reinterpret_cast<const void*>(0x1000), Frame::kUnknownModuleIndex
)));
458 profile
.profile_duration
= base::TimeDelta::FromMilliseconds(100);
459 // Use the sampling period to distinguish the two profiles.
460 profile
.sampling_period
= base::TimeDelta::FromMilliseconds(i
);
461 profile
.preserve_sample_ordering
= false;
463 provider
.AppendSourceProfilesForTesting(std::vector
<Profile
>(1, profile
));
464 ChromeUserMetricsExtension uma_proto
;
465 provider
.ProvideGeneralMetrics(&uma_proto
);
467 ASSERT_EQ(1, uma_proto
.sampled_profile().size());
468 const SampledProfile
& sampled_profile
= uma_proto
.sampled_profile().Get(0);
469 ASSERT_TRUE(sampled_profile
.has_call_stack_profile());
470 const CallStackProfile
& call_stack_profile
=
471 sampled_profile
.call_stack_profile();
472 ASSERT_TRUE(call_stack_profile
.has_sampling_period_ms());
473 EXPECT_EQ(i
, call_stack_profile
.sampling_period_ms());
477 } // namespace metrics