hjd | 0304f11 | 2016-11-14 22:36:53 | [diff] [blame] | 1 | # Adding MemoryInfra Tracing to a Component |
| 2 | |
| 3 | If you have a component that manages memory allocations, you should be |
| 4 | registering and tracking those allocations with Chrome's MemoryInfra system. |
| 5 | This lets you: |
| 6 | |
| 7 | * See an overview of your allocations, giving insight into total size and |
| 8 | breakdown. |
| 9 | * Understand how your allocations change over time and how they are impacted by |
| 10 | other parts of Chrome. |
| 11 | * Catch regressions in your component's allocations size by setting up |
| 12 | telemetry tests which monitor your allocation sizes under certain |
| 13 | circumstances. |
| 14 | |
| 15 | Some existing components that use MemoryInfra: |
| 16 | |
| 17 | * **Discardable Memory**: Tracks usage of discardable memory throughout Chrome. |
| 18 | * **GPU**: Tracks OpenGL and other GPU object allocations. |
| 19 | * **V8**: Tracks the heap size for JS. |
| 20 | |
| 21 | [TOC] |
| 22 | |
| 23 | ## Overview |
| 24 | |
| 25 | In order to hook into Chrome's MemoryInfra system, your component needs to do |
| 26 | two things: |
| 27 | |
| 28 | 1. Create a [`MemoryDumpProvider`][mdp] for your component. |
| 29 | 2. Register and unregister you dump provider with the |
| 30 | [`MemoryDumpManager`][mdm]. |
| 31 | |
John Palmer | 046f987 | 2021-05-24 01:24:56 | [diff] [blame] | 32 | [mdp]: https://chromium.googlesource.com/chromium/src/+/main/base/trace_event/memory_dump_provider.h |
| 33 | [mdm]: https://chromium.googlesource.com/chromium/src/+/main/base/trace_event/memory_dump_manager.h |
hjd | 0304f11 | 2016-11-14 22:36:53 | [diff] [blame] | 34 | |
| 35 | ## Creating a Memory Dump Provider |
| 36 | |
| 37 | You can implement a [`MemoryDumpProvider`][mdp] as a stand-alone class, or as an |
| 38 | additional interface on an existing class. For example, this interface is |
| 39 | frequently implemented on classes which manage a pool of allocations (see |
| 40 | [`cc::ResourcePool`][resource-pool] for an example). |
| 41 | |
| 42 | A `MemoryDumpProvider` has one basic job, to implement `OnMemoryDump`. This |
| 43 | function is responsible for iterating over the resources allocated or tracked by |
| 44 | your component, and creating a [`MemoryAllocatorDump`][mem-alloc-dump] for each |
| 45 | using [`ProcessMemoryDump::CreateAllocatorDump`][pmd]. A simple example: |
| 46 | |
| 47 | ```cpp |
| 48 | bool MyComponent::OnMemoryDump(const MemoryDumpArgs& args, |
| 49 | ProcessMemoryDump* process_memory_dump) { |
| 50 | for (const auto& allocation : my_allocations_) { |
| 51 | auto* dump = process_memory_dump->CreateAllocatorDump( |
| 52 | "path/to/my/component/allocation_" + allocation.id().ToString()); |
| 53 | dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize, |
| 54 | base::trace_event::MemoryAllocatorDump::kUnitsBytes, |
| 55 | allocation.size_bytes()); |
| 56 | |
| 57 | // While you will typically have a kNameSize entry, you can add additional |
| 58 | // entries to your dump with free-form names. In this example we also dump |
| 59 | // an object's "free_size", assuming the object may not be entirely in use. |
| 60 | dump->AddScalar("free_size", |
| 61 | base::trace_event::MemoryAllocatorDump::kUnitsBytes, |
| 62 | allocation.free_size_bytes()); |
| 63 | } |
| 64 | } |
| 65 | ``` |
| 66 | |
| 67 | For many components, this may be all that is needed. See |
| 68 | [Handling Shared Memory Allocations](#Handling-Shared-Memory-Allocations) and |
| 69 | [Suballocations](#Suballocations) for information on more complex use cases. |
| 70 | |
John Palmer | 046f987 | 2021-05-24 01:24:56 | [diff] [blame] | 71 | [resource-pool]: https://chromium.googlesource.com/chromium/src/+/main/cc/resources/resource_pool.h |
| 72 | [mem-alloc-dump]: https://chromium.googlesource.com/chromium/src/+/main/base/trace_event/memory_allocator_dump.h |
| 73 | [pmd]: https://chromium.googlesource.com/chromium/src/+/main/base/trace_event/process_memory_dump.h |
hjd | 0304f11 | 2016-11-14 22:36:53 | [diff] [blame] | 74 | |
| 75 | ## Registering a Memory Dump Provider |
| 76 | |
| 77 | Once you have created a [`MemoryDumpProvider`][mdp], you need to register it |
| 78 | with the [`MemoryDumpManager`][mdm] before the system can start polling it for |
| 79 | memory information. Registration is generally straightforward, and involves |
| 80 | calling `MemoryDumpManager::RegisterDumpProvider`: |
| 81 | |
| 82 | ```cpp |
| 83 | // Each process uses a singleton |MemoryDumpManager|. |
| 84 | base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider( |
| 85 | my_memory_dump_provider_, my_single_thread_task_runner_); |
| 86 | ``` |
| 87 | |
| 88 | In the above code, `my_memory_dump_provider_` is the `MemoryDumpProvider` |
| 89 | outlined in the previous section. `my_single_thread_task_runner_` is more |
| 90 | complex and may be a number of things: |
| 91 | |
| 92 | * Most commonly, if your component is always used from the main message loop, |
| 93 | `my_single_thread_task_runner_` may just be |
Sean Maher | 70f294293 | 2023-01-04 22:15:06 | [diff] [blame] | 94 | [`base::SingleThreadTaskRunner::GetCurrentDefault()`][task-runner-handle]. |
hjd | 0304f11 | 2016-11-14 22:36:53 | [diff] [blame] | 95 | * If your component already uses a custom `base::SingleThreadTaskRunner` for |
| 96 | executing tasks on a specific thread, you should likely use this runner. |
| 97 | |
Sean Maher | edb604e0 | 2023-01-12 15:18:20 | [diff] [blame] | 98 | [task-runner-current-default-handle]: https://chromium.googlesource.com/chromium/src/+/main/base/task/single_thread_task_runner.h |
hjd | 0304f11 | 2016-11-14 22:36:53 | [diff] [blame] | 99 | |
| 100 | ## Unregistration |
| 101 | |
| 102 | Unregistration must happen on the thread belonging to the |
| 103 | `SingleThreadTaskRunner` provided at registration time. Unregistering on another |
| 104 | thread can lead to race conditions if tracing is active when the provider is |
| 105 | unregistered. |
| 106 | |
| 107 | ```cpp |
| 108 | base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider( |
| 109 | my_memory_dump_provider_); |
| 110 | ``` |
| 111 | |
| 112 | ## Handling Shared Memory Allocations |
| 113 | |
| 114 | When an allocation is shared between two components, it may be useful to dump |
| 115 | the allocation in both components, but you also want to avoid double-counting |
| 116 | the allocation. This can be achieved using the concept of _ownership edges_. |
| 117 | An ownership edge represents that the _source_ memory allocator dump owns a |
| 118 | _target_ memory allocator dump. If multiple source dumps own a single target, |
| 119 | then the cost of that target allocation will be split between the sources. |
| 120 | Additionally, importance can be added to a specific ownership edge, allowing |
| 121 | the highest importance source of that edge to claim the entire cost of the |
| 122 | target. |
| 123 | |
| 124 | In the typical case, you will use [`ProcessMemoryDump`][pmd] to create a shared |
| 125 | global allocator dump. This dump will act as the target of all |
| 126 | component-specific dumps of a specific resource: |
| 127 | |
| 128 | ```cpp |
| 129 | // Component 1 is going to create a dump, source_mad, for an allocation, |
| 130 | // alloc_, which may be shared with other components / processes. |
| 131 | MyAllocationType* alloc_; |
| 132 | base::trace_event::MemoryAllocatorDump* source_mad; |
| 133 | |
| 134 | // Component 1 creates and populates source_mad; |
| 135 | ... |
| 136 | |
| 137 | // In addition to creating a source dump, we must create a global shared |
| 138 | // target dump. This dump should be created with a unique global ID which can be |
| 139 | // generated any place the allocation is used. I recommend adding a global ID |
| 140 | // generation function to the allocation type. |
| 141 | base::trace_event::MemoryAllocatorDumpGUID guid(alloc_->GetGUIDString()); |
| 142 | |
| 143 | // From this global ID we can generate the parent allocator dump. |
| 144 | base::trace_event::MemoryAllocatorDump* target_mad = |
| 145 | process_memory_dump->CreateSharedGlobalAllocatorDump(guid); |
| 146 | |
| 147 | // We now create an ownership edge from the source dump to the target dump. |
| 148 | // When creating an edge, you can assign an importance to this edge. If all |
| 149 | // edges have the same importance, the size of the allocation will be split |
| 150 | // between all sources which create a dump for the allocation. If one |
| 151 | // edge has higher importance than the others, its source will be assigned the |
| 152 | // full size of the allocation. |
| 153 | const int kImportance = 1; |
| 154 | process_memory_dump->AddOwnershipEdge( |
| 155 | source_mad->guid(), target_mad->guid(), kImportance); |
| 156 | ``` |
| 157 | |
| 158 | If an allocation is being shared across process boundaries, it may be useful to |
| 159 | generate a global ID which incorporates the ID of the local process, preventing |
| 160 | two processes from generating colliding IDs. As it is not recommended to pass a |
| 161 | process ID between processes for security reasons, a function |
| 162 | `MemoryDumpManager::GetTracingProcessId` is provided which generates a unique ID |
| 163 | per process that can be passed with the resource without security concerns. |
| 164 | Frequently this ID is used to generate a global ID that is based on the |
| 165 | allocated resource's ID combined with the allocating process' tracing ID. |
| 166 | |
| 167 | ## Suballocations |
| 168 | |
| 169 | Another advanced use case involves tracking sub-allocations of a larger |
| 170 | allocation. For instance, this is used in |
| 171 | [`gpu::gles2::TextureManager`][texture-manager] to dump both the suballocations |
| 172 | which make up a texture. To create a suballocation, instead of calling |
| 173 | [`ProcessMemoryDump::CreateAllocatorDump`][pmd] to create a |
| 174 | [`MemoryAllocatorDump`][mem-alloc-dump], you call |
| 175 | [`ProcessMemoryDump::AddSubAllocation`][pmd], providing the ID of the parent |
| 176 | allocation as the first parameter. |
| 177 | |
John Palmer | 046f987 | 2021-05-24 01:24:56 | [diff] [blame] | 178 | [texture-manager]: https://chromium.googlesource.com/chromium/src/+/main/gpu/command_buffer/service/texture_manager.cc |