120 lines
3.1 KiB
Go
120 lines
3.1 KiB
Go
|
// Copyright (c) 2017 Arista Networks, Inc.
|
||
|
// Use of this source code is governed by the Apache License 2.0
|
||
|
// that can be found in the COPYING file.
|
||
|
|
||
|
package main
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"regexp"
|
||
|
"strconv"
|
||
|
|
||
|
"github.com/prometheus/client_golang/prometheus"
|
||
|
"gopkg.in/yaml.v2"
|
||
|
)
|
||
|
|
||
|
// Config is the representation of ocprometheus's YAML config file.
|
||
|
type Config struct {
|
||
|
// Per-device labels.
|
||
|
DeviceLabels map[string]prometheus.Labels
|
||
|
|
||
|
// Prefixes to subscribe to.
|
||
|
Subscriptions []string
|
||
|
|
||
|
// Metrics to collect and how to munge them.
|
||
|
Metrics []*MetricDef
|
||
|
}
|
||
|
|
||
|
// MetricDef is the representation of a metric definiton in the config file.
|
||
|
type MetricDef struct {
|
||
|
// Path is a regexp to match on the Update's full path.
|
||
|
// The regexp must be a prefix match.
|
||
|
// The regexp can define named capture groups to use as labels.
|
||
|
Path string
|
||
|
|
||
|
// Path compiled as a regexp.
|
||
|
re *regexp.Regexp
|
||
|
|
||
|
// Metric name.
|
||
|
Name string
|
||
|
|
||
|
// Metric help string.
|
||
|
Help string
|
||
|
|
||
|
// This map contains the metric descriptors for this metric for each device.
|
||
|
devDesc map[string]*prometheus.Desc
|
||
|
|
||
|
// This is the default metric descriptor for devices that don't have explicit descs.
|
||
|
desc *prometheus.Desc
|
||
|
}
|
||
|
|
||
|
// Parses the config and creates the descriptors for each path and device.
|
||
|
func parseConfig(cfg []byte) (*Config, error) {
|
||
|
config := &Config{
|
||
|
DeviceLabels: make(map[string]prometheus.Labels),
|
||
|
}
|
||
|
if err := yaml.Unmarshal(cfg, config); err != nil {
|
||
|
return nil, fmt.Errorf("Failed to parse config: %v", err)
|
||
|
}
|
||
|
|
||
|
for _, def := range config.Metrics {
|
||
|
def.re = regexp.MustCompile(def.Path)
|
||
|
|
||
|
// Extract label names
|
||
|
reNames := def.re.SubexpNames()[1:]
|
||
|
labelNames := make([]string, len(reNames))
|
||
|
for i, n := range reNames {
|
||
|
labelNames[i] = n
|
||
|
if n == "" {
|
||
|
labelNames[i] = "unnamedLabel" + strconv.Itoa(i+1)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Create a default descriptor only if there aren't any per-device labels,
|
||
|
// or if it's explicitly declared
|
||
|
if len(config.DeviceLabels) == 0 || len(config.DeviceLabels["*"]) > 0 {
|
||
|
def.desc = prometheus.NewDesc(def.Name, def.Help, labelNames, config.DeviceLabels["*"])
|
||
|
}
|
||
|
// Add per-device descriptors
|
||
|
def.devDesc = make(map[string]*prometheus.Desc)
|
||
|
for device, labels := range config.DeviceLabels {
|
||
|
if device == "*" {
|
||
|
continue
|
||
|
}
|
||
|
def.devDesc[device] = prometheus.NewDesc(def.Name, def.Help, labelNames, labels)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return config, nil
|
||
|
}
|
||
|
|
||
|
// Returns the descriptor corresponding to the device and path, and labels extracted from the path.
|
||
|
// If the device and path doesn't match any metrics, returns nil.
|
||
|
func (c *Config) getDescAndLabels(s source) (*prometheus.Desc, []string) {
|
||
|
for _, def := range c.Metrics {
|
||
|
if groups := def.re.FindStringSubmatch(s.path); groups != nil {
|
||
|
if desc, ok := def.devDesc[s.addr]; ok {
|
||
|
return desc, groups[1:]
|
||
|
}
|
||
|
|
||
|
return def.desc, groups[1:]
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
// Sends all the descriptors to the channel.
|
||
|
func (c *Config) getAllDescs(ch chan<- *prometheus.Desc) {
|
||
|
for _, def := range c.Metrics {
|
||
|
// Default descriptor might not be present
|
||
|
if def.desc != nil {
|
||
|
ch <- def.desc
|
||
|
}
|
||
|
|
||
|
for _, desc := range def.devDesc {
|
||
|
ch <- desc
|
||
|
}
|
||
|
}
|
||
|
}
|