feat: extend metrics
This commit is contained in:
1 parent
cdcaaec1f3
commit
1c123cb114
1 file changed
+119
+119
@@ -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);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user