Skip to content

Commit 54a4c97

Browse files
Merge pull request #3738 from SwiftPackageIndex/prometheus-dependency
Prometheus dependency
2 parents 12177f4 + 342e951 commit 54a4c97

File tree

6 files changed

+90
-40
lines changed

6 files changed

+90
-40
lines changed

Sources/App/Core/AppMetrics.swift

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,6 @@ import Vapor
2121

2222
enum AppMetrics {
2323

24-
static let initialized = Mutex(false)
25-
26-
static func bootstrap() {
27-
// prevent tests from boostrapping multiple times
28-
guard !initialized.withLock({ $0 }) else { return }
29-
initialized.withLock {
30-
let client = PrometheusClient()
31-
MetricsSystem.bootstrap(PrometheusMetricsFactory(client: client))
32-
$0 = true
33-
}
34-
}
35-
36-
// metrics
37-
3824
static var analyzeCandidatesCount: PromGauge<Int>? {
3925
gauge("spi_analyze_candidates_count")
4026
}
@@ -155,13 +141,13 @@ enum AppMetrics {
155141
extension AppMetrics {
156142

157143
static func counter<V: Numeric>(_ name: String) -> PromCounter<V>? {
158-
try? MetricsSystem.prometheus()
159-
.createCounter(forType: V.self, named: name)
144+
@Dependency(\.metricsSystem.prometheus) var prometheus
145+
return try? prometheus().createCounter(forType: V.self, named: name)
160146
}
161147

162148
static func gauge<V: DoubleRepresentable>(_ name: String) -> PromGauge<V>? {
163-
try? MetricsSystem.prometheus()
164-
.createGauge(forType: V.self, named: name)
149+
@Dependency(\.metricsSystem.prometheus) var prometheus
150+
return try? prometheus().createGauge(forType: V.self, named: name)
165151
}
166152

167153
}
@@ -176,14 +162,15 @@ extension AppMetrics {
176162
static func push(client: Client, jobName: String) async throws {
177163
@Dependency(\.environment) var environment
178164
@Dependency(\.logger) var logger
165+
@Dependency(\.metricsSystem.prometheus) var prometheus
179166

180167
guard let pushGatewayUrl = environment.metricsPushGatewayUrl() else {
181168
throw AppError.envVariableNotSet("METRICS_PUSHGATEWAY_URL")
182169
}
183170
let url = URI(string: "\(pushGatewayUrl)/metrics/job/\(jobName)")
184171

185172
do {
186-
let metrics: String = try await MetricsSystem.prometheus().collect()
173+
let metrics: String = try await prometheus().collect()
187174
_ = try await client.post(url) { req in
188175
// append "\n" to avoid
189176
// text format parsing error in line 4: unexpected end of input stream
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright Dave Verwer, Sven A. Schmidt, and other contributors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import Dependencies
16+
import Metrics
17+
import Synchronization
18+
@preconcurrency import Prometheus
19+
20+
21+
struct MetricsSystemClient {
22+
var prometheus: @Sendable () throws -> PrometheusClient
23+
}
24+
25+
26+
extension MetricsSystemClient {
27+
private static let initialized = Mutex(false)
28+
29+
func bootstrap() {
30+
guard !Self.initialized.withLock({ $0 }) else { return }
31+
Self.initialized.withLock {
32+
let client = PrometheusClient()
33+
MetricsSystem.bootstrap(PrometheusMetricsFactory(client: client))
34+
$0 = true
35+
}
36+
}
37+
}
38+
39+
40+
extension MetricsSystemClient: DependencyKey {
41+
static var liveValue: Self {
42+
.init(prometheus: { try MetricsSystem.prometheus() })
43+
}
44+
}
45+
46+
47+
extension MetricsSystemClient: TestDependencyKey {
48+
static var testValue: Self {
49+
.init(prometheus: { unimplemented("testValue"); return .init() })
50+
}
51+
}
52+
53+
54+
extension DependencyValues {
55+
var metricsSystem: MetricsSystemClient {
56+
get { self[MetricsSystemClient.self] }
57+
set { self[MetricsSystemClient.self] = newValue }
58+
}
59+
}
60+
61+
62+
#if DEBUG
63+
extension MetricsSystemClient {
64+
static var mock: Self {
65+
let prometheus = PrometheusClient()
66+
return .init(prometheus: { prometheus })
67+
}
68+
}
69+
#endif

Sources/App/configure.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,8 @@ public func configure(_ app: Application, databasePort: Int? = nil) async throws
365365
try routes(app)
366366

367367
// bootstrap app metrics
368-
AppMetrics.bootstrap()
368+
@Dependency(\.metricsSystem) var metricsSystem
369+
metricsSystem.bootstrap()
369370

370371
return host
371372
}

Sources/App/routes.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,8 @@ func routes(_ app: Application) throws {
211211

212212
do { // Metrics
213213
app.get("metrics") { req -> String in
214-
try await MetricsSystem.prometheus().collect()
214+
@Dependency(\.metricsSystem.prometheus) var prometheus
215+
return try await prometheus().collect()
215216
}
216217
}
217218
}

Tests/AppTests/AllTests.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,16 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
import Testing
15+
@testable import App
16+
1617
import Dependencies
18+
import Testing
1719

1820

19-
@Suite(.dependency(\.date.now, .t0)) struct AllTests { }
21+
@Suite(
22+
.dependency(\.date.now, .t0),
23+
.dependency(\.metricsSystem, .mock)
24+
) struct AllTests { }
2025

2126

2227
extension AllTests {

Tests/AppTests/MetricsTests.swift

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -61,18 +61,6 @@ extension AllTests.MetricsTests {
6161
@Test func versions_added() async throws {
6262
try await withApp { app in
6363
// setup
64-
let initialAddedBranch = try #require(
65-
AppMetrics.analyzeVersionsAddedCount?.get(.versionLabels(kind: .branch))
66-
)
67-
let initialAddedTag = try #require(
68-
AppMetrics.analyzeVersionsAddedCount?.get(.versionLabels(kind: .tag))
69-
)
70-
let initialDeletedBranch = try #require(
71-
AppMetrics.analyzeVersionsDeletedCount?.get(.versionLabels(kind: .branch))
72-
)
73-
let initialDeletedTag = try #require(
74-
AppMetrics.analyzeVersionsDeletedCount?.get(.versionLabels(kind: .tag))
75-
)
7664
let pkg = try await savePackage(on: app.db, "1")
7765
let new = [
7866
try Version(package: pkg, reference: .branch("main")),
@@ -91,16 +79,16 @@ extension AllTests.MetricsTests {
9179

9280
// validation
9381
#expect(
94-
AppMetrics.analyzeVersionsAddedCount?.get(.versionLabels(kind: .branch)) == initialAddedBranch + 1
82+
AppMetrics.analyzeVersionsAddedCount?.get(.versionLabels(kind: .branch)) == 1
9583
)
9684
#expect(
97-
AppMetrics.analyzeVersionsAddedCount?.get(.versionLabels(kind: .tag)) == initialAddedTag + 2
85+
AppMetrics.analyzeVersionsAddedCount?.get(.versionLabels(kind: .tag)) == 2
9886
)
9987
#expect(
100-
AppMetrics.analyzeVersionsDeletedCount?.get(.versionLabels(kind: .branch)) == initialDeletedBranch + 1
88+
AppMetrics.analyzeVersionsDeletedCount?.get(.versionLabels(kind: .branch)) == 1
10189
)
10290
#expect(
103-
AppMetrics.analyzeVersionsDeletedCount?.get(.versionLabels(kind: .tag)) == initialDeletedTag + 1
91+
AppMetrics.analyzeVersionsDeletedCount?.get(.versionLabels(kind: .tag)) == 1
10492
)
10593
}
10694
}
@@ -164,7 +152,6 @@ extension AllTests.MetricsTests {
164152

165153
// validation
166154
#expect((AppMetrics.buildTriggerDurationSeconds?.get()) ?? 0 > 0)
167-
print(AppMetrics.buildTriggerDurationSeconds!.get())
168155
}
169156
}
170157
}

0 commit comments

Comments
 (0)