-
Notifications
You must be signed in to change notification settings - Fork 59
/
metrics.go
125 lines (110 loc) · 3.36 KB
/
metrics.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
// Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package charm
import (
"fmt"
"io"
"io/ioutil"
"strconv"
"strings"
goyaml "gopkg.in/yaml.v2"
)
// MetricType is used to identify metric types supported by juju.
type MetricType string
const (
builtinMetricPrefix = "juju"
// Supported metric types.
MetricTypeGauge MetricType = "gauge"
MetricTypeAbsolute MetricType = "absolute"
)
// IsBuiltinMetric reports whether the given metric key is in the builtin metric namespace
func IsBuiltinMetric(key string) bool {
return strings.HasPrefix(key, builtinMetricPrefix)
}
func validateValue(value string) error {
// The largest number of digits that can be returned by strconv.FormatFloat is 24, so
// choose an arbitrary limit somewhat higher than that.
if len(value) > 30 {
return fmt.Errorf("metric value is too large")
}
fValue, err := strconv.ParseFloat(value, 64)
if err != nil {
return fmt.Errorf("invalid value type: expected float, got %q", value)
}
if fValue < 0 {
return fmt.Errorf("invalid value: value must be greater or equal to zero, got %v", value)
}
return nil
}
// validateValue checks if the supplied metric value fits the requirements
// of its expected type.
func (m MetricType) validateValue(value string) error {
switch m {
case MetricTypeGauge, MetricTypeAbsolute:
return validateValue(value)
default:
return fmt.Errorf("unknown metric type %q", m)
}
}
// Metric represents a single metric definition
type Metric struct {
Type MetricType `yaml:"type"`
Description string `yaml:"description"`
}
// Plan represents the plan section of metrics.yaml
type Plan struct {
Required bool `yaml:"required,omitempty"`
}
// Metrics contains the metrics declarations encoded in the metrics.yaml
// file.
type Metrics struct {
Metrics map[string]Metric `yaml:"metrics"`
Plan *Plan `yaml:"plan,omitempty"`
}
// ReadMetrics reads a MetricsDeclaration in YAML format.
func ReadMetrics(r io.Reader) (*Metrics, error) {
data, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}
var metrics Metrics
if err := goyaml.Unmarshal(data, &metrics); err != nil {
return nil, err
}
if metrics.Metrics == nil {
return &metrics, nil
}
for name, metric := range metrics.Metrics {
if IsBuiltinMetric(name) {
if metric.Type != MetricType("") || metric.Description != "" {
return nil, fmt.Errorf("metric %q is using a prefix reserved for built-in metrics: it should not have type or description specification", name)
}
continue
}
switch metric.Type {
case MetricTypeGauge, MetricTypeAbsolute:
default:
return nil, fmt.Errorf("invalid metrics declaration: metric %q has unknown type %q", name, metric.Type)
}
if metric.Description == "" {
return nil, fmt.Errorf("invalid metrics declaration: metric %q lacks description", name)
}
}
return &metrics, nil
}
// ValidateMetric validates the supplied metric name and value against the loaded
// metric definitions.
func (m Metrics) ValidateMetric(name, value string) error {
metric, exists := m.Metrics[name]
if !exists {
return fmt.Errorf("metric %q not defined", name)
}
if IsBuiltinMetric(name) {
return validateValue(value)
}
return metric.Type.validateValue(value)
}
// PlanRequired reports whether these metrics require a plan.
func (m Metrics) PlanRequired() bool {
return m.Plan != nil && m.Plan.Required
}