@@ -64,6 +64,24 @@ private class MetricsHistogram: RecorderHandler {
6464 }
6565}
6666
67+ class MetricsHistogramTimer : TimerHandler {
68+ let histogram : PromHistogram < Int64 , DimensionHistogramLabels >
69+ let labels : DimensionHistogramLabels ?
70+
71+ init ( histogram: PromHistogram < Int64 , DimensionHistogramLabels > , dimensions: [ ( String , String ) ] ) {
72+ self . histogram = histogram
73+ if !dimensions. isEmpty {
74+ self . labels = DimensionHistogramLabels ( dimensions)
75+ } else {
76+ self . labels = nil
77+ }
78+ }
79+
80+ func recordNanoseconds( _ duration: Int64 ) {
81+ return histogram. observe ( duration, labels)
82+ }
83+ }
84+
6785private class MetricsSummary : TimerHandler {
6886 let summary : PromSummary < Int64 , DimensionSummaryLabels >
6987 let labels : DimensionSummaryLabels ?
@@ -82,7 +100,7 @@ private class MetricsSummary: TimerHandler {
82100 }
83101
84102 func recordNanoseconds( _ duration: Int64 ) {
85- summary. observe ( duration, labels)
103+ return summary. observe ( duration, labels)
86104 }
87105}
88106
@@ -94,7 +112,7 @@ private class MetricsSummary: TimerHandler {
94112/// let sanitizer: LabelSanitizer = ...
95113/// let prometheusLabel = sanitizer.sanitize(nonPrometheusLabel)
96114///
97- /// By default `PrometheusLabelSanitizer` is used by `PrometheusClient `
115+ /// By default `PrometheusLabelSanitizer` is used by `PrometheusMetricsFactory `
98116public protocol LabelSanitizer {
99117 /// Sanitize the passed in label to a Prometheus accepted value.
100118 ///
@@ -121,73 +139,119 @@ public struct PrometheusLabelSanitizer: LabelSanitizer {
121139 }
122140}
123141
124- extension PrometheusClient : MetricsFactory {
142+ /// A bridge between PrometheusClient and swift-metrics. Prometheus types don't map perfectly on swift-metrics API,
143+ /// which makes bridge implementation non trivial. This class defines how exactly swift-metrics types should be backed
144+ /// with Prometheus types, e.g. how to sanitize labels, what buckets/quantiles to use for recorder/timer, etc.
145+ public struct PrometheusMetricsFactory : MetricsFactory {
146+
147+ /// Prometheus client to bridge swift-metrics API to.
148+ private let client : PrometheusClient
149+
150+ /// Bridge configuration.
151+ private let configuration : Configuration
152+
153+ public init ( client: PrometheusClient ,
154+ configuration: Configuration = Configuration ( ) ) {
155+ self . client = client
156+ self . configuration = configuration
157+ }
158+
125159 public func destroyCounter( _ handler: CounterHandler ) {
126160 guard let handler = handler as? MetricsCounter else { return }
127- self . removeMetric ( handler. counter)
161+ client . removeMetric ( handler. counter)
128162 }
129163
130164 public func destroyRecorder( _ handler: RecorderHandler ) {
131165 if let handler = handler as? MetricsGauge {
132- self . removeMetric ( handler. gauge)
166+ client . removeMetric ( handler. gauge)
133167 }
134168 if let handler = handler as? MetricsHistogram {
135- self . removeMetric ( handler. histogram)
169+ client . removeMetric ( handler. histogram)
136170 }
137171 }
138-
172+
139173 public func destroyTimer( _ handler: TimerHandler ) {
140- guard let handler = handler as? MetricsSummary else { return }
141- self . removeMetric ( handler. summary)
174+ switch self . configuration. timerImplementation. _wrapped {
175+ case . summary:
176+ guard let handler = handler as? MetricsSummary else { return }
177+ client. removeMetric ( handler. summary)
178+ case . histogram:
179+ guard let handler = handler as? MetricsHistogramTimer else { return }
180+ client. removeMetric ( handler. histogram)
181+ }
142182 }
143183
144184 public func makeCounter( label: String , dimensions: [ ( String , String ) ] ) -> CounterHandler {
145- let label = self . sanitizer . sanitize ( label)
185+ let label = configuration . labelSanitizer . sanitize ( label)
146186 let createHandler = { ( counter: PromCounter ) -> CounterHandler in
147187 return MetricsCounter ( counter: counter, dimensions: dimensions)
148188 }
149- if let counter: PromCounter < Int64 , DimensionLabels > = self . getMetricInstance ( with: label, andType: . counter) {
189+ if let counter: PromCounter < Int64 , DimensionLabels > = client . getMetricInstance ( with: label, andType: . counter) {
150190 return createHandler ( counter)
151191 }
152- return createHandler ( self . createCounter ( forType: Int64 . self, named: label, withLabelType: DimensionLabels . self) )
192+ return createHandler ( client . createCounter ( forType: Int64 . self, named: label, withLabelType: DimensionLabels . self) )
153193 }
154194
155195 public func makeRecorder( label: String , dimensions: [ ( String , String ) ] , aggregate: Bool ) -> RecorderHandler {
156- let label = self . sanitizer . sanitize ( label)
196+ let label = configuration . labelSanitizer . sanitize ( label)
157197 return aggregate ? makeHistogram ( label: label, dimensions: dimensions) : makeGauge ( label: label, dimensions: dimensions)
158198 }
159199
160200 private func makeGauge( label: String , dimensions: [ ( String , String ) ] ) -> RecorderHandler {
161- let label = self . sanitizer . sanitize ( label)
201+ let label = configuration . labelSanitizer . sanitize ( label)
162202 let createHandler = { ( gauge: PromGauge ) -> RecorderHandler in
163203 return MetricsGauge ( gauge: gauge, dimensions: dimensions)
164204 }
165- if let gauge: PromGauge < Double , DimensionLabels > = self . getMetricInstance ( with: label, andType: . gauge) {
205+ if let gauge: PromGauge < Double , DimensionLabels > = client . getMetricInstance ( with: label, andType: . gauge) {
166206 return createHandler ( gauge)
167207 }
168- return createHandler ( createGauge ( forType: Double . self, named: label, withLabelType: DimensionLabels . self) )
208+ return createHandler ( client . createGauge ( forType: Double . self, named: label, withLabelType: DimensionLabels . self) )
169209 }
170210
171211 private func makeHistogram( label: String , dimensions: [ ( String , String ) ] ) -> RecorderHandler {
172- let label = self . sanitizer . sanitize ( label)
212+ let label = configuration . labelSanitizer . sanitize ( label)
173213 let createHandler = { ( histogram: PromHistogram ) -> RecorderHandler in
174214 return MetricsHistogram ( histogram: histogram, dimensions: dimensions)
175215 }
176- if let histogram: PromHistogram < Double , DimensionHistogramLabels > = self . getMetricInstance ( with: label, andType: . histogram) {
216+ if let histogram: PromHistogram < Double , DimensionHistogramLabels > = client . getMetricInstance ( with: label, andType: . histogram) {
177217 return createHandler ( histogram)
178218 }
179- return createHandler ( createHistogram ( forType: Double . self, named: label, labels: DimensionHistogramLabels . self) )
219+ return createHandler ( client . createHistogram ( forType: Double . self, named: label, labels: DimensionHistogramLabels . self) )
180220 }
181221
182222 public func makeTimer( label: String , dimensions: [ ( String , String ) ] ) -> TimerHandler {
183- let label = self . sanitizer. sanitize ( label)
223+ switch configuration. timerImplementation. _wrapped {
224+ case . summary( let quantiles) :
225+ return self . makeSummaryTimer ( label: label, dimensions: dimensions, quantiles: quantiles)
226+ case . histogram( let buckets) :
227+ return self . makeHistogramTimer ( label: label, dimensions: dimensions, buckets: buckets)
228+ }
229+ }
230+
231+ /// There's two different ways to back swift-api `Timer` with Prometheus classes.
232+ /// This method creates `Summary` backed timer implementation
233+ private func makeSummaryTimer( label: String , dimensions: [ ( String , String ) ] , quantiles: [ Double ] ) -> TimerHandler {
234+ let label = configuration. labelSanitizer. sanitize ( label)
184235 let createHandler = { ( summary: PromSummary ) -> TimerHandler in
185236 return MetricsSummary ( summary: summary, dimensions: dimensions)
186237 }
187- if let summary: PromSummary < Int64 , DimensionSummaryLabels > = self . getMetricInstance ( with: label, andType: . summary) {
238+ if let summary: PromSummary < Int64 , DimensionSummaryLabels > = client . getMetricInstance ( with: label, andType: . summary) {
188239 return createHandler ( summary)
189240 }
190- return createHandler ( createSummary ( forType: Int64 . self, named: label, labels: DimensionSummaryLabels . self) )
241+ return createHandler ( client. createSummary ( forType: Int64 . self, named: label, quantiles: quantiles, labels: DimensionSummaryLabels . self) )
242+ }
243+
244+ /// There's two different ways to back swift-api `Timer` with Prometheus classes.
245+ /// This method creates `Histogram` backed timer implementation
246+ private func makeHistogramTimer( label: String , dimensions: [ ( String , String ) ] , buckets: Buckets ) -> TimerHandler {
247+ let createHandler = { ( histogram: PromHistogram ) -> TimerHandler in
248+ MetricsHistogramTimer ( histogram: histogram, dimensions: dimensions)
249+ }
250+ // PromHistogram should be reused when created for the same label, so we try to look it up
251+ if let histogram: PromHistogram < Int64 , DimensionHistogramLabels > = client. getMetricInstance ( with: label, andType: . histogram) {
252+ return createHandler ( histogram)
253+ }
254+ return createHandler ( client. createHistogram ( forType: Int64 . self, named: label, buckets: buckets, labels: DimensionHistogramLabels . self) )
191255 }
192256}
193257
0 commit comments