68 lines
3.1 KiB
Swift
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
|
|
}
|
|
}
|