Skip to content

Commit 3d9678a

Browse files
BridgeJS: Build fixes for profiling API
1 parent 788ad44 commit 3d9678a

File tree

2 files changed

+76
-58
lines changed

2 files changed

+76
-58
lines changed

Plugins/BridgeJS/Sources/BridgeJSCore/Misc.swift

Lines changed: 53 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,3 @@
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

123
public 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
3526
public 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
}

Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
@preconcurrency import func Foundation.exit
22
@preconcurrency import func Foundation.fputs
3+
@preconcurrency import func Foundation.open
4+
@preconcurrency import func Foundation.strerror
35
@preconcurrency import var Foundation.stderr
6+
@preconcurrency import var Foundation.errno
7+
@preconcurrency import var Foundation.O_WRONLY
8+
@preconcurrency import var Foundation.O_CREAT
9+
@preconcurrency import var Foundation.O_TRUNC
410
@preconcurrency import struct Foundation.URL
511
@preconcurrency import struct Foundation.Data
612
@preconcurrency import struct Foundation.ObjCBool
713
@preconcurrency import class Foundation.JSONEncoder
14+
@preconcurrency import class Foundation.FileHandle
815
@preconcurrency import class Foundation.FileManager
916
@preconcurrency import class Foundation.JSONDecoder
1017
@preconcurrency import class Foundation.ProcessInfo
@@ -50,7 +57,7 @@ import BridgeJSUtilities
5057

5158
static func main() throws {
5259
do {
53-
try Profiling.with {
60+
try Profiling.with(Profiling.make) {
5461
try run()
5562
}
5663
} catch {
@@ -318,6 +325,21 @@ private func inputSwiftFiles(targetDirectory: URL, positionalArguments: [String]
318325
return positionalArguments
319326
}
320327

328+
extension Profiling {
329+
static func make() -> Profiling? {
330+
guard let outputPath = ProcessInfo.processInfo.environment["BRIDGE_JS_PROFILING"] else {
331+
return nil
332+
}
333+
let fd = open(outputPath, O_WRONLY | O_CREAT | O_TRUNC, 0o644)
334+
guard fd >= 0 else {
335+
let error = String(cString: strerror(errno))
336+
fatalError("Failed to open profiling output file \(outputPath): \(error)")
337+
}
338+
let output = FileHandle(fileDescriptor: fd, closeOnDealloc: true)
339+
return Profiling.traceEvent(output: { output.write($0.data(using: .utf8) ?? Data()) })
340+
}
341+
}
342+
321343
// MARK: - Minimal Argument Parsing
322344

323345
struct OptionRule {

0 commit comments

Comments
 (0)