feat: extend metrics

This commit is contained in:
dualfroz committed 2026-09-07 18:18:12 +02:00
1 parent cdcaaec1f3
commit 1c123cb114
1 file changed
+119
+119
View File
@@ -479,3 +479,122 @@ test "token bucket never partially consumes on a failed acquire" {
try std.testing.expectApproxEqAbs(@as(f64, 5), b.availableAt(0), 1e-9); try std.testing.expectApproxEqAbs(@as(f64, 5), b.availableAt(0), 1e-9);
} }
test "sliding window limiter smooths across a window boundary" {
const windowNs: u64 = std.time.ns_per_s;
var lim = SlidingWindowLimiter.init(10, windowNs, 0);
var i: usize = 0;
while (i < 10) : (i += 1) try std.testing.expect(lim.allowAt(0));
try std.testing.expect(!lim.allowAt(0)); // exhausted current window
// Halfway into the next window: previous window's 10 requests are
// weighted at 50% overlap (5), so only 5 more should be allowed before
// the weighted total reaches the limit of 10.
const halfwayNextWindow = windowNs + windowNs / 2;
var allowed: usize = 0;
i = 0;
while (i < 20) : (i += 1) {
if (lim.allowAt(halfwayNextWindow)) allowed += 1;
}
try std.testing.expectEqual(@as(usize, 5), allowed);
}
test "sliding window limiter resets fully after a gap of more than one window" {
const windowNs: u64 = std.time.ns_per_s;
var lim = SlidingWindowLimiter.init(3, windowNs, 0);
try std.testing.expect(lim.allowAt(0));
try std.testing.expect(lim.allowAt(0));
try std.testing.expect(lim.allowAt(0));
try std.testing.expect(!lim.allowAt(0));
// Jump 10 windows ahead: previous window count must not leak in.
const farFuture = 10 * windowNs;
try std.testing.expect(lim.allowAt(farFuture));
try std.testing.expect(lim.allowAt(farFuture));
try std.testing.expect(lim.allowAt(farFuture));
try std.testing.expect(!lim.allowAt(farFuture));
}
test "registry exports counters gauges and histograms as sorted prometheus text" {
var c = MetricCounter{};
c.incrBy(7);
var g = Gauge{};
g.set(2.5);
const bounds = [_]f64{ 5, 10 };
var h = try Histogram.init(std.testing.allocator, &bounds);
defer h.deinit();
h.observe(3);
h.observe(8);
var reg = Registry.init(std.testing.allocator);
defer reg.deinit();
try reg.registerCounter("requests_total", &c);
try reg.registerGauge("temperature", &g);
try reg.registerHistogram("latency", &h);
try std.testing.expectEqual(@as(usize, 3), reg.count());
var buf = std.ArrayList(u8).init(std.testing.allocator);
defer buf.deinit();
try reg.exportText(std.testing.allocator, buf.writer());
const text = buf.items;
// sorted alphabetically: latency < requests_total < temperature
try std.testing.expect(std.mem.indexOf(u8, text, "latency_bucket{le=\"5\"} 1\n") != null);
try std.testing.expect(std.mem.indexOf(u8, text, "latency_bucket{le=\"10\"} 2\n") != null);
try std.testing.expect(std.mem.indexOf(u8, text, "latency_bucket{le=\"+Inf\"} 2\n") != null);
try std.testing.expect(std.mem.indexOf(u8, text, "latency_sum 11") != null);
try std.testing.expect(std.mem.indexOf(u8, text, "latency_count 2\n") != null);
try std.testing.expect(std.mem.indexOf(u8, text, "requests_total 7\n") != null);
try std.testing.expect(std.mem.indexOf(u8, text, "temperature 2.5\n") != null);
const latencyPos = std.mem.indexOf(u8, text, "latency_bucket").?;
const requestsPos = std.mem.indexOf(u8, text, "requests_total").?;
const temperaturePos = std.mem.indexOf(u8, text, "temperature").?;
try std.testing.expect(latencyPos < requestsPos);
try std.testing.expect(requestsPos < temperaturePos);
}
test "registry re-registering the same name overwrites rather than duplicating" {
var c1 = MetricCounter{};
c1.incrBy(1);
var c2 = MetricCounter{};
c2.incrBy(99);
var reg = Registry.init(std.testing.allocator);
defer reg.deinit();
try reg.registerCounter("x", &c1);
try reg.registerCounter("x", &c2);
try std.testing.expectEqual(@as(usize, 1), reg.count());
var buf = std.ArrayList(u8).init(std.testing.allocator);
defer buf.deinit();
try reg.exportText(std.testing.allocator, buf.writer());
try std.testing.expectEqualStrings("x 99\n", buf.items);
}
test "formatDbStats renders levels up to the deepest populated one" {
var s = stats_mod.Stats{};
s.memtableEntries = 3;
s.memtableBytes = 128;
s.sstableCount = 2;
s.cacheHits = 9;
s.cacheMisses = 1;
s.levels[0].tables = 1;
s.levels[0].entries = 10;
s.levels[2].tables = 4;
s.levels[2].entries = 40;
var buf = std.ArrayList(u8).init(std.testing.allocator);
defer buf.deinit();
try formatDbStats(buf.writer(), "cobalt", s);
const text = buf.items;
try std.testing.expect(std.mem.indexOf(u8, text, "cobalt_memtable_entries 3\n") != null);
try std.testing.expect(std.mem.indexOf(u8, text, "cobalt_cache_hit_rate 0.900000\n") != null);
try std.testing.expect(std.mem.indexOf(u8, text, "cobalt_level_tables{level=\"0\"} 1\n") != null);
try std.testing.expect(std.mem.indexOf(u8, text, "cobalt_level_tables{level=\"2\"} 4\n") != null);
// level 1 is empty but must still appear since deepestLevel is 2
try std.testing.expect(std.mem.indexOf(u8, text, "cobalt_level_tables{level=\"1\"} 0\n") != null);
// level 3+ must not appear at all
try std.testing.expect(std.mem.indexOf(u8, text, "level=\"3\"") == null);
}