# 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/+/main/base/trace_event/memory_dump_provider.h [mdm]: https://chromium.googlesource.com/chromium/src/+/main/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/+/main/cc/resources/resource_pool.h [mem-alloc-dump]: https://chromium.googlesource.com/chromium/src/+/main/base/trace_event/memory_allocator_dump.h [pmd]: https://chromium.googlesource.com/chromium/src/+/main/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/+/main/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/+/main/gpu/command_buffer/service/texture_manager.cc