Skip to content

Commit 20988c7

Browse files
authored
Merge pull request #97 from ktoso/instrumentsapp
+poc,instruments PoC of an Instruments.app instrument "tracing" (naive)
2 parents 91117cd + 63801ad commit 20988c7

File tree

10 files changed

+616
-9
lines changed

10 files changed

+616
-9
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@
44
/*.xcodeproj
55
xcuserdata/
66
.swiftpm
7+
8+
UseCases/Sources/InstrumentsAppTracingInstrpkg/SpansExampleInstrument/SpansExampleInstrument.xcodeproj/project.xcworkspace/

Tests/TracingInstrumentationTests/SpanTests.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ import XCTest
1818

1919
final class SpanTests: XCTestCase {
2020
func testAddingEventCreatesCopy() {
21-
// TODO: We should probably replace OTSpan at some point with a NoOpSpan for testing things like this.
22-
let span = OTSpan(
21+
// TODO: We should probably replace OTelSpan at some point with a NoOpSpan for testing things like this.
22+
let span = OTelSpan(
2323
operationName: "test",
2424
startTimestamp: .now(),
2525
context: BaggageContext(),
@@ -160,15 +160,15 @@ final class SpanTests: XCTestCase {
160160
var parentContext = BaggageContext()
161161
parentContext[TestBaggageContextKey.self] = "test"
162162

163-
let parent = OTSpan(
163+
let parent = OTelSpan(
164164
operationName: "client",
165165
startTimestamp: .now(),
166166
context: parentContext,
167167
kind: .client,
168168
onEnd: { _ in }
169169
)
170170
let childContext = BaggageContext()
171-
var child = OTSpan(
171+
var child = OTelSpan(
172172
operationName: "server",
173173
startTimestamp: .now(),
174174
context: childContext,

Tests/TracingInstrumentationTests/TracingInstrumentTests.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ final class JaegerTracer: TracingInstrument {
6060
ofKind kind: SpanKind,
6161
at timestamp: Timestamp?
6262
) -> Span {
63-
let span = OTSpan(
63+
let span = OTelSpan(
6464
operationName: operationName,
6565
startTimestamp: timestamp ?? .now(),
6666
context: context,
@@ -111,9 +111,9 @@ extension JaegerTracer {
111111
}
112112
}
113113

114-
// MARK: - OTSpan
114+
// MARK: - OTelSpan
115115

116-
struct OTSpan: Span {
116+
struct OTelSpan: Span {
117117
let operationName: String
118118
let kind: SpanKind
119119

UseCases/Package.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ let package = Package(
66
products: [
77
.executable(name: "ManualContextPropagation", targets: ["ManualContextPropagation"]),
88
.executable(name: "ManualAsyncHTTPClient", targets: ["ManualAsyncHTTPClient"]),
9-
.executable(name: "HTTPEndToEnd", targets: ["HTTPEndToEnd"])
9+
.executable(name: "HTTPEndToEnd", targets: ["HTTPEndToEnd"]),
10+
.executable(name: "InstrumentsAppTracing", targets: ["InstrumentsAppTracing"]),
1011
],
1112
dependencies: [
1213
.package(name: "gsoc-swift-tracing", path: "../"),
@@ -29,6 +30,9 @@ let package = Package(
2930
.product(name: "NIOInstrumentation", package: "gsoc-swift-tracing"),
3031
.product(name: "AsyncHTTPClient", package: "async-http-client"),
3132
.product(name: "NIO", package: "swift-nio"),
32-
])
33+
]),
34+
.target(name: "InstrumentsAppTracing", dependencies: [
35+
.product(name: "Instrumentation", package: "gsoc-swift-tracing"),
36+
]),
3337
]
3438
)
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift Distributed Tracing open source project
4+
//
5+
// Copyright (c) 2020 Moritz Lang and the Swift Tracing project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
//
10+
// SPDX-License-Identifier: Apache-2.0
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
import Baggage
15+
import Instrumentation
16+
import Foundation // string conversion for os_log seems to live here
17+
18+
#if os(macOS) || os(tvOS) || os(iOS) || os(watchOS)
19+
import os.log
20+
import os.signpost
21+
22+
// ==== ----------------------------------------------------------------------------------------------------------------
23+
// MARK: OSSignpost Tracing
24+
25+
@available(OSX 10.14, *)
26+
@available(iOS 10.0, *)
27+
@available(tvOS 10.0, *)
28+
@available(watchOS 3.0, *)
29+
public struct OSSignpostTracingInstrument: TracingInstrument {
30+
let log: OSLog
31+
let signpostName: StaticString
32+
33+
public init(subsystem: String, category: String, signpostName: StaticString) {
34+
self.log = OSLog(subsystem: subsystem, category: category)
35+
self.signpostName = signpostName
36+
}
37+
38+
// ==== ------------------------------------------------------------------------------------------------------------
39+
// MARK: Instrument API
40+
41+
public func extract<Carrier, Extractor>(_ carrier: Carrier, into baggage: inout BaggageContext, using extractor: Extractor) {
42+
// noop; we could handle extracting our keys here
43+
}
44+
45+
public func inject<Carrier, Injector>(_ baggage: BaggageContext, into carrier: inout Carrier, using injector: Injector) {
46+
// noop; we could handle injecting our keys here
47+
}
48+
49+
// ==== ------------------------------------------------------------------------------------------------------------
50+
// MARK: Tracing Instrument API
51+
52+
public func startSpan(
53+
named operationName: String,
54+
context: BaggageContext,
55+
ofKind kind: SpanKind,
56+
at timestamp: Timestamp?
57+
) -> Span {
58+
OSSignpostSpan(
59+
log: self.log,
60+
named: operationName,
61+
signpostName: self.signpostName,
62+
context: context
63+
// , kind ignored
64+
// , timestamp ignored, we capture it automatically
65+
)
66+
}
67+
}
68+
69+
// ==== ----------------------------------------------------------------------------------------------------------------
70+
// MARK: OSSignpost Span
71+
72+
@available(OSX 10.14, *)
73+
@available(iOS 10.0, *)
74+
@available(tvOS 10.0, *)
75+
@available(watchOS 3.0, *)
76+
final class OSSignpostSpan: Span {
77+
let operationName: String
78+
var context: BaggageContext
79+
80+
private let log: OSLog
81+
private let signpostName: StaticString
82+
private var signpostID: OSSignpostID {
83+
self.context.signpostID! // guaranteed that we have "our" ID
84+
}
85+
86+
// TODO: use os_unfair_lock
87+
let lock: NSLock
88+
89+
public let isRecording: Bool
90+
91+
public let startTimestamp: Timestamp
92+
public var endTimestamp: Timestamp?
93+
94+
public var status: SpanStatus? = nil
95+
public let kind: SpanKind = .internal
96+
97+
public var baggage: BaggageContext {
98+
self.context
99+
}
100+
101+
static let beginFormat: StaticString =
102+
"""
103+
b;\
104+
id:%{public}ld;\
105+
parent-ids:%{public}s;\
106+
op-name:%{public}s
107+
"""
108+
static let endFormat: StaticString =
109+
"""
110+
e;
111+
"""
112+
113+
init(
114+
log: OSLog,
115+
named operationName: String,
116+
signpostName: StaticString,
117+
context: BaggageContext
118+
) {
119+
self.log = log
120+
self.operationName = operationName
121+
self.signpostName = signpostName
122+
self.context = context
123+
124+
self.startTimestamp = .now() // meh
125+
self.isRecording = log.signpostsEnabled
126+
127+
self.lock = NSLock()
128+
129+
// // if the context we were started with already had a signpostID, it means we're should link with it
130+
// // TODO: is this right or we should rely on explicit link calls?
131+
// if context.signpostID != nil {
132+
// self.addLink(SpanLink(context: context))
133+
// }
134+
135+
// replace signpostID with "us" i.e. this span
136+
let signpostID = OSSignpostID(
137+
log: log,
138+
object: self
139+
)
140+
self.context.signpostID = signpostID
141+
142+
if self.isRecording {
143+
os_signpost(
144+
.begin,
145+
log: self.log,
146+
name: self.signpostName,
147+
signpostID: self.signpostID,
148+
Self.beginFormat,
149+
self.signpostID.rawValue,
150+
"\(context.signpostTraceParentIDs.map({ "\($0.rawValue)" }).joined(separator: ","))",
151+
operationName
152+
)
153+
}
154+
}
155+
156+
#if DEBUG
157+
deinit {
158+
// sanity checking if we don't accidentally drop spans on the floor without ending them
159+
self.lock.lock() // TODO: somewhat bad idea, we should rather implement endTimestamp as an atomic that's lockless to read (!)
160+
defer { self.lock.lock() }
161+
if self.endTimestamp == nil {
162+
print("""
163+
warning:
164+
Span \(self.signpostID) (\(self.operationName)) \
165+
[todo:source location] \
166+
was dropped without end() being called!
167+
""")
168+
}
169+
}
170+
#endif
171+
172+
173+
public func addLink(_ link: SpanLink) {
174+
guard self.isRecording else { return }
175+
self.lock.lock()
176+
defer { self.lock.unlock() }
177+
178+
guard let id = link.context.signpostID else {
179+
print(
180+
"""
181+
Attempted to addLink(\(link)) to \(self.signpostID) (\(self.operationName))\
182+
but no `signpostID` present in passed in baggage context!
183+
"""
184+
)
185+
return
186+
}
187+
188+
self.context.signpostTraceParentIDs += [id]
189+
}
190+
191+
public func addEvent(_ event: SpanEvent) {
192+
guard self.isRecording else { return }
193+
194+
// perhaps emit it as os_signpost(.event, ...)
195+
}
196+
197+
public var attributes: SpanAttributes {
198+
get {
199+
[:] // ignore
200+
}
201+
set {
202+
// ignore
203+
}
204+
}
205+
206+
207+
public func end(at timestamp: Timestamp) {
208+
guard self.isRecording else { return }
209+
self.lock.lock()
210+
defer { self.lock.unlock() }
211+
212+
guard self.endTimestamp == nil else {
213+
print("warning: attempted to end() more-than-once the span: \(self.signpostID) (\(self.operationName))!")
214+
return
215+
}
216+
self.endTimestamp = timestamp
217+
218+
os_signpost(
219+
.end,
220+
log: self.log,
221+
name: self.signpostName,
222+
signpostID: self.signpostID,
223+
Self.endFormat
224+
)
225+
}
226+
}
227+
228+
// ==== ----------------------------------------------------------------------------------------------------------------
229+
// MARK: Baggage Keys
230+
231+
@available(OSX 10.14, *)
232+
@available(iOS 10.0, *)
233+
@available(tvOS 10.0, *)
234+
@available(watchOS 3.0, *)
235+
enum OSSignpostTracingKeys {
236+
enum TraceParentIDs: BaggageContextKey {
237+
typealias Value = [OSSignpostID]
238+
}
239+
enum SignpostID: BaggageContextKey {
240+
typealias Value = OSSignpostID
241+
}
242+
}
243+
244+
@available(OSX 10.14, *)
245+
@available(iOS 10.0, *)
246+
@available(tvOS 10.0, *)
247+
@available(watchOS 3.0, *)
248+
extension BaggageContext {
249+
var signpostTraceParentIDs: OSSignpostTracingKeys.TraceParentIDs.Value {
250+
get {
251+
self[OSSignpostTracingKeys.TraceParentIDs.self] ?? []
252+
}
253+
set {
254+
self[OSSignpostTracingKeys.TraceParentIDs.self] = newValue
255+
}
256+
}
257+
var signpostID: OSSignpostTracingKeys.SignpostID.Value? {
258+
get {
259+
self[OSSignpostTracingKeys.SignpostID.self]
260+
}
261+
set {
262+
self[OSSignpostTracingKeys.SignpostID.self] = newValue
263+
}
264+
}
265+
}
266+
267+
#endif

0 commit comments

Comments
 (0)