From 507c56ef24d58617843f0c25cf4fa122d8b63b19 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Mon, 30 Dec 2024 17:11:58 +0000 Subject: [PATCH] Add test to ensure http.server.active_requests metric is set (#645) --- Tests/HummingbirdTests/MetricsTests.swift | 78 +++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/Tests/HummingbirdTests/MetricsTests.swift b/Tests/HummingbirdTests/MetricsTests.swift index f2a0af74..5888a24b 100644 --- a/Tests/HummingbirdTests/MetricsTests.swift +++ b/Tests/HummingbirdTests/MetricsTests.swift @@ -21,6 +21,7 @@ import XCTest final class TestMetrics: MetricsFactory { private let lock = NIOLock() let counters = NIOLockedValueBox([String: CounterHandler]()) + let meters = NIOLockedValueBox([String: MeterHandler]()) let recorders = NIOLockedValueBox([String: RecorderHandler]()) let timers = NIOLockedValueBox([String: TimerHandler]()) @@ -30,6 +31,12 @@ final class TestMetrics: MetricsFactory { } } + public func makeMeter(label: String, dimensions: [(String, String)]) -> MeterHandler { + self.meters.withLockedValue { counters in + self.make(label: label, dimensions: dimensions, registry: &counters, maker: TestMeter.init) + } + } + public func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> RecorderHandler { let maker = { (label: String, dimensions: [(String, String)]) -> RecorderHandler in TestRecorder(label: label, dimensions: dimensions, aggregate: aggregate) @@ -64,6 +71,14 @@ final class TestMetrics: MetricsFactory { } } + func destroyMeter(_ handler: MeterHandler) { + if let testMeter = handler as? TestMeter { + _ = self.counters.withLockedValue { counters in + counters.removeValue(forKey: testMeter.label) + } + } + } + func destroyRecorder(_ handler: RecorderHandler) { if let testRecorder = handler as? TestRecorder { _ = self.recorders.withLockedValue { recorders in @@ -112,6 +127,52 @@ internal final class TestCounter: CounterHandler, Equatable { } } +internal final class TestMeter: MeterHandler, Equatable { + let id: String + let label: String + let dimensions: [(String, String)] + let values = NIOLockedValueBox([(Date, Double)]()) + + init(label: String, dimensions: [(String, String)]) { + self.id = NSUUID().uuidString + self.label = label + self.dimensions = dimensions + } + + func set(_ value: Int64) { + self.set(Double(value)) + } + + func set(_ value: Double) { + self.values.withLockedValue { values in + values.append((Date(), value)) + } + print("adding \(value) to \(self.label)") + } + + func increment(by: Double) { + self.values.withLockedValue { values in + let value = values.last?.1 ?? 0.0 + values.append((Date(), value + by)) + } + print("incrementing \(by) to \(self.label)") + + } + + func decrement(by: Double) { + self.values.withLockedValue { values in + let value = values.last?.1 ?? 0.0 + values.append((Date(), value - by)) + } + print("decrementing \(by) to \(self.label)") + + } + + static func == (lhs: TestMeter, rhs: TestMeter) -> Bool { + lhs.id == rhs.id + } +} + internal final class TestRecorder: RecorderHandler, Equatable { let id: String let label: String @@ -314,4 +375,21 @@ final class MetricsTests: XCTestCase { let timer = try XCTUnwrap(Self.testMetrics.timers.withLockedValue { $0 }["http.server.request.duration"] as? TestTimer) XCTAssertGreaterThan(timer.values.withLockedValue { $0 }[0].1, 5_000_000) } + + func testActiveRequestsMetric() async throws { + let router = Router() + router.middlewares.add(MetricsMiddleware()) + router.get("/hello") { _, _ -> Response in + Response(status: .ok) + } + let app = Application(responder: router.buildResponder()) + try await app.test(.router) { client in + try await client.execute(uri: "/hello", method: .get) { _ in } + } + + let meter = try XCTUnwrap(Self.testMetrics.meters.withLockedValue { $0 }["http.server.active_requests"] as? TestMeter) + let values = meter.values.withLockedValue { $0 }.map { $0.1 } + let maxValue = values.max() ?? 0.0 + XCTAssertGreaterThan(maxValue, 0.0) + } }