const std = @import("std");
const http_api = @import("api.zig");
const router = @import("router.zig");
const telemetry = @import("../internal/telemetry.zig");
const http = std.http;
const Row = struct {
stats: telemetry.EndpointStats,
path: []const u8,
group: []const u8,
};
const TrafficSummary = struct {
requests: u64 = 0,
failures: u64 = 0,
writes: u64 = 0,
proxy_count: u64 = 0,
proxy_p95_ms: f64 = 0,
};
const TROUBLE_US = 1000 * std.time.us_per_ms;
const TROUBLE_LIMIT = 12;
pub fn serve(request: *http_api.Request) !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const body = try render(arena.allocator());
try http_api.respond(request, .ok, body, &html_headers);
}
fn render(allocator: std.mem.Allocator) ![]const u8 {
const snap = telemetry.snapshot();
const rows = try collectRows(allocator, snap);
sortRows(rows);
const service_rows = try filterServiceRows(allocator, rows);
const summary = summarizeTraffic(service_rows);
const appview_rows = try renderRouteRows(allocator, try filterAppviewRows(allocator, service_rows), "no appview proxy samples yet.");
const pds_rows = try renderRouteRows(allocator, try filterPdsRows(allocator, service_rows), "no local PDS samples yet.");
const write_rows = try renderWriteRows(allocator, try filterWrites(allocator, rows));
const trouble_rows = try renderTroubleRows(allocator, snap);
const uptime = try formatDuration(allocator, snap.uptime_s);
const failure_rate = if (summary.requests == 0)
"0%"
else
try std.fmt.allocPrint(allocator, "{d:.1}%", .{@as(f64, @floatFromInt(summary.failures)) * 100.0 / @as(f64, @floatFromInt(summary.requests))});
const proxy_p95 = try formatMaybeMs(allocator, summary.proxy_count, summary.proxy_p95_ms);
return std.fmt.allocPrint(allocator,
\\
\\
\\
\\
\\
\\ zds / health
\\
\\
\\
\\
\\
\\ pds health
\\ Live health for this PDS. Counts reset when the service restarts; this page does not count its own refreshes.
\\
\\
\\ needs attention
newest first; only 1s+ requests or server failures
\\ {s}
\\
\\
\\ writes
likes usually createRecord; batches use applyWrites
\\ {s}
\\
\\
\\ appview proxy
client reads forwarded to Bluesky/chat appviews
\\ {s}
\\
\\
\\ pds routes
local repository, sync, auth, and preference routes
\\ {s}
\\
\\
\\
\\
, .{
uptime,
summary.requests,
summary.writes,
proxy_p95,
if (summary.failures == 0) "good" else "bad",
summary.failures,
failure_rate,
trouble_rows,
write_rows,
appview_rows,
pds_rows,
});
}
fn collectRows(allocator: std.mem.Allocator, snap: telemetry.Snapshot) ![]Row {
var rows: std.ArrayList(Row) = .empty;
for (0..@typeInfo(router.Route).@"enum".fields.len) |route_idx| {
for (0..@typeInfo(telemetry.Method).@"enum".fields.len) |method_idx| {
const stats = snap.routes[route_idx][method_idx];
if (stats.count == 0) continue;
const meta = endpointMeta(stats.route, stats.method);
try rows.append(allocator, .{ .stats = stats, .path = meta.path, .group = meta.group });
}
}
return rows.toOwnedSlice(allocator);
}
fn renderRows(allocator: std.mem.Allocator, rows: []const Row) ![]const u8 {
if (rows.len == 0) return allocator.dupe(u8, "no samples yet.
");
var out: std.Io.Writer.Allocating = .init(allocator);
defer out.deinit();
try out.writer.writeAll("| method | route | group | count | p50 | p95 | avg | max | fail |
");
for (rows) |row| {
try out.writer.print(
"| {s} | {s} | {s} | {d} | {d:.1}ms | {d:.1}ms | {d:.1}ms | {d:.1}ms | {d} |
",
.{
@tagName(row.stats.method),
row.stats.method.label(),
row.path,
row.group,
row.stats.count,
row.stats.p50_ms,
row.stats.p95_ms,
row.stats.avg_ms,
row.stats.max_ms,
row.stats.failure_count,
},
);
}
try out.writer.writeAll("
");
return out.toOwnedSlice();
}
fn renderRouteRows(allocator: std.mem.Allocator, rows: []const Row, empty: []const u8) ![]const u8 {
if (rows.len == 0) return std.fmt.allocPrint(allocator, "{s}
", .{empty});
var out: std.Io.Writer.Allocating = .init(allocator);
defer out.deinit();
try out.writer.writeAll("| method | route | count | p50 | p95 | max | fail |
");
for (rows) |row| {
try out.writer.print(
"| {s} | {s} | {d} | {d:.1}ms | {d:.1}ms | {d:.1}ms | {d} |
",
.{
@tagName(row.stats.method),
row.stats.method.label(),
row.path,
row.stats.count,
row.stats.p50_ms,
row.stats.p95_ms,
row.stats.max_ms,
row.stats.failure_count,
},
);
}
try out.writer.writeAll("
");
return out.toOwnedSlice();
}
fn renderWriteRows(allocator: std.mem.Allocator, rows: []const Row) ![]const u8 {
if (rows.len == 0) return allocator.dupe(u8, "no write samples yet.
");
var out: std.Io.Writer.Allocating = .init(allocator);
defer out.deinit();
try out.writer.writeAll("| kind | endpoint | count | p50 | p95 | max | fail |
");
for (rows) |row| {
try out.writer.print(
"| {s} | {s} | {d} | {d:.1}ms | {d:.1}ms | {d:.1}ms | {d} |
",
.{
writeKind(row.stats.route),
row.path,
row.stats.count,
row.stats.p50_ms,
row.stats.p95_ms,
row.stats.max_ms,
row.stats.failure_count,
},
);
}
try out.writer.writeAll("
");
return out.toOwnedSlice();
}
fn renderTroubleRows(allocator: std.mem.Allocator, snap: telemetry.Snapshot) ![]const u8 {
var out: std.Io.Writer.Allocating = .init(allocator);
defer out.deinit();
try out.writer.writeAll("| when | method | class | route | elapsed | status |
");
var i = snap.slow_len;
var rendered: usize = 0;
while (i > 0) {
i -= 1;
const item = snap.slow_requests[i];
if (!needsAttention(item)) continue;
try out.writer.print(
"| {d}s ago | {s} | {s} | {s} | {d:.1}ms | {d} |
",
.{
if (item.at_s > 0) @max(0, snap.started_at_s + @as(i64, @intCast(snap.uptime_s)) - item.at_s) else 0,
@tagName(item.method),
item.method.label(),
item.class.label(),
item.labelText(),
@as(f64, @floatFromInt(item.elapsed_us)) / 1000.0,
statusClass(item),
item.status,
},
);
rendered += 1;
if (rendered == TROUBLE_LIMIT) break;
}
if (rendered == 0) return allocator.dupe(u8, "nothing needs attention right now.
");
try out.writer.writeAll("
");
return out.toOwnedSlice();
}
fn needsAttention(item: telemetry.SlowRequest) bool {
return item.failed() or item.elapsed_us >= TROUBLE_US;
}
fn statusClass(item: telemetry.SlowRequest) []const u8 {
if (item.failed()) return "bad";
if (item.status >= 400) return "group";
return "good";
}
fn filterWrites(allocator: std.mem.Allocator, rows: []const Row) ![]Row {
var filtered: std.ArrayList(Row) = .empty;
for (rows) |row| {
if (isWriteRoute(row.stats.route, row.stats.method)) try filtered.append(allocator, row);
}
return filtered.toOwnedSlice(allocator);
}
fn filterServiceRows(allocator: std.mem.Allocator, rows: []const Row) ![]Row {
var filtered: std.ArrayList(Row) = .empty;
for (rows) |row| {
if (!isNoiseRoute(row.stats.route)) try filtered.append(allocator, row);
}
return filtered.toOwnedSlice(allocator);
}
fn filterAppviewRows(allocator: std.mem.Allocator, rows: []const Row) ![]Row {
var filtered: std.ArrayList(Row) = .empty;
for (rows) |row| {
if (row.stats.route == .proxy_xrpc) try filtered.append(allocator, row);
}
return filtered.toOwnedSlice(allocator);
}
fn filterPdsRows(allocator: std.mem.Allocator, rows: []const Row) ![]Row {
var filtered: std.ArrayList(Row) = .empty;
for (rows) |row| {
if (row.stats.route != .proxy_xrpc and !isWriteRoute(row.stats.route, row.stats.method)) {
try filtered.append(allocator, row);
}
}
return filtered.toOwnedSlice(allocator);
}
fn summarizeTraffic(rows: []const Row) TrafficSummary {
var summary: TrafficSummary = .{};
for (rows) |row| {
summary.requests += row.stats.count;
summary.failures += row.stats.failure_count;
if (isWriteRoute(row.stats.route, row.stats.method)) summary.writes += row.stats.count;
if (row.stats.route == .proxy_xrpc and row.stats.method == .get) {
summary.proxy_count += row.stats.count;
summary.proxy_p95_ms = @max(summary.proxy_p95_ms, row.stats.p95_ms);
}
}
return summary;
}
fn sortRows(rows: []Row) void {
std.mem.sort(Row, rows, {}, struct {
fn lessThan(_: void, a: Row, b: Row) bool {
if (a.stats.p95_ms != b.stats.p95_ms) return a.stats.p95_ms > b.stats.p95_ms;
if (a.stats.max_ms != b.stats.max_ms) return a.stats.max_ms > b.stats.max_ms;
return a.stats.count > b.stats.count;
}
}.lessThan);
}
fn isNoiseRoute(route: router.Route) bool {
return switch (route) {
.stats_page,
.health,
.root,
.api_docs,
.api_openapi,
.favicon,
.og_image,
.cors_preflight,
.not_found,
=> true,
else => false,
};
}
fn isWriteRoute(route: router.Route, method: telemetry.Method) bool {
if (method != .post) return false;
return switch (route) {
.app_preferences_put,
.repo_create_record,
.repo_put_record,
.repo_delete_record,
.repo_apply_writes,
.repo_import_repo,
.repo_upload_blob,
.permissioned_data,
=> true,
else => false,
};
}
fn writeKind(route: router.Route) []const u8 {
return switch (route) {
.repo_create_record => "single record create",
.repo_put_record => "replace by rkey",
.repo_delete_record => "record delete",
.repo_apply_writes => "batched repo writes",
.repo_import_repo => "repo import",
.repo_upload_blob => "blob upload",
.app_preferences_put => "preferences update",
.permissioned_data => "permissioned-data write",
else => "write",
};
}
const EndpointMeta = struct { path: []const u8, group: []const u8 };
fn endpointMeta(route: router.Route, method: telemetry.Method) EndpointMeta {
if (route == .proxy_xrpc) return .{ .path = "proxied XRPC", .group = "proxy" };
if (route == .not_found) return .{ .path = "unmatched request", .group = "unknown" };
for (router.endpoints) |endpoint| {
if (endpoint.route == route and std.mem.eql(u8, endpoint.method, method.label())) return .{ .path = endpoint.path, .group = endpoint.group };
}
for (router.endpoints) |endpoint| {
if (endpoint.route == route) return .{ .path = endpoint.path, .group = endpoint.group };
}
return .{ .path = @tagName(route), .group = "internal" };
}
fn formatDuration(allocator: std.mem.Allocator, seconds: u64) ![]const u8 {
const days = seconds / 86_400;
const hours = (seconds % 86_400) / 3_600;
const minutes = (seconds % 3_600) / 60;
if (days > 0) return std.fmt.allocPrint(allocator, "{d}d {d}h", .{ days, hours });
if (hours > 0) return std.fmt.allocPrint(allocator, "{d}h {d}m", .{ hours, minutes });
return std.fmt.allocPrint(allocator, "{d}m", .{minutes});
}
fn formatMaybeMs(allocator: std.mem.Allocator, count: u64, ms: f64) ![]const u8 {
if (count == 0) return allocator.dupe(u8, "none");
return std.fmt.allocPrint(allocator, "{d:.1}ms", .{ms});
}
const html_headers = [_]http.Header{
.{ .name = "content-type", .value = "text/html; charset=utf-8" },
.{ .name = "cache-control", .value = "no-store" },
.{ .name = "access-control-allow-origin", .value = "*" },
.{ .name = "access-control-allow-private-network", .value = "true" },
.{ .name = "connection", .value = "close" },
};