1 // Copyright 2016 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "components/metrics/content/subprocess_metrics_provider.h"
6
7 #include <memory>
8 #include <string>
9 #include <vector>
10
11 #include "base/metrics/histogram.h"
12 #include "base/metrics/histogram_flattener.h"
13 #include "base/metrics/histogram_snapshot_manager.h"
14 #include "base/metrics/persistent_histogram_allocator.h"
15 #include "base/metrics/persistent_memory_allocator.h"
16 #include "base/metrics/sparse_histogram.h"
17 #include "base/metrics/statistics_recorder.h"
18 #include "base/test/bind.h"
19 #include "content/public/test/browser_task_environment.h"
20 #include "testing/gmock/include/gmock/gmock.h"
21 #include "testing/gtest/include/gtest/gtest.h"
22
23 using ::testing::IsEmpty;
24 using ::testing::UnorderedElementsAre;
25
26 namespace metrics {
27 namespace {
28
29 const uint32_t TEST_MEMORY_SIZE = 64 << 10; // 64 KiB
30
31 struct HistogramData {
32 const std::string histogram_name;
33 const base::HistogramBase::Count total_count;
34 const int64_t sum;
35
operator ==metrics::__anon034290020111::HistogramData36 bool operator==(const HistogramData& other) const {
37 return histogram_name == other.histogram_name &&
38 total_count == other.total_count && sum == other.sum;
39 }
40 };
41
42 class HistogramFlattenerDeltaRecorder : public base::HistogramFlattener {
43 public:
44 HistogramFlattenerDeltaRecorder() = default;
45
46 HistogramFlattenerDeltaRecorder(const HistogramFlattenerDeltaRecorder&) =
47 delete;
48 HistogramFlattenerDeltaRecorder& operator=(
49 const HistogramFlattenerDeltaRecorder&) = delete;
50
51 ~HistogramFlattenerDeltaRecorder() override = default;
52
RecordDelta(const base::HistogramBase & histogram,const base::HistogramSamples & snapshot)53 void RecordDelta(const base::HistogramBase& histogram,
54 const base::HistogramSamples& snapshot) override {
55 // Only record histograms that start with '_' (e.g., _foo, _bar, _baz) to
56 // not record histograms from outside what is being tested.
57 if (histogram.histogram_name()[0] == '_') {
58 recorded_delta_histograms_.emplace_back(
59 histogram.histogram_name(), snapshot.TotalCount(), snapshot.sum());
60 }
61 }
62
GetRecordedDeltaHistograms()63 const std::vector<HistogramData>& GetRecordedDeltaHistograms() {
64 return recorded_delta_histograms_;
65 }
66
67 private:
68 std::vector<HistogramData> recorded_delta_histograms_;
69 };
70
71 // Same as PersistentHistogramAllocator, but will call a callback on being
72 // destroyed.
73 class TestPersistentHistogramAllocator
74 : public base::PersistentHistogramAllocator {
75 public:
76 using base::PersistentHistogramAllocator::PersistentHistogramAllocator;
77
78 TestPersistentHistogramAllocator(const TestPersistentHistogramAllocator&) =
79 delete;
80 TestPersistentHistogramAllocator& operator=(
81 const TestPersistentHistogramAllocator&) = delete;
82
~TestPersistentHistogramAllocator()83 ~TestPersistentHistogramAllocator() override {
84 if (!destroyed_callback_.is_null()) {
85 std::move(destroyed_callback_).Run();
86 }
87 }
88
SetDestroyedCallback(base::OnceClosure destroyed_callback)89 void SetDestroyedCallback(base::OnceClosure destroyed_callback) {
90 destroyed_callback_ = std::move(destroyed_callback);
91 }
92
93 private:
94 base::OnceClosure destroyed_callback_;
95 };
96
97 } // namespace
98
99 class SubprocessMetricsProviderTest : public testing::Test {
100 public:
101 SubprocessMetricsProviderTest(const SubprocessMetricsProviderTest&) = delete;
102 SubprocessMetricsProviderTest& operator=(
103 const SubprocessMetricsProviderTest&) = delete;
104
105 protected:
SubprocessMetricsProviderTest()106 SubprocessMetricsProviderTest()
107 : task_environment_(
108 // Use ThreadPoolExecutionMode::QUEUED so that tests can decide
109 // exactly when tasks posted to ThreadPool start running.
110 base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED) {
111 SubprocessMetricsProvider::CreateInstance();
112
113 // Need to recreate a task runner, otherwise it may be a stale one from a
114 // previous test (it's possible the global instance is re-used across
115 // tests).
116 RecreateTaskRunnerForTesting();
117
118 // MergeHistogramDeltas needs to be called because it uses a histogram
119 // macro which caches a pointer to a histogram. If not done before setting
120 // a persistent global allocator, then it would point into memory that
121 // will go away.
122 SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
123
124 // Create a dedicated StatisticsRecorder for this test.
125 test_recorder_ = base::StatisticsRecorder::CreateTemporaryForTesting();
126
127 // Create a global allocator using a block of memory from the heap.
128 base::GlobalHistogramAllocator::CreateWithLocalMemory(TEST_MEMORY_SIZE, 0,
129 "");
130 }
131
~SubprocessMetricsProviderTest()132 ~SubprocessMetricsProviderTest() override {
133 base::GlobalHistogramAllocator::ReleaseForTesting();
134 }
135
CreateDuplicateAllocator(base::PersistentHistogramAllocator * allocator)136 std::unique_ptr<TestPersistentHistogramAllocator> CreateDuplicateAllocator(
137 base::PersistentHistogramAllocator* allocator) {
138 // Just wrap around the data segment in-use by the passed allocator.
139 return std::make_unique<TestPersistentHistogramAllocator>(
140 std::make_unique<base::PersistentMemoryAllocator>(
141 const_cast<void*>(allocator->data()), allocator->length(), 0, 0, "",
142 base::PersistentMemoryAllocator::kReadWrite));
143 }
144
GetSnapshotHistograms()145 std::vector<HistogramData> GetSnapshotHistograms() {
146 // Flatten what is known to see what has changed since the last time.
147 HistogramFlattenerDeltaRecorder flattener;
148 base::HistogramSnapshotManager snapshot_manager(&flattener);
149 // "true" to the begin() includes histograms held in persistent storage.
150 base::StatisticsRecorder::PrepareDeltas(true, base::Histogram::kNoFlags,
151 base::Histogram::kNoFlags,
152 &snapshot_manager);
153 return flattener.GetRecordedDeltaHistograms();
154 }
155
RegisterSubprocessAllocator(int id,std::unique_ptr<base::PersistentHistogramAllocator> allocator)156 void RegisterSubprocessAllocator(
157 int id,
158 std::unique_ptr<base::PersistentHistogramAllocator> allocator) {
159 SubprocessMetricsProvider::GetInstance()->RegisterSubprocessAllocator(
160 id, std::move(allocator));
161 }
162
DeregisterSubprocessAllocator(int id)163 void DeregisterSubprocessAllocator(int id) {
164 SubprocessMetricsProvider::GetInstance()->DeregisterSubprocessAllocator(id);
165 }
166
RecreateTaskRunnerForTesting()167 void RecreateTaskRunnerForTesting() {
168 SubprocessMetricsProvider::GetInstance()->RecreateTaskRunnerForTesting();
169 }
170
task_environment()171 content::BrowserTaskEnvironment* task_environment() {
172 return &task_environment_;
173 }
174
175 private:
176 // A thread-bundle makes the tests appear on the UI thread, something that is
177 // checked in methods called from the SubprocessMetricsProvider class under
178 // test. This must be constructed before the |provider_| field.
179 content::BrowserTaskEnvironment task_environment_;
180
181 std::unique_ptr<base::StatisticsRecorder> test_recorder_;
182 };
183
TEST_F(SubprocessMetricsProviderTest,SnapshotMetrics)184 TEST_F(SubprocessMetricsProviderTest, SnapshotMetrics) {
185 base::HistogramBase* foo = base::Histogram::FactoryGet("_foo", 1, 100, 10, 0);
186 base::HistogramBase* bar = base::Histogram::FactoryGet("_bar", 1, 100, 10, 0);
187 base::HistogramBase* baz = base::Histogram::FactoryGet("_baz", 1, 100, 10, 0);
188 base::HistogramBase* foobar = base::SparseHistogram::FactoryGet(
189 "_foobar", base::HistogramBase::kUmaTargetedHistogramFlag);
190 foo->Add(42);
191 bar->Add(84);
192 foobar->Add(1);
193 foobar->Add(2);
194 foobar->Add(3);
195
196 // Register a new allocator that duplicates the global one.
197 base::GlobalHistogramAllocator* global_allocator(
198 base::GlobalHistogramAllocator::ReleaseForTesting());
199 auto duplicate_allocator = CreateDuplicateAllocator(global_allocator);
200 bool duplicate_allocator_destroyed = false;
201 duplicate_allocator->SetDestroyedCallback(base::BindLambdaForTesting(
202 [&] { duplicate_allocator_destroyed = true; }));
203 RegisterSubprocessAllocator(123, std::move(duplicate_allocator));
204
205 // Recording should find the two histograms created in persistent memory.
206 SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
207 EXPECT_THAT(GetSnapshotHistograms(),
208 UnorderedElementsAre(
209 HistogramData{"_foo", /*total_count=*/1, /*sum=*/42},
210 HistogramData{"_bar", /*total_count=*/1, /*sum=*/84},
211 HistogramData{"_foobar", /*total_count=*/3, /*sum=*/6}));
212
213 // A second run should have nothing to produce.
214 SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
215 EXPECT_THAT(GetSnapshotHistograms(), IsEmpty());
216
217 // Create a new histogram and update existing ones. Should now report 3 items.
218 baz->Add(1969);
219 foo->Add(10);
220 bar->Add(20);
221 foobar->Add(4);
222 SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
223 EXPECT_THAT(GetSnapshotHistograms(),
224 UnorderedElementsAre(
225 HistogramData{"_foo", /*total_count=*/1, /*sum=*/10},
226 HistogramData{"_bar", /*total_count=*/1, /*sum=*/20},
227 HistogramData{"_baz", /*total_count=*/1, /*sum=*/1969},
228 HistogramData{"_foobar", /*total_count=*/1, /*sum=*/4}));
229
230 // Ensure that deregistering does a final merge of the data.
231 foo->Add(10);
232 bar->Add(20);
233 DeregisterSubprocessAllocator(123);
234 // Do not call MergeHistogramDeltas() here, because the call to
235 // DeregisterSubprocessAllocator() should have already scheduled a task to
236 // merge the histograms.
237 EXPECT_THAT(GetSnapshotHistograms(), IsEmpty());
238 task_environment()->RunUntilIdle();
239 EXPECT_THAT(GetSnapshotHistograms(),
240 UnorderedElementsAre(
241 HistogramData{"_foo", /*total_count=*/1, /*sum=*/10},
242 HistogramData{"_bar", /*total_count=*/1, /*sum=*/20}));
243 // The allocator should have been released after deregistering.
244 EXPECT_TRUE(duplicate_allocator_destroyed);
245
246 // Further snapshots should be empty even if things have changed.
247 foo->Add(10);
248 bar->Add(20);
249 SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
250 EXPECT_THAT(GetSnapshotHistograms(), IsEmpty());
251 }
252
TEST_F(SubprocessMetricsProviderTest,SnapshotMetricsAsync)253 TEST_F(SubprocessMetricsProviderTest, SnapshotMetricsAsync) {
254 base::HistogramBase* foo = base::Histogram::FactoryGet("_foo", 1, 100, 10, 0);
255 base::HistogramBase* bar = base::Histogram::FactoryGet("_bar", 1, 100, 10, 0);
256 base::HistogramBase* baz = base::Histogram::FactoryGet("_baz", 1, 100, 10, 0);
257 base::HistogramBase* foobar = base::SparseHistogram::FactoryGet(
258 "_foobar", base::HistogramBase::kUmaTargetedHistogramFlag);
259 foo->Add(42);
260 bar->Add(84);
261 foobar->Add(1);
262 foobar->Add(2);
263 foobar->Add(3);
264
265 // Register a new allocator that duplicates the global one.
266 base::GlobalHistogramAllocator* global_allocator(
267 base::GlobalHistogramAllocator::ReleaseForTesting());
268 RegisterSubprocessAllocator(123, CreateDuplicateAllocator(global_allocator));
269
270 // Recording should find the two histograms created in persistent memory.
271 SubprocessMetricsProvider::MergeHistogramDeltasForTesting(
272 /*async=*/true, /*done_callback=*/task_environment()->QuitClosure());
273 EXPECT_THAT(GetSnapshotHistograms(), IsEmpty());
274 task_environment()->RunUntilQuit();
275 EXPECT_THAT(GetSnapshotHistograms(),
276 UnorderedElementsAre(
277 HistogramData{"_foo", /*total_count=*/1, /*sum=*/42},
278 HistogramData{"_bar", /*total_count=*/1, /*sum=*/84},
279 HistogramData{"_foobar", /*total_count=*/3, /*sum=*/6}));
280
281 // A second run should have nothing to produce.
282 SubprocessMetricsProvider::MergeHistogramDeltasForTesting(
283 /*async=*/true, /*done_callback=*/task_environment()->QuitClosure());
284 EXPECT_THAT(GetSnapshotHistograms(), IsEmpty());
285 task_environment()->RunUntilQuit();
286 EXPECT_THAT(GetSnapshotHistograms(), IsEmpty());
287
288 // Create a new histogram and update existing ones. Should now report 3 items.
289 baz->Add(1969);
290 foo->Add(10);
291 bar->Add(20);
292 foobar->Add(4);
293 SubprocessMetricsProvider::MergeHistogramDeltasForTesting(
294 /*async=*/true, /*done_callback=*/task_environment()->QuitClosure());
295 EXPECT_THAT(GetSnapshotHistograms(), IsEmpty());
296 task_environment()->RunUntilQuit();
297 EXPECT_THAT(GetSnapshotHistograms(),
298 UnorderedElementsAre(
299 HistogramData{"_foo", /*total_count=*/1, /*sum=*/10},
300 HistogramData{"_bar", /*total_count=*/1, /*sum=*/20},
301 HistogramData{"_baz", /*total_count=*/1, /*sum=*/1969},
302 HistogramData{"_foobar", /*total_count=*/1, /*sum=*/4}));
303
304 // Ensure that deregistering does a final merge of the data.
305 foo->Add(10);
306 bar->Add(20);
307 DeregisterSubprocessAllocator(123);
308 // Do not call MergeHistogramDeltas() here, because the call to
309 // DeregisterSubprocessAllocator() should have already scheduled a task to
310 // merge the histograms.
311 EXPECT_THAT(GetSnapshotHistograms(), IsEmpty());
312 task_environment()->RunUntilIdle();
313 EXPECT_THAT(GetSnapshotHistograms(),
314 UnorderedElementsAre(
315 HistogramData{"_foo", /*total_count=*/1, /*sum=*/10},
316 HistogramData{"_bar", /*total_count=*/1, /*sum=*/20}));
317
318 // Further snapshots should be empty even if things have changed.
319 foo->Add(10);
320 bar->Add(20);
321 SubprocessMetricsProvider::MergeHistogramDeltasForTesting(
322 /*async=*/true, /*done_callback=*/task_environment()->QuitClosure());
323 EXPECT_THAT(GetSnapshotHistograms(), IsEmpty());
324 task_environment()->RunUntilQuit();
325 EXPECT_THAT(GetSnapshotHistograms(), IsEmpty());
326 }
327
328 // Verifies that it is fine to deregister an allocator even if background tasks
329 // that access it are still pending/running.
TEST_F(SubprocessMetricsProviderTest,AllocatorRefCounted)330 TEST_F(SubprocessMetricsProviderTest, AllocatorRefCounted) {
331 base::HistogramBase* foo = base::Histogram::FactoryGet("_foo", 1, 100, 10, 0);
332 base::HistogramBase* bar = base::Histogram::FactoryGet("_bar", 1, 100, 10, 0);
333 base::HistogramBase* baz = base::SparseHistogram::FactoryGet(
334 "_baz", base::HistogramBase::kUmaTargetedHistogramFlag);
335 base::HistogramBase* foobar = base::SparseHistogram::FactoryGet(
336 "_foobar", base::HistogramBase::kUmaTargetedHistogramFlag);
337 foo->Add(42);
338 bar->Add(84);
339 baz->Add(1);
340 baz->Add(2);
341 baz->Add(3);
342 foobar->Add(4);
343 foobar->Add(5);
344 foobar->Add(6);
345
346 // Register a new allocator that duplicates the global one.
347 base::GlobalHistogramAllocator* global_allocator(
348 base::GlobalHistogramAllocator::ReleaseForTesting());
349 auto duplicate_allocator = CreateDuplicateAllocator(global_allocator);
350 bool duplicate_allocator_destroyed = false;
351 duplicate_allocator->SetDestroyedCallback(base::BindLambdaForTesting(
352 [&] { duplicate_allocator_destroyed = true; }));
353 RegisterSubprocessAllocator(123, std::move(duplicate_allocator));
354
355 // Merge histogram deltas. This will be done asynchronously.
356 SubprocessMetricsProvider::MergeHistogramDeltasForTesting(
357 /*async=*/true, /*done_callback=*/base::DoNothing());
358 // Deregister the allocator. This will be done asynchronously.
359 DeregisterSubprocessAllocator(123);
360
361 // The call to DeregisterSubprocessAllocator() above will have removed the
362 // allocator from the internal map. However, the allocator should not have
363 // been freed yet as there are still background tasks pending/running
364 // that have a reference to it (i.e., the tasks from MergeHistogramDeltas()
365 // and DeregisterSubprocessAllocator()).
366 ASSERT_FALSE(duplicate_allocator_destroyed);
367
368 // Run tasks.
369 task_environment()->RunUntilIdle();
370
371 // After all the tasks have finished, the allocator should have been released.
372 EXPECT_TRUE(duplicate_allocator_destroyed);
373
374 // Verify that the histograms were merged.
375 EXPECT_THAT(GetSnapshotHistograms(),
376 UnorderedElementsAre(
377 HistogramData{"_foo", /*total_count=*/1, /*sum=*/42},
378 HistogramData{"_bar", /*total_count=*/1, /*sum=*/84},
379 HistogramData{"_baz", /*total_count=*/3, /*sum=*/6},
380 HistogramData{"_foobar", /*total_count=*/3, /*sum=*/15}));
381 }
382
383 } // namespace metrics
384