Skip to documentation
SLOP

tiny.memtrace.Analyzer

Reference tiny.memtrace Analyzer

Defined in analysis.

API (30)

Actions

Public operations.

Fields and members

Public fields and members.

No direct callersNo direct callsanalysisAnalyzer
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Source: lib/memtrace/src/analysis.zig:356

zig
pub const Analyzer = struct {    allocator: Allocator,    allocators: std.AutoHashMapUnmanaged(u32, AllocatorState) = .{},    allocations: allocations.Map(AllocationRecord) = .{},    scopes: std.StringHashMapUnmanaged(ScopeCounters) = .{},    sites: std.AutoHashMapUnmanaged(u64, SiteCounters) = .{},    site_detail_sizes: std.AutoHashMapUnmanaged(usize, SiteCounters) = .{},    site_detail_scopes: std.StringHashMapUnmanaged(SiteCounters) = .{},    site_detail_return_address: ?u64 = null,    active_scopes: std.AutoHashMapUnmanaged(u32, u64) = .{},    scope_paths: std.AutoHashMapUnmanaged(u32, []const u8) = .{},    track_sites: bool = true,    layer_filter: event_mod.LayerFilter = .backing,    counters: Counters = .{},    physical: PhysicalCounters = .{},    mappings: mappings.Ledger = .{},    integrity_counters: IntegrityCounters = .{},    events: u64 = 0,    first_sequence: ?u64 = null,    last_sequence: ?u64 = null,    start_sequence: ?u64 = null,    stop_sequence: ?u64 = null,    pub fn init(allocator: Allocator) Analyzer {        return .{ .allocator = allocator };    }    pub fn initForLayer(        allocator: Allocator,        layer_filter: event_mod.LayerFilter,    ) Analyzer {        return .{            .allocator = allocator,            .layer_filter = layer_filter,        };    }    pub fn deinit(self: *Analyzer) void {        var iterator = self.scopes.iterator();        while (iterator.next()) |entry| self.allocator.free(entry.key_ptr.*);        self.scopes.deinit(self.allocator);        self.sites.deinit(self.allocator);        self.site_detail_sizes.deinit(self.allocator);        self.site_detail_scopes.deinit(self.allocator);        self.active_scopes.deinit(self.allocator);        self.scope_paths.deinit(self.allocator);        self.allocators.deinit(self.allocator);        self.allocations.deinit(self.allocator);        self.mappings.deinit(self.allocator);        self.* = undefined;    }    pub fn ingestJsonlBytes(self: *Analyzer, bytes: []const u8) !void {        var lines = std.mem.splitScalar(u8, bytes, '\n');        while (lines.next()) |line| try self.ingestJsonLine(line);    }    pub fn ingestJsonLine(self: *Analyzer, line: []const u8) !void {        const text = std.mem.trim(u8, line, " \t\r\n");        if (text.len == 0) return;        if (stack_mod.isMetadataLine(text) or coverage_mod.isMetadataLine(text)) return;        if (event_mod.parseReplayFast(text)) |parsed| {            try self.ingestParsedEvent(parsed);            return;        } else |err| switch (err) {            error.UnsupportedFastEvent => {},            else => return err,        }        try self.ingestGenericJsonLine(text);    }    fn ingestParsedEvent(self: *Analyzer, parsed: ParsedEvent) !void {        const scope = try self.resolveScope(parsed);        const key = switch (parsed.kind) {            .remap => allocationKey(parsed.allocation_id, parsed.old_address),            else => allocationKey(parsed.allocation_id, parsed.address),        };        self.events +|= 1;        self.recordSequence(parsed.seq);        switch (parsed.kind) {            .trace_start => {                self.integrity_counters.start_events +|= 1;                if (self.start_sequence == null) self.start_sequence = parsed.seq;            },            .trace_stop => {                self.integrity_counters.stop_events +|= 1;                self.stop_sequence = parsed.seq;            },            .allocator => if (self.layer_filter.includes(parsed.layer)) {                try self.recordAllocator(                    parsed.allocator_id,                    parsed.retains_freed_memory,                    parsed.layer,                    parsed.lifecycle_instrumented,                    parsed.observation_prefix_complete,                );            },            .scope_enter => try self.recordScopeEnter(parsed.scope_id, scope),            .scope_exit => self.recordScopeExit(parsed.scope_id),            .alloc => if (self.layer_filter.includes(parsed.layer)) {                if (parsed.succeeded) {                    if (parsed.tracked) {                        try self.recordAllocation(                            parsed.allocator_id,                            key,                            scope,                            parsed.return_address,                            parsed.len,                        );                    } else {                        try self.recordUntrackedAllocation(                            scope,                            parsed.return_address,                            parsed.len,                        );                    }                } else {                    self.counters.failed_allocations +|= 1;                }            },            .free => if (self.layer_filter.includes(parsed.layer)) {                if (parsed.tracked) {                    try self.recordFree(                        key,                        parsed.allocator_id,                        scope,                        parsed.len,                    );                } else {                    self.counters.unmatched_frees +|= 1;                }            },            .release => if (self.layer_filter.includes(parsed.layer)) {                try self.recordRelease(key);            },            .resize => if (self.layer_filter.includes(parsed.layer)) {                if (parsed.succeeded) {                    if (parsed.tracked) {                        try self.recordResize(                            key,                            parsed.allocator_id,                            scope,                            parsed.old_len,                            parsed.len,                        );                    } else {                        self.counters.unmatched_resizes +|= 1;                    }                } else {                    self.counters.failed_resizes +|= 1;                    if (!parsed.tracked) {                        self.counters.unmatched_resizes +|= 1;                    }                }            },            .remap => if (self.layer_filter.includes(parsed.layer)) {                if (parsed.succeeded) {                    if (parsed.tracked) {                        try self.recordRemap(                            key,                            allocationKey(                                parsed.allocation_id,                                parsed.address,                            ),                            parsed.allocator_id,                            scope,                            parsed.old_len,                            parsed.len,                        );                    } else if (parsed.allocation_id != 0) {                        try self.recordLostTrackingRemap(                            key,                            parsed.len,                        );                    } else {                        self.counters.unmatched_remaps +|= 1;                    }                } else {                    self.counters.failed_remaps +|= 1;                    if (!parsed.tracked) {                        self.counters.unmatched_remaps +|= 1;                    }                }            },            .lifecycle => if (self.layer_filter.includes(parsed.layer)) {                self.counters.lifecycle_events +|= 1;                if (self.allocators.getPtr(parsed.allocator_id)) |allocator| {                    allocator.prefix_complete = true;                }            },            .map => if (self.layer_filter.includes(parsed.layer)) {                try self.recordPhysicalMap(parsed);            },            .unmap => if (self.layer_filter.includes(parsed.layer)) {                try self.recordPhysicalUnmap(parsed);            },            .protect => if (self.layer_filter.includes(parsed.layer)) {                self.recordPhysicalEffect(parsed.succeeded, .protect);            },            .discard => if (self.layer_filter.includes(parsed.layer)) {                self.recordPhysicalEffect(parsed.succeeded, .discard);            },            .decommit => if (self.layer_filter.includes(parsed.layer)) {                self.recordPhysicalEffect(parsed.succeeded, .decommit);            },            .advise => if (self.layer_filter.includes(parsed.layer)) {                self.recordPhysicalEffect(parsed.succeeded, .advise);            },            .snapshot, .census => {},        }    }    fn recordPhysicalMap(self: *Analyzer, parsed: ParsedEvent) !void {        if (!parsed.succeeded) {            self.physical.failed_maps +|= 1;            return;        }        const address = std.math.cast(usize, parsed.address) orelse            return error.InvalidMappingRange;        const displaced = try self.mappings.map(            self.allocator,            address,            parsed.len,        );        self.physical.maps +|= 1;        self.physical.mapped_bytes +|= parsed.len;        self.physical.displaced_bytes +|= displaced;        self.updatePhysicalLiveBytes();    }    fn recordPhysicalUnmap(self: *Analyzer, parsed: ParsedEvent) !void {        if (!parsed.succeeded) {            self.physical.failed_unmaps +|= 1;            return;        }        const address = std.math.cast(usize, parsed.address) orelse            return error.InvalidMappingRange;        const removal = try self.mappings.unmap(            self.allocator,            address,            parsed.len,        );        self.physical.unmaps +|= 1;        self.physical.unmapped_bytes +|= parsed.len;        self.physical.untracked_unmap_bytes +|= removal.untrackedBytes();        self.updatePhysicalLiveBytes();    }    const PhysicalEffect = enum {        protect,        discard,        decommit,        advise,    };    fn recordPhysicalEffect(        self: *Analyzer,        succeeded: bool,        effect: PhysicalEffect,    ) void {        const counter = switch (effect) {            .protect => if (succeeded)                &self.physical.protects            else                &self.physical.failed_protects,            .discard => if (succeeded)                &self.physical.discards            else                &self.physical.failed_discards,            .decommit => if (succeeded)                &self.physical.decommits            else                &self.physical.failed_decommits,            .advise => if (succeeded)                &self.physical.advises            else                &self.physical.failed_advises,        };        counter.* +|= 1;    }    fn updatePhysicalLiveBytes(self: *Analyzer) void {        self.physical.live_mapped_bytes = self.mappings.mapped_bytes;        self.physical.high_water_mapped_bytes = @max(            self.physical.high_water_mapped_bytes,            self.physical.live_mapped_bytes,        );    }    fn ingestGenericJsonLine(self: *Analyzer, text: []const u8) !void {        var parsed = std.json.parseFromSlice(            std.json.Value,            self.allocator,            text,            .{},        ) catch |err| switch (err) {            error.OutOfMemory => return err,            else => return error.InvalidEventJson,        };        defer parsed.deinit();        const object = switch (parsed.value) {            .object => |object| object,            else => return error.InvalidEventJson,        };        try self.ingestParsedEvent(try event_mod.replayFromJsonObject(object));    }    pub fn captureIntegrity(self: *const Analyzer) CaptureIntegrity {        var result: CaptureIntegrity = .{            .status = "complete",            .action = "none",            .message = null,            .event_count = self.events,            .sequenced_event_count = self.integrity_counters.sequenced_events,            .unsequenced_event_count = self.integrity_counters.unsequenced_events,            .first_sequence = self.first_sequence,            .last_sequence = self.last_sequence,            .sequence_gap_count = self.integrity_counters.sequence_gaps,            .missing_sequence_event_count = self.integrity_counters.missing_sequence_events,            .sequence_regression_count = self.integrity_counters.sequence_regressions,            .start_event_count = self.integrity_counters.start_events,            .stop_event_count = self.integrity_counters.stop_events,            .start_sequence = self.start_sequence,            .stop_sequence = self.stop_sequence,            .unbalanced_event_count = self.unbalancedEventCount(),        };        classifyCaptureIntegrity(&result);        return result;    }    fn recordSequence(self: *Analyzer, maybe_sequence: ?u64) void {        const sequence = maybe_sequence orelse {            self.integrity_counters.unsequenced_events +|= 1;            return;        };        if (sequence == 0) {            self.integrity_counters.unsequenced_events +|= 1;            return;        }        self.integrity_counters.sequenced_events +|= 1;        const previous = self.last_sequence orelse {            self.first_sequence = sequence;            self.last_sequence = sequence;            if (sequence > 1) {                self.integrity_counters.sequence_gaps +|= 1;                self.integrity_counters.missing_sequence_events +|= sequence - 1;            }            return;        };        if (sequence <= previous) {            self.integrity_counters.sequence_regressions +|= 1;        } else if (sequence - previous > 1) {            self.integrity_counters.sequence_gaps +|= 1;            self.integrity_counters.missing_sequence_events +|= sequence - previous - 1;        }        self.last_sequence = sequence;    }    fn recordScopeEnter(        self: *Analyzer,        scope_id: u32,        scope: []const u8,    ) !void {        const owned_scope = try self.internScope(scope);        const path = try self.scope_paths.getOrPut(self.allocator, scope_id);        if (!path.found_existing) {            path.value_ptr.* = owned_scope;        } else if (!std.mem.eql(u8, path.value_ptr.*, owned_scope)) {            return error.ScopeIdentityConflict;        }        const result = try self.active_scopes.getOrPut(self.allocator, scope_id);        if (!result.found_existing) result.value_ptr.* = 0;        result.value_ptr.* +|= 1;        self.integrity_counters.active_scope_events +|= 1;    }    fn recordScopeExit(self: *Analyzer, scope_id: u32) void {        const count = self.active_scopes.getPtr(scope_id) orelse {            self.integrity_counters.unmatched_scope_exits +|= 1;            return;        };        if (count.* == 0) {            self.integrity_counters.unmatched_scope_exits +|= 1;            return;        }        count.* -= 1;        self.integrity_counters.active_scope_events -= 1;        if (count.* == 0) _ = self.active_scopes.remove(scope_id);    }    fn unbalancedEventCount(self: *const Analyzer) u64 {        var count = self.counters.unmatched_frees +|            self.counters.unmatched_resizes +|            self.counters.unmatched_remaps +|            self.integrity_counters.unmatched_scope_exits;        count +|= self.integrity_counters.active_scope_events;        return count;    }    pub fn snapshot(        self: *Analyzer,        allocator: Allocator,        options: SnapshotOptions,    ) !Snapshot {        if (options.layer != self.layer_filter) {            return error.LayerSelectionMismatch;        }        var scope_summaries: [3]std.ArrayListUnmanaged(ScopeSummary) = .{            .empty,            .empty,            .empty,        };        defer for (&scope_summaries) |*summaries| {            summaries.deinit(self.allocator);        };        var site_summaries: [3]std.ArrayListUnmanaged(SiteSummary) = .{            .empty,            .empty,            .empty,        };        defer for (&site_summaries) |*summaries| {            summaries.deinit(self.allocator);        };        inline for (std.meta.tags(Sort)) |sort| {            const summary_options = SummaryOptions{                .top = options.top,                .min_bytes = options.min_bytes,                .include_zero_live = options.include_zero_live,                .include_sites = true,                .site_symbol_binary = options.site_symbol_binary,                .layer = options.layer,                .sort = sort,            };            scope_summaries[@backingInt(sort)] =                try self.collectScopeSummaries(summary_options);            site_summaries[@backingInt(sort)] =                try self.collectSiteSummaries(summary_options);        }        var addresses = std.ArrayListUnmanaged(u64).empty;        defer addresses.deinit(allocator);        inline for (std.meta.tags(Sort)) |sort| {            const sites = site_summaries[@backingInt(sort)].items;            const limit = @min(options.top, sites.len);            for (sites[0..limit]) |site| {                if (std.mem.indexOfScalar(                    u64,                    addresses.items,                    site.return_address,                ) == null) {                    try addresses.append(allocator, site.return_address);                }            }        }        var maybe_symbols = if (options.site_symbol_binary) |binary|            try resolveSiteAddressesAlloc(allocator, binary, addresses.items)        else            null;        defer if (maybe_symbols) |*symbols| symbols.deinit(allocator);        var result = Snapshot{            .allocator = allocator,            .integrity = self.captureIntegrity(),            .summary = summarySnapshot(self),        };        errdefer result.deinit();        inline for (std.meta.tags(Sort)) |sort| {            const scopes = scope_summaries[@backingInt(sort)].items;            const sites = site_summaries[@backingInt(sort)].items;            const ranking = result.rankings.getPtr(sort);            ranking.scopes = try snapshotScopes(                allocator,                scopes[0..@min(options.top, scopes.len)],            );            ranking.sources = try snapshotSources(                allocator,                sites[0..@min(options.top, sites.len)],                if (maybe_symbols) |*symbols| symbols else null,            );        }        if (maybe_symbols) |*symbols| {            result.symbol_storage = symbols.stdout;            symbols.ranges.deinit(allocator);            symbols.frames.deinit(allocator);            symbols.* = undefined;            maybe_symbols = null;        }        return result;    }    pub fn writeSummary(self: *Analyzer, writer: *std.Io.Writer, options: SummaryOptions) !void {        if (options.layer != self.layer_filter) {            return error.LayerSelectionMismatch;        }        var summaries = try self.collectScopeSummaries(options);        defer summaries.deinit(self.allocator);        try writeCaptureIntegrityText(writer, self.captureIntegrity());        if (options.layer == .logical) {            try writer.print(                "memtrace layer={s} sort={s} events={d} allocations={d} " ++                    "frees={d} " ++                    "open_requests={d} requested_bytes={d} " ++                    "explicitly_closed_bytes={d} open_request_bytes={d} " ++                    "high_water_open_request_bytes={d} " ++                    "bulk_invalidated_requests={d} bulk_invalidated_bytes={d} " ++                    "untracked_requests={d} untracked_request_bytes={d}\n",                .{                    options.layer.tag(),                    options.sort.name(),                    self.events,                    self.counters.allocations,                    self.counters.frees,                    self.counters.live_allocations,                    self.counters.allocated_bytes,                    self.counters.freed_bytes,                    self.counters.live_bytes,                    self.counters.high_water_live_bytes,                    self.counters.bulk_invalidated_requests,                    self.counters.bulk_invalidated_bytes,                    self.counters.untracked_requests,                    self.counters.untracked_request_bytes,                },            );            const coverage = self.lifecycleCoverage();            try writer.print(                "memtrace lifecycle_coverage={s} instrumented_producers={d} " ++                    "uninstrumented_producers={d} " ++                    "lifecycle_not_required_producers={d} " ++                    "prefix_complete_producers={d} prefix_partial_producers={d} " ++                    "lifecycle_events={d}\n",                .{                    coverage.status(),                    coverage.instrumented,                    coverage.uninstrumented,                    coverage.not_required,                    coverage.prefix_complete,                    coverage.prefix_partial,                    self.counters.lifecycle_events,                },            );        } else {            try writer.print(                "memtrace layer={s} sort={s} events={d} allocations={d} " ++                    "frees={d} live_allocations={d} allocated_bytes={d} " ++                    "freed_bytes={d} live_bytes={d} " ++                    "high_water_live_bytes={d}\n",                .{                    options.layer.tag(),                    options.sort.name(),                    self.events,                    self.counters.allocations,                    self.counters.frees,                    self.counters.live_allocations,                    self.counters.allocated_bytes,                    self.counters.freed_bytes,                    self.counters.live_bytes,                    self.counters.high_water_live_bytes,                },            );            try writer.print(                "memtrace retained_bytes={d} high_water_retained_bytes={d}\n",                .{                    self.counters.retained_bytes,                    self.counters.high_water_retained_bytes,                },            );        }        try writer.print(            "memtrace lifetimes completed={d} total_events={d} mean_events={d} " ++                "max_events={d} total_byte_events={d} " ++                "mean_byte_events={d} max_byte_events={d}\n",            .{                self.counters.completed_lifetimes,                self.counters.lifetime_total_events,                meanLifetimeEvents(self.counters),                self.counters.lifetime_max_events,                self.counters.lifetime_total_byte_events,                meanLifetimeByteEvents(self.counters),                self.counters.lifetime_max_byte_events,            },        );        if (self.counters.failed_allocations != 0 or            self.counters.failed_resizes != 0 or            self.counters.failed_remaps != 0 or            self.counters.unmatched_frees != 0 or            self.counters.unmatched_resizes != 0 or            self.counters.unmatched_remaps != 0)        {            try writer.print(                "memtrace anomalies failed_allocations={d} failed_resizes={d} " ++                    "failed_remaps={d} unmatched_frees={d} unmatched_resizes={d} " ++                    "unmatched_remaps={d}\n",                .{                    self.counters.failed_allocations,                    self.counters.failed_resizes,                    self.counters.failed_remaps,                    self.counters.unmatched_frees,                    self.counters.unmatched_resizes,                    self.counters.unmatched_remaps,                },            );        }        if (options.layer.includes(.physical_page)) {            try self.writePhysicalSummary(writer);        }        const limit = @min(options.top, summaries.items.len);        for (summaries.items[0..limit]) |summary| {            if (options.layer == .logical) {                try writer.print(                    "{s} open_request_bytes={d} " ++                        "high_water_open_request_bytes={d} requested_bytes={d} " ++                        "explicitly_closed_bytes={d} allocations={d} frees={d} " ++                        "open_requests={d} bulk_invalidated_requests={d} " ++                        "bulk_invalidated_bytes={d} untracked_requests={d} " ++                        "untracked_request_bytes={d}\n",                    .{                        summary.path,                        summary.counters.live_bytes,                        summary.counters.high_water_live_bytes,                        summary.counters.allocated_bytes,                        summary.counters.freed_bytes,                        summary.counters.allocations,                        summary.counters.frees,                        summary.counters.live_allocations,                        summary.counters.bulk_invalidated_requests,                        summary.counters.bulk_invalidated_bytes,                        summary.counters.untracked_requests,                        summary.counters.untracked_request_bytes,                    },                );            } else {                try writer.print(                    "{s} retained_bytes={d} high_water_retained_bytes={d} " ++                        "live_bytes={d} high_water_live_bytes={d} " ++                        "allocated_bytes={d} freed_bytes={d} allocations={d} " ++                        "frees={d} live_allocations={d} completed_lifetimes={d} " ++                        "lifetime_total_events={d} lifetime_mean_events={d} " ++                        "lifetime_max_events={d} " ++                        "lifetime_total_byte_events={d} " ++                        "lifetime_mean_byte_events={d} " ++                        "lifetime_max_byte_events={d}\n",                    .{                        summary.path,                        summary.counters.retained_bytes,                        summary.counters.high_water_retained_bytes,                        summary.counters.live_bytes,                        summary.counters.high_water_live_bytes,                        summary.counters.allocated_bytes,                        summary.counters.freed_bytes,                        summary.counters.allocations,                        summary.counters.frees,                        summary.counters.live_allocations,                        summary.counters.completed_lifetimes,                        summary.counters.lifetime_total_events,                        meanLifetimeEvents(summary.counters),                        summary.counters.lifetime_max_events,                        summary.counters.lifetime_total_byte_events,                        meanLifetimeByteEvents(summary.counters),                        summary.counters.lifetime_max_byte_events,                    },                );            }        }        if (options.include_sites) try self.writeSiteSummary(writer, options);        if (options.site_detail_return_address) |return_address| try self.writeSiteDetailSummary(writer, options, return_address);    }    pub fn writeSummaryJsonl(self: *Analyzer, writer: *std.Io.Writer, options: SummaryOptions) !void {        if (options.layer != self.layer_filter) {            return error.LayerSelectionMismatch;        }        var summaries = try self.collectScopeSummaries(options);        defer summaries.deinit(self.allocator);        var stream = pretty_json.Writer.init(writer, .minified);        const object = try stream.object();        try object.field("kind", "summary");        try object.field("layer", options.layer.tag());        try object.field("sort", options.sort.name());        try object.field("events", self.events);        try object.field("allocations", self.counters.allocations);        try object.field("frees", self.counters.frees);        try object.field("resizes", self.counters.resizes);        try object.field("remaps", self.counters.remaps);        try object.field("allocated_bytes", self.counters.allocated_bytes);        if (options.layer == .logical) {            try object.field("open_requests", self.counters.live_allocations);            try object.field("requested_bytes", self.counters.allocated_bytes);            try object.field(                "explicitly_closed_bytes",                self.counters.freed_bytes,            );            try object.field("open_request_bytes", self.counters.live_bytes);            try object.field(                "high_water_open_request_bytes",                self.counters.high_water_live_bytes,            );            try object.field(                "bulk_invalidated_requests",                self.counters.bulk_invalidated_requests,            );            try object.field(                "bulk_invalidated_bytes",                self.counters.bulk_invalidated_bytes,            );            try object.field(                "untracked_requests",                self.counters.untracked_requests,            );            try object.field(                "untracked_request_bytes",                self.counters.untracked_request_bytes,            );            try object.field(                "lifecycle_events",                self.counters.lifecycle_events,            );            const coverage = self.lifecycleCoverage();            try object.field("lifecycle_coverage", coverage.status());            try object.field(                "lifecycle_instrumented_producers",                coverage.instrumented,            );            try object.field(                "lifecycle_uninstrumented_producers",                coverage.uninstrumented,            );            try object.field(                "lifecycle_not_required_producers",                coverage.not_required,            );            try object.field(                "prefix_complete_producers",                coverage.prefix_complete,            );            try object.field(                "prefix_partial_producers",                coverage.prefix_partial,            );        } else {            try object.field("live_allocations", self.counters.live_allocations);            try object.field("freed_bytes", self.counters.freed_bytes);            try object.field("live_bytes", self.counters.live_bytes);            try object.field(                "high_water_live_bytes",                self.counters.high_water_live_bytes,            );            try object.field("retained_bytes", self.counters.retained_bytes);            try object.field(                "high_water_retained_bytes",                self.counters.high_water_retained_bytes,            );        }        try object.field("completed_lifetimes", self.counters.completed_lifetimes);        try object.field("lifetime_total_events", self.counters.lifetime_total_events);        try object.field("lifetime_mean_events", meanLifetimeEvents(self.counters));        try object.field("lifetime_max_events", self.counters.lifetime_max_events);        try object.field(            "lifetime_total_byte_events",            self.counters.lifetime_total_byte_events,        );        try object.field(            "lifetime_mean_byte_events",            meanLifetimeByteEvents(self.counters),        );        try object.field(            "lifetime_max_byte_events",            self.counters.lifetime_max_byte_events,        );        try object.field("failed_allocations", self.counters.failed_allocations);        try object.field("failed_resizes", self.counters.failed_resizes);        try object.field("failed_remaps", self.counters.failed_remaps);        try object.field("unmatched_frees", self.counters.unmatched_frees);        try object.field("unmatched_resizes", self.counters.unmatched_resizes);        try object.field("unmatched_remaps", self.counters.unmatched_remaps);        if (options.layer.includes(.physical_page)) {            try self.writePhysicalSummaryJson(object);        }        try writeCaptureIntegrityJson(object, self.captureIntegrity());        try object.endLine();        const limit = @min(options.top, summaries.items.len);        for (summaries.items[0..limit]) |summary| {            var row_stream = pretty_json.Writer.init(writer, .minified);            const row = try row_stream.object();            try row.field("kind", "scope");            try row.field("sort", options.sort.name());            try row.field("scope", summary.path);            try writeCounterFields(                row,                summary.counters,                options.layer == .logical,            );            try row.endLine();        }        if (options.include_sites) try self.writeSiteSummaryJsonl(writer, options);        if (options.site_detail_return_address) |return_address| try self.writeSiteDetailSummaryJsonl(writer, options, return_address);    }    fn writePhysicalSummary(        self: *const Analyzer,        writer: *std.Io.Writer,    ) !void {        try writer.print(            "memtrace physical maps={d} unmaps={d} protects={d} discards={d} " ++                "decommits={d} advises={d} mapped_bytes={d} unmapped_bytes={d} " ++                "displaced_bytes={d} untracked_unmap_bytes={d} " ++                "live_mapped_bytes={d} high_water_mapped_bytes={d} " ++                "live_ranges={d}\n",            .{                self.physical.maps,                self.physical.unmaps,                self.physical.protects,                self.physical.discards,                self.physical.decommits,                self.physical.advises,                self.physical.mapped_bytes,                self.physical.unmapped_bytes,                self.physical.displaced_bytes,                self.physical.untracked_unmap_bytes,                self.physical.live_mapped_bytes,                self.physical.high_water_mapped_bytes,                self.mappings.count(),            },        );        if (self.physical.failed_maps != 0 or            self.physical.failed_unmaps != 0 or            self.physical.failed_protects != 0 or            self.physical.failed_discards != 0 or            self.physical.failed_decommits != 0 or            self.physical.failed_advises != 0)        {            try writer.print(                "memtrace physical_failures maps={d} unmaps={d} protects={d} " ++                    "discards={d} decommits={d} advises={d}\n",                .{                    self.physical.failed_maps,                    self.physical.failed_unmaps,                    self.physical.failed_protects,                    self.physical.failed_discards,                    self.physical.failed_decommits,                    self.physical.failed_advises,                },            );        }    }    fn writePhysicalSummaryJson(        self: *const Analyzer,        object: pretty_json.Object,    ) !void {        const physical = try object.object("physical");        try physical.field("maps", self.physical.maps);        try physical.field("unmaps", self.physical.unmaps);        try physical.field("protects", self.physical.protects);        try physical.field("discards", self.physical.discards);        try physical.field("decommits", self.physical.decommits);        try physical.field("advises", self.physical.advises);        try physical.field("failed_maps", self.physical.failed_maps);        try physical.field("failed_unmaps", self.physical.failed_unmaps);        try physical.field("failed_protects", self.physical.failed_protects);        try physical.field("failed_discards", self.physical.failed_discards);        try physical.field("failed_decommits", self.physical.failed_decommits);        try physical.field("failed_advises", self.physical.failed_advises);        try physical.field("mapped_bytes", self.physical.mapped_bytes);        try physical.field("unmapped_bytes", self.physical.unmapped_bytes);        try physical.field("displaced_bytes", self.physical.displaced_bytes);        try physical.field("untracked_unmap_bytes", self.physical.untracked_unmap_bytes);        try physical.field("live_mapped_bytes", self.physical.live_mapped_bytes);        try physical.field("high_water_mapped_bytes", self.physical.high_water_mapped_bytes);        try physical.field("live_ranges", self.mappings.count());        try physical.end();    }    fn collectScopeSummaries(        self: *Analyzer,        options: SummaryOptions,    ) !std.ArrayListUnmanaged(ScopeSummary) {        var summaries = std.ArrayListUnmanaged(ScopeSummary).empty;        errdefer summaries.deinit(self.allocator);        var iterator = self.scopes.iterator();        while (iterator.next()) |entry| {            const counters = entry.value_ptr.*;            const invisible = !options.include_zero_live and counters.live_bytes == 0 and                counters.high_water_live_bytes == 0 and counters.retained_bytes == 0;            if (invisible) continue;            const below_floor = counters.live_bytes < options.min_bytes and                counters.high_water_live_bytes < options.min_bytes and                counters.retained_bytes < options.min_bytes;            if (below_floor) continue;            try summaries.append(self.allocator, .{                .path = entry.key_ptr.*,                .counters = counters,            });        }        std.mem.sort(            ScopeSummary,            summaries.items,            options.sort,            scopeSummaryGreaterThan,        );        return summaries;    }    fn recordAllocator(        self: *Analyzer,        allocator_id: u32,        retains_freed_memory: bool,        layer: event_mod.Layer,        lifecycle_instrumented: bool,        prefix_complete: bool,    ) !void {        try self.allocators.put(self.allocator, allocator_id, .{            .retention = if (retains_freed_memory) .retains_freed_memory else .releases_freed_memory,            .layer = layer,            .lifecycle_instrumented = lifecycle_instrumented,            .prefix_complete = prefix_complete,        });    }    fn recordAllocation(self: *Analyzer, allocator_id: u32, key: u64, scope: []const u8, return_address: u64, len: usize) !void {        const owned_scope = try self.internScope(scope);        try self.allocations.put(self.allocator, key, .{            .allocator_id = allocator_id,            .len = len,            .scope = owned_scope,            .return_address = return_address,            .allocation_event = self.events,            .segment_event = self.events,            .byte_events = 0,        });        self.applyAllocation(allocator_id, owned_scope, len);        if (self.track_sites) {            try self.applySiteAllocation(                allocator_id,                return_address,                len,            );        }        if (self.site_detail_return_address == return_address) {            try self.applySiteDetailAllocation(                allocator_id,                owned_scope,                len,            );        }    }    fn recordUntrackedAllocation(        self: *Analyzer,        scope: []const u8,        return_address: u64,        len: usize,    ) !void {        const owned_scope = try self.internScope(scope);        self.counters.allocations +|= 1;        self.counters.allocated_bytes +|= len;        self.counters.untracked_requests +|= 1;        self.counters.untracked_request_bytes +|= len;        const scope_counters = self.scopes.getPtr(owned_scope).?;        scope_counters.allocations +|= 1;        scope_counters.allocated_bytes +|= len;        scope_counters.untracked_requests +|= 1;        scope_counters.untracked_request_bytes +|= len;        if (self.track_sites) {            const site = try self.siteCounters(return_address);            site.allocations +|= 1;            site.allocated_bytes +|= len;            site.untracked_requests +|= 1;            site.untracked_request_bytes +|= len;        }    }    fn recordFree(self: *Analyzer, key: u64, allocator_id: u32, scope: []const u8, len: usize) !void {        if (self.allocations.fetchRemove(key)) |removed| {            self.clearDrainedAllocations();            self.applyLifetime(                removed.value.scope,                removed.value.return_address,                removed.value.len,                completedLifetimeEvents(removed.value.allocation_event, self.events),                removed.value.byte_events +|                    completedLifetimeByteEvents(                        removed.value.len,                        removed.value.segment_event,                        self.events,                    ),            );            self.applyFree(removed.value.allocator_id, removed.value.scope, removed.value.len);            if (self.track_sites) self.applySiteFree(removed.value.allocator_id, removed.value.return_address, removed.value.len);            if (self.site_detail_return_address == removed.value.return_address) self.applySiteDetailFree(removed.value.allocator_id, removed.value.scope, removed.value.len);            return;        }        self.counters.unmatched_frees += 1;        const owned_scope = try self.internScope(scope);        self.applyFree(allocator_id, owned_scope, len);    }    fn recordRelease(self: *Analyzer, key: u64) !void {        const removed = self.allocations.fetchRemove(key) orelse {            self.counters.unmatched_frees +|= 1;            return;        };        self.clearDrainedAllocations();        self.applyLifetime(            removed.value.scope,            removed.value.return_address,            removed.value.len,            completedLifetimeEvents(                removed.value.allocation_event,                self.events,            ),            removed.value.byte_events +|                completedLifetimeByteEvents(                    removed.value.len,                    removed.value.segment_event,                    self.events,                ),        );        self.applyBulkInvalidation(            removed.value.allocator_id,            removed.value.scope,            removed.value.len,        );        if (self.track_sites) {            self.applySiteBulkInvalidation(                removed.value.return_address,                removed.value.len,            );        }        if (self.site_detail_return_address == removed.value.return_address) {            self.applySiteDetailBulkInvalidation(                removed.value.scope,                removed.value.len,            );        }    }    fn recordResize(self: *Analyzer, key: u64, allocator_id: u32, scope: []const u8, old_len: usize, new_len: usize) !void {        self.counters.resizes += 1;        try self.resizeAllocation(            key,            allocator_id,            scope,            old_len,            new_len,            false,        );    }    fn resizeAllocation(        self: *Analyzer,        key: u64,        allocator_id: u32,        scope: []const u8,        old_len: usize,        new_len: usize,        is_remap: bool,    ) !void {        if (self.allocations.getPtr(key)) |record| {            accrueLifetimeByteEvents(record, self.events);            self.applyResize(record.allocator_id, record.scope, record.len, new_len);            if (self.track_sites) self.applySiteResize(record.allocator_id, record.return_address, record.len, new_len);            if (self.site_detail_return_address == record.return_address) try self.applySiteDetailResize(record.allocator_id, record.scope, record.len, new_len);            record.len = new_len;            return;        }        _ = allocator_id;        _ = scope;        _ = old_len;        if (is_remap) {            self.counters.unmatched_remaps += 1;        } else {            self.counters.unmatched_resizes += 1;        }    }    fn recordRemap(self: *Analyzer, old_key: u64, new_key: u64, allocator_id: u32, scope: []const u8, old_len: usize, new_len: usize) !void {        self.counters.remaps += 1;        if (old_key == new_key) {            try self.resizeAllocation(                old_key,                allocator_id,                scope,                old_len,                new_len,                true,            );            return;        }        if (self.allocations.fetchRemove(old_key)) |removed| {            var record = removed.value;            self.clearDrainedAllocations();            accrueLifetimeByteEvents(&record, self.events);            self.applyResize(record.allocator_id, record.scope, record.len, new_len);            if (self.track_sites) self.applySiteResize(record.allocator_id, record.return_address, record.len, new_len);            if (self.site_detail_return_address == record.return_address) try self.applySiteDetailResize(record.allocator_id, record.scope, record.len, new_len);            record.len = new_len;            try self.allocations.put(self.allocator, new_key, record);            return;        }        self.counters.unmatched_remaps += 1;    }    fn recordLostTrackingRemap(        self: *Analyzer,        key: u64,        new_len: usize,    ) !void {        const removed = self.allocations.fetchRemove(key) orelse {            self.counters.unmatched_remaps +|= 1;            return;        };        self.clearDrainedAllocations();        const record = removed.value;        self.counters.resizes +|= 1;        self.applyResize(            record.allocator_id,            record.scope,            record.len,            new_len,        );        self.applyLostTracking(record.scope, new_len);        if (self.track_sites) {            self.applySiteResize(                record.allocator_id,                record.return_address,                record.len,                new_len,            );            self.applySiteLostTracking(record.return_address, new_len);        }        if (self.site_detail_return_address == record.return_address) {            try self.applySiteDetailResize(                record.allocator_id,                record.scope,                record.len,                new_len,            );            self.applySiteDetailLostTracking(record.scope, new_len);        }    }    fn applyAllocation(self: *Analyzer, allocator_id: u32, scope: []const u8, len: usize) void {        self.counters.allocations += 1;        self.counters.live_allocations += 1;        self.counters.allocated_bytes += len;        self.counters.live_bytes += len;        self.counters.high_water_live_bytes = @max(self.counters.high_water_live_bytes, self.counters.live_bytes);        const tracks_retained = self.allocatorLayer(allocator_id) !=            .logical_allocator;        if (tracks_retained) {            self.counters.retained_bytes += len;            self.counters.high_water_retained_bytes = @max(                self.counters.high_water_retained_bytes,                self.counters.retained_bytes,            );        }        const counters = self.scopes.getPtr(scope) orelse return;        counters.allocations += 1;        counters.live_allocations += 1;        counters.allocated_bytes += len;        counters.live_bytes += len;        counters.high_water_live_bytes = @max(counters.high_water_live_bytes, counters.live_bytes);        if (tracks_retained) {            counters.retained_bytes += len;            counters.high_water_retained_bytes = @max(                counters.high_water_retained_bytes,                counters.retained_bytes,            );        }    }    fn applyFree(self: *Analyzer, allocator_id: u32, scope: []const u8, len: usize) void {        self.counters.frees += 1;        if (self.counters.live_allocations > 0) self.counters.live_allocations -= 1;        self.counters.freed_bytes += len;        if (self.counters.live_bytes >= len) self.counters.live_bytes -= len else self.counters.live_bytes = 0;        const releases = self.allocatorReleases(allocator_id);        if (releases and self.counters.retained_bytes >= len) self.counters.retained_bytes -= len;        const counters = self.scopes.getPtr(scope) orelse return;        counters.frees += 1;        if (counters.live_allocations > 0) counters.live_allocations -= 1;        counters.freed_bytes += len;        if (counters.live_bytes >= len) counters.live_bytes -= len else counters.live_bytes = 0;        if (releases and counters.retained_bytes >= len) counters.retained_bytes -= len;    }    fn applyBulkInvalidation(        self: *Analyzer,        allocator_id: u32,        scope: []const u8,        len: usize,    ) void {        _ = allocator_id;        self.counters.bulk_invalidated_requests +|= 1;        self.counters.bulk_invalidated_bytes +|= len;        if (self.counters.live_allocations > 0) {            self.counters.live_allocations -= 1;        }        if (self.counters.live_bytes >= len) {            self.counters.live_bytes -= len;        } else {            self.counters.live_bytes = 0;        }        const counters = self.scopes.getPtr(scope) orelse return;        counters.bulk_invalidated_requests +|= 1;        counters.bulk_invalidated_bytes +|= len;        if (counters.live_allocations > 0) counters.live_allocations -= 1;        if (counters.live_bytes >= len) {            counters.live_bytes -= len;        } else {            counters.live_bytes = 0;        }    }    fn applyLostTracking(        self: *Analyzer,        scope: []const u8,        len: usize,    ) void {        applyCountersLostTracking(&self.counters, len);        if (self.scopes.getPtr(scope)) |counters| {            applyCountersLostTracking(counters, len);        }    }    fn applyLifetime(        self: *Analyzer,        scope: []const u8,        return_address: u64,        len: usize,        lifetime_events: u64,        lifetime_byte_events: u64,    ) void {        applyCompletedLifetime(            &self.counters,            lifetime_events,            lifetime_byte_events,        );        if (self.scopes.getPtr(scope)) |counters| {            applyCompletedLifetime(                counters,                lifetime_events,                lifetime_byte_events,            );        }        if (self.track_sites) {            if (self.sites.getPtr(return_address)) |counters| {                applyCompletedLifetime(                    counters,                    lifetime_events,                    lifetime_byte_events,                );            }        }        if (self.site_detail_return_address == return_address) {            if (self.site_detail_sizes.getPtr(len)) |counters| {                applyCompletedLifetime(                    counters,                    lifetime_events,                    lifetime_byte_events,                );            }            if (self.site_detail_scopes.getPtr(scope)) |counters| {                applyCompletedLifetime(                    counters,                    lifetime_events,                    lifetime_byte_events,                );            }        }    }    fn applyResize(self: *Analyzer, allocator_id: u32, scope: []const u8, old_len: usize, new_len: usize) void {        if (new_len >= old_len) {            const delta = new_len - old_len;            self.counters.allocated_bytes += delta;            self.counters.live_bytes += delta;            self.counters.high_water_live_bytes = @max(self.counters.high_water_live_bytes, self.counters.live_bytes);            const tracks_retained = self.allocatorLayer(allocator_id) !=                .logical_allocator;            if (tracks_retained) {                self.counters.retained_bytes += delta;                self.counters.high_water_retained_bytes = @max(                    self.counters.high_water_retained_bytes,                    self.counters.retained_bytes,                );            }            if (self.scopes.getPtr(scope)) |counters| {                counters.allocated_bytes += delta;                counters.live_bytes += delta;                counters.high_water_live_bytes = @max(counters.high_water_live_bytes, counters.live_bytes);                if (tracks_retained) {                    counters.retained_bytes += delta;                    counters.high_water_retained_bytes = @max(                        counters.high_water_retained_bytes,                        counters.retained_bytes,                    );                }            }        } else {            const delta = old_len - new_len;            self.counters.freed_bytes += delta;            if (self.counters.live_bytes >= delta) self.counters.live_bytes -= delta else self.counters.live_bytes = 0;            const releases = self.allocatorReleases(allocator_id);            if (releases and self.counters.retained_bytes >= delta) self.counters.retained_bytes -= delta;            if (self.scopes.getPtr(scope)) |counters| {                counters.freed_bytes += delta;                if (counters.live_bytes >= delta) counters.live_bytes -= delta else counters.live_bytes = 0;                if (releases and counters.retained_bytes >= delta) counters.retained_bytes -= delta;            }        }    }    fn applySiteAllocation(        self: *Analyzer,        allocator_id: u32,        return_address: u64,        len: usize,    ) !void {        const counters = try self.siteCounters(return_address);        counters.allocations += 1;        counters.live_allocations += 1;        counters.allocated_bytes += len;        counters.live_bytes += len;        counters.high_water_live_bytes = @max(counters.high_water_live_bytes, counters.live_bytes);        if (self.allocatorLayer(allocator_id) != .logical_allocator) {            counters.retained_bytes += len;            counters.high_water_retained_bytes = @max(                counters.high_water_retained_bytes,                counters.retained_bytes,            );        }    }    fn applySiteFree(self: *Analyzer, allocator_id: u32, return_address: u64, len: usize) void {        const counters = self.sites.getPtr(return_address) orelse return;        counters.frees += 1;        if (counters.live_allocations > 0) counters.live_allocations -= 1;        counters.freed_bytes += len;        if (counters.live_bytes >= len) counters.live_bytes -= len else counters.live_bytes = 0;        if (self.allocatorReleases(allocator_id) and counters.retained_bytes >= len) counters.retained_bytes -= len;    }    fn applySiteBulkInvalidation(        self: *Analyzer,        return_address: u64,        len: usize,    ) void {        const counters = self.sites.getPtr(return_address) orelse return;        counters.bulk_invalidated_requests +|= 1;        counters.bulk_invalidated_bytes +|= len;        if (counters.live_allocations > 0) counters.live_allocations -= 1;        if (counters.live_bytes >= len) {            counters.live_bytes -= len;        } else {            counters.live_bytes = 0;        }    }    fn applySiteLostTracking(        self: *Analyzer,        return_address: u64,        len: usize,    ) void {        const counters = self.sites.getPtr(return_address) orelse return;        applyCountersLostTracking(counters, len);    }    fn applySiteResize(self: *Analyzer, allocator_id: u32, return_address: u64, old_len: usize, new_len: usize) void {        const counters = self.sites.getPtr(return_address) orelse return;        if (new_len >= old_len) {            const delta = new_len - old_len;            counters.allocated_bytes += delta;            counters.live_bytes += delta;            counters.high_water_live_bytes = @max(counters.high_water_live_bytes, counters.live_bytes);            counters.retained_bytes += delta;            counters.high_water_retained_bytes = @max(counters.high_water_retained_bytes, counters.retained_bytes);        } else {            const delta = old_len - new_len;            counters.freed_bytes += delta;            if (counters.live_bytes >= delta) counters.live_bytes -= delta else counters.live_bytes = 0;            if (self.allocatorReleases(allocator_id) and counters.retained_bytes >= delta) counters.retained_bytes -= delta;        }    }    fn siteCounters(self: *Analyzer, return_address: u64) !*SiteCounters {        const entry = try self.sites.getOrPut(self.allocator, return_address);        if (!entry.found_existing) entry.value_ptr.* = .{};        return entry.value_ptr;    }    fn applySiteDetailAllocation(        self: *Analyzer,        allocator_id: u32,        scope: []const u8,        len: usize,    ) !void {        const tracks_retained = self.allocatorLayer(allocator_id) !=            .logical_allocator;        applyDetailAllocation(            try self.siteDetailSizeCounters(len),            len,            tracks_retained,        );        applyDetailAllocation(            try self.siteDetailScopeCounters(scope),            len,            tracks_retained,        );    }    fn applySiteDetailFree(self: *Analyzer, allocator_id: u32, scope: []const u8, len: usize) void {        const releases = self.allocatorReleases(allocator_id);        if (self.site_detail_sizes.getPtr(len)) |counters| applyDetailFree(counters, len, releases);        if (self.site_detail_scopes.getPtr(scope)) |counters| applyDetailFree(counters, len, releases);    }    fn applySiteDetailResize(self: *Analyzer, allocator_id: u32, scope: []const u8, old_len: usize, new_len: usize) !void {        if (old_len == new_len) return;        self.applySiteDetailFree(allocator_id, scope, old_len);        try self.applySiteDetailAllocation(allocator_id, scope, new_len);    }    fn applySiteDetailBulkInvalidation(        self: *Analyzer,        scope: []const u8,        len: usize,    ) void {        if (self.site_detail_sizes.getPtr(len)) |counters| {            applyDetailBulkInvalidation(counters, len);        }        if (self.site_detail_scopes.getPtr(scope)) |counters| {            applyDetailBulkInvalidation(counters, len);        }    }    fn applySiteDetailLostTracking(        self: *Analyzer,        scope: []const u8,        len: usize,    ) void {        if (self.site_detail_sizes.getPtr(len)) |counters| {            applyCountersLostTracking(counters, len);        }        if (self.site_detail_scopes.getPtr(scope)) |counters| {            applyCountersLostTracking(counters, len);        }    }    fn siteDetailSizeCounters(self: *Analyzer, len: usize) !*SiteCounters {        const entry = try self.site_detail_sizes.getOrPut(self.allocator, len);        if (!entry.found_existing) entry.value_ptr.* = .{};        return entry.value_ptr;    }    fn siteDetailScopeCounters(self: *Analyzer, scope: []const u8) !*SiteCounters {        const entry = try self.site_detail_scopes.getOrPut(self.allocator, scope);        if (!entry.found_existing) entry.value_ptr.* = .{};        return entry.value_ptr;    }    fn applyDetailAllocation(        counters: *SiteCounters,        len: usize,        tracks_retained: bool,    ) void {        counters.allocations += 1;        counters.live_allocations += 1;        counters.allocated_bytes += len;        counters.live_bytes += len;        counters.high_water_live_bytes = @max(counters.high_water_live_bytes, counters.live_bytes);        if (tracks_retained) {            counters.retained_bytes += len;            counters.high_water_retained_bytes = @max(                counters.high_water_retained_bytes,                counters.retained_bytes,            );        }    }    fn applyDetailFree(counters: *SiteCounters, len: usize, releases: bool) void {        counters.frees += 1;        if (counters.live_allocations > 0) counters.live_allocations -= 1;        counters.freed_bytes += len;        if (counters.live_bytes >= len) counters.live_bytes -= len else counters.live_bytes = 0;        if (releases and counters.retained_bytes >= len) counters.retained_bytes -= len;    }    fn applyDetailBulkInvalidation(        counters: *SiteCounters,        len: usize,    ) void {        counters.bulk_invalidated_requests +|= 1;        counters.bulk_invalidated_bytes +|= len;        if (counters.live_allocations > 0) counters.live_allocations -= 1;        if (counters.live_bytes >= len) {            counters.live_bytes -= len;        } else {            counters.live_bytes = 0;        }    }    fn writeSiteSummary(self: *Analyzer, writer: *std.Io.Writer, options: SummaryOptions) !void {        var summaries = try self.collectSiteSummaries(options);        defer summaries.deinit(self.allocator);        const limit = @min(options.top, summaries.items.len);        var maybe_symbols = if (options.site_symbol_binary) |binary|            try resolveSiteSummarySymbolsAlloc(                self.allocator,                binary,                summaries.items[0..limit],            )        else            null;        defer if (maybe_symbols) |*symbols| symbols.deinit(self.allocator);        for (summaries.items[0..limit]) |summary| {            const symbol = symbol: {                const symbols = if (maybe_symbols) |*value|                    value                else                    break :symbol null;                const frames = symbols.find(summary.return_address);                break :symbol if (frames.len == 0) null else frames[0];            };            try writer.print(                "site return_address=0x{x}",                .{summary.return_address},            );            if (symbol) |resolved| {                try writer.writeAll(" symbol=");                var symbol_stream = pretty_json.Writer.init(writer, .minified);                try symbol_stream.write(resolved.function);                try writer.writeAll(" location=");                var location_stream = pretty_json.Writer.init(writer, .minified);                try location_stream.write(resolved.location);            }            try writer.print(                " retained_bytes={d} high_water_retained_bytes={d} live_bytes={d} high_water_live_bytes={d} allocated_bytes={d} freed_bytes={d} allocations={d} frees={d} live_allocations={d} completed_lifetimes={d} lifetime_total_events={d} lifetime_mean_events={d} lifetime_max_events={d} lifetime_total_byte_events={d} lifetime_mean_byte_events={d} lifetime_max_byte_events={d}\n",                .{                    summary.counters.retained_bytes,                    summary.counters.high_water_retained_bytes,                    summary.counters.live_bytes,                    summary.counters.high_water_live_bytes,                    summary.counters.allocated_bytes,                    summary.counters.freed_bytes,                    summary.counters.allocations,                    summary.counters.frees,                    summary.counters.live_allocations,                    summary.counters.completed_lifetimes,                    summary.counters.lifetime_total_events,                    meanLifetimeEvents(summary.counters),                    summary.counters.lifetime_max_events,                    summary.counters.lifetime_total_byte_events,                    meanLifetimeByteEvents(summary.counters),                    summary.counters.lifetime_max_byte_events,                },            );        }    }    fn writeSiteDetailSummary(self: *Analyzer, writer: *std.Io.Writer, options: SummaryOptions, return_address: u64) !void {        try writer.print("site_detail return_address=0x{x}\n", .{return_address});        var sizes = try self.collectSiteSizeSummaries(options.sort);        defer sizes.deinit(self.allocator);        const size_limit = @min(options.top, sizes.items.len);        for (sizes.items[0..size_limit]) |summary| {            try writer.print(                "site_size len={d} retained_bytes={d} high_water_retained_bytes={d} live_bytes={d} high_water_live_bytes={d} allocated_bytes={d} freed_bytes={d} allocations={d} frees={d} live_allocations={d} completed_lifetimes={d} lifetime_total_events={d} lifetime_mean_events={d} lifetime_max_events={d} lifetime_total_byte_events={d} lifetime_mean_byte_events={d} lifetime_max_byte_events={d}\n",                .{                    summary.len,                    summary.counters.retained_bytes,                    summary.counters.high_water_retained_bytes,                    summary.counters.live_bytes,                    summary.counters.high_water_live_bytes,                    summary.counters.allocated_bytes,                    summary.counters.freed_bytes,                    summary.counters.allocations,                    summary.counters.frees,                    summary.counters.live_allocations,                    summary.counters.completed_lifetimes,                    summary.counters.lifetime_total_events,                    meanLifetimeEvents(summary.counters),                    summary.counters.lifetime_max_events,                    summary.counters.lifetime_total_byte_events,                    meanLifetimeByteEvents(summary.counters),                    summary.counters.lifetime_max_byte_events,                },            );        }        var scopes = try self.collectSiteScopeSummaries(options.sort);        defer scopes.deinit(self.allocator);        const scope_limit = @min(options.top, scopes.items.len);        for (scopes.items[0..scope_limit]) |summary| {            try writer.writeAll("site_scope scope=");            var stream = pretty_json.Writer.init(writer, .minified);            try stream.write(summary.scope);            try writer.print(                " retained_bytes={d} high_water_retained_bytes={d} live_bytes={d} high_water_live_bytes={d} allocated_bytes={d} freed_bytes={d} allocations={d} frees={d} live_allocations={d} completed_lifetimes={d} lifetime_total_events={d} lifetime_mean_events={d} lifetime_max_events={d} lifetime_total_byte_events={d} lifetime_mean_byte_events={d} lifetime_max_byte_events={d}\n",                .{                    summary.counters.retained_bytes,                    summary.counters.high_water_retained_bytes,                    summary.counters.live_bytes,                    summary.counters.high_water_live_bytes,                    summary.counters.allocated_bytes,                    summary.counters.freed_bytes,                    summary.counters.allocations,                    summary.counters.frees,                    summary.counters.live_allocations,                    summary.counters.completed_lifetimes,                    summary.counters.lifetime_total_events,                    meanLifetimeEvents(summary.counters),                    summary.counters.lifetime_max_events,                    summary.counters.lifetime_total_byte_events,                    meanLifetimeByteEvents(summary.counters),                    summary.counters.lifetime_max_byte_events,                },            );        }    }    fn writeSiteSummaryJsonl(self: *Analyzer, writer: *std.Io.Writer, options: SummaryOptions) !void {        var summaries = try self.collectSiteSummaries(options);        defer summaries.deinit(self.allocator);        const limit = @min(options.top, summaries.items.len);        var maybe_symbols = if (options.site_symbol_binary) |binary|            try resolveSiteSummarySymbolsAlloc(                self.allocator,                binary,                summaries.items[0..limit],            )        else            null;        defer if (maybe_symbols) |*symbols| symbols.deinit(self.allocator);        for (summaries.items[0..limit]) |summary| {            const symbol = symbol: {                const symbols = if (maybe_symbols) |*value|                    value                else                    break :symbol null;                const frames = symbols.find(summary.return_address);                break :symbol if (frames.len == 0) null else frames[0];            };            var stream = pretty_json.Writer.init(writer, .minified);            const object = try stream.object();            try object.field("kind", "site");            try object.field("sort", options.sort.name());            try object.field("return_address", summary.return_address);            if (symbol) |resolved| {                try object.field("symbol", resolved.function);                try object.field("location", resolved.location);            }            try writeCounterFields(                object,                summary.counters,                options.layer == .logical,            );            try object.endLine();        }    }    fn writeSiteDetailSummaryJsonl(self: *Analyzer, writer: *std.Io.Writer, options: SummaryOptions, return_address: u64) !void {        var detail_stream = pretty_json.Writer.init(writer, .minified);        const detail = try detail_stream.object();        try detail.field("kind", "site_detail");        try detail.field("sort", options.sort.name());        try detail.field("return_address", return_address);        try detail.endLine();        var sizes = try self.collectSiteSizeSummaries(options.sort);        defer sizes.deinit(self.allocator);        const size_limit = @min(options.top, sizes.items.len);        for (sizes.items[0..size_limit]) |summary| {            var stream = pretty_json.Writer.init(writer, .minified);            const object = try stream.object();            try object.field("kind", "site_size");            try object.field("sort", options.sort.name());            try object.field("return_address", return_address);            try object.field("len", summary.len);            try writeCounterFields(                object,                summary.counters,                options.layer == .logical,            );            try object.endLine();        }        var scopes = try self.collectSiteScopeSummaries(options.sort);        defer scopes.deinit(self.allocator);        const scope_limit = @min(options.top, scopes.items.len);        for (scopes.items[0..scope_limit]) |summary| {            var stream = pretty_json.Writer.init(writer, .minified);            const object = try stream.object();            try object.field("kind", "site_scope");            try object.field("sort", options.sort.name());            try object.field("return_address", return_address);            try object.field("scope", summary.scope);            try writeCounterFields(                object,                summary.counters,                options.layer == .logical,            );            try object.endLine();        }    }    fn collectSiteSummaries(self: *Analyzer, options: SummaryOptions) !std.ArrayListUnmanaged(SiteSummary) {        var summaries = std.ArrayListUnmanaged(SiteSummary).empty;        var iterator = self.sites.iterator();        while (iterator.next()) |entry| {            const counters = entry.value_ptr.*;            if (!options.include_zero_live and counters.live_bytes == 0 and counters.high_water_live_bytes == 0 and counters.retained_bytes == 0) continue;            if (counters.live_bytes < options.min_bytes and counters.high_water_live_bytes < options.min_bytes and counters.retained_bytes < options.min_bytes) continue;            try summaries.append(self.allocator, .{                .return_address = entry.key_ptr.*,                .counters = counters,            });        }        std.mem.sort(            SiteSummary,            summaries.items,            options.sort,            siteSummaryGreaterThan,        );        return summaries;    }    fn collectSiteSizeSummaries(        self: *Analyzer,        sort: Sort,    ) !std.ArrayListUnmanaged(SiteSizeSummary) {        var summaries = std.ArrayListUnmanaged(SiteSizeSummary).empty;        var iterator = self.site_detail_sizes.iterator();        while (iterator.next()) |entry| {            try summaries.append(self.allocator, .{                .len = entry.key_ptr.*,                .counters = entry.value_ptr.*,            });        }        std.mem.sort(            SiteSizeSummary,            summaries.items,            sort,            siteSizeSummaryGreaterThan,        );        return summaries;    }    fn collectSiteScopeSummaries(        self: *Analyzer,        sort: Sort,    ) !std.ArrayListUnmanaged(SiteScopeSummary) {        var summaries = std.ArrayListUnmanaged(SiteScopeSummary).empty;        var iterator = self.site_detail_scopes.iterator();        while (iterator.next()) |entry| {            try summaries.append(self.allocator, .{                .scope = entry.key_ptr.*,                .counters = entry.value_ptr.*,            });        }        std.mem.sort(            SiteScopeSummary,            summaries.items,            sort,            siteScopeSummaryGreaterThan,        );        return summaries;    }    fn allocatorReleases(self: *Analyzer, allocator_id: u32) bool {        const state = self.allocators.get(allocator_id) orelse return true;        return state.retention == .releases_freed_memory;    }    fn allocatorLayer(        self: *Analyzer,        allocator_id: u32,    ) event_mod.Layer {        const state = self.allocators.get(allocator_id) orelse            return .backing_boundary;        return state.layer;    }    fn lifecycleCoverage(self: *Analyzer) LifecycleCoverage {        var coverage: LifecycleCoverage = .{};        var allocators = self.allocators.valueIterator();        while (allocators.next()) |allocator| {            if (allocator.layer != .logical_allocator) continue;            if (allocator.retention == .releases_freed_memory) {                coverage.not_required +|= 1;            } else if (allocator.lifecycle_instrumented) {                coverage.instrumented +|= 1;            } else {                coverage.uninstrumented +|= 1;            }            if (allocator.prefix_complete) {                coverage.prefix_complete +|= 1;            } else {                coverage.prefix_partial +|= 1;            }        }        return coverage;    }    fn clearDrainedAllocations(self: *Analyzer) void {        if (self.allocations.count() == 0) self.allocations.clearRetainingCapacity();    }    fn internScope(self: *Analyzer, scope: []const u8) ![]const u8 {        const entry = try self.scopes.getOrPut(self.allocator, scope);        if (!entry.found_existing) {            const owned = try self.allocator.dupe(u8, scope);            entry.key_ptr.* = owned;            entry.value_ptr.* = .{};        }        return entry.key_ptr.*;    }    fn resolveScope(        self: *const Analyzer,        parsed: ParsedEvent,    ) ![]const u8 {        if (parsed.scope.len != 0) return parsed.scope;        if (parsed.scope_id == 0) return "root";        return self.scope_paths.get(parsed.scope_id) orelse            error.MissingScopeDefinition;    }};

