Skip to content
Open
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
169 changes: 146 additions & 23 deletions pkg/features/environment/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,26 @@ import (
"strconv"
"strings"

"github.com/czerwonk/junos_exporter/pkg/collector"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"

"github.com/czerwonk/junos_exporter/pkg/collector"
)

const prefix string = "junos_environment_"

var (
statusValues = map[string]int{
"OK": 1,
"Testing": 2,
"Failed": 3,
"Absent": 4,
"Present": 5,
"Online": 1,
"Empty": 4,
"Offline": 3,
}

temperaturesDesc *prometheus.Desc
powerSupplyDesc *prometheus.Desc
fanStatusDesc *prometheus.Desc
Expand All @@ -26,20 +39,28 @@ var (
dcCurrentDesc *prometheus.Desc
dcPowerDesc *prometheus.Desc
dcLoadDesc *prometheus.Desc
dcOutputDesc *prometheus.Desc
inputVoltageDesc *prometheus.Desc
inputCurrentDesc *prometheus.Desc
inputPowerDesc *prometheus.Desc
)

func init() {
l := []string{"target", "re_name", "item"}
temperaturesDesc = prometheus.NewDesc(prefix+"item_temp", "Temperature of the air flowing past", l, nil)
powerSupplyDesc = prometheus.NewDesc(prefix+"power_up", "Status of power supplies (1 OK, 2 Testing, 3 Failed, 4 Absent, 5 Present)", append(l, "status"), nil)
fanStatusDesc = prometheus.NewDesc(prefix+"fan_up", "Status of fans (1 OK, 2 Testing, 3 Failed, 4 Absent, 5 Present)", append(l, "status"), nil)
fanAirflowDesc = prometheus.NewDesc(prefix+"fan_airflow_up", "Status of fan airflows (1 OK, 2 Testing, 3 Failed, 4 Absent, 5 Present)", append(l, "status"), nil)
powerSupplyDesc = prometheus.NewDesc(prefix+"power_up", "Status of power supplies (1 OK/Online, 2 Testing, 3 Failed/Offline, 4 Absent/Empty, 5 Present)", append(l, "status"), nil)
fanStatusDesc = prometheus.NewDesc(prefix+"fan_up", "Status of fans (1 OK/Online, 2 Testing, 3 Failed/Offline, 4 Absent/Empty, 5 Present)", append(l, "status"), nil)
fanAirflowDesc = prometheus.NewDesc(prefix+"fan_airflow_up", "Status of fan airflows (1 OK/Online, 2 Testing, 3 Failed/Offline, 4 Absent/Empty, 5 Present)", append(l, "status"), nil)

pemDesc = prometheus.NewDesc(prefix+"pem_state", "State of PEM module. 1 - Online, 2 - Present, 3 - Empty", append(l, "state"), nil)
pemDesc = prometheus.NewDesc(prefix+"pem_state", "State of PEM module (1 OK/Online, 2 Testing, 3 Failed/Offline, 4 Absent/Empty, 5 Present)", append(l, "state"), nil)
dcVoltageDesc = prometheus.NewDesc(prefix+"pem_voltage", "PEM voltage value", l, nil)
dcCurrentDesc = prometheus.NewDesc(prefix+"pem_current", "PEM current value", l, nil)
dcPowerDesc = prometheus.NewDesc(prefix+"pem_power_usage", "PEM power usage in W", l, nil)
dcLoadDesc = prometheus.NewDesc(prefix+"pem_power_load_percent", "PEM power usage percent of total", l, nil)
dcOutputDesc = prometheus.NewDesc(prefix+"pem_dc_output", "PSM DC output status (1 OK, 0 not OK)", l, nil)
inputVoltageDesc = prometheus.NewDesc(prefix+"pem_input_voltage", "PSU input voltage in V", l, nil)
inputCurrentDesc = prometheus.NewDesc(prefix+"pem_input_current", "PSU input current in A", l, nil)
inputPowerDesc = prometheus.NewDesc(prefix+"pem_input_power", "PSU input power in W", l, nil)

l = []string{"target", "re_name", "item", "fan_name"}
fanDesc = prometheus.NewDesc(prefix+"pem_fanspeed", "Fan speed in RPM", l, nil)
Expand All @@ -63,27 +84,56 @@ func (*environmentCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- temperaturesDesc
ch <- fanDesc
ch <- dcPowerDesc
ch <- dcOutputDesc
ch <- inputVoltageDesc
ch <- inputCurrentDesc
ch <- inputPowerDesc
}

// deviceModel returns the lowercase product model string for the device.
// Most devices expose it via 'show version', but some (e.g. EX4300) return
// chassis-inventory instead, so we fall back to 'show chassis hardware'.
func (c *environmentCollector) deviceModel(client collector.Client) (string, error) {
var v showVersionResult
if err := client.RunCommandAndParse("show version", &v); err != nil {
return "", errors.Wrap(err, "failed to run command 'show version'")
}

if model := strings.ToLower(v.SoftwareInformation.ProductModel); model != "" {
return model, nil
}

// Fallback: EX4300 returns chassis-inventory instead of software-information
var hw showChassisHardwareResult
if err := client.RunCommandAndParse("show chassis hardware", &hw); err == nil {
return strings.ToLower(hw.ChassisInventory.Chassis.Description), nil
}

return "", nil
}

// Collect collects metrics from JunOS
func (c *environmentCollector) Collect(client collector.Client, ch chan<- prometheus.Metric, labelValues []string) error {
model, err := c.deviceModel(client)
if err != nil {
return err
}

c.environmentItems(client, ch, labelValues)
c.environmentPEMItems(client, ch, labelValues)

if strings.Contains(model, "qfx5220") {
c.environmentPEMItemsQFX5220(client, ch, labelValues)
} else if strings.Contains(model, "ex4300") {
c.environmentPEMItemsEX4300(client, ch, labelValues)
} else {
c.environmentPEMItems(client, ch, labelValues)
}
return nil
}

func (c *environmentCollector) environmentItems(client collector.Client, ch chan<- prometheus.Metric, labelValues []string) error {
x := multiEngineResult{}

statusValues := map[string]int{
"OK": 1,
"Testing": 2,
"Failed": 3,
"Absent": 4,
"Present": 5,
}

err := client.RunCommandAndParseWithParser("show chassis environment", func(b []byte) error {
return parseXML(b, &x)
})
Expand Down Expand Up @@ -114,7 +164,7 @@ func (c *environmentCollector) environmentItems(client collector.Client, ch chan
l := labelValues
for _, item := range re.EnvironmentInformation.Items {
l = append(labelValues, re.Name)
if strings.Contains(item.Name, "Power Supply") || strings.Contains(item.Name, "PEM") || strings.Contains(item.Name, "PSM") {
if containsAny(item.Name, []string{"Power Supply", "PEM", "PSM"}) {
l = append(l, item.Name, item.Status)
ch <- prometheus.MustNewConstMetric(powerSupplyDesc, prometheus.GaugeValue, float64(statusValues[item.Status]), l...)
} else if strings.Contains(item.Name, "Fan") {
Expand All @@ -137,13 +187,6 @@ func (c *environmentCollector) environmentItems(client collector.Client, ch chan
func (c *environmentCollector) environmentPEMItems(client collector.Client, ch chan<- prometheus.Metric, labelValues []string) error {
var x = multiEngineResult{}

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

err := client.RunCommandAndParseWithParser("show chassis environment pem", func(b []byte) error {
return parseXML(b, &x)
})
Expand All @@ -160,7 +203,7 @@ func (c *environmentCollector) environmentPEMItems(client collector.Client, ch c
for _, e := range re.EnvironmentComponentInformation.EnvironmentComponentItem {
l := append(labelValues, re.Name, e.Name)

ch <- prometheus.MustNewConstMetric(pemDesc, prometheus.GaugeValue, float64(stateValues[e.State]), append(l, e.State)...)
ch <- prometheus.MustNewConstMetric(pemDesc, prometheus.GaugeValue, float64(statusValues[e.State]), append(l, e.State)...)

for _, f := range e.FanSpeedReading {
rpms, err := strconv.ParseFloat(strings.TrimSuffix(f.FanSpeed, " RPM"), 64)
Expand Down Expand Up @@ -192,6 +235,86 @@ func (c *environmentCollector) environmentPEMItems(client collector.Client, ch c
return nil
}

func (c *environmentCollector) environmentPEMItemsQFX5220(client collector.Client, ch chan<- prometheus.Metric, labelValues []string) error {
x := environmentPEMResultModelQFX5220{}

err := client.RunCommandAndParseWithParser("show chassis environment pem", func(b []byte) error {
return xml.Unmarshal(b, &x)
})
if err != nil {
err := client.RunCommandAndParseWithParser("show chassis environment psm", func(b []byte) error {
return xml.Unmarshal(b, &x)
})
if err != nil {
return err
}
}

reName := "N/A"
for _, item := range x.EnvironmentComponentInformation.EnvironmentComponentItem {
l := append(labelValues, reName, item.Name)

ch <- prometheus.MustNewConstMetric(pemDesc, prometheus.GaugeValue, float64(statusValues[item.State]), append(l, item.State)...)

fan1Speed := item.PsmInformation.FanSpeedReadingPsm.Fan1Speed
if fan1Speed != "" {
rpms, err := strconv.ParseFloat(strings.TrimSuffix(fan1Speed, " RPM"), 64)
if err != nil {
return fmt.Errorf("could not parse fan speed value to float: %s", fan1Speed)
}
ch <- prometheus.MustNewConstMetric(fanDesc, prometheus.GaugeValue, rpms, append(l, item.PsmInformation.FanSpeedReadingPsm.Fan1Name)...)
}

//it could be that the DCOutputValue has the same states as statusValues from above
//but I couldn't verify it for sure
dcOutputVal := 0.0
if strings.EqualFold(strings.ToLower(item.PsmInformation.PsmStatus.DcOutput), "ok") {
dcOutputVal = 1.0
}
ch <- prometheus.MustNewConstMetric(dcOutputDesc, prometheus.GaugeValue, dcOutputVal, l...)
}

return nil
}

func (c *environmentCollector) environmentPEMItemsEX4300(client collector.Client, ch chan<- prometheus.Metric, labelValues []string) error {
x := environmentPEMResultModelEX4300{}

err := client.RunCommandAndParseWithParser("show chassis environment power-supply-unit", func(b []byte) error {
return xml.Unmarshal(b, &x)
})
if err != nil {
return err
}

reName := "N/A"
for _, item := range x.EnvironmentComponentInformation.EnvironmentComponentItem {
pem := item.PemInformation
itemName := fmt.Sprintf("FPC %s PSU %s", pem.FpcSlot, pem.PemSlot)
l := append(labelValues, reName, itemName)

ch <- prometheus.MustNewConstMetric(pemDesc, prometheus.GaugeValue, float64(statusValues[pem.PemState]), append(l, pem.PemState)...)
ch <- prometheus.MustNewConstMetric(temperaturesDesc, prometheus.GaugeValue, pem.PemTemperature, append(labelValues, reName, itemName)...)
ch <- prometheus.MustNewConstMetric(dcVoltageDesc, prometheus.GaugeValue, pem.OutputVolt, l...)
ch <- prometheus.MustNewConstMetric(dcCurrentDesc, prometheus.GaugeValue, pem.OutputCurrent, l...)
ch <- prometheus.MustNewConstMetric(dcPowerDesc, prometheus.GaugeValue, pem.OutputPower, l...)
ch <- prometheus.MustNewConstMetric(inputVoltageDesc, prometheus.GaugeValue, pem.InputVolt, l...)
ch <- prometheus.MustNewConstMetric(inputCurrentDesc, prometheus.GaugeValue, pem.InputCurrent, l...)
ch <- prometheus.MustNewConstMetric(inputPowerDesc, prometheus.GaugeValue, pem.InputPower, l...)
}

return nil
}

func containsAny(s string, items []string) bool {
for _, item := range items {
if strings.Contains(s, item) {
return true
}
}
return false
}

func parseXML(b []byte, res *multiEngineResult) error {
if strings.Contains(string(b), "multi-routing-engine-results") {
return xml.Unmarshal(b, res)
Expand Down
113 changes: 113 additions & 0 deletions pkg/features/environment/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,116 @@ type singleEngineResult struct {
EnvironmentComponentInformation environmentComponentInformation `xml:"environment-component-information"`
EnvironmentInformation environmentInformation `xml:"environment-information"`
}

type showVersionResult struct {
XMLName xml.Name `xml:"rpc-reply"`
Text string `xml:",chardata"`
Junos string `xml:"junos,attr"`
SoftwareInformation struct {
Text string `xml:",chardata"`
HostName string `xml:"host-name"`
ProductModel string `xml:"product-model"`
ProductName string `xml:"product-name"`
OsName string `xml:"os-name"`
JunosVersion string `xml:"junos-version"`
PackageInformation []struct {
Text string `xml:",chardata"`
Name string `xml:"name"`
PackageName string `xml:"package-name"`
Comment string `xml:"comment"`
} `xml:"package-information"`
} `xml:"software-information"`
Cli struct {
Text string `xml:",chardata"`
Banner string `xml:"banner"`
} `xml:"cli"`
}


type showChassisHardwareResult struct {
XMLName xml.Name `xml:"rpc-reply"`
ChassisInventory struct {
Chassis struct {
Description string `xml:"description"`
} `xml:"chassis"`
} `xml:"chassis-inventory"`
}


type environmentPEMResultModelEX4300 struct {
XMLName xml.Name `xml:"rpc-reply"`
Text string `xml:",chardata"`
Junos string `xml:"junos,attr"`
EnvironmentComponentInformation struct {
Text string `xml:",chardata"`
Xmlns string `xml:"xmlns,attr"`
EnvironmentComponentItem []struct {
PemInformation struct {
FpcSlot string `xml:"fpc-slot"`
PemSlot string `xml:"pem-slot"`
PemState string `xml:"pem-state"`
PemTemperature float64 `xml:"pem-temperature"`
InputCurrent float64 `xml:"input-current"`
InputVolt float64 `xml:"input-volt"`
InputPower float64 `xml:"input-power"`
OutputCurrent float64 `xml:"output-current"`
OutputVolt float64 `xml:"output-volt"`
OutputPower float64 `xml:"output-power"`
} `xml:"pem-information"`
} `xml:"environment-component-item"`
} `xml:"environment-component-information"`
Cli struct {
Text string `xml:",chardata"`
Banner string `xml:"banner"`
} `xml:"cli"`
}

type environmentPEMResultModelQFX5220 struct {
XMLName xml.Name `xml:"rpc-reply"`
Text string `xml:",chardata"`
Junos string `xml:"junos,attr"`
EnvironmentComponentInformation struct {
Text string `xml:",chardata"`
Xmlns string `xml:"xmlns,attr"`
EnvironmentComponentItem []struct {
Text string `xml:",chardata"`
Name string `xml:"name"`
State string `xml:"state"`
PsmInformation struct {
Text string `xml:",chardata"`
TemperatureReading struct {
Text string `xml:",chardata"`
TemperatureName string `xml:"temperature-name"`
Temperature struct {
Text string `xml:",chardata"`
Celsius string `xml:"celsius,attr"`
} `xml:"temperature"`
} `xml:"temperature-reading"`
PsmStatus struct {
Text string `xml:",chardata"`
Fans string `xml:"fans"`
DcOutput string `xml:"dc-output"`
} `xml:"psm-status"`
FirmwareVersion string `xml:"firmware-version"`
FanSpeedReadingPsm struct {
Text string `xml:",chardata"`
Fan1Name string `xml:"fan1-name"`
Fan1Speed string `xml:"fan1-speed"`
Fan2Name string `xml:"fan2-name"`
} `xml:"fan-speed-reading-psm"`
} `xml:"psm-information"`
PsmHealthCheckDetail struct {
Text string `xml:",chardata"`
HealthCheckStatus string `xml:"health-check-status"`
HealthCheckStateStr string `xml:"health-check-state-str"`
HealthCheckLastResultStr string `xml:"health-check-last-result-str"`
HealthCheckLastExecStr string `xml:"health-check-last-exec-str"`
HealthCheckNextSchedRunStr string `xml:"health-check-next-sched-run-str"`
} `xml:"psm-health-check-detail"`
} `xml:"environment-component-item"`
} `xml:"environment-component-information"`
Cli struct {
Text string `xml:",chardata"`
Banner string `xml:"banner"`
} `xml:"cli"`
}
Loading