From 3fefedaaef285830cc98ae80231140122076a7e0 Mon Sep 17 00:00:00 2001 From: "Jari (LotU)" Date: Mon, 9 Sep 2019 18:53:03 +0200 Subject: [PATCH] TimeUnits (#42) motivation: some metrics backend prefer to be given a hint about the preferred display unit (seconds, milliseconds, etc) to drive the ux changes: add a `preferedUnit` to TimerHandler (and `TimeUnits`) to capture the prefer display unit --- .gitignore | 1 + Sources/CoreMetrics/Metrics.swift | 28 ++++++++++++++++++++ Tests/MetricsTests/MetricsTests+XCTest.swift | 1 + Tests/MetricsTests/MetricsTests.swift | 26 ++++++++++++++++++ Tests/MetricsTests/TestMetrics.swift | 25 +++++++++++++++++ 5 files changed, 81 insertions(+) diff --git a/.gitignore b/.gitignore index 78fd939..77b93e9 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ .xcode .SourceKitten *.orig +.swiftpm \ No newline at end of file diff --git a/Sources/CoreMetrics/Metrics.swift b/Sources/CoreMetrics/Metrics.swift index 433e90b..8caefcc 100644 --- a/Sources/CoreMetrics/Metrics.swift +++ b/Sources/CoreMetrics/Metrics.swift @@ -172,6 +172,10 @@ public class Gauge: Recorder { } } +public enum TimeUnit { + case nanoseconds, milliseconds, seconds, minutes, hours, days +} + public extension Timer { /// Create a new `Timer`. /// @@ -183,6 +187,18 @@ public extension Timer { self.init(label: label, dimensions: dimensions, handler: handler) } + /// Create a new `Timer`. + /// + /// - parameters: + /// - label: The label for the `Timer`. + /// - dimensions: The dimensions for the `Timer`. + /// - displayUnit: A hint to the backend responsible for presenting the data of the preferred display unit. This is not guaranteed to be supported by all backends. + convenience init(label: String, dimensions: [(String, String)] = [], preferredDisplayUnit displayUnit: TimeUnit) { + let handler = MetricsSystem.factory.makeTimer(label: label, dimensions: dimensions) + handler.preferDisplayUnit(displayUnit) + self.init(label: label, dimensions: dimensions, handler: handler) + } + /// Signal the underlying metrics library that this timer will never be updated again. /// In response the library MAY decide to eagerly release any resources held by this `Timer`. @inlinable @@ -483,6 +499,18 @@ public protocol TimerHandler: AnyObject { /// - parameters: /// - value: Duration to record. func recordNanoseconds(_ duration: Int64) + + /// Set the preferred display unit for this TimerHandler. + /// + /// - parameters: + /// - unit: A hint to the backend responsible for presenting the data of the preferred display unit. This is not guaranteed to be supported by all backends. + func preferDisplayUnit(_ unit: TimeUnit) +} + +extension TimerHandler { + public func preferDisplayUnit(_: TimeUnit) { + // NOOP + } } // MARK: Predefined Metrics Handlers diff --git a/Tests/MetricsTests/MetricsTests+XCTest.swift b/Tests/MetricsTests/MetricsTests+XCTest.swift index b4b2321..3c08cf4 100644 --- a/Tests/MetricsTests/MetricsTests+XCTest.swift +++ b/Tests/MetricsTests/MetricsTests+XCTest.swift @@ -28,6 +28,7 @@ extension MetricsExtensionsTests { ("testTimerBlock", testTimerBlock), ("testTimerWithTimeInterval", testTimerWithTimeInterval), ("testTimerWithDispatchTime", testTimerWithDispatchTime), + ("testTimerUnits", testTimerUnits), ] } } diff --git a/Tests/MetricsTests/MetricsTests.swift b/Tests/MetricsTests/MetricsTests.swift index 08b44fa..a89cf71 100644 --- a/Tests/MetricsTests/MetricsTests.swift +++ b/Tests/MetricsTests/MetricsTests.swift @@ -77,6 +77,32 @@ class MetricsExtensionsTests: XCTestCase { XCTAssertEqual(testTimer.values.count, 5, "expected number of entries to match") XCTAssertEqual(testTimer.values[4].1, 0, "expected value to match") } + + func testTimerUnits() throws { + let metrics = TestMetrics() + MetricsSystem.bootstrapInternal(metrics) + + let name = "timer-\(NSUUID().uuidString)" + let value = Int64.random(in: 0 ... 1000) + + let timer = Timer(label: name) + timer.recordNanoseconds(value) + + let testTimer = timer.handler as! TestTimer + XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match") + XCTAssertEqual(testTimer.values.first!.1, value, "expected value to match") + XCTAssertEqual(metrics.timers.count, 1, "timer should have been stored") + + let secondsName = "timer-seconds-\(NSUUID().uuidString)" + let secondsValue = Int64.random(in: 0 ... 1000) + let secondsTimer = Timer(label: secondsName, preferredDisplayUnit: .seconds) + secondsTimer.recordSeconds(secondsValue) + + let testSecondsTimer = secondsTimer.handler as! TestTimer + XCTAssertEqual(testSecondsTimer.values.count, 1, "expected number of entries to match") + XCTAssertEqual(testSecondsTimer.retriveValueInPreferredUnit(atIndex: 0), secondsValue, "expected value to match") + XCTAssertEqual(metrics.timers.count, 2, "timer should have been stored") + } } // https://bugs.swift.org/browse/SR-6310 diff --git a/Tests/MetricsTests/TestMetrics.swift b/Tests/MetricsTests/TestMetrics.swift index e5b1385..7c686ce 100644 --- a/Tests/MetricsTests/TestMetrics.swift +++ b/Tests/MetricsTests/TestMetrics.swift @@ -135,6 +135,7 @@ internal class TestRecorder: RecorderHandler, Equatable { internal class TestTimer: TimerHandler, Equatable { let id: String let label: String + var displayUnit: TimeUnit? let dimensions: [(String, String)] let lock = NSLock() @@ -143,9 +144,33 @@ internal class TestTimer: TimerHandler, Equatable { init(label: String, dimensions: [(String, String)]) { self.id = NSUUID().uuidString self.label = label + self.displayUnit = nil self.dimensions = dimensions } + func preferDisplayUnit(_ unit: TimeUnit) { + self.lock.withLock { + self.displayUnit = unit + } + } + + func retriveValueInPreferredUnit(atIndex i: Int) -> Int64 { + return self.lock.withLock { + let value = values[i].1 + guard let displayUnit = self.displayUnit else { + return value + } + switch displayUnit { + case .days: return (value / 1_000_000_000) * 60 * 60 * 24 + case .hours: return (value / 1_000_000_000) * 60 * 60 + case .minutes: return (value / 1_000_000_000) * 60 + case .seconds: return value / 1_000_000_000 + case .milliseconds: return value / 1_000_000 + case .nanoseconds: return value + } + } + } + func recordNanoseconds(_ duration: Int64) { self.lock.withLock { values.append((Date(), duration))