Proposal Review: SSWG-0008 (Prometheus Client)
After the discussion thread , we are proposing this as a final revision of this proposal and enter the proposal review phase which will run until the 1st August 2019.
The feedback model will be very similar to the one known from Swift Evolution. The community is asked to provide feedback in the way outlined below and after the review period finishes, the SSWG will -- based on the community feedback -- decide whether to promote the proposal to the Sandbox maturity level or not.
What goes into a review of a proposal?
The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the evolution of the server-side Swift ecosystem.
When reviewing a proposal, here are some questions to consider:
- What is your evaluation of the proposal?
- Is the problem being addressed significant enough?
- Does this proposal fit well with the feel and direction of Swift on Server?
- If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
- How much effort did you put into your review? A glance, a quick reading, or an in-depth study?
Thank you for contributing to the Swift Server Work Group!
What happens if the proposal gets accepted?
If this proposal gets accepted, the official repository will be created and the code (minus examples, the proposal text, etc) will be submitted. The repository will then become usable as a SwiftPM package and a version (likely 0.1.0
) will be tagged. The development (in form of pull requests) will continue as a regular open-source project.
SwiftPrometheus - Prometheus Metrics in Swift
- Proposal: SSWG-0008
- Authors: Jari Koopman / LotU
- Review Manager: TBD
- Status: Implemented
- Pitch: Pitches/Prometheus
Package Description
Prometheus client side implementation.
Package Name | SwiftPrometheus |
Module Name |
Prometheus & PrometheusMetrics
|
Proposed Maturity Level | Sandbox |
License | Apache 2.0 |
Dependencies | swift-nio 1.0.0..<3.0.0 (1.x.x & 2.x.x) - swift-metrics > 1.0.0 |
Introduction
For a background on metrics see the metrics proposal discussion and feedback thread.
Prometheus is one of the most widely used libraries for metrics in the serverside world. SwiftPrometheus is a client side implementation in Swift, with the ability to use it both connected to & separately from swift-metrics.
Motivation
With Prometheus being one of the most widely used metric reporting tools, it's a buildstone that can not be left out in a serverside ecosystem. This package is created for everyone to use & build upon for their metric reporting.
Detailed design
SwiftPrometheus works around one base class PrometheusClient
and some metric types around it. The prometheus metric types are:
(from the prometheus docs)
- Counter - A counter is a cumulative metric that represents a single monotonically increasing counter whose value can only increase or be reset to zero on restart.
- Gauge - A gauge is a metric that represents a single numerical value that can arbitrarily go up and down.
- Histogram - A histogram samples observations (usually things like request durations or response sizes) and counts them in configurable buckets. It also provides a sum of all observed values.
- Summary - Similar to a histogram , a summary samples observations (usually things like request durations and response sizes). While it also provides a total count of observations and a sum of all observed values, it calculates configurable quantiles over a sliding time window.
SwiftPrometheus provides fully featured implementations for all of them, including a thin wrapper around them for integration with swift-metrics
.
API Layout
Below section will lay out the public API of this package. For the internal APIs I would suggest you to read through the code on GitHub . This section is split up into two parts, using this library standalone, or using it integrated with the swift-metrics
package.
Without swift-metrics
To get started, initialise an instance of PrometheusClient
let myProm = PrometheusClient()
Once done, you can use the create*
APIs to create any of the above described metric types.
// MetricLabels is a helper type used to add labeled metrics.
struct MyCodable: MetricLabels {
var thing: String = "*"
}
// - Counter
let counter = myProm.createCounter(forType: Int.self, named: "my_counter", helpText: "Just a counter", initialValue: 42, withLabelType: MyCodable.self)
counter.inc() // Increment by one
counter.inc(12) // Increment by a value
counter.inc(12, MyCodable(thing: "test")) // Increment a labeled counter
// - Gauge
let gauge = myProm.createGauge(forType: Int.self, named: "my_gauge", helpText: "Just a gauge", initialValue: 42, withLabelType: MyCodable.self)
gauge.inc() // Same APIs as Counter
gauge.dec() // Same APIs as `inc()` but reversed.
gauge.set(42) // Set the gauge to a specific value
// - Histogram
// Histograms use special labels, different than the Counter & Gauge
struct HistogramLabel: HistogramLabels {
var le: String = ""
let route: String
init() {
self.route = "*"
}
init(_ route: String) {
self.route = route
}
}
let histogram = myProm.createHistogram(forType: Double.self, named: "my_histogram", helpText: "Just a histogram", labels: HistogramLabel.self)
histogram.observe(123) // Observes a value
// - Summary
// Like Histograms, Summaries use different label types.
struct SummaryLabel: SummaryLabels {
var quantile: String = ""
let route: String
init() {
self.route = "*"
}
init(_ route: String) {
self.route = route
}
}
let summary = myProm.createSummary(forType: Double.self, named: "my_summary", helpText: "Just a summary", labels: SummaryLabel.self)
summary.observe(123) // Observes a value
Then, after you have some metric types, you can use .collect()
on your PromtheusClient
to get your Prometheus formatted string with all the data.
For example, in a Vapor app:
router.get("/metrics") { req -> String in
return myProm.collect()
}
With swift-metrics
For use with swift-metrics, most of the steps described above work the same. To bootstrap the MetricsSystem you create a client and feed it to MetricsSystem
:
let myProm = PrometheusClient()
MetricsSystem.bootstrap(myProm)
After that, you can use the metric types used by swift-metrics for your metrics. The mapping is as follows:
swift-metrics | SwiftPrometheus |
---|---|
Counter | Counter |
Gauge | Gauge |
Recorder (agg) | Histogram |
Timer | Summary |
To get a hold of your PrometheusClient
either to:
a) use custom prometheus behaviour; or
b) get your metrics output
there is a utility function on MetricsSystem
let myProm = try MetricsSystem.prometheus()
This will either return the PrometheusClient
used with .bootstrap()
or throw an error if MetricsSystem
was not bootstrapped with PrometheusClient
Note: There currently is no support for retrieving PrometheusClient
when being used with MultiplexMetricsHandler
Maturity Justification
The implementation has the full feature set required for production use and meets the minimum requirements set forth by the SSWG (except for the fact that I'm a 1 man army creating this library)
Alternatives considered
Other than using a different metrics backend than Prometheus, there are not many alternatives to consider. One thing I'd like to point out though:
This library has support for the destroying of metrics in the way set forth by the swift-metrics
package. However, as described in the Prometheus documentation, once a metric is created with a specific type, so for example a Counter
named my_counter
and that counter is destroyed, it's not allowed to, at a later time, re-create a metric named my_counter
with a DIFFERENT type. (Creating another counter is fine). To keep track of this, PrometheusClient
will hold a dictionary of metric names & types. ([String: MetricType]
). This means that even if you destroy your metrics, your memory footprint will (gradually) increase. All of this is process bound and will reset on a process restart.
Thanks & ending notes
On the ending note of this proposal, I would like to thank a few people specifically:
-
@johannesweiss - Technical help & advise
-
@ktoso - Technical help & advise
-
Anyone who gave input during the initial pitch.
Next to these specific mentions, I'd like to thank you for taking the time to read my proposal and I would love for you to leave a comment below with your thoughts & comments