master
go 305 lines 7.61 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package prometheus
4
5 import (
6 "strconv"
7 "strings"
8
9 "github.com/prometheus/common/model"
10 "github.com/prometheus/prometheus/model/labels"
11 )
12
13 // assembler folds the driver's classified sample stream into typed
14 // MetricFamilies. Grouping of summary quantiles / histogram buckets with their
15 // _sum/_count is keyed by (family name, hash of the base labels). Buffers are
16 // reused across cycles via reset().
17 type assembler struct {
18 metrics MetricFamilies
19 summaries map[assemblyKey]*Summary
20 histograms map[assemblyKey]*Histogram
21 scratch labels.Labels
22
23 // currName/currFamily cache the most recent family to skip the metrics map
24 // lookup for the common case of consecutive samples in the same family.
25 currName string
26 currFamily *MetricFamily
27 }
28
29 type assemblyKey struct {
30 name string
31 hash uint64
32 }
33
34 func (a *assembler) reset() {
35 a.currName = ""
36 a.currFamily = nil
37
38 if a.metrics == nil {
39 a.metrics = make(MetricFamilies)
40 }
41 for _, mf := range a.metrics {
42 mf.help = ""
43 mf.typ = ""
44 mf.metrics = mf.metrics[:0]
45 }
46
47 if a.summaries == nil {
48 a.summaries = make(map[assemblyKey]*Summary)
49 }
50 for k := range a.summaries {
51 delete(a.summaries, k)
52 }
53
54 if a.histograms == nil {
55 a.histograms = make(map[assemblyKey]*Histogram)
56 }
57 for k := range a.histograms {
58 delete(a.histograms, k)
59 }
60 }
61
62 func (a *assembler) applyHelp(name, help string) {
63 mf := a.ensureFamily(name)
64 mf.help = help
65 }
66
67 func (a *assembler) applySample(sample Sample) error {
68 switch sample.Kind {
69 case SampleKindSummaryQuantile:
70 a.addSummaryQuantile(sample)
71 case SampleKindSummarySum:
72 a.addSummarySum(sample)
73 case SampleKindSummaryCount:
74 a.addSummaryCount(sample)
75 case SampleKindHistogramBucket:
76 a.addHistogramBucket(sample)
77 case SampleKindHistogramSum:
78 a.addHistogramSum(sample)
79 case SampleKindHistogramCount:
80 a.addHistogramCount(sample)
81 default:
82 switch sample.FamilyType {
83 case model.MetricTypeSummary:
84 mf := a.summaryFamily(sample.Name)
85 a.summaryFor(mf, assemblyKey{name: sample.Name, hash: sample.Labels.Hash()}, sample.Labels)
86 case model.MetricTypeHistogram:
87 mf := a.histogramFamily(sample.Name)
88 a.histogramFor(mf, assemblyKey{name: sample.Name, hash: sample.Labels.Hash()}, sample.Labels)
89 default:
90 a.addScalar(sample)
91 }
92 }
93 return nil
94 }
95
96 func (a *assembler) families() MetricFamilies {
97 for name, mf := range a.metrics {
98 if len(mf.metrics) == 0 {
99 delete(a.metrics, name)
100 }
101 }
102 return a.metrics
103 }
104
105 func (a *assembler) addScalar(sample Sample) {
106 mf := a.ensureFamily(sample.Name)
107
108 typ := sample.FamilyType
109 if typ == "" {
110 typ = model.MetricTypeUnknown
111 }
112 if mf.typ == "" || mf.typ == model.MetricTypeUnknown {
113 mf.typ = typ
114 }
115
116 m := a.appendMetric(mf, sample.Labels)
117
118 switch typ {
119 case model.MetricTypeGauge:
120 if m.gauge == nil {
121 m.gauge = &Gauge{}
122 }
123 m.gauge.value = sample.Value
124 case model.MetricTypeCounter:
125 if m.counter == nil {
126 m.counter = &Counter{}
127 }
128 m.counter.value = sample.Value
129 default:
130 if m.untyped == nil {
131 m.untyped = &Untyped{}
132 }
133 m.untyped.value = sample.Value
134 }
135 }
136
137 func (a *assembler) addSummaryQuantile(sample Sample) {
138 mf := a.summaryFamily(sample.Name)
139 base, qv, ok := a.stripLabel(sample.Labels, quantileLabel)
140 key := assemblyKey{name: sample.Name}
141 if ok {
142 key.hash = labels.Labels(base).Hash()
143 } else {
144 base = sample.Labels
145 key.hash = sample.Labels.Hash()
146 }
147
148 s := a.summaryFor(mf, key, base)
149 if !ok {
150 return
151 }
152 quantile, _ := strconv.ParseFloat(qv, 64)
153 s.quantiles = append(s.quantiles, Quantile{quantile: quantile, value: sample.Value})
154 }
155
156 func (a *assembler) addSummarySum(sample Sample) {
157 name := strings.TrimSuffix(sample.Name, sumSuffix)
158 mf := a.summaryFamily(name)
159 s := a.summaryFor(mf, assemblyKey{name: name, hash: sample.Labels.Hash()}, sample.Labels)
160 s.sum = sample.Value
161 }
162
163 func (a *assembler) addSummaryCount(sample Sample) {
164 name := strings.TrimSuffix(sample.Name, countSuffix)
165 mf := a.summaryFamily(name)
166 s := a.summaryFor(mf, assemblyKey{name: name, hash: sample.Labels.Hash()}, sample.Labels)
167 s.count = sample.Value
168 }
169
170 func (a *assembler) addHistogramBucket(sample Sample) {
171 name := strings.TrimSuffix(sample.Name, bucketSuffix)
172 mf := a.histogramFamily(name)
173 base, lev, ok := a.stripLabel(sample.Labels, bucketLabel)
174 key := assemblyKey{name: name}
175 if ok {
176 key.hash = labels.Labels(base).Hash()
177 } else {
178 base = sample.Labels
179 key.hash = sample.Labels.Hash()
180 }
181
182 h := a.histogramFor(mf, key, base)
183 if !ok {
184 return
185 }
186 bound, _ := strconv.ParseFloat(lev, 64)
187 h.buckets = append(h.buckets, Bucket{upperBound: bound, cumulativeCount: sample.Value})
188 }
189
190 func (a *assembler) addHistogramSum(sample Sample) {
191 name := strings.TrimSuffix(sample.Name, sumSuffix)
192 mf := a.histogramFamily(name)
193 h := a.histogramFor(mf, assemblyKey{name: name, hash: sample.Labels.Hash()}, sample.Labels)
194 h.sum = sample.Value
195 }
196
197 func (a *assembler) addHistogramCount(sample Sample) {
198 name := strings.TrimSuffix(sample.Name, countSuffix)
199 mf := a.histogramFamily(name)
200 h := a.histogramFor(mf, assemblyKey{name: name, hash: sample.Labels.Hash()}, sample.Labels)
201 h.count = sample.Value
202 }
203
204 func (a *assembler) summaryFamily(name string) *MetricFamily {
205 mf := a.ensureFamily(name)
206 mf.typ = model.MetricTypeSummary
207 return mf
208 }
209
210 func (a *assembler) histogramFamily(name string) *MetricFamily {
211 mf := a.ensureFamily(name)
212 mf.typ = model.MetricTypeHistogram
213 return mf
214 }
215
216 func (a *assembler) summaryFor(mf *MetricFamily, key assemblyKey, lbs labels.Labels) *Summary {
217 if s, ok := a.summaries[key]; ok {
218 return s
219 }
220
221 m := a.appendMetric(mf, lbs)
222 if m.summary == nil {
223 m.summary = &Summary{}
224 } else {
225 m.summary.sum = 0
226 m.summary.count = 0
227 m.summary.quantiles = m.summary.quantiles[:0]
228 }
229
230 a.summaries[key] = m.summary
231 return m.summary
232 }
233
234 func (a *assembler) histogramFor(mf *MetricFamily, key assemblyKey, lbs labels.Labels) *Histogram {
235 if h, ok := a.histograms[key]; ok {
236 return h
237 }
238
239 m := a.appendMetric(mf, lbs)
240 if m.histogram == nil {
241 m.histogram = &Histogram{}
242 } else {
243 m.histogram.sum = 0
244 m.histogram.count = 0
245 m.histogram.buckets = m.histogram.buckets[:0]
246 }
247
248 a.histograms[key] = m.histogram
249 return m.histogram
250 }
251
252 // appendMetric grows mf.metrics by one, reusing the backing array across cycles
253 // and storing a copy of lbs. Instrument pointers on a reused slot are left in
254 // place: a family keeps a stable type across scrapes, so the caller reuses the
255 // existing instrument and allocates only when its pointer is nil. This preserves
256 // the legacy allocation profile (no per-scrape instrument churn).
257 func (a *assembler) appendMetric(mf *MetricFamily, lbs labels.Labels) *Metric {
258 idx := len(mf.metrics)
259 if idx == cap(mf.metrics) {
260 mf.metrics = append(mf.metrics, Metric{})
261 } else {
262 mf.metrics = mf.metrics[:idx+1]
263 }
264
265 m := &mf.metrics[idx]
266 m.labels = m.labels[:0]
267 m.labels = append(m.labels, lbs...)
268 return m
269 }
270
271 func (a *assembler) ensureFamily(name string) *MetricFamily {
272 if a.currFamily != nil && a.currName == name {
273 return a.currFamily
274 }
275 mf, ok := a.metrics[name]
276 if !ok {
277 mf = &MetricFamily{name: name, typ: model.MetricTypeUnknown}
278 a.metrics[name] = mf
279 }
280 a.currName = name
281 a.currFamily = mf
282 return mf
283 }
284
285 // stripLabel returns the label set without name and the removed value, using a
286 // reusable scratch buffer. The result is valid only until the next stripLabel.
287 func (a *assembler) stripLabel(lbs labels.Labels, name string) (labels.Labels, string, bool) {
288 a.scratch = a.scratch[:0]
289 var (
290 value string
291 found bool
292 )
293 for _, lb := range lbs {
294 if lb.Name == name {
295 value = lb.Value
296 found = true
297 continue
298 }
299 a.scratch = append(a.scratch, lb)
300 }
301 if !found {
302 return nil, "", false
303 }
304 return a.scratch, value, true
305 }