diff --git a/pkg/macos/build.zig b/pkg/macos/build.zig index 3e0a97d1a..ddbeb82c9 100644 --- a/pkg/macos/build.zig +++ b/pkg/macos/build.zig @@ -18,15 +18,12 @@ pub fn build(b: *std.Build) !void { .optimize = optimize, }); - var flags = std.ArrayList([]const u8).init(b.allocator); - defer flags.deinit(); lib.addCSourceFile(.{ - .file = b.path("os/zig_log.c"), - .flags = flags.items, + .file = b.path("os/zig_macos.c"), + .flags = &.{"-std=c99"}, }); lib.addCSourceFile(.{ .file = b.path("text/ext.c"), - .flags = flags.items, }); lib.linkFramework("CoreFoundation"); lib.linkFramework("CoreGraphics"); diff --git a/pkg/macos/main.zig b/pkg/macos/main.zig index 42253ba48..e4f4b9504 100644 --- a/pkg/macos/main.zig +++ b/pkg/macos/main.zig @@ -23,6 +23,7 @@ pub const c = @cImport({ @cInclude("IOSurface/IOSurfaceRef.h"); @cInclude("dispatch/dispatch.h"); @cInclude("os/log.h"); + @cInclude("os/signpost.h"); if (builtin.os.tag == .macos) { @cInclude("Carbon/Carbon.h"); diff --git a/pkg/macos/os.zig b/pkg/macos/os.zig index 183913bac..9716a9abc 100644 --- a/pkg/macos/os.zig +++ b/pkg/macos/os.zig @@ -1,6 +1,7 @@ const log = @import("os/log.zig"); pub const c = @import("os/c.zig"); +pub const signpost = @import("os/signpost.zig"); pub const Log = log.Log; pub const LogType = log.LogType; diff --git a/pkg/macos/os/signpost.zig b/pkg/macos/os/signpost.zig new file mode 100644 index 000000000..9fef584e4 --- /dev/null +++ b/pkg/macos/os/signpost.zig @@ -0,0 +1,161 @@ +const std = @import("std"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; +const c = @import("c.zig").c; +const logpkg = @import("log.zig"); +const Log = logpkg.Log; + +/// Checks whether signpost logging is enabled for the given log handle. +/// Returns true if signposts will be recorded for this log, false otherwise. +/// This can be used to avoid expensive operations when signpost logging is disabled. +/// +/// https://developer.apple.com/documentation/os/os_signpost_enabled?language=objc +pub fn enabled(log: *Log) bool { + return c.os_signpost_enabled(@ptrCast(log)); +} + +/// Emits a signpost event - a single point in time marker. +/// Events are useful for marking when specific actions occur, such as +/// user interactions, state changes, or other discrete occurrences. +/// The event will appear as a vertical line in Instruments. +/// +/// https://developer.apple.com/documentation/os/os_signpost_event_emit?language=objc +pub fn emitEvent( + log: *Log, + id: Id, + comptime name: [:0]const u8, +) void { + emitWithName(log, id, .event, name); +} + +/// Marks the beginning of a time interval. +/// Use this with intervalEnd to measure the duration of operations. +/// The same ID must be used for both the begin and end calls. +/// Intervals appear as horizontal bars in Instruments timeline. +/// +/// https://developer.apple.com/documentation/os/os_signpost_interval_begin?language=objc +pub fn intervalBegin(log: *Log, id: Id, comptime name: [:0]const u8) void { + emitWithName(log, id, .interval_begin, name); +} + +/// Marks the end of a time interval. +/// Must be paired with a prior intervalBegin call using the same ID. +/// The name should match the name used in intervalBegin. +/// Instruments will calculate and display the duration between begin and end. +/// +/// https://developer.apple.com/documentation/os/os_signpost_interval_end?language=objc +pub fn intervalEnd(log: *Log, id: Id, comptime name: [:0]const u8) void { + emitWithName(log, id, .interval_end, name); +} + +extern var __dso_handle: usize; + +/// The internal function to emit a signpost with a specific name. +fn emitWithName( + log: *Log, + id: Id, + typ: Type, + comptime name: [:0]const u8, +) void { + var buf: [64]u8 = @splat(0); + c._os_signpost_emit_with_name_impl( + &__dso_handle, + @ptrCast(log), + @intFromEnum(typ), + @intFromEnum(id), + name.ptr, + null, + &buf, + buf.len, + ); +} + +/// https://developer.apple.com/documentation/os/os_signpost_id_t?language=objc +pub const Id = enum(u64) { + null = 0, // OS_SIGNPOST_ID_NULL + invalid = 0xFFFFFFFFFFFFFFFF, // OS_SIGNPOST_ID_INVALID + exclusive = 0xEEEEB0B5B2B2EEEE, // OS_SIGNPOST_ID_EXCLUSIVE + _, + + /// Generates a new signpost ID for use with signpost operations. + /// The ID is unique for the given log handle and can be used to track + /// asynchronous operations or mark specific points of interest in the code. + /// Returns a unique signpost ID that can be used with os_signpost functions. + /// + /// https://developer.apple.com/documentation/os/os_signpost_id_generate?language=objc + pub fn generate(log: *Log) Id { + return @enumFromInt(c.os_signpost_id_generate(@ptrCast(log))); + } + + /// Creates a signpost ID based on a pointer value. + /// This is useful for tracking operations associated with a specific object + /// or memory location. The same pointer will always generate the same ID + /// for a given log handle, allowing correlation of signpost events. + /// Pass null to get the null signpost ID. + /// + /// https://developer.apple.com/documentation/os/os_signpost_id_for_pointer?language=objc + pub fn forPointer(log: *Log, ptr: ?*anyopaque) Id { + return @enumFromInt(c.os_signpost_id_make_with_pointer( + @ptrCast(log), + @ptrCast(ptr), + )); + } + + test "generate ID" { + // We can't really test the return value because it may return null + // if signposts are disabled. + const id: Id = .generate(Log.create("com.mitchellh.ghostty", "test")); + try std.testing.expect(id != .invalid); + } + + test "generate ID for pointer" { + var foo: usize = 0x1234; + const id: Id = .forPointer(Log.create("com.mitchellh.ghostty", "test"), &foo); + try std.testing.expect(id != .null); + } +}; + +/// https://developer.apple.com/documentation/os/ossignposttype?language=objc +pub const Type = enum(u8) { + event = 0, // OS_SIGNPOST_EVENT + interval_begin = 1, // OS_SIGNPOST_INTERVAL_BEGIN + interval_end = 2, // OS_SIGNPOST_INTERVAL_END + + pub const mask: u8 = 0x03; // OS_SIGNPOST_TYPE_MASK +}; + +/// Special os_log category values that surface in Instruments and other +/// tooling. +pub const Category = struct { + /// Points of Interest appear as a dedicated track in Instruments. + /// Use this for high-level application events that help understand + /// the flow of your application. + pub const points_of_interest: [:0]const u8 = "PointsOfInterest"; + + /// Dynamic Tracing category enables runtime-configurable logging. + /// Signposts in this category can be enabled/disabled dynamically + /// without recompiling. + pub const dynamic_tracing: [:0]const u8 = "DynamicTracking"; + + /// Dynamic Stack Tracing category captures call stacks at signpost + /// events. This provides deeper debugging information but has higher + /// performance overhead. + pub const dynamic_stack_tracing: [:0]const u8 = "DynamicStackTracking"; +}; + +test { + _ = Id; +} + +test enabled { + _ = enabled(Log.create("com.mitchellh.ghostty", "test")); +} + +test "intervals" { + const log = Log.create("com.mitchellh.ghostty", "test"); + defer log.release(); + + // Test that we can begin and end an interval + const id = Id.generate(log); + intervalBegin(log, id, "Test Interval"); +} diff --git a/pkg/macos/os/zig_log.c b/pkg/macos/os/zig_macos.c similarity index 90% rename from pkg/macos/os/zig_log.c rename to pkg/macos/os/zig_macos.c index ef3f616d5..1c4f06982 100644 --- a/pkg/macos/os/zig_log.c +++ b/pkg/macos/os/zig_macos.c @@ -1,4 +1,5 @@ #include +#include // A wrapper so we can use the os_log_with_type macro. void zig_os_log_with_type(