@@ -26,67 +26,84 @@ import (
26
26
dto "github.com/prometheus/client_model/go"
27
27
)
28
28
29
- var _ rawCollector = & CachedCollector {}
29
+ var _ TransactionalGatherer = & CachedTGatherer {}
30
30
31
- // CachedCollector allows creating allocation friendly metrics which change less frequently than scrape time, yet
32
- // label values can are changing over time. This collector
31
+ // CachedTGatherer is a transactional gatherer that allows maintaining set of metrics which
32
+ // change less frequently than scrape time, yet label values and values change over time.
33
33
//
34
34
// If you happen to use NewDesc, NewConstMetric or MustNewConstMetric inside Collector.Collect routine, consider
35
- // using CachedCollector instead.
36
- type CachedCollector struct {
35
+ // using CachedTGatherer instead.
36
+ //
37
+ // Use CachedTGatherer with classic Registry using NewMultiTRegistry and ToTransactionalGatherer helpers.
38
+ // TODO(bwplotka): Add non-session update API if useful for watcher-like mechanic.
39
+ type CachedTGatherer struct {
37
40
metrics map [uint64 ]* dto.Metric
38
41
metricFamilyByName map [string ]* dto.MetricFamily
42
+ mMu sync.RWMutex
39
43
40
44
pendingSession bool
45
+ psMu sync.Mutex
41
46
}
42
47
43
- func NewCachedCollector () * CachedCollector {
44
- return & CachedCollector {
48
+ func NewCachedTGatherer () * CachedTGatherer {
49
+ return & CachedTGatherer {
45
50
metrics : make (map [uint64 ]* dto.Metric ),
46
51
metricFamilyByName : map [string ]* dto.MetricFamily {},
47
52
}
48
53
}
49
54
50
- func (c * CachedCollector ) Collect () []* dto.MetricFamily {
51
- // TODO(bwplotka): Optimize potential penalty here.
52
- return internal .NormalizeMetricFamilies (c .metricFamilyByName )
55
+ // Gather implements TransactionalGatherer interface.
56
+ func (c * CachedTGatherer ) Gather () (_ []* dto.MetricFamily , done func (), err error ) {
57
+ c .mMu .RLock ()
58
+ // TODO(bwplotka): Consider caching slice and normalizing on write.
59
+ return internal .NormalizeMetricFamilies (c .metricFamilyByName ), c .mMu .RUnlock , nil
53
60
}
54
61
55
- // NewSession allows to collect all metrics in one go and update cache as much in-place
56
- // as possible to save allocations.
57
- // NOTE: Not concurrency safe and only one allowed at the time (until commit).
58
- func (c * CachedCollector ) NewSession () * CollectSession {
62
+ // NewSession allows to recreate state of all metrics in CachedTGatherer in
63
+ // one go and update cache in-place to save allocations.
64
+ // Only one session is allowed at the time.
65
+ //
66
+ // Session is not concurrency safe.
67
+ func (c * CachedTGatherer ) NewSession () (* CollectSession , error ) {
68
+ c .psMu .Lock ()
69
+ if c .pendingSession {
70
+ c .psMu .Unlock ()
71
+ return nil , errors .New ("only one session allowed, one already pending" )
72
+ }
59
73
c .pendingSession = true
74
+ c .psMu .Unlock ()
75
+
60
76
return & CollectSession {
61
77
c : c ,
62
78
currentMetrics : make (map [uint64 ]* dto.Metric , len (c .metrics )),
63
79
currentByName : make (map [string ]* dto.MetricFamily , len (c .metricFamilyByName )),
64
- }
80
+ }, nil
65
81
}
66
82
67
83
type CollectSession struct {
68
84
closed bool
69
85
70
- c * CachedCollector
86
+ c * CachedTGatherer
71
87
currentMetrics map [uint64 ]* dto.Metric
72
88
currentByName map [string ]* dto.MetricFamily
73
89
}
74
90
91
+ // MustAddMetric is an AddMetric that panics on error.
75
92
func (s * CollectSession ) MustAddMetric (fqName , help string , labelNames , labelValues []string , valueType ValueType , value float64 , ts * time.Time ) {
76
93
if err := s .AddMetric (fqName , help , labelNames , labelValues , valueType , value , ts ); err != nil {
77
94
panic (err )
78
95
}
79
96
}
80
97
81
- // AddMetric .. .
98
+ // AddMetric adds metrics to current session. No changes will be updated in CachedTGatherer until Commit .
82
99
// TODO(bwplotka): Add validation.
83
100
func (s * CollectSession ) AddMetric (fqName , help string , labelNames , labelValues []string , valueType ValueType , value float64 , ts * time.Time ) error {
84
101
if s .closed {
85
102
return errors .New ("new metric: collect session is closed, but was attempted to be used" )
86
103
}
87
104
88
- // Label names can be unsorted, will be sorting them later. The only implication is cachability if
89
- // consumer provide non-deterministic order of those (unlikely since label values has to be matched) .
105
+ // Label names can be unsorted, we will be sorting them later. The only implication is cachability if
106
+ // consumer provide non-deterministic order of those.
90
107
91
108
if len (labelNames ) != len (labelValues ) {
92
109
return errors .New ("new metric: label name has different len than values" )
@@ -116,7 +133,8 @@ func (s *CollectSession) AddMetric(fqName, help string, labelNames, labelValues
116
133
h := xxhash .New ()
117
134
h .WriteString (fqName )
118
135
h .Write (separatorByteSlice )
119
- for i := range labelNames { // Ofc not in the same order...
136
+
137
+ for i := range labelNames {
120
138
h .WriteString (labelNames [i ])
121
139
h .Write (separatorByteSlice )
122
140
h .WriteString (labelValues [i ])
@@ -178,75 +196,96 @@ func (s *CollectSession) AddMetric(fqName, help string, labelNames, labelValues
178
196
}
179
197
s .currentMetrics [hSum ] = m
180
198
181
- // Will be sorted later.
199
+ // Will be sorted later anyway, skip for now .
182
200
d .Metric = append (d .Metric , m )
183
201
return nil
184
202
}
185
203
186
204
func (s * CollectSession ) Commit () {
187
- // TODO(bwplotka): Sort metrics within family.
205
+ s .c .mMu .Lock ()
206
+ // TODO(bwplotka): Sort metrics within family?
188
207
s .c .metricFamilyByName = s .currentByName
189
208
s .c .metrics = s .currentMetrics
209
+ s .c .mMu .Unlock ()
190
210
211
+ s .c .psMu .Lock ()
191
212
s .closed = true
192
213
s .c .pendingSession = false
214
+ s .c .psMu .Unlock ()
193
215
}
194
216
195
- type BlockingRegistry struct {
196
- * Registry
197
-
198
- // rawCollector represents special collectors which requires blocking collect for the whole duration
199
- // of returned dto.MetricFamily usage.
200
- rawCollectors []rawCollector
201
- mu sync.Mutex
202
- }
203
-
204
- func NewBlockingRegistry () * BlockingRegistry {
205
- return & BlockingRegistry {
206
- Registry : NewRegistry (),
207
- }
208
- }
217
+ var _ TransactionalGatherer = & MultiTRegistry {}
209
218
210
- type rawCollector interface {
211
- Collect () []* dto.MetricFamily
212
- }
213
-
214
- func (b * BlockingRegistry ) RegisterRaw (r rawCollector ) error {
215
- // TODO(bwplotka): Register, I guess for dups/check purposes?
216
- b .rawCollectors = append (b .rawCollectors , r )
217
- return nil
219
+ // MultiTRegistry is a TransactionalGatherer that joins gathered metrics from multiple
220
+ // transactional gatherers.
221
+ //
222
+ // It is caller responsibility to ensure two registries have mutually exclusive metric families,
223
+ // no deduplication will happen.
224
+ type MultiTRegistry struct {
225
+ tGatherers []TransactionalGatherer
218
226
}
219
227
220
- func (b * BlockingRegistry ) MustRegisterRaw (r rawCollector ) {
221
- if err := b .RegisterRaw (r ); err != nil {
222
- panic (err )
228
+ // NewMultiTRegistry creates MultiTRegistry.
229
+ func NewMultiTRegistry (tGatherers ... TransactionalGatherer ) * MultiTRegistry {
230
+ return & MultiTRegistry {
231
+ tGatherers : tGatherers ,
223
232
}
224
233
}
225
234
226
- func ( b * BlockingRegistry ) Gather () ( _ [] * dto. MetricFamily , done func (), err error ) {
227
- b . mu . Lock ()
228
- mfs , err := b . Registry . Gather ()
235
+ // Gather implements TransactionalGatherer interface.
236
+ func ( r * MultiTRegistry ) Gather () ( mfs [] * dto. MetricFamily , done func (), err error ) {
237
+ errs := MultiError {}
229
238
230
- // TODO(bwplotka): Returned mfs are sorted, so sort raw ones and inject?
239
+ dFns := make ([] func (), 0 , len ( r . tGatherers ))
231
240
// TODO(bwplotka): Implement concurrency for those?
232
- for _ , r := range b .rawCollectors {
233
- // TODO(bwplotka): Check for duplicates.
234
- mfs = append (mfs , r .Collect ()... )
241
+ for _ , g := range r .tGatherers {
242
+ // TODO(bwplotka): Check for duplicates?
243
+ m , d , err := g .Gather ()
244
+ errs .Append (err )
245
+
246
+ mfs = append (mfs , m ... )
247
+ dFns = append (dFns , d )
235
248
}
236
249
237
250
// TODO(bwplotka): Consider sort in place, given metric family in gather is sorted already.
238
251
sort .Slice (mfs , func (i , j int ) bool {
239
252
return * mfs [i ].Name < * mfs [j ].Name
240
253
})
241
- return mfs , func () { b .mu .Unlock () }, err
254
+ return mfs , func () {
255
+ for _ , d := range dFns {
256
+ d ()
257
+ }
258
+ }, errs .MaybeUnwrap ()
242
259
}
243
260
244
- // TransactionalGatherer ...
261
+ // TransactionalGatherer represents transactional gatherer that can be triggered to notify gatherer that memory
262
+ // used by metric family is no longer used by a caller. This allows implementations with cache.
245
263
type TransactionalGatherer interface {
246
- // Gather ...
264
+ // Gather returns metrics in a lexicographically sorted slice
265
+ // of uniquely named MetricFamily protobufs. Gather ensures that the
266
+ // returned slice is valid and self-consistent so that it can be used
267
+ // for valid exposition. As an exception to the strict consistency
268
+ // requirements described for metric.Desc, Gather will tolerate
269
+ // different sets of label names for metrics of the same metric family.
270
+ //
271
+ // Even if an error occurs, Gather attempts to gather as many metrics as
272
+ // possible. Hence, if a non-nil error is returned, the returned
273
+ // MetricFamily slice could be nil (in case of a fatal error that
274
+ // prevented any meaningful metric collection) or contain a number of
275
+ // MetricFamily protobufs, some of which might be incomplete, and some
276
+ // might be missing altogether. The returned error (which might be a
277
+ // MultiError) explains the details. Note that this is mostly useful for
278
+ // debugging purposes. If the gathered protobufs are to be used for
279
+ // exposition in actual monitoring, it is almost always better to not
280
+ // expose an incomplete result and instead disregard the returned
281
+ // MetricFamily protobufs in case the returned error is non-nil.
282
+ //
283
+ // Important: done is expected to be triggered (even if the error occurs!)
284
+ // once caller does not need returned slice of dto.MetricFamily.
247
285
Gather () (_ []* dto.MetricFamily , done func (), err error )
248
286
}
249
287
288
+ // ToTransactionalGatherer transforms Gatherer to transactional one with noop as done function.
250
289
func ToTransactionalGatherer (g Gatherer ) TransactionalGatherer {
251
290
return & noTransactionGatherer {g : g }
252
291
}
@@ -255,6 +294,7 @@ type noTransactionGatherer struct {
255
294
g Gatherer
256
295
}
257
296
297
+ // Gather implements TransactionalGatherer interface.
258
298
func (g * noTransactionGatherer ) Gather () (_ []* dto.MetricFamily , done func (), err error ) {
259
299
mfs , err := g .g .Gather ()
260
300
return mfs , func () {}, err
0 commit comments