Metrics (original) (raw)
January 8, 2019, 9:30pm 1
Another area the Swift Server working group is looking to improve is server-side metrics. Currently, there is no standard metrics solution which works across frameworks and libraries.
We are interested to collect ideas and requirements people have, especially where they are informed by experience in other language ecosystems, or experience deploying server-side applications in production today.
To kick the discussion off, I'll reply to this message with some initials thoughts of mine.
tomerd January 8, 2019, 9:44pm 2
wSimilar to what we are defining for logging, we would like to suggest an abstract metrics API which will allow application owners to plug-in different metrics backends, and library developers to emit metrics without getting in the way. Reasoning for such architecture is summarized in here, that post is about logging but the same is true for metrics as well.
An example for such abstract API could look something like:
public protocol Counter: AnyObject {
func increment<DataType: BinaryInteger>(_ value: DataType)
}
public protocol Recorder: AnyObject {
func record<DataType: BinaryInteger>(_ value: DataType)
func record<DataType: BinaryFloatingPoint>(_ value: DataType)
}
public protocol Timer: AnyObject {
func recordNanoseconds(_ duration: Int64)
}
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 makeGauge(label: String, dimensions: [(String, String)] = []) -> Recorder {
return self.makeRecorder(label: label, dimensions: dimensions, aggregate: false)
}
}
this means:
- the actual metrics library (eg https://forums.swift.org/t/client-side-prometheus-implementation) will implement
MetricsHandler
- the application owner bootstrap the with metrics library of choice (exact API tbd)
- application and libraries developers call something like the below when they want to emit metrics
let counter = Metrics.makeCounter("foo")
...
counter.increment()`
tanner0101 (Tanner) January 10, 2019, 6:39pm 3
This looks like a great start. A couple questions:
Why do the protocols conform to AnyObject
? Is this just another way of restricting the protocol to a class, or does it mean something more? Side note, maybe it would be nice to have a protocol for this like MetricsObject
or something that unites them.
How will you register which MetricsHandler
you want to use? Will that be exactly like how logging works? Something like
Metrics.bootstrap(PrometheusMetricsHandler.init)
How will using shared metrics objects work? It seems that with the current design, if you wanted to use a shared counter, recorder, and timer you would need to pass those all separately (i.e., req.counter.increment()
, req.recorder.record(...)
). Comparing to logging, we have:
Metrics
=Logging
MetricsHandler
=LogHandler
AnyObject
(MetricsObject
) =Logger
, but more than one
Maybe there should be a separate, unified type like Logger
in this package. With Logging, you just pass around the Logger
and you can log any type of message (i.e., req.logger.log(...)
).
But I'm not really sure if this difference is a bad thing. Maybe it's a common use case to only want to pass around one type of metrics object.
tomerd January 10, 2019, 9:59pm 4
thanks for the feedback @tanner0101
Why do the protocols conform to
AnyObject
? Is this just another way of restricting the protocol to a class, or does it mean something more?
by forcing them to be classes, we can reduce their memory footprint, and there is no real advantage of allowing them to be structs as they don't carry state
How will you register which
MetricsHandler
you want to use? Will that be exactly like how logging works?
exactly! you would "bind" your backend of choice at application bootstrap time, just as you described
Maybe there should be a separate, unified type like
Logger
in this package. With Logging, you just pass around theLogger
and you can log any type of message
the main reason to pass loggers around (for example, expose them at the request/context object) is that they carry contextual state (metadata) which you want to preserve. with metrics, you dont typically carry such state, so its less often the case you want to pass them around. having said that, the dimensions are typically contextual so an argument could be made to pass some metrics object that carries them around and from which you can create named timers, counters, etc. good direction to explore further, not sure of it should be part of the core api or implemented on top by frameworks that want to operate in that manner
tanner0101 (Tanner) January 11, 2019, 8:46pm 5
Ah okay, that makes sense then. Thanks!
Blejwi (Damian) January 14, 2019, 12:51pm 6
Hello,
Great work with the proposal!
I'm thinking about a couple of usability improvements for Timer
class, which may be very useful for future Metrics
users.
I've just posted GitHub issue with my idea, here is the link: Metrics Timer usability improvement idea · Issue #2 · tomerd/swift-server-metrics-api-proposal · GitHub
Didn't want to copy-paste the whole issue, if it's more convenient for you just let me know and I will move it here from GitHub.
Thanks!