Bump version to 0.3.0, enhance disk and GPU info retrieval with caching support, and streamline package count management
All checks were successful
PikaOS Package Build & Release (amd64-v3) / build (push) Successful in 47s

This commit is contained in:
ferreo 2024-12-01 18:15:24 +00:00
parent 829d085d1f
commit dd8f231c51
7 changed files with 264 additions and 30 deletions

View File

@ -1 +1 @@
2
1

View File

@ -6,7 +6,7 @@ set -e
echo "$PIKA_BUILD_ARCH" > pika-build-arch
VERSION="0.2.0"
VERSION="0.3.0"
cd ./pikafetch/

View File

@ -1,3 +1,10 @@
pikafetch (0.3.0-101pika1) pika; urgency=medium
* Multiple disk support, add caching of GPU and packages info
-- ferreo <harderthanfire@gmail.com> Wed, 18 Jan 2023 21:48:14 +0000
pikafetch (0.2.0-101pika3) pika; urgency=medium
* Move to zig nightly, remove all use of libc, switch to using arena allocator

View File

@ -1,11 +1,68 @@
const std = @import("std");
const os = std.os;
const fs = std.fs;
const mem = std.mem;
const ArrayList = std.ArrayList;
pub const DiskInfo = struct {
mount_point: []const u8,
total: u64,
used: u64,
};
// Comptime array of physical filesystem types
const physical_fs = [_][]const u8{
"ext4",
"btrfs",
"xfs",
"ext3",
"ext2",
"f2fs",
"zfs",
"bcachefs",
"vfat",
};
fn isPhysicalFS(fs_type: []const u8) bool {
inline for (physical_fs) |pfs| {
if (mem.eql(u8, fs_type, pfs)) return true;
}
return false;
}
pub fn getDisksInfo(allocator: mem.Allocator) ![]DiskInfo {
var result = try ArrayList(DiskInfo).initCapacity(allocator, 8);
const file = try fs.openFileAbsolute("/proc/mounts", .{});
defer file.close();
var buf_reader = std.io.bufferedReader(file.reader());
var reader = buf_reader.reader();
var buf: [1024]u8 = undefined;
while (try reader.readUntilDelimiterOrEof(&buf, '\n')) |line| {
var iter = mem.splitScalar(u8, line, ' ');
const device = iter.next() orelse continue;
const mount_point = iter.next() orelse continue;
const fs_type = iter.next() orelse continue;
if (device.len == 0) continue;
if (device[0] != '/') continue;
if (!isPhysicalFS(fs_type)) continue;
const info = getDiskInfo(mount_point) catch continue;
if (info.total < 1024 * 1024 * 1024) continue;
try result.append(DiskInfo{
.mount_point = try allocator.dupe(u8, mount_point),
.total = info.total,
.used = info.used,
});
}
return try result.toOwnedSlice();
}
pub fn getDiskInfo(mount_point: []const u8) !DiskInfo {
const Statfs = extern struct {
f_type: u64,
@ -23,7 +80,10 @@ pub fn getDiskInfo(mount_point: []const u8) !DiskInfo {
};
var stats: Statfs = undefined;
const rc = os.linux.syscall2(.statfs, @intFromPtr(mount_point.ptr), @intFromPtr(&stats));
var mount_path_buf: [std.fs.max_path_bytes + 1]u8 = undefined;
const path = try std.fmt.bufPrint(&mount_path_buf, "{s}\x00", .{mount_point});
const rc = os.linux.syscall2(.statfs, @intFromPtr(path.ptr), @intFromPtr(&stats));
if (rc != 0) return error.StatFailed;
const total = stats.f_blocks * stats.f_bsize;
@ -31,6 +91,7 @@ pub fn getDiskInfo(mount_point: []const u8) !DiskInfo {
const used = total - free;
return DiskInfo{
.mount_point = mount_point,
.total = total,
.used = used,
};

View File

@ -38,6 +38,25 @@ const GPUInfo = struct {
};
pub fn getGPUInfo(allocator: mem.Allocator) ![][]const u8 {
const home = try std.process.getEnvVarOwned(allocator, "HOME");
defer allocator.free(home);
const cache_dir = try fs.path.join(allocator, &.{ home, ".config", "pikafetch" });
defer allocator.free(cache_dir);
fs.makeDirAbsolute(cache_dir) catch |err| {
if (err != error.PathAlreadyExists) return err;
};
const cache_path = try fs.path.join(allocator, &.{ cache_dir, "gpu_cache" });
defer allocator.free(cache_path);
if (try isCacheValid(cache_path)) {
if (try readFromCache(allocator, cache_path)) |gpus| {
return gpus;
}
}
var gpus = ArrayList([]const u8).init(allocator);
var dir = fs.openDirAbsolute("/sys/class/drm", .{ .iterate = true }) catch {
@ -62,7 +81,9 @@ pub fn getGPUInfo(allocator: mem.Allocator) ![][]const u8 {
try gpus.append(unknown);
}
return gpus.toOwnedSlice();
const result = try gpus.toOwnedSlice();
try updateCache(cache_path, result);
return result;
}
fn detectGPU(allocator: mem.Allocator, card_name: []const u8) !?[]const u8 {
@ -245,3 +266,67 @@ fn queryHwdb(allocator: mem.Allocator, card_name: []const u8) !?[]const u8 {
return null;
}
fn getBootTime() !i64 {
const file = try fs.openFileAbsolute("/proc/stat", .{});
defer file.close();
var buffer: [4096]u8 = undefined;
const bytes_read = try file.readAll(&buffer);
const content = buffer[0..bytes_read];
var lines = std.mem.splitScalar(u8, content, '\n');
while (lines.next()) |line| {
if (std.mem.startsWith(u8, line, "btime ")) {
const time_str = std.mem.trim(u8, line["btime ".len..], " \t\r\n");
return try std.fmt.parseInt(i64, time_str, 10);
}
}
return error.BootTimeNotFound;
}
fn isCacheValid(cache_path: []const u8) !bool {
const cache_file = fs.openFileAbsolute(cache_path, .{}) catch return false;
defer cache_file.close();
const cache_stat = try cache_file.stat();
const boot_time = try getBootTime();
const boot_time_ns = boot_time * std.time.ns_per_s;
return cache_stat.mtime >= boot_time_ns;
}
fn readFromCache(allocator: mem.Allocator, cache_path: []const u8) !?[][]const u8 {
const file = try fs.openFileAbsolute(cache_path, .{});
defer file.close();
const stat = try file.stat();
const content = try allocator.alloc(u8, @intCast(stat.size));
defer allocator.free(content);
const bytes_read = try file.readAll(content);
const file_content = content[0..bytes_read];
var gpus = ArrayList([]const u8).init(allocator);
var lines = std.mem.splitScalar(u8, file_content, '\n');
while (lines.next()) |line| {
const trimmed = std.mem.trim(u8, line, " \t\r\n");
if (trimmed.len == 0) continue;
try gpus.append(try allocator.dupe(u8, trimmed));
}
const result = try gpus.toOwnedSlice();
if (result.len == 0) return null;
return result;
}
fn updateCache(cache_path: []const u8, gpus: []const []const u8) !void {
const file = try fs.createFileAbsolute(cache_path, .{});
defer file.close();
for (gpus) |gpu| {
try file.writer().print("{s}\n", .{gpu});
}
}

View File

@ -27,8 +27,7 @@ pub const SystemInfo = struct {
gpus: [][]const u8,
swap_total: u64,
swap_used: u64,
disk_total: u64,
disk_used: u64,
disks: []disk.DiskInfo,
shell_name: []const u8,
cpu_info: []const u8,
@ -58,14 +57,14 @@ pub const SystemInfo = struct {
shell_info = shell_data.name;
cpu_info = try cpu.getCPUInfo(allocator);
const swap_info = try memory.getSwapInfo();
const disk_info = try disk.getDiskInfo("/");
const disk_info = try disk.getDisksInfo(allocator);
return SystemInfo{
.username = username.?,
.hostname = hostname_str.?,
.os_name = os_name.?,
.kernel = kernel_ver.?,
.uptime = 0, // TODO: Implement uptime
.uptime = 0,
.memory_total = mem_info.total,
.memory_used = mem_info.used,
.host = host.?,
@ -75,41 +74,46 @@ pub const SystemInfo = struct {
.gpus = gpu_list.?,
.swap_total = swap_info.total,
.swap_used = swap_info.used,
.disk_total = disk_info.total,
.disk_used = disk_info.used,
.disks = disk_info,
.shell_name = shell_info.?,
.cpu_info = cpu_info.?,
};
}
pub fn formatInfo(self: *const SystemInfo, output_allocator: std.mem.Allocator) ![]const []const u8 {
var info = try std.ArrayList([]const u8).initCapacity(output_allocator, 13 + self.gpus.len);
var info = try std.ArrayList([]const u8).initCapacity(output_allocator, 13 + self.gpus.len + self.disks.len);
const calculations = struct {
mem_percentage: u64,
swap_percentage: u64,
disk_percentage: u64,
disk_percentages: []const u64,
mem_fmt: memory.SizeInfo,
mem_total_fmt: memory.SizeInfo,
swap_fmt: memory.SizeInfo,
swap_total_fmt: memory.SizeInfo,
disk_fmt: memory.SizeInfo,
disk_total_fmt: memory.SizeInfo,
usage_colors: [3][]const u8,
usage_colors: []const []const u8,
}{
.mem_percentage = @divFloor(self.memory_used * 100, self.memory_total),
.swap_percentage = if (self.swap_total > 0) @divFloor(self.swap_used * 100, self.swap_total) else 0,
.disk_percentage = if (self.disk_total > 0) @divFloor(self.disk_used * 100, self.disk_total) else 0,
.disk_percentages = blk: {
var percentages = try output_allocator.alloc(u64, self.disks.len);
for (self.disks, 0..) |disk_info, i| {
percentages[i] = @divFloor(disk_info.used * 100, disk_info.total);
}
break :blk percentages;
},
.mem_fmt = memory.formatSize(self.memory_used),
.mem_total_fmt = memory.formatSize(self.memory_total),
.swap_fmt = memory.formatSize(self.swap_used),
.swap_total_fmt = memory.formatSize(self.swap_total),
.disk_fmt = memory.formatSize(self.disk_used),
.disk_total_fmt = memory.formatSize(self.disk_total),
.usage_colors = .{
getUsageColor(@divFloor(self.memory_used * 100, self.memory_total)),
getUsageColor(if (self.swap_total > 0) @divFloor(self.swap_used * 100, self.swap_total) else 0),
getUsageColor(if (self.disk_total > 0) @divFloor(self.disk_used * 100, self.disk_total) else 0),
.usage_colors = blk: {
var colors_array = try output_allocator.alloc([]const u8, 2 + self.disks.len);
colors_array[0] = getUsageColor(@divFloor(self.memory_used * 100, self.memory_total));
colors_array[1] = getUsageColor(if (self.swap_total > 0) @divFloor(self.swap_used * 100, self.swap_total) else 0);
for (self.disks, 0..) |disk_info, i| {
colors_array[2 + i] = getUsageColor(@divFloor(disk_info.used * 100, disk_info.total));
}
break :blk colors_array;
},
};
@ -135,6 +139,23 @@ pub const SystemInfo = struct {
}));
}
for (self.disks, 0..) |disk_info, i| {
const disk_fmt = memory.formatSize(disk_info.used);
const disk_total_fmt = memory.formatSize(disk_info.total);
try info.append(try std.fmt.allocPrint(output_allocator, "{s}Disk {s}:{s} {d:.2} {s} / {d:.2} {s} ({s}{d}%{s})", .{
colors.Color.bold,
disk_info.mount_point,
colors.Color.reset,
disk_fmt.value,
disk_fmt.unit,
disk_total_fmt.value,
disk_total_fmt.unit,
calculations.usage_colors[2 + i],
calculations.disk_percentages[i],
colors.Color.reset,
}));
}
try info.appendSlice(&[_][]const u8{
try std.fmt.allocPrint(output_allocator, "{s}Memory:{s} {d:.2} {s} / {d:.2} {s} ({s}{d}%{s})", .{
colors.Color.bold, colors.Color.reset,
@ -150,13 +171,6 @@ pub const SystemInfo = struct {
calculations.usage_colors[1], calculations.swap_percentage,
colors.Color.reset,
}),
try std.fmt.allocPrint(output_allocator, "{s}Disk (/):{s} {d:.2} {s} / {d:.2} {s} ({s}{d}%{s})", .{
colors.Color.bold, colors.Color.reset,
calculations.disk_fmt.value, calculations.disk_fmt.unit,
calculations.disk_total_fmt.value, calculations.disk_total_fmt.unit,
calculations.usage_colors[2], calculations.disk_percentage,
colors.Color.reset,
}),
try std.fmt.allocPrint(output_allocator, "", .{}),
try std.fmt.allocPrint(output_allocator, "\x1b[30m███\x1b[31m███\x1b[32m███\x1b[33m███\x1b[34m███\x1b[35m███\x1b[36m███\x1b[37m███{s}", .{
colors.Color.reset,

View File

@ -1,10 +1,77 @@
const std = @import("std");
const os = std.os;
const fs = std.fs;
const time = std.time;
pub fn getDpkgCount() !u32 {
const dpkg_path = "/var/lib/dpkg/status";
// Get cache directory
const allocator = std.heap.page_allocator;
const home = try std.process.getEnvVarOwned(allocator, "HOME");
defer allocator.free(home);
const cache_dir = try fs.path.join(allocator, &.{ home, ".config", "pikafetch" });
defer allocator.free(cache_dir);
// Ensure cache directory exists, ignore if it already does
fs.makeDirAbsolute(cache_dir) catch |err| {
if (err != error.PathAlreadyExists) return err;
};
const cache_path = try fs.path.join(allocator, &.{ cache_dir, "dpkg_cache" });
defer allocator.free(cache_path);
// Check if cache exists and is valid
if (try isCacheValid(dpkg_path, cache_path)) {
return try readFromCache(cache_path);
}
// If cache is invalid or doesn't exist, count packages and update cache
const count = try countPackages(dpkg_path);
try updateCache(cache_path, count);
return count;
}
fn isCacheValid(dpkg_path: []const u8, cache_path: []const u8) !bool {
// Check if both files exist
const dpkg_file = fs.openFileAbsolute(dpkg_path, .{}) catch return false;
defer dpkg_file.close();
const cache_file = fs.openFileAbsolute(cache_path, .{}) catch return false;
defer cache_file.close();
// Compare modification times
const dpkg_stat = try dpkg_file.stat();
const cache_stat = try cache_file.stat();
return cache_stat.mtime >= dpkg_stat.mtime;
}
fn readFromCache(cache_path: []const u8) !u32 {
const file = try fs.openFileAbsolute(cache_path, .{});
defer file.close();
var buffer: [128]u8 = undefined;
const bytes_read = try file.readAll(&buffer);
// Parse the cached count
const content = buffer[0..bytes_read];
const count_str = std.mem.trim(u8, content, &std.ascii.whitespace);
return try std.fmt.parseInt(u32, count_str, 10);
}
fn updateCache(cache_path: []const u8, count: u32) !void {
const file = try fs.createFileAbsolute(cache_path, .{});
defer file.close();
try std.fmt.format(file.writer(), "{d}", .{count});
}
fn countPackages(dpkg_path: []const u8) !u32 {
@setEvalBranchQuota(20000);
const file = try std.fs.openFileAbsolute("/var/lib/dpkg/status", .{});
const file = try fs.openFileAbsolute(dpkg_path, .{});
defer file.close();
const stat = try file.stat();