Expose v8 CpuProfiler + add fast properties for some window properties

First, this exposes the v8 Profiler. Right now it's just a commented-out block
in `fetch` and meant for internal debugging.
Depends on: https://github.com/lightpanda-io/zig-v8-fork/pull/105

Use postAttach on Window to attach "static" properties. This comes from
profiling (lightpanda.io) and seeing window.get_self called tens of thousands
of times.
This commit is contained in:
Karl Seguin
2025-10-10 19:48:50 +08:00
parent c502bd901e
commit c381e4153d
6 changed files with 64 additions and 40 deletions

View File

@@ -5,8 +5,8 @@
.fingerprint = 0xda130f3af836cea0,
.dependencies = .{
.v8 = .{
.url = "https://github.com/lightpanda-io/zig-v8-fork/archive/5ce9ade6c6d7f7ab9ab4582a6b1b36fdc8e246e2.tar.gz",
.hash = "v8-0.0.0-xddH6yTGAwA__kfiDozsVXL2_jnycrtDUfR8kIADQFUz",
.url = "https://github.com/lightpanda-io/zig-v8-fork/archive/305bb3706716d32d59b2ffa674731556caa1002b.tar.gz",
.hash = "v8-0.0.0-xddH63bVAwBSEobaUok9J0er1FqsvEujCDDVy6ItqKQ5",
},
//.v8 = .{ .path = "../zig-v8-fork" }
},

View File

@@ -136,14 +136,6 @@ pub const Window = struct {
self.onload_callback = null;
}
pub fn get_window(self: *Window) *Window {
return self;
}
pub fn get_navigator(self: *Window) *Navigator {
return &self.navigator;
}
pub fn get_location(self: *Window) *Location {
return &self.location;
}
@@ -152,22 +144,6 @@ pub const Window = struct {
return page.navigateFromWebAPI(url, .{ .reason = .script });
}
pub fn get_console(self: *Window) *Console {
return &self.console;
}
pub fn get_crypto(self: *Window) *Crypto {
return &self.crypto;
}
pub fn get_self(self: *Window) *Window {
return self;
}
pub fn get_parent(self: *Window) *Window {
return self;
}
// frames return the window itself, but accessing it via a pseudo
// array returns the Window object corresponding to the given frame or
// iframe.
@@ -205,10 +181,6 @@ pub const Window = struct {
return frames.get_length();
}
pub fn get_top(self: *Window) *Window {
return self;
}
pub fn get_document(self: *Window) ?*parser.DocumentHTML {
return self.document;
}
@@ -243,14 +215,6 @@ pub const Window = struct {
return &self.storage_shelf.?.bucket.session;
}
pub fn get_performance(self: *Window) *Performance {
return &self.performance;
}
pub fn get_screen(self: *Window) *Screen {
return &self.screen;
}
pub fn get_CSS(self: *Window) *Css {
return &self.css;
}
@@ -463,6 +427,18 @@ pub const Window = struct {
// and thus the target has already been set to the document.
return self.base.redispatchEvent(evt);
}
pub fn postAttach(self: *Window, js_this: js.This) !void {
try js_this.set("top", self, .{});
try js_this.set("self", self, .{});
try js_this.set("parent", self, .{});
try js_this.set("window", self, .{});
try js_this.set("crypto", &self.crypto, .{});
try js_this.set("screen", &self.screen, .{});
try js_this.set("console", &self.console, .{});
try js_this.set("navigator", &self.navigator, .{});
try js_this.set("performance", &self.performance, .{});
}
};
const TimerCallback = struct {

View File

@@ -29,6 +29,8 @@ isolate: v8.Isolate,
v8_context: v8.Context,
handle_scope: ?v8.HandleScope,
cpu_profiler: ?v8.CpuProfiler = null,
// references Env.templates
templates: []v8.FunctionTemplate,
@@ -1819,6 +1821,11 @@ fn zigJsonToJs(isolate: v8.Isolate, v8_context: v8.Context, value: std.json.Valu
}
}
pub fn getGlobalThis(self: *Context) js.This {
const js_global = self.v8_context.getGlobal();
return .{ .obj = .{ .js_obj = js_global, .context = self } };
}
// == Misc ==
// An interface for types that want to have their jsDeinit function to be
// called when the call context ends
@@ -1847,3 +1854,26 @@ const DestructorCallback = struct {
self.destructorFn(self.ptr);
}
};
// == Profiler ==
pub fn startCpuProfiler(self: *Context) void {
if (builtin.mode != .Debug) {
// Still testing this out, don't have it properly exposed, so add this
// guard for the time being to prevent any accidental/weird prod issues.
@compileError("CPU Profiling is only available in debug builds");
}
std.debug.assert(self.cpu_profiler == null);
v8.CpuProfiler.useDetailedSourcePositionsForProfiling(self.isolate);
const cpu_profiler = v8.CpuProfiler.init(self.isolate);
const title = self.isolate.initStringUtf8("v8_cpu_profile");
cpu_profiler.startProfiling(title);
self.cpu_profiler = cpu_profiler;
}
pub fn stopCpuProfiler(self: *Context) ![]const u8 {
const title = self.isolate.initStringUtf8("v8_cpu_profile");
const profile = self.cpu_profiler.?.stopProfiling(title) orelse unreachable;
const serialized = profile.serialize(self.isolate).?;
return self.jsStringToZig(serialized, .{});
}

View File

@@ -27,7 +27,7 @@ pub fn setIndex(self: Object, index: u32, value: anytype, opts: SetOpts) !void {
return self.set(key, value, opts);
}
pub fn set(self: Object, key: []const u8, value: anytype, opts: SetOpts) !void {
pub fn set(self: Object, key: []const u8, value: anytype, opts: SetOpts) error{ FailedToSet, OutOfMemory }!void {
const context = self.context;
const js_key = v8.String.initUtf8(context.isolate, key);

View File

@@ -849,7 +849,7 @@ pub const Page = struct {
_ = self.session.browser.transfer_arena.reset(.{ .retain_with_limit = 4 * 1024 });
}
// extracted because this sis called from tests to set things up.
// extracted because this is called from tests to set things up.
pub fn setDocument(self: *Page, html_doc: *parser.DocumentHTML) !void {
const doc = parser.documentHTMLToDocument(html_doc);
try parser.documentSetDocumentURI(doc, self.url.raw);

View File

@@ -165,6 +165,24 @@ fn run(alloc: Allocator) !void {
// page
const page = try session.createPage();
// // Comment this out to get a profile of the JS code in v8/profile.json.
// // You can open this in Chrome's profiler.
// // I've seen it generate invalid JSON, but I'm not sure why. It only
// // happens rarely, and I manually fix the file.
// page.js.startCpuProfiler();
// defer {
// if (page.js.stopCpuProfiler()) |profile| {
// std.fs.cwd().writeFile(.{
// .sub_path = "v8/profile.json",
// .data = profile,
// }) catch |err| {
// log.err(.app, "profile write error", .{ .err = err });
// };
// } else |err| {
// log.err(.app, "profile error", .{ .err = err });
// }
// }
_ = page.navigate(url, .{}) catch |err| switch (err) {
error.UnsupportedUriScheme, error.UriMissingHost => {
log.fatal(.app, "invalid fetch URL", .{ .err = err, .url = url });