Skip to content

Commit 46b8b95

Browse files
authored
New Version 2.0 API (#92)
1 parent 6e74b77 commit 46b8b95

35 files changed

+2342
-2637
lines changed

.swiftformat

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# file options
2+
3+
--swiftversion 5.7
4+
--exclude .build
5+
6+
# format options
7+
8+
--self insert
9+
--patternlet inline
10+
--ranges nospace
11+
--stripunusedargs unnamed-only
12+
--ifdef no-indent
13+
--extensionacl on-declarations
14+
--disable typeSugar # https://github.com/nicklockwood/SwiftFormat/issues/636
15+
--disable andOperator
16+
--disable wrapMultilineStatementBraces
17+
--disable enumNamespaces
18+
--disable redundantExtensionACL
19+
--disable redundantReturn
20+
--disable preferKeyPath
21+
--disable sortedSwitchCases
22+
--disable numberFormatting
23+
24+
# rules

Package.swift

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,49 @@
1-
// swift-tools-version:5.2
1+
// swift-tools-version:5.7
2+
//===----------------------------------------------------------------------===//
3+
//
4+
// This source file is part of the SwiftPrometheus open source project
5+
//
6+
// Copyright (c) 2018-2023 SwiftPrometheus project authors
7+
// Licensed under Apache License v2.0
8+
//
9+
// See LICENSE.txt for license information
10+
// See CONTRIBUTORS.txt for the list of SwiftPrometheus project authors
11+
//
12+
// SPDX-License-Identifier: Apache-2.0
13+
//
14+
//===----------------------------------------------------------------------===//
215

316
import PackageDescription
417

518
let package = Package(
6-
name: "SwiftPrometheus",
19+
name: "swift-prometheus",
20+
platforms: [.macOS(.v13), .iOS(.v16), .watchOS(.v9), .tvOS(.v16)],
721
products: [
822
.library(
9-
name: "SwiftPrometheus",
10-
targets: ["Prometheus"])
23+
name: "Prometheus",
24+
targets: ["Prometheus"]
25+
),
1126
],
1227
dependencies: [
13-
.package(url: "https://github.com/apple/swift-metrics.git", from: "2.2.0"),
14-
.package(url: "https://github.com/apple/swift-nio.git", from: "2.0.0"),
28+
.package(url: "https://github.com/apple/swift-atomics.git", from: "1.0.2"),
29+
.package(url: "https://github.com/apple/swift-metrics.git", from: "2.4.1"),
30+
31+
// ~~~ SwiftPM Plugins ~~~
32+
.package(url: "https://github.com/apple/swift-docc-plugin.git", from: "1.3.0"),
1533
],
1634
targets: [
1735
.target(
1836
name: "Prometheus",
1937
dependencies: [
38+
.product(name: "Atomics", package: "swift-atomics"),
2039
.product(name: "CoreMetrics", package: "swift-metrics"),
21-
.product(name: "NIOConcurrencyHelpers", package: "swift-nio"),
22-
.product(name: "NIO", package: "swift-nio"),
23-
]),
24-
.target(
25-
name: "PrometheusExample",
26-
dependencies: [
27-
.target(name: "Prometheus"),
28-
.product(name: "Metrics", package: "swift-metrics"),
29-
]),
40+
]
41+
),
3042
.testTarget(
31-
name: "SwiftPrometheusTests",
32-
dependencies: [.target(name: "Prometheus")]),
43+
name: "PrometheusTests",
44+
dependencies: [
45+
"Prometheus",
46+
]
47+
),
3348
]
3449
)

