tiny.profiling.host.state
Defined in host.
API (15)
Actions
Public operations.
Types and contracts
Public types and contracts.
Values and defaults
Public values and defaults.
Source
Source: src/profiling/host/root.zig:9
zig
pub const state = @import("state.zig");Source: src/profiling/host/state.zig
zig
const std = @import("std");const builtin = @import("builtin");const pretty = @import("pretty");const sys = @import("sys");const pretty_json = pretty.json;pub const schema = "tiny.profiling.host-state/v1";pub const capture_method = "boundary_snapshot_outside_timed_child";pub const perturbation_caveat = "state reads occur outside the timed child but can perturb scheduling and frequency";pub const placement_caveat = "observer CPU is the profiling process CPU; " ++ "the measured child inherits affinity but may migrate";pub const maximum_thermal_zones: usize = 64;pub const CpuTimes = struct { user: u64, nice: u64, system: u64, idle: u64, iowait: ?u64 = null, irq: ?u64 = null, softirq: ?u64 = null, steal: ?u64 = null,};pub const Load = struct { one: f64, five: f64, fifteen: f64, runnable: ?u64 = null, tasks: ?u64 = null,};pub const Pressure = struct { avg10: f64, avg60: f64, avg300: f64, total_usec: u64,};pub const Thermal = struct { discovered: usize, readable: usize, minimum_millicelsius: ?i64 = null, maximum_millicelsius: ?i64 = null, mean_millicelsius: ?f64 = null, truncated: bool = false,};pub const Snapshot = struct { realtime_unix_ns: i128 = 0, monotonic_ns: i128 = 0, observer_cpu_id: ?usize = null, observer_numa_node: ?usize = null, observer_cpu_frequency_khz: ?u64 = null, cpu_times: ?CpuTimes = null, load: ?Load = null, pressure: ?Pressure = null, thermal: ?Thermal = null,};pub const Condition = enum { measurement, capture_control, capture,};pub const Window = struct { condition: Condition, before: Snapshot, after: Snapshot,};pub fn capture() Snapshot { var result = Snapshot{ .realtime_unix_ns = sys.time.realNanoTimestamp(), .monotonic_ns = sys.time.nanoTimestamp(), }; if (builtin.os.tag != .linux) return result; captureLinux(&result); return result;}fn captureLinux(result: *Snapshot) void { capturePlacement(result); var stat_buffer: [4096]u8 = undefined; if (readAbsolute("/proc/stat", &stat_buffer)) |text| { result.cpu_times = parseCpuTimes(text); } var load_buffer: [256]u8 = undefined; if (readAbsolute("/proc/loadavg", &load_buffer)) |text| { result.load = parseLoad(text); } var pressure_buffer: [1024]u8 = undefined; if (readAbsolute("/proc/pressure/cpu", &pressure_buffer)) |text| { result.pressure = parsePressure(text); } result.thermal = captureThermal();}fn capturePlacement(result: *Snapshot) void { const placement = sys.thread.currentPlacement() catch return; result.observer_cpu_id = placement.cpu_id; result.observer_numa_node = placement.numa_node; var path_buffer: [128]u8 = undefined; const path = std.fmt.bufPrint( &path_buffer, "/sys/devices/system/cpu/cpu{d}/cpufreq/scaling_cur_freq", .{placement.cpu_id}, ) catch return; var frequency_buffer: [64]u8 = undefined; const text = readAbsolute(path, &frequency_buffer) orelse return; result.observer_cpu_frequency_khz = parseUnsigned(text);}fn readAbsolute(path: []const u8, storage: []u8) ?[]const u8 { std.debug.assert(std.fs.path.isAbsolute(path)); std.debug.assert(storage.len > 0); var file = sys.fs.openAbsoluteFile(path, .{}) catch return null; defer file.close(sys.fs.debugIo()); var reader_buffer: [4096]u8 = undefined; var reader = file.readerStreaming(sys.fs.debugIo(), &reader_buffer); var used: usize = 0; while (used < storage.len) { const count = reader.interface.readSliceShort(storage[used..]) catch return null; if (count == 0) return storage[0..used]; used += count; } var extra: [1]u8 = undefined; const count = reader.interface.readSliceShort(&extra) catch return null; if (count != 0) return null; return storage;}fn parseCpuTimes(text: []const u8) ?CpuTimes { const line = firstLine(text); var fields = std.mem.tokenizeAny(u8, line, " \t"); if (!std.mem.eql(u8, fields.next() orelse return null, "cpu")) return null; return .{ .user = parseUnsigned(fields.next() orelse return null) orelse return null, .nice = parseUnsigned(fields.next() orelse return null) orelse return null, .system = parseUnsigned(fields.next() orelse return null) orelse return null, .idle = parseUnsigned(fields.next() orelse return null) orelse return null, .iowait = parseOptionalUnsigned(fields.next()), .irq = parseOptionalUnsigned(fields.next()), .softirq = parseOptionalUnsigned(fields.next()), .steal = parseOptionalUnsigned(fields.next()), };}fn parseLoad(text: []const u8) ?Load { var fields = std.mem.tokenizeAny(u8, firstLine(text), " \t"); const one = parseFloat(fields.next() orelse return null) orelse return null; const five = parseFloat(fields.next() orelse return null) orelse return null; const fifteen = parseFloat(fields.next() orelse return null) orelse return null; const tasks = fields.next() orelse return .{ .one = one, .five = five, .fifteen = fifteen, }; const split = std.mem.indexOfScalar(u8, tasks, '/') orelse return null; return .{ .one = one, .five = five, .fifteen = fifteen, .runnable = parseUnsigned(tasks[0..split]), .tasks = parseUnsigned(tasks[split + 1 ..]), };}fn parsePressure(text: []const u8) ?Pressure { var lines = std.mem.splitScalar(u8, text, '\n'); while (lines.next()) |line| { var fields = std.mem.tokenizeAny(u8, line, " \t"); if (!std.mem.eql(u8, fields.next() orelse continue, "some")) continue; return .{ .avg10 = parseKeyFloat(fields.next() orelse return null, "avg10") orelse return null, .avg60 = parseKeyFloat(fields.next() orelse return null, "avg60") orelse return null, .avg300 = parseKeyFloat(fields.next() orelse return null, "avg300") orelse return null, .total_usec = parseKeyUnsigned( fields.next() orelse return null, "total", ) orelse return null, }; } return null;}fn captureThermal() ?Thermal { const root = "/sys/class/thermal"; var directory = std.Io.Dir.openDirAbsolute( std.Options.debug_io, root, .{ .iterate = true }, ) catch return null; defer directory.close(std.Options.debug_io); var result = Thermal{ .discovered = 0, .readable = 0 }; var total: i128 = 0; var iterator = directory.iterate(); while (iterator.next(std.Options.debug_io) catch return null) |entry| { if (!std.mem.startsWith(u8, entry.name, "thermal_zone")) continue; if (result.discovered == maximum_thermal_zones) { result.truncated = true; break; } result.discovered += 1; observeThermal(root, entry.name, &result, &total); } if (result.readable != 0) { result.mean_millicelsius = @as(f64, @floatFromInt(total)) / @as(f64, @floatFromInt(result.readable)); } return result;}fn observeThermal( root: []const u8, name: []const u8, result: *Thermal, total: *i128,) void { var path_buffer: [512]u8 = undefined; const path = std.fmt.bufPrint( &path_buffer, "{s}/{s}/temp", .{ root, name }, ) catch return; var value_buffer: [64]u8 = undefined; const text = readAbsolute(path, &value_buffer) orelse return; const value = std.fmt.parseInt( i64, std.mem.trim(u8, text, " \t\r\n"), 10, ) catch return; result.readable += 1; result.minimum_millicelsius = @min( result.minimum_millicelsius orelse value, value, ); result.maximum_millicelsius = @max( result.maximum_millicelsius orelse value, value, ); total.* += value;}fn firstLine(text: []const u8) []const u8 { const end = std.mem.indexOfScalar(u8, text, '\n') orelse text.len; return text[0..end];}fn parseUnsigned(text: []const u8) ?u64 { return std.fmt.parseInt(u64, std.mem.trim(u8, text, " \t\r\n"), 10) catch null;}fn parseOptionalUnsigned(text: ?[]const u8) ?u64 { return parseUnsigned(text orelse return null);}fn parseFloat(text: []const u8) ?f64 { return std.fmt.parseFloat(f64, text) catch null;}fn parseKeyFloat(text: []const u8, expected: []const u8) ?f64 { const split = std.mem.indexOfScalar(u8, text, '=') orelse return null; if (!std.mem.eql(u8, text[0..split], expected)) return null; return parseFloat(text[split + 1 ..]);}fn parseKeyUnsigned(text: []const u8, expected: []const u8) ?u64 { const split = std.mem.indexOfScalar(u8, text, '=') orelse return null; if (!std.mem.eql(u8, text[0..split], expected)) return null; return parseUnsigned(text[split + 1 ..]);}pub fn writeJson(out: *pretty_json.Writer, value: Snapshot) !void { try out.beginObject(); try out.objectField("schema"); try out.write(schema); try out.objectField("capture_method"); try out.write(capture_method); try out.objectField("perturbation_caveat"); try out.write(perturbation_caveat); try out.objectField("placement_caveat"); try out.write(placement_caveat); try writeTimeAndPlacement(out, value); try writeOptionalObjects(out, value); try out.endObject();}fn writeTimeAndPlacement( out: *pretty_json.Writer, value: Snapshot,) !void { try out.objectField("realtime_unix_ns"); try out.write(value.realtime_unix_ns); try out.objectField("monotonic_ns"); try out.write(value.monotonic_ns); try out.objectField("observer_cpu_id"); try out.write(value.observer_cpu_id); try out.objectField("observer_numa_node"); try out.write(value.observer_numa_node); try out.objectField("observer_cpu_frequency_khz"); try out.write(value.observer_cpu_frequency_khz);}fn writeOptionalObjects( out: *pretty_json.Writer, value: Snapshot,) !void { try out.objectField("cpu_times"); if (value.cpu_times) |times| { try writeCpuTimes(out, times); } else { try out.write(null); } try out.objectField("load"); if (value.load) |load| { try writeLoad(out, load); } else { try out.write(null); } try out.objectField("pressure"); if (value.pressure) |pressure| { try writePressure(out, pressure); } else { try out.write(null); } try out.objectField("thermal"); if (value.thermal) |thermal| { try writeThermal(out, thermal); } else { try out.write(null); }}fn writeCpuTimes( out: *pretty_json.Writer, value: CpuTimes,) !void { try out.beginObject(); inline for (.{ "user", "nice", "system", "idle" }) |field| { try out.objectField(field); try out.write(@field(value, field)); } inline for (.{ "iowait", "irq", "softirq", "steal" }) |field| { try out.objectField(field); try out.write(@field(value, field)); } try out.endObject();}fn writeLoad(out: *pretty_json.Writer, value: Load) !void { try out.beginObject(); try out.objectField("one"); try out.write(value.one); try out.objectField("five"); try out.write(value.five); try out.objectField("fifteen"); try out.write(value.fifteen); try out.objectField("runnable"); try out.write(value.runnable); try out.objectField("tasks"); try out.write(value.tasks); try out.endObject();}fn writePressure(out: *pretty_json.Writer, value: Pressure) !void { try out.beginObject(); try out.objectField("avg10"); try out.write(value.avg10); try out.objectField("avg60"); try out.write(value.avg60); try out.objectField("avg300"); try out.write(value.avg300); try out.objectField("total_usec"); try out.write(value.total_usec); try out.endObject();}fn writeThermal(out: *pretty_json.Writer, value: Thermal) !void { try out.beginObject(); try out.objectField("discovered"); try out.write(value.discovered); try out.objectField("readable"); try out.write(value.readable); try out.objectField("minimum_millicelsius"); try out.write(value.minimum_millicelsius); try out.objectField("maximum_millicelsius"); try out.write(value.maximum_millicelsius); try out.objectField("mean_millicelsius"); try out.write(value.mean_millicelsius); try out.objectField("truncated"); try out.write(value.truncated); try out.endObject();}pub fn writeWindowsJson( out: *pretty_json.Writer, windows: []const Window,) !void { try out.beginObject(); try out.objectField("placement"); try out.write("immediately_before_and_after_each_timed_child"); try out.objectField("windows"); try out.beginArray(); for (windows, 0..) |window, index| { try out.beginObject(); try out.objectField("index"); try out.write(index + 1); try out.objectField("condition"); try out.write(@tagName(window.condition)); try out.objectField("before"); try writeJson(out, window.before); try out.objectField("after"); try writeJson(out, window.after); try out.endObject(); } try out.endArray(); try out.endObject();}test "profiling host state parses Linux scheduler evidence" { const times = parseCpuTimes( "cpu 100 2 30 400 5 6 7 8 0 0\ncpu0 50 1 15 200\n", ).?; try std.testing.expectEqual(@as(u64, 100), times.user); try std.testing.expectEqual(@as(?u64, 8), times.steal); const load = parseLoad("1.25 0.75 0.50 3/128 42\n").?; try std.testing.expectEqual(@as(f64, 1.25), load.one); try std.testing.expectEqual(@as(?u64, 3), load.runnable); try std.testing.expectEqual(@as(?u64, 128), load.tasks);}test "profiling host state parses CPU pressure" { const pressure = parsePressure( "some avg10=0.12 avg60=0.34 avg300=0.56 total=789\n", ).?; try std.testing.expectEqual(@as(f64, 0.12), pressure.avg10); try std.testing.expectEqual(@as(u64, 789), pressure.total_usec); try std.testing.expect(parsePressure("full avg10=1 total=2\n") == null);}Complete caller list for host.state.capture
8 direct callers.
tiny.profiling.driver.acquire.runChildAcquisition[function] atsrc/profiling/driver/acquire.zig:42src.profiling.experiment.acquire.Runner.captureMeasurement[method] — private; no exact target atsrc/profiling/experiment/acquire.zig:326in nearest public ownertiny.profiling.experiment.acquiresrc.profiling.experiment.acquire.failedResetObservation[function] — private; no exact target atsrc/profiling/experiment/acquire.zig:396in nearest public ownertiny.profiling.experiment.acquiresrc.profiling.experiment.run.prepareContext[function] — private; no exact target atsrc/profiling/experiment/run.zig:128in nearest public ownertiny.profiling.experiment.runsrc.profiling.iteration.run.PhaseClock.start[function] — private; no exact target atsrc/profiling/iteration/run.zig:58in nearest public ownertiny.profiling.iteration.runtiny.profiling.iteration.run.execute[function] atsrc/profiling/iteration/run.zig:67src.profiling.iteration.run.finishPhase[function] — private; no exact target atsrc/profiling/iteration/run.zig:707in nearest public ownertiny.profiling.iteration.runtiny.profiling.record.collectMetadata[function] atsrc/profiling/record.zig:212
Audit
| Definitions | 16 |
|---|---|
| Public names | 16 |
| Members | 38 |
| Version | 26.7.0 |
| Revision | daab053ee433 |