tiny.memtrace.Tracer
Defined in tiny.memtrace.
API (53)
Actions
Public operations.
deinitdiffdiffLayerenterfinishEventsinitobserveOwnedAllocatorsobserveOwnedAllocatorsIfAvailableobserverControlOperationssnapshotsnapshotLayertracedAllocatortracedAllocatorWithOptionswriteEventsBundlePathwriteEventsJsonlwriteExecutableArtifactPathwriteExecutableMetadatawriteSummarywriteSummaryJsonl
Fields and members
Public fields and members.
allocation_recordsallocationsallocatorsanomaly_site_countanomaly_sitesanomaly_sites_droppedconfigcontrol_allocatorcontrol_contextcounterscoveragecurrent_scopeeventsexecutable_digestfree_record_headlabel_idslabelsmemory_observer_sinkmutexnext_allocation_idnext_seqobservation_producer_floorobserved_allocatorsobserver_activeobserver_sinkphysical_allocator_idrecording_failuresrequest_site_unaggregated_bytesrequest_site_unaggregated_callsrequest_sitesscope_childrenscopesstacksstream_finished
Source
Source: lib/memtrace/src/tracer.zig:465
zig
pub const Tracer = struct { control_allocator: Allocator, control_context: *ControlAllocatorContext, config: Config, labels: std.ArrayListUnmanaged(Label) = .empty, label_ids: std.StringHashMapUnmanaged(u32) = .{}, scopes: std.ArrayListUnmanaged(ScopeNode) = .empty, scope_children: std.AutoHashMapUnmanaged(ChildKey, u32) = .{}, allocators: std.ArrayListUnmanaged(AllocatorState) = .empty, observed_allocators: std.AutoHashMapUnmanaged( ObservedAllocatorKey, u32, ) = .{}, allocations: std.AutoHashMapUnmanaged(AllocationKey, u32) = .{}, allocation_records: std.ArrayListUnmanaged(AllocationRecord) = .empty, free_record_head: u32 = record_index_none, anomaly_sites: []AnomalySite = &.{}, anomaly_site_count: usize = 0, anomaly_sites_dropped: u64 = 0, request_sites: std.AutoHashMapUnmanaged( RequestSiteKey, RequestSiteCounters, ) = .{}, request_site_unaggregated_calls: u64 = 0, request_site_unaggregated_bytes: u64 = 0, events: std.ArrayListUnmanaged(Event) = .empty, stacks: stack_mod.Interner = .{}, executable_digest: ?stack_mod.Digest = null, coverage: coverage_mod.Manifest = coverage_mod.boundaryManifest(), counters: [layer_count]Counters = @splat(.{}), current_scope: u32 = root_scope_id, next_allocation_id: u64 = 1, next_seq: u64 = 1, recording_failures: u64 = 0, stream_finished: bool = false, observer_sink: observe.Sink = undefined, memory_observer_sink: sys.memory.observe.Sink = undefined, observer_active: bool = false, observation_producer_floor: u64 = 0, physical_allocator_id: ?u32 = null, mutex: std.atomic.Mutex = .unlocked, pub fn init(control_allocator: Allocator, config: Config) !Tracer { if (config.anomaly_site_capacity > anomaly_site_capacity_max) { return error.AnomalySiteCapacityTooLarge; } if (config.request_site_capacity > request_site_capacity_max) { return error.RequestSiteCapacityTooLarge; } if (config.allocation_attribution == .stack) { if (!config.record_events) { return error.StackAttributionRequiresEventRecording; } try stack_mod.capture.validateFrameLimit(config.stack_frame_limit); if (!stack_mod.capture.supportsCapture()) { return error.StackCaptureUnsupported; } } const control_context = try control_allocator.create( ControlAllocatorContext, ); var control_context_owned = true; errdefer { if (control_context_owned) { control_allocator.destroy(control_context); } } control_context.* = .{ .backing = control_allocator }; var tracer: Tracer = .{ .control_allocator = control_context.allocator(), .control_context = control_context, .config = config, }; control_context_owned = false; errdefer { tracer.stream_finished = true; tracer.deinit(); } tracer.anomaly_sites = try tracer.control_allocator.alloc( AnomalySite, config.anomaly_site_capacity, ); try tracer.request_sites.ensureTotalCapacity( tracer.control_allocator, config.request_site_capacity, ); if (config.allocation_attribution == .stack or config.capture_executable_identity) { tracer.executable_digest = try stack_mod.identity.runningDigest(); } try tracer.bootstrapRoot(); if (config.event_writer) |writer| { if (tracer.executable_digest) |digest| { try stack_mod.identity.writeMetadata(writer, digest); } } if (config.record_events) try tracer.appendEventLocked(.{ .kind = .trace_start }); return tracer; } pub fn deinit(self: *Tracer) void { std.debug.assert(!self.observer_active); if (self.config.event_writer != null) { std.debug.assert(self.stream_finished); } for (self.labels.items) |label| self.control_allocator.free(label.text); for (self.scopes.items) |scope| if (scope.path) |path| self.control_allocator.free(path); self.labels.deinit(self.control_allocator); self.label_ids.deinit(self.control_allocator); self.scopes.deinit(self.control_allocator); self.scope_children.deinit(self.control_allocator); self.allocators.deinit(self.control_allocator); self.observed_allocators.deinit(self.control_allocator); self.allocations.deinit(self.control_allocator); self.allocation_records.deinit(self.control_allocator); self.control_allocator.free(self.anomaly_sites); self.request_sites.deinit(self.control_allocator); self.events.deinit(self.control_allocator); self.stacks.deinit(self.control_allocator); const control_context = self.control_context; const control_backing = control_context.backing; self.* = undefined; control_backing.destroy(control_context); } pub fn tracedAllocator(self: *Tracer, backing: Allocator, name: []const u8) !TracingAllocator { return try TracingAllocator.init(self, backing, .{ .name = name }); } pub fn tracedAllocatorWithOptions(self: *Tracer, backing: Allocator, options: TracingAllocatorOptions) !TracingAllocator { return try TracingAllocator.init(self, backing, options); } pub fn observeOwnedAllocatorsIfAvailable( self: *Tracer, ) !?OwnedObservation { if (comptime !observe.enabled or !sys.memory.observe.enabled) { return null; } return try self.observeOwnedAllocators(); } pub fn observeOwnedAllocators(self: *Tracer) !OwnedObservation { if (comptime !observe.enabled) { return error.AllocatorObservationNotCompiled; } if (comptime !sys.memory.observe.enabled) { return error.MemoryObservationNotCompiled; } if (self.observer_active) return error.AllocatorObservationAlreadyActive; _ = sys.memory.pageSize(); self.observer_sink = .{ .context = self, .record = recordOwnedOperation, }; const previous_producer_floor = self.markObservationProducerFloorPending(); var session = observe.install(&self.observer_sink) catch |err| { self.restoreObservationProducerFloor(previous_producer_floor); return err; }; errdefer session.deinit(); self.reconcileObservationProducerFloor( session.producerIdFloor(), ); self.memory_observer_sink = .{ .context = self, .record = recordPhysicalOperation, }; const memory_session = try sys.memory.observe.install( &self.memory_observer_sink, ); self.observer_active = true; self.control_context.setEpochActive(true); self.coverage = coverage_mod.processManifest( self.config.static_coverage, ); return .{ .tracer = self, .session = session, .memory_session = memory_session, }; } pub fn enter(self: *Tracer, label: []const u8) !Scope { self.lock(); defer self.unlock(); const label_id = try self.internLabelLocked(label); const previous = self.current_scope; const scope_id = try self.scopeChildLocked(previous, label_id); self.current_scope = scope_id; try self.appendEventLocked(.{ .kind = .scope_enter, .scope_id = scope_id, .label_id = label_id, .live_bytes = self.countersForLayer(.backing_boundary).live_bytes, }); return .{ .tracer = self, .previous = previous, .scope_id = scope_id, }; } pub fn snapshot(self: *Tracer) Snapshot { return self.snapshotLayer(.backing_boundary); } pub fn snapshotLayer( self: *Tracer, layer: event_mod.Layer, ) Snapshot { self.lock(); defer self.unlock(); return self.countersForLayer(layer).snapshot(self.next_seq - 1); } pub fn diff(self: *Tracer, before: Snapshot) Difference { return self.diffLayer(.backing_boundary, before); } pub fn diffLayer( self: *Tracer, layer: event_mod.Layer, before: Snapshot, ) Difference { const after = self.snapshotLayer(layer); return snapshotDifference(before, after); } fn snapshotDifference(before: Snapshot, after: Snapshot) Difference { return .{ .allocations = after.allocations - before.allocations, .frees = after.frees - before.frees, .live_allocations = signedDiff(after.live_allocations, before.live_allocations), .allocated_bytes = after.allocated_bytes - before.allocated_bytes, .freed_bytes = after.freed_bytes - before.freed_bytes, .live_bytes = signedDiff(after.live_bytes, before.live_bytes), .high_water_live_bytes = after.high_water_live_bytes, .retained_bytes = signedDiff(after.retained_bytes, before.retained_bytes), .high_water_retained_bytes = after.high_water_retained_bytes, .completed_lifetimes = after.completed_lifetimes - before.completed_lifetimes, .lifetime_total_events = after.lifetime_total_events - before.lifetime_total_events, .lifetime_max_events = after.lifetime_max_events, }; } pub fn observerControlOperations(self: *const Tracer) u64 { return self.control_context.operationCount(); } pub fn writeSummary(self: *Tracer, writer: *std.Io.Writer, options: SummaryOptions) !void { self.lock(); defer self.unlock(); const counters = self.countersForLayer(options.layer); var summaries = std.ArrayListUnmanaged(ScopeSummary).empty; defer { for (summaries.items) |summary| self.control_allocator.free(summary.path); summaries.deinit(self.control_allocator); } for (self.scopes.items, 0..) |scope, index| { const scope_counters = scope.counters[@backingInt(options.layer)]; if (index == root_scope_id and scope_counters.allocations == 0) continue; if (!options.include_zero_live and scope_counters.live_bytes == 0 and scope_counters.high_water_live_bytes == 0) continue; if (scope_counters.live_bytes < options.min_live_bytes and scope_counters.high_water_live_bytes < options.min_live_bytes) continue; try summaries.append(self.control_allocator, .{ .scope_id = @intCast(index), .path = try self.scopePathAllocLocked(@intCast(index)), .counters = scope_counters, }); } std.mem.sort(ScopeSummary, summaries.items, {}, scopeSummaryGreaterThan); var request_site_summaries = std.ArrayListUnmanaged(RequestSiteSummary).empty; defer request_site_summaries.deinit(self.control_allocator); if (options.layer == .logical_allocator) { try request_site_summaries.ensureTotalCapacity( self.control_allocator, self.request_sites.count(), ); var request_sites = self.request_sites.iterator(); while (request_sites.next()) |entry| { request_site_summaries.appendAssumeCapacity(.{ .key = entry.key_ptr.*, .counters = entry.value_ptr.*, }); } std.mem.sort( RequestSiteSummary, request_site_summaries.items, {}, requestSiteSummaryGreaterThan, ); } const request_site_limit = @min( options.top, request_site_summaries.items.len, ); var request_site_omitted_calls = self.request_site_unaggregated_calls; var request_site_omitted_bytes = self.request_site_unaggregated_bytes; for (request_site_summaries.items[request_site_limit..]) |site| { request_site_omitted_calls +|= site.counters.calls; request_site_omitted_bytes +|= site.counters.requested_bytes; } if (options.layer == .logical_allocator) { try writer.print( "memtrace layer={s} 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(), counters.allocations, counters.frees, counters.live_allocations, counters.allocated_bytes, counters.freed_bytes, counters.live_bytes, counters.high_water_live_bytes, counters.bulk_invalidated_requests, counters.bulk_invalidated_bytes, counters.untracked_requests, counters.untracked_request_bytes, }, ); const coverage = self.lifecycleCoverageLocked(); 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, counters.lifecycle_events, }, ); try writer.print( "memtrace request_sites capacity={d} groups={d} displayed={d} " ++ "unaggregated_calls={d} unaggregated_bytes={d} " ++ "omitted_calls={d} omitted_bytes={d} exact={s}\n", .{ self.config.request_site_capacity, self.request_sites.count(), request_site_limit, self.request_site_unaggregated_calls, self.request_site_unaggregated_bytes, request_site_omitted_calls, request_site_omitted_bytes, if (request_site_omitted_calls == 0) "true" else "false", }, ); } else { try writer.print( "memtrace layer={s} 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(), counters.allocations, counters.frees, counters.live_allocations, counters.allocated_bytes, counters.freed_bytes, counters.live_bytes, counters.high_water_live_bytes, }, ); try writer.print( "memtrace retained_bytes={d} high_water_retained_bytes={d}\n", .{ counters.retained_bytes, counters.high_water_retained_bytes, }, ); } try writer.print( "memtrace lifetimes completed={d} total_events={d} mean_events={d} max_events={d}\n", .{ counters.completed_lifetimes, counters.lifetime_total_events, meanLifetimeEvents(counters.*), counters.lifetime_max_events, }, ); if (counters.failed_allocations != 0 or counters.failed_resizes != 0 or counters.failed_remaps != 0 or counters.unmatched_frees != 0 or counters.unmatched_resizes != 0 or counters.unmatched_remaps != 0 or self.recording_failures != 0) { try writer.print( "memtrace anomalies failed_allocations={d} failed_resizes={d} " ++ "failed_remaps={d} unmatched_frees={d} unmatched_resizes={d} " ++ "unmatched_remaps={d} " ++ "recording_failures={d}\n", .{ counters.failed_allocations, counters.failed_resizes, counters.failed_remaps, counters.unmatched_frees, counters.unmatched_resizes, counters.unmatched_remaps, self.recording_failures, }, ); } const limit = @min(options.top, summaries.items.len); for (summaries.items[0..limit]) |summary| { if (options.layer == .logical_allocator) { 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} completed_lifetimes={d} " ++ "lifetime_total_events={d} lifetime_mean_events={d} " ++ "lifetime_max_events={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, summary.counters.completed_lifetimes, summary.counters.lifetime_total_events, meanLifetimeEvents(summary.counters), summary.counters.lifetime_max_events, }, ); } 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}\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, }, ); } } for (request_site_summaries.items[0..request_site_limit]) |site| { const path = try self.scopePathAllocLocked(site.key.scope_id); defer self.control_allocator.free(path); try writer.print( "request_site producer_id={d} producer={s} operation={s} " ++ "scope={s} return_address=0x{x} succeeded={s} calls={d} " ++ "requested_bytes={d}\n", .{ site.key.producer_id, @tagName(site.key.producer), @tagName(site.key.operation), path, site.key.return_address, if (site.key.succeeded) "true" else "false", site.counters.calls, site.counters.requested_bytes, }, ); } } pub fn writeSummaryJsonl( self: *Tracer, writer: *std.Io.Writer, options: SummaryOptions, ) !void { self.lock(); defer self.unlock(); const counters = self.countersForLayer(options.layer); var summaries = std.ArrayListUnmanaged(ScopeSummary).empty; defer { for (summaries.items) |summary| self.control_allocator.free(summary.path); summaries.deinit(self.control_allocator); } for (self.scopes.items, 0..) |scope, index| { const scope_counters = scope.counters[@backingInt(options.layer)]; if (index == root_scope_id and scope_counters.allocations == 0) continue; if (!options.include_zero_live and scope_counters.live_bytes == 0 and scope_counters.high_water_live_bytes == 0) { continue; } if (scope_counters.live_bytes < options.min_live_bytes and scope_counters.high_water_live_bytes < options.min_live_bytes) { continue; } try summaries.append(self.control_allocator, .{ .scope_id = @intCast(index), .path = try self.scopePathAllocLocked(@intCast(index)), .counters = scope_counters, }); } std.mem.sort(ScopeSummary, summaries.items, {}, scopeSummaryGreaterThan); var request_site_summaries = std.ArrayListUnmanaged(RequestSiteSummary).empty; defer request_site_summaries.deinit(self.control_allocator); if (options.layer == .logical_allocator) { try request_site_summaries.ensureTotalCapacity( self.control_allocator, self.request_sites.count(), ); var request_sites = self.request_sites.iterator(); while (request_sites.next()) |entry| { request_site_summaries.appendAssumeCapacity(.{ .key = entry.key_ptr.*, .counters = entry.value_ptr.*, }); } std.mem.sort( RequestSiteSummary, request_site_summaries.items, {}, requestSiteSummaryGreaterThan, ); } const request_site_limit = @min( options.top, request_site_summaries.items.len, ); var request_site_omitted_calls = self.request_site_unaggregated_calls; var request_site_omitted_bytes = self.request_site_unaggregated_bytes; for (request_site_summaries.items[request_site_limit..]) |site| { request_site_omitted_calls +|= site.counters.calls; request_site_omitted_bytes +|= site.counters.requested_bytes; } 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("allocations", counters.allocations); try object.field("frees", counters.frees); try object.field("resizes", counters.resizes); try object.field("remaps", counters.remaps); try object.field("allocated_bytes", counters.allocated_bytes); if (options.layer == .logical_allocator) { try object.field("open_requests", counters.live_allocations); try object.field("requested_bytes", counters.allocated_bytes); try object.field( "explicitly_closed_bytes", counters.freed_bytes, ); try object.field("open_request_bytes", counters.live_bytes); try object.field( "high_water_open_request_bytes", counters.high_water_live_bytes, ); try object.field( "bulk_invalidated_requests", counters.bulk_invalidated_requests, ); try object.field( "bulk_invalidated_bytes", counters.bulk_invalidated_bytes, ); try object.field("untracked_requests", counters.untracked_requests); try object.field( "untracked_request_bytes", counters.untracked_request_bytes, ); try object.field("lifecycle_events", counters.lifecycle_events); const coverage = self.lifecycleCoverageLocked(); 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, ); try object.field( "request_site_capacity", self.config.request_site_capacity, ); try object.field("request_site_groups", self.request_sites.count()); try object.field( "request_site_groups_displayed", request_site_limit, ); try object.field( "request_site_unaggregated_calls", self.request_site_unaggregated_calls, ); try object.field( "request_site_unaggregated_bytes", self.request_site_unaggregated_bytes, ); try object.field( "request_site_omitted_calls", request_site_omitted_calls, ); try object.field( "request_site_omitted_bytes", request_site_omitted_bytes, ); try object.field( "request_site_attribution_exact", request_site_omitted_calls == 0, ); } else { try object.field("live_allocations", counters.live_allocations); try object.field("freed_bytes", counters.freed_bytes); try object.field("live_bytes", counters.live_bytes); try object.field( "high_water_live_bytes", counters.high_water_live_bytes, ); try object.field("retained_bytes", counters.retained_bytes); try object.field( "high_water_retained_bytes", counters.high_water_retained_bytes, ); } try object.field("completed_lifetimes", counters.completed_lifetimes); try object.field("lifetime_total_events", counters.lifetime_total_events); try object.field("lifetime_mean_events", meanLifetimeEvents(counters.*)); try object.field("lifetime_max_events", counters.lifetime_max_events); try object.field("failed_allocations", counters.failed_allocations); try object.field("failed_resizes", counters.failed_resizes); try object.field("failed_remaps", counters.failed_remaps); try object.field("unmatched_frees", counters.unmatched_frees); try object.field("unmatched_resizes", counters.unmatched_resizes); try object.field("unmatched_remaps", counters.unmatched_remaps); try object.field("anomaly_sites", self.anomaly_site_count); try object.field( "anomaly_sites_dropped", self.anomaly_sites_dropped, ); try object.field( "anomaly_attribution_exact", self.anomaly_sites_dropped == 0, ); try object.field("recording_failures", self.recording_failures); 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("layer", options.layer.tag()); try row.field("scope", summary.path); try writeCounterFields(row, summary.counters, options.layer); try row.endLine(); } for (request_site_summaries.items[0..request_site_limit]) |site| { const path = try self.scopePathAllocLocked(site.key.scope_id); defer self.control_allocator.free(path); var row_stream = pretty_json.Writer.init(writer, .minified); const row = try row_stream.object(); try row.field("kind", "request_site"); try row.field("layer", event_mod.Layer.logical_allocator.tag()); try row.field("producer_id", site.key.producer_id); try row.field("producer", @tagName(site.key.producer)); try row.field("operation", @tagName(site.key.operation)); try row.field("scope", path); try row.field("return_address", site.key.return_address); try row.field("succeeded", site.key.succeeded); try row.field("calls", site.counters.calls); try row.field("requested_bytes", site.counters.requested_bytes); try row.endLine(); } var site_counts = std.AutoHashMapUnmanaged( LiveSiteKey, LiveSiteSummary, ).empty; defer site_counts.deinit(self.control_allocator); var live_allocations = self.allocations.valueIterator(); while (live_allocations.next()) |record_index| { const allocation = &self.allocation_records.items[record_index.*]; std.debug.assert(allocation.active); if (allocation.layer != options.layer) continue; const key = LiveSiteKey{ .allocator_id = allocation.allocator_id, .scope_id = allocation.scope_id, .return_address = allocation.return_address, }; const entry = try site_counts.getOrPut(self.control_allocator, key); if (!entry.found_existing) { entry.value_ptr.* = .{ .key = key, .live_allocations = 0, .live_bytes = 0, }; } entry.value_ptr.live_allocations += 1; entry.value_ptr.live_bytes += allocation.len; } var live_sites = std.ArrayListUnmanaged(LiveSiteSummary).empty; defer live_sites.deinit(self.control_allocator); try live_sites.ensureTotalCapacity( self.control_allocator, site_counts.count(), ); var sites = site_counts.valueIterator(); while (sites.next()) |site| live_sites.appendAssumeCapacity(site.*); std.mem.sort( LiveSiteSummary, live_sites.items, {}, liveSiteSummaryGreaterThan, ); const live_site_limit = @min(options.top, live_sites.items.len); for (live_sites.items[0..live_site_limit]) |site| { const path = try self.scopePathAllocLocked(site.key.scope_id); defer self.control_allocator.free(path); const allocator_label_id = self.allocators.items[site.key.allocator_id].label_id; var row_stream = pretty_json.Writer.init(writer, .minified); const row = try row_stream.object(); try row.field( "kind", if (options.layer == .logical_allocator) "open_request_site" else "live_site", ); try row.field("layer", options.layer.tag()); try row.field("allocator", self.labels.items[allocator_label_id].text); try row.field("scope", path); try row.field("return_address", site.key.return_address); if (options.layer == .logical_allocator) { try row.field("open_requests", site.live_allocations); try row.field("open_request_bytes", site.live_bytes); } else { try row.field("live_allocations", site.live_allocations); try row.field("live_bytes", site.live_bytes); } try row.endLine(); } for (self.anomaly_sites[0..self.anomaly_site_count]) |site| { if (site.key.layer != options.layer) continue; const path = try self.scopePathAllocLocked(site.key.scope_id); defer self.control_allocator.free(path); var row_stream = pretty_json.Writer.init(writer, .minified); const row = try row_stream.object(); try row.field("kind", "anomaly_site"); try row.field("layer", site.key.layer.tag()); try row.field("reason", @tagName(site.key.reason)); try row.field("operation", @tagName(site.key.operation)); try row.field("producer_id", site.key.producer_id); try row.field("producer", @tagName(site.key.producer)); try row.field("generation", site.key.generation); try row.field("scope", path); try row.field("return_address", site.key.return_address); try row.field("old_len", site.key.old_len); try row.field("new_len", site.key.new_len); try row.field("succeeded", site.key.succeeded); try row.field("first_address", site.first_address); try row.field("occurrences", site.occurrences); try row.endLine(); } } pub fn writeEventsJsonl(self: *Tracer, writer: *std.Io.Writer) !void { self.lock(); defer self.unlock(); if (!self.config.record_events or self.config.event_writer != null) { return error.EventsNotRetained; } if (self.executable_digest) |digest| { try stack_mod.identity.writeMetadata(writer, digest); } try coverage_mod.writeMetadata(writer, self.coverageManifest()); for (self.stacks.definitions.items, 0..) |definition, index| { try stack_mod.capture.writeDefinition( writer, @intCast(index + 1), definition, ); } for (self.events.items) |recorded| { const label_text = if ((recorded.kind == .allocator or recorded.kind == .scope_enter) and recorded.label_id < self.labels.items.len) self.labels.items[recorded.label_id].text else null; const scope_text = if (recorded.kind == .scope_enter and recorded.scope_id < self.scopes.items.len) try self.scopePathCachedLocked(recorded.scope_id) else null; try recorded.writeJsonLine(writer, label_text, scope_text); } try (Event{ .seq = self.next_seq, .kind = .trace_stop, .live_bytes = self.countersForLayer(.backing_boundary).live_bytes, .recording_failures = self.recording_failures, }).writeJsonLine(writer, null, null); } pub fn finishEvents(self: *Tracer) !void { self.lock(); defer self.unlock(); if (self.config.event_writer == null) return error.EventsNotStreamed; if (self.stream_finished) return error.EventStreamAlreadyFinished; try coverage_mod.writeMetadata( self.config.event_writer.?, self.coverageManifest(), ); try self.appendEventLocked(.{ .kind = .trace_stop, .live_bytes = self.countersForLayer(.backing_boundary).live_bytes, .recording_failures = self.recording_failures, }); self.stream_finished = true; } pub fn writeEventsBundlePath(self: *Tracer, path: []const u8) !void { try self.writeExecutableArtifactPath(path); var file = try sys.fs.createFile(path, .{ .truncate = true }); defer sys.fs.closeHandle(file); var buffer: [64 * 1024]u8 = undefined; var output = file.writer(sys.fs.debugIo(), &buffer); try self.writeEventsJsonl(&output.interface); try output.interface.flush(); } pub fn writeExecutableArtifactPath( self: *Tracer, events_path: []const u8, ) !void { if (std.fs.path.dirname(events_path)) |parent| { if (parent.len != 0) try sys.fs.createDirPath(parent); } if (self.executable_digest) |digest| { const artifact_path = try stack_mod.identity.artifactPathAlloc( self.control_allocator, events_path, ); defer self.control_allocator.free(artifact_path); try stack_mod.identity.copyRunningExecutable(artifact_path, digest); } } pub fn writeExecutableMetadata( self: *Tracer, writer: *std.Io.Writer, ) !void { self.lock(); defer self.unlock(); const digest = self.executable_digest orelse return error.ExecutableIdentityNotCaptured; try stack_mod.identity.writeMetadata(writer, digest); } fn registerAllocator(self: *Tracer, name: []const u8, retention: Retention) !u32 { self.lock(); defer self.unlock(); const label_id = try self.internLabelLocked(name); const allocator_id: u32 = @intCast(self.allocators.items.len); try self.allocators.append(self.control_allocator, .{ .label_id = label_id, .retention = retention, .layer = .backing_boundary, }); try self.appendEventLocked(.{ .kind = .allocator, .allocator_id = allocator_id, .label_id = label_id, .retains_freed_memory = retention == .retains_freed_memory, .live_bytes = self.countersForLayer(.backing_boundary).live_bytes, }); return allocator_id; } fn rawAlloc( self: *Tracer, backing: Allocator, allocator_id: u32, len: usize, alignment: Alignment, ret_addr: usize, ) ?[*]u8 { if (observe.suppressed()) { self.noteObserverControlOperation(); return backing.rawAlloc(len, alignment, ret_addr); } var stack_storage: [stack_mod.max_frames_limit]usize = undefined; const captured = self.captureOperation(&stack_storage, ret_addr); var causal_context = observe.beginContext(); const operation_context = boundaryOperationContext(causal_context); const ptr = backing.rawAlloc(len, alignment, ret_addr) orelse { causal_context.finish(); self.recordAllocationFailure( allocator_id, len, alignment, ret_addr, captured, operation_context, ) catch self.markRecordingFailure(); return null; }; causal_context.finish(); self.recordAllocation( allocator_id, ptr, len, alignment, ret_addr, captured, operation_context, ) catch { backing.rawFree(ptr[0..len], alignment, ret_addr); self.markRecordingFailure(); return null; }; return ptr; } fn rawResize( self: *Tracer, backing: Allocator, allocator_id: u32, memory: []u8, alignment: Alignment, new_len: usize, ret_addr: usize, ) bool { if (observe.suppressed()) { self.noteObserverControlOperation(); return backing.rawResize(memory, alignment, new_len, ret_addr); } var stack_storage: [stack_mod.max_frames_limit]usize = undefined; const captured = self.captureOperation(&stack_storage, ret_addr); var causal_context = observe.beginContext(); const operation_context = boundaryOperationContext(causal_context); const succeeded = backing.rawResize(memory, alignment, new_len, ret_addr); causal_context.finish(); self.recordResize( allocator_id, memory.ptr, memory.len, new_len, alignment, ret_addr, succeeded, captured, operation_context, ) catch self.markRecordingFailure(); return succeeded; } fn rawRemap( self: *Tracer, backing: Allocator, allocator_id: u32, memory: []u8, alignment: Alignment, new_len: usize, ret_addr: usize, ) ?[*]u8 { if (observe.suppressed()) { self.noteObserverControlOperation(); return backing.rawRemap(memory, alignment, new_len, ret_addr); } var stack_storage: [stack_mod.max_frames_limit]usize = undefined; const captured = self.captureOperation(&stack_storage, ret_addr); var causal_context = observe.beginContext(); const operation_context = boundaryOperationContext(causal_context); const ptr = backing.rawRemap(memory, alignment, new_len, ret_addr); causal_context.finish(); self.recordRemap( allocator_id, memory.ptr, ptr, memory.len, new_len, alignment, ret_addr, captured, operation_context, ) catch self.markRecordingFailure(); return ptr; } fn rawFree( self: *Tracer, backing: Allocator, allocator_id: u32, memory: []u8, alignment: Alignment, ret_addr: usize, ) void { if (observe.suppressed()) { self.noteObserverControlOperation(); backing.rawFree(memory, alignment, ret_addr); return; } var stack_storage: [stack_mod.max_frames_limit]usize = undefined; const captured = self.captureOperation(&stack_storage, ret_addr); var causal_context = observe.beginContext(); const operation_context = boundaryOperationContext(causal_context); backing.rawFree(memory, alignment, ret_addr); causal_context.finish(); self.recordFree( allocator_id, memory.ptr, memory.len, alignment, ret_addr, captured, operation_context, ) catch self.markRecordingFailure(); } fn recordOwnedEvent( self: *Tracer, observed: observe.Event, ) !void { std.debug.assert(observed.producer_id != 0); std.debug.assert(observed.operation_id != 0); const allocator_id = try self.observedAllocatorId(observed); const alignment = Alignment.fromByteUnits(observed.alignment); const operation_context = OperationContext{ .layer = .logical_allocator, .operation_id = observed.operation_id, .parent_operation_id = observed.parent_operation_id, .producer_id = observed.producer_id, .producer = observed.producer, .generation = observed.generation, .owner_cookie = observed.owner_cookie, }; if (!self.validateObservedIdentity( allocator_id, observed, operation_context, )) return; if (observed.operation == .lifecycle) { return self.recordLifecycle( allocator_id, observed, operation_context, ); } var stack_storage: [stack_mod.max_frames_limit]usize = undefined; const captured = self.captureOperation( &stack_storage, observed.return_address, ); switch (observed.operation) { .alloc => { if (!observed.succeeded) { return self.recordAllocationFailure( allocator_id, observed.len, alignment, observed.return_address, captured, operation_context, ); } std.debug.assert(observed.address != 0); return self.recordAllocation( allocator_id, @ptrFromInt(observed.address), observed.len, alignment, observed.return_address, captured, operation_context, ); }, .resize => { std.debug.assert(observed.old_address != 0); return self.recordResize( allocator_id, @ptrFromInt(observed.old_address), observed.old_len, observed.len, alignment, observed.return_address, observed.succeeded, captured, operation_context, ); }, .remap => { std.debug.assert(observed.old_address != 0); const new_ptr: ?[*]u8 = if (observed.succeeded) @ptrFromInt(observed.address) else null; return self.recordRemap( allocator_id, @ptrFromInt(observed.old_address), new_ptr, observed.old_len, observed.len, alignment, observed.return_address, captured, operation_context, ); }, .free => { std.debug.assert(observed.old_address != 0); return self.recordFree( allocator_id, @ptrFromInt(observed.old_address), observed.old_len, alignment, observed.return_address, captured, operation_context, ); }, .lifecycle => unreachable, } } fn recordPhysicalEvent( self: *Tracer, observed: sys.memory.observe.Event, operation_context: OperationContext, ) !void { const allocator_id = try self.physicalAllocatorId(); var stack_storage: [stack_mod.max_frames_limit]usize = undefined; const captured = self.captureOperation( &stack_storage, observed.return_address, ); self.lock(); defer self.unlock(); const stack_id = try self.internStackLocked(captured); try self.appendEventLocked(.{ .kind = physicalEventKind(observed.operation), .allocator_id = allocator_id, .scope_id = self.current_scope, .label_id = self.scopes.items[self.current_scope].label_id, .address = observed.address, .old_address = observed.address, .len = observed.len, .old_len = observed.len, .alignment = @intCast(sys.memory.pageSize()), .return_address = observed.return_address, .stack_id = stack_id, .succeeded = observed.succeeded, .layer = .physical_page, .operation_id = operation_context.operation_id, .parent_operation_id = operation_context.parent_operation_id, .producer_id = @backingInt(observed.source) + 1, .producer = .sys_memory, }); } fn observedAllocatorId( self: *Tracer, observed: observe.Event, ) !u32 { self.lock(); defer self.unlock(); const key = ObservedAllocatorKey{ .producer_id = observed.producer_id, .producer = observed.producer, }; const entry = try self.observed_allocators.getOrPut( self.control_allocator, key, ); if (entry.found_existing) return entry.value_ptr.*; var committed = false; errdefer { if (!committed) { _ = self.observed_allocators.fetchRemove(key); } } const label_id = try self.internLabelLocked( @tagName(observed.producer), ); const allocator_id = std.math.cast( u32, self.allocators.items.len, ) orelse return error.OutOfMemory; const retention = observedProducerRetention(observed.producer); const floor_pending = self.observation_producer_floor == observation_producer_floor_pending; const prefix_complete = !floor_pending and observed.producer_id >= self.observation_producer_floor; try self.allocators.append(self.control_allocator, .{ .label_id = label_id, .retention = retention, .layer = .logical_allocator, .producer_id = observed.producer_id, .producer = observed.producer, .owner_cookie = observed.owner_cookie, .generation = observed.generation, .prefix_complete = prefix_complete, .lifecycle_instrumented = observedProducerLifecycleInstrumented(observed.producer), .observation_floor_pending = floor_pending, }); entry.value_ptr.* = allocator_id; committed = true; try self.appendObservedAllocatorEventLocked(allocator_id); return allocator_id; } fn appendObservedAllocatorEventLocked( self: *Tracer, allocator_id: u32, ) !void { var suppression = observe.suppress(); defer suppression.deinit(); const allocator = self.allocators.items[allocator_id]; try self.appendEventLocked(.{ .kind = .allocator, .allocator_id = allocator_id, .label_id = allocator.label_id, .retains_freed_memory = allocator.retention == .retains_freed_memory, .live_bytes = self.countersForLayer(.logical_allocator).live_bytes, .layer = .logical_allocator, .producer_id = allocator.producer_id, .producer = allocator.producer, .generation = allocator.generation, .owner_cookie = allocator.owner_cookie, .lifecycle_instrumented = allocator.lifecycle_instrumented, .observation_prefix_complete = allocator.prefix_complete, }); } fn markObservationProducerFloorPending(self: *Tracer) u64 { self.lock(); defer self.unlock(); std.debug.assert( self.observation_producer_floor != observation_producer_floor_pending, ); const previous = self.observation_producer_floor; self.observation_producer_floor = observation_producer_floor_pending; return previous; } fn restoreObservationProducerFloor( self: *Tracer, previous: u64, ) void { self.lock(); defer self.unlock(); std.debug.assert( self.observation_producer_floor == observation_producer_floor_pending, ); self.observation_producer_floor = previous; } fn reconcileObservationProducerFloor( self: *Tracer, producer_floor: u64, ) void { self.lock(); defer self.unlock(); std.debug.assert( self.observation_producer_floor == observation_producer_floor_pending, ); self.observation_producer_floor = producer_floor; for (self.allocators.items, 0..) |*allocator, allocator_index| { if (!allocator.observation_floor_pending) continue; const prefix_was_complete = allocator.prefix_complete; allocator.prefix_complete = allocator.prefix_complete or allocator.producer_id >= producer_floor; allocator.observation_floor_pending = false; if (!prefix_was_complete and allocator.prefix_complete and self.config.record_events) { self.appendObservedAllocatorEventLocked( @intCast(allocator_index), ) catch { self.recording_failures +|= 1; }; } } } fn physicalAllocatorId(self: *Tracer) !u32 { self.lock(); defer self.unlock(); if (self.physical_allocator_id) |allocator_id| return allocator_id; const label_id = try self.internLabelLocked("sys.memory"); const allocator_id = std.math.cast( u32, self.allocators.items.len, ) orelse return error.OutOfMemory; try self.allocators.append(self.control_allocator, .{ .label_id = label_id, .retention = .releases_freed_memory, .layer = .physical_page, }); self.physical_allocator_id = allocator_id; try self.appendEventLocked(.{ .kind = .allocator, .allocator_id = allocator_id, .label_id = label_id, .layer = .physical_page, .producer = .sys_memory, }); return allocator_id; } fn captureOperation( self: *Tracer, storage: *[stack_mod.max_frames_limit]usize, ret_addr: usize, ) ?stack_mod.Capture { if (self.config.allocation_attribution != .stack) return null; return stack_mod.capture.capture( storage, ret_addr, self.config.stack_frame_limit, ); } fn recordAllocation( self: *Tracer, allocator_id: u32, ptr: [*]u8, len: usize, alignment: Alignment, ret_addr: usize, captured: ?stack_mod.Capture, operation_context: OperationContext, ) !void { self.lock(); defer self.unlock(); const stack_id = try self.internStackLocked(captured); const address = @intFromPtr(ptr); const allocation_id = self.next_allocation_id; self.next_allocation_id += 1; const key = AllocationKey{ .allocator_id = allocator_id, .address = address, }; const entry = try self.allocations.getOrPut( self.control_allocator, key, ); if (entry.found_existing) { self.noteAnomalyLocked( .duplicate_current_address, .alloc, operation_context, address, 0, len, true, ret_addr, ); self.applyUntrackedAllocationLocked( allocator_id, self.current_scope, len, operation_context.layer, ); return self.appendEventLocked(.{ .kind = .alloc, .allocator_id = allocator_id, .scope_id = self.current_scope, .label_id = self.scopes.items[self.current_scope].label_id, .address = address, .len = len, .alignment = @intCast(alignment.toByteUnits()), .return_address = ret_addr, .stack_id = stack_id, .succeeded = true, .tracked = false, .live_bytes = self.countersForLayer(operation_context.layer).live_bytes, .layer = operation_context.layer, .operation_id = operation_context.operation_id, .parent_operation_id = operation_context.parent_operation_id, .producer_id = operation_context.producer_id, .producer = operation_context.producer, .generation = operation_context.generation, .owner_cookie = operation_context.owner_cookie, }); } var map_committed = false; errdefer { if (!map_committed) { _ = self.allocations.fetchRemove(key); } } const record_index = try self.acquireAllocationRecordLocked(.{ .allocation_id = allocation_id, .allocator_id = allocator_id, .address = address, .len = len, .alignment = alignment, .scope_id = self.current_scope, .return_address = ret_addr, .stack_id = stack_id, .allocation_seq = self.next_seq, .layer = operation_context.layer, .generation = operation_context.generation, }); entry.value_ptr.* = record_index; self.linkAllocationRecordLocked(record_index); map_committed = true; self.applyAllocationLocked( allocator_id, self.current_scope, len, operation_context.layer, ); try self.appendEventLocked(.{ .kind = .alloc, .allocator_id = allocator_id, .allocation_id = allocation_id, .scope_id = self.current_scope, .label_id = self.scopes.items[self.current_scope].label_id, .address = address, .len = len, .alignment = @intCast(alignment.toByteUnits()), .return_address = ret_addr, .stack_id = stack_id, .succeeded = true, .live_bytes = self.countersForLayer(operation_context.layer).live_bytes, .layer = operation_context.layer, .operation_id = operation_context.operation_id, .parent_operation_id = operation_context.parent_operation_id, .producer_id = operation_context.producer_id, .producer = operation_context.producer, .generation = operation_context.generation, .owner_cookie = operation_context.owner_cookie, }); } fn recordAllocationFailure( self: *Tracer, allocator_id: u32, len: usize, alignment: Alignment, ret_addr: usize, captured: ?stack_mod.Capture, operation_context: OperationContext, ) !void { self.lock(); defer self.unlock(); const stack_id = try self.internStackLocked(captured); self.applyFailedOperationLocked( allocator_id, .alloc, operation_context.layer, ); try self.appendEventLocked(.{ .kind = .alloc, .allocator_id = allocator_id, .scope_id = self.current_scope, .label_id = self.scopes.items[self.current_scope].label_id, .len = len, .alignment = @intCast(alignment.toByteUnits()), .return_address = ret_addr, .stack_id = stack_id, .succeeded = false, .live_bytes = self.countersForLayer(operation_context.layer).live_bytes, .layer = operation_context.layer, .operation_id = operation_context.operation_id, .parent_operation_id = operation_context.parent_operation_id, .producer_id = operation_context.producer_id, .producer = operation_context.producer, .generation = operation_context.generation, .owner_cookie = operation_context.owner_cookie, }); } fn recordResize( self: *Tracer, allocator_id: u32, ptr: [*]u8, old_len: usize, new_len: usize, alignment: Alignment, ret_addr: usize, succeeded: bool, captured: ?stack_mod.Capture, operation_context: OperationContext, ) !void { self.lock(); defer self.unlock(); const address = @intFromPtr(ptr); const stack_id = try self.internStackLocked(captured); const record_index = self.allocations.get(.{ .allocator_id = allocator_id, .address = address, }) orelse { self.countersForLayer(operation_context.layer).unmatched_resizes += 1; self.noteAnomalyLocked( self.unknownReasonLocked(allocator_id), .resize, operation_context, address, old_len, new_len, succeeded, ret_addr, ); if (!succeeded) self.applyFailedOperationLocked( allocator_id, .resize, operation_context.layer, ); return self.appendUnknownResizeLocked( allocator_id, address, old_len, new_len, alignment, ret_addr, stack_id, succeeded, operation_context, ); }; const record = &self.allocation_records.items[record_index]; const scope_id = record.scope_id; std.debug.assert(record.layer == operation_context.layer); if (record.len != old_len) { self.noteAnomalyLocked( .length_mismatch, .resize, operation_context, address, old_len, new_len, succeeded, ret_addr, ); } if (record.alignment != alignment) { self.noteAnomalyLocked( .alignment_mismatch, .resize, operation_context, address, old_len, new_len, succeeded, ret_addr, ); } if (succeeded) { const recorded_old_len = record.len; record.len = new_len; record.alignment = alignment; self.applyResizeLocked( record.allocator_id, scope_id, recorded_old_len, new_len, operation_context.layer, ); } else { self.applyFailedOperationLocked( allocator_id, .resize, operation_context.layer, ); } try self.appendEventLocked(.{ .kind = .resize, .allocator_id = allocator_id, .allocation_id = record.allocation_id, .scope_id = scope_id, .label_id = self.scopes.items[scope_id].label_id, .address = address, .old_len = old_len, .len = new_len, .alignment = @intCast(alignment.toByteUnits()), .return_address = ret_addr, .stack_id = stack_id, .succeeded = succeeded, .live_bytes = self.countersForLayer(operation_context.layer).live_bytes, .layer = operation_context.layer, .operation_id = operation_context.operation_id, .parent_operation_id = operation_context.parent_operation_id, .producer_id = operation_context.producer_id, .producer = operation_context.producer, .generation = operation_context.generation, .owner_cookie = operation_context.owner_cookie, }); } fn appendUnknownResizeLocked( self: *Tracer, allocator_id: u32, address: usize, old_len: usize, new_len: usize, alignment: Alignment, ret_addr: usize, stack_id: u32, succeeded: bool, operation_context: OperationContext, ) !void { try self.appendEventLocked(.{ .kind = .resize, .allocator_id = allocator_id, .scope_id = self.current_scope, .label_id = self.scopes.items[self.current_scope].label_id, .address = address, .old_len = old_len, .len = new_len, .alignment = @intCast(alignment.toByteUnits()), .return_address = ret_addr, .stack_id = stack_id, .succeeded = succeeded, .tracked = false, .live_bytes = self.countersForLayer(operation_context.layer).live_bytes, .layer = operation_context.layer, .operation_id = operation_context.operation_id, .parent_operation_id = operation_context.parent_operation_id, .producer_id = operation_context.producer_id, .producer = operation_context.producer, .generation = operation_context.generation, .owner_cookie = operation_context.owner_cookie, }); } fn recordRemap( self: *Tracer, allocator_id: u32, old_ptr: [*]u8, new_ptr: ?[*]u8, old_len: usize, new_len: usize, alignment: Alignment, ret_addr: usize, captured: ?stack_mod.Capture, operation_context: OperationContext, ) !void { self.lock(); defer self.unlock(); const old_address = @intFromPtr(old_ptr); const stack_id = try self.internStackLocked(captured); const key = AllocationKey{ .allocator_id = allocator_id, .address = old_address, }; const existing_index = self.allocations.get(key) orelse { self.countersForLayer(operation_context.layer).unmatched_remaps += 1; self.noteAnomalyLocked( self.unknownReasonLocked(allocator_id), .remap, operation_context, old_address, old_len, new_len, new_ptr != null, ret_addr, ); if (new_ptr == null) self.applyFailedOperationLocked( allocator_id, .remap, operation_context.layer, ); return self.appendUnknownRemapLocked( allocator_id, old_address, new_ptr, old_len, new_len, alignment, ret_addr, stack_id, operation_context, ); }; const record = &self.allocation_records.items[existing_index]; std.debug.assert(record.layer == operation_context.layer); if (record.len != old_len) { self.noteAnomalyLocked( .length_mismatch, .remap, operation_context, old_address, old_len, new_len, new_ptr != null, ret_addr, ); } if (record.alignment != alignment) { self.noteAnomalyLocked( .alignment_mismatch, .remap, operation_context, old_address, old_len, new_len, new_ptr != null, ret_addr, ); } if (new_ptr == null) { self.applyFailedOperationLocked( allocator_id, .remap, operation_context.layer, ); return self.appendKnownRemapLocked( record.*, old_address, null, old_len, new_len, alignment, ret_addr, stack_id, operation_context, true, ); } const new_address = @intFromPtr(new_ptr.?); if (new_address != old_address and self.allocations.contains(.{ .allocator_id = allocator_id, .address = new_address, })) { self.noteAnomalyLocked( .duplicate_current_address, .remap, operation_context, new_address, old_len, new_len, true, ret_addr, ); _ = self.allocations.fetchRemove(key); self.clearDrainedAllocationsLocked(); const abandoned = record.*; self.unlinkAllocationRecordLocked(existing_index); self.applyLostTrackingLocked( abandoned.allocator_id, abandoned.scope_id, abandoned.len, new_len, operation_context.layer, ); self.recycleAllocationRecordLocked(existing_index); return self.appendKnownRemapLocked( abandoned, old_address, new_ptr, old_len, new_len, alignment, ret_addr, stack_id, operation_context, false, ); } _ = self.allocations.fetchRemove(key).?; self.clearDrainedAllocationsLocked(); const scope_id = record.scope_id; const recorded_old_len = record.len; record.address = new_address; record.len = new_len; record.alignment = alignment; const new_entry = try self.allocations.getOrPut( self.control_allocator, .{ .allocator_id = allocator_id, .address = new_address, }, ); std.debug.assert(!new_entry.found_existing); new_entry.value_ptr.* = existing_index; self.applyResizeLocked( record.allocator_id, scope_id, recorded_old_len, new_len, operation_context.layer, ); self.countersForLayer(operation_context.layer).remaps += 1; if (record.allocator_id < self.allocators.items.len) self.allocators.items[record.allocator_id].counters.remaps += 1; try self.appendKnownRemapLocked( record.*, old_address, new_ptr, old_len, new_len, alignment, ret_addr, stack_id, operation_context, true, ); } fn appendKnownRemapLocked( self: *Tracer, record: AllocationRecord, old_address: usize, new_ptr: ?[*]u8, old_len: usize, new_len: usize, alignment: Alignment, ret_addr: usize, stack_id: u32, operation_context: OperationContext, tracked: bool, ) !void { try self.appendEventLocked(.{ .kind = .remap, .allocator_id = record.allocator_id, .allocation_id = record.allocation_id, .scope_id = record.scope_id, .label_id = self.scopes.items[record.scope_id].label_id, .address = if (new_ptr) |ptr| @intFromPtr(ptr) else old_address, .old_address = old_address, .old_len = old_len, .len = new_len, .alignment = @intCast(alignment.toByteUnits()), .return_address = ret_addr, .stack_id = stack_id, .succeeded = new_ptr != null, .tracked = tracked, .live_bytes = self.countersForLayer(operation_context.layer).live_bytes, .layer = operation_context.layer, .operation_id = operation_context.operation_id, .parent_operation_id = operation_context.parent_operation_id, .producer_id = operation_context.producer_id, .producer = operation_context.producer, .generation = operation_context.generation, .owner_cookie = operation_context.owner_cookie, }); } fn appendUnknownRemapLocked( self: *Tracer, allocator_id: u32, old_address: usize, new_ptr: ?[*]u8, old_len: usize, new_len: usize, alignment: Alignment, ret_addr: usize, stack_id: u32, operation_context: OperationContext, ) !void { const record = AllocationRecord{ .allocation_id = 0, .allocator_id = allocator_id, .address = old_address, .len = old_len, .alignment = alignment, .scope_id = self.current_scope, .return_address = ret_addr, .stack_id = stack_id, .allocation_seq = 0, .layer = operation_context.layer, .generation = operation_context.generation, }; try self.appendKnownRemapLocked( record, old_address, new_ptr, old_len, new_len, alignment, ret_addr, stack_id, operation_context, false, ); } fn recordFree( self: *Tracer, allocator_id: u32, ptr: [*]u8, len: usize, alignment: Alignment, ret_addr: usize, captured: ?stack_mod.Capture, operation_context: OperationContext, ) !void { self.lock(); defer self.unlock(); const address = @intFromPtr(ptr); const stack_id = try self.internStackLocked(captured); const removed = self.allocations.fetchRemove(.{ .allocator_id = allocator_id, .address = address, }) orelse { self.countersForLayer(operation_context.layer).unmatched_frees += 1; self.noteAnomalyLocked( self.unknownReasonLocked(allocator_id), .free, operation_context, address, len, 0, true, ret_addr, ); return self.appendUnknownFreeLocked( allocator_id, address, len, alignment, ret_addr, stack_id, operation_context, ); }; self.clearDrainedAllocationsLocked(); const record_index = removed.value; const record = self.allocation_records.items[record_index]; std.debug.assert(record.layer == operation_context.layer); if (record.len != len) { self.noteAnomalyLocked( .length_mismatch, .free, operation_context, address, len, 0, true, ret_addr, ); } if (record.alignment != alignment) { self.noteAnomalyLocked( .alignment_mismatch, .free, operation_context, address, len, 0, true, ret_addr, ); } self.unlinkAllocationRecordLocked(record_index); self.applyLifetimeLocked( record.scope_id, completedLifetimeEvents(record.allocation_seq, self.next_seq), operation_context.layer, ); self.applyFreeLocked( record.allocator_id, record.scope_id, record.len, operation_context.layer, ); self.recycleAllocationRecordLocked(record_index); try self.appendEventLocked(.{ .kind = .free, .allocator_id = allocator_id, .allocation_id = record.allocation_id, .scope_id = record.scope_id, .label_id = self.scopes.items[record.scope_id].label_id, .address = address, .old_len = len, .len = record.len, .alignment = @intCast(alignment.toByteUnits()), .return_address = ret_addr, .stack_id = stack_id, .succeeded = true, .live_bytes = self.countersForLayer(operation_context.layer).live_bytes, .layer = operation_context.layer, .operation_id = operation_context.operation_id, .parent_operation_id = operation_context.parent_operation_id, .producer_id = operation_context.producer_id, .producer = operation_context.producer, .generation = operation_context.generation, .owner_cookie = operation_context.owner_cookie, }); } fn appendUnknownFreeLocked( self: *Tracer, allocator_id: u32, address: usize, len: usize, alignment: Alignment, ret_addr: usize, stack_id: u32, operation_context: OperationContext, ) !void { try self.appendEventLocked(.{ .kind = .free, .allocator_id = allocator_id, .scope_id = self.current_scope, .label_id = self.scopes.items[self.current_scope].label_id, .address = address, .old_len = len, .len = len, .alignment = @intCast(alignment.toByteUnits()), .return_address = ret_addr, .stack_id = stack_id, .succeeded = true, .tracked = false, .live_bytes = self.countersForLayer(operation_context.layer).live_bytes, .layer = operation_context.layer, .operation_id = operation_context.operation_id, .parent_operation_id = operation_context.parent_operation_id, .producer_id = operation_context.producer_id, .producer = operation_context.producer, .generation = operation_context.generation, .owner_cookie = operation_context.owner_cookie, }); } fn acquireAllocationRecordLocked( self: *Tracer, record: AllocationRecord, ) !u32 { if (self.free_record_head != record_index_none) { const record_index = self.free_record_head; self.free_record_head = self.allocation_records.items[record_index].next_free; self.allocation_records.items[record_index] = record; return record_index; } const record_index = std.math.cast( u32, self.allocation_records.items.len, ) orelse return error.OutOfMemory; try self.allocation_records.append( self.control_allocator, record, ); return record_index; } fn linkAllocationRecordLocked( self: *Tracer, record_index: u32, ) void { const record = &self.allocation_records.items[record_index]; std.debug.assert(record.active); const allocator = &self.allocators.items[record.allocator_id]; record.previous_active = record_index_none; record.next_active = allocator.active_head; if (allocator.active_head != record_index_none) { self.allocation_records.items[allocator.active_head] .previous_active = record_index; } allocator.active_head = record_index; } fn unlinkAllocationRecordLocked( self: *Tracer, record_index: u32, ) void { const record = &self.allocation_records.items[record_index]; std.debug.assert(record.active); const allocator = &self.allocators.items[record.allocator_id]; if (record.previous_active == record_index_none) { std.debug.assert(allocator.active_head == record_index); allocator.active_head = record.next_active; } else { self.allocation_records.items[record.previous_active] .next_active = record.next_active; } if (record.next_active != record_index_none) { self.allocation_records.items[record.next_active] .previous_active = record.previous_active; } record.previous_active = record_index_none; record.next_active = record_index_none; } fn recycleAllocationRecordLocked( self: *Tracer, record_index: u32, ) void { const record = &self.allocation_records.items[record_index]; std.debug.assert(record.active); record.active = false; record.next_free = self.free_record_head; self.free_record_head = record_index; } fn unknownReasonLocked( self: *Tracer, allocator_id: u32, ) AnomalyReason { if (allocator_id >= self.allocators.items.len) { return .unknown_current_generation; } return if (self.allocators.items[allocator_id].prefix_complete) .unknown_current_generation else .unknown_pre_observation; } fn noteAnomalyLocked( self: *Tracer, reason: AnomalyReason, operation: observe.Operation, operation_context: OperationContext, address: usize, old_len: usize, new_len: usize, succeeded: bool, return_address: usize, ) void { const key = AnomalyKey{ .reason = reason, .operation = operation, .layer = operation_context.layer, .producer_id = operation_context.producer_id, .producer = operation_context.producer, .generation = operation_context.generation, .scope_id = self.current_scope, .return_address = return_address, .old_len = old_len, .new_len = new_len, .succeeded = succeeded, }; for (self.anomaly_sites[0..self.anomaly_site_count]) |*site| { if (!std.meta.eql(site.key, key)) continue; site.occurrences +|= 1; return; } if (self.anomaly_site_count == self.anomaly_sites.len) { self.anomaly_sites_dropped +|= 1; return; } self.anomaly_sites[self.anomaly_site_count] = .{ .key = key, .first_address = address, .occurrences = 1, }; self.anomaly_site_count += 1; } fn applyUntrackedAllocationLocked( self: *Tracer, allocator_id: u32, scope_id: u32, len: usize, layer: event_mod.Layer, ) void { const counters = self.countersForLayer(layer); counters.allocations += 1; counters.allocated_bytes += len; counters.untracked_requests += 1; counters.untracked_request_bytes += len; const allocator = &self.allocators.items[allocator_id].counters; allocator.allocations += 1; allocator.allocated_bytes += len; allocator.untracked_requests += 1; allocator.untracked_request_bytes += len; const scope = self.scopeCountersForLayer(scope_id, layer); scope.allocations += 1; scope.allocated_bytes += len; scope.untracked_requests += 1; scope.untracked_request_bytes += len; } fn applyLostTrackingLocked( self: *Tracer, allocator_id: u32, scope_id: u32, old_len: usize, new_len: usize, layer: event_mod.Layer, ) void { self.applyResizeLocked( allocator_id, scope_id, old_len, new_len, layer, ); const counters = self.countersForLayer(layer); counters.live_allocations -= 1; counters.live_bytes -= new_len; counters.untracked_requests += 1; counters.untracked_request_bytes += new_len; const allocator = &self.allocators.items[allocator_id].counters; allocator.live_allocations -= 1; allocator.live_bytes -= new_len; allocator.untracked_requests += 1; allocator.untracked_request_bytes += new_len; const scope = self.scopeCountersForLayer(scope_id, layer); scope.live_allocations -= 1; scope.live_bytes -= new_len; scope.untracked_requests += 1; scope.untracked_request_bytes += new_len; } fn validateObservedIdentity( self: *Tracer, allocator_id: u32, observed: observe.Event, operation_context: OperationContext, ) bool { self.lock(); defer self.unlock(); self.noteRequestSiteLocked(observed); const allocator = &self.allocators.items[allocator_id]; if (allocator.terminal) { self.noteAnomalyLocked( .operation_after_terminal, observed.operation, operation_context, observed.old_address, observed.old_len, observed.len, observed.succeeded, observed.return_address, ); return false; } if (allocator.owner_cookie != observed.owner_cookie) { self.noteAnomalyLocked( .owner_cookie_mismatch, observed.operation, operation_context, observed.old_address, observed.old_len, observed.len, observed.succeeded, observed.return_address, ); return false; } if (observed.generation < allocator.generation) { self.noteAnomalyLocked( .stale_generation, observed.operation, operation_context, observed.old_address, observed.old_len, observed.len, observed.succeeded, observed.return_address, ); return false; } if (observed.generation > allocator.generation) { self.noteAnomalyLocked( .generation_gap, observed.operation, operation_context, observed.old_address, observed.old_len, observed.len, observed.succeeded, observed.return_address, ); return false; } return true; } fn noteRequestSiteLocked( self: *Tracer, observed: observe.Event, ) void { if (observed.operation == .lifecycle) return; const requested_bytes: u64 = std.math.cast( u64, switch (observed.operation) { .alloc, .resize, .remap => observed.len, .free => observed.old_len, .lifecycle => unreachable, }, ) orelse std.math.maxInt(u64); const key = RequestSiteKey{ .producer_id = observed.producer_id, .producer = observed.producer, .operation = observed.operation, .scope_id = self.current_scope, .return_address = observed.return_address, .succeeded = observed.succeeded, }; if (self.request_sites.getPtr(key)) |site| { site.calls +|= 1; site.requested_bytes +|= requested_bytes; return; } if (self.request_sites.count() == @as(usize, self.config.request_site_capacity)) { self.request_site_unaggregated_calls +|= 1; self.request_site_unaggregated_bytes +|= requested_bytes; return; } self.request_sites.putAssumeCapacity(key, .{ .calls = 1, .requested_bytes = requested_bytes, }); } fn recordLifecycle( self: *Tracer, allocator_id: u32, observed: observe.Event, operation_context: OperationContext, ) !void { self.lock(); defer self.unlock(); const allocator = &self.allocators.items[allocator_id]; self.countersForLayer(.logical_allocator).lifecycle_events += 1; allocator.counters.lifecycle_events += 1; var append_error: ?anyerror = null; var record_index = allocator.active_head; while (record_index != record_index_none) { const record = self.allocation_records.items[record_index]; const next = record.next_active; std.debug.assert(record.generation == observed.generation); const removed = self.allocations.fetchRemove(.{ .allocator_id = record.allocator_id, .address = record.address, }) orelse unreachable; std.debug.assert(removed.value == record_index); self.unlinkAllocationRecordLocked(record_index); self.applyLifetimeLocked( record.scope_id, completedLifetimeEvents( record.allocation_seq, self.next_seq, ), .logical_allocator, ); self.applyBulkInvalidationLocked( record.allocator_id, record.scope_id, record.len, .logical_allocator, ); self.recycleAllocationRecordLocked(record_index); if (append_error == null) { self.appendEventLocked(.{ .kind = .release, .allocator_id = record.allocator_id, .allocation_id = record.allocation_id, .scope_id = record.scope_id, .label_id = self.scopes.items[record.scope_id].label_id, .address = record.address, .old_len = record.len, .len = record.len, .alignment = @intCast(record.alignment.toByteUnits()), .return_address = observed.return_address, .stack_id = record.stack_id, .succeeded = observed.succeeded, .live_bytes = self.countersForLayer(.logical_allocator).live_bytes, .layer = .logical_allocator, .operation_id = operation_context.operation_id, .parent_operation_id = operation_context.parent_operation_id, .producer_id = operation_context.producer_id, .producer = operation_context.producer, .generation = operation_context.generation, .owner_cookie = operation_context.owner_cookie, .lifecycle_disposition = observed.lifecycle_disposition, .lifecycle_reason = observed.lifecycle_reason, }) catch |err| { append_error = err; }; } record_index = next; } self.clearDrainedAllocationsLocked(); if (append_error == null) { self.appendEventLocked(.{ .kind = .lifecycle, .allocator_id = allocator_id, .scope_id = self.current_scope, .label_id = self.scopes.items[self.current_scope].label_id, .return_address = observed.return_address, .succeeded = observed.succeeded, .live_bytes = self.countersForLayer(.logical_allocator).live_bytes, .layer = .logical_allocator, .operation_id = operation_context.operation_id, .parent_operation_id = operation_context.parent_operation_id, .producer_id = operation_context.producer_id, .producer = operation_context.producer, .generation = operation_context.generation, .owner_cookie = operation_context.owner_cookie, .lifecycle_disposition = observed.lifecycle_disposition, .lifecycle_reason = observed.lifecycle_reason, }) catch |err| { append_error = err; }; } if (observed.lifecycle_disposition == .end) { allocator.terminal = true; allocator.prefix_complete = true; } else { allocator.generation = std.math.add( u64, allocator.generation, 1, ) catch @panic("allocator observation generation exhausted"); allocator.prefix_complete = true; } if (append_error) |err| return err; } fn internStackLocked( self: *Tracer, captured: ?stack_mod.Capture, ) !u32 { const stack_capture = captured orelse return 0; const interned = try self.stacks.intern( self.control_allocator, stack_capture, ); if (interned.is_new) { if (self.config.event_writer) |writer| { try stack_mod.capture.writeDefinition( writer, interned.id, self.stacks.recordForId(interned.id).*, ); } } return interned.id; } fn applyFailedOperationLocked( self: *Tracer, allocator_id: u32, kind: event_mod.Kind, layer: event_mod.Layer, ) void { const allocator_counters = if (allocator_id < self.allocators.items.len) &self.allocators.items[allocator_id].counters else null; if (allocator_id < self.allocators.items.len) { std.debug.assert(self.allocators.items[allocator_id].layer == layer); } const counters = self.countersForLayer(layer); switch (kind) { .alloc => { counters.failed_allocations +|= 1; if (allocator_counters) |item| item.failed_allocations +|= 1; }, .resize => { counters.failed_resizes +|= 1; if (allocator_counters) |item| item.failed_resizes +|= 1; }, .remap => { counters.failed_remaps +|= 1; if (allocator_counters) |item| item.failed_remaps +|= 1; }, else => unreachable, } } fn markRecordingFailure(self: *Tracer) void { self.lock(); defer self.unlock(); self.recording_failures +|= 1; } fn applyAllocationLocked( self: *Tracer, allocator_id: u32, scope_id: u32, len: usize, layer: event_mod.Layer, ) void { const counters = self.countersForLayer(layer); 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 (layer != .logical_allocator) { counters.retained_bytes += len; counters.high_water_retained_bytes = @max( counters.high_water_retained_bytes, counters.retained_bytes, ); } if (allocator_id < self.allocators.items.len) { const allocator_state = &self.allocators.items[allocator_id]; std.debug.assert(allocator_state.layer == layer); allocator_state.counters.allocations += 1; allocator_state.counters.live_allocations += 1; allocator_state.counters.allocated_bytes += len; allocator_state.counters.live_bytes += len; allocator_state.counters.high_water_live_bytes = @max( allocator_state.counters.high_water_live_bytes, allocator_state.counters.live_bytes, ); if (layer != .logical_allocator) { allocator_state.counters.retained_bytes += len; allocator_state.counters.high_water_retained_bytes = @max( allocator_state.counters.high_water_retained_bytes, allocator_state.counters.retained_bytes, ); } } const scope_counters = self.scopeCountersForLayer(scope_id, layer); scope_counters.allocations += 1; scope_counters.live_allocations += 1; scope_counters.allocated_bytes += len; scope_counters.live_bytes += len; scope_counters.high_water_live_bytes = @max(scope_counters.high_water_live_bytes, scope_counters.live_bytes); if (layer != .logical_allocator) { scope_counters.retained_bytes += len; scope_counters.high_water_retained_bytes = @max(scope_counters.high_water_retained_bytes, scope_counters.retained_bytes); } } fn applyResizeLocked( self: *Tracer, allocator_id: u32, scope_id: u32, old_len: usize, new_len: usize, layer: event_mod.Layer, ) void { const counters = self.countersForLayer(layer); const scope_counters = self.scopeCountersForLayer(scope_id, layer); const allocator_state = if (allocator_id < self.allocators.items.len) &self.allocators.items[allocator_id] else null; if (allocator_state) |state| std.debug.assert(state.layer == layer); counters.resizes += 1; 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, ); if (layer != .logical_allocator) { counters.retained_bytes += delta; counters.high_water_retained_bytes = @max( counters.high_water_retained_bytes, counters.retained_bytes, ); } scope_counters.allocated_bytes += delta; scope_counters.live_bytes += delta; scope_counters.high_water_live_bytes = @max( scope_counters.high_water_live_bytes, scope_counters.live_bytes, ); if (layer != .logical_allocator) { scope_counters.retained_bytes += delta; scope_counters.high_water_retained_bytes = @max( scope_counters.high_water_retained_bytes, scope_counters.retained_bytes, ); } if (allocator_state) |state| { state.counters.allocated_bytes += delta; state.counters.live_bytes += delta; state.counters.high_water_live_bytes = @max( state.counters.high_water_live_bytes, state.counters.live_bytes, ); if (layer != .logical_allocator) { state.counters.retained_bytes += delta; state.counters.high_water_retained_bytes = @max( state.counters.high_water_retained_bytes, state.counters.retained_bytes, ); } } } else { const delta = old_len - new_len; counters.freed_bytes += delta; counters.live_bytes -= delta; scope_counters.freed_bytes += delta; scope_counters.live_bytes -= delta; const releases = layer != .logical_allocator and (allocator_state == null or allocator_state.?.retention == .releases_freed_memory); if (releases) { counters.retained_bytes -= delta; scope_counters.retained_bytes -= delta; } if (allocator_state) |state| { state.counters.freed_bytes += delta; state.counters.live_bytes -= delta; if (releases) state.counters.retained_bytes -= delta; } } if (allocator_state) |state| state.counters.resizes += 1; } fn applyFreeLocked( self: *Tracer, allocator_id: u32, scope_id: u32, len: usize, layer: event_mod.Layer, ) void { const counters = self.countersForLayer(layer); const scope_counters = self.scopeCountersForLayer(scope_id, layer); const allocator_state = if (allocator_id < self.allocators.items.len) &self.allocators.items[allocator_id] else null; if (allocator_state) |state| std.debug.assert(state.layer == layer); counters.frees += 1; counters.live_allocations -= 1; counters.freed_bytes += len; counters.live_bytes -= len; const releases = layer != .logical_allocator and (allocator_state == null or allocator_state.?.retention == .releases_freed_memory); if (releases) counters.retained_bytes -= len; if (allocator_state) |state| { state.counters.frees += 1; state.counters.live_allocations -= 1; state.counters.freed_bytes += len; state.counters.live_bytes -= len; if (releases) state.counters.retained_bytes -= len; } scope_counters.frees += 1; scope_counters.live_allocations -= 1; scope_counters.freed_bytes += len; scope_counters.live_bytes -= len; if (releases) scope_counters.retained_bytes -= len; } fn applyBulkInvalidationLocked( self: *Tracer, allocator_id: u32, scope_id: u32, len: usize, layer: event_mod.Layer, ) void { const counters = self.countersForLayer(layer); counters.bulk_invalidated_requests += 1; counters.bulk_invalidated_bytes += len; counters.live_allocations -= 1; counters.live_bytes -= len; const allocator = &self.allocators.items[allocator_id].counters; allocator.bulk_invalidated_requests += 1; allocator.bulk_invalidated_bytes += len; allocator.live_allocations -= 1; allocator.live_bytes -= len; const scope = self.scopeCountersForLayer(scope_id, layer); scope.bulk_invalidated_requests += 1; scope.bulk_invalidated_bytes += len; scope.live_allocations -= 1; scope.live_bytes -= len; } fn applyLifetimeLocked( self: *Tracer, scope_id: u32, lifetime_events: u64, layer: event_mod.Layer, ) void { applyCompletedLifetime(self.countersForLayer(layer), lifetime_events); applyCompletedLifetime( self.scopeCountersForLayer(scope_id, layer), lifetime_events, ); } fn exitScope(self: *Tracer, scope_id: u32, previous: u32) void { self.lock(); defer self.unlock(); if (self.current_scope == scope_id) self.current_scope = previous; self.appendEventLocked(.{ .kind = .scope_exit, .scope_id = scope_id, .label_id = self.scopes.items[scope_id].label_id, .live_bytes = self.countersForLayer(.backing_boundary).live_bytes, }) catch {}; } fn bootstrapRoot(self: *Tracer) !void { const owned = try self.control_allocator.dupe(u8, "root"); errdefer self.control_allocator.free(owned); try self.labels.append(self.control_allocator, .{ .text = owned }); try self.label_ids.putNoClobber(self.control_allocator, owned, root_label_id); try self.scopes.append(self.control_allocator, .{ .parent = root_scope_id, .label_id = root_label_id }); } fn internLabelLocked(self: *Tracer, text: []const u8) !u32 { if (self.label_ids.get(text)) |id| return id; const id = std.math.cast(u32, self.labels.items.len) orelse return error.OutOfMemory; const owned = try self.control_allocator.dupe(u8, text); errdefer self.control_allocator.free(owned); try self.labels.append(self.control_allocator, .{ .text = owned }); try self.label_ids.putNoClobber(self.control_allocator, owned, id); return id; } fn scopeChildLocked(self: *Tracer, parent: u32, label_id: u32) !u32 { const key = ChildKey{ .parent = parent, .label_id = label_id }; if (self.scope_children.get(key)) |scope_id| return scope_id; const scope_id = std.math.cast(u32, self.scopes.items.len) orelse return error.OutOfMemory; try self.scopes.append(self.control_allocator, .{ .parent = parent, .label_id = label_id }); try self.scope_children.putNoClobber(self.control_allocator, key, scope_id); return scope_id; } fn clearDrainedAllocationsLocked(self: *Tracer) void { if (self.allocations.count() == 0) self.allocations.clearRetainingCapacity(); } fn noteObserverControlOperation(self: *Tracer) void { self.control_context.note(); } fn coverageManifest(self: *const Tracer) coverage_mod.Manifest { var manifest = self.coverage; manifest.observer_control_operations = self.observerControlOperations(); return manifest; } fn countersForLayer( self: *Tracer, layer: event_mod.Layer, ) *Counters { return &self.counters[@backingInt(layer)]; } fn scopeCountersForLayer( self: *Tracer, scope_id: u32, layer: event_mod.Layer, ) *ScopeCounters { return &self.scopes.items[scope_id].counters[@backingInt(layer)]; } fn lifecycleCoverageLocked(self: *const Tracer) LifecycleCoverage { var coverage: LifecycleCoverage = .{}; for (self.allocators.items) |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 appendEventLocked(self: *Tracer, event: Event) !void { var stored = event; stored.seq = self.next_seq; self.next_seq += 1; if (!self.config.record_events) return; if (self.config.event_writer) |writer| { const label_text = if ((stored.kind == .allocator or stored.kind == .scope_enter) and stored.label_id < self.labels.items.len) self.labels.items[stored.label_id].text else null; const scope_text = if (stored.kind == .scope_enter and stored.scope_id < self.scopes.items.len) try self.scopePathCachedLocked(stored.scope_id) else null; try stored.writeJsonLine(writer, label_text, scope_text); return; } try self.events.append(self.control_allocator, stored); } fn scopePathCachedLocked(self: *Tracer, scope_id: u32) ![]const u8 { if (self.scopes.items[scope_id].path) |path| return path; const path = try self.scopePathAllocLocked(scope_id); self.scopes.items[scope_id].path = path; return path; } fn scopePathAllocLocked(self: *Tracer, scope_id: u32) ![]u8 { var stack = std.ArrayListUnmanaged(u32).empty; defer stack.deinit(self.control_allocator); var current = scope_id; while (true) { try stack.append(self.control_allocator, current); if (current == root_scope_id) break; current = self.scopes.items[current].parent; } var total: usize = 0; var index = stack.items.len; while (index > 0) { index -= 1; const label_id = self.scopes.items[stack.items[index]].label_id; total += self.labels.items[label_id].text.len; if (index != 0) total += 1; } const out = try self.control_allocator.alloc(u8, total); var offset: usize = 0; index = stack.items.len; while (index > 0) { index -= 1; const label_id = self.scopes.items[stack.items[index]].label_id; const text = self.labels.items[label_id].text; @memcpy(out[offset .. offset + text.len], text); offset += text.len; if (index != 0) { out[offset] = '/'; offset += 1; } } return out; } fn lock(self: *Tracer) void { while (!self.mutex.tryLock()) std.atomic.spinLoopHint(); } fn unlock(self: *Tracer) void { self.mutex.unlock(); }};Source: lib/memtrace/src/root.zig:65
zig
pub const Tracer = tracer_mod.Tracer;Complete caller list for Tracer.deinit
23 direct callers.
tiny.memtrace.Tracer.init[function] atlib/memtrace/src/tracer.zig:507lib.memtrace.src.tracer.test_event_log_can_stream_without_retaining_records[function] — test source atlib/memtrace/src/tracer.zig:4708in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_event_log_records_alloc_and_free_lines[function] — test source atlib/memtrace/src/tracer.zig:4328in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_event_serialization_rejects_unretained_streams[function] — test source atlib/memtrace/src/tracer.zig:4800in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_exact_attribution_covers_every_operation_and_failed_outcome[function] — test source atlib/memtrace/src/tracer.zig:4134in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_exact_attribution_separates_callers_behind_one_allocation_site[function] — test source atlib/memtrace/src/tracer.zig:4093in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_lifecycle_coverage_treats_release-explicit_producer_as_not_required[function] — test source atlib/memtrace/src/tracer.zig:4448in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_nested_traced_allocator_identities_may_own_the_same_address[function] — test source atlib/memtrace/src/tracer.zig:4196in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_observation_publication_window_reconciles_producer_prefix_floor[function] — test source atlib/memtrace/src/tracer.zig:4364in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_observer_control_allocation_is_counted_and_physically_isolated[function] — test source atlib/memtrace/src/tracer.zig:4768in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_observer_control_remains_isolated_when_backing_allocators_alias[function] — test source atlib/memtrace/src/tracer.zig:4598in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_optional_owned_observation_follows_compiled_capability[function] — test source atlib/memtrace/src/tracer.zig:4696in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_owned_anomalies_retain_bounded_exact_call-site_attribution[function] — test source atlib/memtrace/src/tracer.zig:3970in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_owned_lifecycle_permits_deterministic_same-address_reuse[function] — test source atlib/memtrace/src/tracer.zig:3855in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_owned_logical_operations_correlate_nested_backing_boundaries[function] — test source atlib/memtrace/src/tracer.zig:4510in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_owned_observation_correlates_logical_backing_and_physical_operations[function] — test source atlib/memtrace/src/tracer.zig:4620in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_owned_observation_fails_closed_when_not_compiled[function] — test source atlib/memtrace/src/tracer.zig:4686in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_retaining_allocator_reports_capacity_pressure_after_logical_frees[function] — test source atlib/memtrace/src/tracer.zig:4304in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_snapshot_diffs_report_allocator_deltas[function] — test source atlib/memtrace/src/tracer.zig:4286in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_streamed_coverage_records_the_final_observed_universe[function] — test source atlib/memtrace/src/tracer.zig:4740in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_summary-only_request_attribution_retains_exact_symbolizable_binary[function] — test source atlib/memtrace/src/tracer.zig:3770in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_traced_allocator_attributes_live_allocations_to_scopes[function] — test source atlib/memtrace/src/tracer.zig:4224in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_zero-length_and_predispatch_failures_have_no_memory_effect[function] — test source atlib/memtrace/src/tracer.zig:4172in nearest public ownerlib.memtrace.src.tracer
Complete caller list for Tracer.init
22 direct callers.
lib.memtrace.src.tracer.test_event_log_can_stream_without_retaining_records[function] — test source atlib/memtrace/src/tracer.zig:4708in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_event_log_records_alloc_and_free_lines[function] — test source atlib/memtrace/src/tracer.zig:4328in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_event_serialization_rejects_unretained_streams[function] — test source atlib/memtrace/src/tracer.zig:4800in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_exact_attribution_covers_every_operation_and_failed_outcome[function] — test source atlib/memtrace/src/tracer.zig:4134in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_exact_attribution_separates_callers_behind_one_allocation_site[function] — test source atlib/memtrace/src/tracer.zig:4093in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_lifecycle_coverage_treats_release-explicit_producer_as_not_required[function] — test source atlib/memtrace/src/tracer.zig:4448in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_nested_traced_allocator_identities_may_own_the_same_address[function] — test source atlib/memtrace/src/tracer.zig:4196in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_observation_publication_window_reconciles_producer_prefix_floor[function] — test source atlib/memtrace/src/tracer.zig:4364in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_observer_control_allocation_is_counted_and_physically_isolated[function] — test source atlib/memtrace/src/tracer.zig:4768in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_observer_control_remains_isolated_when_backing_allocators_alias[function] — test source atlib/memtrace/src/tracer.zig:4598in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_optional_owned_observation_follows_compiled_capability[function] — test source atlib/memtrace/src/tracer.zig:4696in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_owned_anomalies_retain_bounded_exact_call-site_attribution[function] — test source atlib/memtrace/src/tracer.zig:3970in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_owned_lifecycle_permits_deterministic_same-address_reuse[function] — test source atlib/memtrace/src/tracer.zig:3855in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_owned_logical_operations_correlate_nested_backing_boundaries[function] — test source atlib/memtrace/src/tracer.zig:4510in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_owned_observation_correlates_logical_backing_and_physical_operations[function] — test source atlib/memtrace/src/tracer.zig:4620in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_owned_observation_fails_closed_when_not_compiled[function] — test source atlib/memtrace/src/tracer.zig:4686in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_retaining_allocator_reports_capacity_pressure_after_logical_frees[function] — test source atlib/memtrace/src/tracer.zig:4304in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_snapshot_diffs_report_allocator_deltas[function] — test source atlib/memtrace/src/tracer.zig:4286in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_streamed_coverage_records_the_final_observed_universe[function] — test source atlib/memtrace/src/tracer.zig:4740in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_summary-only_request_attribution_retains_exact_symbolizable_binary[function] — test source atlib/memtrace/src/tracer.zig:3770in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_traced_allocator_attributes_live_allocations_to_scopes[function] — test source atlib/memtrace/src/tracer.zig:4224in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_zero-length_and_predispatch_failures_have_no_memory_effect[function] — test source atlib/memtrace/src/tracer.zig:4172in nearest public ownerlib.memtrace.src.tracer
Complete caller list for Tracer.observeOwnedAllocators
7 direct callers.
tiny.memtrace.Tracer.observeOwnedAllocatorsIfAvailable[method] atlib/memtrace/src/tracer.zig:599lib.memtrace.src.tracer.test_observer_control_allocation_is_counted_and_physically_isolated[function] — test source atlib/memtrace/src/tracer.zig:4768in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_observer_control_remains_isolated_when_backing_allocators_alias[function] — test source atlib/memtrace/src/tracer.zig:4598in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_owned_logical_operations_correlate_nested_backing_boundaries[function] — test source atlib/memtrace/src/tracer.zig:4510in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_owned_observation_correlates_logical_backing_and_physical_operations[function] — test source atlib/memtrace/src/tracer.zig:4620in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_owned_observation_fails_closed_when_not_compiled[function] — test source atlib/memtrace/src/tracer.zig:4686in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_streamed_coverage_records_the_final_observed_universe[function] — test source atlib/memtrace/src/tracer.zig:4740in nearest public ownerlib.memtrace.src.tracer
Complete caller list for Tracer.tracedAllocator
11 direct callers.
lib.memtrace.src.tracer.test_event_log_can_stream_without_retaining_records[function] — test source atlib/memtrace/src/tracer.zig:4708in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_event_log_records_alloc_and_free_lines[function] — test source atlib/memtrace/src/tracer.zig:4328in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_exact_attribution_covers_every_operation_and_failed_outcome[function] — test source atlib/memtrace/src/tracer.zig:4134in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_exact_attribution_separates_callers_behind_one_allocation_site[function] — test source atlib/memtrace/src/tracer.zig:4093in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_nested_traced_allocator_identities_may_own_the_same_address[function] — test source atlib/memtrace/src/tracer.zig:4196in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_observer_control_remains_isolated_when_backing_allocators_alias[function] — test source atlib/memtrace/src/tracer.zig:4598in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_owned_logical_operations_correlate_nested_backing_boundaries[function] — test source atlib/memtrace/src/tracer.zig:4510in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_owned_observation_correlates_logical_backing_and_physical_operations[function] — test source atlib/memtrace/src/tracer.zig:4620in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_snapshot_diffs_report_allocator_deltas[function] — test source atlib/memtrace/src/tracer.zig:4286in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_traced_allocator_attributes_live_allocations_to_scopes[function] — test source atlib/memtrace/src/tracer.zig:4224in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.test_zero-length_and_predispatch_failures_have_no_memory_effect[function] — test source atlib/memtrace/src/tracer.zig:4172in nearest public ownerlib.memtrace.src.tracer
Complete call list for Tracer.writeSummaryJsonl
7 direct calls.
lib.memtrace.src.tracer.Tracer.countersForLayer[method] — private source atlib/memtrace/src/tracer.zig:3256in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.Tracer.lifecycleCoverageLocked[method] — private source atlib/memtrace/src/tracer.zig:3271in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.Tracer.lock[method] — private source atlib/memtrace/src/tracer.zig:3358in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.Tracer.scopePathAllocLocked[method] — private source atlib/memtrace/src/tracer.zig:3321in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.Tracer.unlock[method] — private source atlib/memtrace/src/tracer.zig:3362in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.meanLifetimeEvents[function] — private source atlib/memtrace/src/tracer.zig:3490in nearest public ownerlib.memtrace.src.tracerlib.memtrace.src.tracer.writeCounterFields[function] — private source atlib/memtrace/src/tracer.zig:3495in nearest public ownerlib.memtrace.src.tracer
Audit
| Definitions | 20 |
|---|---|
| Public names | 20 |
| Members | 34 |
| Version | 26.7.0 |
| Revision | daab053ee433 |