@@ -47,9 +47,14 @@ public final class PrometheusCollectorRegistry: Sendable {
4747 }
4848 }
4949
50- private enum Metric {
51- case counter( Counter , help: String )
52- case counterWithLabels( [ String ] , [ LabelsKey : Counter ] , help: String )
50+ private typealias Metric < T> = ( metric: T , help: String )
51+
52+ private struct MetricContainer < T> {
53+ var metrics : [ LabelsKey : Metric < T > ]
54+ }
55+
56+ private enum MetricType {
57+ case counterMetrics( String , MetricContainer < Counter > )
5358 case gauge( Gauge , help: String )
5459 case gaugeWithLabels( [ String ] , [ LabelsKey : Gauge ] , help: String )
5560 case durationHistogram( DurationHistogram , help: String )
@@ -58,7 +63,7 @@ public final class PrometheusCollectorRegistry: Sendable {
5863 case valueHistogramWithLabels( [ String ] , [ LabelsKey : ValueHistogram ] , [ Double ] , help: String )
5964 }
6065
61- private let box = NIOLockedValueBox ( [ String: Metric ] ( ) )
66+ private let box = NIOLockedValueBox ( [ String: MetricType ] ( ) )
6267
6368 /// Create a new collector registry
6469 public init ( ) { }
@@ -75,25 +80,7 @@ public final class PrometheusCollectorRegistry: Sendable {
7580 /// If the parameter is omitted or an empty string is passed, the `# HELP` line will not be generated for this metric.
7681 /// - Returns: A ``Counter`` that is registered with this ``PrometheusCollectorRegistry``
7782 public func makeCounter( name: String , help: String ) -> Counter {
78- let name = name. ensureValidMetricName ( )
79- let help = help. ensureValidHelpText ( )
80- return self . box. withLockedValue { store -> Counter in
81- guard let value = store [ name] else {
82- let counter = Counter ( name: name, labels: [ ] )
83- store [ name] = . counter( counter, help: help)
84- return counter
85- }
86- guard case . counter( let counter, _) = value else {
87- fatalError (
88- """
89- Could not make Counter with name: \( name) , since another metric type
90- already exists for the same name.
91- """
92- )
93- }
94-
95- return counter
96- }
83+ return self . makeCounter ( name: name, labels: [ ] , help: help)
9784 }
9885
9986 /// Creates a new ``Counter`` collector or returns the already existing one with the same name.
@@ -104,7 +91,7 @@ public final class PrometheusCollectorRegistry: Sendable {
10491 /// - Parameter name: A name to identify ``Counter``'s value.
10592 /// - Returns: A ``Counter`` that is registered with this ``PrometheusCollectorRegistry``
10693 public func makeCounter( name: String ) -> Counter {
107- return self . makeCounter ( name: name, help: " " )
94+ return self . makeCounter ( name: name, labels : [ ] , help: " " )
10895 }
10996
11097 /// Creates a new ``Counter`` collector or returns the already existing one with the same name,
@@ -116,7 +103,7 @@ public final class PrometheusCollectorRegistry: Sendable {
116103 /// - Parameter descriptor: An ``MetricNameDescriptor`` that provides the fully qualified name for the metric.
117104 /// - Returns: A ``Counter`` that is registered with this ``PrometheusCollectorRegistry``
118105 public func makeCounter( descriptor: MetricNameDescriptor ) -> Counter {
119- return self . makeCounter ( name: descriptor. name, help: descriptor. helpText ?? " " )
106+ return self . makeCounter ( name: descriptor. name, labels : [ ] , help: descriptor. helpText ?? " " )
120107 }
121108
122109 /// Creates a new ``Counter`` collector or returns the already existing one with the same name.
@@ -131,51 +118,49 @@ public final class PrometheusCollectorRegistry: Sendable {
131118 /// If the parameter is omitted or an empty string is passed, the `# HELP` line will not be generated for this metric.
132119 /// - Returns: A ``Counter`` that is registered with this ``PrometheusCollectorRegistry``
133120 public func makeCounter( name: String , labels: [ ( String , String ) ] , help: String ) -> Counter {
134- guard !labels. isEmpty else {
135- return self . makeCounter ( name: name, help: help)
136- }
137-
138121 let name = name. ensureValidMetricName ( )
139122 let labels = labels. ensureValidLabelNames ( )
140123 let help = help. ensureValidHelpText ( )
124+ let key = LabelsKey ( labels)
141125
142126 return self . box. withLockedValue { store -> Counter in
143- guard let value = store [ name] else {
144- let labelNames = labels . allLabelNames
127+ guard let entry = store [ name] else {
128+ // First time a Counter is registered with this name.
145129 let counter = Counter ( name: name, labels: labels)
146-
147- store [ name] = . counterWithLabels( labelNames, [ LabelsKey ( labels) : counter] , help: help)
148- return counter
149- }
150- guard case . counterWithLabels( let labelNames, var dimensionLookup, let help) = value else {
151- fatalError (
152- """
153- Could not make Counter with name: \( name) and labels: \( labels) , since another
154- metric type already exists for the same name.
155- """
130+ let newMetric : Metric < Counter > = ( metric: counter, help: help)
131+ let newContainer = MetricContainer < Counter > (
132+ metrics: [ key: newMetric]
156133 )
157- }
158-
159- let key = LabelsKey ( labels)
160- if let counter = dimensionLookup [ key] {
134+ store [ name] = . counterMetrics( name, newContainer)
161135 return counter
162136 }
137+ switch entry {
138+ case . counterMetrics( let existingName, var MetricContainer) :
139+ if let existingMetric = MetricContainer . metrics [ key] {
140+ return existingMetric. metric
141+ }
163142
164- // check if all labels match the already existing ones.
165- if labelNames != labels. allLabelNames {
143+ // Even if the metric name is identical, each label set defines a unique time series
144+ let newCounter = Counter ( name: name, labels: labels)
145+ let netNewMetric : Metric < Counter > = ( metric: newCounter, help: help)
146+ MetricContainer . metrics [ key] = netNewMetric
147+
148+ // Write the modified entry back to the store.
149+ store [ name] = . counterMetrics( existingName, MetricContainer)
150+
151+ return newCounter
152+
153+ default :
154+ // A metric with this name exists, but it's not a Counter. This is a programming error.
155+ // While Prometheus wouldn't stop you, it may result in unpredictable behavior with tools like Grafana or PromQL.
166156 fatalError (
167157 """
168- Could not make Counter with name: \( name ) and labels: \( labels ) , since the
169- label names don't match the label names of previously registered Counters with
170- the same name.
158+ Metric type mismatch:
159+ Could not register a Counter with name ' \( name ) ',
160+ since a different metric type ( \( entry . self ) ) was already registered with this name.
171161 """
172162 )
173163 }
174-
175- let counter = Counter ( name: name, labels: labels)
176- dimensionLookup [ key] = counter
177- store [ name] = . counterWithLabels( labelNames, dimensionLookup, help: help)
178- return counter
179164 }
180165 }
181166
@@ -703,17 +688,19 @@ public final class PrometheusCollectorRegistry: Sendable {
703688 public func unregisterCounter( _ counter: Counter ) {
704689 self . box. withLockedValue { store in
705690 switch store [ counter. name] {
706- case . counter( let storedCounter, _) :
707- guard storedCounter === counter else { return }
708- store. removeValue ( forKey: counter. name)
709- case . counterWithLabels( let labelNames, var dimensions, let help) :
710- let labelsKey = LabelsKey ( counter. labels)
711- guard dimensions [ labelsKey] === counter else { return }
712- dimensions. removeValue ( forKey: labelsKey)
713- if dimensions. isEmpty {
714- store. removeValue ( forKey: counter. name)
691+ case . counterMetrics( let name, var MetricContainer) :
692+ let key = LabelsKey ( counter. labels)
693+ guard let existingMetric = MetricContainer . metrics [ key] ,
694+ existingMetric. metric === counter
695+ else {
696+ return
697+ }
698+ MetricContainer . metrics. removeValue ( forKey: key)
699+
700+ if MetricContainer . metrics. isEmpty {
701+ store. removeValue ( forKey: name)
715702 } else {
716- store [ counter . name] = . counterWithLabels ( labelNames , dimensions , help : help )
703+ store [ name] = . counterMetrics ( name , MetricContainer )
717704 }
718705 default :
719706 return
@@ -806,52 +793,52 @@ public final class PrometheusCollectorRegistry: Sendable {
806793 let prefixHelp = " HELP "
807794 let prefixType = " TYPE "
808795
809- for (label , metric) in metrics {
796+ for (name , metric) in metrics {
810797 switch metric {
811- case . counter ( let counter , let help ) :
812- help . isEmpty ? ( ) : buffer . addLine ( prefix : prefixHelp , name : label , value : help )
813- buffer . addLine ( prefix : prefixType , name : label , value: " counter " )
814- counter . emit ( into : & buffer )
815-
816- case . counterWithLabels ( _ , let counters , let help ) :
817- help . isEmpty ? ( ) : buffer . addLine ( prefix : prefixHelp , name : label , value : help)
818- buffer. addLine ( prefix: prefixType , name: label , value: " counter " )
819- for counter in counters . values {
820- counter . emit ( into: & buffer)
798+ case . counterMetrics ( _ , let MetricContainer ) :
799+ // An entry should not be empty, as a safeguard skip if it is.
800+ guard let _ = MetricContainer . metrics . first ? . value else {
801+ continue
802+ }
803+ for Metric in MetricContainer . metrics . values {
804+ let help = Metric . help
805+ help . isEmpty ? ( ) : buffer. addLine ( prefix: prefixHelp , name: name , value: help )
806+ buffer . addLine ( prefix : prefixType , name : name , value : " counter " )
807+ Metric . metric . emit ( into: & buffer)
821808 }
822809
823810 case . gauge( let gauge, let help) :
824- help. isEmpty ? ( ) : buffer. addLine ( prefix: prefixHelp, name: label , value: help)
825- buffer. addLine ( prefix: prefixType, name: label , value: " gauge " )
811+ help. isEmpty ? ( ) : buffer. addLine ( prefix: prefixHelp, name: name , value: help)
812+ buffer. addLine ( prefix: prefixType, name: name , value: " gauge " )
826813 gauge. emit ( into: & buffer)
827814
828815 case . gaugeWithLabels( _, let gauges, let help) :
829- help. isEmpty ? ( ) : buffer. addLine ( prefix: prefixHelp, name: label , value: help)
830- buffer. addLine ( prefix: prefixType, name: label , value: " gauge " )
816+ help. isEmpty ? ( ) : buffer. addLine ( prefix: prefixHelp, name: name , value: help)
817+ buffer. addLine ( prefix: prefixType, name: name , value: " gauge " )
831818 for gauge in gauges. values {
832819 gauge. emit ( into: & buffer)
833820 }
834821
835822 case . durationHistogram( let histogram, let help) :
836- help. isEmpty ? ( ) : buffer. addLine ( prefix: prefixHelp, name: label , value: help)
837- buffer. addLine ( prefix: prefixType, name: label , value: " histogram " )
823+ help. isEmpty ? ( ) : buffer. addLine ( prefix: prefixHelp, name: name , value: help)
824+ buffer. addLine ( prefix: prefixType, name: name , value: " histogram " )
838825 histogram. emit ( into: & buffer)
839826
840827 case . durationHistogramWithLabels( _, let histograms, _, let help) :
841- help. isEmpty ? ( ) : buffer. addLine ( prefix: prefixHelp, name: label , value: help)
842- buffer. addLine ( prefix: prefixType, name: label , value: " histogram " )
828+ help. isEmpty ? ( ) : buffer. addLine ( prefix: prefixHelp, name: name , value: help)
829+ buffer. addLine ( prefix: prefixType, name: name , value: " histogram " )
843830 for histogram in histograms. values {
844831 histogram. emit ( into: & buffer)
845832 }
846833
847834 case . valueHistogram( let histogram, let help) :
848- help. isEmpty ? ( ) : buffer. addLine ( prefix: prefixHelp, name: label , value: help)
849- buffer. addLine ( prefix: prefixType, name: label , value: " histogram " )
835+ help. isEmpty ? ( ) : buffer. addLine ( prefix: prefixHelp, name: name , value: help)
836+ buffer. addLine ( prefix: prefixType, name: name , value: " histogram " )
850837 histogram. emit ( into: & buffer)
851838
852839 case . valueHistogramWithLabels( _, let histograms, _, let help) :
853- help. isEmpty ? ( ) : buffer. addLine ( prefix: prefixHelp, name: label , value: help)
854- buffer. addLine ( prefix: prefixType, name: label , value: " histogram " )
840+ help. isEmpty ? ( ) : buffer. addLine ( prefix: prefixHelp, name: name , value: help)
841+ buffer. addLine ( prefix: prefixType, name: name , value: " histogram " )
855842 for histogram in histograms. values {
856843 histogram. emit ( into: & buffer)
857844 }
0 commit comments