@@ -64,6 +64,24 @@ private class MetricsHistogram: RecorderHandler {
64
64
}
65
65
}
66
66
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
+
67
85
private class MetricsSummary : TimerHandler {
68
86
let summary : PromSummary < Int64 , DimensionSummaryLabels >
69
87
let labels : DimensionSummaryLabels ?
@@ -82,7 +100,7 @@ private class MetricsSummary: TimerHandler {
82
100
}
83
101
84
102
func recordNanoseconds( _ duration: Int64 ) {
85
- summary. observe ( duration, labels)
103
+ return summary. observe ( duration, labels)
86
104
}
87
105
}
88
106
@@ -94,7 +112,7 @@ private class MetricsSummary: TimerHandler {
94
112
/// let sanitizer: LabelSanitizer = ...
95
113
/// let prometheusLabel = sanitizer.sanitize(nonPrometheusLabel)
96
114
///
97
- /// By default `PrometheusLabelSanitizer` is used by `PrometheusClient `
115
+ /// By default `PrometheusLabelSanitizer` is used by `PrometheusMetricsFactory `
98
116
public protocol LabelSanitizer {
99
117
/// Sanitize the passed in label to a Prometheus accepted value.
100
118
///
@@ -121,73 +139,119 @@ public struct PrometheusLabelSanitizer: LabelSanitizer {
121
139
}
122
140
}
123
141
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
+
125
159
public func destroyCounter( _ handler: CounterHandler ) {
126
160
guard let handler = handler as? MetricsCounter else { return }
127
- self . removeMetric ( handler. counter)
161
+ client . removeMetric ( handler. counter)
128
162
}
129
163
130
164
public func destroyRecorder( _ handler: RecorderHandler ) {
131
165
if let handler = handler as? MetricsGauge {
132
- self . removeMetric ( handler. gauge)
166
+ client . removeMetric ( handler. gauge)
133
167
}
134
168
if let handler = handler as? MetricsHistogram {
135
- self . removeMetric ( handler. histogram)
169
+ client . removeMetric ( handler. histogram)
136
170
}
137
171
}
138
-
172
+
139
173
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
+ }
142
182
}
143
183
144
184
public func makeCounter( label: String , dimensions: [ ( String , String ) ] ) -> CounterHandler {
145
- let label = self . sanitizer . sanitize ( label)
185
+ let label = configuration . labelSanitizer . sanitize ( label)
146
186
let createHandler = { ( counter: PromCounter ) -> CounterHandler in
147
187
return MetricsCounter ( counter: counter, dimensions: dimensions)
148
188
}
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) {
150
190
return createHandler ( counter)
151
191
}
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) )
153
193
}
154
194
155
195
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)
157
197
return aggregate ? makeHistogram ( label: label, dimensions: dimensions) : makeGauge ( label: label, dimensions: dimensions)
158
198
}
159
199
160
200
private func makeGauge( label: String , dimensions: [ ( String , String ) ] ) -> RecorderHandler {
161
- let label = self . sanitizer . sanitize ( label)
201
+ let label = configuration . labelSanitizer . sanitize ( label)
162
202
let createHandler = { ( gauge: PromGauge ) -> RecorderHandler in
163
203
return MetricsGauge ( gauge: gauge, dimensions: dimensions)
164
204
}
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) {
166
206
return createHandler ( gauge)
167
207
}
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) )
169
209
}
170
210
171
211
private func makeHistogram( label: String , dimensions: [ ( String , String ) ] ) -> RecorderHandler {
172
- let label = self . sanitizer . sanitize ( label)
212
+ let label = configuration . labelSanitizer . sanitize ( label)
173
213
let createHandler = { ( histogram: PromHistogram ) -> RecorderHandler in
174
214
return MetricsHistogram ( histogram: histogram, dimensions: dimensions)
175
215
}
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) {
177
217
return createHandler ( histogram)
178
218
}
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) )
180
220
}
181
221
182
222
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)
184
235
let createHandler = { ( summary: PromSummary ) -> TimerHandler in
185
236
return MetricsSummary ( summary: summary, dimensions: dimensions)
186
237
}
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) {
188
239
return createHandler ( summary)
189
240
}
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) )
191
255
}
192
256
}
193
257
0 commit comments