Skip to content

Commit

Permalink
Merge pull request #34 from syseleven/sneubauer/vrrp-exporter
Browse files Browse the repository at this point in the history
Implement VRRP collector and tests
  • Loading branch information
tynany authored Aug 8, 2021
2 parents 72f287c + cffc67b commit 8cf0cb1
Show file tree
Hide file tree
Showing 4 changed files with 401 additions and 6 deletions.
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,34 +22,34 @@ Flags:
--collector.bgp.peer-types
Enable the frr_bgp_peer_types_up metric (default: disabled).
--collector.bgp.peer-types.keys=type ...
Select the keys from the JSON formatted BGP peer description of which the values will be used with the frr_bgp_peer_types_up
metric. Supports multiple values (default: type).
Select the keys from the JSON formatted BGP peer description of which the values will be used with the frr_bgp_peer_types_up metric. Supports multiple values (default:
type).
--collector.bgp.peer-descriptions
Add the value of the desc key from the JSON formatted BGP peer description as a label to peer metrics. (default: disabled).
--collector.bgp.peer-descriptions.plain-text
Use the full text field of the BGP peer description instead of the value of the JSON formatted desc key (default: disabled).
--collector.bgp.advertised-prefixes
Enables the frr_exporter_bgp_prefixes_advertised_count_total metric which exports the number of advertised prefixes to a BGP peer.
This is an option for older versions of FRR that don't have PfxSent field (default: disabled).
Enables the frr_exporter_bgp_prefixes_advertised_count_total metric which exports the number of advertised prefixes to a BGP peer. This is an option for older versions of
FRR that don't have PfxSent field (default: disabled).
--web.listen-address=":9342"
Address on which to expose metrics and web interface.
--web.telemetry-path="/metrics"
Path under which to expose metrics.
--frr.vtysh.path="/usr/bin/vtysh"
Path of vtysh.
--frr.vtysh.pathspace="" Config prefix to be passed to vtysh via the -N flag (optional).
--frr.vtysh.timeout="20s" The timeout when running vtysh commands (default 20s).
--frr.vtysh.timeout="20s" The timeout when running vtysh commands (default: 20s).
--frr.vtysh.sudo Enable sudo when executing vtysh commands.
--collector.bgp Collect BGP Metrics (default: enabled).
--collector.ospf Collect OSPF Metrics (default: enabled).
--collector.bgp6 Collect BGP IPv6 Metrics (default: disabled).
--collector.bgpl2vpn Collect BGP L2VPN Metrics (default: disabled).
--collector.bfd Collect BFD Metrics (default: enabled).
--collector.vrrp Collect VRRP Metrics (default: disabled).
--log.level="info" Only log messages with the given severity or above. Valid levels: [debug, info, warn, error, fatal]
--log.format="logger:stderr"
Set the log target and format. Example: "logger:syslog?appname=bob&local=7" or "logger:stdout?json=true"
--version Show application version.
```

Promethues configuraiton:
Expand Down Expand Up @@ -82,6 +82,7 @@ Name | Description
--- | ---
BGP IPv6 | Per VRF and address family (currently support unicast only) BGP IPv6 metrics:<br> - RIB entries<br> - RIB memory usage<br> - Configured peer count<br> - Peer memory usage<br> - Configure peer group count<br> - Peer group memory usage<br> - Peer messages in<br> - Peer messages out<br> - Peer active prfixes<br> - Peer state (established/down)<br> - Peer uptime
BGP L2VPN | Per VRF and address family (currently support EVPN only) BGP L2VPN EVPN metrics:<br> - RIB entries<br> - RIB memory usage<br> - Configured peer count<br> - Peer memory usage<br> - Configure peer group count<br> - Peer group memory usage<br> - Peer messages in<br> - Peer messages out<br> - Peer active prfixes<br> - Peer state (established/down)<br> - Peer uptime
VRRP | Per VRRP Interface, VrID and Protocol:<br> - Rx and TX statistics<br> - VRRP Status<br> - VRRP State Transitions<br>

### VTYSH
The vtysh command is heavily utilised to extract metrics from FRR. The default timeout is 20s but can be modified via the `--frr.vtysh.timeout` flag.
Expand Down
183 changes: 183 additions & 0 deletions collector/vrrp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package collector

import (
"encoding/json"
"fmt"
"strconv"
"strings"

"github.com/prometheus/client_golang/prometheus"
)

const (
vrrpStatusInitialize string = "Initialize"
vrrpStatusBackup = "Backup"
vrrpStatusMaster = "Master"
)

var (
vrrpSubsystem = "vrrp"
vrrpDesc map[string]*prometheus.Desc

vrrpErrors = []error{}
totalVRRPErrors = 0.0

vrrpStates = []string{vrrpStatusInitialize, vrrpStatusMaster, vrrpStatusBackup}
)

type VrrpVrInfo struct {
Vrid int
Interface string
V6Info VrrpInstanceInfo `json:"v6"`
V4Info VrrpInstanceInfo `json:"v4"`
}

type VrrpInstanceInfo struct {
Subinterface string `json:"interface"`
Status string
Statistics VrrpInstanceStats `json:"stats"`
}

type VrrpInstanceStats struct {
AdverTx *int
AdverRx *int
GarpTx *int
NeighborAdverTx *int
Transitions *int
}

// VRRPCollector collects VRRP metrics, implemented as per prometheus.Collector interface.
type VRRPCollector struct{}

// NewVRRPCollector returns a VRRPCollector struct.
func NewVRRPCollector() *VRRPCollector {
return &VRRPCollector{}
}

// Name of the collector. Used to populate flag name.
func (*VRRPCollector) Name() string {
return vrrpSubsystem
}

// Help describes the metrics this collector scrapes. Used to populate flag help.
func (*VRRPCollector) Help() string {
return "Collect VRRP Metrics"
}

// EnabledByDefault describes whether this collector is enabled by default. Used to populate flag default.
func (*VRRPCollector) EnabledByDefault() bool {
return false
}

// Describe implemented as per the prometheus.Collector interface.
func (*VRRPCollector) Describe(ch chan<- *prometheus.Desc) {
for _, desc := range getVRRPDesc() {
ch <- desc
}
}

// Collect implemented as per the prometheus.Collector interface.
func (c *VRRPCollector) Collect(ch chan<- prometheus.Metric) {
collectVRRP(ch)
}

// CollectErrors returns what errors have been gathered.
func (*VRRPCollector) CollectErrors() []error {
return vrrpErrors
}

// CollectTotalErrors returns total errors.
func (*VRRPCollector) CollectTotalErrors() float64 {
return totalVRRPErrors
}

func getVRRPDesc() map[string]*prometheus.Desc {
if vrrpDesc != nil {
return vrrpDesc
}

vrrpLabels := []string{"proto", "vrid", "interface", "subinterface"}
vrrpStateLabels := append(vrrpLabels, "state")

vrrpDesc = map[string]*prometheus.Desc{
"vrrpState": colPromDesc(vrrpSubsystem, "state", "Status of the VRRP state machine.", vrrpStateLabels),
"adverTx": colPromDesc(vrrpSubsystem, "adverTx_total", "Advertisements sent total.", vrrpLabels),
"adverRx": colPromDesc(vrrpSubsystem, "adverRx_total", "Advertisements received total.", vrrpLabels),
"garpTx": colPromDesc(vrrpSubsystem, "garpTx_total", "Gratuitous ARP sent total.", vrrpLabels),
"neighborAdverTx": colPromDesc(vrrpSubsystem, "neighborAdverTx_total", "Neighbor Advertisements sent total.", vrrpLabels),
"transitions": colPromDesc(vrrpSubsystem, "state_transitions_total", "Number of transitions of the VRRP state machine in total.", vrrpLabels),
}

return vrrpDesc
}

func collectVRRP(ch chan<- prometheus.Metric) {
jsonVRRPInfo, err := getVRRPInfo()
if err != nil {
totalVRRPErrors++
vrrpErrors = append(vrrpErrors, fmt.Errorf("cannot get vrrp info: %s", err))
} else {
if err := processVRRPInfo(ch, jsonVRRPInfo); err != nil {
totalVRRPErrors++
vrrpErrors = append(vrrpErrors, err)
}
}
}

func getVRRPInfo() ([]byte, error) {
args := []string{"-c", "show vrrp json"}

return execVtyshCommand(args...)
}

func processVRRPInfo(ch chan<- prometheus.Metric, jsonVRRPInfo []byte) error {
var jsonList []VrrpVrInfo
if err := json.Unmarshal(jsonVRRPInfo, &jsonList); err != nil {
return fmt.Errorf("cannot unmarshal vrrp json: %s", err)
}

for _, vrInfo := range jsonList {
processInstance(ch, "v4", vrInfo.Vrid, vrInfo.Interface, vrInfo.V4Info)
processInstance(ch, "v6", vrInfo.Vrid, vrInfo.Interface, vrInfo.V6Info)
}

return nil
}

func processInstance(ch chan<- prometheus.Metric, proto string, vrid int, iface string, instance VrrpInstanceInfo) {
vrrpDesc := getVRRPDesc()

vrrpLabels := []string{proto, strconv.Itoa(vrid), iface, instance.Subinterface}

for _, state := range vrrpStates {
stateLabels := append(vrrpLabels, state)

var value float64

if strings.EqualFold(instance.Status, state) {
value = 1
}

newGauge(ch, vrrpDesc["vrrpState"], value, stateLabels...)
}

if instance.Statistics.AdverTx != nil {
newCounter(ch, vrrpDesc["adverTx"], float64(*instance.Statistics.AdverTx), vrrpLabels...)
}

if instance.Statistics.AdverRx != nil {
newCounter(ch, vrrpDesc["adverRx"], float64(*instance.Statistics.AdverRx), vrrpLabels...)
}

if instance.Statistics.GarpTx != nil {
newCounter(ch, vrrpDesc["garpTx"], float64(*instance.Statistics.GarpTx), vrrpLabels...)
}

if instance.Statistics.NeighborAdverTx != nil {
newCounter(ch, vrrpDesc["neighborAdverTx"], float64(*instance.Statistics.NeighborAdverTx), vrrpLabels...)
}

if instance.Statistics.Transitions != nil {
newCounter(ch, vrrpDesc["transitions"], float64(*instance.Statistics.Transitions), vrrpLabels...)
}
}
Loading

0 comments on commit 8cf0cb1

Please sign in to comment.