diff --git a/src/browser/console/console.zig b/src/browser/console/console.zig index d2c7ba19..cd4cb443 100644 --- a/src/browser/console/console.zig +++ b/src/browser/console/console.zig @@ -165,8 +165,7 @@ pub const Console = struct { }; fn timestamp() u32 { - const ts = std.posix.clock_gettime(std.posix.CLOCK.MONOTONIC) catch unreachable; - return @intCast(ts.sec); + return @import("../../datetime.zig").timestamp(); } var test_capture = TestCapture{}; diff --git a/src/browser/dom/performance.zig b/src/browser/dom/performance.zig index 7a068845..655df0c1 100644 --- a/src/browser/dom/performance.zig +++ b/src/browser/dom/performance.zig @@ -23,6 +23,8 @@ const EventTarget = @import("../dom/event_target.zig").EventTarget; const Env = @import("../env.zig").Env; const Page = @import("../page.zig").Page; +const milliTimestamp = @import("../../datetime.zig").milliTimestamp; + pub const Interfaces = .{ Performance, PerformanceEntry, @@ -36,29 +38,27 @@ pub const Performance = struct { // Extend libdom event target for pure zig struct. base: parser.EventTargetTBase = parser.EventTargetTBase{ .internal_target_type = .performance }, - time_origin: std.time.Timer, + time_origin: u64, // if (Window.crossOriginIsolated) -> Resolution in isolated contexts: 5 microseconds // else -> Resolution in non-isolated contexts: 100 microseconds const ms_resolution = 100; - fn limitedResolutionMs(nanoseconds: u64) f64 { - const elapsed_at_resolution = ((nanoseconds / std.time.ns_per_us) + ms_resolution / 2) / ms_resolution * ms_resolution; - const elapsed = @as(f64, @floatFromInt(elapsed_at_resolution)); - return elapsed / @as(f64, std.time.us_per_ms); - } - - pub fn get_timeOrigin(self: *const Performance) f64 { - const is_posix = switch (@import("builtin").os.tag) { // From std.time.zig L125 - .windows, .uefi, .wasi => false, - else => true, + pub fn init() Performance { + return .{ + .time_origin = milliTimestamp(), }; - const zero = std.time.Instant{ .timestamp = if (!is_posix) 0 else .{ .sec = 0, .nsec = 0 } }; - const started = self.time_origin.started.since(zero); - return limitedResolutionMs(started); } - pub fn _now(self: *Performance) f64 { - return limitedResolutionMs(self.time_origin.read()); + pub fn get_timeOrigin(self: *const Performance) u64 { + return self.time_origin; + } + + pub fn reset(self: *Performance) void { + self.time_origin = milliTimestamp(); + } + + pub fn _now(self: *const Performance) u64 { + return milliTimestamp() - self.time_origin; } pub fn _mark(_: *Performance, name: []const u8, _options: ?PerformanceMark.Options, page: *Page) !PerformanceMark { @@ -152,14 +152,14 @@ pub const PerformanceMark = struct { const Options = struct { detail: ?Env.JsObject = null, - start_time: ?f64 = null, + startTime: ?f64 = null, }; pub fn constructor(name: []const u8, _options: ?Options, page: *Page) !PerformanceMark { const perf = &page.window.performance; const options = _options orelse Options{}; - const start_time = options.start_time orelse perf._now(); + const start_time = options.startTime orelse @as(f64, @floatFromInt(perf._now())); if (start_time < 0.0) { return error.TypeError; @@ -181,16 +181,13 @@ pub const PerformanceMark = struct { const testing = @import("./../../testing.zig"); test "Performance: get_timeOrigin" { - var perf = Performance{ .time_origin = try std.time.Timer.start() }; + var perf = Performance.init(); const time_origin = perf.get_timeOrigin(); try testing.expect(time_origin >= 0); - - // Check resolution - try testing.expectDelta(@rem(time_origin * std.time.us_per_ms, 100.0), 0.0, 0.2); } test "Performance: now" { - var perf = Performance{ .time_origin = try std.time.Timer.start() }; + var perf = Performance.init(); // Monotonically increasing var now = perf._now(); @@ -198,16 +195,12 @@ test "Performance: now" { try testing.expectEqual(now, 0); now = perf._now(); } - // Check resolution - try testing.expectDelta(@rem(now * std.time.us_per_ms, 100.0), 0.0, 0.1); var after = perf._now(); while (after <= now) { // Loop untill after > now try testing.expectEqual(after, now); after = perf._now(); } - // Check resolution - try testing.expectDelta(@rem(after * std.time.us_per_ms, 100.0), 0.0, 0.1); } test "Browser.Performance.Mark" { @@ -215,13 +208,17 @@ test "Browser.Performance.Mark" { defer runner.deinit(); try runner.testCases(&.{ - .{ "let performance = window.performance", "undefined" }, + .{ "let performance = window.performance", null }, .{ "performance instanceof Performance", "true" }, - .{ "let mark = performance.mark(\"start\")", "undefined" }, - .{ "mark instanceof PerformanceMark", "true" }, - .{ "mark.name", "start" }, - .{ "mark.entryType", "mark" }, - .{ "mark.duration", "0" }, - .{ "mark.detail", "null" }, + + .{ "let mark1 = performance.mark(\"start\")", null }, + .{ "mark1 instanceof PerformanceMark", "true" }, + .{ "mark1.name", "start" }, + .{ "mark1.entryType", "mark" }, + .{ "mark1.duration", "0" }, + .{ "mark1.detail", "null" }, + + .{ "let mark2 = performance.mark(\"start\", {startTime: 32939393.9})", null }, + .{ "mark2.startTime", "32939393.9" }, }, .{}); } diff --git a/src/browser/html/window.zig b/src/browser/html/window.zig index 496b17ac..203d3b8c 100644 --- a/src/browser/html/window.zig +++ b/src/browser/html/window.zig @@ -76,7 +76,7 @@ pub const Window = struct { .document = html_doc, .target = target orelse "", .navigator = navigator orelse .{}, - .performance = .{ .time_origin = try std.time.Timer.start() }, + .performance = Performance.init(), }; } @@ -86,7 +86,7 @@ pub const Window = struct { } pub fn replaceDocument(self: *Window, doc: *parser.DocumentHTML) !void { - self.performance.time_origin.reset(); // When to reset see: https://developer.mozilla.org/en-US/docs/Web/API/Performance/timeOrigin + self.performance.reset(); // When to reset see: https://developer.mozilla.org/en-US/docs/Web/API/Performance/timeOrigin self.document = doc; try parser.documentHTMLSetLocation(Location, doc, &self.location); } diff --git a/src/browser/page.zig b/src/browser/page.zig index b1bff355..211ed598 100644 --- a/src/browser/page.zig +++ b/src/browser/page.zig @@ -1101,8 +1101,7 @@ pub const NavigateOpts = struct { }; fn timestamp() u32 { - const ts = std.posix.clock_gettime(std.posix.CLOCK.MONOTONIC) catch unreachable; - return @intCast(ts.sec); + return @import("../datetime.zig").timestamp(); } // A callback from libdom whenever a script tag is added to the DOM. diff --git a/src/datetime.zig b/src/datetime.zig index 3bfe4043..9e98abe9 100644 --- a/src/datetime.zig +++ b/src/datetime.zig @@ -502,6 +502,28 @@ pub const DateTime = struct { } }; +pub fn timestamp() u32 { + const ts = timespec(); + return @intCast(ts.sec); +} + +pub fn milliTimestamp() u64 { + const ts = timespec(); + return @as(u64, @intCast(ts.sec)) * 1000 + @as(u64, @intCast(@divTrunc(ts.nsec, 1_000_000))); +} + +fn timespec() std.posix.timespec { + const clock_id = switch (@import("builtin").os.tag) { + .freebsd, .dragonfly => std.posix.CLOCK.MONOTONIC_FAST, + .macos, .ios, .tvos, .watchos, .visionos => std.posix.CLOCK.UPTIME_RAW, // continues counting while suspended + .linux => std.posix.CLOCK.BOOTTIME, // continues counting while suspended + else => std.posix.CLOCK.MONOTONIC, + }; + // we don't currently support platforms where, at the very least, + // posix.CLOCK.MONOTONIC wouldn't be available. + return std.posix.clock_gettime(clock_id) catch unreachable; +} + fn writeDate(into: []u8, date: Date) u8 { var buf: []u8 = undefined; // cast year to a u16 so it doesn't insert a sign diff --git a/src/log.zig b/src/log.zig index 2e8f1910..abeaeadd 100644 --- a/src/log.zig +++ b/src/log.zig @@ -321,14 +321,14 @@ fn writeString(comptime format: Format, value: []const u8, writer: anytype) !voi return writer.writeByte('"'); } -fn timestamp() i64 { +fn timestamp() u64 { if (comptime @import("builtin").is_test) { return 1739795092929; } - return std.time.milliTimestamp(); + return @import("datetime.zig").milliTimestamp(); } -var first_log: i64 = 0; +var first_log: u64 = 0; fn elapsed() struct { time: f64, unit: []const u8 } { const now = timestamp(); diff --git a/src/server.zig b/src/server.zig index ab8c55bb..2a16bd8d 100644 --- a/src/server.zig +++ b/src/server.zig @@ -905,8 +905,7 @@ fn buildJSONVersionResponse( } fn timestamp() u32 { - const ts = std.posix.clock_gettime(std.posix.CLOCK.MONOTONIC) catch unreachable; - return @intCast(ts.sec); + return @import("datetime.zig").timestamp(); } // In-place string lowercase