Files
nucleic/Sources/NucleicCore/StartupSignposts.swift
T

68 lines
3.1 KiB
Swift

import Foundation
// OSSignposter on Darwin; nucleicd's Linux build has no `os`, so every call no-ops there.
#if canImport(os)
import os
#endif
/// Startup signposting (docs/MAIN_THREAD_PERFORMANCE_PLAN.md item 6, feeding item 9's
/// "startup hydration and maintenance" measurement pass): named `os_signpost` intervals over
/// the launch sequence, so Instruments' os_signpost track (subsystem "com.nucleic", category
/// "startup") can attribute launch latency to its phases instead of one opaque blob:
///
/// • `LaunchToFirstFrame` — begun in `NucleicApp.init` (the first code we own; the dyld +
/// Swift-runtime slice *before* it is what Instruments' App Launch template already
/// covers), ended when the root view's `.task` fires — i.e. the first frame at which the
/// window's content hierarchy is live and interactive.
/// • `Hydration` — the ordered project/session install plus the concurrent noncritical
/// hydration group (activity-feed seeding, todo loading, provider probing).
/// • `LockReconcile` — the launch lock/nvrsion reconciliation gate that commands await
/// (`AppStore.awaitStartupLockReconciliation`), running concurrently with hydration.
/// • `DeferredMaintenance` — the post-first-paint maintenance batch
/// (`AppStore.runDeferredStartupMaintenanceOnce`).
///
/// `@MainActor` because the in-flight interval state is shared mutable and every caller —
/// the app's init/`.task` and the store's launch tasks — is already main-actor-isolated.
@MainActor
public enum StartupSignposts {
public enum Phase: Sendable, Hashable {
case launchToFirstFrame
case hydration
case lockReconcile
case deferredMaintenance
/// Signpost interval names must be `StaticString`s and byte-identical at begin/end,
/// hence a switch over literals rather than anything derived at runtime.
var name: StaticString {
switch self {
case .launchToFirstFrame: return "LaunchToFirstFrame"
case .hydration: return "Hydration"
case .lockReconcile: return "LockReconcile"
case .deferredMaintenance: return "DeferredMaintenance"
}
}
}
#if canImport(os)
private static let signposter = OSSignposter(subsystem: "com.nucleic", category: "startup")
/// In-flight interval states by phase. Each phase runs at most once per launch; a stray
/// double-begin just replaces (and orphans) the earlier interval rather than trapping.
private static var inFlight: [Phase: OSSignpostIntervalState] = [:]
#endif
public static func begin(_ phase: Phase) {
#if canImport(os)
inFlight[phase] = signposter.beginInterval(phase.name)
#endif
}
/// Ends `phase` if it's open; a no-op otherwise (e.g. `end` on a phase whose `begin` was
/// skipped because the process isn't the GUI app).
public static func end(_ phase: Phase) {
#if canImport(os)
guard let state = inFlight.removeValue(forKey: phase) else { return }
signposter.endInterval(phase.name, state)
#endif
}
}