File: adding_memory_infra_tracing.md

package info (click to toggle)
chromium-browser 57.0.2987.98-1~deb8u1
  • links: PTS, VCS
  • area: main
  • in suites: jessie
  • size: 2,637,852 kB
  • ctags: 2,544,394
  • sloc: cpp: 12,815,961; ansic: 3,676,222; python: 1,147,112; asm: 526,608; java: 523,212; xml: 286,794; perl: 92,654; sh: 86,408; objc: 73,271; makefile: 27,698; cs: 18,487; yacc: 13,031; tcl: 12,957; pascal: 4,875; ml: 4,716; lex: 3,904; sql: 3,862; ruby: 1,982; lisp: 1,508; php: 1,368; exp: 404; awk: 325; csh: 117; jsp: 39; sed: 37
file content (178 lines) | stat: -rw-r--r-- 8,343 bytes parent folder | download | duplicates (11)
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
# Adding MemoryInfra Tracing to a Component

If you have a component that manages memory allocations, you should be
registering and tracking those allocations with Chrome's MemoryInfra system.
This lets you:

 * See an overview of your allocations, giving insight into total size and
   breakdown.
 * Understand how your allocations change over time and how they are impacted by
   other parts of Chrome.
 * Catch regressions in your component's allocations size by setting up
   telemetry tests which monitor your allocation sizes under certain
   circumstances.

Some existing components that use MemoryInfra:

 * **Discardable Memory**: Tracks usage of discardable memory throughout Chrome.
 * **GPU**: Tracks OpenGL and other GPU object allocations.
 * **V8**: Tracks the heap size for JS.

[TOC]

## Overview

In order to hook into Chrome's MemoryInfra system, your component needs to do
two things:

 1. Create a [`MemoryDumpProvider`][mdp] for your component.
 2. Register and unregister you dump provider with the
    [`MemoryDumpManager`][mdm].

[mdp]: https://chromium.googlesource.com/chromium/src/+/master/base/trace_event/memory_dump_provider.h
[mdm]: https://chromium.googlesource.com/chromium/src/+/master/base/trace_event/memory_dump_manager.h

## Creating a Memory Dump Provider

You can implement a [`MemoryDumpProvider`][mdp] as a stand-alone class, or as an
additional interface on an existing class. For example, this interface is
frequently implemented on classes which manage a pool of allocations (see
[`cc::ResourcePool`][resource-pool] for an example).

A `MemoryDumpProvider` has one basic job, to implement `OnMemoryDump`. This
function is responsible for iterating over the resources allocated or tracked by
your component, and creating a [`MemoryAllocatorDump`][mem-alloc-dump] for each
using [`ProcessMemoryDump::CreateAllocatorDump`][pmd]. A simple example:

```cpp
bool MyComponent::OnMemoryDump(const MemoryDumpArgs& args,
                               ProcessMemoryDump* process_memory_dump) {
  for (const auto& allocation : my_allocations_) {
    auto* dump = process_memory_dump->CreateAllocatorDump(
        "path/to/my/component/allocation_" + allocation.id().ToString());
    dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
                    base::trace_event::MemoryAllocatorDump::kUnitsBytes,
                    allocation.size_bytes());

    // While you will typically have a kNameSize entry, you can add additional
    // entries to your dump with free-form names. In this example we also dump
    // an object's "free_size", assuming the object may not be entirely in use.
    dump->AddScalar("free_size",
                    base::trace_event::MemoryAllocatorDump::kUnitsBytes,
                    allocation.free_size_bytes());
  }
}
```

