diff --git a/collectors.go b/collectors.go index 16bd9a8e..ee4105cf 100644 --- a/collectors.go +++ b/collectors.go @@ -3,6 +3,8 @@ package main import ( + "regexp" + "github.com/czerwonk/junos_exporter/internal/config" "github.com/czerwonk/junos_exporter/pkg/collector" "github.com/czerwonk/junos_exporter/pkg/connector" @@ -44,13 +46,13 @@ import ( type collectors struct { logicalSystem string - dynamicLabels *interfacelabels.DynamicLabels + dynamicLabels *interfacelabels.DynamicLabelManager collectors map[string]collector.RPCCollector devices map[string][]collector.RPCCollector cfg *config.Config } -func collectorsForDevices(devices []*connector.Device, cfg *config.Config, logicalSystem string, dynamicLabels *interfacelabels.DynamicLabels) *collectors { +func collectorsForDevices(devices []*connector.Device, cfg *config.Config, logicalSystem string, dynamicLabels *interfacelabels.DynamicLabelManager) *collectors { c := &collectors{ logicalSystem: logicalSystem, dynamicLabels: dynamicLabels, @@ -60,13 +62,13 @@ func collectorsForDevices(devices []*connector.Device, cfg *config.Config, logic } for _, d := range devices { - c.initCollectorsForDevices(d) + c.initCollectorsForDevices(d, cfg.IfDescReg) } return c } -func (c *collectors) initCollectorsForDevices(device *connector.Device) { +func (c *collectors) initCollectorsForDevices(device *connector.Device, bgpDescRe *regexp.Regexp) { f := c.cfg.FeaturesForDevice(device.Host) c.devices[device.Host] = make([]collector.RPCCollector, 0) @@ -78,7 +80,7 @@ func (c *collectors) initCollectorsForDevices(device *connector.Device) { }) c.addCollectorIfEnabledForDevice(device, "bfd", f.BFD, bfd.NewCollector) c.addCollectorIfEnabledForDevice(device, "bgp", f.BGP, func() collector.RPCCollector { - return bgp.NewCollector(c.logicalSystem) + return bgp.NewCollector(c.logicalSystem, bgpDescRe) }) c.addCollectorIfEnabledForDevice(device, "env", f.Environment, environment.NewCollector) c.addCollectorIfEnabledForDevice(device, "firewall", f.Firewall, firewall.NewCollector) diff --git a/collectors_test.go b/collectors_test.go index 924e1e69..b5a5d9fd 100644 --- a/collectors_test.go +++ b/collectors_test.go @@ -40,7 +40,7 @@ func TestCollectorsRegistered(t *testing.T) { cols := collectorsForDevices([]*connector.Device{{ Host: "::1", - }}, c, "", interfacelabels.NewDynamicLabels()) + }}, c, "", interfacelabels.NewDynamicLabelManager()) assert.Equal(t, 20, len(cols.collectors), "collector count") } @@ -88,7 +88,7 @@ func TestCollectorsForDevices(t *testing.T) { d2 := &connector.Device{ Host: "2001:678:1e0::2", } - cols := collectorsForDevices([]*connector.Device{d1, d2}, c, "", interfacelabels.NewDynamicLabels()) + cols := collectorsForDevices([]*connector.Device{d1, d2}, c, "", interfacelabels.NewDynamicLabelManager()) assert.Equal(t, 20, len(cols.collectorsForDevice(d1)), "device 1 collector count") diff --git a/devices.go b/devices.go index ca6c55c1..9cbf8e19 100644 --- a/devices.go +++ b/devices.go @@ -3,6 +3,7 @@ package main import ( + "fmt" "os" "regexp" "strings" @@ -56,10 +57,15 @@ func deviceFromDeviceConfig(device *config.DeviceConfig, hostname string, cfg *c } // check whether there is a device specific regex otherwise fallback to global regex - if len(device.IfDescReg) == 0 { + if len(device.IfDescRegStr) == 0 { device.IfDescReg = cfg.IfDescReg } else { - regexp.MustCompile(device.IfDescReg) + re, err := regexp.Compile(device.IfDescRegStr) + if err != nil { + return nil, fmt.Errorf("unable to compile device description regex for %q: %q: %w", hostname, device.IfDescRegStr, err) + } + + device.IfDescReg = re } return &connector.Device{ diff --git a/internal/config/config.go b/internal/config/config.go index d7b30f59..945d3481 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -3,6 +3,7 @@ package config import ( + "fmt" "io" "regexp" @@ -11,12 +12,26 @@ import ( // Config represents the configuration for the exporter type Config struct { - Password string `yaml:"password"` - Targets []string `yaml:"targets,omitempty"` - Devices []*DeviceConfig `yaml:"devices,omitempty"` - Features FeatureConfig `yaml:"features,omitempty"` - LSEnabled bool `yaml:"logical_systems,omitempty"` - IfDescReg string `yaml:"interface_description_regex,omitempty"` + Password string `yaml:"password"` + Targets []string `yaml:"targets,omitempty"` + Devices []*DeviceConfig `yaml:"devices,omitempty"` + Features FeatureConfig `yaml:"features,omitempty"` + LSEnabled bool `yaml:"logical_systems,omitempty"` + IfDescReStr string `yaml:"interface_description_regex,omitempty"` + IfDescReg *regexp.Regexp `yaml:"-"` +} + +func (c *Config) load() error { + if c.IfDescReStr != "" { + re, err := regexp.Compile(c.IfDescReStr) + if err != nil { + return fmt.Errorf("unable to compile interfce description regex %q: %w", c.IfDescReStr, err) + } + + c.IfDescReg = re + } + + return nil } // DeviceConfig is the config representation of 1 device @@ -27,7 +42,8 @@ type DeviceConfig struct { KeyFile string `yaml:"key_file,omitempty"` KeyPassphrase string `yaml:"key_passphrase,omitempty"` Features *FeatureConfig `yaml:"features,omitempty"` - IfDescReg string `yaml:"interface_description_regex,omitempty"` + IfDescRegStr string `yaml:"interface_description_regex,omitempty"` + IfDescReg *regexp.Regexp `yaml:"-"` IsHostPattern bool `yaml:"host_pattern,omitempty"` HostPattern *regexp.Regexp } @@ -94,6 +110,11 @@ func Load(reader io.Reader) (*Config, error) { return nil, err } + err = c.load() + if err != nil { + return nil, err + } + for _, device := range c.Devices { if device.IsHostPattern { hostPattern, err := regexp.Compile(device.Host) @@ -110,7 +131,6 @@ func Load(reader io.Reader) (*Config, error) { func setDefaultValues(c *Config) { c.Password = "" c.LSEnabled = false - c.IfDescReg = "" f := &c.Features f.Alarm = true f.BGP = true diff --git a/junos_collector.go b/junos_collector.go index 907f936d..66d5a2c4 100644 --- a/junos_collector.go +++ b/junos_collector.go @@ -40,7 +40,7 @@ type junosCollector struct { } func newJunosCollector(ctx context.Context, devices []*connector.Device, logicalSystem string) *junosCollector { - l := interfacelabels.NewDynamicLabels() + l := interfacelabels.NewDynamicLabelManager() clients := make(map[*connector.Device]*rpc.Client) @@ -78,22 +78,13 @@ func newJunosCollector(ctx context.Context, devices []*connector.Device, logical func deviceInterfaceRegex(host string) *regexp.Regexp { dc := cfg.FindDeviceConfig(host) - if len(dc.IfDescReg) > 0 { - regex, err := regexp.Compile(dc.IfDescReg) + if len(dc.IfDescRegStr) > 0 { + regex, err := regexp.Compile(dc.IfDescRegStr) if err == nil { return regex } - log.Errorf("device specific dynamic label regex %s invalid: %v", dc.IfDescReg, err) - } - - if len(cfg.IfDescReg) > 0 { - regex, err := regexp.Compile(cfg.IfDescReg) - if err == nil { - return regex - } - - log.Errorf("global dynamic label regex (%s) invalid: %v", cfg.IfDescReg, err) + log.Errorf("device specific dynamic label regex %s invalid: %v", dc.IfDescRegStr, err) } return interfacelabels.DefaultInterfaceDescRegex() diff --git a/main.go b/main.go index 06e79ffe..8d112108 100644 --- a/main.go +++ b/main.go @@ -212,7 +212,7 @@ func loadConfigFromFlags() *config.Config { c := config.New() c.Targets = strings.Split(*sshHosts, ",") c.LSEnabled = *lsEnabled - c.IfDescReg = *interfaceDescriptionRegex + c.IfDescReStr = *interfaceDescriptionRegex f := &c.Features f.Alarm = *alarmEnabled diff --git a/pkg/features/bgp/collector.go b/pkg/features/bgp/collector.go index 8e5c1999..3d5e47ce 100644 --- a/pkg/features/bgp/collector.go +++ b/pkg/features/bgp/collector.go @@ -5,8 +5,10 @@ package bgp import ( "fmt" "math" + "regexp" "github.com/czerwonk/junos_exporter/pkg/collector" + "github.com/czerwonk/junos_exporter/pkg/interfacelabels" "github.com/prometheus/client_golang/prometheus" "strings" @@ -14,7 +16,7 @@ import ( const prefix string = "junos_bgp_session_" -var ( +type description struct { upDesc *prometheus.Desc stateDesc *prometheus.Desc receivedPrefixesDesc *prometheus.Desc @@ -31,42 +33,51 @@ var ( medDesc *prometheus.Desc preferenceDesc *prometheus.Desc holdTimeDesc *prometheus.Desc -) +} + +func newDescriptions(dynLabels interfacelabels.InterfaceLabels) *description { + d := &description{} -func init() { l := []string{"target", "asn", "ip", "description", "group"} - upDesc = prometheus.NewDesc(prefix+"up", "Session is up (1 = Established)", l, nil) - stateDesc = prometheus.NewDesc(prefix+"state", "State of the bgp Session (1 = Active, 2 = Connect, 3 = Established, 4 = Idle, 5 = OpenConfirm, 6 = OpenSent, 7 = route reflector client, 0 = Other)", l, nil) - inputMessagesDesc = prometheus.NewDesc(prefix+"messages_input_count", "Number of received messages", l, nil) - outputMessagesDesc = prometheus.NewDesc(prefix+"messages_output_count", "Number of transmitted messages", l, nil) - flapsDesc = prometheus.NewDesc(prefix+"flap_count", "Number of session flaps", l, nil) - medDesc = prometheus.NewDesc(prefix+"metric_out", "MED configured for the session", l, nil) - preferenceDesc = prometheus.NewDesc(prefix+"preference", "Preference configured for the session", l, nil) - holdTimeDesc = prometheus.NewDesc(prefix+"hold_time_seconds", "Hold time configured for the session", l, nil) + l = append(l, dynLabels.Keys()...) + d.upDesc = prometheus.NewDesc(prefix+"up", "Session is up (1 = Established)", l, nil) + d.stateDesc = prometheus.NewDesc(prefix+"state", "State of the bgp Session (1 = Active, 2 = Connect, 3 = Established, 4 = Idle, 5 = OpenConfirm, 6 = OpenSent, 7 = route reflector client, 0 = Other)", l, nil) + d.inputMessagesDesc = prometheus.NewDesc(prefix+"messages_input_count", "Number of received messages", l, nil) + d.outputMessagesDesc = prometheus.NewDesc(prefix+"messages_output_count", "Number of transmitted messages", l, nil) + d.flapsDesc = prometheus.NewDesc(prefix+"flap_count", "Number of session flaps", l, nil) + d.medDesc = prometheus.NewDesc(prefix+"metric_out", "MED configured for the session", l, nil) + d.preferenceDesc = prometheus.NewDesc(prefix+"preference", "Preference configured for the session", l, nil) + d.holdTimeDesc = prometheus.NewDesc(prefix+"hold_time_seconds", "Hold time configured for the session", l, nil) infoLabels := append(l, "local_as", "import_policy", "export_policy", "options") - infoDesc = prometheus.NewDesc(prefix+"info", "Information about the session (e.g. configuration)", infoLabels, nil) + d.infoDesc = prometheus.NewDesc(prefix+"info", "Information about the session (e.g. configuration)", infoLabels, nil) l = append(l, "table") - receivedPrefixesDesc = prometheus.NewDesc(prefix+"prefixes_received_count", "Number of received prefixes", l, nil) - acceptedPrefixesDesc = prometheus.NewDesc(prefix+"prefixes_accepted_count", "Number of accepted prefixes", l, nil) - rejectedPrefixesDesc = prometheus.NewDesc(prefix+"prefixes_rejected_count", "Number of rejected prefixes", l, nil) - activePrefixesDesc = prometheus.NewDesc(prefix+"prefixes_active_count", "Number of active prefixes (best route in RIB)", l, nil) - advertisedPrefixesDesc = prometheus.NewDesc(prefix+"prefixes_advertised_count", "Number of prefixes announced to peer", l, nil) - prefixesLimitPercentageDesc = prometheus.NewDesc(prefix+"prefixes_limit_percentage", "percentage of received prefixes against prefix-limit", l, nil) - prefixesLimitCountDesc = prometheus.NewDesc(prefix+"prefixes_limit_count", "prefix-count variable set in prefix-limit", l, nil) + d.receivedPrefixesDesc = prometheus.NewDesc(prefix+"prefixes_received_count", "Number of received prefixes", l, nil) + d.acceptedPrefixesDesc = prometheus.NewDesc(prefix+"prefixes_accepted_count", "Number of accepted prefixes", l, nil) + d.rejectedPrefixesDesc = prometheus.NewDesc(prefix+"prefixes_rejected_count", "Number of rejected prefixes", l, nil) + d.activePrefixesDesc = prometheus.NewDesc(prefix+"prefixes_active_count", "Number of active prefixes (best route in RIB)", l, nil) + d.advertisedPrefixesDesc = prometheus.NewDesc(prefix+"prefixes_advertised_count", "Number of prefixes announced to peer", l, nil) + d.prefixesLimitPercentageDesc = prometheus.NewDesc(prefix+"prefixes_limit_percentage", "percentage of received prefixes against prefix-limit", l, nil) + d.prefixesLimitCountDesc = prometheus.NewDesc(prefix+"prefixes_limit_count", "prefix-count variable set in prefix-limit", l, nil) + + return d } type bgpCollector struct { LogicalSystem string + descriptionRe *regexp.Regexp } type groupMap map[int64]group // NewCollector creates a new collector -func NewCollector(logicalSystem string) collector.RPCCollector { - return &bgpCollector{LogicalSystem: logicalSystem} +func NewCollector(logicalSystem string, descRe *regexp.Regexp) collector.RPCCollector { + return &bgpCollector{ + LogicalSystem: logicalSystem, + descriptionRe: descRe, + } } // Name returns the name of the collector @@ -76,21 +87,22 @@ func (*bgpCollector) Name() string { // Describe describes the metrics func (*bgpCollector) Describe(ch chan<- *prometheus.Desc) { - ch <- upDesc - ch <- receivedPrefixesDesc - ch <- acceptedPrefixesDesc - ch <- rejectedPrefixesDesc - ch <- activePrefixesDesc - ch <- advertisedPrefixesDesc - ch <- inputMessagesDesc - ch <- outputMessagesDesc - ch <- flapsDesc - ch <- prefixesLimitCountDesc - ch <- prefixesLimitPercentageDesc - ch <- infoDesc - ch <- medDesc - ch <- preferenceDesc - ch <- holdTimeDesc + d := newDescriptions(nil) + ch <- d.upDesc + ch <- d.receivedPrefixesDesc + ch <- d.acceptedPrefixesDesc + ch <- d.rejectedPrefixesDesc + ch <- d.activePrefixesDesc + ch <- d.advertisedPrefixesDesc + ch <- d.inputMessagesDesc + ch <- d.outputMessagesDesc + ch <- d.flapsDesc + ch <- d.prefixesLimitCountDesc + ch <- d.prefixesLimitPercentageDesc + ch <- d.infoDesc + ch <- d.medDesc + ch <- d.preferenceDesc + ch <- d.holdTimeDesc } // Collect collects metrics from JunOS @@ -151,7 +163,7 @@ func (c *bgpCollector) collect(client collector.Client, ch chan<- prometheus.Met func (c *bgpCollector) collectForPeer(p peer, groups groupMap, ch chan<- prometheus.Metric, labelValues []string) { ip := strings.Split(p.IP, "+") - l := append(labelValues, []string{ + lv := append(labelValues, []string{ p.ASN, ip[0], p.Description, @@ -162,26 +174,31 @@ func (c *bgpCollector) collectForPeer(p peer, groups groupMap, ch chan<- prometh up = 1 } - ch <- prometheus.MustNewConstMetric(upDesc, prometheus.GaugeValue, float64(up), l...) - ch <- prometheus.MustNewConstMetric(stateDesc, prometheus.GaugeValue, bgpStateToNumber(p.State), l...) - ch <- prometheus.MustNewConstMetric(inputMessagesDesc, prometheus.GaugeValue, float64(p.InputMessages), l...) - ch <- prometheus.MustNewConstMetric(outputMessagesDesc, prometheus.GaugeValue, float64(p.OutputMessages), l...) - ch <- prometheus.MustNewConstMetric(flapsDesc, prometheus.GaugeValue, float64(p.Flaps), l...) - ch <- prometheus.MustNewConstMetric(preferenceDesc, prometheus.GaugeValue, float64(p.OptionInformation.Preference), l...) - ch <- prometheus.MustNewConstMetric(medDesc, prometheus.GaugeValue, float64(p.OptionInformation.MetricOut), l...) - ch <- prometheus.MustNewConstMetric(holdTimeDesc, prometheus.GaugeValue, float64(p.OptionInformation.Holdtime), l...) + dynLabels := interfacelabels.ParseDescription(p.Description, c.descriptionRe) + lv = append(lv, dynLabels.Values()...) + + d := newDescriptions(dynLabels) + + ch <- prometheus.MustNewConstMetric(d.upDesc, prometheus.GaugeValue, float64(up), lv...) + ch <- prometheus.MustNewConstMetric(d.stateDesc, prometheus.GaugeValue, bgpStateToNumber(p.State), lv...) + ch <- prometheus.MustNewConstMetric(d.inputMessagesDesc, prometheus.GaugeValue, float64(p.InputMessages), lv...) + ch <- prometheus.MustNewConstMetric(d.outputMessagesDesc, prometheus.GaugeValue, float64(p.OutputMessages), lv...) + ch <- prometheus.MustNewConstMetric(d.flapsDesc, prometheus.GaugeValue, float64(p.Flaps), lv...) + ch <- prometheus.MustNewConstMetric(d.preferenceDesc, prometheus.GaugeValue, float64(p.OptionInformation.Preference), lv...) + ch <- prometheus.MustNewConstMetric(d.medDesc, prometheus.GaugeValue, float64(p.OptionInformation.MetricOut), lv...) + ch <- prometheus.MustNewConstMetric(d.holdTimeDesc, prometheus.GaugeValue, float64(p.OptionInformation.Holdtime), lv...) - infoValues := append(l, + infoValues := append(lv, localASNForPeer(p), formatPolicy(p.OptionInformation.ImportPolicy), formatPolicy(p.OptionInformation.ExportPolicy), p.OptionInformation.Options) - ch <- prometheus.MustNewConstMetric(infoDesc, prometheus.GaugeValue, 1, infoValues...) + ch <- prometheus.MustNewConstMetric(d.infoDesc, prometheus.GaugeValue, 1, infoValues...) - c.collectRIBForPeer(p, ch, l) + c.collectRIBForPeer(p, ch, lv, d) } -func (*bgpCollector) collectRIBForPeer(p peer, ch chan<- prometheus.Metric, labelValues []string) { +func (*bgpCollector) collectRIBForPeer(p peer, ch chan<- prometheus.Metric, labelValues []string, d *description) { var rib_name string // derive the name of the rib for which the prefix limit is configured by examining the NLRI type @@ -200,21 +217,21 @@ func (*bgpCollector) collectRIBForPeer(p peer, ch chan<- prometheus.Metric, labe } if p.OptionInformation.PrefixLimit.PrefixCount > 0 { - ch <- prometheus.MustNewConstMetric(prefixesLimitCountDesc, prometheus.GaugeValue, float64(p.OptionInformation.PrefixLimit.PrefixCount), append(labelValues, rib_name)...) + ch <- prometheus.MustNewConstMetric(d.prefixesLimitCountDesc, prometheus.GaugeValue, float64(p.OptionInformation.PrefixLimit.PrefixCount), append(labelValues, rib_name)...) } for _, rib := range p.RIBs { l := append(labelValues, rib.Name) - ch <- prometheus.MustNewConstMetric(receivedPrefixesDesc, prometheus.GaugeValue, float64(rib.ReceivedPrefixes), l...) - ch <- prometheus.MustNewConstMetric(acceptedPrefixesDesc, prometheus.GaugeValue, float64(rib.AcceptedPrefixes), l...) - ch <- prometheus.MustNewConstMetric(rejectedPrefixesDesc, prometheus.GaugeValue, float64(rib.RejectedPrefixes), l...) - ch <- prometheus.MustNewConstMetric(activePrefixesDesc, prometheus.GaugeValue, float64(rib.ActivePrefixes), l...) - ch <- prometheus.MustNewConstMetric(advertisedPrefixesDesc, prometheus.GaugeValue, float64(rib.AdvertisedPrefixes), l...) + ch <- prometheus.MustNewConstMetric(d.receivedPrefixesDesc, prometheus.GaugeValue, float64(rib.ReceivedPrefixes), l...) + ch <- prometheus.MustNewConstMetric(d.acceptedPrefixesDesc, prometheus.GaugeValue, float64(rib.AcceptedPrefixes), l...) + ch <- prometheus.MustNewConstMetric(d.rejectedPrefixesDesc, prometheus.GaugeValue, float64(rib.RejectedPrefixes), l...) + ch <- prometheus.MustNewConstMetric(d.activePrefixesDesc, prometheus.GaugeValue, float64(rib.ActivePrefixes), l...) + ch <- prometheus.MustNewConstMetric(d.advertisedPrefixesDesc, prometheus.GaugeValue, float64(rib.AdvertisedPrefixes), l...) if rib.Name == rib_name { if p.OptionInformation.PrefixLimit.PrefixCount > 0 { prefixesLimitPercent := float64(rib.ReceivedPrefixes) / float64(p.OptionInformation.PrefixLimit.PrefixCount) - ch <- prometheus.MustNewConstMetric(prefixesLimitPercentageDesc, prometheus.GaugeValue, math.Round(prefixesLimitPercent*100)/100, l...) + ch <- prometheus.MustNewConstMetric(d.prefixesLimitPercentageDesc, prometheus.GaugeValue, math.Round(prefixesLimitPercent*100)/100, l...) } } } diff --git a/pkg/features/interfacediagnostics/collector.go b/pkg/features/interfacediagnostics/collector.go index a1f32cd9..77056b40 100644 --- a/pkg/features/interfacediagnostics/collector.go +++ b/pkg/features/interfacediagnostics/collector.go @@ -17,7 +17,7 @@ import ( const prefix = "junos_interface_diagnostics_" type interfaceDiagnosticsCollector struct { - labels *interfacelabels.DynamicLabels + labels *interfacelabels.DynamicLabelManager laserBiasCurrentDesc *prometheus.Desc laserBiasCurrentHighAlarmThresholdDesc *prometheus.Desc laserBiasCurrentLowAlarmThresholdDesc *prometheus.Desc @@ -67,7 +67,7 @@ type interfaceDiagnosticsCollector struct { } // NewCollector creates a new collector -func NewCollector(labels *interfacelabels.DynamicLabels) collector.RPCCollector { +func NewCollector(labels *interfacelabels.DynamicLabelManager) collector.RPCCollector { c := &interfaceDiagnosticsCollector{ labels: labels, } diff --git a/pkg/features/interfacequeue/collector.go b/pkg/features/interfacequeue/collector.go index 4f8c6ccc..87a87786 100644 --- a/pkg/features/interfacequeue/collector.go +++ b/pkg/features/interfacequeue/collector.go @@ -12,7 +12,7 @@ import ( const prefix = "junos_interface_queues_" // NewCollector creates an queue collector instance -func NewCollector(labels *interfacelabels.DynamicLabels) collector.RPCCollector { +func NewCollector(labels *interfacelabels.DynamicLabelManager) collector.RPCCollector { c := &interfaceQueueCollector{ labels: labels, } @@ -22,7 +22,7 @@ func NewCollector(labels *interfacelabels.DynamicLabels) collector.RPCCollector } type interfaceQueueCollector struct { - labels *interfacelabels.DynamicLabels + labels *interfacelabels.DynamicLabelManager queuedPackets *prometheus.Desc queuedBytes *prometheus.Desc transferedPackets *prometheus.Desc diff --git a/pkg/features/interfaces/collector.go b/pkg/features/interfaces/collector.go index 9424a2c0..f17ebc3f 100644 --- a/pkg/features/interfaces/collector.go +++ b/pkg/features/interfaces/collector.go @@ -16,7 +16,7 @@ const prefix = "junos_interface_" // Collector collects interface metrics type interfaceCollector struct { - labels *interfacelabels.DynamicLabels + labels *interfacelabels.DynamicLabelManager receiveBytesDesc *prometheus.Desc receivePacketsDesc *prometheus.Desc receiveErrorsDesc *prometheus.Desc @@ -57,7 +57,7 @@ type interfaceCollector struct { } // NewCollector creates a new collector -func NewCollector(labels *interfacelabels.DynamicLabels) collector.RPCCollector { +func NewCollector(labels *interfacelabels.DynamicLabelManager) collector.RPCCollector { c := &interfaceCollector{ labels: labels, } diff --git a/pkg/interfacelabels/dynamic_labels.go b/pkg/interfacelabels/dynamic_labels.go index 38fd3b90..3382284f 100644 --- a/pkg/interfacelabels/dynamic_labels.go +++ b/pkg/interfacelabels/dynamic_labels.go @@ -24,18 +24,18 @@ func DefaultInterfaceDescRegex() *regexp.Regexp { return regexp.MustCompile(`\[([^=\]]+)(=[^\]]+)?\]`) } -// NewDynamicLabels create a new instance of DynamicLabels -func NewDynamicLabels() *DynamicLabels { - return &DynamicLabels{ +// NewDynamicLabelManager create a new instance of DynamicLabels +func NewDynamicLabelManager() *DynamicLabelManager { + return &DynamicLabelManager{ labelNames: make(map[string]int), - labels: make(map[interfaceKey][]*interfaceLabel), + labels: make(map[interfaceKey][]*InterfaceLabel), } } -// DynamicLabels parses and manages dynamic labels and label values -type DynamicLabels struct { +// DynamicLabelManager parses and manages dynamic labels and label values +type DynamicLabelManager struct { labelNames map[string]int - labels map[interfaceKey][]*interfaceLabel + labels map[interfaceKey][]*InterfaceLabel labelCount int mu sync.Mutex } @@ -45,13 +45,21 @@ type interfaceKey struct { ifaceName string } -type interfaceLabel struct { +type InterfaceLabel struct { name string value string } +func (il *InterfaceLabel) Name() string { + return il.name +} + +func (il *InterfaceLabel) Value() string { + return il.value +} + // CollectDescriptions collects labels from descriptions -func (l *DynamicLabels) CollectDescriptions(device *connector.Device, client collector.Client, ifDescReg *regexp.Regexp) error { +func (l *DynamicLabelManager) CollectDescriptions(device *connector.Device, client collector.Client, ifDescReg *regexp.Regexp) error { r := &result{} err := client.RunCommandAndParse("show interfaces descriptions", r) if err != nil { @@ -65,7 +73,7 @@ func (l *DynamicLabels) CollectDescriptions(device *connector.Device, client col } // LabelNames returns the names for all dynamic labels -func (l *DynamicLabels) LabelNames() []string { +func (l *DynamicLabelManager) LabelNames() []string { names := make([]string, len(l.labelNames)) for k, v := range l.labelNames { @@ -76,7 +84,7 @@ func (l *DynamicLabels) LabelNames() []string { } // ValuesForInterface returns the values for all dynamic labels -func (l *DynamicLabels) ValuesForInterface(device *connector.Device, ifaceName string) []string { +func (l *DynamicLabelManager) ValuesForInterface(device *connector.Device, ifaceName string) []string { labels := make([]string, len(l.labelNames)) k := interfaceKey{host: device.Host, ifaceName: ifaceName} @@ -92,12 +100,12 @@ func (l *DynamicLabels) ValuesForInterface(device *connector.Device, ifaceName s return labels } -func (l *DynamicLabels) parseDescriptions(device *connector.Device, ifaces []interfaceDescription, ifDescReg *regexp.Regexp) { +func (l *DynamicLabelManager) parseDescriptions(device *connector.Device, ifaces []interfaceDescription, ifDescReg *regexp.Regexp) { l.mu.Lock() defer l.mu.Unlock() for _, in := range ifaces { - labels := l.parseDescription(in, ifDescReg) + labels := ParseDescription(in.Description, ifDescReg) for _, la := range labels { if _, found := l.labelNames[la.name]; !found { @@ -111,14 +119,14 @@ func (l *DynamicLabels) parseDescriptions(device *connector.Device, ifaces []int } } -func (l *DynamicLabels) parseDescription(iface interfaceDescription, ifDescReg *regexp.Regexp) []*interfaceLabel { - labels := make([]*interfaceLabel, 0) +func ParseDescription(description string, ifDescReg *regexp.Regexp) InterfaceLabels { + labels := make(InterfaceLabels, 0) - if len(iface.Description) == 0 { + if len(description) == 0 || ifDescReg == nil { return labels } - matches := ifDescReg.FindAllStringSubmatch(iface.Description, -1) + matches := ifDescReg.FindAllStringSubmatch(description, -1) for _, m := range matches { n := strings.ToLower(m[1]) @@ -126,7 +134,7 @@ func (l *DynamicLabels) parseDescription(iface interfaceDescription, ifDescReg * continue } - label := &interfaceLabel{ + label := &InterfaceLabel{ name: n, } @@ -143,3 +151,23 @@ func (l *DynamicLabels) parseDescription(iface interfaceDescription, ifDescReg * return labels } + +type InterfaceLabels []*InterfaceLabel + +func (ils InterfaceLabels) Keys() []string { + ret := make([]string, 0, len(ils)) + for _, il := range ils { + ret = append(ret, il.name) + } + + return ret +} + +func (ils InterfaceLabels) Values() []string { + ret := make([]string, 0, len(ils)) + for _, il := range ils { + ret = append(ret, il.value) + } + + return ret +} diff --git a/pkg/interfacelabels/dynamic_labels_test.go b/pkg/interfacelabels/dynamic_labels_test.go index 94a30701..c0ea6bb1 100644 --- a/pkg/interfacelabels/dynamic_labels_test.go +++ b/pkg/interfacelabels/dynamic_labels_test.go @@ -12,7 +12,7 @@ import ( func TestParseDescriptions(t *testing.T) { t.Run("Test default", func(t *testing.T) { - l := NewDynamicLabels() + l := NewDynamicLabelManager() regex := DefaultInterfaceDescRegex() if1 := interfaceDescription{ @@ -45,7 +45,7 @@ func TestParseDescriptions(t *testing.T) { }) t.Run("Test custom regex", func(t *testing.T) { - l := NewDynamicLabels() + l := NewDynamicLabelManager() regex := regexp.MustCompile(`[[\s]([^=\[\]]+)(=[^,\]]+)?[,\]]`) if1 := interfaceDescription{