tiny.memtrace.Analyzer
Defined in analysis.
API (30)
Actions
Public operations.
captureIntegritydeinitingestJsonLineingestJsonlBytesinitinitForLayersnapshotwriteSummarywriteSummaryJsonl
Fields and members
Public fields and members.
active_scopesallocationsallocatorallocatorscounterseventsfirst_sequenceintegrity_counterslast_sequencelayer_filtermappingsphysicalscope_pathsscopessite_detail_return_addresssite_detail_scopessite_detail_sizessitesstart_sequencestop_sequencetrack_sites
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;Complete caller list for Analyzer.deinit
8 direct callers.
lib.memtrace.src.analysis.test_escaped_canonical_scope_uses_generic_replay[function] — test source atlib/memtrace/src/analysis.zig:2659in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.test_event_analysis_classifies_sequence_loss_and_tail_truncation[function] — test source atlib/memtrace/src/analysis.zig:3168in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.test_event_analysis_preserves_retained_allocator_pressure[function] — test source atlib/memtrace/src/analysis.zig:2891in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.test_event_analysis_reports_allocation_sites[function] — test source atlib/memtrace/src/analysis.zig:3192in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.test_event_analysis_reports_completed_allocation_lifetimes[function] — test source atlib/memtrace/src/analysis.zig:3243in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.test_event_analysis_reports_site_detail_by_size_and_scope[function] — test source atlib/memtrace/src/analysis.zig:3214in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.test_event_analysis_resolves_scope_ids_from_scope_entry_events[function] — test source atlib/memtrace/src/analysis.zig:2680in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.test_event_analysis_separates_resize_and_remap_event_counts[function] — test source atlib/memtrace/src/analysis.zig:2927in nearest public ownertiny.memtrace.analysis
Complete caller list for Analyzer.init
8 direct callers.
lib.memtrace.src.analysis.test_escaped_canonical_scope_uses_generic_replay[function] — test source atlib/memtrace/src/analysis.zig:2659in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.test_event_analysis_classifies_sequence_loss_and_tail_truncation[function] — test source atlib/memtrace/src/analysis.zig:3168in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.test_event_analysis_preserves_retained_allocator_pressure[function] — test source atlib/memtrace/src/analysis.zig:2891in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.test_event_analysis_reports_allocation_sites[function] — test source atlib/memtrace/src/analysis.zig:3192in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.test_event_analysis_reports_completed_allocation_lifetimes[function] — test source atlib/memtrace/src/analysis.zig:3243in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.test_event_analysis_reports_site_detail_by_size_and_scope[function] — test source atlib/memtrace/src/analysis.zig:3214in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.test_event_analysis_resolves_scope_ids_from_scope_entry_events[function] — test source atlib/memtrace/src/analysis.zig:2680in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.test_event_analysis_separates_resize_and_remap_event_counts[function] — test source atlib/memtrace/src/analysis.zig:2927in nearest public ownertiny.memtrace.analysis
Complete caller list for Analyzer.initForLayer
7 direct callers.
lib.memtrace.src.analysis.test_event_analysis_classifies_release-explicit_lifecycle_as_not_required[function] — test source atlib/memtrace/src/analysis.zig:2971in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.test_event_analysis_separates_backing_logical_and_failed_allocations[function] — test source atlib/memtrace/src/analysis.zig:3331in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.test_event_replay_matches_failed_known_and_lost-tracking_remaps[function] — test source atlib/memtrace/src/analysis.zig:3030in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.test_physical_advice_records_success_and_failure_without_changing_mapped_bytes[function] — test source atlib/memtrace/src/analysis.zig:3439in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.test_physical_analysis_preserves_partial_mapping_lifecycles[function] — test source atlib/memtrace/src/analysis.zig:3395in nearest public ownertiny.memtrace.analysistiny.memtrace.analysis.writeSummaryFromJsonlPath[function] atlib/memtrace/src/analysis.zig:2555tiny.memtrace.analysis.writeSummaryJsonlFromJsonlPath[function] atlib/memtrace/src/analysis.zig:2564
Complete call list for Analyzer.snapshot
7 direct calls.
tiny.memtrace.Analyzer.captureIntegrity[method] atlib/memtrace/src/analysis.zig:667lib.memtrace.src.analysis.Analyzer.collectScopeSummaries[method] — private source atlib/memtrace/src/analysis.zig:1236in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.Analyzer.collectSiteSummaries[method] — private source atlib/memtrace/src/analysis.zig:2073in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.resolveSiteAddressesAlloc[function] — private source atlib/memtrace/src/analysis.zig:2878in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.snapshotScopes[function] — private source atlib/memtrace/src/analysis.zig:2228in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.snapshotSources[function] — private source atlib/memtrace/src/analysis.zig:2249in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.summarySnapshot[function] — private source atlib/memtrace/src/analysis.zig:2196in nearest public ownertiny.memtrace.analysis
Complete call list for Analyzer.writeSummary
9 direct calls.
tiny.memtrace.Analyzer.captureIntegrity[method] atlib/memtrace/src/analysis.zig:667lib.memtrace.src.analysis.Analyzer.collectScopeSummaries[method] — private source atlib/memtrace/src/analysis.zig:1236in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.Analyzer.lifecycleCoverage[method] — private source atlib/memtrace/src/analysis.zig:2150in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.Analyzer.writePhysicalSummary[method] — private source atlib/memtrace/src/analysis.zig:1161in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.Analyzer.writeSiteDetailSummary[method] — private source atlib/memtrace/src/analysis.zig:1926in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.Analyzer.writeSiteSummary[method] — private source atlib/memtrace/src/analysis.zig:1868in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.meanLifetimeByteEvents[function] — private source atlib/memtrace/src/analysis.zig:2654in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.meanLifetimeEvents[function] — private source atlib/memtrace/src/analysis.zig:2649in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.writeCaptureIntegrityText[function] — private source atlib/memtrace/src/analysis.zig:2476in nearest public ownertiny.memtrace.analysis
Complete call list for Analyzer.writeSummaryJsonl
10 direct calls.
tiny.memtrace.Analyzer.captureIntegrity[method] atlib/memtrace/src/analysis.zig:667lib.memtrace.src.analysis.Analyzer.collectScopeSummaries[method] — private source atlib/memtrace/src/analysis.zig:1236in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.Analyzer.lifecycleCoverage[method] — private source atlib/memtrace/src/analysis.zig:2150in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.Analyzer.writePhysicalSummaryJson[method] — private source atlib/memtrace/src/analysis.zig:1209in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.Analyzer.writeSiteDetailSummaryJsonl[method] — private source atlib/memtrace/src/analysis.zig:2028in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.Analyzer.writeSiteSummaryJsonl[method] — private source atlib/memtrace/src/analysis.zig:1988in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.meanLifetimeByteEvents[function] — private source atlib/memtrace/src/analysis.zig:2654in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.meanLifetimeEvents[function] — private source atlib/memtrace/src/analysis.zig:2649in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.writeCaptureIntegrityJson[function] — private source atlib/memtrace/src/analysis.zig:2517in nearest public ownertiny.memtrace.analysislib.memtrace.src.analysis.writeCounterFields[function] — private source atlib/memtrace/src/analysis.zig:2405in nearest public ownertiny.memtrace.analysis
Audit
| Definitions | 10 |
|---|---|
| Public names | 20 |
| Members | 21 |
| Version | 26.7.0 |
| Revision | daab053ee433 |