Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,6 @@ sample*
.rewrite/
.venv/
config.test.json

# local secrets-bearing test config (do not commit)
anotherconfig.test.json
46 changes: 46 additions & 0 deletions src/frontend/exec.zig
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,52 @@ pub fn policiesActiveFor(registry: *policy.Registry, signal: service_mod.Signal)
};
}

/// Mirror the active snapshot's per-signal policy counts into the gauge. Called
/// at scrape time so the gauge always reflects the live snapshot without hooking
/// the loader's reload path. Counts match `policiesActiveFor` (the fast-path
/// gate), so a 0 here is exactly when that signal is raw-forwarded untouched.
pub fn refreshPolicyGauge(ctx: *SharedCtx) void {
const metrics = ctx.metrics orelse return;
const snapshot = ctx.registry.getSnapshot();
metrics.setPoliciesLoaded(.log, if (snapshot) |s| @intCast(s.getLogTargetIndices().len) else 0);
metrics.setPoliciesLoaded(.metric, if (snapshot) |s| @intCast(s.getMetricTargetIndices().len) else 0);
metrics.setPoliciesLoaded(.trace, if (snapshot) |s| @intCast(s.trace_target_indices.len) else 0);
}

/// Dump the loaded policies in the active snapshot, for the `/_edge/policies`
/// debug endpoint. `json=true` emits the full policy tree (match rules, keep,
/// transform — via the proto type's own jsonStringify); otherwise a one-line
/// text summary per policy (id, signal, enabled, name).
pub fn writePolicies(registry: *policy.Registry, w: *std.Io.Writer, json: bool) !void {
const snapshot = registry.getSnapshot();
if (json) {
// std.json's default struct serializer over the proto types. Repeated
// fields surface as {"items":[...],"capacity":N} (raw ArrayList) — fine
// for a debug dump; the policy data is all there.
try w.print("{{\"version\":{d},\"policies\":", .{if (snapshot) |s| s.version else 0});
try std.json.Stringify.value(if (snapshot) |s| s.policies else &.{}, .{}, w);
try w.writeAll("}\n");
return;
}
const s = snapshot orelse {
try w.writeAll("# no policy snapshot loaded (0 policies)\n");
return;
};
try w.print("# snapshot version={d} policies={d} (log={d} metric={d} trace={d})\n", .{
s.version,
s.policies.len,
s.getLogTargetIndices().len,
s.getMetricTargetIndices().len,
s.trace_target_indices.len,
});
for (s.policies) |*p| {
const signal = if (p.target) |t| @tagName(std.meta.activeTag(t)) else "none";
try w.print("id={s} signal={s} enabled={} name={s}\n", .{
p.id, signal, p.enabled, p.name,
});
}
}

pub const BufferedResult = struct {
body: []const u8,
all_dropped: bool,
Expand Down
10 changes: 10 additions & 0 deletions src/frontend/httpz/server.zig
Original file line number Diff line number Diff line change
Expand Up @@ -246,10 +246,20 @@ pub const Handler = struct {
// with the stdio driver's /_edge/metrics short-circuit.
if (req.method == .GET and std.mem.eql(u8, path, "/_edge/metrics")) {
res.header("content-type", "text/plain; version=0.0.4");
exec.refreshPolicyGauge(ctx);
if (ctx.metrics) |metrics| try metrics.writePrometheus(res.writer());
return;
}

// Dump the loaded policy snapshot (id/signal/enabled/name). Pairs with
// the gauge: shows *which* policies are active, not just how many.
if (req.method == .GET and std.mem.eql(u8, path, "/_edge/policies")) {
const json = std.mem.eql(u8, (try req.query()).get("format") orelse "", "json");
res.header("content-type", if (json) "application/json" else "text/plain; charset=utf-8");
try exec.writePolicies(ctx.registry, res.writer(), json);
return;
}

// Debug tap (config-gated): block this request up to 1s while data-plane
// threads stream the next N records into our buffer, before or after
// policy evaluation. ctx.tap is null unless enabled in config.
Expand Down
24 changes: 24 additions & 0 deletions src/runtime/runtime_metrics.zig
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ const PolicyLabels = struct {
telemetry: PolicyTelemetryLabel,
};

pub const SignalLabel = enum { log, metric, trace };

const PolicySignalLabels = struct {
signal: SignalLabel,
};

const BuildInfoLabels = struct {
version: []const u8,
commit: []const u8,
Expand All @@ -112,6 +118,7 @@ const InternalMetrics = struct {
edge_policy_records_evaluated_total: PolicyRecordsEvaluatedTotal,
edge_policy_records_kept_total: PolicyRecordsKeptTotal,
edge_policy_records_dropped_total: PolicyRecordsDroppedTotal,
edge_policies_loaded: PoliciesLoaded,
edge_build_info: BuildInfo,

const RequestsTotal = m.CounterVec(u64, RequestLabels);
Expand All @@ -126,6 +133,7 @@ const InternalMetrics = struct {
const PolicyRecordsEvaluatedTotal = m.CounterVec(u64, PolicyLabels);
const PolicyRecordsKeptTotal = m.CounterVec(u64, PolicyLabels);
const PolicyRecordsDroppedTotal = m.CounterVec(u64, PolicyLabels);
const PoliciesLoaded = m.GaugeVec(u64, PolicySignalLabels);
const BuildInfo = m.GaugeVec(u64, BuildInfoLabels);
};

Expand Down Expand Up @@ -195,6 +203,13 @@ pub const RuntimeMetrics = struct {
.{ .help = "Total number of telemetry records dropped after policy evaluation." },
.{},
),
.edge_policies_loaded = try InternalMetrics.PoliciesLoaded.init(
allocator,
io,
"edge_policies_loaded",
.{ .help = "Number of loaded policies targeting each signal in the active snapshot." },
.{},
),
.edge_build_info = try InternalMetrics.BuildInfo.init(
allocator,
io,
Expand Down Expand Up @@ -247,6 +262,10 @@ pub const RuntimeMetrics = struct {
try self.internal.edge_policy_records_kept_total.incrBy(.{ .telemetry = telemetry }, 0);
try self.internal.edge_policy_records_dropped_total.incrBy(.{ .telemetry = telemetry }, 0);
}

inline for (std.meta.tags(SignalLabel)) |signal| {
try self.internal.edge_policies_loaded.set(.{ .signal = signal }, 0);
}
}

pub fn deinit(self: *RuntimeMetrics) void {
Expand Down Expand Up @@ -335,6 +354,11 @@ pub const RuntimeMetrics = struct {
}, dropped_count) catch |err| log.debug("failed to record policy dropped metric: {}", .{err});
}

pub fn setPoliciesLoaded(self: *RuntimeMetrics, signal: SignalLabel, count: u64) void {
self.internal.edge_policies_loaded.set(.{ .signal = signal }, count) catch |err|
log.warn("failed to set policies loaded metric: {}", .{err});
}

pub fn setBuildInfo(self: *RuntimeMetrics, version: []const u8, commit: []const u8) void {
self.internal.edge_build_info.set(.{
.version = version,
Expand Down
Loading