update readme to reflect API changes (#10)
motivation: prepare to final pitch to community changes: adjust readme with latest API changes
This commit is contained in:
parent
1f44332af3
commit
a5aa28873c
80
README.md
80
README.md
|
|
@ -29,7 +29,7 @@ counter.increment(100)
|
||||||
recorder.record(100)
|
recorder.record(100)
|
||||||
```
|
```
|
||||||
|
|
||||||
`Gauges`: A Gauge is a metric that represents a single numerical value that can arbitrarily go up and down. Gauges are typically used for measured values like temperatures or current memory usage, but also "counts" that can go up and down, like the number of active threads. Gauges are modeled as `Recorder` with a sample size of 1 and that does not perform any aggregation.
|
`Gauge`: A Gauge is a metric that represents a single numerical value that can arbitrarily go up and down. Gauges are typically used for measured values like temperatures or current memory usage, but also "counts" that can go up and down, like the number of active threads. Gauges are modeled as `Recorder` with a sample size of 1 and that does not perform any aggregation.
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
gauge.record(100)
|
gauge.record(100)
|
||||||
|
|
@ -45,10 +45,10 @@ How would you use `counter`, `recorder`, `gauge` and `timer` in you application
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
func processRequest(request: Request) -> Response {
|
func processRequest(request: Request) -> Response {
|
||||||
let requestCounter = Metrics.makeCounter("request.count", ["url": request.url])
|
let requestCounter = Counter("request.count", ["url": request.url])
|
||||||
let requestTimer = Metrics.makeTimer("request.duration", ["url": request.url])
|
let requestTimer = Timer("request.duration", ["url": request.url])
|
||||||
let requestSizeRecorder = Metrics.makeRecorder("request.size", ["url": request.url])
|
let requestSizeRecorder = Recorder("request.size", ["url": request.url])
|
||||||
let responseSizeRecorder = Metrics.makeRecorder("response.size", ["url": request.url])
|
let responseSizeRecorder = Recorder("response.size", ["url": request.url])
|
||||||
|
|
||||||
requestCounter.increment()
|
requestCounter.increment()
|
||||||
requestSizeRecorder.record(request.size)
|
requestSizeRecorder.record(request.size)
|
||||||
|
|
@ -60,50 +60,75 @@ How would you use `counter`, `recorder`, `gauge` and `timer` in you application
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
To ensure performance, `Metrics.makeXxx` can return a cached copy of the metric object so can be called on the hot path.
|
|
||||||
|
|
||||||
## Detailed design
|
## Detailed design
|
||||||
|
|
||||||
### Implementing a metrics backend (eg prometheus client library)
|
### Implementing a metrics backend (eg prometheus client library)
|
||||||
|
|
||||||
As seen above, the general function `Metrics.makeXxx` provides a metric object. This raises the question of what metrics backend I will actually get when calling `Metrics.makeXxx`? The answer is that it's configurable _per application_. The application sets up the metrics backend it wishes the whole application to use. Libraries should never change the metrics implementation as that is something owned by the application. Configuring the metrics backend is straightforward:
|
As seen above, the constructors `Counter`, `Timer`, `Recorder` and `Gauge` provides a metric object. This raises the question of what metrics backend I will actually get when calling these constructors? The answer is that it's configurable _per application_. The application sets up the metrics backend it wishes the whole application to use. Libraries should never change the metrics implementation as that is something owned by the application. Configuring the metrics backend is straightforward:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
Metrics.bootstrap(MyFavouriteMetricsImplementation.init)
|
MetricsSystem.bootstrap(MyFavouriteMetricsImplementation.init)
|
||||||
```
|
```
|
||||||
|
|
||||||
This instructs the `Metrics` system to install `MyFavouriteMetricsImplementation` as the metrics backend (`MetricsHandler`) to use. This should only be done once at the beginning of the program.
|
This instructs the `MetricsSystem` to install `MyFavouriteMetricsImplementation` as the metrics backend (`MetricsFactory`) to use. This should only be done once at the beginning of the program.
|
||||||
|
|
||||||
Given the above, an implementation of a metric backend needs to conform to `protocol MetricsHandler`:
|
Given the above, an implementation of a metric backend needs to conform to `protocol MetricsFactory`:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
public protocol MetricsHandler {
|
public protocol MetricsFactory {
|
||||||
func makeCounter(label: String, dimensions: [(String, String)]) -> Counter
|
func makeCounter(label: String, dimensions: [(String, String)]) -> CounterHandler
|
||||||
func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> Recorder
|
func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> RecorderHandler
|
||||||
func makeTimer(label: String, dimensions: [(String, String)]) -> Timer
|
func makeTimer(label: String, dimensions: [(String, String)]) -> TimerHandler
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Here is an example in-memory implementation:
|
The `MetricsFactory` is responsible for instantiating the concrete metrics classes that capture the metrics and perform aggregation and calculation of various quantiles as needed.
|
||||||
|
|
||||||
|
**Counter**
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
class SimpleMetricsLibrary: MetricsHandler {
|
public protocol CounterHandler: AnyObject {
|
||||||
|
func increment<DataType: BinaryInteger>(_ value: DataType)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Timer**
|
||||||
|
|
||||||
|
```swift
|
||||||
|
public protocol TimerHandler: AnyObject {
|
||||||
|
func recordNanoseconds(_ duration: Int64)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Recorder**
|
||||||
|
|
||||||
|
```swift
|
||||||
|
public protocol RecorderHandler: AnyObject {
|
||||||
|
func record<DataType: BinaryInteger>(_ value: DataType)
|
||||||
|
func record<DataType: BinaryFloatingPoint>(_ value: DataType)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Here is a full example of an in-memory implementation:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
class SimpleMetricsLibrary: MetricsFactory {
|
||||||
init() {}
|
init() {}
|
||||||
|
|
||||||
func makeCounter(label: String, dimensions: [(String, String)]) -> Counter {
|
func makeCounter(label: String, dimensions: [(String, String)]) -> CounterHandler {
|
||||||
return ExampleCounter(label, dimensions)
|
return ExampleCounter(label, dimensions)
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> Recorder {
|
func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> RecorderHandler {
|
||||||
let maker:(String, [(String, String)]) -> Recorder = aggregate ? ExampleRecorder.init : ExampleGauge.init
|
let maker:(String, [(String, String)]) -> Recorder = aggregate ? ExampleRecorder.init : ExampleGauge.init
|
||||||
return maker(label, dimensions)
|
return maker(label, dimensions)
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeTimer(label: String, dimensions: [(String, String)]) -> Timer {
|
func makeTimer(label: String, dimensions: [(String, String)]) -> TimerHandler {
|
||||||
return ExampleTimer(label, dimensions)
|
return ExampleTimer(label, dimensions)
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ExampleCounter: Counter {
|
private class ExampleCounter: CounterHandler {
|
||||||
init(_: String, _: [(String, String)]) {}
|
init(_: String, _: [(String, String)]) {}
|
||||||
|
|
||||||
let lock = NSLock()
|
let lock = NSLock()
|
||||||
|
|
@ -115,7 +140,7 @@ class SimpleMetricsLibrary: MetricsHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ExampleRecorder: Recorder {
|
private class ExampleRecorder: RecorderHandler {
|
||||||
init(_: String, _: [(String, String)]) {}
|
init(_: String, _: [(String, String)]) {}
|
||||||
|
|
||||||
private let lock = NSLock()
|
private let lock = NSLock()
|
||||||
|
|
@ -158,7 +183,7 @@ class SimpleMetricsLibrary: MetricsHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ExampleGauge: Recorder {
|
private class ExampleGauge: RecorderHandler {
|
||||||
init(_: String, _: [(String, String)]) {}
|
init(_: String, _: [(String, String)]) {}
|
||||||
|
|
||||||
let lock = NSLock()
|
let lock = NSLock()
|
||||||
|
|
@ -173,7 +198,7 @@ class SimpleMetricsLibrary: MetricsHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ExampleTimer: ExampleRecorder, Timer {
|
private class ExampleTimer: ExampleRecorder, TimerHandler {
|
||||||
func recordNanoseconds(_ duration: Int64) {
|
func recordNanoseconds(_ duration: Int64) {
|
||||||
super.record(duration)
|
super.record(duration)
|
||||||
}
|
}
|
||||||
|
|
@ -181,13 +206,6 @@ class SimpleMetricsLibrary: MetricsHandler {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
which is installed using
|
|
||||||
|
|
||||||
```swift
|
|
||||||
Metrics.bootstrap(SimpleMetricsLibrary.init)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## State
|
## State
|
||||||
|
|
||||||
This is an early proposal so there are still plenty of things to decide and tweak and I'd invite everybody to participate.
|
This is an early proposal so there are still plenty of things to decide and tweak and I'd invite everybody to participate.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue