Timer.record(_ duration:): protect from overflows
This commit is contained in:
parent
b01946077d
commit
02fc732f99
|
|
@ -76,20 +76,37 @@ extension Timer {
|
||||||
}
|
}
|
||||||
|
|
||||||
#if swift(>=5.7)
|
#if swift(>=5.7)
|
||||||
|
|
||||||
|
public enum TimerError: Error {
|
||||||
|
case durationToIntOverflow
|
||||||
|
}
|
||||||
|
|
||||||
extension Timer {
|
extension Timer {
|
||||||
/// Convenience for recording a duration based on ``Duration``.
|
/// Convenience for recording a duration based on ``Duration``.
|
||||||
/// Duration will be recorded in nanoseconds.
|
|
||||||
///
|
///
|
||||||
/// - parameters:
|
/// `Duration` will be converted to an `Int64` number of nanoseconds, and then recorded with nanosecond precision.
|
||||||
/// - duration: The duration to record.
|
///
|
||||||
|
/// - 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, *)
|
@available(macOS 13, iOS 16, tvOS 15, watchOS 8, *)
|
||||||
@inlinable
|
@inlinable
|
||||||
public func record(_ duration: Duration) {
|
public func record(_ duration: Duration) throws {
|
||||||
// `Duration` doesn't have a nice way to convert it nanoseconds or seconds,
|
// `Duration` doesn't have a nice way to convert it nanoseconds or seconds,
|
||||||
// so we'll do the multiplication manually.
|
// so we'll do the multiplication manually.
|
||||||
// nanoseconds are the smallest unit Timer can track, so we'll record in that.
|
// Nanoseconds are the smallest unit Timer can track, so we'll record in that.
|
||||||
let durationNanoseconds = duration.components.seconds * 1_000_000_000 + duration.components.attoseconds / 1_000_000_000
|
let (seconds, overflow) = duration.components.seconds.multipliedReportingOverflow(by: 1_000_000_000)
|
||||||
self.recordNanoseconds(durationNanoseconds)
|
guard !overflow else {
|
||||||
|
throw TimerError.durationToIntOverflow
|
||||||
|
}
|
||||||
|
|
||||||
|
let (nanoseconds, attosecondsOverflow) = seconds.addingReportingOverflow(duration.components.attoseconds / 1_000_000_000)
|
||||||
|
guard !attosecondsOverflow else {
|
||||||
|
throw TimerError.durationToIntOverflow
|
||||||
|
}
|
||||||
|
|
||||||
|
self.recordNanoseconds(nanoseconds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -108,16 +108,20 @@ class MetricsExtensionsTests: XCTestCase {
|
||||||
MetricsSystem.bootstrapInternal(metrics)
|
MetricsSystem.bootstrapInternal(metrics)
|
||||||
|
|
||||||
let name = "timer-\(UUID().uuidString)"
|
let name = "timer-\(UUID().uuidString)"
|
||||||
|
let timer = Timer(label: name)
|
||||||
|
|
||||||
let duration = Duration(secondsComponent: 3, attosecondsComponent: 123_000_000_000_000_000)
|
let duration = Duration(secondsComponent: 3, attosecondsComponent: 123_000_000_000_000_000)
|
||||||
let durationInNanoseconds = duration.components.seconds * 1_000_000_000 + duration.components.attoseconds / 1_000_000_000
|
let durationInNanoseconds = duration.components.seconds * 1_000_000_000 + duration.components.attoseconds / 1_000_000_000
|
||||||
|
|
||||||
let timer = Timer(label: name)
|
XCTAssertNoThrow(try timer.record(duration))
|
||||||
timer.record(duration)
|
|
||||||
|
|
||||||
let testTimer = try metrics.expectTimer(timer)
|
let testTimer = try metrics.expectTimer(timer)
|
||||||
XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match")
|
XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match")
|
||||||
XCTAssertEqual(testTimer.values.first, durationInNanoseconds, "expected value to match")
|
XCTAssertEqual(testTimer.values.first, durationInNanoseconds, "expected value to match")
|
||||||
XCTAssertEqual(metrics.timers.count, 1, "timer should have been stored")
|
XCTAssertEqual(metrics.timers.count, 1, "timer should have been stored")
|
||||||
|
|
||||||
|
let overflowDuration = Duration(secondsComponent: 10_000_000_000, attosecondsComponent: 123)
|
||||||
|
XCTAssertThrowsError(try timer.record(overflowDuration))
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue