Add `Timer.measure` methods (#140)
# Motivation This PR supersedes https://github.com/apple/swift-metrics/pull/135. The goal is to make it easier to measure asynchronous code when using `Metrics`. # Modification This PR does: - Deprecate the current static method for measuring synchronous code - Add a new instance method to measure synchronous code - Add a new instance method to measure asynchronous code
This commit is contained in:
parent
cbd39ceaca
commit
3c0f419970
|
|
@ -112,4 +112,44 @@ extension Timer {
|
||||||
|
|
||||||
self.recordNanoseconds(nanoseconds.partialValue)
|
self.recordNanoseconds(nanoseconds.partialValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if compiler(>=6.0)
|
||||||
|
/// Convenience for measuring duration of a closure.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - clock: The clock used for measuring the duration. Defaults to the continuous clock.
|
||||||
|
/// - body: The closure to record the duration of.
|
||||||
|
@inlinable
|
||||||
|
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
|
||||||
|
public func measure<Result, Failure: Error, Clock: _Concurrency.Clock>(
|
||||||
|
clock: Clock = .continuous,
|
||||||
|
body: () throws(Failure) -> Result
|
||||||
|
) throws(Failure) -> Result where Clock.Duration == Duration {
|
||||||
|
let start = clock.now
|
||||||
|
defer {
|
||||||
|
self.record(duration: start.duration(to: clock.now))
|
||||||
|
}
|
||||||
|
return try body()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience for measuring duration of a closure.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - clock: The clock used for measuring the duration. Defaults to the continuous clock.
|
||||||
|
/// - isolation: The isolation of the method. Defaults to the isolation of the caller.
|
||||||
|
/// - body: The closure to record the duration of.
|
||||||
|
@inlinable
|
||||||
|
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
|
||||||
|
public func measure<Result, Failure: Error, Clock: _Concurrency.Clock>(
|
||||||
|
clock: Clock = .continuous,
|
||||||
|
isolation: isolated (any Actor)? = #isolation,
|
||||||
|
body: () async throws(Failure) -> sending Result
|
||||||
|
) async throws(Failure) -> sending Result where Clock.Duration == Duration {
|
||||||
|
let start = clock.now
|
||||||
|
defer {
|
||||||
|
self.record(duration: start.duration(to: clock.now))
|
||||||
|
}
|
||||||
|
return try await body()
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -220,6 +220,43 @@ class MetricsExtensionsTests: XCTestCase {
|
||||||
"expected value to match"
|
"expected value to match"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if compiler(>=6.0)
|
||||||
|
func testTimerMeasure() async throws {
|
||||||
|
// bootstrap with our test metrics
|
||||||
|
let metrics = TestMetrics()
|
||||||
|
MetricsSystem.bootstrapInternal(metrics)
|
||||||
|
// run the test
|
||||||
|
let name = "timer-\(UUID().uuidString)"
|
||||||
|
let delay = Duration.milliseconds(5)
|
||||||
|
let timer = Timer(label: name)
|
||||||
|
try await timer.measure {
|
||||||
|
try await Task.sleep(for: delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
let expectedTimer = try metrics.expectTimer(name)
|
||||||
|
XCTAssertEqual(1, expectedTimer.values.count, "expected number of entries to match")
|
||||||
|
XCTAssertGreaterThan(expectedTimer.values[0], delay.nanosecondsClamped, "expected delay to match")
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
func testTimerMeasureFromMainActor() async throws {
|
||||||
|
// bootstrap with our test metrics
|
||||||
|
let metrics = TestMetrics()
|
||||||
|
MetricsSystem.bootstrapInternal(metrics)
|
||||||
|
// run the test
|
||||||
|
let name = "timer-\(UUID().uuidString)"
|
||||||
|
let delay = Duration.milliseconds(5)
|
||||||
|
let timer = Timer(label: name)
|
||||||
|
try await timer.measure {
|
||||||
|
try await Task.sleep(for: delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
let expectedTimer = try metrics.expectTimer(name)
|
||||||
|
XCTAssertEqual(1, expectedTimer.values.count, "expected number of entries to match")
|
||||||
|
XCTAssertGreaterThan(expectedTimer.values[0], delay.nanosecondsClamped, "expected delay to match")
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://bugs.swift.org/browse/SR-6310
|
// https://bugs.swift.org/browse/SR-6310
|
||||||
|
|
@ -251,3 +288,25 @@ extension DispatchTimeInterval {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if swift(>=5.7)
|
||||||
|
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
|
||||||
|
extension Swift.Duration {
|
||||||
|
fileprivate var nanosecondsClamped: Int64 {
|
||||||
|
let components = self.components
|
||||||
|
|
||||||
|
let secondsComponentNanos = components.seconds.multipliedReportingOverflow(by: 1_000_000_000)
|
||||||
|
let attosCompononentNanos = components.attoseconds / 1_000_000_000
|
||||||
|
let combinedNanos = secondsComponentNanos.partialValue.addingReportingOverflow(attosCompononentNanos)
|
||||||
|
|
||||||
|
guard
|
||||||
|
!secondsComponentNanos.overflow,
|
||||||
|
!combinedNanos.overflow
|
||||||
|
else {
|
||||||
|
return .max
|
||||||
|
}
|
||||||
|
|
||||||
|
return combinedNanos.partialValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue