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  }