Skip to content

Commit

Permalink
feat: display sys infos (#46)
Browse files Browse the repository at this point in the history
* 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 <f.obersteiner@posteo.de>
Co-authored-by: FObersteiner <florian.obersteiner@kit.edu>
  • Loading branch information
3 people authored Feb 23, 2024
1 parent 2cbbfa9 commit a211139
Show file tree
Hide file tree
Showing 7 changed files with 267 additions and 1 deletion.
2 changes: 1 addition & 1 deletion examples/basic.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
27 changes: 27 additions & 0 deletions util/format.zig
Original file line number Diff line number Diff line change
@@ -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" };
Expand All @@ -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 {
Expand Down
68 changes: 68 additions & 0 deletions util/os/linux.zig
Original file line number Diff line number Diff line change
@@ -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;
}
35 changes: 35 additions & 0 deletions util/os/osx.zig
Original file line number Diff line number Diff line change
@@ -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];
}
60 changes: 60 additions & 0 deletions util/os/windows.zig
Original file line number Diff line number Diff line change
@@ -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];
}
60 changes: 60 additions & 0 deletions util/platform.zig
Original file line number Diff line number Diff line change
@@ -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),
};
}
16 changes: 16 additions & 0 deletions zbench.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit a211139

Please sign in to comment.