Skip to content

Commit 1129780

Browse files
authored
Added PoE interface metrics (#265)
* Implemented PoE statistics * Fixed small label issue
1 parent d272878 commit 1129780

File tree

7 files changed

+190
-0
lines changed

7 files changed

+190
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ The following metrics are supported by now:
5252
* LDP (number of neighbors, sessions and session states)
5353
* VRRP (state per interface)
5454
* Subscribers Information (show subscribers client-type dhcp detail)
55+
* PoE (show poe interface)
5556

5657
## Feature specific mappings
5758
Some collected time series behave like enums - Integer values represent a certain state/meaning.

collectors.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package main
44

55
import (
6+
"github.com/czerwonk/junos_exporter/pkg/features/poe"
67
"regexp"
78

89
"github.com/czerwonk/junos_exporter/internal/config"
@@ -119,6 +120,7 @@ func (c *collectors) initCollectorsForDevices(device *connector.Device, descRe *
119120
c.addCollectorIfEnabledForDevice(device, "mpls_lsp", f.MPLSLSP, mplslsp.NewCollector)
120121
c.addCollectorIfEnabledForDevice(device, "subscriber", f.Subscriber, subscriber.NewCollector)
121122
c.addCollectorIfEnabledForDevice(device, "macsec", f.MACSec, macsec.NewCollector)
123+
c.addCollectorIfEnabledForDevice(device, "poe", f.Poe, poe.NewCollector)
122124
}
123125

124126
func (c *collectors) addCollectorIfEnabledForDevice(device *connector.Device, key string, enabled bool, newCollector func() collector.RPCCollector) {

internal/config/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ type FeatureConfig struct {
9898
License bool `yaml:"license,omitempty"`
9999
Subscriber bool `yaml:"subscriber,omitempty"`
100100
MACSec bool `yaml:"macsec,omitempty"`
101+
Poe bool `yaml:"poe,omitempty"`
101102
}
102103

103104
// New creates a new config
@@ -176,6 +177,7 @@ func setDefaultValues(c *Config) {
176177
f.BFD = false
177178
f.License = false
178179
f.MACSec = true
180+
f.Poe = false
179181
}
180182

181183
// FeaturesForDevice gets the feature set configured for a device

main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ var (
8585
tracingCollectorEndpoint = flag.String("tracing.collector.grpc-endpoint", "", "Sets the tracing provider (stdout or collector)")
8686
subscriberEnabled = flag.Bool("subscriber.enabled", false, "Scrape subscribers detail")
8787
macsecEnabled = flag.Bool("macsec.enabled", true, "Scrape MACSec metrics")
88+
poeEnabled = flag.Bool("poe.enabled", true, "Scrape PoE metrics")
8889
cfg *config.Config
8990
devices []*connector.Device
9091
connManager *connector.SSHConnectionManager
@@ -251,6 +252,7 @@ func loadConfigFromFlags() *config.Config {
251252
f.License = *licenseEnabled
252253
f.Subscriber = *subscriberEnabled
253254
f.MACSec = *macsecEnabled
255+
f.Poe = *poeEnabled
254256
return c
255257
}
256258

pkg/features/poe/collector.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package poe
2+
3+
import (
4+
"github.com/czerwonk/junos_exporter/pkg/collector"
5+
"github.com/pkg/errors"
6+
"github.com/prometheus/client_golang/prometheus"
7+
"strconv"
8+
"strings"
9+
)
10+
11+
const prefix string = "junos_poe_"
12+
13+
var (
14+
poeEnabledDesc *prometheus.Desc
15+
poeStatusDesc *prometheus.Desc
16+
poePowerLimitDesc *prometheus.Desc
17+
poePowerDesc *prometheus.Desc
18+
poeClassDesc *prometheus.Desc
19+
)
20+
21+
// Initialize metrics descriptions
22+
func init() {
23+
labels := []string{"target", "interface"}
24+
poeEnabledDesc = prometheus.NewDesc(prefix+"enabled", "Information about interface status", labels, nil)
25+
poeStatusDesc = prometheus.NewDesc(prefix+"status", "Information about interface PoE status", labels, nil)
26+
poePowerLimitDesc = prometheus.NewDesc(prefix+"power_limit", "Information about interface PoE power limit", labels, nil)
27+
poePowerDesc = prometheus.NewDesc(prefix+"power", "Information about interface PoE power usage", labels, nil)
28+
poeClassDesc = prometheus.NewDesc(prefix+"class", "Information about interface PoE class", labels, nil)
29+
}
30+
31+
type poeCollector struct{}
32+
33+
// NewCollector creates a new collector
34+
func NewCollector() collector.RPCCollector {
35+
return &poeCollector{}
36+
}
37+
38+
// Name returns the name of the collector
39+
func (p poeCollector) Name() string {
40+
return "poe"
41+
}
42+
43+
// Describe describes the metrics
44+
func (p poeCollector) Describe(ch chan<- *prometheus.Desc) {
45+
ch <- poeEnabledDesc
46+
ch <- poeStatusDesc
47+
ch <- poePowerLimitDesc
48+
ch <- poePowerDesc
49+
ch <- poeClassDesc
50+
}
51+
52+
func (p poeCollector) Collect(client collector.Client, ch chan<- prometheus.Metric, labelValues []string) error {
53+
var result poeInterfaceResult
54+
err := client.RunCommandAndParse("show poe interface", &result)
55+
if err != nil {
56+
return errors.Wrap(err, "failed to run command 'show poe interface'")
57+
}
58+
for _, i := range result.Poe.InterfaceInformation {
59+
p.CollectForInterface(i, ch, labelValues)
60+
}
61+
return nil
62+
}
63+
64+
func (p *poeCollector) CollectForInterface(iface InterfaceInformation, ch chan<- prometheus.Metric, labelValues []string) {
65+
lv := append(labelValues, []string{iface.Name}...)
66+
67+
enabled := 0
68+
if iface.Enabled == "Enabled" {
69+
enabled = 1
70+
}
71+
status := 0
72+
if iface.Status == "ON" {
73+
status = 1
74+
}
75+
powerLimit := parsePower(iface.PowerLimit)
76+
powerUsage := parsePower(iface.Power)
77+
78+
class := -1.0
79+
if iface.Class != "not-applicable" {
80+
pClass, _ := strconv.ParseFloat(iface.Class, 64)
81+
class = pClass
82+
}
83+
84+
ch <- prometheus.MustNewConstMetric(poeEnabledDesc, prometheus.GaugeValue, float64(enabled), lv...)
85+
ch <- prometheus.MustNewConstMetric(poeStatusDesc, prometheus.GaugeValue, float64(status), lv...)
86+
ch <- prometheus.MustNewConstMetric(poePowerLimitDesc, prometheus.GaugeValue, powerLimit, lv...)
87+
ch <- prometheus.MustNewConstMetric(poePowerDesc, prometheus.GaugeValue, powerUsage, lv...)
88+
ch <- prometheus.MustNewConstMetric(poeClassDesc, prometheus.GaugeValue, class, lv...)
89+
}
90+
91+
func parsePower(s string) float64 {
92+
powerWithoutSuffix := strings.ReplaceAll(s, "W", "")
93+
parsedPower, _ := strconv.ParseFloat(powerWithoutSuffix, 64)
94+
return parsedPower
95+
}

pkg/features/poe/rpc.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package poe
2+
3+
import "encoding/xml"
4+
5+
// structure for poe interface result
6+
type poeInterfaceResult struct {
7+
XMLName xml.Name `xml:"rpc-reply"`
8+
Poe struct {
9+
InterfaceInformation []InterfaceInformation `xml:"interface-information"`
10+
} `xml:"poe"`
11+
}
12+
13+
// InterfaceInformation structure for poe information from interface
14+
type InterfaceInformation struct {
15+
Name string `xml:"interface-name"`
16+
Enabled string `xml:"interface-enabled"`
17+
Status string `xml:"interface-status"`
18+
PowerLimit string `xml:"interface-power-limit"`
19+
LldpNegotiationPower string `xml:"interface-lldp-negotiation-power"`
20+
Priority string `xml:"interface-priority"`
21+
LldpNegotiationPriority string `xml:"interface-lldp-negotiation-priority"`
22+
Power string `xml:"interface-power"`
23+
Asterisk string `xml:"interface-asterisk"`
24+
Class string `xml:"interface-class"`
25+
}

pkg/features/poe/rpc_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package poe
2+
3+
import (
4+
"encoding/xml"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestParseXML(t *testing.T) {
11+
resultData := `
12+
<rpc-reply xmlns:junos="http://xml.juniper.net/junos/15.1R7/junos">
13+
<poe>
14+
<interface-information>
15+
<interface-name>ge-0/0/0</interface-name>
16+
<interface-enabled>Enabled</interface-enabled>
17+
<interface-status>ON</interface-status>
18+
<interface-power-limit>30.0W</interface-power-limit>
19+
<interface-lldp-negotiation-power> </interface-lldp-negotiation-power>
20+
<interface-priority>Low</interface-priority>
21+
<interface-lldp-negotiation-priority> </interface-lldp-negotiation-priority>
22+
<interface-power>5.3W</interface-power>
23+
<interface-asterisk> </interface-asterisk>
24+
<interface-class>4</interface-class>
25+
</interface-information>
26+
<interface-information>
27+
<interface-name>ge-0/0/23</interface-name>
28+
<interface-enabled>Enabled</interface-enabled>
29+
<interface-status>OFF</interface-status>
30+
<interface-power-limit>15.4W</interface-power-limit>
31+
<interface-lldp-negotiation-power> </interface-lldp-negotiation-power>
32+
<interface-priority>Low</interface-priority>
33+
<interface-lldp-negotiation-priority> </interface-lldp-negotiation-priority>
34+
<interface-power>0.0W</interface-power>
35+
<interface-asterisk> </interface-asterisk>
36+
<interface-class>not-applicable</interface-class>
37+
</interface-information>
38+
</poe>
39+
<cli>
40+
<banner>{master:0}</banner>
41+
</cli>
42+
</rpc-reply>
43+
`
44+
45+
var result poeInterfaceResult
46+
47+
err := xml.Unmarshal([]byte(resultData), &result)
48+
assert.NoError(t, err)
49+
50+
assert.Equal(t, "ge-0/0/0", result.Poe.InterfaceInformation[0].Name)
51+
assert.Equal(t, "Enabled", result.Poe.InterfaceInformation[0].Enabled)
52+
assert.Equal(t, "ON", result.Poe.InterfaceInformation[0].Status)
53+
assert.Equal(t, "30.0W", result.Poe.InterfaceInformation[0].PowerLimit)
54+
assert.Equal(t, "5.3W", result.Poe.InterfaceInformation[0].Power)
55+
assert.Equal(t, "4", result.Poe.InterfaceInformation[0].Class)
56+
57+
assert.Equal(t, "ge-0/0/23", result.Poe.InterfaceInformation[1].Name)
58+
assert.Equal(t, "Enabled", result.Poe.InterfaceInformation[1].Enabled)
59+
assert.Equal(t, "OFF", result.Poe.InterfaceInformation[1].Status)
60+
assert.Equal(t, "15.4W", result.Poe.InterfaceInformation[1].PowerLimit)
61+
assert.Equal(t, "0.0W", result.Poe.InterfaceInformation[1].Power)
62+
assert.Equal(t, "not-applicable", result.Poe.InterfaceInformation[1].Class)
63+
}

0 commit comments

Comments
 (0)