1- import class Foundation. FileHandle
2- import class Foundation. ProcessInfo
3- import func Foundation. open
4- import func Foundation. strerror
5- import var Foundation. errno
6- import var Foundation. O_WRONLY
7- import var Foundation. O_CREAT
8- import var Foundation. O_TRUNC
9-
101// MARK: - ProgressReporting
112
123public struct ProgressReporting {
@@ -31,61 +22,66 @@ public struct ProgressReporting {
3122
3223// MARK: - Profiling
3324
34- /// A simple time-profiler to emit `chrome://tracing` format
25+ /// A simple time-profiler API
3526public final class Profiling {
3627 nonisolated ( unsafe) static var current : Profiling ?
3728
38- let startTime : ContinuousClock . Instant
39- let clock = ContinuousClock ( )
40- let output : @Sendable ( String ) -> Void
41- var firstEntry = true
42-
43- init ( output: @Sendable @escaping ( String ) -> Void ) {
44- self . startTime = ContinuousClock . now
45- self . output = output
46- }
47-
48- public static func with( body: @escaping ( ) throws -> Void ) rethrows -> Void {
49- guard let outputPath = ProcessInfo . processInfo. environment [ " BRIDGE_JS_PROFILING " ] else {
50- return try body ( )
29+ let beginEntry : ( _ label: String ) -> Void
30+ let endEntry : ( _ label: String ) -> Void
31+ let finalize : ( ) -> Void
32+
33+ /// Create a profiling instance that outputs Trace Event Format, which
34+ /// can be viewed in chrome://tracing or other compatible viewers.
35+ /// https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/edit?usp=sharing
36+ public static func traceEvent( output: @escaping ( String ) -> Void ) -> Profiling {
37+ let clock = ContinuousClock ( )
38+ let startTime = clock. now
39+ var firstEntry = true
40+
41+ func formatTimestamp( ) -> Int {
42+ let duration = startTime. duration ( to: . now)
43+ let ( seconds, attoseconds) = duration. components
44+ // Convert to microseconds
45+ return Int ( seconds * 1_000_000 + attoseconds / 1_000_000_000_000 )
5146 }
52- let fd = open ( outputPath, O_WRONLY | O_CREAT | O_TRUNC, 0o644 )
53- guard fd >= 0 else {
54- let error = String ( cString: strerror ( errno) )
55- fatalError ( " Failed to open profiling output file \( outputPath) : \( error) " )
56- }
57- let output = FileHandle ( fileDescriptor: fd, closeOnDealloc: true )
58- let profiling = Profiling ( output: { output. write ( $0. data ( using: . utf8) ?? Data ( ) ) } )
59- defer {
60- profiling. output ( " ] \n " )
61- }
62- Profiling . current = profiling
63- defer {
64- Profiling . current = nil
65- }
66- return try body ( )
67- }
6847
69- private func formatTimestamp( instant: ContinuousClock . Instant ) -> Int {
70- let duration = self . startTime. duration ( to: instant)
71- let ( seconds, attoseconds) = duration. components
72- // Convert to microseconds
73- return Int ( seconds * 1_000_000 + attoseconds / 1_000_000_000_000 )
48+ return Profiling (
49+ beginEntry: { label in
50+ let entry = #"{"ph":"B","pid":1,"name": \#( JSON . serialize ( label) ) ,"ts": \#( formatTimestamp ( ) ) }"#
51+ if firstEntry {
52+ firstEntry = false
53+ output ( " [ \n \( entry) " )
54+ } else {
55+ output ( " , \n \( entry) " )
56+ }
57+ } ,
58+ endEntry: { label in
59+ output ( #",\n{"ph":"E","pid":1,"name": \#( JSON . serialize ( label) ) ,"ts": \#( formatTimestamp ( ) ) }"# )
60+ } ,
61+ finalize: {
62+ output ( " ] \n " )
63+ }
64+ )
7465 }
7566
76- func begin ( _ label : String , _ instant : ContinuousClock . Instant ) {
77- let entry = #"{"ph":"B","pid":1,"name": \#( JSON . serialize ( label) ) ,"ts": \#( formatTimestamp ( instant : instant ) ) }"#
78- if firstEntry {
79- firstEntry = false
80- output ( " [ \n \( entry ) " )
81- } else {
82- output ( " , \n \( entry ) " )
83- }
67+ public init (
68+ beginEntry : @escaping ( _ label: String ) -> Void ,
69+ endEntry : @escaping ( _ label : String ) -> Void ,
70+ finalize : @escaping ( ) -> Void
71+ ) {
72+ self . beginEntry = beginEntry
73+ self . endEntry = endEntry
74+ self . finalize = finalize
8475 }
8576
86- func end( _ label: String , _ instant: ContinuousClock . Instant ) {
87- let entry = #"{"ph":"E","pid":1,"name": \#( JSON . serialize ( label) ) ,"ts": \#( formatTimestamp ( instant: instant) ) }"#
88- output ( " , \n \( entry) " )
77+ public static func with( _ makeCurrent: ( ) -> Profiling ? , body: @escaping ( ) throws -> Void ) rethrows -> Void {
78+ guard let current = makeCurrent ( ) else {
79+ return try body ( )
80+ }
81+ defer { current. finalize ( ) }
82+ Profiling . current = current
83+ defer { Profiling . current = nil }
84+ return try body ( )
8985 }
9086}
9187
@@ -94,9 +90,9 @@ public func withSpan<T>(_ label: String, body: @escaping () throws -> T) rethrow
9490 guard let profiling = Profiling . current else {
9591 return try body ( )
9692 }
97- profiling. begin ( label, profiling . clock . now )
93+ profiling. beginEntry ( label)
9894 defer {
99- profiling. end ( label, profiling . clock . now )
95+ profiling. endEntry ( label)
10096 }
10197 return try body ( )
10298}
0 commit comments