Donner SVG 0.8.0-pre
SVG editor and embeddable C⁠+⁠+⁠20 engine.
Loading...
Searching...
No Matches
MemoryAttribution.h File Reference

Per-frame attribution of retained bytes by category (the single-canvas presenter work). More...

#include <cstddef>
#include <cstdint>
#include "donner/base/HeapSizeHistogram.h"
Include dependency graph for MemoryAttribution.h:

Classes

struct  donner::MemoryAttributionSample
 One frame's view of where the process's bytes are. More...
struct  donner::MemoryStageSample
 Net allocator movement attributed to each stage. More...
class  donner::ScopedHeapDelta
 Brackets one stage of the frame and attributes the allocator movement across it. More...

Namespaces

namespace  donner
 Top-level Donner namespace, which is split into different sub-namespaces such as donner::svg and donner::css.

Enumerations

enum class  donner::MemoryCategory : std::uint8_t {
  donner::CompositorSegmentBitmaps = 0 ,
  donner::CompositorSegmentTextures = 1 ,
  donner::CompositorLayerBitmaps = 2 ,
  donner::CompositorLayerTextures = 3 ,
  donner::RenderResultTiles = 4 ,
  donner::WorkerFrameSnapshot = 5 ,
  donner::PresentationTiles = 6 ,
  donner::PresentationOverviewTiles = 7 ,
  donner::PresentationRetired = 8 ,
  donner::LayerThumbnails = 9
}
 A subsystem that retains or allocates pixel-scale memory. Kept small and stable: values are published to the stats surface as array indices. More...
enum class  donner::MemoryStage : std::uint8_t {
  donner::WorkerRenderFrame = 0 ,
  donner::WorkerBuildPreview = 1 ,
  donner::WorkerFinalSnapshot = 2 ,
  donner::WorkerOther = 3 ,
  donner::AppPollResult = 4 ,
  donner::AppUiFrame = 5 ,
  donner::AppHostFrame = 6 ,
  donner::AppInput = 7
}
 A bracketed stage of the frame, measured by what it does to the allocator rather than by what a subsystem says it holds. More...

Functions

const char * donner::MemoryCategoryName (MemoryCategory category)
 Stable short names for MemoryCategory, indexed by the enum value. Used as the key set of the published stats object so a reader does not have to keep an index table in sync.
void donner::SetRetainedBytes (MemoryCategory category, std::uint64_t bytes)
 Publish bytes as the current retained total for category, replacing whatever the category last published.
void donner::SetEntryCount (MemoryCategory category, std::uint64_t count)
 Publish count as the current live object count for category.
void donner::AddTransientBytes (MemoryCategory category, std::uint64_t bytes)
 Add bytes to the frame's allocation flow for category.
MemoryAttributionSample donner::SampleMemoryAttribution ()
 Read every counter, fold the per-frame flows into their high waters, and open a new frame window. Call once per frame from the publishing thread.
MemoryAttributionSample donner::PeekMemoryAttribution ()
 Read every counter without closing the frame window.
const char * donner::MemoryStageName (MemoryStage stage)
 Stable short names for MemoryStage, indexed by the enum value.
MemoryStageSample donner::SampleMemoryStages ()
 Read the stage counters and open a new frame window for them.
AllocTag donner::AllocTagForStage (MemoryStage stage)
 The AllocTag that names the same part of the frame as stage, so a stage bracket and a large-block tag never disagree about what to call it.

Variables

constexpr std::size_t donner::kMemoryCategoryCount = 10
 Number of distinct MemoryCategory values.
constexpr std::size_t donner::kMemoryStageCount = 8
 Number of distinct MemoryStage values.

Detailed Description

Per-frame attribution of retained bytes by category (the single-canvas presenter work).

The browser editor links with -sMAXIMUM_MEMORY=512MB. When linear memory reaches that ceiling emscripten_resize_heap fails and the module aborts, so "how many bytes does a frame retain, and in which subsystem" is a correctness question in the browser, not a tuning question. A single emscripten_get_heap_size() number cannot answer it: linear memory is a high-water mark that never shrinks, so it reports the worst instant since boot without saying which subsystem produced it.

This probe splits that number three ways:

  • Linear memory (wasmHeapBytes): the wasm memory.size, i.e. the allocator's sbrk high water. This is what hits MAXIMUM_MEMORY.
  • Live malloc bytes (mallocLiveBytes): what is actually reachable. A gap between this and linear memory is allocator retention - fragmentation or a large transient peak - not a leak.
  • Per-category retained bytes: what each subsystem believes it is holding, published by that subsystem. Their sum bounds the part of mallocLiveBytes this codebase can explain.

Two kinds of counter, because two kinds of growth need different questions asked of them:

  • SetRetainedBytes is a level. The owner republishes its current total whenever it changes; the probe tracks the high water. Unbounded retention shows up as a level that climbs and never falls.
  • AddTransientBytes is a per-frame flow. The owner adds what it allocated during the frame; SampleMemoryAttribution closes the window. A per-frame allocation that should have been a reuse shows up as a flow that stays high while the level stays flat, which is exactly the shape that inflates linear memory without leaking.

Counters are process-wide atomics rather than thread-locals: the render thread owns the compositor categories and the app thread owns the presentation categories, and the publisher must read both.


Class Documentation

◆ donner::MemoryAttributionSample

struct donner::MemoryAttributionSample

One frame's view of where the process's bytes are.

Class Members
uint64_t entryCounts[kMemoryCategoryCount] = {} Live object count per category (textures, tiles), where the owner tracks one.
uint64_t mallocArenaBytes = 0 Total space the allocator has taken from the system, from mallinfo.
uint64_t mallocFreeBytes = 0 Bytes in the allocator's free lists, from mallinfo.
uint64_t mallocLiveBytes = 0 Bytes in allocated malloc blocks, from mallinfo; 0 where unavailable.
uint64_t mallocLiveHighWaterBytes = 0 Highest mallocLiveBytes seen since boot.
uint64_t retainedBytes[kMemoryCategoryCount] = {} Current retained bytes per category, indexed by MemoryCategory.
uint64_t retainedHighWaterBytes[kMemoryCategoryCount] = {} Highest retainedBytes seen since boot, per category.
uint64_t totalRetainedBytes = 0 Sum of retainedBytes across categories.
uint64_t totalRetainedHighWaterBytes = 0 Highest totalRetainedBytes seen since boot.
uint64_t transientBytes[kMemoryCategoryCount] = {} Bytes allocated during the frame just closed, per category.
uint64_t transientHighWaterBytes[kMemoryCategoryCount] = {} Highest single-frame transientBytes seen since boot, per category.
uint64_t wasmHeapBytes = 0 wasm linear memory size in bytes; 0 where the platform has no such notion.
uint64_t wasmHeapHighWaterBytes = 0 Highest wasmHeapBytes seen since boot.

◆ donner::MemoryStageSample

struct donner::MemoryStageSample

Net allocator movement attributed to each stage.

Class Members
int64_t cumulativeNetBytes[kMemoryStageCount] = {} Net live-heap change since boot, per stage. A stage that is in balance hovers near zero however long the session runs; a stage that retains climbs without bound, and this is the number that names it.
uint64_t entries[kMemoryStageCount] = {} Times the stage was entered since boot.
int64_t maxNetBytes[kMemoryStageCount] = {} Largest single-entry net growth seen for the stage, in bytes.
int64_t netBytes[kMemoryStageCount] = {} Net live-heap change during the frame just closed, per stage. Negative means the stage freed more than it allocated.