Merge pull request #3172 from lightpanda-io/js_mem_gauge

metrics: Add js memory gauge
This commit is contained in:
Karl Seguin authored and GitHub committed 2026-08-12 07:46:21 +08:00
commit efc7613768
5 files changed
+38 -1

No files matched your search

+3 -1
View File
@@ -128,7 +128,9 @@ fn shrank(self: *Arena, n: usize) void {
}
fn resized(self: *Arena, old_len: usize, new_len: usize) void {
if (new_len >= old_len) self.grew(new_len - old_len) else self.shrank(old_len - new_len);
// self.bytes always contains old_len, so this can't wrap
self.bytes = self.bytes - old_len + new_len;
lp.metrics.arena_memory_bytes.add(@as(i64, @intCast(new_len)) - @as(i64, @intCast(old_len)));
}
const vtable = Allocator.VTable{
+9
View File
@@ -34,6 +34,7 @@ arena_miss: CounterEnum("size", @import("ArenaPool.zig").BucketSize) = .{},
arena_inflight: GaugeEnum("size", @import("ArenaPool.zig").BucketSize) = .{},
arena_memory_bytes: Gauge = .{},
navigate: CounterEnum("type", @import("telemetry/telemetry.zig").Event.Navigate.Context) = .{},
js_heap_physical_bytes: Gauge = .{},
js_heap_size_bytes: Histogram(&.{
4 * 1024 * 1024,
8 * 1024 * 1024,
@@ -91,6 +92,7 @@ const help = .{
.arena_inflight = "Arenas currently checked out of the pool. Above the bucket's max, every acquisition is a miss and every release is discarded",
.arena_memory_bytes = "Backing memory held by pooled arenas, including capacity retained on the free list",
.navigate = "Navigations by initiating frame type",
.js_heap_physical_bytes = "V8 heap physical size summed over every live isolate (one per CDP connection).",
.js_heap_size_bytes = "V8 heap physical size, sampled when a page is closed",
.http_requests = "HTTP requests submitted, by dispatch mode (excludes internal requests like robots.txt)",
.http_status = "Final HTTP response status category (redirects counted once, at the final hop)",
@@ -160,6 +162,13 @@ const Gauge = struct {
_ = @atomicRmw(isize, &self.value, .Sub, @intCast(n), .monotonic);
}
// For callers that track an absolute value and report the change since
// their last report. There's no set(): the gauge is a sum over threads,
// so a caller can only move its own contribution.
pub fn add(self: *Gauge, n: i64) void {
_ = @atomicRmw(isize, &self.value, .Add, @intCast(n), .monotonic);
}
fn write(self: *const Gauge, comptime name: []const u8, comptime help_text: []const u8, writer: *std.Io.Writer) !void {
try writer.writeAll("# HELP " ++ name ++ " " ++ help_text ++ "\n" ++ "# TYPE " ++ name ++ " gauge\n");
try writer.print(name ++ " {d}\n", .{@atomicLoad(isize, &self.value, .monotonic)});
+13
View File
@@ -53,6 +53,9 @@ selector_cache: Selector.Cache,
// Pinned-arena bytes we haven't told v8 about yet
arena_account: lp.Arena.Account = .{},
// Our isolate's heap size as of the last reportJsHeap().
last_reported_js_bytes: usize = 0,
// Permission state set via CDP Browser.grantPermissions / setPermission /
// resetPermissions, keyed by permission name (e.g. "geolocation"). Read back
// by navigator.permissions.query(). Scoped to the Browser so it persists
@@ -139,6 +142,10 @@ pub fn deinit(self: *Browser) void {
const allocator = self.allocator;
self.closeSession();
lp.metrics.js_heap_physical_bytes.add(-@as(i64, @intCast(self.last_reported_js_bytes)));
self.last_reported_js_bytes = 0;
// After this returns, the watchdog thread holds no reference to our env
// or http_client — required before either is torn down.
self.app.watchdog.unregister(&self.watchdog_entry);
@@ -210,6 +217,12 @@ pub fn flushArenaMemory(self: *Browser) void {
self.env.isolate.adjustAmountOfExternalAllocatedMemory(delta);
}
pub fn reportJsHeap(self: *Browser) void {
const bytes = self.env.isolate.getHeapStatistics().total_physical_size;
lp.metrics.js_heap_physical_bytes.add(@as(i64, @intCast(bytes)) - @as(i64, @intCast(self.last_reported_js_bytes)));
self.last_reported_js_bytes = bytes;
}
pub fn runMicrotasks(self: *Browser) void {
self.env.runMicrotasks();
}
+12
View File
@@ -472,6 +472,11 @@ pub fn init(self: *Frame, frame_id: u32, page: *Page, opts: InitOpts) !void {
}.runIdleTasks, 200, .{ .name = "frame.runIdleTasks", .blocks_done = false });
}
}
if (parent == null) {
// no point reporting this for each child page
session.browser.reportJsHeap();
}
}
pub fn deinit(self: *Frame) void {
@@ -543,6 +548,9 @@ pub fn deinit(self: *Frame) void {
const browser = page.session.browser;
browser.http_client.abortOwner(&self._http_owner);
if (self.parent == null) {
browser.reportJsHeap();
}
browser.env.destroyContext(self.js);
@@ -1228,6 +1236,10 @@ pub fn documentIsComplete(self: *Frame) void {
error.JsException => {}, // already logged
else => log.err(.frame, "document is complete", .{ .err = err, .type = self._type, .url = self.url }),
};
if (self.parent == null) {
self._session.browser.reportJsHeap();
}
}
fn _documentIsComplete(self: *Frame) !void {
+1
View File
@@ -206,6 +206,7 @@ pub fn deinit(self: *Page) void {
const session = self.session;
lp.metrics.js_heap_size_bytes.observe(session.browser.env.isolate.getHeapStatistics().total_physical_size);
session.browser.reportJsHeap();
defer session.browser.env.memoryPressureNotification(.moderate);
self.identity.deinit();