summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
authorMitchell Hashimoto <m@mitchellh.com>2025-07-07 14:14:28 -0700
committerMitchell Hashimoto <m@mitchellh.com>2025-07-09 15:06:23 -0700
commit8506637ae614237a39f3a167e94bb998979ffb25 (patch)
tree0b12f9529842b2f1fdf29f6f34268e3897c2f8f4 /pkg
parent464dc78172d3c906b68bfc62f81aa7972ee4991f (diff)
macos: add signpost API
Diffstat (limited to 'pkg')
-rw-r--r--pkg/macos/build.zig7
-rw-r--r--pkg/macos/main.zig1
-rw-r--r--pkg/macos/os.zig1
-rw-r--r--pkg/macos/os/signpost.zig161
-rw-r--r--pkg/macos/os/zig_macos.c (renamed from pkg/macos/os/zig_log.c)1
5 files changed, 166 insertions, 5 deletions
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
index ef3f616d5..1c4f06982 100644
--- a/pkg/macos/os/zig_log.c
+++ b/pkg/macos/os/zig_macos.c
@@ -1,4 +1,5 @@
#include <os/log.h>
+#include <os/signpost.h>
// A wrapper so we can use the os_log_with_type macro.
void zig_os_log_with_type(