From a5b5160ec8b457f05150c401eed9cad7eb8f82ac Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 29 Mar 2023 19:44:36 +0900 Subject: [PATCH] Implement TracerClock and abstract away "now" (#98) --- Sources/Instrumentation/Instrument.swift | 2 +- .../InstrumentationSystem.swift | 2 +- Sources/Instrumentation/Locks.swift | 2 +- .../Instrumentation/MultiplexInstrument.swift | 2 +- Sources/Instrumentation/NoOpInstrument.swift | 2 +- .../InstrumentationSystem+Tracing.swift | 2 +- Sources/Tracing/NoOpTracer.swift | 23 +- Sources/Tracing/SpanProtocol.swift | 41 +-- Sources/Tracing/Tracer.swift | 150 ++++++++-- Sources/Tracing/TracerProtocol+Legacy.swift | 267 +++++++++++++----- Sources/Tracing/TracerProtocol.swift | 136 +++++++-- Sources/Tracing/TracingTime.swift | 184 ++++++++++++ .../_TracingBenchmarkTools/ArgParser.swift | 2 +- .../BenchmarkCategory.swift | 2 +- .../BenchmarkTools.swift | 2 +- .../_TracingBenchmarkTools/DriverUtils.swift | 2 +- .../SpanAttributesDSLBenchmark.swift | 2 +- Sources/_TracingBenchmarks/main.swift | 2 +- .../InstrumentTests.swift | 2 +- .../InstrumentationSystemTests.swift | 2 +- Tests/TracingTests/ActorTracingTests.swift | 39 +++ .../DynamicTracepointTracerTests.swift | 58 ++-- Tests/TracingTests/SpanTests.swift | 10 +- Tests/TracingTests/TestTracer.swift | 32 +-- Tests/TracingTests/TracedLock.swift | 2 +- Tests/TracingTests/TracedLockTests.swift | 31 +- Tests/TracingTests/TracerTests.swift | 37 ++- Tests/TracingTests/TracerTimeTests.swift | 74 +++++ .../TracingInstrumentationSystemTests.swift | 2 +- scripts/validate_license_headers.sh | 2 +- 30 files changed, 886 insertions(+), 230 deletions(-) create mode 100644 Sources/Tracing/TracingTime.swift create mode 100644 Tests/TracingTests/ActorTracingTests.swift create mode 100644 Tests/TracingTests/TracerTimeTests.swift diff --git a/Sources/Instrumentation/Instrument.swift b/Sources/Instrumentation/Instrument.swift index 59ff81c..7695535 100644 --- a/Sources/Instrumentation/Instrument.swift +++ b/Sources/Instrumentation/Instrument.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2022 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2023 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // diff --git a/Sources/Instrumentation/InstrumentationSystem.swift b/Sources/Instrumentation/InstrumentationSystem.swift index ac72be2..11e9b7d 100644 --- a/Sources/Instrumentation/InstrumentationSystem.swift +++ b/Sources/Instrumentation/InstrumentationSystem.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2022 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2023 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // diff --git a/Sources/Instrumentation/Locks.swift b/Sources/Instrumentation/Locks.swift index ae50cb7..606432f 100644 --- a/Sources/Instrumentation/Locks.swift +++ b/Sources/Instrumentation/Locks.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2021 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2023 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // diff --git a/Sources/Instrumentation/MultiplexInstrument.swift b/Sources/Instrumentation/MultiplexInstrument.swift index fd96c19..21e03c4 100644 --- a/Sources/Instrumentation/MultiplexInstrument.swift +++ b/Sources/Instrumentation/MultiplexInstrument.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2022 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2023 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // diff --git a/Sources/Instrumentation/NoOpInstrument.swift b/Sources/Instrumentation/NoOpInstrument.swift index 040da89..f007566 100644 --- a/Sources/Instrumentation/NoOpInstrument.swift +++ b/Sources/Instrumentation/NoOpInstrument.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2022 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2023 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // diff --git a/Sources/Tracing/InstrumentationSystem+Tracing.swift b/Sources/Tracing/InstrumentationSystem+Tracing.swift index a67710e..feef86b 100644 --- a/Sources/Tracing/InstrumentationSystem+Tracing.swift +++ b/Sources/Tracing/InstrumentationSystem+Tracing.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2022 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2023 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // diff --git a/Sources/Tracing/NoOpTracer.swift b/Sources/Tracing/NoOpTracer.swift index 2c93016..1fe2d7a 100644 --- a/Sources/Tracing/NoOpTracer.swift +++ b/Sources/Tracing/NoOpTracer.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2022 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2023 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // @@ -17,18 +17,19 @@ import Dispatch @_exported import InstrumentationBaggage /// Tracer that ignores all operations, used when no tracing is required. +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) // for TaskLocal Baggage public struct NoOpTracer: LegacyTracerProtocol { public typealias TracerSpan = NoOpSpan public init() {} - public func startAnySpan(_ operationName: String, - baggage: @autoclosure () -> Baggage, - ofKind kind: SpanKind, - at time: DispatchWallTime, - function: String, - file fileID: String, - line: UInt) -> any Span + public func startAnySpan(_ operationName: String, + baggage: @autoclosure () -> Baggage, + ofKind kind: SpanKind, + clock: Clock, + function: String, + file fileID: String, + line: UInt) -> any Span { NoOpSpan(baggage: baggage()) } @@ -83,7 +84,7 @@ public struct NoOpTracer: LegacyTracerProtocol { } } - public func end(at time: DispatchWallTime) { + public func end(clock: Clock) { // ignore } } @@ -91,11 +92,11 @@ public struct NoOpTracer: LegacyTracerProtocol { #if swift(>=5.7.0) extension NoOpTracer: TracerProtocol { - public func startSpan( + public func startSpan( _ operationName: String, baggage: @autoclosure () -> Baggage, ofKind kind: SpanKind, - at time: DispatchWallTime, + clock: Clock, function: String, file fileID: String, line: UInt diff --git a/Sources/Tracing/SpanProtocol.swift b/Sources/Tracing/SpanProtocol.swift index 30eef12..2b3bea1 100644 --- a/Sources/Tracing/SpanProtocol.swift +++ b/Sources/Tracing/SpanProtocol.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2022 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2023 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // @@ -12,11 +12,6 @@ // //===----------------------------------------------------------------------===// -#if swift(>=5.6.0) -@preconcurrency import struct Dispatch.DispatchWallTime -#else -import struct Dispatch.DispatchWallTime -#endif @_exported import InstrumentationBaggage /// A `Span` represents an interval from the start of an operation to its end, along with additional metadata included @@ -98,10 +93,11 @@ public protocol Span: _SwiftTracingSendableSpan { /// Implementations SHOULD prevent double-emitting by marking a span as ended internally, however it still is a /// programming mistake to rely on this behavior. /// - /// - Parameter time: The `DispatchWallTime` at which the span ended. + /// Parameters: + /// - clock: The clock to use as time source for the start time of the ``Span`` /// /// - SeeAlso: `Span.end()` which automatically uses the "current" time. - func end(at time: DispatchWallTime) + func end(clock: Clock) } extension Span { @@ -115,12 +111,10 @@ extension Span { /// Implementations SHOULD prevent double-emitting by marking a span as ended internally, however it still is a /// programming mistake to rely on this behavior. /// - /// - Parameter time: The `DispatchWallTime` at which the span ended. - /// - /// - SeeAlso: ``end(at:)`` which allows passing in a specific time, e.g. if the operation was ended and recorded somewhere and we need to post-factum record it. + /// - SeeAlso: ``end(clock:)`` which allows passing in a specific time, e.g. if the operation was ended and recorded somewhere and we need to post-factum record it. /// Generally though prefer using the ``end()`` version of this API in user code and structure your system such that it can be called in the right place and time. public func end() { - self.end(at: .now()) + self.end(clock: DefaultTracerClock()) } /// Adds a ``SpanLink`` between this `Span` and the given `Span`. @@ -153,18 +147,31 @@ public struct SpanEvent: Equatable { /// One or more ``SpanAttribute``s with the same restrictions as defined for ``Span`` attributes. public var attributes: SpanAttributes - /// The `DispatchWallTime` at which this event occurred. - public let time: DispatchWallTime + /// The timestamp at which this event occurred. + /// + /// It should be expressed as the number of milliseconds since UNIX Epoch (January 1st 1970). + public let millisecondsSinceEpoch: UInt64 /// Create a new `SpanEvent`. /// - Parameters: /// - name: The human-readable name of this event. /// - attributes: attributes describing this event. Defaults to no attributes. - /// - time: The `DispatchWallTime` at which this event occurred. Defaults to `.now()`. - public init(name: String, attributes: SpanAttributes = [:], at time: DispatchWallTime = .now()) { + /// - clock: The clock to use as time source for the start time of the ``Span`` + public init(name: String, + clock: Clock, + attributes: SpanAttributes = [:]) + { + self.name = name + self.attributes = attributes + self.millisecondsSinceEpoch = clock.now.millisecondsSinceEpoch + } + + public init(name: String, + attributes: SpanAttributes = [:]) + { self.name = name self.attributes = attributes - self.time = time + self.millisecondsSinceEpoch = DefaultTracerClock.now.millisecondsSinceEpoch } } diff --git a/Sources/Tracing/Tracer.swift b/Sources/Tracing/Tracer.swift index 2138a96..110bdd3 100644 --- a/Sources/Tracing/Tracer.swift +++ b/Sources/Tracing/Tracer.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2022 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2023 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // @@ -44,43 +44,84 @@ extension Tracer { /// /// - Parameters: /// - operationName: The name of the operation being traced. This may be a handler function, database call, ... + /// - clock: The clock to use as time source for the start time of the ``Span`` /// - baggage: The `Baggage` providing information on where to start the new ``Span``. /// - kind: The ``SpanKind`` of the new ``Span``. - /// - time: The time at which to start the new ``Span``. /// - function: The function name in which the span was started /// - fileID: The `fileID` where the span was started. /// - line: The file line where the span was started. - static func startSpan( + @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) // for TaskLocal Baggage + static func startSpan( _ operationName: String, + clock: Clock, baggage: @autoclosure () -> Baggage = .current ?? .topLevel, ofKind kind: SpanKind = .internal, - at time: DispatchWallTime = .now(), function: String = #function, file fileID: String = #fileID, line: UInt = #line ) -> any Span { // Effectively these end up calling the same method, however // we try to not use the deprecated methods ourselves anyway - #if swift(>=5.7.0) - InstrumentationSystem.tracer.startSpan( + InstrumentationSystem.legacyTracer.startAnySpan( operationName, + clock: clock, + baggage: baggage(), + ofKind: kind, function: function, file: fileID, line: line ) - #else + } + + @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) // for TaskLocal Baggage + static func startSpan( + _ operationName: String, + baggage: @autoclosure () -> Baggage = .current ?? .topLevel, + ofKind kind: SpanKind = .internal, + function: String = #function, + file fileID: String = #fileID, + line: UInt = #line + ) -> any Span { + // Effectively these end up calling the same method, however + // we try to not use the deprecated methods ourselves anyway InstrumentationSystem.legacyTracer.startAnySpan( operationName, + clock: DefaultTracerClock(), baggage: baggage(), ofKind: kind, - at: time, function: function, file: fileID, line: line ) - #endif } + #if swift(>=5.7.0) + @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) // for TaskLocal Baggage + static func startSpan( + _ operationName: String, + baggage: @autoclosure () -> Baggage = .current ?? .topLevel, + ofKind kind: SpanKind = .internal, + clock: some TracerClock = DefaultTracerClock(), + function: String = #function, + file fileID: String = #fileID, + line: UInt = #line + ) -> any Span { + // Effectively these end up calling the same method, however + // we try to not use the deprecated methods ourselves anyway + InstrumentationSystem.tracer.startAnySpan( + operationName, + clock: clock, + baggage: baggage(), + ofKind: kind, + function: function, + file: fileID, + line: line + ) + } + #endif + + // ==== withSpan + sync --------------------------------------------------- + /// Start a new ``Span`` and automatically end when the `operation` completes, /// including recording the `error` in case the operation throws. /// @@ -97,13 +138,38 @@ extension Tracer { /// - operationName: The name of the operation being traced. This may be a handler function, database call, ... /// - baggage: The `Baggage` providing information on where to start the new ``Span``. /// - kind: The ``SpanKind`` of the new ``Span``. - /// - time: The time at which to start the new ``Span``. + /// - clock: The clock to use as time source for the start time of the ``Span`` /// - function: The function name in which the span was started /// - fileID: The `fileID` where the span was started. /// - line: The file line where the span was started. /// - operation: The operation that this span should be measuring /// - Returns: the value returned by `operation` /// - Throws: the error the `operation` has thrown (if any) + @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) // for TaskLocal Baggage + public static func withSpan( + _ operationName: String, + clock: Clock, + baggage: @autoclosure () -> Baggage = .current ?? .topLevel, + ofKind kind: SpanKind = .internal, + function: String = #function, + file fileID: String = #fileID, + line: UInt = #line, + _ operation: (any Span) throws -> T + ) rethrows -> T { + try InstrumentationSystem.legacyTracer.withAnySpan( + operationName, + clock: DefaultTracerClock(), + baggage: baggage(), + ofKind: kind, + function: function, + file: fileID, + line: line + ) { anySpan in + try operation(anySpan) + } + } + + @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) // for TaskLocal Baggage public static func withSpan( _ operationName: String, baggage: @autoclosure () -> Baggage = .current ?? .topLevel, @@ -113,9 +179,10 @@ extension Tracer { line: UInt = #line, _ operation: (any Span) throws -> T ) rethrows -> T { - #if swift(>=5.7.0) try InstrumentationSystem.legacyTracer.withAnySpan( operationName, + clock: DefaultTracerClock(), + baggage: baggage(), ofKind: kind, function: function, file: fileID, @@ -123,9 +190,23 @@ extension Tracer { ) { anySpan in try operation(anySpan) } - #else + } + + #if swift(>=5.7.0) + public static func withSpan( + _ operationName: String, + baggage: @autoclosure () -> Baggage = .current ?? .topLevel, + ofKind kind: SpanKind = .internal, + clock: some TracerClock = DefaultTracerClock(), + function: String = #function, + file fileID: String = #fileID, + line: UInt = #line, + _ operation: (any Span) throws -> T + ) rethrows -> T { try InstrumentationSystem.legacyTracer.withAnySpan( operationName, + clock: clock, + baggage: baggage(), ofKind: kind, function: function, file: fileID, @@ -133,8 +214,10 @@ extension Tracer { ) { anySpan in try operation(anySpan) } - #endif } + #endif + + // ==== withSpan + async -------------------------------------------------- /// Start a new ``Span`` and automatically end when the `operation` completes, /// including recording the `error` in case the operation throws. @@ -152,21 +235,42 @@ extension Tracer { /// - operationName: The name of the operation being traced. This may be a handler function, database call, ... /// - baggage: The `Baggage` providing information on where to start the new ``Span``. /// - kind: The ``SpanKind`` of the new ``Span``. - /// - time: The time at which to start the new ``Span``. + /// - clock: The clock to use as time source for the start time of the ``Span`` /// - function: The function name in which the span was started /// - fileID: The `fileID` where the span was started. /// - line: The file line where the span was started. /// - operation: The operation that this span should be measuring /// - Returns: the value returned by `operation` /// - Throws: the error the `operation` has thrown (if any) - #if swift(>=5.7.0) - @_unsafeInheritExecutor - @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) + @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) // for TaskLocal Baggage + public static func withSpan( + _ operationName: String, + clock: Clock, + baggage: @autoclosure () -> Baggage = .current ?? .topLevel, + ofKind kind: SpanKind = .internal, + function: String = #function, + file fileID: String = #fileID, + line: UInt = #line, + _ operation: (any Span) async throws -> T + ) async rethrows -> T { + try await InstrumentationSystem.legacyTracer.withAnySpan( + operationName, + clock: DefaultTracerClock(), + baggage: baggage(), + ofKind: kind, + function: function, + file: fileID, + line: line + ) { anySpan in + try await operation(anySpan) + } + } + + @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) // for TaskLocal Baggage public static func withSpan( _ operationName: String, baggage: @autoclosure () -> Baggage = .current ?? .topLevel, ofKind kind: SpanKind = .internal, - at time: DispatchWallTime = .now(), function: String = #function, file fileID: String = #fileID, line: UInt = #line, @@ -174,9 +278,9 @@ extension Tracer { ) async rethrows -> T { try await InstrumentationSystem.legacyTracer.withAnySpan( operationName, + clock: DefaultTracerClock(), baggage: baggage(), ofKind: kind, - at: time, function: function, file: fileID, line: line @@ -184,13 +288,13 @@ extension Tracer { try await operation(anySpan) } } - #else // TODO: remove this if/else when we require 5.7; it is only here to add @_unsafeInheritExecutor - @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) + + #if swift(>=5.7.0) public static func withSpan( _ operationName: String, baggage: @autoclosure () -> Baggage = .current ?? .topLevel, ofKind kind: SpanKind = .internal, - at time: DispatchWallTime = .now(), + clock: some TracerClock = DefaultTracerClock(), function: String = #function, file fileID: String = #fileID, line: UInt = #line, @@ -198,9 +302,9 @@ extension Tracer { ) async rethrows -> T { try await InstrumentationSystem.legacyTracer.withAnySpan( operationName, + clock: clock, baggage: baggage(), ofKind: kind, - at: time, function: function, file: fileID, line: line diff --git a/Sources/Tracing/TracerProtocol+Legacy.swift b/Sources/Tracing/TracerProtocol+Legacy.swift index 07e3da6..407942f 100644 --- a/Sources/Tracing/TracerProtocol+Legacy.swift +++ b/Sources/Tracing/TracerProtocol+Legacy.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2022 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2023 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // @@ -20,8 +20,7 @@ import Dispatch public protocol LegacyTracerProtocol: InstrumentProtocol { /// Start a new span returning an existential ``Span`` reference. /// - /// This API will be deprecated as soon as Swift 5.9 is released, and the Swift 5.7 requiring `TracerProtocol` - /// is recommended instead. + /// - Warning: This method will be deprecated in favor of `TracerProtocol/withSpan` as soon as this project is able to require Swift 5.7. /// /// The current task-local `Baggage` is picked up and provided to the underlying tracer. /// It is also possible to pass a specific `baggage` explicitly, in which case attempting @@ -43,15 +42,15 @@ public protocol LegacyTracerProtocol: InstrumentProtocol { /// - operationName: The name of the operation being traced. This may be a handler function, database call, ... /// - baggage: The `Baggage` providing information on where to start the new ``Span``. /// - kind: The ``SpanKind`` of the new ``Span``. - /// - time: The time at which to start the new ``Span``. + /// - clock: The clock to use as time source for the start time of the ``Span`` /// - function: The function name in which the span was started /// - fileID: The `fileID` where the span was started. /// - line: The file line where the span was started. - func startAnySpan( + func startAnySpan( _ operationName: String, baggage: @autoclosure () -> Baggage, ofKind kind: SpanKind, - at time: DispatchWallTime, + clock: Clock, function: String, file fileID: String, line: UInt @@ -71,10 +70,11 @@ public protocol LegacyTracerProtocol: InstrumentProtocol { @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) // for TaskLocal Baggage extension LegacyTracerProtocol { + // ==== startSpan --------------------------------------------------------- + /// Start a new span returning an existential ``Span`` reference. /// - /// This API will be deprecated as soon as Swift 5.9 is released, and the Swift 5.7 requiring `TracerProtocol` - /// is recommended instead. + /// - Warning: This method will be deprecated in favor of `TracerProtocol/withSpan` as soon as this project is able to require Swift 5.7. /// /// The current task-local `Baggage` is picked up and provided to the underlying tracer. /// It is also possible to pass a specific `baggage` explicitly, in which case attempting @@ -96,15 +96,15 @@ extension LegacyTracerProtocol { /// - operationName: The name of the operation being traced. This may be a handler function, database call, ... /// - baggage: The `Baggage` providing information on where to start the new ``Span``. /// - kind: The ``SpanKind`` of the new ``Span``. - /// - time: The time at which to start the new ``Span``. + /// - clock: The clock to use as time source for the start time of the ``Span`` /// - function: The function name in which the span was started /// - fileID: The `fileID` where the span was started. /// - line: The file line where the span was started. - public func startAnySpan( + public func startAnySpan( _ operationName: String, + clock: Clock, baggage: @autoclosure () -> Baggage = .current ?? .topLevel, ofKind kind: SpanKind = .internal, - at time: DispatchWallTime = .now(), function: String = #function, file fileID: String = #fileID, line: UInt = #line @@ -113,18 +113,17 @@ extension LegacyTracerProtocol { operationName, baggage: baggage(), ofKind: kind, - at: time, + clock: clock, function: function, file: fileID, line: line ) } - /// Start a new ``Span`` and automatically end when the `operation` completes, - /// including recording the `error` in case the operation throws. + /// Start a new span returning an existential ``Span`` reference. + /// + /// - Warning: This method will be deprecated in favor of `TracerProtocol/withSpan` as soon as this project is able to require Swift 5.7. /// - /// This API will be deprecated as soon as Swift 5.9 is released, and the Swift 5.7 requiring `TracerProtocol` - /// is recommended instead. /// /// The current task-local `Baggage` is picked up and provided to the underlying tracer. /// It is also possible to pass a specific `baggage` explicitly, in which case attempting @@ -146,24 +145,72 @@ extension LegacyTracerProtocol { /// - operationName: The name of the operation being traced. This may be a handler function, database call, ... /// - baggage: The `Baggage` providing information on where to start the new ``Span``. /// - kind: The ``SpanKind`` of the new ``Span``. - /// - time: The time at which to start the new ``Span``. + /// - function: The function name in which the span was started + /// - fileID: The `fileID` where the span was started. + /// - line: The file line where the span was started. + public func startAnySpan( + _ operationName: String, + baggage: @autoclosure () -> Baggage = .current ?? .topLevel, + ofKind kind: SpanKind = .internal, + function: String = #function, + file fileID: String = #fileID, + line: UInt = #line + ) -> any Span { + self.startAnySpan( + operationName, + baggage: baggage(), + ofKind: kind, + clock: DefaultTracerClock(), + function: function, + file: fileID, + line: line + ) + } + + // ==== withAnySpan + sync ------------------------------------------------ + + /// Start a new ``Span`` and automatically end when the `operation` completes, + /// including recording the `error` in case the operation throws. + /// + /// The current task-local `Baggage` is picked up and provided to the underlying tracer. + /// It is also possible to pass a specific `baggage` explicitly, in which case attempting + /// to pick up the task-local baggage is prevented. This can be useful when we know that + /// we're about to start a top-level span, or if a span should be started from a different, + /// stored away previously, + /// + /// - Warning: You MUST NOT ``Span/end()`` the span explicitly, because at the end of the `withSpan` + /// operation closure returning the span will be closed automatically. + /// + /// - Parameters: + /// - operationName: The name of the operation being traced. This may be a handler function, database call, ... + /// - baggage: The `Baggage` providing information on where to start the new ``Span``. + /// - kind: The ``SpanKind`` of the new ``Span``. + /// - clock: The clock to use as time source for the start time of the ``Span`` /// - function: The function name in which the span was started /// - fileID: The `fileID` where the span was started. /// - line: The file line where the span was started. /// - operation: The operation that this span should be measuring /// - Returns: the value returned by `operation` /// - Throws: the error the `operation` has thrown (if any) - public func withAnySpan( + public func withAnySpan( _ operationName: String, + clock: Clock, baggage: @autoclosure () -> Baggage = .current ?? .topLevel, ofKind kind: SpanKind = .internal, - at time: DispatchWallTime = .now(), function: String = #function, file fileID: String = #fileID, line: UInt = #line, _ operation: (any Span) throws -> T ) rethrows -> T { - let span = self.startAnySpan(operationName, baggage: baggage(), ofKind: kind, at: time, function: function, file: fileID, line: line) + let span = self.startAnySpan( + operationName, + baggage: baggage(), + ofKind: kind, + clock: clock, + function: function, + file: fileID, + line: line + ) defer { span.end() } do { return try Baggage.$current.withValue(span.baggage) { @@ -178,8 +225,7 @@ extension LegacyTracerProtocol { /// Start a new ``Span`` and automatically end when the `operation` completes, /// including recording the `error` in case the operation throws. /// - /// This API will be deprecated as soon as Swift 5.9 is released, and the Swift 5.7 requiring `TracerProtocol` - /// is recommended instead. + /// - Warning: This method will be deprecated in favor of `TracerProtocol/withSpan` as soon as this project is able to require Swift 5.7. /// /// The current task-local `Baggage` is picked up and provided to the underlying tracer. /// It is also possible to pass a specific `baggage` explicitly, in which case attempting @@ -187,21 +233,114 @@ extension LegacyTracerProtocol { /// we're about to start a top-level span, or if a span should be started from a different, /// stored away previously, /// - /// - Note: Legacy API, prefer using ``startSpan(_:baggage:ofKind:at: + /// - Warning: You MUST NOT ``Span/end()`` the span explicitly, because at the end of the `withSpan` + /// operation closure returning the span will be closed automatically. /// - /// - Note: Prefer ``withSpan(_:baggage:ofKind:at:function:file:line:operation:)`` to start - /// a span as it automatically takes care of ending the span, and recording errors when thrown. - /// Use `startSpan` iff you need to pass the span manually to a different - /// location in your source code to end it. + /// - Parameters: + /// - operationName: The name of the operation being traced. This may be a handler function, database call, ... + /// - baggage: The `Baggage` providing information on where to start the new ``Span``. + /// - kind: The ``SpanKind`` of the new ``Span``. + /// - function: The function name in which the span was started + /// - fileID: The `fileID` where the span was started. + /// - line: The file line where the span was started. + /// - operation: The operation that this span should be measuring + /// - Returns: the value returned by `operation` + /// - Throws: the error the `operation` has thrown (if any) + public func withAnySpan( + _ operationName: String, + baggage: @autoclosure () -> Baggage = .current ?? .topLevel, + ofKind kind: SpanKind = .internal, + function: String = #function, + file fileID: String = #fileID, + line: UInt = #line, + _ operation: (any Span) throws -> T + ) rethrows -> T { + try self.withAnySpan( + operationName, + clock: DefaultTracerClock(), + baggage: baggage(), + ofKind: kind, + function: function, + file: fileID, + line: line, + operation + ) + } + + // ==== withAnySpan async ------------------------------------------------- + + /// Start a new ``Span`` and automatically end when the `operation` completes, + /// including recording the `error` in case the operation throws. /// - /// - Warning: You must `end()` the span when it the measured operation has completed explicitly, - /// otherwise the span object will potentially never be released nor reported. + /// - Warning: This method will be deprecated in favor of `TracerProtocol/withSpan` as soon as this project is able to require Swift 5.7. + /// + /// The current task-local `Baggage` is picked up and provided to the underlying tracer. + /// It is also possible to pass a specific `baggage` explicitly, in which case attempting + /// to pick up the task-local baggage is prevented. This can be useful when we know that + /// we're about to start a top-level span, or if a span should be started from a different, + /// stored away previously, + /// + /// - Warning: You MUST NOT ``Span/end()`` the span explicitly, because at the end of the `withSpan` + /// operation closure returning the span will be closed automatically. + /// + /// - Parameters: + /// - operationName: The name of the operation being traced. This may be a handler function, database call, ... + /// - baggage: The `Baggage` providing information on where to start the new ``Span``. + /// - kind: The ``SpanKind`` of the new ``Span``. + /// - function: The function name in which the span was started + /// - fileID: The `fileID` where the span was started. + /// - line: The file line where the span was started. + /// - operation: The operation that this span should be measuring + /// - Returns: the value returned by `operation` + /// - Throws: the error the `operation` has thrown (if any) + public func withAnySpan( + _ operationName: String, + clock: Clock, + baggage: @autoclosure () -> Baggage = .current ?? .topLevel, + ofKind kind: SpanKind = .internal, + function: String = #function, + file fileID: String = #fileID, + line: UInt = #line, + _ operation: (any Span) async throws -> T + ) async rethrows -> T { + let span = self.startAnySpan( + operationName, + clock: clock, + baggage: baggage(), + ofKind: kind, + function: function, + file: fileID, + line: line + ) + defer { span.end() } + do { + return try await Baggage.$current.withValue(span.baggage) { + try await operation(span) + } + } catch { + span.recordError(error) + throw error // rethrow + } + } + + /// Start a new ``Span`` and automatically end when the `operation` completes, + /// including recording the `error` in case the operation throws. + /// + /// - Warning: This method will be deprecated in favor of `TracerProtocol/withSpan` as soon as this project is able to require Swift 5.7. + /// + /// The current task-local `Baggage` is picked up and provided to the underlying tracer. + /// It is also possible to pass a specific `baggage` explicitly, in which case attempting + /// to pick up the task-local baggage is prevented. This can be useful when we know that + /// we're about to start a top-level span, or if a span should be started from a different, + /// stored away previously, + /// + /// - Warning: You MUST NOT ``Span/end()`` the span explicitly, because at the end of the `withSpan` + /// operation closure returning the span will be closed automatically. /// /// - Parameters: /// - operationName: The name of the operation being traced. This may be a handler function, database call, ... /// - baggage: The `Baggage` providing information on where to start the new ``Span``. /// - kind: The ``SpanKind`` of the new ``Span``. - /// - time: The time at which to start the new ``Span``. /// - function: The function name in which the span was started /// - fileID: The `fileID` where the span was started. /// - line: The file line where the span was started. @@ -212,13 +351,20 @@ extension LegacyTracerProtocol { _ operationName: String, baggage: @autoclosure () -> Baggage = .current ?? .topLevel, ofKind kind: SpanKind = .internal, - at time: DispatchWallTime = .now(), function: String = #function, file fileID: String = #fileID, line: UInt = #line, _ operation: (any Span) async throws -> T ) async rethrows -> T { - let span = self.startAnySpan(operationName, baggage: baggage(), ofKind: kind, at: time, function: function, file: fileID, line: line) + let span = self.startAnySpan( + operationName, + clock: DefaultTracerClock(), + baggage: baggage(), + ofKind: kind, + function: function, + file: fileID, + line: line + ) defer { span.end() } do { return try await Baggage.$current.withValue(span.baggage) { @@ -234,12 +380,10 @@ extension LegacyTracerProtocol { #if swift(>=5.7.0) // Provide compatibility shims of the `...AnySpan` APIs to the 5.7 requiring `TracerProtocol`. -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) extension TracerProtocol { /// Start a new span returning an existential ``Span`` reference. /// - /// This API will be deprecated as soon as Swift 5.9 is released, and the Swift 5.7 requiring `TracerProtocol` - /// is recommended instead. + /// - Warning: This method will be deprecated in favor of `TracerProtocol/withSpan` as soon as this project is able to require Swift 5.7. /// /// The current task-local `Baggage` is picked up and provided to the underlying tracer. /// It is also possible to pass a specific `baggage` explicitly, in which case attempting @@ -261,15 +405,15 @@ extension TracerProtocol { /// - operationName: The name of the operation being traced. This may be a handler function, database call, ... /// - baggage: The `Baggage` providing information on where to start the new ``Span``. /// - kind: The ``SpanKind`` of the new ``Span``. - /// - time: The time at which to start the new ``Span``. + /// - clock: The clock to use as time source for the start time of the ``Span`` /// - function: The function name in which the span was started /// - fileID: The `fileID` where the span was started. /// - line: The file line where the span was started. public func startAnySpan( _ operationName: String, + clock: some TracerClock = DefaultTracerClock(), baggage: @autoclosure () -> Baggage = .current ?? .topLevel, ofKind kind: SpanKind = .internal, - at time: DispatchWallTime = .now(), function: String = #function, file fileID: String = #fileID, line: UInt = #line @@ -278,7 +422,7 @@ extension TracerProtocol { operationName, baggage: baggage(), ofKind: kind, - at: time, + clock: clock, function: function, file: fileID, line: line @@ -288,8 +432,7 @@ extension TracerProtocol { /// Start a new ``Span`` and automatically end when the `operation` completes, /// including recording the `error` in case the operation throws. /// - /// This API will be deprecated as soon as Swift 5.9 is released, and the Swift 5.7 requiring `TracerProtocol` - /// is recommended instead. + /// - Warning: This method will be deprecated in favor of `TracerProtocol/withSpan` as soon as this project is able to require Swift 5.7. /// /// The current task-local `Baggage` is picked up and provided to the underlying tracer. /// It is also possible to pass a specific `baggage` explicitly, in which case attempting @@ -311,7 +454,7 @@ extension TracerProtocol { /// - operationName: The name of the operation being traced. This may be a handler function, database call, ... /// - baggage: The `Baggage` providing information on where to start the new ``Span``. /// - kind: The ``SpanKind`` of the new ``Span``. - /// - time: The time at which to start the new ``Span``. + /// - clock: The clock to use as time source for the start time of the ``Span`` /// - function: The function name in which the span was started /// - fileID: The `fileID` where the span was started. /// - line: The file line where the span was started. @@ -320,9 +463,9 @@ extension TracerProtocol { /// - Throws: the error the `operation` has thrown (if any) public func withAnySpan( _ operationName: String, + clock: some TracerClock = DefaultTracerClock(), baggage: @autoclosure () -> Baggage = .current ?? .topLevel, ofKind kind: SpanKind = .internal, - at time: DispatchWallTime = .now(), function: String = #function, file fileID: String = #fileID, line: UInt = #line, @@ -332,7 +475,7 @@ extension TracerProtocol { operationName, baggage: baggage(), ofKind: kind, - at: time, + clock: clock, function: function, file: fileID, line: line @@ -344,8 +487,7 @@ extension TracerProtocol { /// Start a new ``Span`` and automatically end when the `operation` completes, /// including recording the `error` in case the operation throws. /// - /// This API will be deprecated as soon as Swift 5.9 is released, and the Swift 5.7 requiring `TracerProtocol` - /// is recommended instead. + /// - Warning: This method will be deprecated in favor of `TracerProtocol/withSpan` as soon as this project is able to require Swift 5.7. /// /// The current task-local `Baggage` is picked up and provided to the underlying tracer. /// It is also possible to pass a specific `baggage` explicitly, in which case attempting @@ -367,54 +509,28 @@ extension TracerProtocol { /// - operationName: The name of the operation being traced. This may be a handler function, database call, ... /// - baggage: The `Baggage` providing information on where to start the new ``Span``. /// - kind: The ``SpanKind`` of the new ``Span``. - /// - time: The time at which to start the new ``Span``. + /// - clock: The clock to use as time source for the start time of the ``Span`` /// - function: The function name in which the span was started /// - fileID: The `fileID` where the span was started. /// - line: The file line where the span was started. /// - operation: The operation that this span should be measuring /// - Returns: the value returned by `operation` /// - Throws: the error the `operation` has thrown (if any) - #if swift(>=5.7.0) - @_unsafeInheritExecutor public func withAnySpan( _ operationName: String, + clock: some TracerClock = DefaultTracerClock(), baggage: @autoclosure () -> Baggage = .current ?? .topLevel, ofKind kind: SpanKind = .internal, - at time: DispatchWallTime = .now(), function: String = #function, file fileID: String = #fileID, line: UInt = #line, - _ operation: (any Span) async throws -> T - ) async rethrows -> T { - try await self.withSpan( - operationName, - baggage: baggage(), - ofKind: kind, - at: time, - function: function, - file: fileID, - line: line - ) { span in - try await operation(span) - } - } - #else // TODO: remove this if/else when we require 5.7; it is only here to add @_unsafeInheritExecutor - @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) - public func withAnySpan( - _ operationName: String, - baggage: @autoclosure () -> Baggage = .current ?? .topLevel, - ofKind kind: SpanKind = .internal, - at time: DispatchWallTime = .now(), - function: String = #function, - file fileID: String = #fileID, - line: UInt = #line, - _ operation: (any Span) async throws -> T + @_inheritActorContext @_implicitSelfCapture _ operation: (any Span) async throws -> T ) async rethrows -> T { try await self.withSpan( operationName, baggage: baggage(), ofKind: kind, - at: time, + clock: clock, function: function, file: fileID, line: line @@ -422,6 +538,5 @@ extension TracerProtocol { try await operation(span) } } - #endif } #endif diff --git a/Sources/Tracing/TracerProtocol.swift b/Sources/Tracing/TracerProtocol.swift index 9017334..c063a42 100644 --- a/Sources/Tracing/TracerProtocol.swift +++ b/Sources/Tracing/TracerProtocol.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2022 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2023 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // @@ -12,7 +12,6 @@ // //===----------------------------------------------------------------------===// -import Dispatch @_exported import Instrumentation @_exported import InstrumentationBaggage @@ -20,6 +19,7 @@ import Dispatch // MARK: Tracer protocol #if swift(>=5.7.0) + /// A tracer capable of creating new trace spans. /// /// A tracer is a special kind of instrument with the added ability to start a ``Span``. @@ -48,15 +48,15 @@ public protocol TracerProtocol: LegacyTracerProtocol { /// - operationName: The name of the operation being traced. This may be a handler function, database call, ... /// - baggage: The `Baggage` providing information on where to start the new ``Span``. /// - kind: The ``SpanKind`` of the new ``Span``. - /// - time: The time at which to start the new ``Span``. + /// - clock: The clock to use as time source for the start time of the ``Span`` /// - function: The function name in which the span was started /// - fileID: The `fileID` where the span was started. /// - line: The file line where the span was started. - func startSpan( + func startSpan( _ operationName: String, baggage: @autoclosure () -> Baggage, ofKind kind: SpanKind, - at time: DispatchWallTime, + clock: Clock, function: String, file fileID: String, line: UInt @@ -85,7 +85,7 @@ extension TracerProtocol { /// - operationName: The name of the operation being traced. This may be a handler function, database call, ... /// - baggage: The `Baggage` providing information on where to start the new ``Span``. /// - kind: The ``SpanKind`` of the new ``Span``. - /// - time: The time at which to start the new ``Span``. + /// - clock: The clock to use as time source for the start time of the ``Span`` /// - function: The function name in which the span was started /// - fileID: The `fileID` where the span was started. /// - line: The file line where the span was started. @@ -93,7 +93,7 @@ extension TracerProtocol { _ operationName: String, baggage: @autoclosure () -> Baggage = .current ?? .topLevel, ofKind kind: SpanKind = .internal, - at time: DispatchWallTime = .now(), + clock: some TracerClock = DefaultTracerClock(), function: String = #function, file fileID: String = #fileID, line: UInt = #line @@ -102,7 +102,7 @@ extension TracerProtocol { operationName, baggage: baggage(), ofKind: kind, - at: time, + clock: clock, function: function, file: fileID, line: line @@ -113,7 +113,6 @@ extension TracerProtocol { // ==== ---------------------------------------------------------------------------------------------------------------- // MARK: Starting spans: `withSpan` -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) // for TaskLocal Baggage extension TracerProtocol { /// Start a new ``Span`` and automatically end when the `operation` completes, /// including recording the `error` in case the operation throws. @@ -131,7 +130,7 @@ extension TracerProtocol { /// - operationName: The name of the operation being traced. This may be a handler function, database call, ... /// - baggage: The `Baggage` providing information on where to start the new ``Span``. /// - kind: The ``SpanKind`` of the new ``Span``. - /// - time: The time at which to start the new ``Span``. + /// - clock: The clock to use as time source for the start time of the ``Span`` /// - function: The function name in which the span was started /// - fileID: The `fileID` where the span was started. /// - line: The file line where the span was started. @@ -142,7 +141,7 @@ extension TracerProtocol { _ operationName: String, baggage: @autoclosure () -> Baggage = .current ?? .topLevel, ofKind kind: SpanKind = .internal, - at time: DispatchWallTime = .now(), + clock: some TracerClock = DefaultTracerClock(), function: String = #function, file fileID: String = #fileID, line: UInt = #line, @@ -152,7 +151,7 @@ extension TracerProtocol { operationName, baggage: baggage(), ofKind: kind, - at: time, + clock: clock, function: function, file: fileID, line: line @@ -184,29 +183,132 @@ extension TracerProtocol { /// - operationName: The name of the operation being traced. This may be a handler function, database call, ... /// - baggage: The `Baggage` providing information on where to start the new ``Span``. /// - kind: The ``SpanKind`` of the new ``Span``. - /// - time: The time at which to start the new ``Span``. + /// - clock: The clock to use as time source for the start time of the ``Span`` + /// - function: The function name in which the span was started + /// - fileID: The `fileID` where the span was started. + /// - line: The file line where the span was started. + /// - operation: The operation that this span should be measuring + /// - Returns: the value returned by `operation` + /// - Throws: the error the `operation` has thrown (if any) + public func withSpan( + _ operationName: String, + baggage: @autoclosure () -> Baggage = .current ?? .topLevel, + ofKind kind: SpanKind = .internal, + function: String = #function, + file fileID: String = #fileID, + line: UInt = #line, + _ operation: (TracerSpan) throws -> T + ) rethrows -> T { + let span = self.startSpan( + operationName, + baggage: baggage(), + ofKind: kind, + clock: DefaultTracerClock(), + function: function, + file: fileID, + line: line + ) + defer { span.end() } + do { + return try Baggage.$current.withValue(span.baggage) { + try operation(span) + } + } catch { + span.recordError(error) + throw error // rethrow + } + } + + /// Start a new ``Span`` and automatically end when the `operation` completes, + /// including recording the `error` in case the operation throws. + /// + /// The current task-local `Baggage` is picked up and provided to the underlying tracer. + /// It is also possible to pass a specific `baggage` explicitly, in which case attempting + /// to pick up the task-local baggage is prevented. This can be useful when we know that + /// we're about to start a top-level span, or if a span should be started from a different, + /// stored away previously, + /// + /// - Warning: You MUST NOT ``Span/end()`` the span explicitly, because at the end of the `withSpan` + /// operation closure returning the span will be closed automatically. + /// + /// - Parameters: + /// - operationName: The name of the operation being traced. This may be a handler function, database call, ... + /// - baggage: The `Baggage` providing information on where to start the new ``Span``. + /// - kind: The ``SpanKind`` of the new ``Span``. + /// - clock: The clock to use as time source for the start time of the ``Span`` + /// - function: The function name in which the span was started + /// - fileID: The `fileID` where the span was started. + /// - line: The file line where the span was started. + /// - operation: The operation that this span should be measuring + /// - Returns: the value returned by `operation` + /// - Throws: the error the `operation` has thrown (if any) + public func withSpan( + _ operationName: String, + baggage: @autoclosure () -> Baggage = .current ?? .topLevel, + ofKind kind: SpanKind = .internal, + function: String = #function, + file fileID: String = #fileID, + line: UInt = #line, + @_inheritActorContext @_implicitSelfCapture _ operation: (TracerSpan) async throws -> T + ) async rethrows -> T { + let span = self.startSpan( + operationName, + baggage: baggage(), + ofKind: kind, + clock: DefaultTracerClock(), + function: function, + file: fileID, + line: line + ) + defer { span.end() } + do { + return try await Baggage.$current.withValue(span.baggage) { + try await operation(span) + } + } catch { + span.recordError(error) + throw error // rethrow + } + } + + /// Start a new ``Span`` and automatically end when the `operation` completes, + /// including recording the `error` in case the operation throws. + /// + /// The current task-local `Baggage` is picked up and provided to the underlying tracer. + /// It is also possible to pass a specific `baggage` explicitly, in which case attempting + /// to pick up the task-local baggage is prevented. This can be useful when we know that + /// we're about to start a top-level span, or if a span should be started from a different, + /// stored away previously, + /// + /// - Warning: You MUST NOT ``Span/end()`` the span explicitly, because at the end of the `withSpan` + /// operation closure returning the span will be closed automatically. + /// + /// - Parameters: + /// - operationName: The name of the operation being traced. This may be a handler function, database call, ... + /// - baggage: The `Baggage` providing information on where to start the new ``Span``. + /// - kind: The ``SpanKind`` of the new ``Span``. + /// - clock: The clock to use as time source for the start time of the ``Span`` /// - function: The function name in which the span was started /// - fileID: The `fileID` where the span was started. /// - line: The file line where the span was started. /// - operation: The operation that this span should be measuring /// - Returns: the value returned by `operation` /// - Throws: the error the `operation` has thrown (if any) - @_unsafeInheritExecutor public func withSpan( _ operationName: String, baggage: @autoclosure () -> Baggage = .current ?? .topLevel, ofKind kind: SpanKind = .internal, - at time: DispatchWallTime = .now(), + clock: some TracerClock = DefaultTracerClock(), function: String = #function, file fileID: String = #fileID, line: UInt = #line, - _ operation: (TracerSpan) async throws -> T + @_inheritActorContext @_implicitSelfCapture _ operation: (TracerSpan) async throws -> T ) async rethrows -> T { let span = self.startSpan( operationName, baggage: baggage(), ofKind: kind, - at: time, + clock: clock, function: function, file: fileID, line: line diff --git a/Sources/Tracing/TracingTime.swift b/Sources/Tracing/TracingTime.swift new file mode 100644 index 0000000..f3a8c4a --- /dev/null +++ b/Sources/Tracing/TracingTime.swift @@ -0,0 +1,184 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Distributed Tracing open source project +// +// Copyright (c) 2020-2023 Apple Inc. and the Swift Distributed Tracing project +// authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +#if os(Linux) +import Glibc +#else +import Darwin +#endif + +@_exported import Instrumentation +@_exported import InstrumentationBaggage + +public protocol SwiftDistributedTracingDurationProtocol: Comparable, AdditiveArithmetic, Sendable { + static func / (_ lhs: Self, _ rhs: Int) -> Self + static func /= (_ lhs: inout Self, _ rhs: Int) + static func * (_ lhs: Self, _ rhs: Int) -> Self + static func *= (_ lhs: inout Self, _ rhs: Int) + + static func / (_ lhs: Self, _ rhs: Self) -> Double +} + +extension SwiftDistributedTracingDurationProtocol { + public static func /= (_ lhs: inout Self, _ rhs: Int) { + lhs = lhs / rhs + } +} + +public protocol SwiftDistributedTracingInstantProtocol: Comparable, Hashable, Sendable {} + +public protocol TracerInstantProtocol: SwiftDistributedTracingInstantProtocol { + /// Representation of this instant as the number of milliseconds since UNIX Epoch (January 1st 1970) + @inlinable + var millisecondsSinceEpoch: UInt64 { get } +} + +/// A specialized clock protocol for purposes of tracing. +/// +/// A tracer clock must ONLY be able to offer the current time in the form of an unix timestamp. +/// It does not have to allow sleeping, nor is it interchangeable with other notions of clocks (e.g. such as monotonic time etc). +/// +/// If the standard library, or foundation, or someone else were to implement an UTCClock or UNIXTimestampClock, +/// they can be made to conform to `TracerClock`. +/// +/// The primary purpose of this clock protocol is to enable mocking the "now" time when starting and ending spans, +/// especially when the system is already using some notion of simulated or mocked time, such that traces are +/// expressed using the same notion of time. +public protocol TracerClock { + associatedtype Instant: TracerInstantProtocol + + var now: Self.Instant { get } +} + +/// A basic "timestamp clock" implementation that is able to five the current time as an unix timestamp. +public struct DefaultTracerClock: TracerClock { + public typealias Instant = Timestamp + internal typealias TimeInterval = Double + + public init() { + // empty + } + + public struct Timestamp: TracerInstantProtocol { + /// Milliseconds since January 1st, 1970, also known as "unix epoch". + public var millisecondsSinceEpoch: UInt64 + + internal init(millisecondsSinceEpoch: UInt64) { + self.millisecondsSinceEpoch = millisecondsSinceEpoch + } + + public static func < (lhs: Instant, rhs: Instant) -> Bool { + lhs.millisecondsSinceEpoch < rhs.millisecondsSinceEpoch + } + + public static func == (lhs: Instant, rhs: Instant) -> Bool { + lhs.millisecondsSinceEpoch == rhs.millisecondsSinceEpoch + } + + public func hash(into hasher: inout Hasher) { + self.millisecondsSinceEpoch.hash(into: &hasher) + } + } + + public static var now: Self.Instant { + DefaultTracerClock().now + } + + public var now: Self.Instant { + var ts = timespec() + clock_gettime(CLOCK_REALTIME, &ts) + /// We use unsafe arithmetic here because `UInt64.max` nanoseconds is more than 580 years, + /// and the odds that this code will still be running 530 years from now is very, very low, + /// so as a practical matter this will never overflow. + let nowNanos = UInt64(ts.tv_sec) &* 1_000_000_000 &+ UInt64(ts.tv_nsec) + let nowMillis = UInt64(nowNanos / 1_000_000) // nanos to millis + + return Instant(millisecondsSinceEpoch: nowMillis) + } +} + +#if swift(>=5.7.0) +@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) +extension Duration { + typealias Value = Int64 + + var nanoseconds: Value { + let (seconds, attoseconds) = self.components + let sNanos = seconds * Value(1_000_000_000) + let asNanos = attoseconds / Value(1_000_000_000) + let (totalNanos, overflow) = sNanos.addingReportingOverflow(asNanos) + return overflow ? .max : totalNanos + } + + /// The microseconds representation of the `TimeAmount`. + var microseconds: Value { + self.nanoseconds / TimeUnit.microseconds.rawValue + } + + /// The milliseconds representation of the `TimeAmount`. + var milliseconds: Value { + self.nanoseconds / TimeUnit.milliseconds.rawValue + } + + /// The seconds representation of the `TimeAmount`. + var seconds: Value { + self.nanoseconds / TimeUnit.seconds.rawValue + } + + var isEffectivelyInfinite: Bool { + self.nanoseconds == .max + } +} + +@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) +extension Duration { + private func chooseUnit(_ ns: Value) -> TimeUnit { + if ns / TimeUnit.seconds.rawValue > 0 { + return TimeUnit.seconds + } else if ns / TimeUnit.milliseconds.rawValue > 0 { + return TimeUnit.milliseconds + } else if ns / TimeUnit.microseconds.rawValue > 0 { + return TimeUnit.microseconds + } else { + return TimeUnit.nanoseconds + } + } + + /// Represents number of nanoseconds within given time unit + enum TimeUnit: Value { + case seconds = 1_000_000_000 + case milliseconds = 1_000_000 + case microseconds = 1000 + case nanoseconds = 1 + + var abbreviated: String { + switch self { + case .nanoseconds: return "ns" + case .microseconds: return "μs" + case .milliseconds: return "ms" + case .seconds: return "s" + } + } + + func duration(_ duration: Int) -> Duration { + switch self { + case .nanoseconds: return .nanoseconds(Value(duration)) + case .microseconds: return .microseconds(Value(duration)) + case .milliseconds: return .milliseconds(Value(duration)) + case .seconds: return .seconds(Value(duration)) + } + } + } +} +#endif diff --git a/Sources/_TracingBenchmarkTools/ArgParser.swift b/Sources/_TracingBenchmarkTools/ArgParser.swift index 16ddb61..518defb 100644 --- a/Sources/_TracingBenchmarkTools/ArgParser.swift +++ b/Sources/_TracingBenchmarkTools/ArgParser.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2021 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2023 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // diff --git a/Sources/_TracingBenchmarkTools/BenchmarkCategory.swift b/Sources/_TracingBenchmarkTools/BenchmarkCategory.swift index 126cc16..174fa28 100644 --- a/Sources/_TracingBenchmarkTools/BenchmarkCategory.swift +++ b/Sources/_TracingBenchmarkTools/BenchmarkCategory.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2021 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2023 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // diff --git a/Sources/_TracingBenchmarkTools/BenchmarkTools.swift b/Sources/_TracingBenchmarkTools/BenchmarkTools.swift index e48793d..1a906ee 100644 --- a/Sources/_TracingBenchmarkTools/BenchmarkTools.swift +++ b/Sources/_TracingBenchmarkTools/BenchmarkTools.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2021 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2023 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // diff --git a/Sources/_TracingBenchmarkTools/DriverUtils.swift b/Sources/_TracingBenchmarkTools/DriverUtils.swift index 5968c0c..8c33366 100644 --- a/Sources/_TracingBenchmarkTools/DriverUtils.swift +++ b/Sources/_TracingBenchmarkTools/DriverUtils.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2021 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2023 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // diff --git a/Sources/_TracingBenchmarks/SpanAttributesDSLBenchmark.swift b/Sources/_TracingBenchmarks/SpanAttributesDSLBenchmark.swift index 8281fc5..1f1faf6 100644 --- a/Sources/_TracingBenchmarks/SpanAttributesDSLBenchmark.swift +++ b/Sources/_TracingBenchmarks/SpanAttributesDSLBenchmark.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2021 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2023 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // diff --git a/Sources/_TracingBenchmarks/main.swift b/Sources/_TracingBenchmarks/main.swift index 4ae4b3a..6613a37 100644 --- a/Sources/_TracingBenchmarks/main.swift +++ b/Sources/_TracingBenchmarks/main.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2021 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2023 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // diff --git a/Tests/InstrumentationTests/InstrumentTests.swift b/Tests/InstrumentationTests/InstrumentTests.swift index cada7ed..f33e8b9 100644 --- a/Tests/InstrumentationTests/InstrumentTests.swift +++ b/Tests/InstrumentationTests/InstrumentTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2021 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2023 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // diff --git a/Tests/InstrumentationTests/InstrumentationSystemTests.swift b/Tests/InstrumentationTests/InstrumentationSystemTests.swift index 5f0a2c8..efd2d4c 100644 --- a/Tests/InstrumentationTests/InstrumentationSystemTests.swift +++ b/Tests/InstrumentationTests/InstrumentationSystemTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2021 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2023 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // diff --git a/Tests/TracingTests/ActorTracingTests.swift b/Tests/TracingTests/ActorTracingTests.swift new file mode 100644 index 0000000..6d6bd7f --- /dev/null +++ b/Tests/TracingTests/ActorTracingTests.swift @@ -0,0 +1,39 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Distributed Tracing open source project +// +// Copyright (c) 2020-2023 Apple Inc. and the Swift Distributed Tracing project +// authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +@testable import Instrumentation +import InstrumentationBaggage +import Tracing +import XCTest + +final class ActorTracingTests: XCTestCase { + override class func tearDown() { + super.tearDown() + InstrumentationSystem.bootstrapInternal(nil) + } +} + +func work() async {} + +actor Foo { + var bar = 0 + func foo() async { + var num = 0 + await Tracer.withSpan(#function) { _ in + bar += 1 + await work() + num += 1 + } + } +} diff --git a/Tests/TracingTests/DynamicTracepointTracerTests.swift b/Tests/TracingTests/DynamicTracepointTracerTests.swift index 1904d63..cc872b3 100644 --- a/Tests/TracingTests/DynamicTracepointTracerTests.swift +++ b/Tests/TracingTests/DynamicTracepointTracerTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2021 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2023 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // @@ -24,7 +24,6 @@ final class DynamicTracepointTracerTests: XCTestCase { } func test_adhoc_enableBySourceLoc() { - #if swift(>=5.5) let tracer = DynamicTracepointTestTracer() InstrumentationSystem.bootstrapInternal(tracer) @@ -72,7 +71,6 @@ final class DynamicTracepointTracerTests: XCTestCase { } XCTAssertEqual(tracer.spans[0].baggage.spanID, "span-id-fake-\(fileID)-\(fakeLine)") XCTAssertEqual(tracer.spans[1].baggage.spanID, "span-id-fake-\(fileID)-\(fakeNextLine)") - #endif } func test_adhoc_enableByFunction() { @@ -167,11 +165,11 @@ final class DynamicTracepointTestTracer: LegacyTracerProtocol { var onEndSpan: (any Span) -> Void = { _ in } - func startAnySpan( + func startAnySpan( _ operationName: String, baggage: @autoclosure () -> Baggage, ofKind kind: SpanKind, - at time: DispatchWallTime, + clock: Clock, function: String, file fileID: String, line: UInt @@ -183,12 +181,12 @@ final class DynamicTracepointTestTracer: LegacyTracerProtocol { let span = TracepointSpan( operationName: operationName, - startTime: time, + startTime: clock.now, baggage: baggage(), kind: kind, file: fileID, line: line, - onEnd: onEndSpan + onEnd: self.onEndSpan ) self.spans.append(span) return span @@ -260,8 +258,8 @@ extension DynamicTracepointTestTracer { private var status: SpanStatus? - private let startTime: DispatchWallTime - private(set) var endTime: DispatchWallTime? + private let startTime: UInt64 + private(set) var endTime: UInt64? public var operationName: String private(set) var baggage: Baggage @@ -272,7 +270,7 @@ extension DynamicTracepointTestTracer { static func notRecording(file fileID: String, line: UInt) -> TracepointSpan { let span = TracepointSpan( operationName: "", - startTime: .now(), + startTime: DefaultTracerClock().now, baggage: .topLevel, kind: .internal, file: fileID, @@ -283,16 +281,16 @@ extension DynamicTracepointTestTracer { return span } - init(operationName: String, - startTime: DispatchWallTime, - baggage: Baggage, - kind: SpanKind, - file fileID: String, - line: UInt, - onEnd: @escaping (TracepointSpan) -> Void) + init(operationName: String, + startTime: Instant, + baggage: Baggage, + kind: SpanKind, + file fileID: String, + line: UInt, + onEnd: @escaping (TracepointSpan) -> Void) { self.operationName = operationName - self.startTime = startTime + self.startTime = startTime.millisecondsSinceEpoch self.baggage = baggage self.onEnd = onEnd self.kind = kind @@ -324,8 +322,8 @@ extension DynamicTracepointTestTracer { // nothing } - func end(at time: DispatchWallTime) { - self.endTime = time + func end(clock: Clock) { + self.endTime = clock.now.millisecondsSinceEpoch self.onEnd(self) } } @@ -335,13 +333,13 @@ extension DynamicTracepointTestTracer { extension DynamicTracepointTestTracer: TracerProtocol { typealias TracerSpan = TracepointSpan - func startSpan(_ operationName: String, - baggage: @autoclosure () -> Baggage, - ofKind kind: Tracing.SpanKind, - at time: DispatchWallTime, - function: String, - file fileID: String, - line: UInt) -> TracepointSpan + func startSpan(_ operationName: String, + baggage: @autoclosure () -> Baggage, + ofKind kind: Tracing.SpanKind, + clock: Clock, + function: String, + file fileID: String, + line: UInt) -> TracepointSpan { let tracepoint = TracepointID(function: function, fileID: fileID, line: line) guard self.shouldRecord(tracepoint: tracepoint) else { @@ -350,12 +348,12 @@ extension DynamicTracepointTestTracer: TracerProtocol { let span = TracepointSpan( operationName: operationName, - startTime: time, + startTime: clock.now, baggage: baggage(), kind: kind, file: fileID, line: line, - onEnd: onEndSpan + onEnd: self.onEndSpan ) self.spans.append(span) return span @@ -363,7 +361,5 @@ extension DynamicTracepointTestTracer: TracerProtocol { } #endif -#if compiler(>=5.6.0) extension DynamicTracepointTestTracer: @unchecked Sendable {} // only intended for single threaded testing extension DynamicTracepointTestTracer.TracepointSpan: @unchecked Sendable {} // only intended for single threaded testing -#endif diff --git a/Tests/TracingTests/SpanTests.swift b/Tests/TracingTests/SpanTests.swift index 1ddd03b..52cb06f 100644 --- a/Tests/TracingTests/SpanTests.swift +++ b/Tests/TracingTests/SpanTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2021 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2023 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // @@ -161,7 +161,7 @@ final class SpanTests: XCTestCase { let parent = TestSpan( operationName: "client", - startTime: .now(), + startTime: DefaultTracerClock.now, baggage: parentBaggage, kind: .client, onEnd: { _ in } @@ -169,7 +169,7 @@ final class SpanTests: XCTestCase { let childBaggage = Baggage.topLevel let child = TestSpan( operationName: "server", - startTime: .now(), + startTime: DefaultTracerClock.now, baggage: childBaggage, kind: .server, onEnd: { _ in } @@ -195,7 +195,7 @@ final class SpanTests: XCTestCase { let parent = TestSpan( operationName: "client", - startTime: .now(), + startTime: DefaultTracerClock.now, baggage: parentBaggage, kind: .client, onEnd: { _ in } @@ -203,7 +203,7 @@ final class SpanTests: XCTestCase { let childBaggage = Baggage.topLevel let child = TestSpan( operationName: "server", - startTime: .now(), + startTime: DefaultTracerClock.now, baggage: childBaggage, kind: .server, onEnd: { _ in } diff --git a/Tests/TracingTests/TestTracer.swift b/Tests/TracingTests/TestTracer.swift index 44940f9..eee74e6 100644 --- a/Tests/TracingTests/TestTracer.swift +++ b/Tests/TracingTests/TestTracer.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2021 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2023 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // @@ -23,21 +23,21 @@ final class TestTracer: LegacyTracerProtocol { private(set) var spans = [TestSpan]() var onEndSpan: (TestSpan) -> Void = { _ in } - func startAnySpan( + func startAnySpan( _ operationName: String, baggage: @autoclosure () -> Baggage, ofKind kind: SpanKind, - at time: DispatchWallTime, + clock: Clock, function: String, file fileID: String, line: UInt ) -> any Span { let span = TestSpan( operationName: operationName, - startTime: time, + startTime: clock.now, baggage: baggage(), kind: kind, - onEnd: onEndSpan + onEnd: self.onEndSpan ) self.spans.append(span) return span @@ -66,21 +66,21 @@ final class TestTracer: LegacyTracerProtocol { #if swift(>=5.7.0) extension TestTracer: TracerProtocol { - func startSpan( + func startSpan( _ operationName: String, baggage: @autoclosure () -> Baggage, ofKind kind: SpanKind, - at time: DispatchWallTime, + clock: Clock, function: String, file fileID: String, line: UInt ) -> TestSpan { let span = TestSpan( operationName: operationName, - startTime: time, + startTime: clock.now, baggage: baggage(), kind: kind, - onEnd: onEndSpan + onEnd: self.onEndSpan ) self.spans.append(span) return span @@ -124,8 +124,8 @@ final class TestSpan: Span { private var status: SpanStatus? - private let startTime: DispatchWallTime - private(set) var endTime: DispatchWallTime? + public let startTime: UInt64 + public private(set) var endTime: UInt64? private(set) var recordedErrors: [(Error, SpanAttributes)] = [] @@ -150,15 +150,15 @@ final class TestSpan: Span { let onEnd: (TestSpan) -> Void - init( + init( operationName: String, - startTime: DispatchWallTime, + startTime: Instant, baggage: Baggage, kind: SpanKind, onEnd: @escaping (TestSpan) -> Void ) { self.operationName = operationName - self.startTime = startTime + self.startTime = startTime.millisecondsSinceEpoch self.baggage = baggage self.onEnd = onEnd self.kind = kind @@ -181,8 +181,8 @@ final class TestSpan: Span { self.recordedErrors.append((error, attributes)) } - func end(at time: DispatchWallTime) { - self.endTime = time + func end(clock: Clock) { + self.endTime = clock.now.millisecondsSinceEpoch self.onEnd(self) } } diff --git a/Tests/TracingTests/TracedLock.swift b/Tests/TracingTests/TracedLock.swift index 946a2f1..5d0f80f 100644 --- a/Tests/TracingTests/TracedLock.swift +++ b/Tests/TracingTests/TracedLock.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2021 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2023 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // diff --git a/Tests/TracingTests/TracedLockTests.swift b/Tests/TracingTests/TracedLockTests.swift index 1aff598..9294bf9 100644 --- a/Tests/TracingTests/TracedLockTests.swift +++ b/Tests/TracingTests/TracedLockTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2021 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2023 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // @@ -60,18 +60,18 @@ enum TaskIDKey: BaggageKey { /// Only intended to be used in single-threaded testing. private final class TracedLockPrintlnTracer: LegacyTracerProtocol { - func startAnySpan( + func startAnySpan( _ operationName: String, baggage: @autoclosure () -> Baggage, ofKind kind: SpanKind, - at time: DispatchWallTime, + clock: Clock, function: String, file fileID: String, line: UInt ) -> any Span { TracedLockPrintlnSpan( operationName: operationName, - startTime: time, + startTime: clock.now, kind: kind, baggage: baggage() ) @@ -102,8 +102,8 @@ private final class TracedLockPrintlnTracer: LegacyTracerProtocol { private var status: SpanStatus? - private let startTime: DispatchWallTime - private(set) var endTime: DispatchWallTime? + private let startTimeMillis: UInt64 + private(set) var endTimeMillis: UInt64? var operationName: String let baggage: Baggage @@ -124,18 +124,18 @@ private final class TracedLockPrintlnTracer: LegacyTracerProtocol { private(set) var isRecording = false - init( + init( operationName: String, - startTime: DispatchWallTime, + startTime: Instant, kind: SpanKind, baggage: Baggage ) { self.operationName = operationName - self.startTime = startTime + self.startTimeMillis = startTime.millisecondsSinceEpoch self.baggage = baggage self.kind = kind - print(" span [\(self.operationName): \(self.baggage[TaskIDKey.self] ?? "no-name")] @ \(self.startTime): start") + print(" span [\(self.operationName): \(self.baggage[TaskIDKey.self] ?? "no-name")] @ \(self.startTimeMillis): start") } func setStatus(_ status: SpanStatus) { @@ -153,8 +153,9 @@ private final class TracedLockPrintlnTracer: LegacyTracerProtocol { func recordError(_ error: Error, attributes: SpanAttributes) {} - func end(at time: DispatchWallTime) { - self.endTime = time + func end(clock: Clock) { + let time = clock.now + self.endTimeMillis = time.millisecondsSinceEpoch print(" span [\(self.operationName): \(self.baggage[TaskIDKey.self] ?? "no-name")] @ \(time): end") } } @@ -162,18 +163,18 @@ private final class TracedLockPrintlnTracer: LegacyTracerProtocol { #if swift(>=5.7.0) extension TracedLockPrintlnTracer: TracerProtocol { - func startSpan( + func startSpan( _ operationName: String, baggage: @autoclosure () -> Baggage, ofKind kind: SpanKind, - at time: DispatchWallTime, + clock: Clock, function: String, file fileID: String, line: UInt ) -> TracedLockPrintlnSpan { TracedLockPrintlnSpan( operationName: operationName, - startTime: time, + startTime: clock.now, kind: kind, baggage: baggage() ) diff --git a/Tests/TracingTests/TracerTests.swift b/Tests/TracingTests/TracerTests.swift index 8cff8df..f463e4f 100644 --- a/Tests/TracingTests/TracerTests.swift +++ b/Tests/TracingTests/TracerTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2021 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2023 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // @@ -351,7 +351,40 @@ final class TracerTests: XCTestCase { #endif } - #if swift(>=5.5) && canImport(_Concurrency) + func testWithSpanSignatures() { + let tracer = TestTracer() + let clock = DefaultTracerClock() + + #if swift(>=5.7.0) + tracer.withSpan("") { _ in } + tracer.withSpan("", clock: clock) { _ in } + tracer.withSpan("", baggage: .topLevel) { _ in } + #endif + + tracer.withAnySpan("") { _ in } + tracer.withAnySpan("", clock: clock) { _ in } + tracer.withAnySpan("", baggage: .topLevel) { _ in } + } + + #if swift(>=5.7.0) +// @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) + /// Helper method to execute async operations until we can use async tests (currently incompatible with the generated LinuxMain file). + /// - Parameter operation: The operation to test. + func testAsync(_ operation: @escaping () async throws -> Void) rethrows { + let group = DispatchGroup() + group.enter() + Task.detached { + do { + try await operation() + } catch { + throw error + } + group.leave() + } + group.wait() + } + + #elseif swift(>=5.5) && canImport(_Concurrency) @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) /// Helper method to execute async operations until we can use async tests (currently incompatible with the generated LinuxMain file). /// - Parameter operation: The operation to test. diff --git a/Tests/TracingTests/TracerTimeTests.swift b/Tests/TracingTests/TracerTimeTests.swift new file mode 100644 index 0000000..3e2bb9d --- /dev/null +++ b/Tests/TracingTests/TracerTimeTests.swift @@ -0,0 +1,74 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Distributed Tracing open source project +// +// Copyright (c) 2020-2023 Apple Inc. and the Swift Distributed Tracing project +// authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import struct Foundation.Date +@testable import Instrumentation +import Tracing +import XCTest + +final class TracerTimeTests: XCTestCase { + override class func tearDown() { + super.tearDown() + InstrumentationSystem.bootstrapInternal(nil) + } + + func testTracerTime() { + let t = DefaultTracerClock.now + let d = Date() + XCTAssertEqual( + Double(t.millisecondsSinceEpoch) / 1000, // seconds + d.timeIntervalSince1970, // seconds + accuracy: 10 + ) + } + + func testMockTimeStartSpan() { + let tracer = TestTracer() + InstrumentationSystem.bootstrapInternal(tracer) + defer { + InstrumentationSystem.bootstrapInternal(NoOpTracer()) + } + + let mockClock = MockClock() + mockClock.setTime(13) + #if swift(>=5.7.0) + let span: TestSpan = tracer.startSpan("start", clock: mockClock) + XCTAssertEqual(span.startTime, 13) + #else + let span: TestSpan = tracer.startAnySpan("start", clock: mockClock) as! TestSpan + XCTAssertEqual(span.startTime, 13) + #endif + } +} + +final class MockClock: TracerClock { + var _now: UInt64 = 0 + + init() {} + + func setTime(_ time: UInt64) { + self._now = time + } + + struct Instant: TracerInstantProtocol { + var millisecondsSinceEpoch: UInt64 + static func < (lhs: MockClock.Instant, rhs: MockClock.Instant) -> Bool { + lhs.millisecondsSinceEpoch < rhs.millisecondsSinceEpoch + } + } + + var now: Instant { + Instant(millisecondsSinceEpoch: self._now) + } +} diff --git a/Tests/TracingTests/TracingInstrumentationSystemTests.swift b/Tests/TracingTests/TracingInstrumentationSystemTests.swift index b0b9ec2..d7ed546 100644 --- a/Tests/TracingTests/TracingInstrumentationSystemTests.swift +++ b/Tests/TracingTests/TracingInstrumentationSystemTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2021 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2023 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // diff --git a/scripts/validate_license_headers.sh b/scripts/validate_license_headers.sh index 399902f..ef99c15 100755 --- a/scripts/validate_license_headers.sh +++ b/scripts/validate_license_headers.sh @@ -32,7 +32,7 @@ here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" function replace_acceptable_years() { # this needs to replace all acceptable forms with 'YEARS' - sed -e 's/202[01]-202[12]/YEARS/' -e 's/202[012]/YEARS/' + sed -e 's/202[01]-202[123]/YEARS/' -e 's/202[0123]/YEARS/' } printf "=> Checking license headers\n"