Skip to content

Commit

Permalink
Release 1.0.0 (#69)
Browse files Browse the repository at this point in the history
* Sanitize Dimensions (#68)

* Add failing test for sanitising dimension

* Fix test

* Add new DimensionsSanitizer

* Remove labels parametrisation (#63)

* Stop leaking 'le', 'quantile' in Summary/Histogram labels, remove labels parametrisation

* chore: Remove base labels protocol and add ExpressibleByArrayLiteral

* chore: Cleanup documentation. Remove deprecations

Co-authored-by: Jari (LotU) <[email protected]>

* Add async/await APIs (#67)

* Add async/await APIs

* Revert unrelated change

* Add #if swift for 5.2

* Fix Swift version number check

* Add task API

Co-authored-by: Tim Condon <[email protected]>
Co-authored-by: Anton <[email protected]>
  • Loading branch information
3 people authored Jun 20, 2022
1 parent 36740d1 commit 688e1fd
Show file tree
Hide file tree
Showing 21 changed files with 437 additions and 584 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
build/
.swiftpm/
.idea
.vscode/
5 changes: 1 addition & 4 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ let package = Package(
products: [
.library(
name: "SwiftPrometheus",
targets: ["Prometheus"]),
.executable(
name: "PrometheusExample",
targets: ["PrometheusExample"]),
targets: ["Prometheus"])
],
dependencies: [
.package(url: "https://github.com/apple/swift-metrics.git", from: "2.2.0"),
Expand Down
26 changes: 8 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,20 +102,18 @@ summary.observe(4.7) // Observe the given value
```

## Labels
All metric types support adding labels, allowing for grouping of related metrics.
All metric types support adding labels, allowing for grouping of related metrics. Labels are passed when recording values to your metric as an instance of `DimensionLabels`, or as an array of `(String, String)`.

Example with a counter:

```swift
struct RouteLabels: MetricLabels {
var route: String = "*"
}

let counter = myProm.createCounter(forType: Int.self, named: "my_counter", helpText: "Just a counter", withLabelType: RouteLabels.self)
let counter = myProm.createCounter(forType: Int.self, named: "my_counter", helpText: "Just a counter")

let counter = prom.createCounter(forType: Int.self, named: "my_counter", helpText: "Just a counter", withLabelType: RouteLabels.self)
let counter = prom.createCounter(forType: Int.self, named: "my_counter", helpText: "Just a counter")

counter.inc(12, .init(route: "/"))
counter.inc(12, .init([("route", "/users")]))
// OR
counter.inc(12, [("route", "/users")])
```

# Exporting
Expand All @@ -125,16 +123,8 @@ Prometheus itself is designed to "pull" metrics from a destination. Following th
By default, this should be accessible on your main serving port, at the `/metrics` endpoint. An example in [Vapor](https://vapor.codes) 4 syntax looks like:

```swift
app.get("metrics") { req -> EventLoopFuture<String> in
let promise = req.eventLoop.makePromise(of: String.self)
DispatchQueue.global().async {
do {
try MetricsSystem.prometheus().collect(into: promise)
} catch {
promise.fail(error)
}
}
return promise.futureResult
app.get("metrics") { req async throws -> String in
return try await MetricsSystem.prometheus().collect()
}
```

Expand Down
8 changes: 4 additions & 4 deletions Sources/Prometheus/MetricTypes/Counter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import NIOConcurrencyHelpers
/// Prometheus Counter metric
///
/// See: https://prometheus.io/docs/concepts/metric_types/#counter
public class PromCounter<NumType: Numeric, Labels: MetricLabels>: PromMetric, PrometheusHandled {
public class PromCounter<NumType: Numeric>: PromMetric, PrometheusHandled {
/// Prometheus instance that created this Counter
internal weak var prometheus: PrometheusClient?

Expand All @@ -22,7 +22,7 @@ public class PromCounter<NumType: Numeric, Labels: MetricLabels>: PromMetric, Pr
private let initialValue: NumType

/// Storage of values that have labels attached
internal var metrics: [Labels: NumType] = [:]
internal var metrics: [DimensionLabels: NumType] = [:]

/// Lock used for thread safety
internal let lock: Lock
Expand Down Expand Up @@ -75,7 +75,7 @@ public class PromCounter<NumType: Numeric, Labels: MetricLabels>: PromMetric, Pr
/// - labels: Labels to attach to the value
///
@discardableResult
public func inc(_ amount: NumType = 1, _ labels: Labels? = nil) -> NumType {
public func inc(_ amount: NumType = 1, _ labels: DimensionLabels? = nil) -> NumType {
return self.lock.withLock {
if let labels = labels {
var val = self.metrics[labels] ?? self.initialValue
Expand All @@ -95,7 +95,7 @@ public class PromCounter<NumType: Numeric, Labels: MetricLabels>: PromMetric, Pr
/// - labels: Labels to get the value for
///
/// - Returns: The value of the Counter attached to the provided labels
public func get(_ labels: Labels? = nil) -> NumType {
public func get(_ labels: DimensionLabels? = nil) -> NumType {
return self.lock.withLock {
if let labels = labels {
return self.metrics[labels] ?? initialValue
Expand Down
22 changes: 11 additions & 11 deletions Sources/Prometheus/MetricTypes/Gauge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import NIOConcurrencyHelpers
/// Prometheus Gauge metric
///
/// See https://prometheus.io/docs/concepts/metric_types/#gauge
public class PromGauge<NumType: DoubleRepresentable, Labels: MetricLabels>: PromMetric, PrometheusHandled {
public class PromGauge<NumType: DoubleRepresentable>: PromMetric, PrometheusHandled {
/// Prometheus instance that created this Gauge
internal weak var prometheus: PrometheusClient?

Expand All @@ -24,7 +24,7 @@ public class PromGauge<NumType: DoubleRepresentable, Labels: MetricLabels>: Prom
private let initialValue: NumType

/// Storage of values that have labels attached
private var metrics: [Labels: NumType] = [:]
private var metrics: [DimensionLabels: NumType] = [:]

/// Lock used for thread safety
private let lock: Lock
Expand Down Expand Up @@ -78,7 +78,7 @@ public class PromGauge<NumType: DoubleRepresentable, Labels: MetricLabels>: Prom
///
/// - Returns: The value of the Gauge attached to the provided labels
@discardableResult
public func setToCurrentTime(_ labels: Labels? = nil) -> NumType {
public func setToCurrentTime(_ labels: DimensionLabels? = nil) -> NumType {
return self.set(.init(Date().timeIntervalSince1970), labels)
}

Expand All @@ -94,7 +94,7 @@ public class PromGauge<NumType: DoubleRepresentable, Labels: MetricLabels>: Prom
///
/// - Returns: The same type of function passed in for `body`, but wrapped to track progress.
@inlinable
public func trackInProgress<T>(_ labels: Labels? = nil, _ body: @escaping () throws -> T) -> (() throws -> T) {
public func trackInProgress<T>(_ labels: DimensionLabels? = nil, _ body: @escaping () throws -> T) -> (() throws -> T) {
return {
self.inc()
defer {
Expand All @@ -109,7 +109,7 @@ public class PromGauge<NumType: DoubleRepresentable, Labels: MetricLabels>: Prom
/// - labels: Labels to attach to the resulting value.
/// - body: Closure to run & record execution time of.
@inlinable
public func time<T>(_ labels: Labels? = nil, _ body: @escaping () throws -> T) rethrows -> T {
public func time<T>(_ labels: DimensionLabels? = nil, _ body: @escaping () throws -> T) rethrows -> T {
let start = DispatchTime.now().uptimeNanoseconds
defer {
let delta = Double(DispatchTime.now().uptimeNanoseconds - start)
Expand All @@ -127,7 +127,7 @@ public class PromGauge<NumType: DoubleRepresentable, Labels: MetricLabels>: Prom
///
/// - Returns: The value of the Gauge attached to the provided labels
@discardableResult
public func set(_ amount: NumType, _ labels: Labels? = nil) -> NumType {
public func set(_ amount: NumType, _ labels: DimensionLabels? = nil) -> NumType {
return self.lock.withLock {
if let labels = labels {
self.metrics[labels] = amount
Expand All @@ -147,7 +147,7 @@ public class PromGauge<NumType: DoubleRepresentable, Labels: MetricLabels>: Prom
///
/// - Returns: The value of the Gauge attached to the provided labels
@discardableResult
public func inc(_ amount: NumType, _ labels: Labels? = nil) -> NumType {
public func inc(_ amount: NumType, _ labels: DimensionLabels? = nil) -> NumType {
return self.lock.withLock {
if let labels = labels {
var val = self.metrics[labels] ?? self.initialValue
Expand All @@ -168,7 +168,7 @@ public class PromGauge<NumType: DoubleRepresentable, Labels: MetricLabels>: Prom
///
/// - Returns: The value of the Gauge attached to the provided labels
@discardableResult
public func inc(_ labels: Labels? = nil) -> NumType {
public func inc(_ labels: DimensionLabels? = nil) -> NumType {
return self.inc(1, labels)
}

Expand All @@ -180,7 +180,7 @@ public class PromGauge<NumType: DoubleRepresentable, Labels: MetricLabels>: Prom
///
/// - Returns: The value of the Gauge attached to the provided labels
@discardableResult
public func dec(_ amount: NumType, _ labels: Labels? = nil) -> NumType {
public func dec(_ amount: NumType, _ labels: DimensionLabels? = nil) -> NumType {
return self.lock.withLock {
if let labels = labels {
var val = self.metrics[labels] ?? self.initialValue
Expand All @@ -201,7 +201,7 @@ public class PromGauge<NumType: DoubleRepresentable, Labels: MetricLabels>: Prom
///
/// - Returns: The value of the Gauge attached to the provided labels
@discardableResult
public func dec(_ labels: Labels? = nil) -> NumType {
public func dec(_ labels: DimensionLabels? = nil) -> NumType {
return self.dec(1, labels)
}

Expand All @@ -211,7 +211,7 @@ public class PromGauge<NumType: DoubleRepresentable, Labels: MetricLabels>: Prom
/// - labels: Labels to get the value for
///
/// - Returns: The value of the Gauge attached to the provided labels
public func get(_ labels: Labels? = nil) -> NumType {
public func get(_ labels: DimensionLabels? = nil) -> NumType {
return self.lock.withLock {
if let labels = labels {
return self.metrics[labels] ?? initialValue
Expand Down
58 changes: 18 additions & 40 deletions Sources/Prometheus/MetricTypes/Histogram.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,24 +63,10 @@ public struct Buckets: ExpressibleByArrayLiteral {
}
}

/// Label type Histograms can use
public protocol HistogramLabels: MetricLabels {
/// Bucket
var le: String { get set }
}

extension HistogramLabels {
/// Creates empty HistogramLabels
init() {
self.init()
self.le = ""
}
}

/// Prometheus Histogram metric
///
/// See https://prometheus.io/docs/concepts/metric_types/#Histogram
public class PromHistogram<NumType: DoubleRepresentable, Labels: HistogramLabels>: PromMetric, PrometheusHandled {
public class PromHistogram<NumType: DoubleRepresentable>: PromMetric, PrometheusHandled {
/// Prometheus instance that created this Histogram
internal weak var prometheus: PrometheusClient?

Expand All @@ -93,19 +79,16 @@ public class PromHistogram<NumType: DoubleRepresentable, Labels: HistogramLabels
public let _type: PromMetricType = .histogram

/// Bucketed values for this Histogram
private var buckets: [PromCounter<NumType, EmptyLabels>] = []
private var buckets: [PromCounter<NumType>] = []

/// Buckets used by this Histogram
internal let upperBounds: [Double]

/// Labels for this Histogram
internal let labels: Labels

/// Sub Histograms for this Histogram
fileprivate var subHistograms: [Labels: PromHistogram<NumType, Labels>] = [:]
fileprivate var subHistograms: [DimensionLabels: PromHistogram<NumType>] = [:]

/// Total value of the Histogram
private let sum: PromCounter<NumType, EmptyLabels>
private let sum: PromCounter<NumType>

/// Lock used for thread safety
private let lock: Lock
Expand All @@ -115,19 +98,16 @@ public class PromHistogram<NumType: DoubleRepresentable, Labels: HistogramLabels
/// - Parameters:
/// - name: Name of the Histogram
/// - help: Help text of the Histogram
/// - labels: Labels for the Histogram
/// - buckets: Buckets to use for the Histogram
/// - p: Prometheus instance creating this Histogram
internal init(_ name: String, _ help: String? = nil, _ labels: Labels = Labels(), _ buckets: Buckets = .defaultBuckets, _ p: PrometheusClient) {
internal init(_ name: String, _ help: String? = nil, _ buckets: Buckets = .defaultBuckets, _ p: PrometheusClient) {
self.name = name
self.help = help

self.prometheus = p

self.sum = .init("\(self.name)_sum", nil, 0, p)

self.labels = labels

self.upperBounds = buckets.buckets

self.lock = Lock()
Expand All @@ -142,8 +122,8 @@ public class PromHistogram<NumType: DoubleRepresentable, Labels: HistogramLabels
/// - Returns:
/// Newline separated Prometheus formatted metric string
public func collect() -> String {
let (buckets, subHistograms, labels) = self.lock.withLock {
(self.buckets, self.subHistograms, self.labels)
let (buckets, subHistograms) = self.lock.withLock {
(self.buckets, self.subHistograms)
}

var output = [String]()
Expand All @@ -157,13 +137,13 @@ public class PromHistogram<NumType: DoubleRepresentable, Labels: HistogramLabels
collectBuckets(buckets: buckets,
upperBounds: self.upperBounds,
name: self.name,
labels: labels,
labels: nil,
sum: self.sum.get(),
into: &output)

subHistograms.forEach { subHistogram in
let (subHistogramBuckets, subHistogramLabels) = self.lock.withLock {
(subHistogram.value.buckets, subHistogram.value.labels)
(subHistogram.value.buckets, subHistogram.key)
}
collectBuckets(buckets: subHistogramBuckets,
upperBounds: subHistogram.value.upperBounds,
Expand All @@ -175,22 +155,20 @@ public class PromHistogram<NumType: DoubleRepresentable, Labels: HistogramLabels
return output.joined(separator: "\n")
}

private func collectBuckets(buckets: [PromCounter<NumType, EmptyLabels>],
private func collectBuckets(buckets: [PromCounter<NumType>],
upperBounds: [Double],
name: String,
labels: Labels,
labels: DimensionLabels?,
sum: NumType,
into output: inout [String]) {
var labels = labels
var acc: NumType = 0
for (i, bound) in upperBounds.enumerated() {
acc += buckets[i].get()
labels.le = bound.description
let labelsString = encodeLabels(labels)
let labelsString = encodeLabels(EncodableHistogramLabels(labels: labels, le: bound.description))
output.append("\(name)_bucket\(labelsString) \(acc)")
}

let labelsString = encodeLabels(labels, ["le"])
let labelsString = encodeLabels(EncodableHistogramLabels(labels: labels))
output.append("\(name)_count\(labelsString) \(acc)")

output.append("\(name)_sum\(labelsString) \(sum)")
Expand All @@ -201,8 +179,8 @@ public class PromHistogram<NumType: DoubleRepresentable, Labels: HistogramLabels
/// - Parameters:
/// - value: Value to observe
/// - labels: Labels to attach to the observed value
public func observe(_ value: NumType, _ labels: Labels? = nil) {
if let labels = labels, type(of: labels) != type(of: EmptyHistogramLabels()) {
public func observe(_ value: NumType, _ labels: DimensionLabels? = nil) {
if let labels = labels {
self.getOrCreateHistogram(with: labels)
.observe(value)
}
Expand All @@ -222,7 +200,7 @@ public class PromHistogram<NumType: DoubleRepresentable, Labels: HistogramLabels
/// - labels: Labels to attach to the resulting value.
/// - body: Closure to run & record.
@inlinable
public func time<T>(_ labels: Labels? = nil, _ body: @escaping () throws -> T) rethrows -> T {
public func time<T>(_ labels: DimensionLabels? = nil, _ body: @escaping () throws -> T) rethrows -> T {
let start = DispatchTime.now().uptimeNanoseconds
defer {
let delta = Double(DispatchTime.now().uptimeNanoseconds - start)
Expand All @@ -232,7 +210,7 @@ public class PromHistogram<NumType: DoubleRepresentable, Labels: HistogramLabels
}

/// Helper for histograms & labels
fileprivate func getOrCreateHistogram(with labels: Labels) -> PromHistogram<NumType, Labels> {
fileprivate func getOrCreateHistogram(with labels: DimensionLabels) -> PromHistogram<NumType> {
let subHistograms = lock.withLock { self.subHistograms }
if let histogram = subHistograms[labels] {
precondition(histogram.name == self.name,
Expand Down Expand Up @@ -262,7 +240,7 @@ public class PromHistogram<NumType: DoubleRepresentable, Labels: HistogramLabels
return histogram
}
guard let prometheus = prometheus else { fatalError("Lingering Histogram") }
let newHistogram = PromHistogram(self.name, self.help, labels, Buckets(self.upperBounds), prometheus)
let newHistogram = PromHistogram(self.name, self.help, Buckets(self.upperBounds), prometheus)
self.subHistograms[labels] = newHistogram
return newHistogram
}
Expand Down
22 changes: 0 additions & 22 deletions Sources/Prometheus/MetricTypes/PromMetric.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,25 +50,3 @@ internal protocol PrometheusHandled {
/// Prometheus client handling this metric
var prometheus: PrometheusClient? { get }
}

/// Base MetricLabels protocol
///
/// MetricLabels are used to enrich & specify metrics.
///
/// struct Labels: MetricLabels {
/// let status: String = "unknown"
/// }
/// let counter = myProm.createCounter(...)
/// counter.inc(12, labels: Labels(status: "failure")
/// counter.inc(1, labels: Labels(status: "success")
/// Will result in the following Prometheus output:
///
/// # TYPE my_counter counter
/// my_counter 0
/// my_counter{status="unknown"} 0
/// my_counter{status="failure"} 12
/// my_counter{status="success"} 1
public protocol MetricLabels: Encodable, Hashable {
/// Create empty labels
init()
}
Loading

0 comments on commit 688e1fd

Please sign in to comment.