From a2111391cff0c069480f9a914ca73ab6322da77b Mon Sep 17 00:00:00 2001 From: hndrk <51416554+hendriknielaender@users.noreply.github.com> Date: Fri, 23 Feb 2024 21:33:08 +0100 Subject: [PATCH] feat: display sys infos (#46) * feat: display sys infos * fix: missing os imports * chore: add cpu cores & memory fn * cleanup, add bounds checks, fix determination of physical CPU cores * chore: use scoped logger for util/os osx & windows * fix: add sufficient length checks --------- Co-authored-by: FObersteiner Co-authored-by: FObersteiner --- examples/basic.zig | 2 +- util/format.zig | 27 ++++++++++++++++++ util/os/linux.zig | 68 +++++++++++++++++++++++++++++++++++++++++++++ util/os/osx.zig | 35 +++++++++++++++++++++++ util/os/windows.zig | 60 +++++++++++++++++++++++++++++++++++++++ util/platform.zig | 60 +++++++++++++++++++++++++++++++++++++++ zbench.zig | 16 +++++++++++ 7 files changed, 267 insertions(+), 1 deletion(-) create mode 100644 util/os/linux.zig create mode 100644 util/os/osx.zig create mode 100644 util/os/windows.zig create mode 100644 util/platform.zig diff --git a/examples/basic.zig b/examples/basic.zig index 4c13c27..1cea2c6 100644 --- a/examples/basic.zig +++ b/examples/basic.zig @@ -21,7 +21,7 @@ test "bench test basic" { const resultsAlloc = std.ArrayList(zbench.BenchmarkResult).init(test_allocator); var benchmarkResults = zbench.BenchmarkResults.init(resultsAlloc); defer benchmarkResults.deinit(); - var bench = try zbench.Benchmark.init("My Benchmark", test_allocator, .{ .iterations = 10 }); + var bench = try zbench.Benchmark.init("My Benchmark", test_allocator, .{ .iterations = 10, .display_system_info = true }); try zbench.run(myBenchmark, &bench, &benchmarkResults); try benchmarkResults.prettyPrint(); diff --git a/util/format.zig b/util/format.zig index 37b7adf..1d1f3fc 100644 --- a/util/format.zig +++ b/util/format.zig @@ -1,5 +1,6 @@ const std = @import("std"); const Color = @import("./color.zig").Color; +const log = std.log.scoped(.zbench_format); pub fn duration(buffer: []u8, d: u64) ![]u8 { const units = [_][]const u8{ "ns", "µs", "ms", "s" }; @@ -20,6 +21,32 @@ pub fn duration(buffer: []u8, d: u64) ![]u8 { return formatted; } +pub fn memorySize(bytes: u64, allocator: std.mem.Allocator) ![]const u8 { + const units = .{ "B", "KB", "MB", "GB", "TB" }; + var size: f64 = @floatFromInt(bytes); + var unit_index: usize = 0; + + while (size >= 1024 and unit_index < units.len - 1) : (unit_index += 1) { + size /= 1024; + } + + const unit = switch (unit_index) { + 0 => "B", + 1 => "KB", + 2 => "MB", + 3 => "GB", + 4 => "TB", + 5 => "PB", + 6 => "EB", + else => unreachable, + }; + + // Format the result with two decimal places if needed + var buf: [64]u8 = undefined; // Buffer for formatting + const formattedSize = try std.fmt.bufPrint(&buf, "{d:.2} {s}", .{ size, unit }); + return allocator.dupe(u8, formattedSize); +} + /// Pretty-prints the header for the result pretty-print table /// writer: Type that has the associated method print (for example std.io.getStdOut.writer()) pub fn prettyPrintHeader(writer: anytype) !void { diff --git a/util/os/linux.zig b/util/os/linux.zig new file mode 100644 index 0000000..84d3177 --- /dev/null +++ b/util/os/linux.zig @@ -0,0 +1,68 @@ +const std = @import("std"); +const fs = std.fs; +const mem = std.mem; +const log = std.log.scoped(.zbench_platform_linux); + +pub fn getCpuName(allocator: mem.Allocator) ![]const u8 { + const file = try fs.cwd().openFile("/proc/cpuinfo", .{}); + defer file.close(); + + var buf: [128]u8 = undefined; + _ = try file.read(&buf); + + const start = if (mem.indexOf(u8, &buf, "model name")) |pos| pos + 13 else 0; + const end = if (mem.indexOfScalar(u8, buf[start..], '\n')) |pos| start + pos else 0; + + if ((start == 0 and end == 0) or (start > end)) { + return error.CouldNotFindCpuName; + } + + return allocator.dupe(u8, buf[start..end]); +} + +pub fn getCpuCores() !u32 { + const file = try fs.cwd().openFile("/proc/cpuinfo", .{}); + defer file.close(); + + var buf: [1024]u8 = undefined; + _ = try file.read(&buf); + + var token_iterator = std.mem.tokenizeSequence(u8, &buf, "\n"); + while (token_iterator.next()) |line| { + if (std.mem.startsWith(u8, line, "cpu cores")) { + const start = if (mem.indexOf(u8, line, ":")) |pos| pos + 2 else 0; + return try std.fmt.parseInt(u32, line[start..], 10); + } + } + + return error.CouldNotFindNumCores; +} + +pub fn getTotalMemory() !u64 { + const file = try std.fs.cwd().openFile("/proc/meminfo", .{}); + defer file.close(); + + var buf: [128]u8 = undefined; + _ = try file.read(&buf); + + var token_iterator = std.mem.tokenizeSequence(u8, &buf, "\n"); + while (token_iterator.next()) |line| { + if (std.mem.startsWith(u8, line, "MemTotal:")) { + // Extract the numeric value from the line + var parts = std.mem.tokenizeSequence(u8, line, " "); + var valueFound = false; + while (parts.next()) |part| { + if (valueFound) { + // Convert the extracted value to bytes (from kB) + const memKb = try std.fmt.parseInt(u64, part, 10); + return memKb * 1024; // Convert kB to bytes + } + if (std.mem.eql(u8, part, "MemTotal:")) { + valueFound = true; + } + } + } + } + + return error.CouldNotFindMemoryTotal; +} diff --git a/util/os/osx.zig b/util/os/osx.zig new file mode 100644 index 0000000..14e12ce --- /dev/null +++ b/util/os/osx.zig @@ -0,0 +1,35 @@ +const std = @import("std"); +const log = std.log.scoped(.zbench_platform_osx); + +pub fn getCpuName(allocator: std.mem.Allocator) ![]const u8 { + return try exec(allocator, &.{ "sysctl", "-n", "machdep.cpu.brand_string" }); +} + +pub fn getCpuCores(allocator: std.mem.Allocator) !u32 { + const coresString = try exec(allocator, &.{ "sysctl", "-n", "hw.physicalcpu" }); + defer allocator.free(coresString); + + return std.fmt.parseInt(u32, coresString, 10) catch |err| { + log.err("Error parsing CPU cores count: {}\n", .{err}); + return err; + }; +} + +pub fn getTotalMemory(allocator: std.mem.Allocator) !u64 { + const memSizeString = try exec(allocator, &.{ "sysctl", "-n", "hw.memsize" }); + defer allocator.free(memSizeString); + + // Parse the string to a 64-bit unsigned integer + return std.fmt.parseInt(u64, memSizeString, 10) catch |err| { + log.err("Error parsing total memory size: {}\n", .{err}); + return err; + }; +} + +fn exec(allocator: std.mem.Allocator, args: []const []const u8) ![]const u8 { + const stdout = (try std.process.Child.exec(.{ .allocator = allocator, .argv = args })).stdout; + + if (stdout.len == 0) return error.EmptyOutput; + + return stdout[0 .. stdout.len - 1]; +} diff --git a/util/os/windows.zig b/util/os/windows.zig new file mode 100644 index 0000000..b339580 --- /dev/null +++ b/util/os/windows.zig @@ -0,0 +1,60 @@ +const std = @import("std"); +const log = std.log.scoped(.zbench_platform_windows); + +pub fn getCpuName(allocator: std.mem.Allocator) ![]const u8 { + const stdout = try exec(allocator, &.{ "wmic", "cpu", "get", "name" }); + + // Ensure stdout is long enough before slicing + if (stdout.len < 52) return error.InsufficientLength; + + return stdout[45 .. stdout.len - 7]; +} + +pub fn getCpuCores(allocator: std.mem.Allocator) !u32 { + const stdout = try exec(allocator, &.{ "wmic", "cpu", "get", "NumberOfCores" }); + + // Process the command output to extract the cores count + // WMIC output has headers and multiple lines, we need to find the first digit occurrence + var start: usize = 0; + while (start < stdout.len and !std.ascii.isDigit(stdout[start])) : (start += 1) {} + if (start == stdout.len) return error.InvalidData; + + var end = start; + while (end < stdout.len and std.ascii.isDigit(stdout[end])) : (end += 1) {} + + // Parse the extracted string to an integer + return std.fmt.parseInt(u32, stdout[start..end], 10) catch |err| { + log.err("Error parsing CPU cores count: {}\n", .{err}); + return err; + }; +} + +pub fn getTotalMemory(allocator: std.mem.Allocator) !u64 { + // Execute the WMIC command to get total physical memory + const output = try exec(allocator, &.{ "wmic", "ComputerSystem", "get", "TotalPhysicalMemory" }); + defer allocator.free(output); + + // Tokenize the output to find the numeric value + var lines = std.mem.tokenize(u8, output, "\r\n"); + _ = lines.next(); // Skip the first line, which is the header + + // The second line contains the memory size in bytes + if (lines.next()) |line| { + // Trim spaces and parse the memory size + const memSizeStr = std.mem.trim(u8, line, " \r\n\t"); + return std.fmt.parseInt(u64, memSizeStr, 10) catch |err| { + log.err("Error parsing total memory size: {}\n", .{err}); + return err; + }; + } + + return error.CouldNotRetrieveMemorySize; +} + +fn exec(allocator: std.mem.Allocator, args: []const []const u8) ![]const u8 { + const stdout = (try std.ChildProcess.exec(.{ .allocator = allocator, .argv = args })).stdout; + + if (stdout.len == 0) return error.EmptyOutput; + + return stdout[0 .. stdout.len - 1]; +} diff --git a/util/platform.zig b/util/platform.zig new file mode 100644 index 0000000..84e0ec4 --- /dev/null +++ b/util/platform.zig @@ -0,0 +1,60 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +const format = @import("format.zig"); + +const lnx = @import("os/linux.zig"); +const mac = @import("os/osx.zig"); +const win = @import("os/windows.zig"); + +pub const OsInfo = struct { + platform: []const u8, + cpu: []const u8, + cpu_cores: u32, + memory_total: []const u8, + // ... other system information +}; + +const platform = @tagName(builtin.os.tag) ++ " " ++ @tagName(builtin.cpu.arch); + +pub fn getSystemInfo(allocator: std.mem.Allocator) !OsInfo { + return switch (builtin.os.tag) { + .linux => try linux(allocator), + .macos => try macos(allocator), + .windows => try windows(allocator), + else => error.UnsupportedOs, + }; +} + +pub fn linux(allocator: std.mem.Allocator) !OsInfo { + const memory = try lnx.getTotalMemory(); + + return OsInfo{ + .platform = platform, + .cpu = try lnx.getCpuName(allocator), + .cpu_cores = try lnx.getCpuCores(), + .memory_total = try format.memorySize(memory, allocator), + }; +} + +pub fn macos(allocator: std.mem.Allocator) !OsInfo { + const memory = try mac.getTotalMemory(allocator); + + return OsInfo{ + .platform = platform, + .cpu = try mac.getCpuName(allocator), + .cpu_cores = try mac.getCpuCores(allocator), + .memory_total = try format.memorySize(memory, allocator), + }; +} + +pub fn windows(allocator: std.mem.Allocator) !OsInfo { + const memory = try win.getTotalMemory(allocator); + + return OsInfo{ + .platform = platform, + .cpu = try win.getCpuName(allocator), + .cpu_cores = try win.getCpuCores(allocator), + .memory_total = try format.memorySize(memory, allocator), + }; +} diff --git a/zbench.zig b/zbench.zig index 19e8982..7b5cf84 100644 --- a/zbench.zig +++ b/zbench.zig @@ -3,11 +3,13 @@ //!zig-autodoc-guide: docs/advanced.md const std = @import("std"); +const builtin = @import("builtin"); const log = std.log.scoped(.zbench); const c = @import("./util/color.zig"); const format = @import("./util/format.zig"); const quicksort = @import("./util/quicksort.zig"); +const platform = @import("./util/platform.zig"); /// Configuration for benchmarking. /// This struct holds settings to control the behavior of benchmark executions. @@ -311,6 +313,20 @@ pub fn run(comptime func: BenchFunc, bench: *Benchmark, benchResult: *BenchmarkR const MAX_N = 65536; // maximum number of executions for the final benchmark run const MAX_ITERATIONS = bench.config.max_iterations; // Define a maximum number of iterations + if (bench.config.display_system_info) { + const allocator = std.heap.page_allocator; + const info = try platform.getSystemInfo(allocator); + + std.debug.print( + \\ + \\ Operating System: {s} + \\ CPU: {s} + \\ CPU Cores: {d} + \\ Total Memory: {s} + \\ + , .{ info.platform, info.cpu, info.cpu_cores, info.memory_total }); + } + if (bench.config.iterations != 0) { // If user-defined iterations are specified, use them directly bench.N = bench.config.iterations;