Skip to content
This repository was archived by the owner on Apr 23, 2021. It is now read-only.

+poc,instruments PoC of an Instruments.app instrument "tracing" (naive) #97

Merged
merged 1 commit into from
Aug 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@
/*.xcodeproj
xcuserdata/
.swiftpm

UseCases/Sources/InstrumentsAppTracingInstrpkg/SpansExampleInstrument/SpansExampleInstrument.xcodeproj/project.xcworkspace/
8 changes: 4 additions & 4 deletions Tests/TracingInstrumentationTests/SpanTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import XCTest

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

let parent = OTSpan(
let parent = OTelSpan(
operationName: "client",
startTimestamp: .now(),
context: parentContext,
kind: .client,
onEnd: { _ in }
)
let childContext = BaggageContext()
var child = OTSpan(
var child = OTelSpan(
operationName: "server",
startTimestamp: .now(),
context: childContext,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ final class JaegerTracer: TracingInstrument {
ofKind kind: SpanKind,
at timestamp: Timestamp?
) -> Span {
let span = OTSpan(
let span = OTelSpan(
operationName: operationName,
startTimestamp: timestamp ?? .now(),
context: context,
Expand Down Expand Up @@ -111,9 +111,9 @@ extension JaegerTracer {
}
}

// MARK: - OTSpan
// MARK: - OTelSpan

struct OTSpan: Span {
struct OTelSpan: Span {
let operationName: String
let kind: SpanKind

Expand Down
8 changes: 6 additions & 2 deletions UseCases/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ let package = Package(
products: [
.executable(name: "ManualContextPropagation", targets: ["ManualContextPropagation"]),
.executable(name: "ManualAsyncHTTPClient", targets: ["ManualAsyncHTTPClient"]),
.executable(name: "HTTPEndToEnd", targets: ["HTTPEndToEnd"])
.executable(name: "HTTPEndToEnd", targets: ["HTTPEndToEnd"]),
.executable(name: "InstrumentsAppTracing", targets: ["InstrumentsAppTracing"]),
],
dependencies: [
.package(name: "gsoc-swift-tracing", path: "../"),
Expand All @@ -29,6 +30,9 @@ let package = Package(
.product(name: "NIOInstrumentation", package: "gsoc-swift-tracing"),
.product(name: "AsyncHTTPClient", package: "async-http-client"),
.product(name: "NIO", package: "swift-nio"),
])
]),
.target(name: "InstrumentsAppTracing", dependencies: [
.product(name: "Instrumentation", package: "gsoc-swift-tracing"),
]),
]
)
267 changes: 267 additions & 0 deletions UseCases/Sources/InstrumentsAppTracing/OSSignpostTracing.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Tracing open source project
//
// Copyright (c) 2020 Moritz Lang and the Swift Tracing project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import Baggage
import Instrumentation
import Foundation // string conversion for os_log seems to live here

#if os(macOS) || os(tvOS) || os(iOS) || os(watchOS)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is an apple platforms only thing

import os.log
import os.signpost

// ==== ----------------------------------------------------------------------------------------------------------------
// MARK: OSSignpost Tracing

@available(OSX 10.14, *)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since signposts are only available since then.
(I may have gotten the availability wrong on other OSes)

@available(iOS 10.0, *)
@available(tvOS 10.0, *)
@available(watchOS 3.0, *)
public struct OSSignpostTracingInstrument: TracingInstrument {
let log: OSLog
let signpostName: StaticString

public init(subsystem: String, category: String, signpostName: StaticString) {
self.log = OSLog(subsystem: subsystem, category: category)
self.signpostName = signpostName
}

// ==== ------------------------------------------------------------------------------------------------------------
// MARK: Instrument API

public func extract<Carrier, Extractor>(_ carrier: Carrier, into baggage: inout BaggageContext, using extractor: Extractor) {
// noop; we could handle extracting our keys here
}

public func inject<Carrier, Injector>(_ baggage: BaggageContext, into carrier: inout Carrier, using injector: Injector) {
// noop; we could handle injecting our keys here
}

// ==== ------------------------------------------------------------------------------------------------------------
// MARK: Tracing Instrument API

public func startSpan(
named operationName: String,
context: BaggageContext,
ofKind kind: SpanKind,
at timestamp: Timestamp?
) -> Span {
OSSignpostSpan(
log: self.log,
named: operationName,
signpostName: self.signpostName,
context: context
// , kind ignored
// , timestamp ignored, we capture it automatically
)
}
}
Comment on lines +52 to +67
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note that some tracer may choose not to expose setting the timestamp explicitly by user.

this is the case with XRay tracer as well, at the moment (I dont really see a use case for that);
arguably its easier to change XRay tracer than OSSignpostSpan ;-)

BTW this is not required by OT


// ==== ----------------------------------------------------------------------------------------------------------------
// MARK: OSSignpost Span

@available(OSX 10.14, *)
@available(iOS 10.0, *)
@available(tvOS 10.0, *)
@available(watchOS 3.0, *)
final class OSSignpostSpan: Span {
let operationName: String
var context: BaggageContext

private let log: OSLog
private let signpostName: StaticString
private var signpostID: OSSignpostID {
self.context.signpostID! // guaranteed that we have "our" ID
}

// TODO: use os_unfair_lock
let lock: NSLock

public let isRecording: Bool

public let startTimestamp: Timestamp
public var endTimestamp: Timestamp?

public var status: SpanStatus? = nil
public let kind: SpanKind = .internal

public var baggage: BaggageContext {
self.context
}

static let beginFormat: StaticString =
"""
b;\
id:%{public}ld;\
parent-ids:%{public}s;\
op-name:%{public}s
"""
static let endFormat: StaticString =
"""
e;
"""

init(
log: OSLog,
named operationName: String,
signpostName: StaticString,
context: BaggageContext
) {
self.log = log
self.operationName = operationName
self.signpostName = signpostName
self.context = context

self.startTimestamp = .now() // meh
self.isRecording = log.signpostsEnabled

self.lock = NSLock()

// // if the context we were started with already had a signpostID, it means we're should link with it
// // TODO: is this right or we should rely on explicit link calls?
// if context.signpostID != nil {
// self.addLink(SpanLink(context: context))
// }

// replace signpostID with "us" i.e. this span
let signpostID = OSSignpostID(
log: log,
object: self
)
self.context.signpostID = signpostID

if self.isRecording {
os_signpost(
.begin,
log: self.log,
name: self.signpostName,
signpostID: self.signpostID,
Self.beginFormat,
self.signpostID.rawValue,
"\(context.signpostTraceParentIDs.map({ "\($0.rawValue)" }).joined(separator: ","))",
operationName
)
}
}

#if DEBUG
deinit {
// sanity checking if we don't accidentally drop spans on the floor without ending them
self.lock.lock() // TODO: somewhat bad idea, we should rather implement endTimestamp as an atomic that's lockless to read (!)
defer { self.lock.lock() }
if self.endTimestamp == nil {
print("""
warning:
Span \(self.signpostID) (\(self.operationName)) \
[todo:source location] \
was dropped without end() being called!
""")
}
}
#endif


public func addLink(_ link: SpanLink) {
guard self.isRecording else { return }
self.lock.lock()
defer { self.lock.unlock() }

guard let id = link.context.signpostID else {
print(
"""
Attempted to addLink(\(link)) to \(self.signpostID) (\(self.operationName))\
but no `signpostID` present in passed in baggage context!
"""
)
return
}

self.context.signpostTraceParentIDs += [id]
}

public func addEvent(_ event: SpanEvent) {
guard self.isRecording else { return }

// perhaps emit it as os_signpost(.event, ...)
}

public var attributes: SpanAttributes {
get {
[:] // ignore
}
set {
// ignore
}
}


public func end(at timestamp: Timestamp) {
guard self.isRecording else { return }
self.lock.lock()
defer { self.lock.unlock() }

guard self.endTimestamp == nil else {
print("warning: attempted to end() more-than-once the span: \(self.signpostID) (\(self.operationName))!")
return
}
self.endTimestamp = timestamp

os_signpost(
.end,
log: self.log,
name: self.signpostName,
signpostID: self.signpostID,
Self.endFormat
)
}
}

// ==== ----------------------------------------------------------------------------------------------------------------
// MARK: Baggage Keys

@available(OSX 10.14, *)
@available(iOS 10.0, *)
@available(tvOS 10.0, *)
@available(watchOS 3.0, *)
enum OSSignpostTracingKeys {
enum TraceParentIDs: BaggageContextKey {
typealias Value = [OSSignpostID]
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not "really" used and I don't think there's a good way to make use of this info in instruments... though I may have to learn more about it. Not a huge goal of this PoC anyway.

}
enum SignpostID: BaggageContextKey {
typealias Value = OSSignpostID
}
}

@available(OSX 10.14, *)
@available(iOS 10.0, *)
@available(tvOS 10.0, *)
@available(watchOS 3.0, *)
extension BaggageContext {
var signpostTraceParentIDs: OSSignpostTracingKeys.TraceParentIDs.Value {
get {
self[OSSignpostTracingKeys.TraceParentIDs.self] ?? []
}
set {
self[OSSignpostTracingKeys.TraceParentIDs.self] = newValue
}
}
var signpostID: OSSignpostTracingKeys.SignpostID.Value? {
get {
self[OSSignpostTracingKeys.SignpostID.self]
}
set {
self[OSSignpostTracingKeys.SignpostID.self] = newValue
}
}
}

#endif
Loading