tiny.profiling.memory
Defined in tiny.profiling.
API (15)
Actions
Public operations.
Types and contracts
Public types and contracts.
Values and defaults
Public values and defaults.
Source
Source: src/profiling/memory.zig
zig
const std = @import("std");const capture = @import("capture");const pretty = @import("pretty");const sys = @import("sys");const allocation = @import("root.zig").allocation;const allocation_trace = @import("capture/root.zig").memtrace;const catalog = @import("root.zig").catalog;const host = @import("root.zig").host;const json = @import("root.zig").json;const schema = @import("root.zig").schema;const pretty_json = pretty.json;const max_structured_bytes = 128 * 1024 * 1024;const max_repetition_bytes = 1024 * 1024;pub const effect_method = "deterministic_percentile_bootstrap_unpaired_mean_percent_change";pub const effect_bootstrap_iterations = capture.compare.effect.bootstrap_iterations;pub const effect_confidence_per_mille = capture.compare.effect.confidence_per_mille;pub const Unit = enum { bytes, count, pub fn name(self: Unit) []const u8 { return @tagName(self); }};pub const Distribution = enum { point_estimate, raw_executions, incomplete_executions, pub fn name(self: Distribution) []const u8 { return @tagName(self); }};pub const BudgetKind = enum { allocation, memory, pub fn name(self: BudgetKind) []const u8 { return @tagName(self); }};pub const LoadOptions = struct { structured_path: ?[]const u8, allocation_repetitions_path: ?[]const u8 = null, expected_executions: usize = 1, workload: []const u8 = "",};pub const Metric = struct { workload: []const u8, key: []const u8, label: []const u8, source_kind: []const u8, source_path: []const u8, source_line: usize, unit: Unit, value: f64, sample_count: u64 = 1, samples: []const f64 = &.{}, distribution: Distribution = .point_estimate,};pub const Comparison = struct { workload: []const u8, key: []const u8, label: []const u8, budget_kind: BudgetKind, unit: Unit, baseline_value: f64, candidate_value: f64, percent_change: f64, threshold_percent: f64, baseline_samples: u64 = 1, candidate_samples: u64 = 1, baseline_distribution: Distribution = .point_estimate, candidate_distribution: Distribution = .point_estimate, effect_low_percent: ?f64 = null, effect_high_percent: ?f64 = null, status: []const u8,};const Source = struct { kind: []const u8, path: []const u8, line: usize,};const AllocationTotals = struct { workload: []const u8 = "", source_kind: []const u8 = "allocations", source_path: []const u8 = "", saw: bool = false, totals: allocation.Totals = .{},};const RepetitionField = struct { summary_name: []const u8, metric_name: []const u8, unit: Unit,};const repetition_fields = [_]RepetitionField{ .{ .summary_name = "allocations", .metric_name = "allocation_events", .unit = .count }, .{ .summary_name = "frees", .metric_name = "free_events", .unit = .count }, .{ .summary_name = "resizes", .metric_name = "resize_events", .unit = .count }, .{ .summary_name = "remaps", .metric_name = "remap_events", .unit = .count }, .{ .summary_name = "allocated_bytes", .metric_name = "allocated_bytes", .unit = .bytes }, .{ .summary_name = "live_allocations", .metric_name = "live_allocations", .unit = .count }, .{ .summary_name = "live_bytes", .metric_name = "live_bytes", .unit = .bytes }, .{ .summary_name = "high_water_live_bytes", .metric_name = "high_water_live_bytes", .unit = .bytes, }, .{ .summary_name = "high_water_retained_bytes", .metric_name = "high_water_retained_bytes", .unit = .bytes, },};const RepetitionEvidence = struct { observed: usize = 0, complete: bool = true, counts: [repetition_fields.len]usize = @as([repetition_fields.len]usize, @splat(0)), samples: [repetition_fields.len][host.process.max_executions]f64 = undefined,};pub fn load(allocator: std.mem.Allocator, options: LoadOptions) ![]const Metric { var result: std.ArrayList(Metric) = .empty; if (options.structured_path) |actual_path| { try loadStructured(allocator, &result, actual_path); } if (options.expected_executions > 1) { if (options.allocation_repetitions_path) |path| { try loadRepetitions(allocator, &result, options, path); } } return try result.toOwnedSlice(allocator);}fn loadStructured( allocator: std.mem.Allocator, result: *std.ArrayList(Metric), actual_path: []const u8,) !void { const text = sys.fs.readFileAlloc(allocator, actual_path, max_structured_bytes) catch |err| switch (err) { error.FileNotFound => return, else => |actual| return actual, }; defer allocator.free(text); var allocations: AllocationTotals = .{}; var lines = std.mem.splitScalar(u8, text, '\n'); while (lines.next()) |line| { const trimmed = std.mem.trim(u8, line, " \t\r"); if (trimmed.len == 0) continue; var parsed = std.json.parseFromSlice(std.json.Value, allocator, trimmed, .{}) catch continue; defer parsed.deinit(); const object = json.object(parsed.value) catch continue; const row = json.object(object.get("row") orelse continue) catch continue; const source = parseSource(allocator, object) catch continue; const workload = parseWorkload(allocator, object) catch continue; const classification = schema.classify(row, source.kind); if (classification.family == .allocation) { try recordAllocation(allocator, &allocations, workload, source, row); } else if (schema.isMemoryMetric(row, source.kind)) { try appendRowMetrics(allocator, result, workload, source, row); } } if (allocations.saw) try appendAllocationMetrics(allocator, result, allocations);}fn loadRepetitions( allocator: std.mem.Allocator, result: *std.ArrayList(Metric), options: LoadOptions, path: []const u8,) !void { if (options.expected_executions > host.process.max_executions) { return error.InvalidProfilingJson; } const evidence = try readRepetitions(allocator, path, options.expected_executions); for (repetition_fields, 0..) |field, field_index| { const count = evidence.counts[field_index]; if (count == 0) { markAllocationMetricIncomplete(result.items, options.workload, field.metric_name); continue; } const key = try std.fmt.allocPrint( allocator, "allocations|{s}", .{field.metric_name}, ); removeMetric(result, options.workload, key); const samples = try allocator.dupe( f64, evidence.samples[field_index][0..count], ); try result.append(allocator, .{ .workload = options.workload, .key = key, .label = key, .source_kind = "allocation_repetitions", .source_path = path, .source_line = 0, .unit = field.unit, .value = mean(samples), .sample_count = @intCast(samples.len), .samples = samples, .distribution = if (evidence.complete and evidence.observed == options.expected_executions and samples.len == options.expected_executions) .raw_executions else .incomplete_executions, }); }}fn readRepetitions( allocator: std.mem.Allocator, path: []const u8, expected_executions: usize,) !RepetitionEvidence { var evidence = RepetitionEvidence{}; const text = sys.fs.readFileAlloc(allocator, path, max_repetition_bytes) catch |err| switch (err) { error.FileNotFound => { evidence.complete = false; return evidence; }, else => |actual| return actual, }; defer allocator.free(text); var lines = std.mem.splitScalar(u8, text, '\n'); while (lines.next()) |line| { const trimmed = std.mem.trim(u8, line, " \t\r"); if (trimmed.len == 0) continue; if (evidence.observed >= host.process.max_executions) { return error.InvalidProfilingJson; } evidence.observed += 1; parseRepetitionLine( allocator, &evidence, trimmed, evidence.observed, ) catch |err| switch (err) { error.OutOfMemory => return err, else => evidence.complete = false, }; } if (evidence.observed != expected_executions) evidence.complete = false; return evidence;}fn parseRepetitionLine( allocator: std.mem.Allocator, evidence: *RepetitionEvidence, line: []const u8, expected_index: usize,) !void { var parsed = try std.json.parseFromSlice(std.json.Value, allocator, line, .{}); defer parsed.deinit(); const object = try json.object(parsed.value); const row_schema = json.string(object.get("schema")) orelse return error.InvalidProfilingJson; if (!std.mem.eql(u8, row_schema, allocation_trace.repetition_schema)) { return error.InvalidProfilingJson; } const execution_index = json.asU64(object.get("execution_index")) orelse return error.InvalidProfilingJson; if (execution_index != expected_index) return error.InvalidProfilingJson; const state = json.string(object.get("state")) orelse return error.InvalidProfilingJson; if (!std.mem.eql(u8, state, "summary_written")) { return error.IncompleteProfilingEvidence; } const summary = try json.object(object.get("summary") orelse return error.InvalidProfilingJson); const integrity = try json.object(summary.get("capture_integrity") orelse return error.InvalidProfilingJson); const status = json.string(integrity.get("status")) orelse return error.InvalidProfilingJson; if (!std.mem.eql(u8, status, "complete")) { evidence.complete = false; } for (repetition_fields, 0..) |field, field_index| { const value = json.asF64(summary.get(field.summary_name)) orelse { evidence.complete = false; continue; }; const count = evidence.counts[field_index]; evidence.samples[field_index][count] = value; evidence.counts[field_index] += 1; }}fn removeMetric( result: *std.ArrayList(Metric), workload: []const u8, key: []const u8,) void { for (result.items, 0..) |row, index| { if (std.mem.eql(u8, row.workload, workload) and std.mem.eql(u8, row.key, key)) { _ = result.orderedRemove(index); return; } }}fn markAllocationMetricIncomplete( metrics: []Metric, workload: []const u8, metric_name: []const u8,) void { for (metrics) |*row| { if (!std.mem.eql(u8, row.workload, workload)) continue; if (!std.mem.startsWith(u8, row.key, "allocations|")) continue; if (!std.mem.eql(u8, row.key["allocations|".len..], metric_name)) continue; row.distribution = .incomplete_executions; row.sample_count = 0; return; }}fn mean(samples: []const f64) f64 { std.debug.assert(samples.len > 0); var total: f64 = 0; for (samples) |sample| total += sample; return total / @as(f64, @floatFromInt(samples.len));}pub fn compare( allocator: std.mem.Allocator, base: []const Metric, candidate: []const Metric, default_threshold_percent: f64,) ![]const Comparison { var rows: std.ArrayList(Comparison) = .empty; for (candidate) |candidate_metric| { const base_metric = find(base, candidate_metric.workload, candidate_metric.key) orelse continue; if (base_metric.value <= 0) continue; const percent = ((candidate_metric.value - base_metric.value) / base_metric.value) * 100; const budget_kind = metricBudgetKind(candidate_metric); const threshold = thresholdPercent(candidate_metric, default_threshold_percent); if (percent < threshold) continue; const effect_interval = try allocationEffectInterval( allocator, base_metric, candidate_metric, budget_kind, ); try rows.append(allocator, .{ .workload = candidate_metric.workload, .key = candidate_metric.key, .label = candidate_metric.label, .budget_kind = budget_kind, .unit = candidate_metric.unit, .baseline_value = base_metric.value, .candidate_value = candidate_metric.value, .percent_change = percent, .threshold_percent = threshold, .baseline_samples = base_metric.sample_count, .candidate_samples = candidate_metric.sample_count, .baseline_distribution = base_metric.distribution, .candidate_distribution = candidate_metric.distribution, .effect_low_percent = if (effect_interval) |interval| interval.low else null, .effect_high_percent = if (effect_interval) |interval| interval.high else null, .status = comparisonStatus( base_metric, candidate_metric, threshold, effect_interval, budget_kind, ), }); } return try rows.toOwnedSlice(allocator);}fn allocationEffectInterval( allocator: std.mem.Allocator, base: Metric, candidate: Metric, budget_kind: BudgetKind,) !?capture.compare.effect.Interval { if (budget_kind != .allocation) return null; if (base.distribution != .raw_executions or candidate.distribution != .raw_executions) { return null; } if (base.samples.len < 2 or candidate.samples.len < 2) return null; var effect_storage = try capture.compare.EffectStorage.init(allocator, .{ .max_samples_per_distribution = @max( base.samples.len, candidate.samples.len, ), }); defer effect_storage.deinit(allocator); effect_storage.activate(); return try capture.compare.effect.bootstrapMeanPercentChangeInterval( &effect_storage, base.samples, candidate.samples, capture.compare.effect.percentChangeSeed(candidate.workload), );}fn comparisonStatus( base: Metric, candidate: Metric, threshold_percent: f64, effect_interval: ?capture.compare.effect.Interval, budget_kind: BudgetKind,) []const u8 { if (budget_kind != .allocation) return "memory_regression"; if (base.distribution == .incomplete_executions or candidate.distribution == .incomplete_executions) { return "allocation_repetition_incomplete"; } if (effect_interval) |interval| { if (interval.low >= threshold_percent) { return "allocation_sample_regression"; } return "allocation_sample_regression_uncertain"; } if (base.distribution == .raw_executions or candidate.distribution == .raw_executions) { return "allocation_sample_regression_candidate"; } return "allocation_regression_candidate";}fn appendRowMetrics( allocator: std.mem.Allocator, result: *std.ArrayList(Metric), workload: []const u8, source: Source, row: std.json.ObjectMap,) !void { if (eventIs(row, "bench_end")) { if (normalizedBenchCounter(row, "alloc_bytes")) |value| try appendMetric(allocator, result, workload, source, row, "alloc_bytes_per_eval", .bytes, value); if (normalizedBenchCounter(row, "alloc_count")) |value| try appendMetric(allocator, result, workload, source, row, "alloc_count_per_eval", .count, value); return; } const name = fieldString(row, "nameOrId"); const metric_name = json.string(row.get("metric")) orelse name; if (std.mem.eql(u8, source.kind, "allocations") and allocation.isMetricName(metric_name)) { const value = firstNumber(row, &.{ "value", "median", "mean", "allocated_bytes", "alloc_bytes", "alloc_count" }) orelse return; try appendSyntheticMetric(allocator, result, workload, source, metric_name, parseUnit(row, metric_name), value); return; } const unit = parseUnit(row, metric_name); const value = firstNumber(row, &.{ "value", "median", "mean", "allocated_bytes", "retained_bytes", "high_water_live_bytes", "alloc_bytes", "alloc_count" }) orelse return; try appendMetric(allocator, result, workload, source, row, metric_name, unit, value);}fn appendMetric( allocator: std.mem.Allocator, result: *std.ArrayList(Metric), workload: []const u8, source: Source, row: std.json.ObjectMap, metric_name: []const u8, unit: Unit, value: f64,) !void { const key = try memoryKey(allocator, row, metric_name); try result.append(allocator, .{ .workload = workload, .key = key, .label = try memoryLabel(allocator, row, key), .source_kind = source.kind, .source_path = source.path, .source_line = source.line, .unit = unit, .value = value, });}fn recordAllocation( allocator: std.mem.Allocator, totals: *AllocationTotals, workload: []const u8, source: Source, row: std.json.ObjectMap,) !void { if (!totals.saw) { totals.saw = true; totals.workload = try allocator.dupe(u8, workload); totals.source_kind = try allocator.dupe(u8, source.kind); totals.source_path = try allocator.dupe(u8, source.path); } totals.totals.record(row);}fn appendAllocationMetrics(allocator: std.mem.Allocator, result: *std.ArrayList(Metric), totals: AllocationTotals) !void { const source = Source{ .kind = totals.source_kind, .path = totals.source_path, .line = 0 }; for (totals.totals.metrics()) |metric_value| try appendSyntheticMetric(allocator, result, totals.workload, source, metric_value.name, parseMetricUnit(metric_value), @floatFromInt(metric_value.value));}fn appendSyntheticMetric( allocator: std.mem.Allocator, result: *std.ArrayList(Metric), workload: []const u8, source: Source, metric_name: []const u8, unit: Unit, value: f64,) !void { const key = try std.fmt.allocPrint(allocator, "allocations|{s}", .{metric_name}); try result.append(allocator, .{ .workload = workload, .key = key, .label = key, .source_kind = source.kind, .source_path = source.path, .source_line = source.line, .unit = unit, .value = value, });}fn thresholdPercent(metric: Metric, default_threshold_percent: f64) f64 { if (catalog.find(metric.workload)) |workload| { return switch (metricBudgetKind(metric)) { .allocation => workload.allocationBudgetPercent() orelse default_threshold_percent, .memory => workload.rssThresholdPercent(default_threshold_percent), }; } return default_threshold_percent;}fn find(metrics: []const Metric, workload: []const u8, key: []const u8) ?Metric { for (metrics) |metric| { if (std.mem.eql(u8, metric.workload, workload) and std.mem.eql(u8, metric.key, key)) return metric; } return null;}fn parseSource(allocator: std.mem.Allocator, object: std.json.ObjectMap) !Source { const source = try json.object(object.get("source") orelse return error.InvalidProfilingJson); return .{ .kind = try allocator.dupe(u8, json.string(source.get("kind")) orelse ""), .path = try allocator.dupe(u8, json.string(source.get("path")) orelse ""), .line = @intCast(json.asU64(source.get("line")) orelse 0), };}fn parseWorkload(allocator: std.mem.Allocator, object: std.json.ObjectMap) ![]const u8 { const workload = try json.object(object.get("workload") orelse return error.InvalidProfilingJson); return try allocator.dupe(u8, json.string(workload.get("name")) orelse return error.InvalidProfilingJson);}fn parseUnit(row: std.json.ObjectMap, metric_name: []const u8) Unit { if (json.string(row.get("unit"))) |unit| { if (std.mem.eql(u8, unit, "bytes")) return .bytes; if (std.mem.eql(u8, unit, "count")) return .count; } return if (std.mem.indexOf(u8, metric_name, "bytes") != null) .bytes else .count;}fn parseMetricUnit(metric_value: allocation.Metric) Unit { if (std.mem.eql(u8, metric_value.unit, "bytes")) return .bytes; return .count;}fn firstNumber(row: std.json.ObjectMap, fields: []const []const u8) ?f64 { for (fields) |field| { if (json.asF64(row.get(field))) |value| return value; } return null;}fn normalizedBenchCounter(row: std.json.ObjectMap, field: []const u8) ?f64 { const value = json.asF64(row.get(field)) orelse return null; const samples = json.asF64(row.get("samples")) orelse 1; const evals = json.asF64(row.get("evals")) orelse 1; const divisor = samples * evals; if (divisor <= 0) return value; return value / divisor;}fn eventIs(row: std.json.ObjectMap, expected: []const u8) bool { return if (json.string(row.get("event"))) |event| std.mem.eql(u8, event, expected) else false;}fn memoryKey(allocator: std.mem.Allocator, row: std.json.ObjectMap, metric_name: []const u8) ![]const u8 { return try std.fmt.allocPrint(allocator, "{s}|{s}|{s}|{s}|{s}|{s}|{s}|{s}|{s}|{s}", .{ fieldString(row, "benchmark"), fieldString(row, "suite"), fieldString(row, "mode"), fieldString(row, "pipeline"), fieldString(row, "kind"), fieldString(row, "summary"), fieldString(row, "workload"), fieldString(row, "phase"), fieldString(row, "nameOrId"), metric_name, });}fn memoryLabel(allocator: std.mem.Allocator, row: std.json.ObjectMap, fallback: []const u8) ![]const u8 { const benchmark = fieldString(row, "benchmark"); const row_workload = fieldString(row, "workload"); const summary = fieldString(row, "summary"); const phase = fieldString(row, "phase"); const name = fieldString(row, "nameOrId"); if (benchmark.len == 0 and row_workload.len == 0 and summary.len == 0 and phase.len == 0 and name.len == 0) return fallback; return try std.fmt.allocPrint(allocator, "{s} {s} {s} {s} {s}", .{ benchmark, row_workload, summary, phase, name });}fn fieldString(row: std.json.ObjectMap, field: []const u8) []const u8 { if (std.mem.eql(u8, field, "nameOrId")) { return json.string(row.get("name")) orelse json.string(row.get("id")) orelse ""; } return json.string(row.get(field)) orelse "";}fn isAllocationKey(key: []const u8) bool { return std.mem.startsWith(u8, key, "allocations|");}pub fn isAllocationMetric(metric: Metric) bool { if (isAllocationKey(metric.key)) return true; if (catalog.find(metric.workload)) |workload| { return workload.supportsAllocationCounters(); } return false;}fn metricBudgetKind(metric: Metric) BudgetKind { return if (isAllocationMetric(metric)) .allocation else .memory;}fn repetitionTestLine( allocator: std.mem.Allocator, execution_index: usize, allocated_bytes: u64, high_water_live_bytes: u64, integrity_status: []const u8,) ![]const u8 { return try pretty_json.renderMinifiedLineAlloc( allocator, .{ .schema = allocation_trace.repetition_schema, .execution_index = execution_index, .exit_code = @as(u8, 0), .state = "summary_written", .summary = .{ .allocations = @as(u8, 4), .frees = @as(u8, 4), .resizes = @as(u8, 0), .remaps = @as(u8, 0), .allocated_bytes = allocated_bytes, .live_allocations = @as(u8, 0), .live_bytes = @as(u8, 0), .high_water_live_bytes = high_water_live_bytes, .high_water_retained_bytes = allocated_bytes, .capture_integrity = .{ .status = integrity_status }, }, }, );}fn loadForOom(allocator: std.mem.Allocator, options: LoadOptions) !void { var arena_state = std.heap.ArenaAllocator.init(allocator); defer arena_state.deinit(); _ = try load(arena_state.allocator(), options);}test "profiling memory metrics parse bench allocation rows" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); const allocator = arena_state.allocator(); try sys.fs.writeFile(".zig-cache/profile-memory-test.jsonl", \\{"schema":"tiny.profiling.structured/v1","workload":{"name":"gpalloc.allocator","package":"lib/gpalloc","step":"gpalloc-bench"},"source":{"kind":"bench_jsonl","path":"bench.jsonl","line":1},"row":{"schema":"tiny.profiling.metric/v1","event":"bench_end","family":"timing","metric":"duration_ns","unit":"ns","suite":"gpalloc","name":"alloc","sample_ns":[10,20],"samples":3,"evals":2,"alloc_count":6,"alloc_bytes":96}} \\ ); const metrics = try load(allocator, .{ .structured_path = ".zig-cache/profile-memory-test.jsonl", }); try std.testing.expectEqual(@as(usize, 2), metrics.len); try std.testing.expectEqualStrings("gpalloc.allocator", metrics[0].workload); try std.testing.expectEqual(Unit.bytes, metrics[0].unit); try std.testing.expectEqual(@as(f64, 16), metrics[0].value); sys.fs.deleteFile(".zig-cache/profile-memory-test.jsonl") catch {};}test "profiling memory retains complete allocation repetition distributions" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); const allocator = arena_state.allocator(); const path = ".zig-cache/profile-memory-repetitions-test.jsonl"; defer sys.fs.deleteFile(path) catch {}; const first = try repetitionTestLine(allocator, 1, 10, 5, "complete"); const second = try repetitionTestLine(allocator, 2, 20, 10, "complete"); const third = try repetitionTestLine(allocator, 3, 30, 15, "complete"); try sys.fs.writeFile(path, first); try sys.fs.appendFile(path, &.{ second, third }); const options = LoadOptions{ .structured_path = null, .allocation_repetitions_path = path, .expected_executions = 3, .workload = "allocator", }; const metrics = try load(allocator, options); try std.testing.expectEqual(@as(usize, repetition_fields.len), metrics.len); const allocated = find(metrics, "allocator", "allocations|allocated_bytes").?; try std.testing.expectEqual(Distribution.raw_executions, allocated.distribution); try std.testing.expectEqual(@as(u64, 3), allocated.sample_count); try std.testing.expectEqual(@as(f64, 20), allocated.value); try std.testing.expectEqualSlices(f64, &.{ 10, 20, 30 }, allocated.samples); const high_water = find( metrics, "allocator", "allocations|high_water_live_bytes", ).?; try std.testing.expectEqual(@as(f64, 10), high_water.value); try std.testing.checkAllAllocationFailures( std.testing.allocator, loadForOom, .{options}, );}test "profiling memory caveats partial and missing allocation repetitions" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); const allocator = arena_state.allocator(); const path = ".zig-cache/profile-memory-repetition-caveat-test.jsonl"; defer sys.fs.deleteFile(path) catch {}; const first = try repetitionTestLine(allocator, 1, 10, 5, "complete"); const partial = try repetitionTestLine( allocator, 2, 30, 15, "missing_stop_event", ); try sys.fs.writeFile(path, first); try sys.fs.appendFile(path, &.{partial}); const metrics = try load(allocator, .{ .structured_path = null, .allocation_repetitions_path = path, .expected_executions = 3, .workload = "allocator", }); const allocated = find(metrics, "allocator", "allocations|allocated_bytes").?; try std.testing.expectEqual( Distribution.incomplete_executions, allocated.distribution, ); try std.testing.expectEqual(@as(u64, 2), allocated.sample_count); try std.testing.expectEqual(@as(f64, 20), allocated.value);}test "profiling memory marks a missing repetition artifact incomplete" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); const allocator = arena_state.allocator(); const structured_path = ".zig-cache/profile-memory-missing-repetitions-test.jsonl"; defer sys.fs.deleteFile(structured_path) catch {}; try sys.fs.writeFile( structured_path, "{\"schema\":\"tiny.profiling.structured/v1\"," ++ "\"workload\":{\"name\":\"allocator\"}," ++ "\"source\":{\"kind\":\"allocations\"," ++ "\"path\":\"allocations.jsonl\",\"line\":0}," ++ "\"row\":{\"schema\":\"tiny.profiling.metric/v1\"," ++ "\"family\":\"memory\",\"metric\":\"allocated_bytes\"," ++ "\"name\":\"allocated_bytes\",\"unit\":\"bytes\",\"value\":10}}\n", ); const metrics = try load(allocator, .{ .structured_path = structured_path, .allocation_repetitions_path = ".zig-cache/profile-memory-repetitions-does-not-exist.jsonl", .expected_executions = 3, .workload = "allocator", }); const allocated = find(metrics, "allocator", "allocations|allocated_bytes").?; try std.testing.expectEqual( Distribution.incomplete_executions, allocated.distribution, ); try std.testing.expectEqual(@as(u64, 0), allocated.sample_count);}test "profiling memory compares supported and caveated allocation samples" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); const allocator = arena_state.allocator(); const baseline_samples = [_]f64{ 10, 10, 10, 10 }; const supported_samples = [_]f64{ 20, 20, 20, 20 }; const uncertain_samples = [_]f64{ 5, 5, 35, 35 }; const baseline = repetitionMetric(10, &baseline_samples, .raw_executions); const supported = repetitionMetric(20, &supported_samples, .raw_executions); const supported_rows = try compare(allocator, &.{baseline}, &.{supported}, 10); try std.testing.expectEqualStrings( "allocation_sample_regression", supported_rows[0].status, ); try std.testing.expect(supported_rows[0].effect_low_percent.? >= 10); const uncertain = repetitionMetric(20, &uncertain_samples, .raw_executions); const uncertain_rows = try compare(allocator, &.{baseline}, &.{uncertain}, 10); try std.testing.expectEqualStrings( "allocation_sample_regression_uncertain", uncertain_rows[0].status, ); const incomplete = repetitionMetric(20, &supported_samples, .incomplete_executions); const incomplete_rows = try compare(allocator, &.{baseline}, &.{incomplete}, 10); try std.testing.expectEqualStrings( "allocation_repetition_incomplete", incomplete_rows[0].status, );}test "profiling memory applies workload budgets to compact allocation counters" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); const allocator = arena_state.allocator(); const baseline = Metric{ .workload = "gpalloc.allocator", .key = "gpalloc|small|alloc_bytes_per_eval", .label = "gpalloc small", .source_kind = "bench_jsonl", .source_path = "bench.jsonl", .source_line = 1, .unit = .bytes, .value = 100, }; var candidate = baseline; candidate.value = 129; try std.testing.expectEqual( @as(usize, 0), (try compare(allocator, &.{baseline}, &.{candidate}, 10)).len, ); candidate.value = 130; const rows = try compare(allocator, &.{baseline}, &.{candidate}, 10); try std.testing.expectEqual(@as(usize, 1), rows.len); try std.testing.expectEqual(BudgetKind.allocation, rows[0].budget_kind); try std.testing.expectEqual(@as(f64, 30), rows[0].threshold_percent); try std.testing.expectEqualStrings( "allocation_regression_candidate", rows[0].status, );}fn repetitionMetric( value: f64, samples: []const f64, distribution: Distribution,) Metric { return .{ .workload = "allocator", .key = "allocations|allocated_bytes", .label = "allocated bytes", .source_kind = "allocation_repetitions", .source_path = "repetitions.jsonl", .source_line = 0, .unit = .bytes, .value = value, .sample_count = @intCast(samples.len), .samples = samples, .distribution = distribution, };}test "profiling memory metrics aggregate allocation events" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); const allocator = arena_state.allocator(); try sys.fs.writeFile(".zig-cache/profile-memory-allocations-test.jsonl", \\{"schema":"tiny.profiling.structured/v1","workload":{"name":"gpalloc.allocator","package":"lib/gpalloc","step":"gpalloc-bench"},"source":{"kind":"allocations","path":"allocations.jsonl","line":1},"row":{"kind":"alloc","len":64}} \\{"schema":"tiny.profiling.structured/v1","workload":{"name":"gpalloc.allocator","package":"lib/gpalloc","step":"gpalloc-bench"},"source":{"kind":"allocations","path":"allocations.jsonl","line":2},"row":{"kind":"resize","old_len":64,"new_len":96}} \\{"schema":"tiny.profiling.structured/v1","workload":{"name":"gpalloc.allocator","package":"lib/gpalloc","step":"gpalloc-bench"},"source":{"kind":"allocations","path":"allocations.jsonl","line":3},"row":{"kind":"free","len":96}} \\ ); const metrics = try load(allocator, .{ .structured_path = ".zig-cache/profile-memory-allocations-test.jsonl", }); try std.testing.expectEqual(@as(usize, 5), metrics.len); const allocated = find(metrics, "gpalloc.allocator", "allocations|allocated_bytes").?; try std.testing.expectEqual(Unit.bytes, allocated.unit); try std.testing.expectEqual(@as(f64, 96), allocated.value); sys.fs.deleteFile(".zig-cache/profile-memory-allocations-test.jsonl") catch {};}test "profiling memory metrics accept summarized allocation rows" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); const allocator = arena_state.allocator(); try sys.fs.writeFile(".zig-cache/profile-memory-allocation-summary-test.jsonl", \\{"schema":"tiny.profiling.structured/v1","workload":{"name":"gpalloc.allocator","package":"lib/gpalloc","step":"gpalloc-bench"},"source":{"kind":"allocations","path":"allocations.jsonl","line":0},"row":{"schema":"tiny.profiling.metric/v1","family":"memory","metric":"allocated_bytes","name":"allocated_bytes","unit":"bytes","value":96}} \\{"schema":"tiny.profiling.structured/v1","workload":{"name":"gpalloc.allocator","package":"lib/gpalloc","step":"gpalloc-bench"},"source":{"kind":"allocations","path":"allocations.jsonl","line":0},"row":{"schema":"tiny.profiling.metric/v1","family":"memory","metric":"allocation_events","name":"allocation_events","unit":"count","value":2}} \\ ); const metrics = try load(allocator, .{ .structured_path = ".zig-cache/profile-memory-allocation-summary-test.jsonl", }); try std.testing.expectEqual(@as(usize, 2), metrics.len); const allocated = find(metrics, "gpalloc.allocator", "allocations|allocated_bytes").?; try std.testing.expectEqual(Unit.bytes, allocated.unit); try std.testing.expectEqual(@as(f64, 96), allocated.value); const events = find(metrics, "gpalloc.allocator", "allocations|allocation_events").?; try std.testing.expectEqual(Unit.count, events.unit); try std.testing.expectEqual(@as(f64, 2), events.value); sys.fs.deleteFile(".zig-cache/profile-memory-allocation-summary-test.jsonl") catch {};}test "profiling memory metric keys include benchmark identity" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); const allocator = arena_state.allocator(); try sys.fs.writeFile(".zig-cache/profile-memory-key-test.jsonl", \\{"schema":"tiny.profiling.structured/v1","workload":{"name":"smg.graph","package":"tools/smg","step":"smg-bench"},"source":{"kind":"bench_jsonl","path":"bench.jsonl","line":1},"row":{"schema":"tiny.profiling.metric/v1","event":"bench_end","family":"timing","metric":"duration_ns","unit":"ns","suite":"bench","name":"a","sample_ns":[10,20],"samples":1,"evals":1,"alloc_count":1,"alloc_bytes":10}} \\{"schema":"tiny.profiling.structured/v1","workload":{"name":"smg.graph","package":"tools/smg","step":"smg-bench"},"source":{"kind":"bench_jsonl","path":"bench.jsonl","line":2},"row":{"schema":"tiny.profiling.metric/v1","event":"bench_end","family":"timing","metric":"duration_ns","unit":"ns","suite":"bench","name":"b","sample_ns":[10,20],"samples":1,"evals":1,"alloc_count":2,"alloc_bytes":20}} \\ ); const metrics = try load(allocator, .{ .structured_path = ".zig-cache/profile-memory-key-test.jsonl", }); try std.testing.expectEqual(@as(usize, 4), metrics.len); try std.testing.expect(!std.mem.eql(u8, metrics[0].key, metrics[2].key)); sys.fs.deleteFile(".zig-cache/profile-memory-key-test.jsonl") catch {};}Source: src/profiling/root.zig:32
zig
pub const memory = @import("memory.zig");Complete caller list for memory.load
8 direct callers.
src.profiling.memory.loadForOom[function] — private; no exact target atsrc/profiling/memory.zig:679in nearest public ownertiny.profiling.memorysrc.profiling.memory.test_profiling_memory_caveats_partial_and_missing_allocation_repetitions[function] — test; no exact target atsrc/profiling/memory.zig:740in nearest public ownertiny.profiling.memorysrc.profiling.memory.test_profiling_memory_marks_a_missing_repetition_artifact_incomplete[function] — test; no exact target atsrc/profiling/memory.zig:771in nearest public ownertiny.profiling.memorysrc.profiling.memory.test_profiling_memory_metric_keys_include_benchmark_identity[function] — test; no exact target atsrc/profiling/memory.zig:923in nearest public ownertiny.profiling.memorysrc.profiling.memory.test_profiling_memory_metrics_accept_summarized_allocation_rows[function] — test; no exact target atsrc/profiling/memory.zig:901in nearest public ownertiny.profiling.memorysrc.profiling.memory.test_profiling_memory_metrics_aggregate_allocation_events[function] — test; no exact target atsrc/profiling/memory.zig:881in nearest public ownertiny.profiling.memorysrc.profiling.memory.test_profiling_memory_metrics_parse_bench_allocation_rows[function] — test; no exact target atsrc/profiling/memory.zig:685in nearest public ownertiny.profiling.memorysrc.profiling.memory.test_profiling_memory_retains_complete_allocation_repetition_distributions[function] — test; no exact target atsrc/profiling/memory.zig:703in nearest public ownertiny.profiling.memory
Audit
| Definitions | 16 |
|---|---|
| Public names | 16 |
| Members | 38 |
| Version | 26.7.0 |
| Revision | daab053ee433 |