Something went wrong. Try again.
A GPU terminal emulator for Wayland and Windows on libghostty-vt, drawing with Vulkan, running a program on a pty or a serial port.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie <jeff@ocjtech.us>// SPDX-License-Identifier: MIT
//! How long starting up took, phase by phase, measured from when the//! process began: printed to standard error when `HARRIER_STARTUP_TIMES` is//! set, and nothing otherwise. What every change to the startup path is//! measured with. The time since the last mark is since the last on any//! thread: the fonts load on one of their own.
const std = @import("std");const builtin = @import("builtin");const linux = std.os.linux;const win32 = if (builtin.os.tag == .windows) @import("win32").everything else struct {};
var enabled: bool = false;/// Marks come from more than one thread: the fonts load on their own.var last_ns: std.atomic.Value(u64) = .init(0);
/// Turns the marks on, if the environment asks for them.pub fn init(env: *const std.process.Environ.Map) void { enabled = env.get("HARRIER_STARTUP_TIMES") != null;}
/// Notes that `phase` is done: how long since the process started, and/// how long since the last mark.pub fn mark(phase: []const u8) void { if (!enabled) return; const now = sinceStart() orelse return; var buf: [128]u8 = undefined; const last = last_ns.swap(now, .acq_rel); const line = std.fmt.bufPrint(&buf, "startup: {d:>7.1} ms (+{d:>6.1}) {s}\n", .{ @as(f64, @floatFromInt(now)) / 1e6, @as(f64, @floatFromInt(now -| last)) / 1e6, phase, }) catch return; switch (builtin.os.tag) { .windows => { var written: u32 = 0; _ = win32.WriteFile(win32.GetStdHandle(win32.STD_ERROR_HANDLE), line.ptr, @intCast(line.len), &written, null); }, else => _ = linux.write(2, line.ptr, line.len), }}
var once: std.StaticBitSet(8) = .empty;
/// `mark`, the first time `which` comes up only.pub fn markOnce(comptime which: enum { first_draw, first_frame }, phase: []const u8) void { if (!enabled or once.isSet(@backingInt(which))) return; once.set(@backingInt(which)); mark(phase);}
/// Nanoseconds since the process started, by the kernel's account.fn sinceStart() ?u64 { return switch (builtin.os.tag) { .windows => sinceStartWindows(), else => sinceStartLinux(), };}
/// The time now less the process's creation time, both in the 100 ns ticks/// Windows keeps time in.fn sinceStartWindows() ?u64 { var created: win32.FILETIME = undefined; var exited: win32.FILETIME = undefined; var kernel: win32.FILETIME = undefined; var user: win32.FILETIME = undefined; if (win32.GetProcessTimes(win32.GetCurrentProcess(), &created, &exited, &kernel, &user) == 0) return null; var now: win32.FILETIME = undefined; win32.GetSystemTimePreciseAsFileTime(&now); return (fileTicks(now) -| fileTicks(created)) * 100;}
fn fileTicks(t: win32.FILETIME) u64 { return @as(u64, t.dwHighDateTime) << 32 | t.dwLowDateTime;}
/// The clock now, less the process's start time from /proc/self/stat.fn sinceStartLinux() ?u64 { var ts: linux.timespec = undefined; if (linux.errno(linux.clock_gettime(.BOOTTIME, &ts)) != .SUCCESS) return null; const now: u64 = @as(u64, @intCast(ts.sec)) * std.time.ns_per_s + @as(u64, @intCast(ts.nsec)); const start = startTime() orelse return null; return now -| start;}
var start_cache: ?u64 = null;
fn startTime() ?u64 { if (start_cache) |s| return s; const fd = linux.open("/proc/self/stat", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); if (linux.errno(fd) != .SUCCESS) return null; defer _ = linux.close(@intCast(fd)); var buf: [1024]u8 = undefined; const n = linux.read(@intCast(fd), &buf, buf.len); if (linux.errno(n) != .SUCCESS) return null; // The 22nd field, in clock ticks since boot; the command name, the 2nd, // is in parentheses and may hold spaces, so counting starts after it. const text = buf[0..n]; const close = std.mem.findScalarLast(u8, text, ')') orelse return null; var fields = std.mem.tokenizeScalar(u8, text[close + 1 ..], ' '); var i: usize = 3; while (fields.next()) |f| : (i += 1) { if (i == 22) { const ticks = std.fmt.parseInt(u64, f, 10) catch return null; // USER_HZ is 100 on every Linux harrier runs on. start_cache = ticks * (std.time.ns_per_s / 100); return start_cache; } } return null;}