diff --git a/pkg/features/environment/collector.go b/pkg/features/environment/collector.go index 21f423e4..a4bda240 100644 --- a/pkg/features/environment/collector.go +++ b/pkg/features/environment/collector.go @@ -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 @@ -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) @@ -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) }) @@ -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") { @@ -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) }) @@ -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) @@ -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) diff --git a/pkg/features/environment/rpc.go b/pkg/features/environment/rpc.go index 745f3e2d..c2332c1b 100644 --- a/pkg/features/environment/rpc.go +++ b/pkg/features/environment/rpc.go @@ -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"` +}