refactor APIs to make them closer to swift-log APIs
motivation: after much discussion around logging API, we settled on a different API style, primairly the fact that we will use initializers instead of factories changes: * introduce intermediate classes for Counter, Timer and Recorder which are designed to replace the Metrics.makeCounter, Metrics.makeTimer and Metrics.makeRecorder APIs and wrap corresponding CounterHandler, TimerHandler and ReorderHandler coming from the metrics implmentation * rename Metrics to MetricsSystem * rename MetricsHandler -> MetricsFactory * remove Metrics.withCounter, Metrics.withTimer and Metrics.withRecorder syntactic sugar * rename Metrics.timed with Timer.measure * make sure metrics system can only be initialized/bootstrapped once per process * adjust and add tests * add a bit of docs on key APIs
This commit is contained in:
parent
2953390316
commit
1f44332af3
|
|
@ -12,164 +12,211 @@
|
|||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
public protocol Counter: AnyObject {
|
||||
/// This is the Counter protocol a metrics library implements
|
||||
public protocol CounterHandler: AnyObject {
|
||||
func increment<DataType: BinaryInteger>(_ value: DataType)
|
||||
}
|
||||
|
||||
public extension Counter {
|
||||
// This is the user facing Counter API. It must have reference semantics, and its behviour depend ons the `CounterHandler` implementation
|
||||
public class Counter: CounterHandler {
|
||||
@usableFromInline
|
||||
var handler: CounterHandler
|
||||
public let label: String
|
||||
public let dimensions: [(String, String)]
|
||||
|
||||
// this method is public to provide an escape hatch for situations one must use a custom factory instead of the gloabl one
|
||||
// we do not expect this API to be used in normal circumstances, so if you find yourself using it make sure its for a good reason
|
||||
public init(label: String, dimensions: [(String, String)], handler: CounterHandler) {
|
||||
self.label = label
|
||||
self.dimensions = dimensions
|
||||
self.handler = handler
|
||||
}
|
||||
|
||||
@inlinable
|
||||
func increment() {
|
||||
public func increment<DataType: BinaryInteger>(_ value: DataType) {
|
||||
self.handler.increment(value)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
public func increment() {
|
||||
self.increment(1)
|
||||
}
|
||||
}
|
||||
|
||||
public protocol Recorder: AnyObject {
|
||||
public extension Counter {
|
||||
public convenience init(label: String, dimensions: [(String, String)] = []) {
|
||||
let handler = MetricsSystem.factory.makeCounter(label: label, dimensions: dimensions)
|
||||
self.init(label: label, dimensions: dimensions, handler: handler)
|
||||
}
|
||||
}
|
||||
|
||||
/// This is the Recorder protocol a metrics library implements
|
||||
public protocol RecorderHandler: AnyObject {
|
||||
func record<DataType: BinaryInteger>(_ value: DataType)
|
||||
func record<DataType: BinaryFloatingPoint>(_ value: DataType)
|
||||
}
|
||||
|
||||
public protocol Timer: AnyObject {
|
||||
// This is the user facing Recorder API. It must have reference semantics, and its behviour depend ons the `RecorderHandler` implementation
|
||||
public class Recorder: RecorderHandler {
|
||||
@usableFromInline
|
||||
var handler: RecorderHandler
|
||||
public let label: String
|
||||
public let dimensions: [(String, String)]
|
||||
public let aggregate: Bool
|
||||
|
||||
// this method is public to provide an escape hatch for situations one must use a custom factory instead of the gloabl one
|
||||
// we do not expect this API to be used in normal circumstances, so if you find yourself using it make sure its for a good reason
|
||||
public init(label: String, dimensions: [(String, String)], aggregate: Bool, handler: RecorderHandler) {
|
||||
self.label = label
|
||||
self.dimensions = dimensions
|
||||
self.aggregate = aggregate
|
||||
self.handler = handler
|
||||
}
|
||||
|
||||
@inlinable
|
||||
public func record<DataType: BinaryInteger>(_ value: DataType) {
|
||||
self.handler.record(value)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
public func record<DataType: BinaryFloatingPoint>(_ value: DataType) {
|
||||
self.handler.record(value)
|
||||
}
|
||||
}
|
||||
|
||||
public extension Recorder {
|
||||
public convenience init(label: String, dimensions: [(String, String)] = [], aggregate: Bool = true) {
|
||||
let handler = MetricsSystem.factory.makeRecorder(label: label, dimensions: dimensions, aggregate: aggregate)
|
||||
self.init(label: label, dimensions: dimensions, aggregate: aggregate, handler: handler)
|
||||
}
|
||||
}
|
||||
|
||||
// A Gauge is a convenience for non-aggregating Recorder
|
||||
public class Gauge: Recorder {
|
||||
public convenience init(label: String, dimensions: [(String, String)] = []) {
|
||||
self.init(label: label, dimensions: dimensions, aggregate: false)
|
||||
}
|
||||
}
|
||||
|
||||
// This is the Timer protocol a metrics library implements
|
||||
public protocol TimerHandler: AnyObject {
|
||||
func recordNanoseconds(_ duration: Int64)
|
||||
}
|
||||
|
||||
public extension Timer {
|
||||
// This is the user facing Timer API. It must have reference semantics, and its behviour depend ons the `RecorderHandler` implementation
|
||||
public class Timer: TimerHandler {
|
||||
@usableFromInline
|
||||
var handler: TimerHandler
|
||||
public let label: String
|
||||
public let dimensions: [(String, String)]
|
||||
|
||||
// this method is public to provide an escape hatch for situations one must use a custom factory instead of the gloabl one
|
||||
// we do not expect this API to be used in normal circumstances, so if you find yourself using it make sure its for a good reason
|
||||
public init(label: String, dimensions: [(String, String)], handler: TimerHandler) {
|
||||
self.label = label
|
||||
self.dimensions = dimensions
|
||||
self.handler = handler
|
||||
}
|
||||
|
||||
@inlinable
|
||||
func recordMicroseconds<DataType: BinaryInteger>(_ duration: DataType) {
|
||||
public func recordNanoseconds(_ duration: Int64) {
|
||||
self.handler.recordNanoseconds(duration)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
public func recordMicroseconds<DataType: BinaryInteger>(_ duration: DataType) {
|
||||
self.recordNanoseconds(Int64(duration) * 1000)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
func recordMicroseconds<DataType: BinaryFloatingPoint>(_ duration: DataType) {
|
||||
public func recordMicroseconds<DataType: BinaryFloatingPoint>(_ duration: DataType) {
|
||||
self.recordNanoseconds(Int64(duration * 1000))
|
||||
}
|
||||
|
||||
@inlinable
|
||||
func recordMilliseconds<DataType: BinaryInteger>(_ duration: DataType) {
|
||||
public func recordMilliseconds<DataType: BinaryInteger>(_ duration: DataType) {
|
||||
self.recordNanoseconds(Int64(duration) * 1_000_000)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
func recordMilliseconds<DataType: BinaryFloatingPoint>(_ duration: DataType) {
|
||||
public func recordMilliseconds<DataType: BinaryFloatingPoint>(_ duration: DataType) {
|
||||
self.recordNanoseconds(Int64(duration * 1_000_000))
|
||||
}
|
||||
|
||||
@inlinable
|
||||
func recordSeconds<DataType: BinaryInteger>(_ duration: DataType) {
|
||||
public func recordSeconds<DataType: BinaryInteger>(_ duration: DataType) {
|
||||
self.recordNanoseconds(Int64(duration) * 1_000_000_000)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
func recordSeconds<DataType: BinaryFloatingPoint>(_ duration: DataType) {
|
||||
public func recordSeconds<DataType: BinaryFloatingPoint>(_ duration: DataType) {
|
||||
self.recordNanoseconds(Int64(duration * 1_000_000_000))
|
||||
}
|
||||
}
|
||||
|
||||
public protocol MetricsHandler {
|
||||
func makeCounter(label: String, dimensions: [(String, String)]) -> Counter
|
||||
func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> Recorder
|
||||
func makeTimer(label: String, dimensions: [(String, String)]) -> Timer
|
||||
}
|
||||
|
||||
public extension MetricsHandler {
|
||||
@inlinable
|
||||
func makeCounter(label: String) -> Counter {
|
||||
return self.makeCounter(label: label, dimensions: [])
|
||||
}
|
||||
|
||||
@inlinable
|
||||
func makeRecorder(label: String, aggregate: Bool = true) -> Recorder {
|
||||
return self.makeRecorder(label: label, dimensions: [], aggregate: aggregate)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
func makeTimer(label: String) -> Timer {
|
||||
return self.makeTimer(label: label, dimensions: [])
|
||||
public extension Timer {
|
||||
public convenience init(label: String, dimensions: [(String, String)] = []) {
|
||||
let handler = MetricsSystem.factory.makeTimer(label: label, dimensions: dimensions)
|
||||
self.init(label: label, dimensions: dimensions, handler: handler)
|
||||
}
|
||||
}
|
||||
|
||||
public extension MetricsHandler {
|
||||
@inlinable
|
||||
func makeGauge(label: String, dimensions: [(String, String)] = []) -> Recorder {
|
||||
return self.makeRecorder(label: label, dimensions: dimensions, aggregate: false)
|
||||
public protocol MetricsFactory {
|
||||
func makeCounter(label: String, dimensions: [(String, String)]) -> CounterHandler
|
||||
func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> RecorderHandler
|
||||
func makeTimer(label: String, dimensions: [(String, String)]) -> TimerHandler
|
||||
}
|
||||
|
||||
// This is the metrics system itself, it's mostly used set the type of the `MetricsFactory` implementation.
|
||||
public enum MetricsSystem {
|
||||
fileprivate static let lock = ReadWriteLock()
|
||||
fileprivate static var _factory: MetricsFactory = NOOPMetricsHandler.instance
|
||||
fileprivate static var initialized = false
|
||||
|
||||
// Configures which `LogHandler` to use in the application.
|
||||
public static func bootstrap(_ factory: MetricsFactory) {
|
||||
self.lock.withWriterLock {
|
||||
precondition(!self.initialized, "metrics system can only be initialized once per process. currently used factory: \(self.factory)")
|
||||
self._factory = factory
|
||||
self.initialized = true
|
||||
}
|
||||
}
|
||||
|
||||
public extension MetricsHandler {
|
||||
@inlinable
|
||||
func withCounter(label: String, dimensions: [(String, String)] = [], then: (Counter) -> Void) {
|
||||
then(self.makeCounter(label: label, dimensions: dimensions))
|
||||
}
|
||||
|
||||
@inlinable
|
||||
func withRecorder(label: String, dimensions: [(String, String)] = [], aggregate: Bool = true, then: (Recorder) -> Void) {
|
||||
then(self.makeRecorder(label: label, dimensions: dimensions, aggregate: aggregate))
|
||||
}
|
||||
|
||||
@inlinable
|
||||
func withTimer(label: String, dimensions: [(String, String)] = [], then: (Timer) -> Void) {
|
||||
then(self.makeTimer(label: label, dimensions: dimensions))
|
||||
}
|
||||
|
||||
@inlinable
|
||||
func withGauge(label: String, dimensions: [(String, String)] = [], then: (Recorder) -> Void) {
|
||||
then(self.makeGauge(label: label, dimensions: dimensions))
|
||||
// for our testing we want to allow multiple bootstraping
|
||||
internal static func bootstrapInternal(_ factory: MetricsFactory) {
|
||||
self.lock.withWriterLock {
|
||||
self._factory = factory
|
||||
}
|
||||
}
|
||||
|
||||
public enum Metrics {
|
||||
private static let lock = ReadWriteLock()
|
||||
private static var _handler: MetricsHandler = NOOPMetricsHandler.instance
|
||||
|
||||
public static func bootstrap(_ handler: MetricsHandler) {
|
||||
self.lock.withWriterLockVoid {
|
||||
self._handler = handler
|
||||
internal static var factory: MetricsFactory {
|
||||
return self.lock.withReaderLock { self._factory }
|
||||
}
|
||||
}
|
||||
|
||||
public static var global: MetricsHandler {
|
||||
return self.lock.withReaderLock { self._handler }
|
||||
}
|
||||
/// Ships with the metrics module, used to multiplex to multiple metrics handlers
|
||||
public final class MultiplexMetricsHandler: MetricsFactory {
|
||||
private let factories: [MetricsFactory]
|
||||
public init(factories: [MetricsFactory]) {
|
||||
self.factories = factories
|
||||
}
|
||||
|
||||
public extension Metrics {
|
||||
@inlinable
|
||||
func makeCounter(label: String, dimensions: [(String, String)]) -> Counter {
|
||||
return Metrics.global.makeCounter(label: label, dimensions: dimensions)
|
||||
public func makeCounter(label: String, dimensions: [(String, String)]) -> CounterHandler {
|
||||
return MuxCounter(factories: self.factories, label: label, dimensions: dimensions)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> Recorder {
|
||||
return Metrics.global.makeRecorder(label: label, dimensions: dimensions, aggregate: aggregate)
|
||||
public func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> RecorderHandler {
|
||||
return MuxRecorder(factories: self.factories, label: label, dimensions: dimensions, aggregate: aggregate)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
func makeTimer(label: String, dimensions: [(String, String)]) -> Timer {
|
||||
return Metrics.global.makeTimer(label: label, dimensions: dimensions)
|
||||
}
|
||||
public func makeTimer(label: String, dimensions: [(String, String)]) -> TimerHandler {
|
||||
return MuxTimer(factories: self.factories, label: label, dimensions: dimensions)
|
||||
}
|
||||
|
||||
public final class MultiplexMetricsHandler: MetricsHandler {
|
||||
private let handlers: [MetricsHandler]
|
||||
public init(handlers: [MetricsHandler]) {
|
||||
self.handlers = handlers
|
||||
}
|
||||
|
||||
public func makeCounter(label: String, dimensions: [(String, String)]) -> Counter {
|
||||
return MuxCounter(handlers: self.handlers, label: label, dimensions: dimensions)
|
||||
}
|
||||
|
||||
public func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> Recorder {
|
||||
return MuxRecorder(handlers: self.handlers, label: label, dimensions: dimensions, aggregate: aggregate)
|
||||
}
|
||||
|
||||
public func makeTimer(label: String, dimensions: [(String, String)]) -> Timer {
|
||||
return MuxTimer(handlers: self.handlers, label: label, dimensions: dimensions)
|
||||
}
|
||||
|
||||
private class MuxCounter: Counter {
|
||||
let counters: [Counter]
|
||||
public init(handlers: [MetricsHandler], label: String, dimensions: [(String, String)]) {
|
||||
self.counters = handlers.map { $0.makeCounter(label: label, dimensions: dimensions) }
|
||||
private class MuxCounter: CounterHandler {
|
||||
let counters: [CounterHandler]
|
||||
public init(factories: [MetricsFactory], label: String, dimensions: [(String, String)]) {
|
||||
self.counters = factories.map { $0.makeCounter(label: label, dimensions: dimensions) }
|
||||
}
|
||||
|
||||
func increment<DataType: BinaryInteger>(_ value: DataType) {
|
||||
|
|
@ -177,10 +224,10 @@ public final class MultiplexMetricsHandler: MetricsHandler {
|
|||
}
|
||||
}
|
||||
|
||||
private class MuxRecorder: Recorder {
|
||||
let recorders: [Recorder]
|
||||
public init(handlers: [MetricsHandler], label: String, dimensions: [(String, String)], aggregate: Bool) {
|
||||
self.recorders = handlers.map { $0.makeRecorder(label: label, dimensions: dimensions, aggregate: aggregate) }
|
||||
private class MuxRecorder: RecorderHandler {
|
||||
let recorders: [RecorderHandler]
|
||||
public init(factories: [MetricsFactory], label: String, dimensions: [(String, String)], aggregate: Bool) {
|
||||
self.recorders = factories.map { $0.makeRecorder(label: label, dimensions: dimensions, aggregate: aggregate) }
|
||||
}
|
||||
|
||||
func record<DataType: BinaryInteger>(_ value: DataType) {
|
||||
|
|
@ -192,10 +239,10 @@ public final class MultiplexMetricsHandler: MetricsHandler {
|
|||
}
|
||||
}
|
||||
|
||||
private class MuxTimer: Timer {
|
||||
let timers: [Timer]
|
||||
public init(handlers: [MetricsHandler], label: String, dimensions: [(String, String)]) {
|
||||
self.timers = handlers.map { $0.makeTimer(label: label, dimensions: dimensions) }
|
||||
private class MuxTimer: TimerHandler {
|
||||
let timers: [TimerHandler]
|
||||
public init(factories: [MetricsFactory], label: String, dimensions: [(String, String)]) {
|
||||
self.timers = factories.map { $0.makeTimer(label: label, dimensions: dimensions) }
|
||||
}
|
||||
|
||||
func recordNanoseconds(_ duration: Int64) {
|
||||
|
|
@ -204,20 +251,20 @@ public final class MultiplexMetricsHandler: MetricsHandler {
|
|||
}
|
||||
}
|
||||
|
||||
public final class NOOPMetricsHandler: MetricsHandler, Counter, Recorder, Timer {
|
||||
public final class NOOPMetricsHandler: MetricsFactory, CounterHandler, RecorderHandler, TimerHandler {
|
||||
public static let instance = NOOPMetricsHandler()
|
||||
|
||||
private init() {}
|
||||
|
||||
public func makeCounter(label: String, dimensions: [(String, String)]) -> Counter {
|
||||
public func makeCounter(label: String, dimensions: [(String, String)]) -> CounterHandler {
|
||||
return self
|
||||
}
|
||||
|
||||
public func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> Recorder {
|
||||
public func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> RecorderHandler {
|
||||
return self
|
||||
}
|
||||
|
||||
public func makeTimer(label: String, dimensions: [(String, String)]) -> Timer {
|
||||
public func makeTimer(label: String, dimensions: [(String, String)]) -> TimerHandler {
|
||||
return self
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ enum Example1 {
|
|||
static func main() {
|
||||
// bootstrap with our example metrics library
|
||||
let metrics = ExampleMetricsLibrary()
|
||||
Metrics.bootstrap(metrics)
|
||||
MetricsSystem.bootstrap(metrics)
|
||||
|
||||
let server = Server()
|
||||
let client = Client(server: server)
|
||||
|
|
@ -38,7 +38,7 @@ enum Example1 {
|
|||
}
|
||||
|
||||
class Client {
|
||||
private let activeRequestsGauge = Metrics.global.makeGauge(label: "Client::ActiveRequests")
|
||||
private let activeRequestsGauge = Gauge(label: "Client::ActiveRequests")
|
||||
private let server: Server
|
||||
init(server: Server) {
|
||||
self.server = server
|
||||
|
|
@ -46,9 +46,9 @@ enum Example1 {
|
|||
|
||||
func run(iterations: Int) {
|
||||
let group = DispatchGroup()
|
||||
let requestsCounter = Metrics.global.makeCounter(label: "Client::TotalRequests")
|
||||
let requestTimer = Metrics.global.makeTimer(label: "Client::doSomethig")
|
||||
let resultRecorder = Metrics.global.makeRecorder(label: "Client::doSomethig::result")
|
||||
let requestsCounter = Counter(label: "Client::TotalRequests")
|
||||
let requestTimer = Timer(label: "Client::doSomethig")
|
||||
let resultRecorder = Recorder(label: "Client::doSomethig::result")
|
||||
for _ in 0 ... iterations {
|
||||
group.enter()
|
||||
let start = Date()
|
||||
|
|
@ -78,10 +78,10 @@ enum Example1 {
|
|||
|
||||
class Server {
|
||||
let library = RandomLibrary()
|
||||
let requestsCounter = Metrics.global.makeCounter(label: "Server::TotalRequests")
|
||||
let requestsCounter = Counter(label: "Server::TotalRequests")
|
||||
|
||||
func doSomethig(callback: @escaping (Int64) -> Void) {
|
||||
let timer = Metrics.global.makeTimer(label: "Server::doSomethig")
|
||||
let timer = Timer(label: "Server::doSomethig")
|
||||
let start = Date()
|
||||
requestsCounter.increment()
|
||||
DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(Int.random(in: 5 ... 500))) {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
import Metrics
|
||||
|
||||
class ExampleMetricsLibrary: MetricsHandler {
|
||||
class ExampleMetricsLibrary: MetricsFactory {
|
||||
private let config: Config
|
||||
private let lock = NSLock()
|
||||
var counters = [ExampleCounter]()
|
||||
|
|
@ -29,16 +29,16 @@ class ExampleMetricsLibrary: MetricsHandler {
|
|||
self.config = config
|
||||
}
|
||||
|
||||
func makeCounter(label: String, dimensions: [(String, String)]) -> Counter {
|
||||
func makeCounter(label: String, dimensions: [(String, String)]) -> CounterHandler {
|
||||
return self.register(label: label, dimensions: dimensions, registry: &self.counters, maker: ExampleCounter.init)
|
||||
}
|
||||
|
||||
func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> Recorder {
|
||||
func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> RecorderHandler {
|
||||
let options = aggregate ? self.config.recorder.aggregationOptions : nil
|
||||
return self.makeRecorder(label: label, dimensions: dimensions, options: options)
|
||||
}
|
||||
|
||||
func makeRecorder(label: String, dimensions: [(String, String)], options: [AggregationOption]?) -> Recorder {
|
||||
func makeRecorder(label: String, dimensions: [(String, String)], options: [AggregationOption]?) -> RecorderHandler {
|
||||
guard let options = options else {
|
||||
return self.register(label: label, dimensions: dimensions, registry: &self.gauges, maker: ExampleGauge.init)
|
||||
}
|
||||
|
|
@ -48,11 +48,11 @@ class ExampleMetricsLibrary: MetricsHandler {
|
|||
return self.register(label: label, dimensions: dimensions, registry: &self.recorders, maker: maker)
|
||||
}
|
||||
|
||||
func makeTimer(label: String, dimensions: [(String, String)]) -> Timer {
|
||||
func makeTimer(label: String, dimensions: [(String, String)]) -> TimerHandler {
|
||||
return self.makeTimer(label: label, dimensions: dimensions, options: self.config.timer.aggregationOptions)
|
||||
}
|
||||
|
||||
func makeTimer(label: String, dimensions: [(String, String)], options: [AggregationOption]) -> Timer {
|
||||
func makeTimer(label: String, dimensions: [(String, String)], options: [AggregationOption]) -> TimerHandler {
|
||||
let maker = { (label: String, dimensions: [(String, String)]) -> ExampleTimer in
|
||||
ExampleTimer(label: label, dimensions: dimensions, options: options)
|
||||
}
|
||||
|
|
@ -99,7 +99,7 @@ class ExampleMetricsLibrary: MetricsHandler {
|
|||
}
|
||||
}
|
||||
|
||||
class ExampleCounter: Counter, CustomStringConvertible {
|
||||
class ExampleCounter: CounterHandler, CustomStringConvertible {
|
||||
let label: String
|
||||
let dimensions: [(String, String)]
|
||||
init(label: String, dimensions: [(String, String)]) {
|
||||
|
|
@ -120,7 +120,7 @@ class ExampleCounter: Counter, CustomStringConvertible {
|
|||
}
|
||||
}
|
||||
|
||||
class ExampleRecorder: Recorder, CustomStringConvertible {
|
||||
class ExampleRecorder: RecorderHandler, CustomStringConvertible {
|
||||
let label: String
|
||||
let dimensions: [(String, String)]
|
||||
let options: [AggregationOption]
|
||||
|
|
@ -226,7 +226,7 @@ class ExampleRecorder: Recorder, CustomStringConvertible {
|
|||
}
|
||||
}
|
||||
|
||||
class ExampleGauge: Recorder, CustomStringConvertible {
|
||||
class ExampleGauge: RecorderHandler, CustomStringConvertible {
|
||||
let label: String
|
||||
let dimensions: [(String, String)]
|
||||
init(label: String, dimensions: [(String, String)]) {
|
||||
|
|
@ -250,7 +250,7 @@ class ExampleGauge: Recorder, CustomStringConvertible {
|
|||
}
|
||||
}
|
||||
|
||||
class ExampleTimer: ExampleRecorder, Timer {
|
||||
class ExampleTimer: ExampleRecorder, TimerHandler {
|
||||
func recordNanoseconds(_ duration: Int64) {
|
||||
super.record(duration)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
import Metrics
|
||||
|
||||
class RandomLibrary {
|
||||
let methodCallsCounter = Metrics.global.makeCounter(label: "RandomLibrary::TotalMethodCalls")
|
||||
let methodCallsCounter = Counter(label: "RandomLibrary::TotalMethodCalls")
|
||||
|
||||
func doSomething() {
|
||||
self.methodCallsCounter.increment()
|
||||
|
|
@ -26,7 +26,7 @@ class RandomLibrary {
|
|||
|
||||
func doSomethingSlow(callback: @escaping () -> Void) {
|
||||
self.methodCallsCounter.increment()
|
||||
Metrics.global.withTimer(label: "RandomLibrary::doSomethingSlow") { timer in
|
||||
let timer = Timer(label: "RandomLibrary::doSomethingSlow")
|
||||
let start = Date()
|
||||
DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(Int.random(in: 5 ... 500))) {
|
||||
timer.record(Date().timeIntervalSince(start))
|
||||
|
|
@ -34,4 +34,3 @@ class RandomLibrary {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,23 +17,23 @@
|
|||
|
||||
import Metrics
|
||||
|
||||
class SimpleMetricsLibrary: MetricsHandler {
|
||||
class SimpleMetricsLibrary: MetricsFactory {
|
||||
init() {}
|
||||
|
||||
func makeCounter(label: String, dimensions: [(String, String)]) -> Counter {
|
||||
func makeCounter(label: String, dimensions: [(String, String)]) -> CounterHandler {
|
||||
return ExampleCounter(label, dimensions)
|
||||
}
|
||||
|
||||
func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> Recorder {
|
||||
let maker: (String, [(String, String)]) -> Recorder = aggregate ? ExampleRecorder.init : ExampleGauge.init
|
||||
func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> RecorderHandler {
|
||||
let maker: (String, [(String, String)]) -> RecorderHandler = aggregate ? ExampleRecorder.init : ExampleGauge.init
|
||||
return maker(label, dimensions)
|
||||
}
|
||||
|
||||
func makeTimer(label: String, dimensions: [(String, String)]) -> Timer {
|
||||
func makeTimer(label: String, dimensions: [(String, String)]) -> TimerHandler {
|
||||
return ExampleTimer(label, dimensions)
|
||||
}
|
||||
|
||||
private class ExampleCounter: Counter {
|
||||
private class ExampleCounter: CounterHandler {
|
||||
init(_: String, _: [(String, String)]) {}
|
||||
|
||||
let lock = NSLock()
|
||||
|
|
@ -45,7 +45,7 @@ class SimpleMetricsLibrary: MetricsHandler {
|
|||
}
|
||||
}
|
||||
|
||||
private class ExampleRecorder: Recorder {
|
||||
private class ExampleRecorder: RecorderHandler {
|
||||
init(_: String, _: [(String, String)]) {}
|
||||
|
||||
private let lock = NSLock()
|
||||
|
|
@ -88,7 +88,7 @@ class SimpleMetricsLibrary: MetricsHandler {
|
|||
}
|
||||
}
|
||||
|
||||
private class ExampleGauge: Recorder {
|
||||
private class ExampleGauge: RecorderHandler {
|
||||
init(_: String, _: [(String, String)]) {}
|
||||
|
||||
let lock = NSLock()
|
||||
|
|
@ -103,7 +103,7 @@ class SimpleMetricsLibrary: MetricsHandler {
|
|||
}
|
||||
}
|
||||
|
||||
private class ExampleTimer: ExampleRecorder, Timer {
|
||||
private class ExampleTimer: ExampleRecorder, TimerHandler {
|
||||
func recordNanoseconds(_ duration: Int64) {
|
||||
super.record(duration)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
@_exported import CoreMetrics
|
||||
@_exported import protocol CoreMetrics.Timer
|
||||
@_exported import class CoreMetrics.Timer
|
||||
@_exported import Foundation
|
||||
|
||||
public extension MetricsHandler {
|
||||
// Convenience for measuring duration of a closure
|
||||
public extension Timer {
|
||||
@inlinable
|
||||
func timed<T>(label: String, dimensions: [(String, String)] = [], body: @escaping () throws -> T) rethrows -> T {
|
||||
let timer = self.makeTimer(label: label, dimensions: dimensions)
|
||||
public static func measure<T>(label: String, dimensions: [(String, String)] = [], body: @escaping () throws -> T) rethrows -> T {
|
||||
let timer = Timer(label: label, dimensions: dimensions)
|
||||
let start = Date()
|
||||
defer {
|
||||
timer.record(Date().timeIntervalSince(start))
|
||||
|
|
@ -14,14 +15,15 @@ public extension MetricsHandler {
|
|||
}
|
||||
}
|
||||
|
||||
// Convenience for using Foundation and Dispatch
|
||||
public extension Timer {
|
||||
@inlinable
|
||||
func record(_ duration: TimeInterval) {
|
||||
public func record(_ duration: TimeInterval) {
|
||||
self.recordSeconds(duration)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
func record(_ duration: DispatchTimeInterval) {
|
||||
public func record(_ duration: DispatchTimeInterval) {
|
||||
switch duration {
|
||||
case .nanoseconds(let value):
|
||||
self.recordNanoseconds(Int64(value))
|
||||
|
|
|
|||
|
|
@ -19,10 +19,11 @@ class MetricsTests: XCTestCase {
|
|||
func testCounters() throws {
|
||||
// bootstrap with our test metrics
|
||||
let metrics = TestMetrics()
|
||||
Metrics.bootstrap(metrics)
|
||||
MetricsSystem.bootstrapInternal(metrics)
|
||||
let group = DispatchGroup()
|
||||
let name = "counter-\(NSUUID().uuidString)"
|
||||
let counter = Metrics.global.makeCounter(label: name) as! TestCounter
|
||||
let counter = Counter(label: name)
|
||||
let testCounter = counter.handler as! TestCounter
|
||||
let total = Int.random(in: 500 ... 1000)
|
||||
for _ in 0 ... total {
|
||||
group.enter()
|
||||
|
|
@ -32,17 +33,17 @@ class MetricsTests: XCTestCase {
|
|||
}
|
||||
}
|
||||
group.wait()
|
||||
XCTAssertEqual(counter.values.count - 1, total, "expected number of entries to match")
|
||||
XCTAssertEqual(testCounter.values.count - 1, total, "expected number of entries to match")
|
||||
}
|
||||
|
||||
func testCounterBlock() throws {
|
||||
// bootstrap with our test metrics
|
||||
let metrics = TestMetrics()
|
||||
Metrics.bootstrap(metrics)
|
||||
MetricsSystem.bootstrapInternal(metrics)
|
||||
// run the test
|
||||
let name = "counter-\(NSUUID().uuidString)"
|
||||
let value = Int.random(in: Int.min ... Int.max)
|
||||
Metrics.global.withCounter(label: name) { $0.increment(value) }
|
||||
Counter(label: name).increment(value)
|
||||
let counter = metrics.counters[name] as! TestCounter
|
||||
XCTAssertEqual(counter.values.count, 1, "expected number of entries to match")
|
||||
XCTAssertEqual(counter.values[0].1, Int64(value), "expected value to match")
|
||||
|
|
@ -51,10 +52,11 @@ class MetricsTests: XCTestCase {
|
|||
func testRecorders() throws {
|
||||
// bootstrap with our test metrics
|
||||
let metrics = TestMetrics()
|
||||
Metrics.bootstrap(metrics)
|
||||
MetricsSystem.bootstrapInternal(metrics)
|
||||
let group = DispatchGroup()
|
||||
let name = "recorder-\(NSUUID().uuidString)"
|
||||
let recorder = Metrics.global.makeRecorder(label: name) as! TestRecorder
|
||||
let recorder = Recorder(label: name)
|
||||
let testRecorder = recorder.handler as! TestRecorder
|
||||
let total = Int.random(in: 500 ... 1000)
|
||||
for _ in 0 ... total {
|
||||
group.enter()
|
||||
|
|
@ -64,47 +66,49 @@ class MetricsTests: XCTestCase {
|
|||
}
|
||||
}
|
||||
group.wait()
|
||||
XCTAssertEqual(recorder.values.count - 1, total, "expected number of entries to match")
|
||||
XCTAssertEqual(testRecorder.values.count - 1, total, "expected number of entries to match")
|
||||
}
|
||||
|
||||
func testRecordersInt() throws {
|
||||
// bootstrap with our test metrics
|
||||
let metrics = TestMetrics()
|
||||
Metrics.bootstrap(metrics)
|
||||
let recorder = Metrics.global.makeRecorder(label: "test-recorder") as! TestRecorder
|
||||
MetricsSystem.bootstrapInternal(metrics)
|
||||
let recorder = Recorder(label: "test-recorder")
|
||||
let testRecorder = recorder.handler as! TestRecorder
|
||||
let values = (0 ... 999).map { _ in Int32.random(in: Int32.min ... Int32.max) }
|
||||
for i in 0 ... values.count - 1 {
|
||||
recorder.record(values[i])
|
||||
}
|
||||
XCTAssertEqual(values.count, recorder.values.count, "expected number of entries to match")
|
||||
XCTAssertEqual(values.count, testRecorder.values.count, "expected number of entries to match")
|
||||
for i in 0 ... values.count - 1 {
|
||||
XCTAssertEqual(Int32(recorder.values[i].1), values[i], "expected value #\(i) to match.")
|
||||
XCTAssertEqual(Int32(testRecorder.values[i].1), values[i], "expected value #\(i) to match.")
|
||||
}
|
||||
}
|
||||
|
||||
func testRecordersFloat() throws {
|
||||
// bootstrap with our test metrics
|
||||
let metrics = TestMetrics()
|
||||
Metrics.bootstrap(metrics)
|
||||
let recorder = Metrics.global.makeRecorder(label: "test-recorder") as! TestRecorder
|
||||
MetricsSystem.bootstrapInternal(metrics)
|
||||
let recorder = Recorder(label: "test-recorder")
|
||||
let testRecorder = recorder.handler as! TestRecorder
|
||||
let values = (0 ... 999).map { _ in Float.random(in: Float(Int32.min) ... Float(Int32.max)) }
|
||||
for i in 0 ... values.count - 1 {
|
||||
recorder.record(values[i])
|
||||
}
|
||||
XCTAssertEqual(values.count, recorder.values.count, "expected number of entries to match")
|
||||
XCTAssertEqual(values.count, testRecorder.values.count, "expected number of entries to match")
|
||||
for i in 0 ... values.count - 1 {
|
||||
XCTAssertEqual(Float(recorder.values[i].1), values[i], "expected value #\(i) to match.")
|
||||
XCTAssertEqual(Float(testRecorder.values[i].1), values[i], "expected value #\(i) to match.")
|
||||
}
|
||||
}
|
||||
|
||||
func testRecorderBlock() throws {
|
||||
// bootstrap with our test metrics
|
||||
let metrics = TestMetrics()
|
||||
Metrics.bootstrap(metrics)
|
||||
MetricsSystem.bootstrapInternal(metrics)
|
||||
// run the test
|
||||
let name = "recorder-\(NSUUID().uuidString)"
|
||||
let value = Double.random(in: Double(Int.min) ... Double(Int.max))
|
||||
Metrics.global.withRecorder(label: name) { $0.record(value) }
|
||||
Recorder(label: name).record(value)
|
||||
let recorder = metrics.recorders[name] as! TestRecorder
|
||||
XCTAssertEqual(recorder.values.count, 1, "expected number of entries to match")
|
||||
XCTAssertEqual(recorder.values[0].1, value, "expected value to match")
|
||||
|
|
@ -113,10 +117,11 @@ class MetricsTests: XCTestCase {
|
|||
func testTimers() throws {
|
||||
// bootstrap with our test metrics
|
||||
let metrics = TestMetrics()
|
||||
Metrics.bootstrap(metrics)
|
||||
MetricsSystem.bootstrapInternal(metrics)
|
||||
let group = DispatchGroup()
|
||||
let name = "timer-\(NSUUID().uuidString)"
|
||||
let timer = Metrics.global.makeTimer(label: name) as! TestTimer
|
||||
let timer = Timer(label: name)
|
||||
let testTimer = timer.handler as! TestTimer
|
||||
let total = Int.random(in: 500 ... 1000)
|
||||
for _ in 0 ... total {
|
||||
group.enter()
|
||||
|
|
@ -126,17 +131,17 @@ class MetricsTests: XCTestCase {
|
|||
}
|
||||
}
|
||||
group.wait()
|
||||
XCTAssertEqual(timer.values.count - 1, total, "expected number of entries to match")
|
||||
XCTAssertEqual(testTimer.values.count - 1, total, "expected number of entries to match")
|
||||
}
|
||||
|
||||
func testTimerBlock() throws {
|
||||
// bootstrap with our test metrics
|
||||
let metrics = TestMetrics()
|
||||
Metrics.bootstrap(metrics)
|
||||
MetricsSystem.bootstrapInternal(metrics)
|
||||
// run the test
|
||||
let name = "timer-\(NSUUID().uuidString)"
|
||||
let value = Int64.random(in: Int64.min ... Int64.max)
|
||||
Metrics.global.withTimer(label: name) { $0.recordNanoseconds(value) }
|
||||
Timer(label: name).recordNanoseconds(value)
|
||||
let timer = metrics.timers[name] as! TestTimer
|
||||
XCTAssertEqual(timer.values.count, 1, "expected number of entries to match")
|
||||
XCTAssertEqual(timer.values[0].1, value, "expected value to match")
|
||||
|
|
@ -145,41 +150,42 @@ class MetricsTests: XCTestCase {
|
|||
func testTimerVariants() throws {
|
||||
// bootstrap with our test metrics
|
||||
let metrics = TestMetrics()
|
||||
Metrics.bootstrap(metrics)
|
||||
MetricsSystem.bootstrapInternal(metrics)
|
||||
// run the test
|
||||
let timer = Metrics.global.makeTimer(label: "test-timer") as! TestTimer
|
||||
let timer = Timer(label: "test-timer")
|
||||
let testTimer = timer.handler as! TestTimer
|
||||
// nano
|
||||
let nano = Int64.random(in: 0 ... 5)
|
||||
timer.recordNanoseconds(nano)
|
||||
XCTAssertEqual(timer.values.count, 1, "expected number of entries to match")
|
||||
XCTAssertEqual(timer.values[0].1, nano, "expected value to match")
|
||||
XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match")
|
||||
XCTAssertEqual(testTimer.values[0].1, nano, "expected value to match")
|
||||
// micro
|
||||
let micro = Int64.random(in: 0 ... 5)
|
||||
timer.recordMicroseconds(micro)
|
||||
XCTAssertEqual(timer.values.count, 2, "expected number of entries to match")
|
||||
XCTAssertEqual(timer.values[1].1, micro * 1000, "expected value to match")
|
||||
XCTAssertEqual(testTimer.values.count, 2, "expected number of entries to match")
|
||||
XCTAssertEqual(testTimer.values[1].1, micro * 1000, "expected value to match")
|
||||
// milli
|
||||
let milli = Int64.random(in: 0 ... 5)
|
||||
timer.recordMilliseconds(milli)
|
||||
XCTAssertEqual(timer.values.count, 3, "expected number of entries to match")
|
||||
XCTAssertEqual(timer.values[2].1, milli * 1_000_000, "expected value to match")
|
||||
XCTAssertEqual(testTimer.values.count, 3, "expected number of entries to match")
|
||||
XCTAssertEqual(testTimer.values[2].1, milli * 1_000_000, "expected value to match")
|
||||
// seconds
|
||||
let sec = Int64.random(in: 0 ... 5)
|
||||
timer.recordSeconds(sec)
|
||||
XCTAssertEqual(timer.values.count, 4, "expected number of entries to match")
|
||||
XCTAssertEqual(timer.values[3].1, sec * 1_000_000_000, "expected value to match")
|
||||
XCTAssertEqual(testTimer.values.count, 4, "expected number of entries to match")
|
||||
XCTAssertEqual(testTimer.values[3].1, sec * 1_000_000_000, "expected value to match")
|
||||
}
|
||||
|
||||
func testGauge() throws {
|
||||
// bootstrap with our test metrics
|
||||
let metrics = TestMetrics()
|
||||
Metrics.bootstrap(metrics)
|
||||
MetricsSystem.bootstrapInternal(metrics)
|
||||
// run the test
|
||||
let name = "gauge-\(NSUUID().uuidString)"
|
||||
let value = Double.random(in: -1000 ... 1000)
|
||||
let gauge = Metrics.global.makeGauge(label: name)
|
||||
let gauge = Gauge(label: name)
|
||||
gauge.record(value)
|
||||
let recorder = gauge as! TestRecorder
|
||||
let recorder = gauge.handler as! TestRecorder
|
||||
XCTAssertEqual(recorder.values.count, 1, "expected number of entries to match")
|
||||
XCTAssertEqual(recorder.values[0].1, value, "expected value to match")
|
||||
}
|
||||
|
|
@ -187,11 +193,11 @@ class MetricsTests: XCTestCase {
|
|||
func testGaugeBlock() throws {
|
||||
// bootstrap with our test metrics
|
||||
let metrics = TestMetrics()
|
||||
Metrics.bootstrap(metrics)
|
||||
MetricsSystem.bootstrapInternal(metrics)
|
||||
// run the test
|
||||
let name = "gauge-\(NSUUID().uuidString)"
|
||||
let value = Double.random(in: -1000 ... 1000)
|
||||
Metrics.global.withGauge(label: name) { $0.record(value) }
|
||||
Gauge(label: name).record(value)
|
||||
let recorder = metrics.recorders[name] as! TestRecorder
|
||||
XCTAssertEqual(recorder.values.count, 1, "expected number of entries to match")
|
||||
XCTAssertEqual(recorder.values[0].1, value, "expected value to match")
|
||||
|
|
@ -199,19 +205,28 @@ class MetricsTests: XCTestCase {
|
|||
|
||||
func testMUX() throws {
|
||||
// bootstrap with our test metrics
|
||||
let handlers = [TestMetrics(), TestMetrics(), TestMetrics()]
|
||||
Metrics.bootstrap(MultiplexMetricsHandler(handlers: handlers))
|
||||
let factories = [TestMetrics(), TestMetrics(), TestMetrics()]
|
||||
MetricsSystem.bootstrapInternal(MultiplexMetricsHandler(factories: factories))
|
||||
// run the test
|
||||
let name = NSUUID().uuidString
|
||||
let value = Int.random(in: Int.min ... Int.max)
|
||||
Metrics.global.withCounter(label: name) { counter in
|
||||
counter.increment(value)
|
||||
}
|
||||
handlers.forEach { handler in
|
||||
let counter = handler.counters.first?.1 as! TestCounter
|
||||
Counter(label: name).increment(value)
|
||||
factories.forEach { factory in
|
||||
let counter = factory.counters.first?.1 as! TestCounter
|
||||
XCTAssertEqual(counter.label, name, "expected label to match")
|
||||
XCTAssertEqual(counter.values.count, 1, "expected number of entries to match")
|
||||
XCTAssertEqual(counter.values[0].1, Int64(value), "expected value to match")
|
||||
}
|
||||
}
|
||||
|
||||
func testCustomFactory() {
|
||||
class CustomHandler: CounterHandler {
|
||||
func increment<DataType>(_: DataType) where DataType: BinaryInteger {}
|
||||
}
|
||||
|
||||
let counter1 = Counter(label: "foo")
|
||||
XCTAssertFalse(counter1.handler is CustomHandler, "expected non-custom log handler")
|
||||
let counter2 = Counter(label: "foo", dimensions: [], handler: CustomHandler())
|
||||
XCTAssertTrue(counter2.handler is CustomHandler, "expected custom log handler")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
@testable import CoreMetrics
|
||||
@testable import Metrics
|
||||
import XCTest
|
||||
|
||||
|
|
@ -19,11 +20,11 @@ class MetricsExtensionsTests: XCTestCase {
|
|||
func testTimerBlock() throws {
|
||||
// bootstrap with our test metrics
|
||||
let metrics = TestMetrics()
|
||||
Metrics.bootstrap(metrics)
|
||||
MetricsSystem.bootstrapInternal(metrics)
|
||||
// run the test
|
||||
let name = "timer-\(NSUUID().uuidString)"
|
||||
let delay = 0.05
|
||||
Metrics.global.timed(label: name) {
|
||||
Timer.measure(label: name) {
|
||||
Thread.sleep(forTimeInterval: delay)
|
||||
}
|
||||
let timer = metrics.timers[name] as! TestTimer
|
||||
|
|
@ -34,44 +35,46 @@ class MetricsExtensionsTests: XCTestCase {
|
|||
func testTimerWithTimeInterval() throws {
|
||||
// bootstrap with our test metrics
|
||||
let metrics = TestMetrics()
|
||||
Metrics.bootstrap(metrics)
|
||||
MetricsSystem.bootstrapInternal(metrics)
|
||||
// run the test
|
||||
let timer = Metrics.global.makeTimer(label: "test-timer") as! TestTimer
|
||||
let timer = Timer(label: "test-timer")
|
||||
let testTimer = timer.handler as! TestTimer
|
||||
let timeInterval = TimeInterval(Double.random(in: 1 ... 500))
|
||||
timer.record(timeInterval)
|
||||
XCTAssertEqual(1, timer.values.count, "expected number of entries to match")
|
||||
XCTAssertEqual(timer.values[0].1, Int64(timeInterval * 1_000_000_000), "expected value to match")
|
||||
XCTAssertEqual(1, testTimer.values.count, "expected number of entries to match")
|
||||
XCTAssertEqual(testTimer.values[0].1, Int64(timeInterval * 1_000_000_000), "expected value to match")
|
||||
}
|
||||
|
||||
func testTimerWithDispatchTime() throws {
|
||||
// bootstrap with our test metrics
|
||||
let metrics = TestMetrics()
|
||||
Metrics.bootstrap(metrics)
|
||||
MetricsSystem.bootstrapInternal(metrics)
|
||||
// run the test
|
||||
let timer = Metrics.global.makeTimer(label: "test-timer") as! TestTimer
|
||||
let timer = Timer(label: "test-timer")
|
||||
let testTimer = timer.handler as! TestTimer
|
||||
// nano
|
||||
let nano = DispatchTimeInterval.nanoseconds(Int.random(in: 1 ... 500))
|
||||
timer.record(nano)
|
||||
XCTAssertEqual(timer.values.count, 1, "expected number of entries to match")
|
||||
XCTAssertEqual(.nanoseconds(Int(timer.values[0].1)), nano, "expected value to match")
|
||||
XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match")
|
||||
XCTAssertEqual(.nanoseconds(Int(testTimer.values[0].1)), nano, "expected value to match")
|
||||
// micro
|
||||
let micro = DispatchTimeInterval.microseconds(Int.random(in: 1 ... 500))
|
||||
timer.record(micro)
|
||||
XCTAssertEqual(timer.values.count, 2, "expected number of entries to match")
|
||||
XCTAssertEqual(.nanoseconds(Int(timer.values[1].1)), micro, "expected value to match")
|
||||
XCTAssertEqual(testTimer.values.count, 2, "expected number of entries to match")
|
||||
XCTAssertEqual(.nanoseconds(Int(testTimer.values[1].1)), micro, "expected value to match")
|
||||
// milli
|
||||
let milli = DispatchTimeInterval.milliseconds(Int.random(in: 1 ... 500))
|
||||
timer.record(milli)
|
||||
XCTAssertEqual(timer.values.count, 3, "expected number of entries to match")
|
||||
XCTAssertEqual(.nanoseconds(Int(timer.values[2].1)), milli, "expected value to match")
|
||||
XCTAssertEqual(testTimer.values.count, 3, "expected number of entries to match")
|
||||
XCTAssertEqual(.nanoseconds(Int(testTimer.values[2].1)), milli, "expected value to match")
|
||||
// seconds
|
||||
let sec = DispatchTimeInterval.seconds(Int.random(in: 1 ... 500))
|
||||
timer.record(sec)
|
||||
XCTAssertEqual(timer.values.count, 4, "expected number of entries to match")
|
||||
XCTAssertEqual(.nanoseconds(Int(timer.values[3].1)), sec, "expected value to match")
|
||||
XCTAssertEqual(testTimer.values.count, 4, "expected number of entries to match")
|
||||
XCTAssertEqual(.nanoseconds(Int(testTimer.values[3].1)), sec, "expected value to match")
|
||||
// never
|
||||
timer.record(DispatchTimeInterval.never)
|
||||
XCTAssertEqual(timer.values.count, 5, "expected number of entries to match")
|
||||
XCTAssertEqual(timer.values[4].1, 0, "expected value to match")
|
||||
XCTAssertEqual(testTimer.values.count, 5, "expected number of entries to match")
|
||||
XCTAssertEqual(testTimer.values[4].1, 0, "expected value to match")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,27 +13,27 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
|
||||
@testable import CoreMetrics
|
||||
@testable import protocol CoreMetrics.Timer
|
||||
@testable import class CoreMetrics.Timer
|
||||
import Foundation
|
||||
|
||||
internal class TestMetrics: MetricsHandler {
|
||||
internal class TestMetrics: MetricsFactory {
|
||||
private let lock = NSLock() // TODO: consider lock per cache?
|
||||
var counters = [String: Counter]()
|
||||
var recorders = [String: Recorder]()
|
||||
var timers = [String: Timer]()
|
||||
var counters = [String: CounterHandler]()
|
||||
var recorders = [String: RecorderHandler]()
|
||||
var timers = [String: TimerHandler]()
|
||||
|
||||
public func makeCounter(label: String, dimensions: [(String, String)]) -> Counter {
|
||||
public func makeCounter(label: String, dimensions: [(String, String)]) -> CounterHandler {
|
||||
return self.make(label: label, dimensions: dimensions, registry: &self.counters, maker: TestCounter.init)
|
||||
}
|
||||
|
||||
public func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> Recorder {
|
||||
let maker = { (label: String, dimensions: [(String, String)]) -> Recorder in
|
||||
public func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> RecorderHandler {
|
||||
let maker = { (label: String, dimensions: [(String, String)]) -> RecorderHandler in
|
||||
TestRecorder(label: label, dimensions: dimensions, aggregate: aggregate)
|
||||
}
|
||||
return self.make(label: label, dimensions: dimensions, registry: &self.recorders, maker: maker)
|
||||
}
|
||||
|
||||
public func makeTimer(label: String, dimensions: [(String, String)]) -> Timer {
|
||||
public func makeTimer(label: String, dimensions: [(String, String)]) -> TimerHandler {
|
||||
return self.make(label: label, dimensions: dimensions, registry: &self.timers, maker: TestTimer.init)
|
||||
}
|
||||
|
||||
|
|
@ -46,7 +46,7 @@ internal class TestMetrics: MetricsHandler {
|
|||
}
|
||||
}
|
||||
|
||||
internal class TestCounter: Counter, Equatable {
|
||||
internal class TestCounter: CounterHandler, Equatable {
|
||||
let id: String
|
||||
let label: String
|
||||
let dimensions: [(String, String)]
|
||||
|
|
@ -72,7 +72,7 @@ internal class TestCounter: Counter, Equatable {
|
|||
}
|
||||
}
|
||||
|
||||
internal class TestRecorder: Recorder, Equatable {
|
||||
internal class TestRecorder: RecorderHandler, Equatable {
|
||||
let id: String
|
||||
let label: String
|
||||
let dimensions: [(String, String)]
|
||||
|
|
@ -105,7 +105,7 @@ internal class TestRecorder: Recorder, Equatable {
|
|||
}
|
||||
}
|
||||
|
||||
internal class TestTimer: Timer, Equatable {
|
||||
internal class TestTimer: TimerHandler, Equatable {
|
||||
let id: String
|
||||
let label: String
|
||||
let dimensions: [(String, String)]
|
||||
|
|
|
|||
Loading…
Reference in New Issue