15
15
package metrics
16
16
17
17
import (
18
+ "errors"
18
19
"fmt"
19
20
20
21
"github.com/prometheus/client_golang/prometheus"
60
61
[]string {
61
62
"GVK" ,
62
63
})
64
+
65
+ userMetrics = map [string ]prometheus.Collector {}
63
66
)
64
67
65
68
func init () {
@@ -82,6 +85,165 @@ func RegisterBuildInfo(r prometheus.Registerer) {
82
85
r .MustRegister (buildInfo )
83
86
}
84
87
88
+ type UserMetric struct {
89
+ Name string `json:"name" yaml:"name"`
90
+ Help string `json:"description" yaml:"description"`
91
+ Counter * UserMetricCounter `json:"counter,omitempty" yaml:"counter,omitempty"`
92
+ Gauge * UserMetricGauge `json:"gauge,omitempty" yaml:"gauge,omitempty"`
93
+ Histogram * UserMetricHistogram `json:"histogram,omitempty" yaml:"histogram,omitempty"`
94
+ Summary * UserMetricSummary `json:"summary,omitempty" yaml:"summary,omitempty"`
95
+ }
96
+
97
+ type UserMetricCounter struct {
98
+ Inc bool `json:"increment,omitempty" yaml:"increment,omitempty"`
99
+ Add float64 `json:"add,omitempty" yaml:"add,omitempty"`
100
+ }
101
+
102
+ type UserMetricGauge struct {
103
+ Set float64 `json:"set,omitempty" yaml:"set,omitempty"`
104
+ Inc bool `json:"increment,omitempty" yaml:"increment,omitempty"`
105
+ Dec bool `json:"decrement,omitempty" yaml:"decrement,omitempty"`
106
+ SetToCurrentTime bool `json:"set_to_current_time,omitempty" yaml:"set_to_current_time,omitempty"`
107
+ Add float64 `json:"add,omitempty" yaml:"add,omitempty"`
108
+ Sub float64 `json:"subtract,omitempty" yaml:"subtract,omitempty"`
109
+ }
110
+
111
+ type UserMetricHistogram struct {
112
+ Observe float64 `json:"observe,omitempty" yaml:"observe,omitempty"`
113
+ }
114
+
115
+ type UserMetricSummary struct {
116
+ Observe float64 `json:"observe,omitempty" yaml:"observe,omitempty"`
117
+ }
118
+
119
+ func validateMetricSpec (metricSpec UserMetric ) error {
120
+ var metricConfigs int
121
+ if metricSpec .Counter != nil {
122
+ metricConfigs ++
123
+ }
124
+ if metricSpec .Gauge != nil {
125
+ metricConfigs ++
126
+ }
127
+ if metricSpec .Summary != nil {
128
+ metricConfigs ++
129
+ }
130
+ if metricSpec .Histogram != nil {
131
+ metricConfigs ++
132
+ }
133
+ if metricConfigs > 1 {
134
+ return errors .New ("only one metric can be processed at a time" )
135
+ } else if metricConfigs == 0 {
136
+ return errors .New ("a request should contain at least one metric" )
137
+ }
138
+ return nil
139
+ }
140
+
141
+ func handleCounter (metricSpec UserMetric , counter prometheus.Counter ) error {
142
+ if metricSpec .Counter == nil {
143
+ return fmt .Errorf ("cannot change metric type of %s, which is a counter" , metricSpec .Name )
144
+ }
145
+ if metricSpec .Counter .Inc {
146
+ counter .Inc ()
147
+ } else if metricSpec .Counter .Add != 0.0 {
148
+ if metricSpec .Counter .Add < 0 {
149
+ return errors .New ("counter metrics cannot decrease in value" )
150
+ }
151
+ counter .Add (metricSpec .Counter .Add )
152
+ }
153
+ return nil
154
+ }
155
+
156
+ func handleGauge (metricSpec UserMetric , gauge prometheus.Gauge ) error {
157
+ if metricSpec .Gauge == nil {
158
+ return fmt .Errorf ("cannot change metric type of %s, which is a gauge" , metricSpec .Name )
159
+ }
160
+ if metricSpec .Gauge .Inc {
161
+ gauge .Inc ()
162
+ } else if metricSpec .Gauge .Dec {
163
+ gauge .Dec ()
164
+ } else if metricSpec .Gauge .Add != 0.0 {
165
+ gauge .Add (metricSpec .Gauge .Add )
166
+ } else if metricSpec .Gauge .Sub != 0.0 {
167
+ gauge .Sub (metricSpec .Gauge .Sub )
168
+ } else if metricSpec .Gauge .Set != 0.0 {
169
+ gauge .Set (metricSpec .Gauge .Set )
170
+ } else if metricSpec .Gauge .SetToCurrentTime {
171
+ gauge .SetToCurrentTime ()
172
+ }
173
+ return nil
174
+ }
175
+
176
+ func handleSummaryOrHistogram (metricSpec UserMetric , summary prometheus.Summary ) error {
177
+ if metricSpec .Histogram == nil && metricSpec .Summary == nil {
178
+ return fmt .Errorf ("cannot change metric type of %s, which is a histogram or summary" , metricSpec .Name )
179
+ }
180
+ if metricSpec .Histogram != nil {
181
+ summary .Observe (metricSpec .Histogram .Observe )
182
+ } else if metricSpec .Summary != nil {
183
+ summary .Observe (metricSpec .Summary .Observe )
184
+ }
185
+ return nil
186
+ }
187
+
188
+ func ensureMetric (r prometheus.Registerer , metricSpec UserMetric ) {
189
+ if _ , ok := userMetrics [metricSpec .Name ]; ! ok {
190
+ // This is the first time we've seen this metric
191
+ logf .Log .WithName ("metrics" ).Info ("Registering" , "metric" , metricSpec .Name )
192
+ if metricSpec .Counter != nil {
193
+ userMetrics [metricSpec .Name ] = prometheus .NewCounter (prometheus.CounterOpts {
194
+ Name : metricSpec .Name ,
195
+ Help : metricSpec .Help ,
196
+ })
197
+ }
198
+ if metricSpec .Gauge != nil {
199
+ userMetrics [metricSpec .Name ] = prometheus .NewGauge (prometheus.GaugeOpts {
200
+ Name : metricSpec .Name ,
201
+ Help : metricSpec .Help ,
202
+ })
203
+ }
204
+ if metricSpec .Histogram != nil {
205
+ userMetrics [metricSpec .Name ] = prometheus .NewHistogram (prometheus.HistogramOpts {
206
+ Name : metricSpec .Name ,
207
+ Help : metricSpec .Help ,
208
+ })
209
+ }
210
+ if metricSpec .Summary != nil {
211
+ userMetrics [metricSpec .Name ] = prometheus .NewSummary (prometheus.SummaryOpts {
212
+ Name : metricSpec .Name ,
213
+ Help : metricSpec .Help ,
214
+ })
215
+ }
216
+ if err := r .Register (userMetrics [metricSpec .Name ]); err != nil {
217
+ logf .Log .WithName ("metrics" ).Info ("Unable to register %s metric with prometheus." , metricSpec .Name )
218
+ }
219
+ }
220
+ }
221
+
222
+ func HandleUserMetric (r prometheus.Registerer , metricSpec UserMetric ) error {
223
+ if err := validateMetricSpec (metricSpec ); err != nil {
224
+ return err
225
+ }
226
+ ensureMetric (r , metricSpec )
227
+ collector := userMetrics [metricSpec .Name ]
228
+ switch v := collector .(type ) {
229
+ // Gauge must be first, because a Counter is a Gauge, but a Gauge is not a Counter.
230
+ case prometheus.Gauge :
231
+ if err := handleGauge (metricSpec , v ); err != nil {
232
+ return err
233
+ }
234
+ case prometheus.Counter :
235
+ if err := handleCounter (metricSpec , v ); err != nil {
236
+ return err
237
+ }
238
+ // Histogram and Summary interfaces are identical, so we accept either case.
239
+ case prometheus.Histogram :
240
+ if err := handleSummaryOrHistogram (metricSpec , v ); err != nil {
241
+ return err
242
+ }
243
+ }
244
+ return nil
245
+ }
246
+
85
247
func ReconcileSucceeded (gvk string ) {
86
248
defer recoverMetricPanic ()
87
249
reconcileResults .WithLabelValues (gvk , "succeeded" ).Inc ()
0 commit comments