tiny.profiling.environment
Defined in tiny.profiling.
API (6)
Actions
Public operations.
Types and contracts
Public types and contracts.
Source
Source: src/profiling/environment.zig
zig
const std = @import("std");const builtin = @import("builtin");const pretty = @import("pretty");const sys = @import("sys");const frequency = @import("frequency.zig");const json = @import("json.zig");const pretty_json = pretty.json;const max_command_bytes = 4096;const max_cpuinfo_bytes = 4 * 1024 * 1024;const max_identity_bytes = 256;const max_process_status_bytes = 64 * 1024;pub const Host = struct { hostname: ?[]const u8 = null, os: ?[]const u8 = null, kernel: ?[]const u8 = null, arch: ?[]const u8 = null, cpu_model: ?[]const u8 = null, cpu_count: ?u64 = null, cpu_frequency_policy: ?[]const u8 = null, process_cpu_affinity: ?[]const u8 = null, process_memory_affinity: ?[]const u8 = null,};pub const ComparisonSupport = enum { supported, missing_optimize, optimize_mismatch, missing_host, os_mismatch, arch_mismatch, cpu_count_mismatch, hostname_mismatch, kernel_mismatch, cpu_model_mismatch, cpu_frequency_policy_missing, cpu_frequency_policy_mismatch, process_cpu_affinity_missing, process_cpu_affinity_mismatch, process_memory_affinity_missing, process_memory_affinity_mismatch,};const ProcessAffinity = struct { cpu: ?[]const u8 = null, memory: ?[]const u8 = null,};pub fn collect( allocator: std.mem.Allocator, process_io: std.Io,) !Host { const process_affinity = if (builtin.target.os.tag == .linux) try collectProcessAffinity(allocator) else ProcessAffinity{}; const cpu_frequency_policy = if (process_affinity.cpu) |affinity| try frequency.collect(allocator, affinity) else null; const hostname = (try readTrimmed( allocator, "/proc/sys/kernel/hostname", max_identity_bytes, )) orelse try commandCapture(allocator, process_io, &.{"hostname"}); const kernel = (try readTrimmed( allocator, "/proc/sys/kernel/osrelease", max_identity_bytes, )) orelse try commandCapture(allocator, process_io, &.{ "uname", "-r" }); return .{ .hostname = hostname, .os = @tagName(builtin.target.os.tag), .kernel = kernel, .arch = @tagName(builtin.cpu.arch), .cpu_model = try cpuModel(allocator), .cpu_count = @intCast(sys.thread.cpuCount()), .cpu_frequency_policy = cpu_frequency_policy, .process_cpu_affinity = process_affinity.cpu, .process_memory_affinity = process_affinity.memory, };}pub fn parse(value: ?std.json.Value) Host { const object = json.object(value orelse return .{}) catch return .{}; const host = json.object(object.get("host") orelse return .{}) catch return .{}; return .{ .hostname = json.string(host.get("hostname")), .os = json.string(host.get("os")), .kernel = json.string(host.get("kernel")), .arch = json.string(host.get("arch")), .cpu_model = json.string(host.get("cpu_model")), .cpu_count = json.asU64(host.get("cpu_count")), .cpu_frequency_policy = json.string(host.get("cpu_frequency_policy")), .process_cpu_affinity = json.string(host.get("process_cpu_affinity")), .process_memory_affinity = json.string(host.get("process_memory_affinity")), };}pub fn writeJson(out: *pretty_json.Writer, host: Host) !void { try out.beginObject(); try out.objectField("hostname"); try out.write(host.hostname); try out.objectField("os"); try out.write(host.os); try out.objectField("kernel"); try out.write(host.kernel); try out.objectField("arch"); try out.write(host.arch); try out.objectField("cpu_model"); try out.write(host.cpu_model); try out.objectField("cpu_count"); try out.write(host.cpu_count); try out.objectField("cpu_frequency_policy"); try out.write(host.cpu_frequency_policy); try out.objectField("process_cpu_affinity"); try out.write(host.process_cpu_affinity); try out.objectField("process_memory_affinity"); try out.write(host.process_memory_affinity); try out.endObject();}pub fn comparisonSupport( baseline_optimize: ?[]const u8, baseline_host: Host, candidate_optimize: ?[]const u8, candidate_host: Host,) ComparisonSupport { const baseline_mode = baseline_optimize orelse return .missing_optimize; const candidate_mode = candidate_optimize orelse return .missing_optimize; if (!std.mem.eql(u8, baseline_mode, candidate_mode)) return .optimize_mismatch; if (!mandatory(baseline_host) or !mandatory(candidate_host)) return .missing_host; if (!same(baseline_host.os, candidate_host.os)) return .os_mismatch; if (!same(baseline_host.arch, candidate_host.arch)) return .arch_mismatch; if (baseline_host.cpu_count.? != candidate_host.cpu_count.?) return .cpu_count_mismatch; if (!same(baseline_host.hostname, candidate_host.hostname)) return .hostname_mismatch; if (!same(baseline_host.kernel, candidate_host.kernel)) return .kernel_mismatch; if (!same(baseline_host.cpu_model, candidate_host.cpu_model)) return .cpu_model_mismatch; const linux = std.mem.eql(u8, baseline_host.os.?, "linux"); if (linux and (baseline_host.process_cpu_affinity == null or candidate_host.process_cpu_affinity == null)) { return .process_cpu_affinity_missing; } if (!same( baseline_host.process_cpu_affinity, candidate_host.process_cpu_affinity, )) return .process_cpu_affinity_mismatch; if (linux and (baseline_host.process_memory_affinity == null or candidate_host.process_memory_affinity == null)) { return .process_memory_affinity_missing; } if (!same( baseline_host.process_memory_affinity, candidate_host.process_memory_affinity, )) return .process_memory_affinity_mismatch; if (linux and (baseline_host.cpu_frequency_policy == null or candidate_host.cpu_frequency_policy == null)) { return .cpu_frequency_policy_missing; } if (!same( baseline_host.cpu_frequency_policy, candidate_host.cpu_frequency_policy, )) return .cpu_frequency_policy_mismatch; return .supported;}fn mandatory(host: Host) bool { return host.os != null and host.arch != null and host.cpu_count != null;}fn same(baseline: ?[]const u8, candidate: ?[]const u8) bool { if (baseline == null or candidate == null) return baseline == null and candidate == null; return std.mem.eql(u8, baseline.?, candidate.?);}fn commandCapture( allocator: std.mem.Allocator, process_io: std.Io, argv: []const []const u8,) !?[]const u8 { const result = sys.process.run(allocator, process_io, .{ .argv = argv, .stdout_limit = .limited(max_command_bytes), .stderr_limit = .limited(max_command_bytes), }) catch return null; defer allocator.free(result.stderr); if (sys.process.exitCode(result.term) != 0) { allocator.free(result.stdout); return null; } const trimmed = std.mem.trim(u8, result.stdout, " \t\r\n"); if (trimmed.len == 0) { allocator.free(result.stdout); return null; } const copy = try allocator.dupe(u8, trimmed); allocator.free(result.stdout); return copy;}fn cpuModel(allocator: std.mem.Allocator) !?[]const u8 { const text = try readBounded( allocator, "/proc/cpuinfo", max_cpuinfo_bytes, ) orelse return null; defer allocator.free(text); const model = parseCpuModel(text) orelse return null; return try allocator.dupe(u8, model);}fn collectProcessAffinity(allocator: std.mem.Allocator) !ProcessAffinity { const text = try readBounded( allocator, "/proc/self/status", max_process_status_bytes, ) orelse return .{}; defer allocator.free(text); const affinity = parseProcessAffinity(text); const cpu = if (affinity.cpu) |value| try allocator.dupe(u8, value) else null; errdefer if (cpu) |value| allocator.free(value); return .{ .cpu = cpu, .memory = if (affinity.memory) |value| try allocator.dupe(u8, value) else null, };}fn parseProcessAffinity(text: []const u8) ProcessAffinity { return .{ .cpu = field(text, "Cpus_allowed_list"), .memory = field(text, "Mems_allowed_list"), };}fn parseCpuModel(text: []const u8) ?[]const u8 { return field(text, "model name") orelse field(text, "Hardware") orelse field(text, "Processor");}fn field(text: []const u8, name: []const u8) ?[]const u8 { var lines = std.mem.splitScalar(u8, text, '\n'); while (lines.next()) |line| { const split = std.mem.indexOfScalar(u8, line, ':') orelse continue; const key = std.mem.trim(u8, line[0..split], " \t\r"); if (!std.ascii.eqlIgnoreCase(key, name)) continue; const value = std.mem.trim(u8, line[split + 1 ..], " \t\r"); if (value.len != 0) return value; } return null;}fn readTrimmed( allocator: std.mem.Allocator, path: []const u8, limit: usize,) !?[]const u8 { const text = try readBounded(allocator, path, limit) orelse return null; defer allocator.free(text); const value = std.mem.trim(u8, text, " \t\r\n"); if (value.len == 0) return null; return try allocator.dupe(u8, value);}fn readBounded( allocator: std.mem.Allocator, path: []const u8, limit: usize,) !?[]u8 { std.debug.assert(limit > 0); std.debug.assert(std.fs.path.isAbsolute(path)); var file = sys.fs.openAbsoluteFile(path, .{}) catch return null; defer file.close(sys.fs.debugIo()); var buffer: [4096]u8 = undefined; var reader = file.readerStreaming(sys.fs.debugIo(), &buffer); return reader.interface.allocRemaining(allocator, .limited(limit)) catch return null;}test "profiling environment parses Linux CPU models" { try std.testing.expectEqualStrings( "AMD Ryzen 7 7800X3D 8-Core Processor", parseCpuModel( \\processor : 0 \\model name : AMD Ryzen 7 7800X3D 8-Core Processor \\ ).?, ); try std.testing.expectEqualStrings( "Raspberry Pi 5 Model B Rev 1.0", parseCpuModel( \\processor : 0 \\Hardware : Raspberry Pi 5 Model B Rev 1.0 \\ ).?, );}test "profiling environment parses the process affinity envelope" { const affinity = parseProcessAffinity( \\Name: tiny-profile \\Cpus_allowed: 0000ffff \\Cpus_allowed_list: 0-15 \\Mems_allowed: 00000000,00000001 \\Mems_allowed_list: 0 \\ ); try std.testing.expectEqualStrings("0-15", affinity.cpu.?); try std.testing.expectEqualStrings("0", affinity.memory.?); const missing = parseProcessAffinity("Name:\ttiny-profile\n"); try std.testing.expectEqual(null, missing.cpu); try std.testing.expectEqual(null, missing.memory);}const test_linux_host = Host{ .hostname = "bench-a", .os = "linux", .kernel = "7.0.11", .arch = "x86_64", .cpu_model = "AMD Ryzen 7 7800X3D", .cpu_count = 16, .cpu_frequency_policy = "policy-a", .process_cpu_affinity = "0-15", .process_memory_affinity = "0",};test "profiling environment requires matching build and mandatory host fields" { const host = test_linux_host; try std.testing.expectEqual( ComparisonSupport.supported, comparisonSupport("ReleaseFast", host, "ReleaseFast", host), ); try std.testing.expectEqual( ComparisonSupport.missing_optimize, comparisonSupport(null, host, "ReleaseFast", host), ); try std.testing.expectEqual( ComparisonSupport.optimize_mismatch, comparisonSupport("ReleaseSafe", host, "ReleaseFast", host), ); var candidate = host; candidate.cpu_count = null; try std.testing.expectEqual( ComparisonSupport.missing_host, comparisonSupport("ReleaseFast", host, "ReleaseFast", candidate), ); candidate = host; candidate.os = "freebsd"; try std.testing.expectEqual( ComparisonSupport.os_mismatch, comparisonSupport("ReleaseFast", host, "ReleaseFast", candidate), ); candidate = host; candidate.arch = "aarch64"; try std.testing.expectEqual( ComparisonSupport.arch_mismatch, comparisonSupport("ReleaseFast", host, "ReleaseFast", candidate), ); candidate = host; candidate.cpu_count = 8; try std.testing.expectEqual( ComparisonSupport.cpu_count_mismatch, comparisonSupport("ReleaseFast", host, "ReleaseFast", candidate), );}test "profiling environment rejects host drift" { const host = test_linux_host; var candidate = host; candidate.hostname = "bench-b"; try std.testing.expectEqual( ComparisonSupport.hostname_mismatch, comparisonSupport("ReleaseFast", host, "ReleaseFast", candidate), ); candidate = host; candidate.kernel = "7.0.12"; try std.testing.expectEqual( ComparisonSupport.kernel_mismatch, comparisonSupport("ReleaseFast", host, "ReleaseFast", candidate), ); candidate = host; candidate.cpu_model = "AMD Ryzen 9 9950X3D"; try std.testing.expectEqual( ComparisonSupport.cpu_model_mismatch, comparisonSupport("ReleaseFast", host, "ReleaseFast", candidate), ); candidate.hostname = null; try std.testing.expectEqual( ComparisonSupport.hostname_mismatch, comparisonSupport("ReleaseFast", host, "ReleaseFast", candidate), ); candidate = host; candidate.kernel = null; var baseline = host; baseline.kernel = null; try std.testing.expectEqual( ComparisonSupport.supported, comparisonSupport("ReleaseFast", baseline, "ReleaseFast", candidate), );}test "profiling environment requires matching Linux process affinity" { const host = test_linux_host; var candidate = host; candidate.process_cpu_affinity = "0-7"; try std.testing.expectEqual( ComparisonSupport.process_cpu_affinity_mismatch, comparisonSupport("ReleaseFast", host, "ReleaseFast", candidate), ); candidate = host; candidate.process_memory_affinity = "1"; try std.testing.expectEqual( ComparisonSupport.process_memory_affinity_mismatch, comparisonSupport("ReleaseFast", host, "ReleaseFast", candidate), ); candidate = host; candidate.process_cpu_affinity = null; try std.testing.expectEqual( ComparisonSupport.process_cpu_affinity_missing, comparisonSupport("ReleaseFast", host, "ReleaseFast", candidate), ); candidate = host; candidate.process_memory_affinity = null; try std.testing.expectEqual( ComparisonSupport.process_memory_affinity_missing, comparisonSupport("ReleaseFast", host, "ReleaseFast", candidate), );}test "profiling environment requires matching Linux frequency policy" { const host = test_linux_host; var candidate = host; candidate.cpu_frequency_policy = "policy-b"; try std.testing.expectEqual( ComparisonSupport.cpu_frequency_policy_mismatch, comparisonSupport("ReleaseFast", host, "ReleaseFast", candidate), ); candidate = host; candidate.cpu_frequency_policy = null; try std.testing.expectEqual( ComparisonSupport.cpu_frequency_policy_missing, comparisonSupport("ReleaseFast", host, "ReleaseFast", candidate), );}test "profiling environment permits absent affinity outside Linux" { const host = Host{ .os = "macos", .arch = "aarch64", .cpu_count = 8, }; try std.testing.expectEqual( ComparisonSupport.supported, comparisonSupport("ReleaseFast", host, "ReleaseFast", host), ); var candidate = host; candidate.process_cpu_affinity = "0-7"; try std.testing.expectEqual( ComparisonSupport.process_cpu_affinity_mismatch, comparisonSupport("ReleaseFast", host, "ReleaseFast", candidate), ); candidate = host; candidate.cpu_frequency_policy = "policy-a"; try std.testing.expectEqual( ComparisonSupport.cpu_frequency_policy_mismatch, comparisonSupport("ReleaseFast", host, "ReleaseFast", candidate), );}Source: src/profiling/root.zig:22
zig
pub const environment = @import("environment.zig");Audit
| Definitions | 6 |
|---|---|
| Public names | 6 |
| Members | 9 |
| Version | 26.7.0 |
| Revision | daab053ee433 |