generated from wm-packages/pika-hyprland-settings
Switch gostat with zigstat
All checks were successful
PikaOS Package Build & Release (amd64-v3) / build (push) Successful in 40s
All checks were successful
PikaOS Package Build & Release (amd64-v3) / build (push) Successful in 40s
This commit is contained in:
parent
e381297455
commit
1e056c7491
2
.github/release-nest-v3
vendored
2
.github/release-nest-v3
vendored
@ -1 +1 @@
|
||||
2
|
||||
1
|
@ -1,3 +1,9 @@
|
||||
pikabar (1.0.0-101pika18) pika; urgency=medium
|
||||
|
||||
* gostat replaced with zigstat
|
||||
|
||||
-- ferrreo <harderthanfire@gmail.com> Sat, 01 Oct 2022 14:50:00 +0300
|
||||
|
||||
pikabar (1.0.0-101pika17) pika; urgency=medium
|
||||
|
||||
* Less of a chonker
|
||||
|
Binary file not shown.
BIN
pikabar/usr/share/pikabar/programs/zigstat
Executable file
BIN
pikabar/usr/share/pikabar/programs/zigstat
Executable file
Binary file not shown.
@ -17,9 +17,9 @@ Item {
|
||||
property string memoryUsage: ""
|
||||
|
||||
Process {
|
||||
id: gostatProcess
|
||||
id: zigstatProcess
|
||||
running: true
|
||||
command: [Quickshell.shellRoot + "/programs/gostat", root.updateInterval]
|
||||
command: [Quickshell.shellRoot + "/programs/zigstat", root.updateInterval]
|
||||
stdout: SplitParser {
|
||||
onRead: function (line) {
|
||||
try {
|
||||
@ -28,7 +28,7 @@ Item {
|
||||
root.cpuTemp = data.cputemp + "°C";
|
||||
root.memoryUsage = data.mem + "G";
|
||||
} catch (e) {
|
||||
console.error("Failed to parse gostat output:", e);
|
||||
console.error("Failed to parse zigstat output:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
15
src/zigstat/build.zig
Normal file
15
src/zigstat/build.zig
Normal file
@ -0,0 +1,15 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "zigstat",
|
||||
.root_source_file = .{ .cwd_relative = "src/main.zig" },
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
b.installArtifact(exe);
|
||||
}
|
72
src/zigstat/build.zig.zon
Normal file
72
src/zigstat/build.zig.zon
Normal file
@ -0,0 +1,72 @@
|
||||
.{
|
||||
// This is the default name used by packages depending on this one. For
|
||||
// example, when a user runs `zig fetch --save <url>`, this field is used
|
||||
// as the key in the `dependencies` table. Although the user can choose a
|
||||
// different name, most users will stick with this provided value.
|
||||
//
|
||||
// It is redundant to include "zig" in this name because it is already
|
||||
// within the Zig package namespace.
|
||||
.name = "zigstat",
|
||||
|
||||
// This is a [Semantic Version](https://semver.org/).
|
||||
// In a future version of Zig it will be used for package deduplication.
|
||||
.version = "0.1.0",
|
||||
|
||||
// This field is optional.
|
||||
// This is currently advisory only; Zig does not yet do anything
|
||||
// with this value.
|
||||
//.minimum_zig_version = "0.11.0",
|
||||
|
||||
// This field is optional.
|
||||
// Each dependency must either provide a `url` and `hash`, or a `path`.
|
||||
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
|
||||
// Once all dependencies are fetched, `zig build` no longer requires
|
||||
// internet connectivity.
|
||||
.dependencies = .{
|
||||
// See `zig fetch --save <url>` for a command-line interface for adding dependencies.
|
||||
//.example = .{
|
||||
// // When updating this field to a new URL, be sure to delete the corresponding
|
||||
// // `hash`, otherwise you are communicating that you expect to find the old hash at
|
||||
// // the new URL.
|
||||
// .url = "https://example.com/foo.tar.gz",
|
||||
//
|
||||
// // This is computed from the file contents of the directory of files that is
|
||||
// // obtained after fetching `url` and applying the inclusion rules given by
|
||||
// // `paths`.
|
||||
// //
|
||||
// // This field is the source of truth; packages do not come from a `url`; they
|
||||
// // come from a `hash`. `url` is just one of many possible mirrors for how to
|
||||
// // obtain a package matching this `hash`.
|
||||
// //
|
||||
// // Uses the [multihash](https://multiformats.io/multihash/) format.
|
||||
// .hash = "...",
|
||||
//
|
||||
// // When this is provided, the package is found in a directory relative to the
|
||||
// // build root. In this case the package's hash is irrelevant and therefore not
|
||||
// // computed. This field and `url` are mutually exclusive.
|
||||
// .path = "foo",
|
||||
|
||||
// // When this is set to `true`, a package is declared to be lazily
|
||||
// // fetched. This makes the dependency only get fetched if it is
|
||||
// // actually used.
|
||||
// .lazy = false,
|
||||
//},
|
||||
},
|
||||
|
||||
// Specifies the set of files and directories that are included in this package.
|
||||
// Only files and directories listed here are included in the `hash` that
|
||||
// is computed for this package. Only files listed here will remain on disk
|
||||
// when using the zig package manager. As a rule of thumb, one should list
|
||||
// files required for compilation plus any license(s).
|
||||
// Paths are relative to the build root. Use the empty string (`""`) to refer to
|
||||
// the build root itself.
|
||||
// A directory listed here means that all files within, recursively, are included.
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
"src",
|
||||
// For example...
|
||||
//"LICENSE",
|
||||
//"README.md",
|
||||
},
|
||||
}
|
260
src/zigstat/src/main.zig
Normal file
260
src/zigstat/src/main.zig
Normal file
@ -0,0 +1,260 @@
|
||||
const std = @import("std");
|
||||
const fs = std.fs;
|
||||
const mem = std.mem;
|
||||
const time = std.time;
|
||||
const json = std.json;
|
||||
const math = std.math;
|
||||
|
||||
// Shared static buffers
|
||||
const SharedBuffers = struct {
|
||||
path_buf: [256]u8 = undefined,
|
||||
read_buf: [256]u8 = undefined,
|
||||
};
|
||||
|
||||
// Cache for temperature sensor paths
|
||||
const TempSensorCache = struct {
|
||||
initialized: bool = false,
|
||||
sensor_type: enum { none, coretemp, k10temp } = .none,
|
||||
path: [256]u8 = undefined,
|
||||
path_len: usize = 0,
|
||||
|
||||
fn init(self: *TempSensorCache, allocator: std.mem.Allocator, bufs: *SharedBuffers) !void {
|
||||
if (self.initialized) return;
|
||||
|
||||
var hwmon_dir = try fs.openDirAbsolute("/sys/class/hwmon", .{ .iterate = true });
|
||||
defer hwmon_dir.close();
|
||||
|
||||
var iter = hwmon_dir.iterate();
|
||||
while (try iter.next()) |entry| {
|
||||
const name_path = try std.fmt.bufPrint(&bufs.path_buf, "/sys/class/hwmon/{s}/name", .{entry.name});
|
||||
const name_content = fs.cwd().readFileAlloc(allocator, name_path, 64) catch continue;
|
||||
defer allocator.free(name_content);
|
||||
|
||||
const trimmed_name = mem.trim(u8, name_content, "\n");
|
||||
if (mem.eql(u8, trimmed_name, "coretemp")) {
|
||||
self.sensor_type = .coretemp;
|
||||
const path_slice = try std.fmt.bufPrint(&self.path, "/sys/class/hwmon/{s}/", .{entry.name});
|
||||
self.path_len = path_slice.len;
|
||||
break;
|
||||
} else if (mem.eql(u8, trimmed_name, "k10temp")) {
|
||||
self.sensor_type = .k10temp;
|
||||
const path_slice = try std.fmt.bufPrint(&self.path, "/sys/class/hwmon/{s}/", .{entry.name});
|
||||
self.path_len = path_slice.len;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
self.initialized = true;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer _ = gpa.deinit();
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var shared_bufs = SharedBuffers{};
|
||||
var temp_cache = TempSensorCache{};
|
||||
|
||||
// Default sleep duration of 3 seconds
|
||||
var sleep_duration: u64 = 3 * time.ns_per_s;
|
||||
|
||||
// Parse command line args for custom duration
|
||||
const args = try std.process.argsAlloc(allocator);
|
||||
defer std.process.argsFree(allocator, args);
|
||||
if (args.len > 1) {
|
||||
const duration_str = args[1];
|
||||
if (parseDuration(duration_str)) |duration| {
|
||||
sleep_duration = duration;
|
||||
} else |_| {
|
||||
std.debug.print("Invalid duration format. Using default of 3s.\n", .{});
|
||||
}
|
||||
}
|
||||
|
||||
const stdout = std.io.getStdOut().writer();
|
||||
|
||||
while (true) {
|
||||
const mem_info = try getMemoryUsage(&shared_bufs);
|
||||
const cpu_usage = try getCpuUsage(&shared_bufs);
|
||||
const cpu_temp = try getCpuTemp(allocator, &shared_bufs, &temp_cache);
|
||||
|
||||
try stdout.print("{{ \"mem\":\"{d:.1}\", \"cpu\": \"{d:.1}\", \"cputemp\": \"{d}\" }}\n", .{ mem_info, cpu_usage, cpu_temp });
|
||||
|
||||
time.sleep(sleep_duration);
|
||||
}
|
||||
}
|
||||
|
||||
fn getMemoryUsage(bufs: *SharedBuffers) !f64 {
|
||||
const file = try fs.openFileAbsolute("/proc/meminfo", .{});
|
||||
defer file.close();
|
||||
|
||||
var total: u64 = 0;
|
||||
var available: u64 = 0;
|
||||
var found_count: u8 = 0;
|
||||
|
||||
// Read the file in one go
|
||||
const bytes_read = try file.read(&bufs.read_buf);
|
||||
var start: usize = 0;
|
||||
var i: usize = 0;
|
||||
|
||||
// Manual line parsing to avoid buffered reader overhead
|
||||
while (i < bytes_read and found_count < 2) {
|
||||
if (bufs.read_buf[i] == '\n' or i == bytes_read - 1) {
|
||||
const line = bufs.read_buf[start..i];
|
||||
|
||||
if (mem.indexOf(u8, line, "MemTotal:") != null) {
|
||||
var it = mem.tokenize(u8, line, " kB");
|
||||
_ = it.next();
|
||||
total = try std.fmt.parseInt(u64, it.next() orelse "0", 10);
|
||||
found_count += 1;
|
||||
} else if (mem.indexOf(u8, line, "MemAvailable:") != null) {
|
||||
var it = mem.tokenize(u8, line, " kB");
|
||||
_ = it.next();
|
||||
available = try std.fmt.parseInt(u64, it.next() orelse "0", 10);
|
||||
found_count += 1;
|
||||
}
|
||||
|
||||
start = i + 1;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
const byte_to_giga = 1000000.0;
|
||||
return @as(f64, @floatFromInt(total - available)) / byte_to_giga;
|
||||
}
|
||||
|
||||
fn getCpuUsage(bufs: *SharedBuffers) !f64 {
|
||||
const first = try getCpuTimes(bufs);
|
||||
time.sleep(50 * time.ns_per_ms);
|
||||
const second = try getCpuTimes(bufs);
|
||||
|
||||
const idle_diff = second.idle - first.idle;
|
||||
const total_diff = second.total - first.total;
|
||||
|
||||
if (total_diff == 0) return 0;
|
||||
return (1.0 - @as(f64, @floatFromInt(idle_diff)) / @as(f64, @floatFromInt(total_diff))) * 100.0;
|
||||
}
|
||||
|
||||
const CpuTimes = struct {
|
||||
idle: u64,
|
||||
total: u64,
|
||||
};
|
||||
|
||||
fn getCpuTimes(bufs: *SharedBuffers) !CpuTimes {
|
||||
const file = try fs.openFileAbsolute("/proc/stat", .{});
|
||||
defer file.close();
|
||||
|
||||
const bytes_read = try file.read(&bufs.read_buf);
|
||||
const first_line = mem.sliceTo(bufs.read_buf[0..bytes_read], '\n');
|
||||
|
||||
if (mem.startsWith(u8, first_line, "cpu ")) {
|
||||
var it = mem.tokenize(u8, first_line, " ");
|
||||
_ = it.next();
|
||||
|
||||
var total: u64 = 0;
|
||||
var idle: u64 = 0;
|
||||
var i: u32 = 0;
|
||||
|
||||
while (it.next()) |val| {
|
||||
const num = try std.fmt.parseInt(u64, val, 10);
|
||||
if (i == 3) idle = num;
|
||||
if (i == 4) idle += num;
|
||||
total += num;
|
||||
i += 1;
|
||||
if (i >= 5) break;
|
||||
}
|
||||
|
||||
return CpuTimes{ .idle = idle, .total = total };
|
||||
}
|
||||
|
||||
return error.NoCpuLine;
|
||||
}
|
||||
|
||||
fn getCpuTemp(allocator: std.mem.Allocator, bufs: *SharedBuffers, cache: *TempSensorCache) !i32 {
|
||||
try cache.init(allocator, bufs);
|
||||
|
||||
if (cache.sensor_type == .none) return 0;
|
||||
|
||||
const path = cache.path[0..cache.path_len];
|
||||
return switch (cache.sensor_type) {
|
||||
.coretemp => try getCoreTemp(allocator, path, bufs),
|
||||
.k10temp => try getK10Temp(allocator, path, bufs),
|
||||
.none => 0,
|
||||
};
|
||||
}
|
||||
|
||||
// Update getK10Temp and getCoreTemp to use the shared buffers
|
||||
fn getK10Temp(allocator: std.mem.Allocator, hwmon_path: []const u8, bufs: *SharedBuffers) !i32 {
|
||||
var dir = try fs.openDirAbsolute(hwmon_path, .{ .iterate = true });
|
||||
defer dir.close();
|
||||
|
||||
var iter = dir.iterate();
|
||||
while (try iter.next()) |entry| {
|
||||
if (mem.startsWith(u8, entry.name, "temp") and mem.endsWith(u8, entry.name, "_label")) {
|
||||
const label_path = try std.fmt.bufPrint(&bufs.path_buf, "{s}{s}", .{ hwmon_path, entry.name });
|
||||
const label_content = fs.cwd().readFileAlloc(allocator, label_path, 64) catch continue;
|
||||
defer allocator.free(label_content);
|
||||
|
||||
if (mem.eql(u8, mem.trim(u8, label_content, "\n"), "Tctl")) {
|
||||
const label_index = mem.indexOf(u8, entry.name, "_label").?;
|
||||
const input_path = try std.fmt.bufPrint(&bufs.path_buf, "{s}{s}_input", .{ hwmon_path, entry.name[0..label_index] });
|
||||
|
||||
const temp_content = fs.cwd().readFileAlloc(allocator, input_path, 64) catch continue;
|
||||
defer allocator.free(temp_content);
|
||||
|
||||
const temp = try std.fmt.parseInt(i32, mem.trim(u8, temp_content, "\n"), 10);
|
||||
return @divTrunc(temp, 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn getCoreTemp(allocator: std.mem.Allocator, hwmon_path: []const u8, bufs: *SharedBuffers) !i32 {
|
||||
var dir = try fs.openDirAbsolute(hwmon_path, .{ .iterate = true });
|
||||
defer dir.close();
|
||||
|
||||
var total_temp: i32 = 0;
|
||||
var count: f64 = 0;
|
||||
|
||||
var iter = dir.iterate();
|
||||
while (try iter.next()) |entry| {
|
||||
if (mem.startsWith(u8, entry.name, "temp") and mem.endsWith(u8, entry.name, "_input")) {
|
||||
const temp_path = try std.fmt.bufPrint(&bufs.path_buf, "{s}{s}", .{ hwmon_path, entry.name });
|
||||
const temp_content = fs.cwd().readFileAlloc(allocator, temp_path, 64) catch continue;
|
||||
defer allocator.free(temp_content);
|
||||
|
||||
const temp = try std.fmt.parseInt(i32, mem.trim(u8, temp_content, "\n"), 10);
|
||||
total_temp += temp;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
return @divTrunc(@as(i32, @intFromFloat(@round(@as(f64, @floatFromInt(total_temp)) / count))), 1000);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Parses duration strings like "1s", "500ms", "1m", etc.
|
||||
/// Returns the duration in nanoseconds
|
||||
fn parseDuration(str: []const u8) !u64 {
|
||||
if (str.len < 2) return error.InvalidFormat;
|
||||
|
||||
const value_str = str[0 .. str.len - 1];
|
||||
const unit = str[str.len - 1];
|
||||
|
||||
const value = try std.fmt.parseInt(u64, value_str, 10);
|
||||
|
||||
return switch (unit) {
|
||||
's' => value * time.ns_per_s,
|
||||
'm' => value * time.ns_per_s * 60,
|
||||
'h' => value * time.ns_per_s * 60 * 60,
|
||||
else => if (mem.eql(u8, str[str.len - 2 ..], "ms"))
|
||||
value * time.ns_per_ms
|
||||
else
|
||||
error.InvalidUnit,
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue
Block a user