File: subprocess_metrics_provider.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (323 lines) | stat: -rw-r--r-- 12,033 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/metrics/content/subprocess_metrics_provider.h"

#include <utility>

#include "base/debug/crash_logging.h"
#include "base/debug/dump_without_crashing.h"
#include "base/debug/leak_annotations.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/histogram_base.h"
#include "base/metrics/persistent_histogram_allocator.h"
#include "base/metrics/persistent_memory_allocator.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "components/metrics/metrics_features.h"
#include "content/public/browser/browser_child_process_host.h"
#include "content/public/browser/browser_child_process_host_iterator.h"
#include "content/public/browser/child_process_data.h"

namespace metrics {
namespace {

SubprocessMetricsProvider* g_subprocess_metrics_provider = nullptr;

scoped_refptr<base::TaskRunner> CreateTaskRunner() {
  // This task runner must block shutdown to ensure metrics are not lost.
  return base::ThreadPool::CreateTaskRunner(
      {base::TaskPriority::BEST_EFFORT,
       base::TaskShutdownBehavior::BLOCK_SHUTDOWN});
}

}  // namespace

// static
bool SubprocessMetricsProvider::CreateInstance() {
  if (g_subprocess_metrics_provider) {
    return false;
  }
  g_subprocess_metrics_provider = new SubprocessMetricsProvider();
  ANNOTATE_LEAKING_OBJECT_PTR(g_subprocess_metrics_provider);
  return true;
}

// static
SubprocessMetricsProvider* SubprocessMetricsProvider::GetInstance() {
  return g_subprocess_metrics_provider;
}

// static
void SubprocessMetricsProvider::MergeHistogramDeltasForTesting(
    bool async,
    base::OnceClosure done_callback) {
  GetInstance()->MergeHistogramDeltas(async, std::move(done_callback));
}

SubprocessMetricsProvider::RefCountedAllocator::RefCountedAllocator(
    std::unique_ptr<base::PersistentHistogramAllocator> allocator)
    : allocator_(std::move(allocator)) {
  CHECK(allocator_);
}

SubprocessMetricsProvider::RefCountedAllocator::~RefCountedAllocator() =
    default;

SubprocessMetricsProvider::SubprocessMetricsProvider()
    : task_runner_(CreateTaskRunner()) {
  base::StatisticsRecorder::RegisterHistogramProvider(
      weak_ptr_factory_.GetWeakPtr());
  content::BrowserChildProcessObserver::Add(this);
  g_subprocess_metrics_provider = this;

  // Ensure no child processes currently exist so that we do not miss any.
  CHECK(content::RenderProcessHost::AllHostsIterator().IsAtEnd());
  CHECK(content::BrowserChildProcessHostIterator().Done());
}

SubprocessMetricsProvider::~SubprocessMetricsProvider() {
  // This object should never be deleted since it is leaky.
  NOTREACHED();
}

void SubprocessMetricsProvider::RegisterSubprocessAllocator(
    int id,
    std::unique_ptr<base::PersistentHistogramAllocator> allocator) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  CHECK(allocator);

  // Pass a custom RangesManager so that we do not register the BucketRanges
  // with the global StatisticsRecorder when creating histogram objects using
  // the allocator's underlying data. This avoids unnecessary contention on the
  // global StatisticsRecorder lock.
  // Note: Since |allocator| may be merged from different threads concurrently,
  // for example on the UI thread and on a background thread, we must use
  // ThreadSafeRangesManager.
  allocator->SetRangesManager(new base::ThreadSafeRangesManager());
  // Insert the allocator into the internal map and verify that there was no
  // allocator with the same ID already.
  auto result = allocators_by_id_.emplace(
      id, base::MakeRefCounted<RefCountedAllocator>(std::move(allocator)));
  CHECK(result.second);
}

void SubprocessMetricsProvider::DeregisterSubprocessAllocator(int id) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  auto it = allocators_by_id_.find(id);
  if (it == allocators_by_id_.end()) {
    return;
  }

  // Extract the matching allocator from the list of active ones. It is possible
  // that a background task is currently holding a reference to it. Removing it
  // from the internal map is fine though, as it is refcounted.
  scoped_refptr<RefCountedAllocator> allocator = std::move(it->second);
  allocators_by_id_.erase(it);
  CHECK(allocator);

  // Merge the last deltas from the allocator before releasing the ref (and
  // deleting if the last one).
  auto* allocator_ptr = allocator.get();
  task_runner_->PostTaskAndReply(
      FROM_HERE,
      base::BindOnce(
          &SubprocessMetricsProvider::MergeHistogramDeltasFromAllocator, id,
          // Unretained is needed to pass a refcounted class as a raw pointer.
          // It is safe because it is kept alive by the reply task.
          base::Unretained(allocator_ptr)),
      base::BindOnce(
          &SubprocessMetricsProvider::OnMergeHistogramDeltasFromAllocator,
          std::move(allocator)));
}

void SubprocessMetricsProvider::MergeHistogramDeltas(
    bool async,
    base::OnceClosure done_callback) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  if (async) {
    // Make a copy of the internal allocators map (with its own refptrs) to pass
    // to the background task.
    auto allocators = std::make_unique<AllocatorByIdMap>(allocators_by_id_);
    auto* allocators_ptr = allocators.get();

    // This is intentionally not posted to |task_runner_| because not running
    // this task does not imply data loss, so no point in blocking shutdown
    // (hence CONTINUE_ON_SHUTDOWN). However, there might be some contention on
    // the StatisticsRecorder between this task and those posted to
    // |task_runner_|.
    base::ThreadPool::PostTaskAndReply(
        FROM_HERE,
        {base::TaskPriority::BEST_EFFORT,
         base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
        base::BindOnce(&MergeHistogramDeltasFromAllocators, allocators_ptr),
        base::BindOnce(
            &SubprocessMetricsProvider::OnMergeHistogramDeltasFromAllocators,
            std::move(allocators), std::move(done_callback)));
  } else {
    MergeHistogramDeltasFromAllocators(&allocators_by_id_);
    std::move(done_callback).Run();
  }
}

