Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/grafana-agent
Path: blob/main/pkg/util/testappender/testappender.go
4096 views
1
// Package testappender exposes utilities to test code which writes to
2
// Prometheus storage.Appenders.
3
package testappender
4
5
import (
6
"fmt"
7
8
"github.com/grafana/agent/pkg/util/testappender/internal/dtobuilder"
9
dto "github.com/prometheus/client_model/go"
10
"github.com/prometheus/common/model"
11
"github.com/prometheus/prometheus/model/exemplar"
12
"github.com/prometheus/prometheus/model/histogram"
13
"github.com/prometheus/prometheus/model/labels"
14
"github.com/prometheus/prometheus/model/metadata"
15
"github.com/prometheus/prometheus/storage"
16
"github.com/prometheus/prometheus/tsdb"
17
)
18
19
// Appender implements storage.Appender. It keeps track of samples, metadata,
20
// and exemplars written to it.
21
//
22
// When Commit is called, the written data will be converted into a slice of
23
// *dto.MetricFamily, when can then be used for asserting against expectations
24
// in tests.
25
//
26
// The zero value of Appender is ready for use. Appender is only intended for
27
// test code, and is not optimized for production.
28
//
29
// Appender is not safe for concurrent use.
30
type Appender struct {
31
// HideTimestamps, when true, will omit timestamps from results.
32
HideTimestamps bool
33
34
commitCalled, rollbackCalled bool
35
36
samples map[string]dtobuilder.Sample // metric labels -> sample
37
exemplars map[string]dtobuilder.SeriesExemplar // metric labels -> series exemplar
38
metadata map[string]metadata.Metadata // metric family name -> metadata
39
40
families []*dto.MetricFamily
41
}
42
43
var _ storage.Appender = (*Appender)(nil)
44
45
func (app *Appender) init() {
46
if app.samples == nil {
47
app.samples = make(map[string]dtobuilder.Sample)
48
}
49
if app.exemplars == nil {
50
app.exemplars = make(map[string]dtobuilder.SeriesExemplar)
51
}
52
if app.metadata == nil {
53
app.metadata = make(map[string]metadata.Metadata)
54
}
55
}
56
57
// Append adds or updates a sample for a given metric, identified by labels. l
58
// must not be empty. If Append is called twice for the same metric, older
59
// samples are discarded.
60
//
61
// Upon calling Commit, a MetricFamily is created for each unique `__name__`
62
// label. If UpdateMetadata is not called for the named series, the series will
63
// be treated as untyped. The timestamp of the metric will be reported with the
64
// value denoted by t.
65
//
66
// The ref field is ignored, and Append always returns 0 for the resulting
67
// storage.SeriesRef.
68
func (app *Appender) Append(ref storage.SeriesRef, l labels.Labels, t int64, v float64) (storage.SeriesRef, error) {
69
if app.commitCalled || app.rollbackCalled {
70
return 0, fmt.Errorf("appender is closed")
71
}
72
app.init()
73
74
l = l.WithoutEmpty()
75
if len(l) == 0 {
76
return 0, fmt.Errorf("empty labelset: %w", tsdb.ErrInvalidSample)
77
}
78
if lbl, dup := l.HasDuplicateLabelNames(); dup {
79
return 0, fmt.Errorf("label name %q is not unique: %w", lbl, tsdb.ErrInvalidSample)
80
}
81
82
app.samples[l.String()] = dtobuilder.Sample{
83
Labels: l,
84
Timestamp: t,
85
Value: v,
86
PrintTimestamp: !app.HideTimestamps,
87
}
88
return 0, nil
89
}
90
91
// AppendExemplar adds an exemplar for a given metric, identified by labels. l
92
// must not be empty.
93
//
94
// Upon calling Commit, exemplars are injected into the resulting Metrics for
95
// any Counter or Histogram.
96
//
97
// The ref field is ignored, and AppendExemplar always returns 0 for the
98
// resulting storage.SeriesRef.
99
func (app *Appender) AppendExemplar(ref storage.SeriesRef, l labels.Labels, e exemplar.Exemplar) (storage.SeriesRef, error) {
100
if app.commitCalled || app.rollbackCalled {
101
return 0, fmt.Errorf("appender is closed")
102
}
103
app.init()
104
105
l = l.WithoutEmpty()
106
if len(l) == 0 {
107
return 0, fmt.Errorf("empty labelset: %w", tsdb.ErrInvalidSample)
108
}
109
if lbl, dup := l.HasDuplicateLabelNames(); dup {
110
return 0, fmt.Errorf("label name %q is not unique: %w", lbl, tsdb.ErrInvalidSample)
111
}
112
113
app.exemplars[l.String()] = dtobuilder.SeriesExemplar{
114
Labels: l,
115
Exemplar: e,
116
}
117
return 0, nil
118
}
119
120
// UpdateMetadata associates metadata for a given named metric. l must not be
121
// empty. Only the `__name__` label is used from the label set; other labels
122
// are ignored.
123
//
124
// Upon calling Commit, the metadata will be injected into the associated
125
// MetricFamily. If m represents a histogram, metrics suffixed with `_bucket`,
126
// `_sum`, and `_count` will be brought into the same MetricFamily.
127
func (app *Appender) UpdateMetadata(ref storage.SeriesRef, l labels.Labels, m metadata.Metadata) (storage.SeriesRef, error) {
128
if app.commitCalled || app.rollbackCalled {
129
return 0, fmt.Errorf("appender is closed")
130
}
131
app.init()
132
133
l = l.WithoutEmpty()
134
if len(l) == 0 {
135
return 0, fmt.Errorf("empty labelset: %w", tsdb.ErrInvalidSample)
136
}
137
if lbl, dup := l.HasDuplicateLabelNames(); dup {
138
return 0, fmt.Errorf("label name %q is not unique: %w", lbl, tsdb.ErrInvalidSample)
139
}
140
141
// Metadata is associated with just the metric family, retrieved by the
142
// __name__ label. All metrics for that same name always have the same
143
// metadata.
144
familyName := l.Get(model.MetricNameLabel)
145
if familyName == "" {
146
return 0, fmt.Errorf("__name__ label missing: %w", tsdb.ErrInvalidSample)
147
}
148
app.metadata[familyName] = m
149
return 0, nil
150
}
151
152
// AppendHistogram implements storage.Appendable, but always returns an error
153
// as native histograms are not supported.
154
func (app *Appender) AppendHistogram(ref storage.SeriesRef, l labels.Labels, t int64, h *histogram.Histogram, fh *histogram.FloatHistogram) (storage.SeriesRef, error) {
155
return 0, fmt.Errorf("native histograms are not supported")
156
}
157
158
// Commit commits pending samples, exemplars, and metadata, converting them
159
// into a slice of *dto.MetricsFamily. Call MetricFamilies to get the resulting
160
// data.
161
//
162
// After calling Commit, no other methods except MetricFamilies may be called.
163
func (app *Appender) Commit() error {
164
if app.commitCalled || app.rollbackCalled {
165
return fmt.Errorf("appender is closed")
166
}
167
168
app.commitCalled = true
169
app.families = dtobuilder.Build(
170
app.samples,
171
app.exemplars,
172
app.metadata,
173
)
174
return nil
175
}
176
177
// Rollback discards pending samples, exemplars, and metadata.
178
//
179
// After calling Rollback, no other methods may be called and the Appender must
180
// be discarded.
181
func (app *Appender) Rollback() error {
182
if app.commitCalled || app.rollbackCalled {
183
return fmt.Errorf("appender is closed")
184
}
185
186
app.rollbackCalled = true
187
return nil
188
}
189
190
// MetricFamilies returns the generated slice of *dto.MetricsFamily.
191
// MetricFamilies returns an error unless Commit was called.
192
//
193
// MetricFamilies always returns a non-nil slice. If no data was appended, the
194
// resulting slice has a length of zero.
195
func (app *Appender) MetricFamilies() ([]*dto.MetricFamily, error) {
196
if !app.commitCalled {
197
return nil, fmt.Errorf("MetricFamilies is not ready")
198
} else if app.rollbackCalled {
199
return nil, fmt.Errorf("appender is closed")
200
}
201
202
return app.families, nil
203
}
204
205