/ src / libs / os-stats.zig
os-stats.zig
  1  const std = @import("std");
  2  const utils = @import("utils");
  3  
  4  const fs = std.fs;
  5  const os = std.os;
  6  const system = os.system;
  7  const print = std.debug.print;
  8  
  9  const str = []const u8;
 10  
 11  const CPUSample = struct {
 12      /// Time spent in user mode
 13      user: u64,
 14      /// Time spent processing niced processes
 15      nice: u64,
 16      /// Time spent in system mode
 17      system: u64,
 18      /// Time spend idling
 19      idle: u64,
 20      // iowait: u64,
 21      // irq: u64,
 22      // softirq: u64,
 23      // steal: u64,
 24      // guest: u64,
 25      // guest_nice: u64,
 26  };
 27  
 28  pub const CPUInfo = struct {
 29      usage: f16, // percentage, cannot exceed 100
 30      /// CPU Archicture
 31      arch: str,
 32      /// CPU Model and speed
 33      model: str,
 34  };
 35  
 36  pub const OSInfo = struct {
 37      type: str,
 38      platform: str,
 39      version: str,
 40      release: str,
 41  };
 42  
 43  pub const RAMStat = struct {
 44      percent: u7, // shouldn't exceed 100, the smallest integer that can fit 100 is a u7
 45      free: u32, // supports up to 4TB of ram
 46      max: u32,
 47  };
 48  
 49  /// get time since boot in second
 50  pub fn getUptime() i64 {
 51      var ts: os.timespec = undefined;
 52      os.clock_gettime(os.linux.CLOCK.BOOTTIME, &ts) catch unreachable;
 53      return @as(i64, ts.tv_sec);
 54  }
 55  
 56  fn parseCPUStatFields(field_line: []u8) CPUSample {
 57      var it = std.mem.splitScalar(u8, field_line, ' ');
 58      var result: CPUSample = undefined;
 59      // TODO: Optimise using metaprogramming
 60      result.user = std.fmt.parseUnsigned(u64, it.first(), 10) catch unreachable;
 61      result.nice = std.fmt.parseUnsigned(u64, it.next().?, 10) catch unreachable;
 62      result.system = std.fmt.parseUnsigned(u64, it.next().?, 10) catch unreachable;
 63      result.idle = std.fmt.parseUnsigned(u64, it.next().?, 10) catch unreachable;
 64      // result.iowait = std.fmt.parseUnsigned(u64, it.next().?, 10) catch unreachable;
 65      // result.irq = std.fmt.parseUnsigned(u64, it.next().?, 10) catch unreachable;
 66      // result.softirq = std.fmt.parseUnsigned(u64, it.next().?, 10) catch unreachable;
 67      // result.steal = std.fmt.parseUnsigned(u64, it.next().?, 10) catch unreachable;
 68      // result.guest = std.fmt.parseUnsigned(u64, it.next().?, 10) catch unreachable;
 69      // result.guest_nice = std.fmt.parseUnsigned(u64, it.next().?, 10) catch unreachable;
 70  
 71      return result;
 72  }
 73  
 74  fn getCPUSample() ?CPUSample {
 75      // SEE: https://stackoverflow.com/questions/11356330/how-to-get-cpu-usage
 76      // SEE: https://www.baeldung.com/linux/get-cpu-usage
 77      var buf: [200]u8 = undefined;
 78  
 79      var file_descriptor = fs.openFileAbsolute("/proc/stat", .{ .intended_io_mode = .blocking }) catch return null;
 80  
 81      defer file_descriptor.close();
 82      const file = file_descriptor.reader();
 83      const line = (file.readUntilDelimiter(&buf, '\n') catch return null)[5..];
 84      const stats = parseCPUStatFields(line);
 85  
 86      // print("{s}\n{any}", .{ line, stats });
 87      return stats;
 88  }
 89  
 90  /// Query system for CPU Usage
 91  /// Returns an integer in between 0 and 100
 92  pub fn getCPUPercent(sampleTime: ?u12) ?f16 {
 93      const time_to_wait: u64 = std.time.ns_per_ms * @as(u64, @intCast((sampleTime orelse 100)));
 94      const sample1 = getCPUSample() orelse return null;
 95      std.time.sleep(time_to_wait);
 96      const sample2 = getCPUSample() orelse return null;
 97  
 98      const sample1_total_ticks = tick: {
 99          var total: u64 = 0;
100          total += sample1.user;
101          total += sample1.nice;
102          total += sample1.system;
103  
104          break :tick total;
105      };
106  
107      const sample2_total_ticks = tick: {
108          var total: u64 = 0;
109          total += sample2.user;
110          total += sample2.nice;
111          total += sample2.system;
112  
113          break :tick total;
114      };
115  
116      const total_ticks = @abs(sample2_total_ticks - sample1_total_ticks);
117      const idle_ticks = @abs(sample2.idle - sample1.idle);
118  
119      const percent = @as(f128, @floatFromInt(total_ticks)) / (@as(f128, @floatFromInt(idle_ticks)) + (@as(f128, @floatFromInt(total_ticks)))) * 100;
120  
121      return @floatCast(percent);
122  }
123  
124  /// Query RAM Information such as
125  /// - available memory
126  /// - available total
127  /// - RAM usage as percentage
128  pub fn getRAMStats() ?RAMStat {
129      var buff: [50]u8 = undefined;
130      var buf2: [50]u8 = undefined;
131  
132      const mem_total_str = utils.parseKVPairOpenFile("/proc/meminfo", "MemTotal", &buff, ':') catch null;
133      const mem_available_str = utils.parseKVPairOpenFile("/proc/meminfo", "MemAvailable", &buf2, ':') catch null;
134  
135      if (mem_total_str == null or mem_available_str == null) return null;
136  
137      const mem_total = std.fmt.parseUnsigned(u26, mem_total_str.?[0 .. mem_total_str.?.len - 3], 10) catch return null;
138      const mem_available = std.fmt.parseUnsigned(u26, mem_available_str.?[0 .. mem_available_str.?.len - 3], 10) catch return null;
139      const percent: u7 = @intFromFloat(@floor((@as(f32, @floatFromInt(mem_available)) / @as(f32, @floatFromInt(mem_total)) * 100)));
140  
141      return .{
142          .percent = percent,
143          .free = mem_available,
144          .max = mem_total,
145      };
146  }