Source: lib/memtrace/src/root.zig:49

zig
pub const Analyzer = analysis_mod.Analyzer;
Called byCallsAnalyzersnapshotAnalyzerwriteSummaryAnalyzerwriteSummaryJsonltest sourcelib.memtrace.src.analysistest: event analysis classifies seque...test sourcelib.memtrace.src.analysistest: event analysis preserves retain...private sourcelib.memtrace.src.analysis.AnalyzerunbalancedEventCountprivate sourcelib.memtrace.src.analysisclassifyCaptureIntegrityAnalyzercaptureIntegrity
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.memtrace.src.analysistest: escaped canonical scope uses ge...test sourcelib.memtrace.src.analysistest: event analysis classifies seque...test sourcelib.memtrace.src.analysistest: event analysis preserves retain...test sourcelib.memtrace.src.analysistest: event analysis reports allocati...test sourcelib.memtrace.src.analysistest: event analysis reports complete...+3 moreprivate sourcelib.memtrace.src.mappings.LedgerdeinitAnalyzerdeinit
Static calls · unresolved targets: 1 · external targets: 5.
Called byCallsAnalyzeringestJsonlBytestest sourcelib.memtrace.src.analysistest: event analysis reports allocati...test sourcelib.memtrace.src.analysistest: event analysis reports complete...test sourcelib.memtrace.src.analysistest: event analysis reports site det...private sourcelib.memtrace.src.analysis.AnalyzeringestGenericJsonLineprivate sourcelib.memtrace.src.analysis.AnalyzeringestParsedEventAnalyzeringestJsonLine
Static calls · unresolved targets: 0 · external targets: 3.
Called byCallstest sourcelib.memtrace.src.analysistest: escaped canonical scope uses ge...test sourcelib.memtrace.src.analysistest: event analysis classifies seque...test sourcelib.memtrace.src.analysistest: event analysis preserves retain...test sourcelib.memtrace.src.analysistest: event analysis resolves scope i...test sourcelib.memtrace.src.analysistest: event analysis separates resize...AnalyzeringestJsonLineAnalyzeringestJsonlBytes
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callstest sourcelib.memtrace.src.analysistest: escaped canonical scope uses ge...test sourcelib.memtrace.src.analysistest: event analysis classifies seque...test sourcelib.memtrace.src.analysistest: event analysis preserves retain...test sourcelib.memtrace.src.analysistest: event analysis reports allocati...test sourcelib.memtrace.src.analysistest: event analysis reports complete...+3 moreAnalyzerinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.memtrace.src.analysistest: event analysis classifies relea...test sourcelib.memtrace.src.analysistest: event analysis separates backin...test sourcelib.memtrace.src.analysistest: event replay matches failed kno...test sourcelib.memtrace.src.analysistest: physical advice records success...test sourcelib.memtrace.src.analysistest: physical analysis preserves par...+2 moreAnalyzerinitForLayer
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.memtrace.src.analysistest: event analysis reports complete...AnalyzercaptureIntegrityprivate sourcelib.memtrace.src.analysis.AnalyzercollectScopeSummariesprivate sourcelib.memtrace.src.analysis.AnalyzercollectSiteSummariesprivate sourcelib.memtrace.src.analysisresolveSiteAddressesAllocprivate sourcelib.memtrace.src.analysissnapshotScopes+2 moreAnalyzersnapshot
Static calls · unresolved targets: 1 · external targets: 7.
Called byCallstest sourcelib.memtrace.src.analysistest: escaped canonical scope uses ge...test sourcelib.memtrace.src.analysistest: event analysis preserves retain...test sourcelib.memtrace.src.analysistest: event analysis reports allocati...test sourcelib.memtrace.src.analysistest: event analysis reports complete...test sourcelib.memtrace.src.analysistest: event analysis reports site det...test sourcelib.memtrace.src.analysistest: event analysis resolves scope i...AnalyzercaptureIntegrityprivate sourcelib.memtrace.src.analysis.AnalyzercollectScopeSummariesprivate sourcelib.memtrace.src.analysis.AnalyzerlifecycleCoverageprivate sourcelib.memtrace.src.analysis.AnalyzerwritePhysicalSummaryprivate sourcelib.memtrace.src.analysis.AnalyzerwriteSiteDetailSummary+4 moreAnalyzerwriteSummary
Static calls · unresolved targets: 1 · external targets: 5.
Called byCallstest sourcelib.memtrace.src.analysistest: event analysis reports complete...test sourcelib.memtrace.src.analysistest: event analysis separates resize...AnalyzercaptureIntegrityprivate sourcelib.memtrace.src.analysis.AnalyzercollectScopeSummariesprivate sourcelib.memtrace.src.analysis.AnalyzerlifecycleCoverageprivate sourcelib.memtrace.src.analysis.AnalyzerwritePhysicalSummaryJsonprivate sourcelib.memtrace.src.analysis.AnalyzerwriteSiteDetailSummaryJsonl+5 moreAnalyzerwriteSummaryJsonl
Static calls · unresolved targets: 0 · external targets: 11.

Complete caller list for Analyzer.deinit

8 direct callers.

Complete caller list for Analyzer.init

8 direct callers.

Complete caller list for Analyzer.initForLayer

7 direct callers.

Complete call list for Analyzer.snapshot

7 direct calls.

Complete call list for Analyzer.writeSummary

9 direct calls.

Complete call list for Analyzer.writeSummaryJsonl

10 direct calls.

Audit

Definitions10
Public names20
Members21
Version26.7.0
Revisiondaab053ee433