Add guards for Int64 overflow in `Timer` methods (#29)
Motivation: `Timer.record*` methods are generic to cover all cases of `BinaryInteger` values and converts the passed value into `Int64` before doing time unit conversions and overflow checks. Modifications: All methods that accept `BinaryInteger` types and converts to `Int64` now guards against values higher than `Int64.max` Result: Users can reliably use the `Timer.record*` APIs with `BinaryInteger` types without concern for unknowing fatal errors.
This commit is contained in:
parent
4a9e0de637
commit
e4883dab79
|
|
@ -273,6 +273,8 @@ public class Timer {
|
||||||
/// - value: Duration to record.
|
/// - value: Duration to record.
|
||||||
@inlinable
|
@inlinable
|
||||||
public func recordMicroseconds<DataType: BinaryInteger>(_ duration: DataType) {
|
public func recordMicroseconds<DataType: BinaryInteger>(_ duration: DataType) {
|
||||||
|
guard duration <= Int64.max else { return self.recordNanoseconds(Int64.max) }
|
||||||
|
|
||||||
let result = Int64(duration).multipliedReportingOverflow(by: 1000)
|
let result = Int64(duration).multipliedReportingOverflow(by: 1000)
|
||||||
if result.overflow {
|
if result.overflow {
|
||||||
self.recordNanoseconds(Int64.max)
|
self.recordNanoseconds(Int64.max)
|
||||||
|
|
@ -296,6 +298,8 @@ public class Timer {
|
||||||
/// - value: Duration to record.
|
/// - value: Duration to record.
|
||||||
@inlinable
|
@inlinable
|
||||||
public func recordMilliseconds<DataType: BinaryInteger>(_ duration: DataType) {
|
public func recordMilliseconds<DataType: BinaryInteger>(_ duration: DataType) {
|
||||||
|
guard duration <= Int64.max else { return self.recordNanoseconds(Int64.max) }
|
||||||
|
|
||||||
let result = Int64(duration).multipliedReportingOverflow(by: 1_000_000)
|
let result = Int64(duration).multipliedReportingOverflow(by: 1_000_000)
|
||||||
if result.overflow {
|
if result.overflow {
|
||||||
self.recordNanoseconds(Int64.max)
|
self.recordNanoseconds(Int64.max)
|
||||||
|
|
@ -319,6 +323,8 @@ public class Timer {
|
||||||
/// - value: Duration to record.
|
/// - value: Duration to record.
|
||||||
@inlinable
|
@inlinable
|
||||||
public func recordSeconds<DataType: BinaryInteger>(_ duration: DataType) {
|
public func recordSeconds<DataType: BinaryInteger>(_ duration: DataType) {
|
||||||
|
guard duration <= Int64.max else { return self.recordNanoseconds(Int64.max) }
|
||||||
|
|
||||||
let result = Int64(duration).multipliedReportingOverflow(by: 1_000_000_000)
|
let result = Int64(duration).multipliedReportingOverflow(by: 1_000_000_000)
|
||||||
if result.overflow {
|
if result.overflow {
|
||||||
self.recordNanoseconds(Int64.max)
|
self.recordNanoseconds(Int64.max)
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ extension MetricsTests {
|
||||||
("testTimerBlock", testTimerBlock),
|
("testTimerBlock", testTimerBlock),
|
||||||
("testTimerVariants", testTimerVariants),
|
("testTimerVariants", testTimerVariants),
|
||||||
("testTimerOverflow", testTimerOverflow),
|
("testTimerOverflow", testTimerOverflow),
|
||||||
|
("testTimerHandlesUnsignedOverflow", testTimerHandlesUnsignedOverflow),
|
||||||
("testGauge", testGauge),
|
("testGauge", testGauge),
|
||||||
("testGaugeBlock", testGaugeBlock),
|
("testGaugeBlock", testGaugeBlock),
|
||||||
("testMUX", testMUX),
|
("testMUX", testMUX),
|
||||||
|
|
|
||||||
|
|
@ -217,6 +217,27 @@ class MetricsTests: XCTestCase {
|
||||||
XCTAssertEqual(testTimer.values[3].1, Int64.max, "expected value to match")
|
XCTAssertEqual(testTimer.values[3].1, Int64.max, "expected value to match")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testTimerHandlesUnsignedOverflow() throws {
|
||||||
|
// bootstrap with our test metrics
|
||||||
|
let metrics = TestMetrics()
|
||||||
|
MetricsSystem.bootstrapInternal(metrics)
|
||||||
|
// run the test
|
||||||
|
let timer = Timer(label: "test-timer")
|
||||||
|
let testTimer = timer.handler as! TestTimer
|
||||||
|
// micro
|
||||||
|
timer.recordMicroseconds(UInt64.max)
|
||||||
|
XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match")
|
||||||
|
XCTAssertEqual(testTimer.values[0].1, Int64.max, "expected value to match")
|
||||||
|
// milli
|
||||||
|
timer.recordMilliseconds(UInt64.max)
|
||||||
|
XCTAssertEqual(testTimer.values.count, 2, "expected number of entries to match")
|
||||||
|
XCTAssertEqual(testTimer.values[1].1, Int64.max, "expected value to match")
|
||||||
|
// seconds
|
||||||
|
timer.recordSeconds(UInt64.max)
|
||||||
|
XCTAssertEqual(testTimer.values.count, 3, "expected number of entries to match")
|
||||||
|
XCTAssertEqual(testTimer.values[2].1, Int64.max, "expected value to match")
|
||||||
|
}
|
||||||
|
|
||||||
func testGauge() throws {
|
func testGauge() throws {
|
||||||
// bootstrap with our test metrics
|
// bootstrap with our test metrics
|
||||||
let metrics = TestMetrics()
|
let metrics = TestMetrics()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue