Skip to content

Commit 788aefc

Browse files
committed
metrics: set additional labels via wrapper
This adds error checking for the additional values and keeps RecordMetrics itself simple (no additional value parameter, no error return code).
1 parent 9f02fb6 commit 788aefc

File tree

2 files changed

+371
-10
lines changed

2 files changed

+371
-10
lines changed

metrics/metrics.go

Lines changed: 89 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,22 @@ type CSIMetricsManager interface {
6767
// operationName - Name of the CSI operation.
6868
// operationErr - Error, if any, that resulted from execution of operation.
6969
// operationDuration - time it took for the operation to complete
70-
// labelValues - for each additional label that was defined in WithLabels(), one value has to be passed (usually none)
70+
//
71+
// If WithLabelNames was used to define additional labels when constructing
72+
// the manager, then WithLabelValues should be used to create a wrapper which
73+
// holds the corresponding values before calling RecordMetrics of the wrapper.
74+
// Labels with missing values are recorded as empty.
7175
RecordMetrics(
7276
operationName string,
7377
operationErr error,
74-
operationDuration time.Duration,
75-
labelValues ...string)
78+
operationDuration time.Duration)
79+
80+
// WithLabelValues must be used to add the additional label
81+
// values defined via WithLabelNames. When calling RecordMetrics
82+
// without it or with too few values, the missing values are
83+
// recorded as empty. WithLabelValues can be called multiple times
84+
// and then accumulates values.
85+
WithLabelValues(labels map[string]string) (CSIMetricsManager, error)
7686

7787
// SetDriverName is called to update the CSI driver name. This should be done
7888
// as soon as possible, otherwise metrics recorded by this manager will be
@@ -108,6 +118,9 @@ func WithStabilityLevel(stabilityLevel metrics.StabilityLevel) MetricsManagerOpt
108118
// default labels (driver, method call, and gRPC result). This makes
109119
// it possible to partition the histograms along additional
110120
// dimensions.
121+
//
122+
// To record a metrics with additional values, use
123+
// CSIMetricManager.WithLabelValues().RecordMetrics().
111124
func WithLabelNames(labelNames ...string) MetricsManagerOption {
112125
return func(cmm *csiMetricsManager) {
113126
cmm.additionalLabelNames = labelNames
@@ -208,25 +221,91 @@ func (cmm *csiMetricsManager) GetRegistry() metrics.KubeRegistry {
208221
return cmm.registry
209222
}
210223

211-
// RecordMetrics must be called upon CSI Operation completion to record
212-
// the operation's metric.
213-
// operationName - Name of the CSI operation.
214-
// operationErr - Error, if any, that resulted from execution of operation.
215-
// operationDuration - time it took for the operation to complete
216-
// labelValues - for each additional label that was defined in WithLabels(), one value has to be passed (usually none)
224+
// RecordMetrics implements CSIMetricsManager.RecordMetrics.
217225
func (cmm *csiMetricsManager) RecordMetrics(
226+
operationName string,
227+
operationErr error,
228+
operationDuration time.Duration) {
229+
cmm.recordMetricsWithLabels(operationName, operationErr, operationDuration)
230+
}
231+
232+
// recordMetricsWithLabels is the internal implementation of RecordMetrics.
233+
func (cmm *csiMetricsManager) recordMetricsWithLabels(
218234
operationName string,
219235
operationErr error,
220236
operationDuration time.Duration,
221237
labelValues ...string) {
222238
values := []string{cmm.driverName, operationName, getErrorCode(operationErr)}
223-
values = append(values, labelValues...)
239+
toAdd := len(labelValues)
240+
if toAdd > len(cmm.additionalLabelNames) {
241+
// To many labels?! Truncate. Shouldn't happen because of
242+
// error checking in WithLabelValues.
243+
toAdd = len(cmm.additionalLabelNames)
244+
}
245+
values = append(values, labelValues[0:toAdd]...)
246+
for i := toAdd; i < len(cmm.additionalLabelNames); i++ {
247+
// Backfill missing values with empty string.
248+
values = append(values, "")
249+
}
224250
for _, label := range cmm.additionalLabels {
225251
values = append(values, label.value)
226252
}
227253
cmm.csiOperationsLatencyMetric.WithLabelValues(values...).Observe(operationDuration.Seconds())
228254
}
229255

256+
type csiMetricsManagerWithValues struct {
257+
*csiMetricsManager
258+
259+
// additionalValues holds the values passed via WithLabelValues.
260+
additionalValues []string
261+
}
262+
263+
// WithLabelValues in the base metrics manager creates a fresh wrapper with no labels and let's
264+
// that deal with adding the label values.
265+
func (cmm *csiMetricsManager) WithLabelValues(labels map[string]string) (CSIMetricsManager, error) {
266+
cmmv := &csiMetricsManagerWithValues{csiMetricsManager: cmm}
267+
return cmmv.WithLabelValues(labels)
268+
}
269+
270+
// WithLabelValues in the wrapper creates a wrapper which has all existing labels and
271+
// adds the new ones, with error checking.
272+
func (cmmv *csiMetricsManagerWithValues) WithLabelValues(labels map[string]string) (CSIMetricsManager, error) {
273+
extended := &csiMetricsManagerWithValues{cmmv.csiMetricsManager, append([]string{}, cmmv.additionalValues...)}
274+
275+
for name := range labels {
276+
if !cmmv.haveAdditionalLabel(name) {
277+
return nil, fmt.Errorf("label %q was not defined via WithLabelNames", name)
278+
}
279+
}
280+
// Add in same order as in the label definition.
281+
for _, name := range cmmv.additionalLabelNames {
282+
if value, ok := labels[name]; ok {
283+
if len(cmmv.additionalValues) >= len(extended.additionalLabelNames) {
284+
return nil, fmt.Errorf("label %q = %q cannot be added, all labels already have values %v", name, value, cmmv.additionalValues)
285+
}
286+
extended.additionalValues = append(extended.additionalValues, value)
287+
}
288+
}
289+
return extended, nil
290+
}
291+
292+
func (cmm *csiMetricsManager) haveAdditionalLabel(name string) bool {
293+
for _, n := range cmm.additionalLabelNames {
294+
if n == name {
295+
return true
296+
}
297+
}
298+
return false
299+
}
300+
301+
// RecordMetrics passes the stored values as to the implementation.
302+
func (cmmv *csiMetricsManagerWithValues) RecordMetrics(
303+
operationName string,
304+
operationErr error,
305+
operationDuration time.Duration) {
306+
cmmv.recordMetricsWithLabels(operationName, operationErr, operationDuration, cmmv.additionalValues...)
307+
}
308+
230309
// SetDriverName is called to update the CSI driver name. This should be done
231310
// as soon as possible, otherwise metrics recorded by this manager will be
232311
// recorded with an "unknown-driver" driver_name.

0 commit comments

Comments
 (0)