diff --git a/Sources/Prometheus/MetricTypes/PromMetric.swift b/Sources/Prometheus/MetricTypes/PromMetric.swift index 602786b..690ce84 100644 --- a/Sources/Prometheus/MetricTypes/PromMetric.swift +++ b/Sources/Prometheus/MetricTypes/PromMetric.swift @@ -13,6 +13,9 @@ public enum PromMetricType: String { } public enum Prometheus { + /// Default capacity of Summaries + public static let defaultSummaryCapacity = 500 + /// Default quantiles used by Summaries public static let defaultQuantiles = [0.01, 0.05, 0.5, 0.9, 0.95, 0.99, 0.999] } diff --git a/Sources/Prometheus/MetricTypes/Summary.swift b/Sources/Prometheus/MetricTypes/Summary.swift index c9bc48b..498d07e 100644 --- a/Sources/Prometheus/MetricTypes/Summary.swift +++ b/Sources/Prometheus/MetricTypes/Summary.swift @@ -1,4 +1,5 @@ import NIOConcurrencyHelpers +import NIO import struct CoreMetrics.TimeUnit import Dispatch @@ -43,8 +44,11 @@ public class PromSummary: P private let count: PromCounter /// Values in this Summary - private var values: [NumType] = [] - + private var values: CircularBuffer + + /// Number of values to keep for calculating quantiles + internal let capacity: Int + /// Quantiles used by this Summary internal let quantiles: [Double] @@ -60,9 +64,10 @@ public class PromSummary: P /// - name: Name of the Summary /// - help: Help text of the Summary /// - labels: Labels for the Summary + /// - capacity: Number of values to keep for calculating quantiles /// - quantiles: Quantiles to use for the Summary /// - p: Prometheus instance creating this Summary - internal init(_ name: String, _ help: String? = nil, _ labels: Labels = Labels(), _ quantiles: [Double] = Prometheus.defaultQuantiles, _ p: PrometheusClient) { + internal init(_ name: String, _ help: String? = nil, _ labels: Labels = Labels(), _ capacity: Int = Prometheus.defaultSummaryCapacity, _ quantiles: [Double] = Prometheus.defaultQuantiles, _ p: PrometheusClient) { self.name = name self.help = help @@ -74,6 +79,10 @@ public class PromSummary: P self.count = .init("\(self.name)_count", nil, 0, p) + self.values = CircularBuffer(initialCapacity: capacity) + + self.capacity = capacity + self.quantiles = quantiles self.labels = labels @@ -159,6 +168,9 @@ public class PromSummary: P } self.count.inc(1) self.sum.inc(value) + if self.values.count == self.capacity { + _ = self.values.popFirst() + } self.values.append(value) } } @@ -190,7 +202,7 @@ extension PrometheusClient { if let summary = summaries.first { return summary } else { - let newSummary = PromSummary(summary.name, summary.help, labels, summary.quantiles, self) + let newSummary = PromSummary(summary.name, summary.help, labels, summary.capacity, summary.quantiles, self) summary.subSummaries.append(newSummary) return newSummary } diff --git a/Sources/Prometheus/Prometheus.swift b/Sources/Prometheus/Prometheus.swift index 076f379..cb5ceb3 100644 --- a/Sources/Prometheus/Prometheus.swift +++ b/Sources/Prometheus/Prometheus.swift @@ -240,6 +240,7 @@ public class PrometheusClient { /// - type: The type the summary will observe /// - name: Name of the summary /// - helpText: Help text for the summary. Usually a short description + /// - capacity: Number of observations to keep for calculating quantiles /// - quantiles: Quantiles to calculate /// - labels: Labels to give this summary. Can be left out to default to no labels /// @@ -248,6 +249,7 @@ public class PrometheusClient { forType type: T.Type, named name: String, helpText: String? = nil, + capacity: Int = Prometheus.defaultSummaryCapacity, quantiles: [Double] = Prometheus.defaultQuantiles, labels: U.Type) -> PromSummary { @@ -255,8 +257,7 @@ public class PrometheusClient { if let cachedSummary: PromSummary = self._getMetricInstance(with: name, andType: .summary) { return cachedSummary } - - let summary = PromSummary(name, helpText, U(), quantiles, self) + let summary = PromSummary(name, helpText, U(), capacity, quantiles, self) let oldInstrument = self.metrics.updateValue(summary, forKey: name) precondition(oldInstrument == nil, "Label \(oldInstrument!.name) is already associated with a \(oldInstrument!._type).") return summary diff --git a/Tests/SwiftPrometheusTests/SummaryTests.swift b/Tests/SwiftPrometheusTests/SummaryTests.swift index d835791..d6a4935 100644 --- a/Tests/SwiftPrometheusTests/SummaryTests.swift +++ b/Tests/SwiftPrometheusTests/SummaryTests.swift @@ -144,4 +144,21 @@ final class SummaryTests: XCTestCase { my_summary_sum{myValue=\"labels\"} 123.0 """) } + + func testStandaloneSummaryWithCustomCapacity() { + let capacity = 10 + let summary = prom.createSummary(forType: Double.self, named: "my_summary", helpText: "Summary for testing", capacity: capacity, quantiles: [0.5, 0.99], labels: BaseSummaryLabels.self) + + for i in 0 ..< capacity { summary.observe(Double(i * 1_000)) } + for i in 0 ..< capacity { summary.observe(Double(i)) } + + XCTAssertEqual(summary.collect(), """ + # HELP my_summary Summary for testing + # TYPE my_summary summary + my_summary{quantile="0.5", myValue="*"} 4.5 + my_summary{quantile="0.99", myValue="*"} 9.0 + my_summary_count{myValue="*"} 20.0 + my_summary_sum{myValue="*"} 45045.0 + """) + } }