This commit is contained in:
Andrius 2023-06-23 10:05:04 +00:00 committed by GitHub
commit 98f810cae2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 154 additions and 0 deletions

View File

@ -24,6 +24,7 @@ extension Timer {
/// - dimensions: The dimensions for the Timer.
/// - body: Closure to run & record.
@inlinable
@available(*, deprecated, message: "Please use non-static version on an already created Timer")
public static func measure<T>(label: String, dimensions: [(String, String)] = [], body: @escaping () throws -> T) rethrows -> T {
let timer = Timer(label: label, dimensions: dimensions)
let start = DispatchTime.now().uptimeNanoseconds
@ -74,3 +75,88 @@ extension Timer {
}
}
}
#if (os(macOS) && swift(>=5.7.1)) || (!os(macOS) && swift(>=5.7))
extension Timer {
/// Convenience for recording a duration based on Duration.
///
/// - parameters:
/// - duration: The duration to record.
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
public func record(duration: Duration) {
self.recordNanoseconds(duration.nanosecondsClamped)
}
/// Convenience for recording a duration since Instant using provided Clock
///
/// - parameters:
/// - instant: The instant to measure duration since
/// - clock: The clock to measure duration with
@inlinable
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
public func recordDurationSince<C: Clock>(
instant: C.Instant,
clock: C = ContinuousClock.continuous
) where C.Duration == Duration {
self.record(duration: instant.duration(to: clock.now))
}
}
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
internal extension Swift.Duration {
/// The duration represented as nanoseconds, clamped to maximum expressible value.
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
}
}
extension Timer {
/// Convenience for measuring duration of a closure
///
/// - parameters:
/// - body: Closure to run & record.
@inlinable
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
public func measure<T>(
body: @escaping () throws -> T
) rethrows -> T {
let start = ContinuousClock.continuous.now
defer {
self.recordDurationSince(instant: start, clock: ContinuousClock.continuous)
}
return try body()
}
/// Convenience for measuring duration of an async closure with a provided clock
///
/// - parameters:
/// - clock: The clock to measure closure duration with
/// - body: Closure to run & record.
@inlinable
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
public func measure<T, C: Clock>(
clock: C = ContinuousClock.continuous,
body: @escaping () async throws -> T
) async rethrows -> T where C.Duration == Duration {
let start = clock.now
defer {
self.recordDurationSince(instant: start, clock: clock)
}
return try await body()
}
}
#endif // (os(macOS) && swift(>=5.7.1)) || (!os(macOS) && swift(>=5.7))

View File

@ -152,6 +152,74 @@ class MetricsExtensionsTests: XCTestCase {
testTimer.preferDisplayUnit(.days)
XCTAssertEqual(testTimer.valueInPreferredUnit(atIndex: 0), value / (60 * 60 * 24), accuracy: 0.000000001, "expected value to match")
}
#if (os(macOS) && swift(>=5.7.1)) || (!os(macOS) && swift(>=5.7))
func testTimerBlock() 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")
}
func testTimerWithDuration() throws {
// bootstrap with our test metrics
let metrics = TestMetrics()
MetricsSystem.bootstrapInternal(metrics)
// run the test
let name = "test-timer"
let timer = Timer(label: name)
let duration = Duration.milliseconds(5)
timer.record(duration: duration)
let expectedTimer = try metrics.expectTimer(name)
XCTAssertEqual(1, expectedTimer.values.count, "expected number of entries to match")
XCTAssertEqual(expectedTimer.values[0], duration.nanosecondsClamped, "expected delay to match")
}
func testTimerWithDurationOnContinuousClock() async throws {
// bootstrap with our test metrics
let metrics = TestMetrics()
MetricsSystem.bootstrapInternal(metrics)
// run the test
let name = "test-timer"
let timer = Timer(label: name)
let clock = ContinuousClock()
let start = clock.now
let duration = Duration.milliseconds(5)
try await Task.sleep(for: duration)
timer.recordDurationSince(instant: start, clock: clock)
let expectedTimer = try metrics.expectTimer(name)
XCTAssertEqual(1, expectedTimer.values.count, "expected number of entries to match")
XCTAssertGreaterThan(expectedTimer.values[0], duration.nanosecondsClamped, "expected delay to match")
}
func testTimerWithDurationOnDefaultContinuousClock() async throws {
// bootstrap with our test metrics
let metrics = TestMetrics()
MetricsSystem.bootstrapInternal(metrics)
// run the test
let name = "test-timer"
let timer = Timer(label: name)
let start = ContinuousClock.now
let duration = Duration.milliseconds(5)
try await Task.sleep(for: duration)
timer.recordDurationSince(instant: start)
let expectedTimer = try metrics.expectTimer(name)
XCTAssertEqual(1, expectedTimer.values.count, "expected number of entries to match")
XCTAssertGreaterThan(expectedTimer.values[0], duration.nanosecondsClamped, "expected delay to match")
}
#endif
}
// https://bugs.swift.org/browse/SR-6310