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+
110// MARK: - ProgressReporting
211
312public struct ProgressReporting {
@@ -20,6 +29,112 @@ public struct ProgressReporting {
2029 }
2130}
2231
32+ // MARK: - Profiling
33+
34+ /// A simple time-profiler to emit `chrome://tracing` format
35+ public final class Profiling {
36+ nonisolated ( unsafe) static var current : Profiling ?
37+
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 ( )
51+ }
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+ }
68+
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 )
74+ }
75+
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+ }
84+ }
85+
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) " )
89+ }
90+ }
91+
92+ /// Mark a span of code with a label and measure the duration.
93+ public func withSpan< T> ( _ label: String , body: @escaping ( ) throws -> T ) rethrows -> T {
94+ guard let profiling = Profiling . current else {
95+ return try body ( )
96+ }
97+ profiling. begin ( label, profiling. clock. now)
98+ defer {
99+ profiling. end ( label, profiling. clock. now)
100+ }
101+ return try body ( )
102+ }
103+
104+ /// Foundation-less JSON serialization
105+ private enum JSON {
106+ static func serialize( _ value: String ) -> String {
107+ // https://www.ietf.org/rfc/rfc4627.txt
108+ var output = " \" "
109+ for scalar in value. unicodeScalars {
110+ switch scalar {
111+ case " \" " :
112+ output += " \\ \" "
113+ case " \\ " :
114+ output += " \\ \\ "
115+ case " \u{08} " :
116+ output += " \\ b "
117+ case " \u{0C} " :
118+ output += " \\ f "
119+ case " \n " :
120+ output += " \\ n "
121+ case " \r " :
122+ output += " \\ r "
123+ case " \t " :
124+ output += " \\ t "
125+ case " \u{20} " ... " \u{21} " , " \u{23} " ... " \u{5B} " , " \u{5D} " ... " \u{10FFFF} " :
126+ output. unicodeScalars. append ( scalar)
127+ default :
128+ var hex = String ( scalar. value, radix: 16 , uppercase: true )
129+ hex = String ( repeating: " 0 " , count: 4 - hex. count) + hex
130+ output += " \\ u " + hex
131+ }
132+ }
133+ output += " \" "
134+ return output
135+ }
136+ }
137+
23138// MARK: - DiagnosticError
24139
25140import SwiftSyntax
0 commit comments