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