void SubprocessMetricsProvider::BrowserChildProcessLaunchedAndConnected(
    const content::ChildProcessData& data) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  // See if the new process has a memory allocator and take control of it if so.
  // This call can only be made on the browser's IO thread.
  content::BrowserChildProcessHost* host =
      content::BrowserChildProcessHost::FromID(data.id);
  // |host| should not be null, but such cases have been observed in the wild so
  // gracefully handle this scenario.
  if (!host) {
    return;
  }

  std::unique_ptr<base::PersistentMemoryAllocator> allocator =
      host->TakeMetricsAllocator();
  // The allocator can be null in tests.
  if (!allocator) {
    return;
  }

  RegisterSubprocessAllocator(
      data.id, std::make_unique<base::PersistentHistogramAllocator>(
                   std::move(allocator)));
}

void SubprocessMetricsProvider::BrowserChildProcessHostDisconnected(
    const content::ChildProcessData& data) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DeregisterSubprocessAllocator(data.id);
}

void SubprocessMetricsProvider::BrowserChildProcessCrashed(
    const content::ChildProcessData& data,
    const content::ChildProcessTerminationInfo& info) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DeregisterSubprocessAllocator(data.id);
}

void SubprocessMetricsProvider::BrowserChildProcessKilled(
    const content::ChildProcessData& data,
    const content::ChildProcessTerminationInfo& info) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DeregisterSubprocessAllocator(data.id);
}

void SubprocessMetricsProvider::OnRenderProcessHostCreated(
    content::RenderProcessHost* host) {
  // Sometimes, the same host will cause multiple notifications in tests so
  // could possibly do the same in a release build.
  if (!scoped_observations_.IsObservingSource(host)) {
    scoped_observations_.AddObservation(host);
  }
}

void SubprocessMetricsProvider::RenderProcessReady(
    content::RenderProcessHost* host) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  // If the render-process-host passed a persistent-memory-allocator to the
  // renderer process, extract it and register it here.
  std::unique_ptr<base::PersistentMemoryAllocator> allocator =
      host->TakeMetricsAllocator();
  if (allocator) {
    RegisterSubprocessAllocator(
        host->GetDeprecatedID(),
        std::make_unique<base::PersistentHistogramAllocator>(
            std::move(allocator)));
  }
}

void SubprocessMetricsProvider::RenderProcessExited(
    content::RenderProcessHost* host,
    const content::ChildProcessTerminationInfo& info) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  DeregisterSubprocessAllocator(host->GetDeprecatedID());
}

void SubprocessMetricsProvider::RenderProcessHostDestroyed(
    content::RenderProcessHost* host) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  // It's possible for a Renderer to terminate without RenderProcessExited
  // (above) being called so it's necessary to de-register also upon the
  // destruction of the host. If both get called, no harm is done.

  DeregisterSubprocessAllocator(host->GetDeprecatedID());
  scoped_observations_.RemoveObservation(host);
}

void SubprocessMetricsProvider::RecreateTaskRunnerForTesting() {
  task_runner_ = CreateTaskRunner();
}

// static
void SubprocessMetricsProvider::MergeHistogramDeltasFromAllocator(
    int id,
    RefCountedAllocator* allocator) {
  DCHECK(allocator);

  int histogram_count = 0;
  base::PersistentHistogramAllocator* allocator_ptr = allocator->allocator();
  base::PersistentHistogramAllocator::Iterator hist_iter(allocator_ptr);
  while (true) {
    std::unique_ptr<base::HistogramBase> histogram = hist_iter.GetNext();
    if (!histogram) {
      break;
    }
    // We expect histograms to match as subprocesses shouldn't have version skew
    // with the browser process.
    bool merge_success =
        allocator_ptr->MergeHistogramDeltaToStatisticsRecorder(histogram.get());

    // When merging child process histograms into the parent, we expect the
    // merge operation to succeed. If it doesn't, it means the histograms have
    // different types or buckets, which indicates a programming error (i.e.
    // non-matching logging code across browser and child for a histogram). In
    // this case DumpWithoutCrashing() with a crash key with the histogram name.
    if (!merge_success) {
      SCOPED_CRASH_KEY_STRING256("SubprocessMetricsProvider", "histogram",
                                 histogram->histogram_name());
      base::debug::DumpWithoutCrashing();
    }
    ++histogram_count;
  }

  DVLOG(1) << "Reported " << histogram_count << " histograms from subprocess #"
           << id;
}

// static
void SubprocessMetricsProvider::MergeHistogramDeltasFromAllocators(
    AllocatorByIdMap* allocators) {
  for (const auto& iter : *allocators) {
    MergeHistogramDeltasFromAllocator(iter.first, iter.second.get());
  }
}

// static
void SubprocessMetricsProvider::OnMergeHistogramDeltasFromAllocator(
    scoped_refptr<RefCountedAllocator> allocator) {
  // This method does nothing except have ownership on |allocator|. When this
  // method exits, |allocator| will be released (unless there are background
  // tasks currently holding references).
}

// static
void SubprocessMetricsProvider::OnMergeHistogramDeltasFromAllocators(
    std::unique_ptr<AllocatorByIdMap> allocators,
    base::OnceClosure done_callback) {
  std::move(done_callback).Run();
  // When this method exits, |allocators| will be released. It's possible some
  // allocators are from subprocesses that have already been deregistered, so
  // they will also be released here (assuming no other background tasks
  // currently hold references).
}

}  // namespace metrics