-
Notifications
You must be signed in to change notification settings - Fork 35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New Version 2.0 API #92
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really nice! Left some comments inline
@@ -0,0 +1,24 @@ | |||
# file options |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In all of our recent projects, we actually adopted swift-format
since it has a more stable formatting between versions. Would recommend picking it here as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we change this in a follow up? #94
import Atomics | ||
import CoreMetrics | ||
|
||
public final class Counter: Sendable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh we require that all metric backing handlers are classes? This is a bit sad since this could just be a struct with reference semantics.
Second question, does this need to be public?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh we require that all metric backing handlers are classes? This is a bit sad since this could just be a struct with reference semantics.
I eventually want to change swift-metrics to not require AnyObject anymore. This will make NoOps much cheaper.
Second question, does this need to be public?
Yes. I think users should be able to use the Prometheus lib without going through swift-metrics.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed on both, eventually we can do a revamp of metrics lib. It's one of the earliest and we've learned much since.
Yes, types should be public, prom has more detailed semantics than swift metrics so an "app" can definitely use it direcly
self.labels = labels | ||
|
||
var prerendered = [UInt8]() | ||
prerendered.reserveCapacity(64) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you comment why we need to reserve 64 here?
|
||
A prometheus client library for Swift. | ||
|
||
## Overview |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add installation instructions to the index here as well. Allows us to point people directly at the documentation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we do this in a follow up?
Sources/Prometheus/Histogram.swift
Outdated
public typealias ValueHistogram = Histogram<Double> | ||
|
||
public final class Histogram<Value: Bucketable>: @unchecked Sendable { | ||
private let lock = NIOLock() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we please use a NIOLockedValueBox
here. We are really really encouraging everyone to not just put @unchcked Sendable
on their types but rather stick everything into the locked value box since that only has condition Sendable
conformance and we have come across a few places where people used a lock and incorrectly declared @unchecked Sendable
.
/// | ||
/// To use a ``PrometheusCollectorRegistry`` with `swift-metrics` use the ``PrometheusMetricsFactory``. | ||
public final class PrometheusCollectorRegistry: @unchecked Sendable { | ||
// Sendability is enforced through the internal `lock` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here please
|
||
import CoreMetrics | ||
|
||
/// A wrapper around ``PrometheusClient`` to implement the `swift-metrics` `MetricsFactory` protocol |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no PrometheusClient
anymore
|
||
/// A wrapper around ``PrometheusClient`` to implement the `swift-metrics` `MetricsFactory` protocol | ||
public struct PrometheusMetricsFactory: Sendable { | ||
/// The underlying ``PrometheusClient`` that is used to generate |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here ;)
public var valueHistogramBuckets: [String: [Double]] | ||
|
||
public var labelAndDimensionSanitizer: @Sendable (_ label: String, _ dimensions: [(String, String)]) -> (String, [(String, String)]) | ||
|
||
public init(client: PrometheusCollectorRegistry) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we get some docs on those three things?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also do I have to create two things now to bootstrap prometheus? Both the registry
and the factory
? Couldn't we just default this parameter here to .init()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we should create a new one, or if we should provide a .default
singleton. I think I'd be more in favor of the .default
, wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see a big benefit on a default singleton here and we avoided introducing singletons in AHC because we want to discuss this in the SSWG in a broader scope. So I would say we shouldn't start introducing it here. Just .init()
default seems fine to me.
if let prerenderedLabels = Self.prerenderLabels(labels) { | ||
prerendered.append(UInt8(ascii: "{")) | ||
prerendered.append(contentsOf: prerenderedLabels) | ||
prerendered.append(contentsOf: #"} "#.utf8) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can't use ascii here?
} | ||
|
||
public func increment(by amount: Int64) { | ||
precondition(amount >= 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we give those preconditions good messages always please? It's much nicer user experience when crashes contain message
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW crashed don't contain precondition messages. Only fatal errors messages are included. I have personally switched over to fatal error exclusively because of that reason
Sources/Prometheus/Counter.swift
Outdated
|
||
public final class Counter: Sendable { | ||
let intAtomic = ManagedAtomic(Int64(0)) | ||
let floatAtomic = ManagedAtomic(Double(0).bitPattern) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit but it's weird to call a Double property "float"; yeah "floating point" but it's a double ->
let floatAtomic = ManagedAtomic(Double(0).bitPattern) | |
let doubleAtomic = ManagedAtomic(Double(0).bitPattern) |
Sources/Prometheus/Counter.swift
Outdated
|
||
let name: String | ||
let labels: [(String, String)] | ||
let prerenderedExport: [UInt8] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's the key to the perf wins I suppose, good 👍
// We busy loop here until we can update the atomic successfully. | ||
// Using relaxed ordering here is sufficient, since the as-if rules guarantess that | ||
// the following operations are executed in the order presented here. Every statement | ||
// depends on the execution before. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's clear here but thx for comments around busy loops, always good to document those 💯
import Atomics | ||
import CoreMetrics | ||
|
||
public final class Gauge: Sendable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In follow up please add docs to all those types
self.atomic.store(value.bitPattern, ordering: .relaxed) | ||
} | ||
|
||
public func increment(by amount: Double = 1.0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A bit inconsistent, on purpose? The counter had Int64 variants of APIs as well, no need here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nope not un porpose
|
||
import CoreMetrics | ||
|
||
public protocol Bucketable: AdditiveArithmetic, Comparable, Sendable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Docs for all those types
Sources/Prometheus/Histogram.swift
Outdated
var bucketRepresentation: String { get } | ||
} | ||
|
||
public typealias TimeHistogram = Histogram<Duration> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm.... not really "Time", or is it from prom specs...? If not, then mirror the contained value maybe?
I'd expect DurationHistogram
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed to DurationHistogram
|
||
// MARK: Destroying Metrics | ||
|
||
public func destroyCounter(_ counter: Counter) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They're pretty clear but please document.
E.g. if one can make a new one after a destroy etc
// MARK: Emitting | ||
|
||
public func emit(into buffer: inout [UInt8]) { | ||
let metrics = self.box.withLockedValue { $0 } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this is the NIO type, but tbh for such there should be a get() on it, WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM overall, minor comments inline.
Looks nice! |
Feel free to merge at will btw 👍 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Everything looks good but we are missing an implementation of the newly added Meter
type.
In swift-server#92, we added a Histogram that is generic over the observed type. This means that each Histogram was also carrying a generic argument. To work around this we created typealiases for `DurationHistogram` and `ValueHistogram`. The motivation behind this was to preserve as much detailed information as possible. Duration can be more precise than Double. However in the grand scheme of things we now believe that this is overkill and a simpler to use Histogram type is more valuable. Thus this patch removes the generic argument from Histogram.
Motivation
Swift Prometheus has been in development since 2018. The previous maintainer (@MrLotU) stepped down about a year ago. Since then @ktoso and @fabianfett have maintained the existing code base.
In order to improve performance in the Prometheus library, we want to replace the existing implementation with a new one for version 2.0. While we do this we also want to get the general API closer to what the prometheus project recommends in their Writing client libraries guide. This means that we will also start to enforce Prometheus rules. For example having two counter with the same name, but one with labels and one without, is illegal in the Prometheus format. Lastly we want to remove the SwiftNIO dependency that is not needed anymore.
I'm well aware that this is a very large PR that changes lots of moving things.
Changes
NIOConcurrencyHelpers
PrometheusClient
withPrometheusCollectorRegistry
Summary
implementationCounter
,Gauge
andHistogram
implementationsPerformance difference
In simple testing scenarios:
Differences to Writing client libraries guide
Collector
protocol, that users could implement to create their own instruments.Collector
PrometheusCollectorRegistry
does not offer aregister
andunregister
method forCollector
sPrometheusCollectorRegistry
PrometheusCollectorRegistry
scollector
Do we want to offer this API? With the current patch we could add this API later.