For many components, this may be all that is needed. See
[Handling Shared Memory Allocations](#Handling-Shared-Memory-Allocations) and
[Suballocations](#Suballocations) for information on more complex use cases.

[resource-pool]:  https://chromium.googlesource.com/chromium/src/+/master/cc/resources/resource_pool.h
[mem-alloc-dump]: https://chromium.googlesource.com/chromium/src/+/master/base/trace_event/memory_allocator_dump.h
[pmd]:            https://chromium.googlesource.com/chromium/src/+/master/base/trace_event/process_memory_dump.h

## Registering a Memory Dump Provider

Once you have created a [`MemoryDumpProvider`][mdp], you need to register it
with the [`MemoryDumpManager`][mdm] before the system can start polling it for
memory information. Registration is generally straightforward, and involves
calling `MemoryDumpManager::RegisterDumpProvider`:

```cpp
// Each process uses a singleton |MemoryDumpManager|.
base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
    my_memory_dump_provider_, my_single_thread_task_runner_);
```

In the above code, `my_memory_dump_provider_` is the `MemoryDumpProvider`
outlined in the previous section. `my_single_thread_task_runner_` is more
complex and may be a number of things:

 * Most commonly, if your component is always used from the main message loop,
   `my_single_thread_task_runner_` may just be
   [`base::ThreadTaskRunnerHandle::Get()`][task-runner-handle].
 * If your component already uses a custom `base::SingleThreadTaskRunner` for
   executing tasks on a specific thread, you should likely use this runner.

[task-runner-handle]: https://chromium.googlesource.com/chromium/src/+/master/base/thread_task_runner_handle.h

## Unregistration

Unregistration must happen on the thread belonging to the
`SingleThreadTaskRunner` provided at registration time. Unregistering on another
thread can lead to race conditions if tracing is active when the provider is
unregistered.

```cpp
base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
      my_memory_dump_provider_);
```

## Handling Shared Memory Allocations

When an allocation is shared between two components, it may be useful to dump
the allocation in both components, but you also want to avoid double-counting
the allocation. This can be achieved using the concept of _ownership edges_.
An ownership edge represents that the _source_ memory allocator dump owns a
_target_ memory allocator dump. If multiple source dumps own a single target,
then the cost of that target allocation will be split between the sources.
Additionally, importance can be added to a specific ownership edge, allowing
the highest importance source of that edge to claim the entire cost of the
target.

In the typical case, you will use [`ProcessMemoryDump`][pmd] to create a shared
global allocator dump. This dump will act as the target of all
component-specific dumps of a specific resource:

```cpp
// Component 1 is going to create a dump, source_mad, for an allocation,
// alloc_, which may be shared with other components / processes.
MyAllocationType* alloc_;
base::trace_event::MemoryAllocatorDump* source_mad;

// Component 1 creates and populates source_mad;
...

// In addition to creating a source dump, we must create a global shared
// target dump. This dump should be created with a unique global ID which can be
// generated any place the allocation is used. I recommend adding a global ID
// generation function to the allocation type.
base::trace_event::MemoryAllocatorDumpGUID guid(alloc_->GetGUIDString());

// From this global ID we can generate the parent allocator dump.
base::trace_event::MemoryAllocatorDump* target_mad =
    process_memory_dump->CreateSharedGlobalAllocatorDump(guid);

// We now create an ownership edge from the source dump to the target dump.
// When creating an edge, you can assign an importance to this edge. If all
// edges have the same importance, the size of the allocation will be split
// between all sources which create a dump for the allocation. If one
// edge has higher importance than the others, its source will be assigned the
// full size of the allocation.
const int kImportance = 1;
process_memory_dump->AddOwnershipEdge(
    source_mad->guid(), target_mad->guid(), kImportance);
```

If an allocation is being shared across process boundaries, it may be useful to
generate a global ID which incorporates the ID of the local process, preventing
two processes from generating colliding IDs. As it is not recommended to pass a
process ID between processes for security reasons, a function
`MemoryDumpManager::GetTracingProcessId` is provided which generates a unique ID
per process that can be passed with the resource without security concerns.
Frequently this ID is used to generate a global ID that is based on the
allocated resource's ID combined with the allocating process' tracing ID.

## Suballocations

Another advanced use case involves tracking sub-allocations of a larger
allocation. For instance, this is used in
[`gpu::gles2::TextureManager`][texture-manager] to dump both the suballocations
which make up a texture. To create a suballocation, instead of calling
[`ProcessMemoryDump::CreateAllocatorDump`][pmd] to create a
[`MemoryAllocatorDump`][mem-alloc-dump], you call
[`ProcessMemoryDump::AddSubAllocation`][pmd], providing the ID of the parent
allocation as the first parameter.

[texture-manager]: https://chromium.googlesource.com/chromium/src/+/master/gpu/command_buffer/service/texture_manager.cc