Skip to documentation
SLOP

tiny.profiling.host.state

Reference 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.

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

Source

Called byCallsdriver.acquirerunChildAcquisitionprivate; no linksrc.profiling.experiment.acquire.RunnercaptureMeasurementprivate; no linksrc.profiling.experiment.acquirefailedResetObservationprivate; no linksrc.profiling.experiment.runprepareContextprivate; no linksrc.profiling.iteration.run.PhaseClockstart+3 moreprivate; no linksrc.profiling.host.statecaptureLinuxhost.statecapture
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsprivate; no linksrc.profiling.experiment.receiptwriteEnvironmentprivate; no linksrc.profiling.experiment.receiptwriteHostWindowhost.statewriteWindowsJsoniteration.receiptwriteJsonprivate; no linksrc.profiling.iteration.receiptwritePhaseprivate; no linksrc.profiling.recordwriteEnvironmentJsonprivate; no linksrc.profiling.host.statewriteOptionalObjectsprivate; no linksrc.profiling.host.statewriteTimeAndPlacementhost.statewriteJson
Static calls · unresolved targets: 1 · external targets: 3.
Called byCallsprivate; no linksrc.profiling.recordwriteWorkloadJsonhost.statewriteJsonhost.statewriteWindowsJson
Static calls · unresolved targets: 1 · external targets: 5.

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.

Audit

Definitions16
Public names16
Members38
Version26.7.0
Revisiondaab053ee433