summaryrefslogtreecommitdiff
path: root/introspect/src/linux/runtime.zig
blob: 503d6c229e2df1d4cba43fe073dc55fe2595cb66 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
//! Generators for `Config.runtime`: /runtime/{pid,ppid,uptime,argv,cwd,env,clients}.
//! The core passes every generator `Shared.ctx`; `Fns(Ctx, field)` casts it
//! to `*Ctx` and reads the `Info` stored in `@field(ctx, field)`.
const std = @import("std");
const linux = std.os.linux;
const Writer = std.Io.Writer;

/// What the generators report. Texts are borrowed for the server's lifetime.
pub const Info = struct {
    /// argv, one argument per line.
    argv: []const u8 = "",
    /// Environment, one KEY=VALUE per line.
    env: []const u8 = "",
    cwd: []const u8 = "",
    /// Monotonic seconds at startup; /runtime/uptime is the difference.
    start_mono: i64 = 0,
    /// Live client count, published by the probe.
    clients: ?*const std.atomic.Value(u32) = null,

    pub fn now() Info {
        return .{ .start_mono = monotonicSecs() };
    }
};

pub fn monotonicSecs() i64 {
    var ts: linux.timespec = undefined;
    _ = linux.clock_gettime(.MONOTONIC, &ts);
    return ts.sec;
}

/// Seconds since the epoch, clamped to u32 (for atime/mtime and `fn/now`).
pub fn realtimeSecs() u32 {
    var ts: linux.timespec = undefined;
    _ = linux.clock_gettime(.REALTIME, &ts);
    return @intCast(std.math.clamp(ts.sec, 0, std.math.maxInt(u32)));
}

/// The `Config.runtime` type: `Ctx` is the type behind `Shared.ctx`, `field`
/// the name of its `Info` field.
pub fn Fns(comptime Ctx: type, comptime field: []const u8) type {
    return struct {
        fn info(ctx: *anyopaque) *const Info {
            const c: *Ctx = @ptrCast(@alignCast(ctx));
            return &@field(c, field);
        }
        pub fn pid(_: *anyopaque, w: *Writer) anyerror!void {
            try w.print("{d}", .{linux.getpid()});
        }
        pub fn ppid(_: *anyopaque, w: *Writer) anyerror!void {
            try w.print("{d}", .{linux.getppid()});
        }
        pub fn uptime(ctx: *anyopaque, w: *Writer) anyerror!void {
            try w.print("{d}", .{monotonicSecs() - info(ctx).start_mono});
        }
        pub fn argv(ctx: *anyopaque, w: *Writer) anyerror!void {
            try w.writeAll(info(ctx).argv);
        }
        pub fn cwd(ctx: *anyopaque, w: *Writer) anyerror!void {
            try w.writeAll(info(ctx).cwd);
        }
        pub fn env(ctx: *anyopaque, w: *Writer) anyerror!void {
            try w.writeAll(info(ctx).env);
        }
        pub fn clients(ctx: *anyopaque, w: *Writer) anyerror!void {
            const n: u32 = if (info(ctx).clients) |c| c.load(.acquire) else 0;
            try w.print("{d}", .{n});
        }
    };
}

test "runtime generators read Info through the context" {
    const Ctx = struct { x: u32, info: Info };
    var count: std.atomic.Value(u32) = .init(3);
    var ctx: Ctx = .{ .x = 0, .info = .{ .argv = "a\nb\n", .cwd = "/tmp", .env = "K=V\n", .start_mono = monotonicSecs(), .clients = &count } };
    const F = Fns(Ctx, "info");
    var buf: [64]u8 = undefined;
    var w: Writer = .fixed(&buf);
    try F.clients(&ctx, &w);
    try std.testing.expectEqualStrings("3", w.buffered());
    w = .fixed(&buf);
    try F.argv(&ctx, &w);
    try std.testing.expectEqualStrings("a\nb\n", w.buffered());
    w = .fixed(&buf);
    try F.uptime(&ctx, &w);
    try std.testing.expect(w.buffered().len >= 1);
    w = .fixed(&buf);
    try F.pid(&ctx, &w);
    try std.testing.expectEqual(linux.getpid(), try std.fmt.parseInt(i32, w.buffered(), 10));
    try std.testing.expectEqual(@as(usize, 7), @typeInfo(F).@"struct".decls.len);
}