Sources/Prometheus/Counter.swift

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftPrometheus open source project
4+
//
5+
// Copyright (c) 2018-2023 SwiftPrometheus project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftPrometheus project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import Atomics
16+
import CoreMetrics
17+
18+
/// A counter is a cumulative metric that represents a single monotonically increasing counter whose value
19+
/// can only increase or be ``reset()`` to zero on restart.
20+
///
21+
/// For example, you can use a counter to represent the number of requests served, tasks completed, or errors.
22+
///
23+
/// Do not use a counter to expose a value that can decrease. For example, do not use a counter for the
24+
/// number of currently running processes; instead use a ``Gauge``.
25+
public final class Counter: Sendable {
26+
private let intAtomic = ManagedAtomic(Int64(0))
27+
private let floatAtomic = ManagedAtomic(Double(0).bitPattern)
28+
29+
let name: String
30+
let labels: [(String, String)]
31+
private let prerenderedExport: [UInt8]
32+
33+
init(name: String, labels: [(String, String)]) {
34+
self.name = name
35+
self.labels = labels
36+
37+
var prerendered = [UInt8]()
38+
// 64 bytes is a good tradeoff to prevent reallocs lots of reallocs when appending names
39+
// and memory footprint.
40+
prerendered.reserveCapacity(64)
41+
prerendered.append(contentsOf: name.utf8)
42+
if let prerenderedLabels = Self.prerenderLabels(labels) {
43+
prerendered.append(UInt8(ascii: "{"))
44+
prerendered.append(contentsOf: prerenderedLabels)
45+
prerendered.append(contentsOf: #"} "#.utf8)
46+
} else {
47+
prerendered.append(UInt8(ascii: " "))
48+
}
49+
50+
self.prerenderedExport = prerendered
51+
}
52+
53+
public func increment() {
54+
self.increment(by: Int64(1))
55+
}
56+
57+
public func increment(by amount: Int64) {
58+
precondition(amount >= 0)
59+
self.intAtomic.wrappingIncrement(by: amount, ordering: .relaxed)
60+
}
61+
62+
public func increment(by amount: Double) {
63+
precondition(amount >= 0)
64+
// We busy loop here until we can update the atomic successfully.
65+
// Using relaxed ordering here is sufficient, since the as-if rules guarantess that
66+
// the following operations are executed in the order presented here. Every statement
67+
// depends on the execution before.
68+
while true {
69+
let bits = self.floatAtomic.load(ordering: .relaxed)
70+
let value = Double(bitPattern: bits) + amount
71+
let (exchanged, _) = self.floatAtomic.compareExchange(expected: bits, desired: value.bitPattern, ordering: .relaxed)
72+
if exchanged {
73+
break
74+
}
75+
}
76+
}
77+
78+
public func reset() {
79+
self.intAtomic.store(0, ordering: .relaxed)
80+
self.floatAtomic.store(Double.zero.bitPattern, ordering: .relaxed)
81+
}
82+
}
83+
84+
extension Counter: CoreMetrics.CounterHandler {}
85+
extension Counter: CoreMetrics.FloatingPointCounterHandler {}
86+
87+
extension Counter: PrometheusMetric {
88+
func emit(into buffer: inout [UInt8]) {
89+
buffer.append(contentsOf: self.prerenderedExport)
90+
let doubleValue = Double(bitPattern: self.floatAtomic.load(ordering: .relaxed))
91+
let intValue = self.intAtomic.load(ordering: .relaxed)
92+
if doubleValue == .zero {
93+
buffer.append(contentsOf: "\(intValue)".utf8)
94+
} else {
95+
buffer.append(contentsOf: "\(doubleValue + Double(intValue))".utf8)
96+
}
97+
buffer.append(UInt8(ascii: "\n"))
98+
}
99+
}

Sources/Prometheus/Docs.docc/index.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# ``Prometheus``
2+
3+
A prometheus client library for Swift.
4+
5+
## Overview
6+
7+
``Prometheus`` supports creating ``Counter``s, ``Gauge``s and ``Histogram``s and exporting their
8+
values in the Prometheus export format.
9+
10+
## Topics
11+
12+
### Collectors
13+
14+
- ``Counter``
15+
- ``Gauge``
16+
- ``Histogram``
17+
- ``Bucketable``
18+
- ``DurationHistogram``
19+
- ``ValueHistogram``

Sources/Prometheus/Gauge.swift

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftPrometheus open source project
4+
//
5+
// Copyright (c) 2018-2023 SwiftPrometheus project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftPrometheus project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import Atomics
16+
import CoreMetrics
17+
18+
/// A gauge is a metric that represents a single numerical value that can arbitrarily go up and down.
19+
///
20+
/// Gauges are typically used for measured values like temperatures or current memory usage, but
21+
/// also "counts" that can go up and down, like the number of concurrent requests.
22+
public final class Gauge: Sendable {
23+
let atomic = ManagedAtomic(Double.zero.bitPattern)
24+
25+
let name: String
26+
let labels: [(String, String)]
27+
let prerenderedExport: [UInt8]
28+
29+
init(name: String, labels: [(String, String)]) {
30+
self.name = name
31+
self.labels = labels
32+
33+
var prerendered = [UInt8]()
34+
// 64 bytes is a good tradeoff to prevent reallocs lots of reallocs when appending names
35+
// and memory footprint.
36+
prerendered.reserveCapacity(64)
37+
prerendered.append(contentsOf: name.utf8)
38+
if let prerenderedLabels = Self.prerenderLabels(labels) {
39+
prerendered.append(UInt8(ascii: "{"))
40+
prerendered.append(contentsOf: prerenderedLabels)
41+
prerendered.append(contentsOf: #"} "#.utf8)
42+
} else {
43+
prerendered.append(UInt8(ascii: " "))
44+
}
45+
46+
self.prerenderedExport = prerendered
47+
}
48+
49+
public func set(to value: Double) {
50+
self.atomic.store(value.bitPattern, ordering: .relaxed)
51+
}
52+
53+
public func increment(by amount: Double = 1.0) {
54+
// We busy loop here until we can update the atomic successfully.
55+
// Using relaxed ordering here is sufficient, since the as-if rules guarantess that
56+
// the following operations are executed in the order presented here. Every statement
57+
// depends on the execution before.
58+
while true {
59+
let bits = self.atomic.load(ordering: .relaxed)
60+
let value = Double(bitPattern: bits) + amount
61+
let (exchanged, _) = self.atomic.compareExchange(expected: bits, desired: value.bitPattern, ordering: .relaxed)
62+
if exchanged {
63+
break
64+
}
65+
}
66+
}
67+
68+
public func decrement(by amount: Double = 1.0) {
69+
self.increment(by: -amount)
70+
}
71+
}
72+
73+
extension Gauge: CoreMetrics.RecorderHandler {
74+
public func record(_ value: Int64) {
75+
self.record(Double(value))
76+
}
77+
78+
public func record(_ value: Double) {
79+
self.set(to: value)
80+
}
81+
}
82+
83+
extension Gauge: PrometheusMetric {
84+
func emit(into buffer: inout [UInt8]) {
85+
let value = Double(bitPattern: self.atomic.load(ordering: .relaxed))
86+
87+
buffer.append(contentsOf: self.prerenderedExport)
88+
buffer.append(contentsOf: "\(value)".utf8)
89+
buffer.append(UInt8(ascii: "\n"))
90+
}
91+
}

0 commit comments

Comments
 (0)