Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 73 additions & 8 deletions pkg/features/power/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package power

import (
"encoding/xml"
"fmt"
"strings"

"github.com/czerwonk/junos_exporter/pkg/collector"
Expand All @@ -26,8 +27,20 @@ var (
dcCurrentDesc *prometheus.Desc
dcVoltageDesc *prometheus.Desc
dcLoadDesc *prometheus.Desc

// EX4300 power budget statistics
powerBudgetSuppliedPsuDesc *prometheus.Desc
powerBudgetPowerSupplyStateDesc *prometheus.Desc
powerBudgetTotalPowerSupplied *prometheus.Desc
powerBudgetActualPowerUsed *prometheus.Desc
)

var stateValues = map[string]int{
"Online": 1,
"Present": 2,
"Empty": 3,
}

func init() {
l := []string{"target", "re_name"}
capacitySysActualDesc = prometheus.NewDesc(prefix+"capacity_sys_actual_usage", "Actual power usage for the system, in watts", l, nil)
Expand All @@ -48,6 +61,11 @@ func init() {
dcLoadDesc = prometheus.NewDesc(prefix+"pem_power_load_percent", "PEM power usage percent of total", l, nil)

pemPowerStateDesc = prometheus.NewDesc(prefix+"pem_power_state", "PEM power state. 1 - Online, 2 - Present, 3 - Empty", append(l, "state"), nil)

powerBudgetSuppliedPsuDesc = prometheus.NewDesc(prefix+"budget_power_supplied_psu", "Power supplied by the PSU, in watts", []string{"target", "line_card_slot", "psu_slot", "psu_type"}, nil)
powerBudgetPowerSupplyStateDesc = prometheus.NewDesc(prefix+"budget_power_supply_state", "Power supply state. 1 - Online, 2 - Present, 3 - Empty", []string{"target", "line_card_slot", "psu_slot", "psu_type", "power_supply_state"}, nil)
powerBudgetTotalPowerSupplied = prometheus.NewDesc(prefix+"budget_total_power_supplied", "Total power supplied by the PSU, in watts", []string{"target", "line_card_slot"}, nil)
powerBudgetActualPowerUsed = prometheus.NewDesc(prefix+"budget_actual_power_used", "Actual power used by the system, in watts", []string{"target", "line_card_slot"}, nil)
}

type powerCollector struct {
Expand Down Expand Up @@ -80,23 +98,70 @@ func (*powerCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- dcLoadDesc
}

// Collect collects metrics from JunOS
func (c *powerCollector) Collect(client collector.Client, ch chan<- prometheus.Metric, labelValues []string) error {
stateValues := map[string]int{
"Online": 1,
"Present": 2,
"Empty": 3,
platform, err := c.getPlatform(client)
if err != nil {
return fmt.Errorf("failed to get platform: %w", err)
}

if isEX4300(platform) {
return c.collectChassisPowerBudgetStatistics(client, ch, labelValues)
}

return c.collectChassisPower(client, ch, labelValues)
}

func isEX4300(platform string) bool {
return strings.HasPrefix(platform, "EX4300")
}

func (c *powerCollector) getPlatform(client collector.Client) (string, error) {
var chasHW ChassisHardwareResult
err := client.RunCommandAndParseWithParser("show chassis hardware", func(b []byte) error {
return xml.Unmarshal(b, &chasHW)
})
if err != nil {
return "", err
}

var x = multiRoutingEngineResult{}
return chasHW.Platform, nil
}

func (c *powerCollector) collectChassisPowerBudgetStatistics(client collector.Client, ch chan<- prometheus.Metric, labelValues []string) error {
powerBudgetRes := powerBudgetResult{}
err := client.RunCommandAndParseWithParser("show chassis power-budget-statistics", func(b []byte) error {
return xml.Unmarshal(b, &powerBudgetRes)
})
if err != nil {
return err
}

for _, i := range powerBudgetRes.PowerBudgetInformation {
l := append(labelValues, i.LineCardSlot)
ch <- prometheus.MustNewConstMetric(powerBudgetTotalPowerSupplied, prometheus.GaugeValue, float64(i.TotalPowerSupplied), l...)
ch <- prometheus.MustNewConstMetric(powerBudgetActualPowerUsed, prometheus.GaugeValue, float64(i.ActualPowerUsed), l...)

for _, psu := range i.PSUs {
pl := append(l, fmt.Sprintf("%d", psu.Slot), psu.Type)
ch <- prometheus.MustNewConstMetric(powerBudgetSuppliedPsuDesc, prometheus.GaugeValue, float64(psu.PowerSupplied), pl...)
ch <- prometheus.MustNewConstMetric(powerBudgetPowerSupplyStateDesc, prometheus.GaugeValue, float64(stateValues[psu.State]), append(pl, psu.State)...)
}
}

return nil
}

// Collect collects metrics from JunOS
func (c *powerCollector) collectChassisPower(client collector.Client, ch chan<- prometheus.Metric, labelValues []string) error {
var mreRes = multiRoutingEngineResult{}
err := client.RunCommandAndParseWithParser("show chassis power", func(b []byte) error {
return parseXML(b, &x)
return parseXML(b, &mreRes)
})
if err != nil {
return err
}

for _, re := range x.Results.RoutingEngine {
for _, re := range mreRes.Results.RoutingEngine {
l := append(labelValues, re.Name)

if re.PowerUsageInformation.PowerUsageSystem.CapacitySysActual > 0 {
Expand Down
83 changes: 83 additions & 0 deletions pkg/features/power/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,86 @@ type singleRoutingEngineResult struct {
XMLName xml.Name `xml:"rpc-reply"`
PowerUsageInformation powerUsageInformation `xml:"power-usage-information"`
}

type powerBudgetResult struct {
XMLName xml.Name `xml:"rpc-reply"`
PowerBudgetInformation []PowerBudgetInformation `xml:"power-budget-information"`
}

type PowerBudgetInformation struct {
LineCardSlot string `xml:"line-card-slot"`
PSUs []PSU
PsuRedundancyConfig string `xml:"psu-redundancy-config"`
TotalPowerSupplied int `xml:"total-power-supplied"`
BasePower int `xml:"base-power"`
ActualPowerUsed int `xml:"actual-power-used"`
}

type PSU struct {
Slot int `xml:"psu-slot"`
Type string `xml:"psu-type"`
PowerSupplied int `xml:"power-supplied"`
State string `xml:"state"`
}

// UnmarshalXML handles the flat, ungrouped PSU sibling elements by accumulating
// PSU fields into a new PSU each time a <psu-slot> is encountered.
func (p *PowerBudgetInformation) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var current *PSU

flush := func() {
if current != nil {
p.PSUs = append(p.PSUs, *current)
current = nil
}
}

for {
tok, err := d.Token()
if err != nil {
return err
}

switch t := tok.(type) {
case xml.StartElement:
switch t.Name.Local {
case "line-card-slot":
d.DecodeElement(&p.LineCardSlot, &t)
case "psu-slot":
flush() // save previous PSU before starting a new one
current = &PSU{}
d.DecodeElement(&current.Slot, &t)
case "psu-type":
if current != nil {
d.DecodeElement(&current.Type, &t)
}
case "power-supplied-psu":
if current != nil {
d.DecodeElement(&current.PowerSupplied, &t)
}
case "power-supply-state":
if current != nil {
d.DecodeElement(&current.State, &t)
}
case "total-power-supplied":
d.DecodeElement(&p.TotalPowerSupplied, &t)
case "base-power":
d.DecodeElement(&p.BasePower, &t)
case "actual-power-used":
d.DecodeElement(&p.ActualPowerUsed, &t)
default:
d.Skip()
}
case xml.EndElement:
if t.Name.Local == start.Name.Local {
flush() // save the last pending PSU
return nil
}
}
}
}

type ChassisHardwareResult struct {
XMLName xml.Name `xml:"rpc-reply"`
Platform string `xml:"chassis-inventory>chassis>description"`
}
Loading