Skip to content

Commit

Permalink
TimeUnits (#42)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
MrLotU authored and tomerd committed Sep 9, 2019
1 parent 853893b commit 3fefeda
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
.xcode
.SourceKitten
*.orig
.swiftpm
28 changes: 28 additions & 0 deletions Sources/CoreMetrics/Metrics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
///
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions Tests/MetricsTests/MetricsTests+XCTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ extension MetricsExtensionsTests {
("testTimerBlock", testTimerBlock),
("testTimerWithTimeInterval", testTimerWithTimeInterval),
("testTimerWithDispatchTime", testTimerWithDispatchTime),
("testTimerUnits", testTimerUnits),
]
}
}
26 changes: 26 additions & 0 deletions Tests/MetricsTests/MetricsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 25 additions & 0 deletions Tests/MetricsTests/TestMetrics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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))
Expand Down

0 comments on commit 3fefeda

Please sign in to comment.