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);
|
||||
}
|
||||
|
||||
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