diff --git a/Sources/Metrics/Metrics.swift b/Sources/Metrics/Metrics.swift index 0d309f9..c916c76 100644 --- a/Sources/Metrics/Metrics.swift +++ b/Sources/Metrics/Metrics.swift @@ -74,3 +74,29 @@ extension Timer { } } } + +#if swift(>=5.7) +extension Timer { + /// Convenience for recording a duration based on ``Duration``. + /// + /// `Duration` will be converted to an `Int64` number of nanoseconds, and then recorded with nanosecond precision. + /// + /// - Parameters: + /// - duration: The `Duration` to record. + /// + /// - Throws: `TimerError.durationToIntOverflow` if conversion from `Duration` to `Int64` of Nanoseconds overflowed. + @available(macOS 13, iOS 16, tvOS 15, watchOS 8, *) + @inlinable + public func record(_ duration: Duration) { + // `Duration` doesn't have a nice way to convert it nanoseconds or seconds, + // and manual conversion can overflow. + let seconds = duration.components.seconds.multipliedReportingOverflow(by: 1_000_000_000) + guard !seconds.overflow else { return self.recordNanoseconds(Int64.max) } + + let nanoseconds = seconds.partialValue.addingReportingOverflow(duration.components.attoseconds / 1_000_000_000) + guard !nanoseconds.overflow else { return self.recordNanoseconds(Int64.max) } + + self.recordNanoseconds(nanoseconds.partialValue) + } +} +#endif diff --git a/Tests/MetricsTests/MetricsTests+XCTest.swift b/Tests/MetricsTests/MetricsTests+XCTest.swift index ead970e..3c58452 100644 --- a/Tests/MetricsTests/MetricsTests+XCTest.swift +++ b/Tests/MetricsTests/MetricsTests+XCTest.swift @@ -29,6 +29,7 @@ extension MetricsExtensionsTests { ("testTimerWithTimeInterval", testTimerWithTimeInterval), ("testTimerWithDispatchTime", testTimerWithDispatchTime), ("testTimerWithDispatchTimeInterval", testTimerWithDispatchTimeInterval), + ("testTimerDuration", testTimerDuration), ("testTimerUnits", testTimerUnits), ("testPreferDisplayUnit", testPreferDisplayUnit), ] diff --git a/Tests/MetricsTests/MetricsTests.swift b/Tests/MetricsTests/MetricsTests.swift index 14139ad..3b590fd 100644 --- a/Tests/MetricsTests/MetricsTests.swift +++ b/Tests/MetricsTests/MetricsTests.swift @@ -96,6 +96,38 @@ class MetricsExtensionsTests: XCTestCase { XCTAssertEqual(metrics.timers.count, 1, "timer should have been stored") } + func testTimerDuration() throws { + // Wrapping only the insides of the test case so that the generated + // tests on Linux in MetricsTests+XCTest don't complain that the func does not exist. + #if swift(>=5.7) + guard #available(iOS 16, macOS 13, tvOS 15, watchOS 8, *) else { + throw XCTSkip("Timer.record(_ duration: Duration) is not available on this platform") + } + + let metrics = TestMetrics() + MetricsSystem.bootstrapInternal(metrics) + + let name = "timer-\(UUID().uuidString)" + let timer = Timer(label: name) + + let duration = Duration(secondsComponent: 3, attosecondsComponent: 123_000_000_000_000_000) + let nanoseconds = duration.components.seconds * 1_000_000_000 + duration.components.attoseconds / 1_000_000_000 + timer.record(duration) + + // Record a Duration that would overflow, + // expect Int64.max to be recorded. + timer.record(Duration(secondsComponent: 10_000_000_000, attosecondsComponent: 123)) + + let testTimer = try metrics.expectTimer(timer) + XCTAssertEqual(testTimer.values.count, 2, "expected number of entries to match") + XCTAssertEqual(testTimer.values.first, nanoseconds, "expected value to match") + XCTAssertEqual(testTimer.values[1], Int64.max, "expected to record Int64.max if Durataion overflows") + XCTAssertEqual(metrics.timers.count, 1, "timer should have been stored") + #elseif swift(>=5.2) + throw XCTSkip("Timer.record(_ duration: Duration) is only available on Swift >=5.7") + #endif + } + func testTimerUnits() throws { let metrics = TestMetrics() MetricsSystem.bootstrapInternal(metrics) diff --git a/scripts/soundness.sh b/scripts/soundness.sh index c29ed90..123d70b 100755 --- a/scripts/soundness.sh +++ b/scripts/soundness.sh @@ -64,6 +64,12 @@ fi printf "\033[0;32mokay.\033[0m\n" printf "=> Checking format... " + +if [[ ! -x $(which swiftformat) ]]; then + printf "\033[0;31mswiftformat not found!\033[0m\n" + exit 1 +fi + FIRST_OUT="$(git status --porcelain)" swiftformat . > /dev/null 2>&1 SECOND_OUT="$(git status --porcelain)"