From c9dcd93fb4348d7ee6b4f7096e19f68e3c9c71ec Mon Sep 17 00:00:00 2001 From: Jignesh Atul Paratane Date: Sun, 15 Feb 2026 16:26:56 +0530 Subject: [PATCH 01/31] Segregated the modules containing the structure defined for metrics, and added temperature metric structure boilerplate code and added comments as to which data points are available from the endpoints to be extracted from the json response. --- internal/metrics/metrics.go | 4 ++-- internal/models/psu_model.go | 6 ++++++ internal/models/temperature_models.go | 24 ++++++++++++++++++++++++ internal/redfish/redfish.go | 10 +++------- 4 files changed, 35 insertions(+), 9 deletions(-) create mode 100644 internal/models/psu_model.go create mode 100644 internal/models/temperature_models.go diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go index f9c95fd..9e22e05 100644 --- a/internal/metrics/metrics.go +++ b/internal/metrics/metrics.go @@ -2,7 +2,7 @@ package metrics import ( "github.com/prometheus/client_golang/prometheus" - "goprom/internal/redfish" + "goprom/internal/models" ) var ( @@ -34,7 +34,7 @@ func InitMetrics() { prometheus.MustRegister(redfishUp) } -func UpdateMetrics(power *redfish.PowerControl) { +func UpdateMetrics(power *models.PowerControl) { psuWatts.Set(power.PowerConsumedWatts) psuCapacityWatts.Set(power.PowerCapacityWatts) } diff --git a/internal/models/psu_model.go b/internal/models/psu_model.go new file mode 100644 index 0000000..d4219da --- /dev/null +++ b/internal/models/psu_model.go @@ -0,0 +1,6 @@ +package models + +type PowerControl struct { + PowerConsumedWatts float64 `json:"PowerConsumedWatts"` + PowerCapacityWatts float64 `json:"PowerCapacityWatts"` +} \ No newline at end of file diff --git a/internal/models/temperature_models.go b/internal/models/temperature_models.go new file mode 100644 index 0000000..0460d8f --- /dev/null +++ b/internal/models/temperature_models.go @@ -0,0 +1,24 @@ +package models + +type Temperature struct { + +} + + +/*Data Points Available: + - LowerThresholdCritical + - LowerThresholdFatal + - LowerThresholdNonCritical + - MaxReadingRangeTemp + - MinReadingRangeTemp + - ReadingCelsius + - SensorNumber + - UpperThresholdCritical + - UpperThresholdFatal + - UpperThresholdNonCritical + - Status + - Health + - State + + ** Name of the sensor is taken as the label for the metric +*/ \ No newline at end of file diff --git a/internal/redfish/redfish.go b/internal/redfish/redfish.go index 331b408..045a0cc 100644 --- a/internal/redfish/redfish.go +++ b/internal/redfish/redfish.go @@ -8,14 +8,10 @@ import ( "encoding/json" "net/http" "time" + "goprom/internal/models" ) -type PowerControl struct { - PowerConsumedWatts float64 `json:"PowerConsumedWatts"` - PowerCapacityWatts float64 `json:"PowerCapacityWatts"` -} - -func RetrievePowerValues() (*PowerControl, error) { +func RetrievePowerValues() (*models.PowerControl, error) { tr := &http.Transport{ TLSClientConfig: &tls.Config{ @@ -55,7 +51,7 @@ func RetrievePowerValues() (*PowerControl, error) { // body, _ := io.ReadAll(resp.Body) // fmt.Println(string(body)) - var power PowerControl + var power models.PowerControl err = json.NewDecoder(resp.Body).Decode(&power) if err != nil { return nil, err From 8985dd41939b8f0d4986ad0af4f9595725b1024c Mon Sep 17 00:00:00 2001 From: Jignesh Atul Paratane Date: Sun, 15 Feb 2026 21:23:47 +0530 Subject: [PATCH 02/31] Added a temperature structure, to be added as a metric model, this model is standard for all the components like system board, CPU, etc. #2 --- internal/models/temperature_models.go | 49 ++++++++++++++++++--------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/internal/models/temperature_models.go b/internal/models/temperature_models.go index 0460d8f..c7b2415 100644 --- a/internal/models/temperature_models.go +++ b/internal/models/temperature_models.go @@ -1,24 +1,41 @@ package models type Temperature struct { - + Name string `json:"Name"` + SensorNumber int `json:"SensorNumber"` + PhysicalContext string `json:"PhysicalContext"` + LowerThresholdCritical *float64 `json:"LowerThresholdCritical"` + LowerThresholdFatal *float64 `json:"LowerThresholdFatal"` + LowerThresholdNonCritical *float64 `json:"LowerThresholdNonCritical"` + MaxReadingRangeTemp *float64 `json:"MaxReadingRangeTemp"` + MinReadingRangeTemp *float64 `json:"MinReadingRangeTemp"` + ReadingCelsius *float64 `json:"ReadingCelsius"` + UpperThresholdCritical *float64 `json:"UpperThresholdCritical"` + UpperThresholdFatal *float64 `json:"UpperThresholdFatal"` + UpperThresholdNonCritical *float64 `json:"UpperThresholdNonCritical"` + Status Status `json:"Status"` } +type Status struct { + Health *string `json:"Health"` + State *string `json:"State"` +} /*Data Points Available: - - LowerThresholdCritical - - LowerThresholdFatal - - LowerThresholdNonCritical - - MaxReadingRangeTemp - - MinReadingRangeTemp - - ReadingCelsius - - SensorNumber - - UpperThresholdCritical - - UpperThresholdFatal - - UpperThresholdNonCritical - - Status - - Health - - State +- LowerThresholdCritical +- LowerThresholdFatal +- LowerThresholdNonCritical +- MaxReadingRangeTemp +- MinReadingRangeTemp +- ReadingCelsius +- PhysicalContext +- SensorNumber +- UpperThresholdCritical +- UpperThresholdFatal +- UpperThresholdNonCritical +- Status + - Health + - State - ** Name of the sensor is taken as the label for the metric -*/ \ No newline at end of file +** Name of the sensor is taken as the label for the metric +*/ From c647688e799b7ca3857088b04294952db722b5ed Mon Sep 17 00:00:00 2001 From: Jignesh Atul Paratane Date: Mon, 16 Feb 2026 14:45:27 +0530 Subject: [PATCH 03/31] Added a RetrieveTempertureValues() test method to retrieve the temperature metrics at a specific endpoint. #2 --- internal/redfish/redfish.go | 45 +++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/internal/redfish/redfish.go b/internal/redfish/redfish.go index 045a0cc..d90f433 100644 --- a/internal/redfish/redfish.go +++ b/internal/redfish/redfish.go @@ -59,3 +59,48 @@ func RetrievePowerValues() (*models.PowerControl, error) { return &power, nil } + +func RetrieveTemperatureValues() (*models.Temperature, error) { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + } + + client := &http.Client{ + Transport: tr, + Timeout: 30 * time.Second, + } + + req, err := http.NewRequest( + "GET", + "https://192.168.1.101/redfish/v1/Chassis/System.Embedded.1/Sensors/Temperatures/iDRAC.Embedded.1%23SystemBoardExhaustTemp", + nil, + ) + + if err != nil { + return nil, err + } + + req.SetBasicAuth("root", "calvin") + req.Header.Set("Accept", "application/json") + + resp, err := client.Do(req) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } + + var temperature models.Temperature + err = json.NewDecoder(resp.Body).Decode(&temperature) + if err != nil { + return nil, err + } + + return &temperature, nil +} \ No newline at end of file From a1445d057827ea271e3a6c7cf696e9a1fa9ec29d Mon Sep 17 00:00:00 2001 From: Jignesh Atul Paratane Date: Mon, 16 Feb 2026 14:47:37 +0530 Subject: [PATCH 04/31] Added a second fetch function to call RetrieveTemperatureMetrics test function. #2 --- cmd/exporter/main.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/cmd/exporter/main.go b/cmd/exporter/main.go index c761ef3..9fc00f9 100644 --- a/cmd/exporter/main.go +++ b/cmd/exporter/main.go @@ -12,7 +12,7 @@ import ( func main() { metrics.InitMetrics() - + metrics.InitTemperatureMetrics() go func() { //time.Sleep(20 * time.Second) ticker := time.NewTicker(60 * time.Second) @@ -28,10 +28,21 @@ func main() { metrics.UpdateHealth(true) metrics.UpdateMetrics(power) } - fetch() + fetch2 := func() { + temperature, err := redfish.RetrieveTemperatureValues() + if err != nil { + log.Printf("Error retrieving temperature values: %v", err) + metrics.UpdateHealth(false) + return + } + metrics.UpdateTemperatureMetrics(temperature) + } + fetch() + fetch2() for range ticker.C { fetch() + fetch2() } }() From bb9964249d68212abafddba0c390612e6c2d1759 Mon Sep 17 00:00:00 2001 From: Jignesh Atul Paratane Date: Mon, 16 Feb 2026 14:50:14 +0530 Subject: [PATCH 05/31] Added a new module for temperature metrics initialization and updation, the module is not built up completely, this module served as a test purpose to verify the scrape for those endpoints work as intended. #2 --- internal/metrics/temperature_metrics.go | 56 +++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 internal/metrics/temperature_metrics.go diff --git a/internal/metrics/temperature_metrics.go b/internal/metrics/temperature_metrics.go new file mode 100644 index 0000000..93b4ac3 --- /dev/null +++ b/internal/metrics/temperature_metrics.go @@ -0,0 +1,56 @@ +package metrics + +import ( + "goprom/internal/models" + + "github.com/prometheus/client_golang/prometheus" +) + +var ( + SystemBoardExhaustTemp = prometheus.NewGauge( + prometheus.GaugeOpts{ + Name: "system_board_exhaust_temperature_celsius", + Help: "Current system board exhaust temperature in Celsius", + }, + ) +) + +func InitTemperatureMetrics() { + prometheus.MustRegister(SystemBoardExhaustTemp) +} + +func UpdateTemperatureMetrics(temperature *models.Temperature) { + SystemBoardExhaustTemp.Set(*temperature.ReadingCelsius) +} + +func mapHeathToFloat(health *string) float64 { + if health == nil { + return -1 + } + + switch *health { + case "OK": + return 1 + case "Warning": + return 0.5 + case "Critical": + return 0 + default: + return -1 + } +} + +func mapStateToFloat(s *string) float64 { + if s == nil { + return -1 + } + + switch *s { + case "Enabled": + return 1 + case "Disabled": + return 0 + default: + return -1 + } +} From 258a29ed511c88ca33d59f7bdf2bb80478760615 Mon Sep 17 00:00:00 2001 From: Jignesh Atul Paratane Date: Mon, 16 Feb 2026 15:20:44 +0530 Subject: [PATCH 06/31] Changed the Gauge type of the Reading Celsius metric, from Gauge to GaugeVec, and added labels to uniquely indentify each metric. This serves as a boilerplate code for all other metrics with same properties. #2 --- internal/metrics/temperature_metrics.go | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/internal/metrics/temperature_metrics.go b/internal/metrics/temperature_metrics.go index 93b4ac3..dc52d01 100644 --- a/internal/metrics/temperature_metrics.go +++ b/internal/metrics/temperature_metrics.go @@ -1,26 +1,36 @@ package metrics import ( + "strconv" + "goprom/internal/models" "github.com/prometheus/client_golang/prometheus" ) var ( - SystemBoardExhaustTemp = prometheus.NewGauge( + ReadingCelsius = prometheus.NewGaugeVec( prometheus.GaugeOpts{ - Name: "system_board_exhaust_temperature_celsius", - Help: "Current system board exhaust temperature in Celsius", - }, + Name: "current_temperature_reading_celsius", + Help: "Current temperature reading in Celsius", + }, []string{"name", "physical_context", "sensor_number"}, ) ) func InitTemperatureMetrics() { - prometheus.MustRegister(SystemBoardExhaustTemp) + prometheus.MustRegister(ReadingCelsius) } func UpdateTemperatureMetrics(temperature *models.Temperature) { - SystemBoardExhaustTemp.Set(*temperature.ReadingCelsius) + if temperature.ReadingCelsius == nil { + return + } + + ReadingCelsius.With(prometheus.Labels{ + "name": temperature.Name, + "physical_context": temperature.PhysicalContext, + "sensor_number": strconv.Itoa(temperature.SensorNumber), + }).Set(*temperature.ReadingCelsius) } func mapHeathToFloat(health *string) float64 { From d5d5b0e3abb55bf18a28db974fe6eba76a793698 Mon Sep 17 00:00:00 2001 From: Jignesh Atul Paratane Date: Mon, 16 Feb 2026 15:46:15 +0530 Subject: [PATCH 07/31] Added several other metrics to be monitored, and updated the Name and Help to get appropriate meanings for the metrics. #2 --- internal/metrics/temperature_metrics.go | 98 ++++++++++++++++--------- 1 file changed, 62 insertions(+), 36 deletions(-) diff --git a/internal/metrics/temperature_metrics.go b/internal/metrics/temperature_metrics.go index dc52d01..7f1737f 100644 --- a/internal/metrics/temperature_metrics.go +++ b/internal/metrics/temperature_metrics.go @@ -9,58 +9,84 @@ import ( ) var ( - ReadingCelsius = prometheus.NewGaugeVec( + temperatureCelsius = prometheus.NewGaugeVec( prometheus.GaugeOpts{ - Name: "current_temperature_reading_celsius", - Help: "Current temperature reading in Celsius", - }, []string{"name", "physical_context", "sensor_number"}, + Name: "redfish_temperature_celsius", + Help: "Temperature sensor reading in Celsius.", + }, + []string{"sensor", "physical_context", "sensor_number"}, + ) + + temperatureLowerCritical = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "redfish_temperature_lower_threshold_critical_celsius", + Help: "Lower critical temperature threshold in Celsius.", + }, + []string{"sensor", "physical_context", "sensor_number"}, + ) + + temperatureLowerFatal = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "redfish_temperature_lower_threshold_fatal_celsius", + Help: "Lower fatal temperature threshold in Celsius.", + }, + []string{"sensor", "physical_context", "sensor_number"}, + ) + + temperatureUpperCritical = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "redfish_temperature_upper_threshold_critical_celsius", + Help: "Upper critical temperature threshold in Celsius.", + }, + []string{"sensor", "physical_context", "sensor_number"}, + ) + + temperatureUpperFatal = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "redfish_temperature_upper_threshold_fatal_celsius", + Help: "Upper fatal temperature threshold in Celsius.", + }, + []string{"sensor", "physical_context", "sensor_number"}, ) ) func InitTemperatureMetrics() { - prometheus.MustRegister(ReadingCelsius) + prometheus.MustRegister( + temperatureCelsius, + temperatureLowerCritical, + temperatureLowerFatal, + temperatureUpperCritical, + temperatureUpperFatal, + ) } -func UpdateTemperatureMetrics(temperature *models.Temperature) { - if temperature.ReadingCelsius == nil { +func UpdateTemperatureMetrics(temp *models.Temperature) { + + if temp.ReadingCelsius == nil { return } - ReadingCelsius.With(prometheus.Labels{ - "name": temperature.Name, - "physical_context": temperature.PhysicalContext, - "sensor_number": strconv.Itoa(temperature.SensorNumber), - }).Set(*temperature.ReadingCelsius) -} + labels := prometheus.Labels{ + "sensor": temp.Name, + "physical_context": temp.PhysicalContext, + "sensor_number": strconv.Itoa(temp.SensorNumber), + } -func mapHeathToFloat(health *string) float64 { - if health == nil { - return -1 + temperatureCelsius.With(labels).Set(*temp.ReadingCelsius) + + if temp.LowerThresholdCritical != nil { + temperatureLowerCritical.With(labels).Set(*temp.LowerThresholdCritical) } - switch *health { - case "OK": - return 1 - case "Warning": - return 0.5 - case "Critical": - return 0 - default: - return -1 + if temp.LowerThresholdFatal != nil { + temperatureLowerFatal.With(labels).Set(*temp.LowerThresholdFatal) } -} -func mapStateToFloat(s *string) float64 { - if s == nil { - return -1 + if temp.UpperThresholdCritical != nil { + temperatureUpperCritical.With(labels).Set(*temp.UpperThresholdCritical) } - switch *s { - case "Enabled": - return 1 - case "Disabled": - return 0 - default: - return -1 + if temp.UpperThresholdFatal != nil { + temperatureUpperFatal.With(labels).Set(*temp.UpperThresholdFatal) } } From 320873a9e233575fd1efba64ede266c871ffe9b6 Mon Sep 17 00:00:00 2001 From: Jignesh Atul Paratane Date: Mon, 16 Feb 2026 17:39:59 +0530 Subject: [PATCH 08/31] Added more metrics for monitoring, some of these include the state and health for a particular component of the server. #2 --- internal/metrics/temperature_metrics.go | 79 +++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/internal/metrics/temperature_metrics.go b/internal/metrics/temperature_metrics.go index 7f1737f..192ed24 100644 --- a/internal/metrics/temperature_metrics.go +++ b/internal/metrics/temperature_metrics.go @@ -48,6 +48,38 @@ var ( }, []string{"sensor", "physical_context", "sensor_number"}, ) + + lowerThresholdNonCritical = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "redfish_temperature_lower_threshold_non_critical_celsius", + Help: "Lower non-critical temperature threshold in Celsius.", + }, + []string{"sensor", "physical_context", "sensor_number"}, + ) + + upperThresholdNonCritical = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "redfish_temperature_upper_threshold_non_critical_celsius", + Help: "Upper non-critical temperature threshold in Celsius.", + }, + []string{"sensor", "physical_context", "sensor_number"}, + ) + + TemperatureHealth = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "redfish_temperature_health", + Help: "Health status of the temperature sensor (1=OK, 0.5=Warning, 0=Critical, -1=Unknown).", + }, + []string{"sensor", "physical_context", "sensor_number"}, + ) + + TemperatureState = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "redfish_temperature_state", + Help: "State of the temperature sensor (1=Enabled, 0=Disabled, -1=Unknown).", + }, + []string{"sensor", "physical_context", "sensor_number"}, + ) ) func InitTemperatureMetrics() { @@ -57,6 +89,10 @@ func InitTemperatureMetrics() { temperatureLowerFatal, temperatureUpperCritical, temperatureUpperFatal, + TemperatureHealth, + TemperatureState, + lowerThresholdNonCritical, + upperThresholdNonCritical, ) } @@ -89,4 +125,47 @@ func UpdateTemperatureMetrics(temp *models.Temperature) { if temp.UpperThresholdFatal != nil { temperatureUpperFatal.With(labels).Set(*temp.UpperThresholdFatal) } + + if temp.LowerThresholdNonCritical != nil { + lowerThresholdNonCritical.With(labels).Set(*temp.LowerThresholdNonCritical) + } + + if temp.UpperThresholdNonCritical != nil { + upperThresholdNonCritical.With(labels).Set(*temp.UpperThresholdNonCritical) + } + + TemperatureHealth.With(labels).Set(mapHealthToFloat(temp.Status.Health)) + TemperatureState.With(labels).Set(mapStateToFloat(temp.Status.State)) +} + +func mapHealthToFloat(h *string) float64 { + if h == nil { + return -1 + } + + switch *h { + case "OK": + return 1 + case "Warning": + return 0.5 + case "Critical": + return 0 + default: + return -1 + } +} + +func mapStateToFloat(s *string) float64 { + if s == nil { + return -1 + } + + switch *s { + case "Enabled": + return 1 + case "Disabled": + return 0 + default: + return -1 + } } From 64421548fb9b1f13d1c4eea829df09e3b336e271 Mon Sep 17 00:00:00 2001 From: Jignesh Atul Paratane Date: Mon, 16 Feb 2026 18:39:41 +0530 Subject: [PATCH 09/31] Adjusted the code structure for better visibility. #2 --- internal/redfish/redfish.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/redfish/redfish.go b/internal/redfish/redfish.go index d90f433..92377ea 100644 --- a/internal/redfish/redfish.go +++ b/internal/redfish/redfish.go @@ -6,9 +6,9 @@ import ( // "io" "encoding/json" + "goprom/internal/models" "net/http" "time" - "goprom/internal/models" ) func RetrievePowerValues() (*models.PowerControl, error) { @@ -103,4 +103,4 @@ func RetrieveTemperatureValues() (*models.Temperature, error) { } return &temperature, nil -} \ No newline at end of file +} From 280fff8b63a3fed1b1069cf654750c3a40a07433 Mon Sep 17 00:00:00 2001 From: Jignesh Atul Paratane Date: Tue, 17 Feb 2026 11:35:37 +0530 Subject: [PATCH 10/31] Added a endpoints.go file to put all polling metrics, and be used for testing purposes when in test stages in CI. #3 --- internal/redfish/endpoints.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 internal/redfish/endpoints.go diff --git a/internal/redfish/endpoints.go b/internal/redfish/endpoints.go new file mode 100644 index 0000000..459db4b --- /dev/null +++ b/internal/redfish/endpoints.go @@ -0,0 +1,16 @@ +package redfish + +var power_endpoints = []string { + "/redfish/v1/Chassis/System.Embedded.1/Power/PowerControl", +} + +var temperature_endpoints = []string { + "/redfish/v1/Chassis/System.Embedded.1/Sensors/Temperatures/iDRAC.Embedded.1%23SystemBoardInletTemp", + "/redfish/v1/Chassis/System.Embedded.1/Sensors/Temperatures/iDRAC.Embedded.1%23SystemBoardExhaustTemp", + "/redfish/v1/Chassis/System.Embedded.1/Sensors/Temperatures/iDRAC.Embedded.1%23CPU2Temp", + "/redfish/v1/Chassis/System.Embedded.1/Sensors/Temperatures/iDRAC.Embedded.1%23CPU1Temp", +} + +var fan_endpoints = []string { + +} \ No newline at end of file From ef59651d28a479acd7871f63d851b368ac1a8540 Mon Sep 17 00:00:00 2001 From: Jignesh Atul Paratane Date: Tue, 17 Feb 2026 11:37:27 +0530 Subject: [PATCH 11/31] Added fan endpoints endpoints.go file to put all polling metrics. #3 --- internal/redfish/endpoints.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/internal/redfish/endpoints.go b/internal/redfish/endpoints.go index 459db4b..23bac10 100644 --- a/internal/redfish/endpoints.go +++ b/internal/redfish/endpoints.go @@ -1,16 +1,21 @@ package redfish -var power_endpoints = []string { +var power_endpoints = []string{ "/redfish/v1/Chassis/System.Embedded.1/Power/PowerControl", } -var temperature_endpoints = []string { +var temperature_endpoints = []string{ "/redfish/v1/Chassis/System.Embedded.1/Sensors/Temperatures/iDRAC.Embedded.1%23SystemBoardInletTemp", "/redfish/v1/Chassis/System.Embedded.1/Sensors/Temperatures/iDRAC.Embedded.1%23SystemBoardExhaustTemp", "/redfish/v1/Chassis/System.Embedded.1/Sensors/Temperatures/iDRAC.Embedded.1%23CPU2Temp", "/redfish/v1/Chassis/System.Embedded.1/Sensors/Temperatures/iDRAC.Embedded.1%23CPU1Temp", } -var fan_endpoints = []string { - -} \ No newline at end of file +var fan_endpoints = []string{ + "/redfish/v1/Chassis/System.Embedded.1/Sensors/Fans/0x17%7C%7CFan.Embedded.1", + "/redfish/v1/Chassis/System.Embedded.1/Sensors/Fans/0x17%7C%7CFan.Embedded.2", + "/redfish/v1/Chassis/System.Embedded.1/Sensors/Fans/0x17%7C%7CFan.Embedded.3", + "/redfish/v1/Chassis/System.Embedded.1/Sensors/Fans/0x17%7C%7CFan.Embedded.4", + "/redfish/v1/Chassis/System.Embedded.1/Sensors/Fans/0x17%7C%7CFan.Embedded.5", + "/redfish/v1/Chassis/System.Embedded.1/Sensors/Fans/0x17%7C%7CFan.Embedded.6", +} From a196d5b7f3b17087b6dd0f735f970c3560994bf1 Mon Sep 17 00:00:00 2001 From: Jignesh Atul Paratane Date: Tue, 17 Feb 2026 11:55:58 +0530 Subject: [PATCH 12/31] Added a empty client.go file with a empty defined struct, which will later be written upon structure clarfiication of client calling stages and HTTP requests processes. #3 --- internal/redfish/client.go | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 internal/redfish/client.go diff --git a/internal/redfish/client.go b/internal/redfish/client.go new file mode 100644 index 0000000..b65a16e --- /dev/null +++ b/internal/redfish/client.go @@ -0,0 +1,6 @@ +package redfish + +type Client struct { +} + +/*The client.go file serves as a centrailzed client creation for all HTTP requests, and allows using same client for different endpoint types.*/ \ No newline at end of file From 706ab5943bec7e21d91cf8c699cbba1baf52e688 Mon Sep 17 00:00:00 2001 From: Jignesh Atul Paratane Date: Tue, 17 Feb 2026 12:07:37 +0530 Subject: [PATCH 13/31] Defined the Client struct in client.go module, this struct is universal for all the client creations. #3 --- internal/redfish/client.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/internal/redfish/client.go b/internal/redfish/client.go index b65a16e..2a16286 100644 --- a/internal/redfish/client.go +++ b/internal/redfish/client.go @@ -1,6 +1,14 @@ package redfish +import ( + "net/http" +) + type Client struct { + BaseURL string + Username string + Password string + HTTPClient *http.Client } -/*The client.go file serves as a centrailzed client creation for all HTTP requests, and allows using same client for different endpoint types.*/ \ No newline at end of file +/*The client.go file serves as a centrailzed client creation for all HTTP requests, and allows using same client for different endpoint types.*/ From cdc027d96c57de6742350b01c3460f410a1714f0 Mon Sep 17 00:00:00 2001 From: Jignesh Atul Paratane Date: Tue, 17 Feb 2026 13:58:04 +0530 Subject: [PATCH 14/31] Added lint.yml file for linting tests to be done on the codebase, this test will ensure that correct formatting of code occurs and any unused code will be flagged and notified. #4 --- .github/workflows/lint.yml | 42 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/workflows/lint.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..3d1e640 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,42 @@ +name: Go-Lint + +on: + push: + branches: + - develop + - main + pull_request: + branches: + - develop + - main + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.25' + + - name: Check formatting (gofmt) + run: | + if [ -n "$(gofmt -l .)" ]; then + echo "The following files are not properly formatted:" + gofmt -l . + exit 1 + fi + + - name: Run go vet + run: go vet ./... + + - name: Install golangci-lint + run: | + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.52.2 + + - name: Run golangci-lint + run: golangci-lint run \ No newline at end of file From 7f84d3b8403c9e16cac6177a6310574fe284acf0 Mon Sep 17 00:00:00 2001 From: Jignesh Atul Paratane Date: Tue, 17 Feb 2026 14:00:46 +0530 Subject: [PATCH 15/31] chore: format code using gofmt --- internal/metrics/metrics.go | 6 +++--- internal/models/psu_model.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go index 9e22e05..2489648 100644 --- a/internal/metrics/metrics.go +++ b/internal/metrics/metrics.go @@ -15,8 +15,8 @@ var ( psuCapacityWatts = prometheus.NewGauge( prometheus.GaugeOpts{ - Name:"power_capacity_watts", - Help:"Maximum PSU power capacity in watts", + Name: "power_capacity_watts", + Help: "Maximum PSU power capacity in watts", }, ) @@ -45,4 +45,4 @@ func UpdateHealth(up bool) { } else { redfishUp.Set(0) } -} \ No newline at end of file +} diff --git a/internal/models/psu_model.go b/internal/models/psu_model.go index d4219da..8e0ac37 100644 --- a/internal/models/psu_model.go +++ b/internal/models/psu_model.go @@ -3,4 +3,4 @@ package models type PowerControl struct { PowerConsumedWatts float64 `json:"PowerConsumedWatts"` PowerCapacityWatts float64 `json:"PowerCapacityWatts"` -} \ No newline at end of file +} From e7ed9c3069cecc25e131718737bb4d704e3e789b Mon Sep 17 00:00:00 2001 From: Jignesh Atul Paratane Date: Tue, 17 Feb 2026 14:04:32 +0530 Subject: [PATCH 16/31] Modified the lint.yml workflow file, to resolve the version compability issue of golangci-lint with the current version of Go used in project. #4 --- .github/workflows/lint.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 3d1e640..cf11070 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -34,9 +34,7 @@ jobs: - name: Run go vet run: go vet ./... - - name: Install golangci-lint - run: | - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.52.2 - - name: Run golangci-lint - run: golangci-lint run \ No newline at end of file + uses: golangci/golangci-lint-action@v6 + with: + version: latest \ No newline at end of file From 8b73a54b09f75c2e251d65f7c52ece4b29021fdf Mon Sep 17 00:00:00 2001 From: Jignesh Atul Paratane Date: Tue, 17 Feb 2026 14:07:02 +0530 Subject: [PATCH 17/31] Changed the version of go to 1.24 from 1.25 for enabling the use of golangci-lint test pack. --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index a6c1467..f9d0ab7 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module goprom -go 1.25.0 +go 1.24.0 require ( github.com/beorn7/perks v1.0.1 // indirect From 814e0cf8279a5ac06b1f6e0e3da1db67fbec5bd0 Mon Sep 17 00:00:00 2001 From: Jignesh Atul Paratane Date: Tue, 17 Feb 2026 14:17:04 +0530 Subject: [PATCH 18/31] Commented out the unused endpoints.go file contents to eliminate the workflow failure pointing out to this specific file. --- internal/redfish/endpoints.go | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/internal/redfish/endpoints.go b/internal/redfish/endpoints.go index 23bac10..c3ec0c2 100644 --- a/internal/redfish/endpoints.go +++ b/internal/redfish/endpoints.go @@ -1,21 +1,21 @@ package redfish -var power_endpoints = []string{ - "/redfish/v1/Chassis/System.Embedded.1/Power/PowerControl", -} +// var power_endpoints = []string{ +// "/redfish/v1/Chassis/System.Embedded.1/Power/PowerControl", +// } -var temperature_endpoints = []string{ - "/redfish/v1/Chassis/System.Embedded.1/Sensors/Temperatures/iDRAC.Embedded.1%23SystemBoardInletTemp", - "/redfish/v1/Chassis/System.Embedded.1/Sensors/Temperatures/iDRAC.Embedded.1%23SystemBoardExhaustTemp", - "/redfish/v1/Chassis/System.Embedded.1/Sensors/Temperatures/iDRAC.Embedded.1%23CPU2Temp", - "/redfish/v1/Chassis/System.Embedded.1/Sensors/Temperatures/iDRAC.Embedded.1%23CPU1Temp", -} +// var temperature_endpoints = []string{ +// "/redfish/v1/Chassis/System.Embedded.1/Sensors/Temperatures/iDRAC.Embedded.1%23SystemBoardInletTemp", +// "/redfish/v1/Chassis/System.Embedded.1/Sensors/Temperatures/iDRAC.Embedded.1%23SystemBoardExhaustTemp", +// "/redfish/v1/Chassis/System.Embedded.1/Sensors/Temperatures/iDRAC.Embedded.1%23CPU2Temp", +// "/redfish/v1/Chassis/System.Embedded.1/Sensors/Temperatures/iDRAC.Embedded.1%23CPU1Temp", +// } -var fan_endpoints = []string{ - "/redfish/v1/Chassis/System.Embedded.1/Sensors/Fans/0x17%7C%7CFan.Embedded.1", - "/redfish/v1/Chassis/System.Embedded.1/Sensors/Fans/0x17%7C%7CFan.Embedded.2", - "/redfish/v1/Chassis/System.Embedded.1/Sensors/Fans/0x17%7C%7CFan.Embedded.3", - "/redfish/v1/Chassis/System.Embedded.1/Sensors/Fans/0x17%7C%7CFan.Embedded.4", - "/redfish/v1/Chassis/System.Embedded.1/Sensors/Fans/0x17%7C%7CFan.Embedded.5", - "/redfish/v1/Chassis/System.Embedded.1/Sensors/Fans/0x17%7C%7CFan.Embedded.6", -} +// var fan_endpoints = []string{ +// "/redfish/v1/Chassis/System.Embedded.1/Sensors/Fans/0x17%7C%7CFan.Embedded.1", +// "/redfish/v1/Chassis/System.Embedded.1/Sensors/Fans/0x17%7C%7CFan.Embedded.2", +// "/redfish/v1/Chassis/System.Embedded.1/Sensors/Fans/0x17%7C%7CFan.Embedded.3", +// "/redfish/v1/Chassis/System.Embedded.1/Sensors/Fans/0x17%7C%7CFan.Embedded.4", +// "/redfish/v1/Chassis/System.Embedded.1/Sensors/Fans/0x17%7C%7CFan.Embedded.5", +// "/redfish/v1/Chassis/System.Embedded.1/Sensors/Fans/0x17%7C%7CFan.Embedded.6", +// } From dc00678ed8cdf8243e72001460b049b39e274197 Mon Sep 17 00:00:00 2001 From: Jignesh Atul Paratane Date: Tue, 17 Feb 2026 17:28:39 +0530 Subject: [PATCH 19/31] Added the Constructor for calling the Client struct and initialized the http request logic with TLS verification to off. #3 --- internal/redfish/client.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/internal/redfish/client.go b/internal/redfish/client.go index 2a16286..4fcccf0 100644 --- a/internal/redfish/client.go +++ b/internal/redfish/client.go @@ -1,7 +1,9 @@ package redfish import ( + "time" "net/http" + "crypto/tls" ) type Client struct { @@ -11,4 +13,22 @@ type Client struct { HTTPClient *http.Client } +func NewClient(baseURL, username, password string) *Client { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + } + + return &Client{ + BaseURL: baseURL, + Username: username, + Password: password, + HTTPClient: &http.Client{ + Transport: tr, + Timeout: 15 * time.Second, + }, + } +} + /*The client.go file serves as a centrailzed client creation for all HTTP requests, and allows using same client for different endpoint types.*/ From 24e749868f776f8ba860bc6fa637cf27b9bb96ad Mon Sep 17 00:00:00 2001 From: Jignesh Atul Paratane Date: Tue, 17 Feb 2026 17:36:47 +0530 Subject: [PATCH 20/31] Added the centralized get function to call all the endpoints, this get function can be used for all the different endpoints, provided the endpoint url exists, and an appropriate struct exists for the method to put values in. #3 --- internal/redfish/client.go | 41 ++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/internal/redfish/client.go b/internal/redfish/client.go index 4fcccf0..385ec03 100644 --- a/internal/redfish/client.go +++ b/internal/redfish/client.go @@ -1,9 +1,11 @@ package redfish import ( - "time" - "net/http" "crypto/tls" + "encoding/json" + "fmt" + "net/http" + "time" ) type Client struct { @@ -21,14 +23,45 @@ func NewClient(baseURL, username, password string) *Client { } return &Client{ - BaseURL: baseURL, + BaseURL: baseURL, Username: username, Password: password, HTTPClient: &http.Client{ Transport: tr, - Timeout: 15 * time.Second, + Timeout: 15 * time.Second, }, } } +func (c *Client) Get(endpoint string, target interface{}) (int, error) { + req, err := http.NewRequest("GET", c.BaseURL+endpoint, nil) + if err != nil { + return 0, nil + } + + req.SetBasicAuth(c.Username, c.Password) + req.Header.Set("Accept", "application/json") + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return 0, nil + } + + defer resp.Body.Close() + + if resp.StatusCode == http.StatusNotFound { + return http.StatusNotFound, nil + } + + if resp.StatusCode != http.StatusOK { + return resp.StatusCode, fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } + + if err := json.NewDecoder(resp.Body).Decode(target); err != nil { + return resp.StatusCode, err + } + + return resp.StatusCode, nil +} + /*The client.go file serves as a centrailzed client creation for all HTTP requests, and allows using same client for different endpoint types.*/ From c90ea57f8089033499871fb8ffe3f31b050b4f44 Mon Sep 17 00:00:00 2001 From: Jignesh Atul Paratane Date: Tue, 17 Feb 2026 17:45:24 +0530 Subject: [PATCH 21/31] Fixed the returns of two if conditions in the Get() method, replaced the return nil, to return err to prevent the supression of errors. #3 --- internal/redfish/client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/redfish/client.go b/internal/redfish/client.go index 385ec03..4443d31 100644 --- a/internal/redfish/client.go +++ b/internal/redfish/client.go @@ -36,7 +36,7 @@ func NewClient(baseURL, username, password string) *Client { func (c *Client) Get(endpoint string, target interface{}) (int, error) { req, err := http.NewRequest("GET", c.BaseURL+endpoint, nil) if err != nil { - return 0, nil + return 0, err } req.SetBasicAuth(c.Username, c.Password) @@ -44,7 +44,7 @@ func (c *Client) Get(endpoint string, target interface{}) (int, error) { resp, err := c.HTTPClient.Do(req) if err != nil { - return 0, nil + return 0, err } defer resp.Body.Close() From 732cbf251eb810413af679629295d28698e689e1 Mon Sep 17 00:00:00 2001 From: Jignesh Atul Paratane Date: Tue, 17 Feb 2026 18:40:13 +0530 Subject: [PATCH 22/31] Added redfish_temperature.go module, this module specifically uses the newly defined client structure and function, and uses endpoints.go for getting the BaseURL and endpoints to poll values from. #3 --- internal/redfish/redfish_temperature.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 internal/redfish/redfish_temperature.go diff --git a/internal/redfish/redfish_temperature.go b/internal/redfish/redfish_temperature.go new file mode 100644 index 0000000..34c03f1 --- /dev/null +++ b/internal/redfish/redfish_temperature.go @@ -0,0 +1,24 @@ +package redfish + +import "goprom/internal/models" + +func (c *Client) GetTemperatures(endpoints []string) ([]*models.Temperature, error) { + var results []*models.Temperature + + for _, endpoint := range endpoints { + var temp models.Temperature + + status, err := c.Get(endpoint, &temp) + if err != nil { + continue + } + + if status == 404 { + continue + } + + results = append(results, &temp) + } + + return results, nil +} From 0256136b28e00907ad0e0797e78a8e51f8b2c616 Mon Sep 17 00:00:00 2001 From: Jignesh Atul Paratane Date: Tue, 17 Feb 2026 18:42:11 +0530 Subject: [PATCH 23/31] Uncommented the endpoints, and added a base url to be used as a supplicant for client structs and for different types of endpoint metrics. #3 --- internal/redfish/endpoints.go | 36 ++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/internal/redfish/endpoints.go b/internal/redfish/endpoints.go index c3ec0c2..8c224f0 100644 --- a/internal/redfish/endpoints.go +++ b/internal/redfish/endpoints.go @@ -1,21 +1,23 @@ package redfish -// var power_endpoints = []string{ -// "/redfish/v1/Chassis/System.Embedded.1/Power/PowerControl", -// } +var BaseURL = "https://192.168.1.101" -// var temperature_endpoints = []string{ -// "/redfish/v1/Chassis/System.Embedded.1/Sensors/Temperatures/iDRAC.Embedded.1%23SystemBoardInletTemp", -// "/redfish/v1/Chassis/System.Embedded.1/Sensors/Temperatures/iDRAC.Embedded.1%23SystemBoardExhaustTemp", -// "/redfish/v1/Chassis/System.Embedded.1/Sensors/Temperatures/iDRAC.Embedded.1%23CPU2Temp", -// "/redfish/v1/Chassis/System.Embedded.1/Sensors/Temperatures/iDRAC.Embedded.1%23CPU1Temp", -// } +var Power_Endpoints = []string{ + "/redfish/v1/Chassis/System.Embedded.1/Power/PowerControl", +} -// var fan_endpoints = []string{ -// "/redfish/v1/Chassis/System.Embedded.1/Sensors/Fans/0x17%7C%7CFan.Embedded.1", -// "/redfish/v1/Chassis/System.Embedded.1/Sensors/Fans/0x17%7C%7CFan.Embedded.2", -// "/redfish/v1/Chassis/System.Embedded.1/Sensors/Fans/0x17%7C%7CFan.Embedded.3", -// "/redfish/v1/Chassis/System.Embedded.1/Sensors/Fans/0x17%7C%7CFan.Embedded.4", -// "/redfish/v1/Chassis/System.Embedded.1/Sensors/Fans/0x17%7C%7CFan.Embedded.5", -// "/redfish/v1/Chassis/System.Embedded.1/Sensors/Fans/0x17%7C%7CFan.Embedded.6", -// } +var Temperature_Endpoints = []string{ + "/redfish/v1/Chassis/System.Embedded.1/Sensors/Temperatures/iDRAC.Embedded.1%23SystemBoardInletTemp", + "/redfish/v1/Chassis/System.Embedded.1/Sensors/Temperatures/iDRAC.Embedded.1%23SystemBoardExhaustTemp", + "/redfish/v1/Chassis/System.Embedded.1/Sensors/Temperatures/iDRAC.Embedded.1%23CPU2Temp", + "/redfish/v1/Chassis/System.Embedded.1/Sensors/Temperatures/iDRAC.Embedded.1%23CPU1Temp", +} + +var Fan_Endpoints = []string{ + "/redfish/v1/Chassis/System.Embedded.1/Sensors/Fans/0x17%7C%7CFan.Embedded.1", + "/redfish/v1/Chassis/System.Embedded.1/Sensors/Fans/0x17%7C%7CFan.Embedded.2", + "/redfish/v1/Chassis/System.Embedded.1/Sensors/Fans/0x17%7C%7CFan.Embedded.3", + "/redfish/v1/Chassis/System.Embedded.1/Sensors/Fans/0x17%7C%7CFan.Embedded.4", + "/redfish/v1/Chassis/System.Embedded.1/Sensors/Fans/0x17%7C%7CFan.Embedded.5", + "/redfish/v1/Chassis/System.Embedded.1/Sensors/Fans/0x17%7C%7CFan.Embedded.6", +} From 1f268611cdd4dd1c90101c0b27053c7a2b7fb04f Mon Sep 17 00:00:00 2001 From: Jignesh Atul Paratane Date: Tue, 17 Feb 2026 18:45:02 +0530 Subject: [PATCH 24/31] Added a thrid fetch function this fetch function is responsible for calling the temperature metrics update method to update based on the ticker time interval. #2 --- cmd/exporter/main.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/cmd/exporter/main.go b/cmd/exporter/main.go index 9fc00f9..052c7f8 100644 --- a/cmd/exporter/main.go +++ b/cmd/exporter/main.go @@ -38,11 +38,31 @@ func main() { } metrics.UpdateTemperatureMetrics(temperature) } + + fetch3 := func() { + client := redfish.NewClient( + redfish.BaseURL, + "root", + "calvin", + ) + + temps, err := client.GetTemperatures(redfish.Temperature_Endpoints) + if err != nil { + log.Printf("Error retrieving temperature values: %v", err) + return + } + + for _, t := range temps { + metrics.UpdateTemperatureMetrics(t) + } + } fetch() fetch2() + fetch3() for range ticker.C { fetch() fetch2() + fetch3() } }() From ada8bd0803eb9171b8ba46060d1f9478aeb73ac9 Mon Sep 17 00:00:00 2001 From: Jignesh Atul Paratane Date: Wed, 18 Feb 2026 22:41:37 +0530 Subject: [PATCH 25/31] Added a directory collector, and a code file collector.go in which the collector will be defined, right now only the client interface and struct is defined. #5 --- internal/collector/redfish_collector.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 internal/collector/redfish_collector.go diff --git a/internal/collector/redfish_collector.go b/internal/collector/redfish_collector.go new file mode 100644 index 0000000..c96e2b2 --- /dev/null +++ b/internal/collector/redfish_collector.go @@ -0,0 +1,15 @@ +package collector + +import "github.com/prometheus/client_golang/prometheus" + +type RedfishAPI interface { + Get(endpoint string, target interface{}) (int, error) +} + +type RedfishCollector struct { + client RedfishAPI + + powerConsumedDesc *prometheus.Desc + powerCapacityDesc *prometheus.Desc + redfishUpDesc *prometheus.Desc +} From 9c4a2c2a3044595ee4019972970770be7e325145 Mon Sep 17 00:00:00 2001 From: Jignesh Atul Paratane Date: Wed, 18 Feb 2026 22:51:16 +0530 Subject: [PATCH 26/31] Added a Describe() method standard to Collector interface, and created a new constructor to initialize metrics. #5 --- internal/collector/redfish_collector.go | 33 +++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/internal/collector/redfish_collector.go b/internal/collector/redfish_collector.go index c96e2b2..55afd05 100644 --- a/internal/collector/redfish_collector.go +++ b/internal/collector/redfish_collector.go @@ -13,3 +13,36 @@ type RedfishCollector struct { powerCapacityDesc *prometheus.Desc redfishUpDesc *prometheus.Desc } + +func NewRedfishCollector(client RedfishAPI) *RedfishCollector { + return &RedfishCollector{ + client: client, + powerConsumedDesc: prometheus.NewDesc( + "power_consumed_watts", + "Current PSU power consumption in watts", + nil, + nil, + ), + + powerCapacityDesc: prometheus.NewDesc( + "power_capacity_watts", + "PSU power capacity in watts", + nil, + nil, + ), + + redfishUpDesc: prometheus.NewDesc( + "redfish_up", + "Redfish API Response Status (1 = up, 0 = down)", + nil, + nil, + ), + } +} + +func (c *RedfishCollector) Describe(ch chan<- *prometheus.Desc) { + ch <- c.powerConsumedDesc + ch <- c.powerCapacityDesc + ch <- c.redfishUpDesc +} + From 5eaed1581f374ace53d5367af6a938e7f5897dce Mon Sep 17 00:00:00 2001 From: Jignesh Atul Paratane Date: Wed, 18 Feb 2026 23:07:16 +0530 Subject: [PATCH 27/31] Added the Collect() method implementing the collector interface. Added some trivial metrics to be tested for robustness and reliability. #5 --- internal/collector/redfish_collector.go | 56 ++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/internal/collector/redfish_collector.go b/internal/collector/redfish_collector.go index 55afd05..8197ac7 100644 --- a/internal/collector/redfish_collector.go +++ b/internal/collector/redfish_collector.go @@ -1,6 +1,11 @@ package collector -import "github.com/prometheus/client_golang/prometheus" +import ( + "goprom/internal/models" + "net/http" + + "github.com/prometheus/client_golang/prometheus" +) type RedfishAPI interface { Get(endpoint string, target interface{}) (int, error) @@ -46,3 +51,52 @@ func (c *RedfishCollector) Describe(ch chan<- *prometheus.Desc) { ch <- c.redfishUpDesc } +func (c *RedfishCollector) Collect(ch chan<- prometheus.Metric) { + + var power models.PowerControl + status, err := c.client.Get("/redfish/v1/Chassis/System.Embedded.1/Power", &power) + if err != nil { + ch <- prometheus.MustNewConstMetric( + c.redfishUpDesc, + prometheus.GaugeValue, + 0, + ) + return + } + + if status == http.StatusNotFound { + ch <- prometheus.MustNewConstMetric( + c.redfishUpDesc, + prometheus.GaugeValue, + 1, + ) + return + } + + if status != http.StatusOK { + ch <- prometheus.MustNewConstMetric( + c.redfishUpDesc, + prometheus.GaugeValue, + 0, + ) + return + } + + ch <- prometheus.MustNewConstMetric( + c.powerConsumedDesc, + prometheus.GaugeValue, + float64(power.PowerConsumedWatts), + ) + + ch <- prometheus.MustNewConstMetric( + c.powerCapacityDesc, + prometheus.GaugeValue, + float64(power.PowerCapacityWatts), + ) + + ch <- prometheus.MustNewConstMetric( + c.redfishUpDesc, + prometheus.GaugeValue, + 1, + ) +} From e856c636eb7d76f14c2756866fa4348ff68b0626 Mon Sep 17 00:00:00 2001 From: Jignesh Atul Paratane Date: Thu, 19 Feb 2026 14:11:00 +0530 Subject: [PATCH 28/31] Fixed the endpoint url, to fix the 0 value return at scrape time issue. #5 --- internal/collector/redfish_collector.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/collector/redfish_collector.go b/internal/collector/redfish_collector.go index 8197ac7..398978a 100644 --- a/internal/collector/redfish_collector.go +++ b/internal/collector/redfish_collector.go @@ -54,7 +54,7 @@ func (c *RedfishCollector) Describe(ch chan<- *prometheus.Desc) { func (c *RedfishCollector) Collect(ch chan<- prometheus.Metric) { var power models.PowerControl - status, err := c.client.Get("/redfish/v1/Chassis/System.Embedded.1/Power", &power) + status, err := c.client.Get("/redfish/v1/Chassis/System.Embedded.1/Power/PowerControl", &power) if err != nil { ch <- prometheus.MustNewConstMetric( c.redfishUpDesc, From b373a4c04231637bd402bca3094213283035de5b Mon Sep 17 00:00:00 2001 From: Jignesh Atul Paratane Date: Thu, 19 Feb 2026 14:12:25 +0530 Subject: [PATCH 29/31] Commented out the legacy Ticker Method based calling and replaced with Collector based calling. #5 --- cmd/exporter/main.go | 110 +++++++++++++++++++++++-------------------- 1 file changed, 59 insertions(+), 51 deletions(-) diff --git a/cmd/exporter/main.go b/cmd/exporter/main.go index 052c7f8..288428c 100644 --- a/cmd/exporter/main.go +++ b/cmd/exporter/main.go @@ -1,70 +1,78 @@ package main import ( - "goprom/internal/metrics" + "goprom/internal/collector" "goprom/internal/redfish" "log" "net/http" - "time" + "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" ) func main() { - metrics.InitMetrics() - metrics.InitTemperatureMetrics() - go func() { - //time.Sleep(20 * time.Second) - ticker := time.NewTicker(60 * time.Second) - defer ticker.Stop() + // metrics.InitMetrics() + // metrics.InitTemperatureMetrics() + // go func() { + // //time.Sleep(20 * time.Second) + // ticker := time.NewTicker(60 * time.Second) + // defer ticker.Stop() - fetch := func() { - power, err := redfish.RetrievePowerValues() - if err != nil { - log.Printf("Error retrieving power values: %v", err) - metrics.UpdateHealth(false) - return - } - metrics.UpdateHealth(true) - metrics.UpdateMetrics(power) - } + // fetch := func() { + // power, err := redfish.RetrievePowerValues() + // if err != nil { + // log.Printf("Error retrieving power values: %v", err) + // metrics.UpdateHealth(false) + // return + // } + // metrics.UpdateHealth(true) + // metrics.UpdateMetrics(power) + // } - fetch2 := func() { - temperature, err := redfish.RetrieveTemperatureValues() - if err != nil { - log.Printf("Error retrieving temperature values: %v", err) - metrics.UpdateHealth(false) - return - } - metrics.UpdateTemperatureMetrics(temperature) - } + // fetch2 := func() { + // temperature, err := redfish.RetrieveTemperatureValues() + // if err != nil { + // log.Printf("Error retrieving temperature values: %v", err) + // metrics.UpdateHealth(false) + // return + // } + // metrics.UpdateTemperatureMetrics(temperature) + // } - fetch3 := func() { - client := redfish.NewClient( - redfish.BaseURL, - "root", - "calvin", - ) + // fetch3 := func() { + // client := redfish.NewClient( + // redfish.BaseURL, + // "root", + // "calvin", + // ) - temps, err := client.GetTemperatures(redfish.Temperature_Endpoints) - if err != nil { - log.Printf("Error retrieving temperature values: %v", err) - return - } + // temps, err := client.GetTemperatures(redfish.Temperature_Endpoints) + // if err != nil { + // log.Printf("Error retrieving temperature values: %v", err) + // return + // } - for _, t := range temps { - metrics.UpdateTemperatureMetrics(t) - } - } - fetch() - fetch2() - fetch3() - for range ticker.C { - fetch() - fetch2() - fetch3() - } - }() + // for _, t := range temps { + // metrics.UpdateTemperatureMetrics(t) + // } + // } + // fetch() + // fetch2() + // fetch3() + // for range ticker.C { + // fetch() + // fetch2() + // fetch3() + // } + // }() + + rfCollector := collector.NewRedfishCollector(redfish.NewClient( + redfish.BaseURL, + "root", + "calvin", + )) + + prometheus.MustRegister(rfCollector) http.Handle("/metrics", promhttp.Handler()) From 4c2427c275a2a3d2fbf13c165ba0ee2b52d3bf98 Mon Sep 17 00:00:00 2001 From: Jignesh Atul Paratane Date: Thu, 19 Feb 2026 14:36:23 +0530 Subject: [PATCH 30/31] Implemented the collector interface and emitting of temperature metrics. #5 --- internal/collector/redfish_collector.go | 250 ++++++++++++++++++++---- 1 file changed, 215 insertions(+), 35 deletions(-) diff --git a/internal/collector/redfish_collector.go b/internal/collector/redfish_collector.go index 398978a..ae2631a 100644 --- a/internal/collector/redfish_collector.go +++ b/internal/collector/redfish_collector.go @@ -2,7 +2,10 @@ package collector import ( "goprom/internal/models" + "goprom/internal/redfish" "net/http" + "strconv" + "time" "github.com/prometheus/client_golang/prometheus" ) @@ -14,89 +17,266 @@ type RedfishAPI interface { type RedfishCollector struct { client RedfishAPI + // Power powerConsumedDesc *prometheus.Desc powerCapacityDesc *prometheus.Desc - redfishUpDesc *prometheus.Desc + + // Health + redfishUpDesc *prometheus.Desc + scrapeDurationDesc *prometheus.Desc + + // Temperature metrics + readingCelsiusDesc *prometheus.Desc + lowerThresholdCriticalDesc *prometheus.Desc + lowerThresholdFatalDesc *prometheus.Desc + upperThresholdCriticalDesc *prometheus.Desc + upperThresholdFatalDesc *prometheus.Desc + lowerThresholdNonCriticalDesc *prometheus.Desc + upperThresholdNonCriticalDesc *prometheus.Desc } func NewRedfishCollector(client RedfishAPI) *RedfishCollector { return &RedfishCollector{ client: client, + + // Power powerConsumedDesc: prometheus.NewDesc( - "power_consumed_watts", + "redfish_power_consumed_watts", "Current PSU power consumption in watts", nil, nil, ), powerCapacityDesc: prometheus.NewDesc( - "power_capacity_watts", + "redfish_power_capacity_watts", "PSU power capacity in watts", nil, nil, ), + // Overall health redfishUpDesc: prometheus.NewDesc( "redfish_up", - "Redfish API Response Status (1 = up, 0 = down)", + "Overall Redfish scrape status (1 = success, 0 = failure)", + nil, + nil, + ), + + scrapeDurationDesc: prometheus.NewDesc( + "redfish_scrape_duration_seconds", + "Time taken to scrape Redfish in seconds", nil, nil, ), + + // Temperature metrics + readingCelsiusDesc: prometheus.NewDesc( + "redfish_temperature_reading_celsius", + "Current temperature reading", + []string{"name", "physical_context", "sensor_number"}, + nil, + ), + + lowerThresholdCriticalDesc: prometheus.NewDesc( + "redfish_temperature_lower_threshold_critical", + "Lower critical temperature threshold", + []string{"name", "physical_context", "sensor_number"}, + nil, + ), + + lowerThresholdFatalDesc: prometheus.NewDesc( + "redfish_temperature_lower_threshold_fatal", + "Lower fatal temperature threshold", + []string{"name", "physical_context", "sensor_number"}, + nil, + ), + + upperThresholdCriticalDesc: prometheus.NewDesc( + "redfish_temperature_upper_threshold_critical", + "Upper critical temperature threshold", + []string{"name", "physical_context", "sensor_number"}, + nil, + ), + + upperThresholdFatalDesc: prometheus.NewDesc( + "redfish_temperature_upper_threshold_fatal", + "Upper fatal temperature threshold", + []string{"name", "physical_context", "sensor_number"}, + nil, + ), + + lowerThresholdNonCriticalDesc: prometheus.NewDesc( + "redfish_temperature_lower_threshold_non_critical", + "Lower non-critical temperature threshold", + []string{"name", "physical_context", "sensor_number"}, + nil, + ), + + upperThresholdNonCriticalDesc: prometheus.NewDesc( + "redfish_temperature_upper_threshold_non_critical", + "Upper non-critical temperature threshold", + []string{"name", "physical_context", "sensor_number"}, + nil, + ), } } func (c *RedfishCollector) Describe(ch chan<- *prometheus.Desc) { + // Power ch <- c.powerConsumedDesc ch <- c.powerCapacityDesc + + // Health ch <- c.redfishUpDesc + ch <- c.scrapeDurationDesc + + // Temperature + ch <- c.readingCelsiusDesc + ch <- c.lowerThresholdCriticalDesc + ch <- c.lowerThresholdFatalDesc + ch <- c.upperThresholdCriticalDesc + ch <- c.upperThresholdFatalDesc + ch <- c.lowerThresholdNonCriticalDesc + ch <- c.upperThresholdNonCriticalDesc } func (c *RedfishCollector) Collect(ch chan<- prometheus.Metric) { + start := time.Now() + overallSuccess := true + //------------------------------------- + // 1. POWER ENDPOINT + //------------------------------------- var power models.PowerControl - status, err := c.client.Get("/redfish/v1/Chassis/System.Embedded.1/Power/PowerControl", &power) - if err != nil { - ch <- prometheus.MustNewConstMetric( - c.redfishUpDesc, - prometheus.GaugeValue, - 0, - ) - return - } + status, err := c.client.Get(redfish.Power_Endpoints[0], &power) - if status == http.StatusNotFound { + if err != nil || (status != http.StatusOK && status != http.StatusNotFound) { + overallSuccess = false + } else if status == http.StatusOK { ch <- prometheus.MustNewConstMetric( - c.redfishUpDesc, + c.powerConsumedDesc, prometheus.GaugeValue, - 1, + float64(power.PowerConsumedWatts), ) - return - } - - if status != http.StatusOK { ch <- prometheus.MustNewConstMetric( - c.redfishUpDesc, + c.powerCapacityDesc, prometheus.GaugeValue, - 0, + float64(power.PowerCapacityWatts), ) - return } - ch <- prometheus.MustNewConstMetric( - c.powerConsumedDesc, - prometheus.GaugeValue, - float64(power.PowerConsumedWatts), - ) + //------------------------------------- + // 2. TEMPERATURE ENDPOINTS + //------------------------------------- + for _, endpoint := range redfish.Temperature_Endpoints { + var t models.Temperature + status, err := c.client.Get(endpoint, &t) - ch <- prometheus.MustNewConstMetric( - c.powerCapacityDesc, - prometheus.GaugeValue, - float64(power.PowerCapacityWatts), - ) + if err != nil { + overallSuccess = false + continue + } + if status == http.StatusNotFound { + continue + } + if status != http.StatusOK { + overallSuccess = false + continue + } + + // Labels + labels := []string{t.Name, t.PhysicalContext, strconv.Itoa(t.SensorNumber)} + + //------------------------------------- + // Emit temperature metrics + //------------------------------------- + if t.ReadingCelsius != nil { + ch <- prometheus.MustNewConstMetric( + c.readingCelsiusDesc, + prometheus.GaugeValue, + float64(*t.ReadingCelsius), + labels..., + ) + } + // ---- SAFE THRESHOLD EMITS ---- + + // Lower Critical + if t.LowerThresholdCritical != nil { + ch <- prometheus.MustNewConstMetric( + c.lowerThresholdCriticalDesc, + prometheus.GaugeValue, + float64(*t.LowerThresholdCritical), + labels..., + ) + } + + // Lower Fatal + if t.LowerThresholdFatal != nil { + ch <- prometheus.MustNewConstMetric( + c.lowerThresholdFatalDesc, + prometheus.GaugeValue, + float64(*t.LowerThresholdFatal), + labels..., + ) + } + + // Upper Critical + if t.UpperThresholdCritical != nil { + ch <- prometheus.MustNewConstMetric( + c.upperThresholdCriticalDesc, + prometheus.GaugeValue, + float64(*t.UpperThresholdCritical), + labels..., + ) + } + + // Upper Fatal + if t.UpperThresholdFatal != nil { + ch <- prometheus.MustNewConstMetric( + c.upperThresholdFatalDesc, + prometheus.GaugeValue, + float64(*t.UpperThresholdFatal), + labels..., + ) + } + + // Lower Non-Critical + if t.LowerThresholdNonCritical != nil { + ch <- prometheus.MustNewConstMetric( + c.lowerThresholdNonCriticalDesc, + prometheus.GaugeValue, + float64(*t.LowerThresholdNonCritical), + labels..., + ) + } + + // Upper Non-Critical + if t.UpperThresholdNonCritical != nil { + ch <- prometheus.MustNewConstMetric( + c.upperThresholdNonCriticalDesc, + prometheus.GaugeValue, + float64(*t.UpperThresholdNonCritical), + labels..., + ) + } + } + + //------------------------------------- + // 3. EXPORTER HEALTH METRIC + //------------------------------------- + if overallSuccess { + ch <- prometheus.MustNewConstMetric(c.redfishUpDesc, prometheus.GaugeValue, 1) + } else { + ch <- prometheus.MustNewConstMetric(c.redfishUpDesc, prometheus.GaugeValue, 0) + } + //------------------------------------- + // 4. SCRAPE DURATION + //------------------------------------- + duration := time.Since(start).Seconds() ch <- prometheus.MustNewConstMetric( - c.redfishUpDesc, + c.scrapeDurationDesc, prometheus.GaugeValue, - 1, + duration, ) } From 9f403db97e43e7fade83eedda3baea70c5cc20f0 Mon Sep 17 00:00:00 2001 From: Jignesh Atul Paratane Date: Thu, 19 Feb 2026 18:05:16 +0530 Subject: [PATCH 31/31] Removed trivial comments from redfish_collector.go file. #5 --- internal/collector/redfish_collector.go | 33 +------------------------ 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/internal/collector/redfish_collector.go b/internal/collector/redfish_collector.go index ae2631a..450f2b2 100644 --- a/internal/collector/redfish_collector.go +++ b/internal/collector/redfish_collector.go @@ -17,15 +17,12 @@ type RedfishAPI interface { type RedfishCollector struct { client RedfishAPI - // Power powerConsumedDesc *prometheus.Desc powerCapacityDesc *prometheus.Desc - // Health redfishUpDesc *prometheus.Desc scrapeDurationDesc *prometheus.Desc - // Temperature metrics readingCelsiusDesc *prometheus.Desc lowerThresholdCriticalDesc *prometheus.Desc lowerThresholdFatalDesc *prometheus.Desc @@ -39,7 +36,6 @@ func NewRedfishCollector(client RedfishAPI) *RedfishCollector { return &RedfishCollector{ client: client, - // Power powerConsumedDesc: prometheus.NewDesc( "redfish_power_consumed_watts", "Current PSU power consumption in watts", @@ -54,7 +50,6 @@ func NewRedfishCollector(client RedfishAPI) *RedfishCollector { nil, ), - // Overall health redfishUpDesc: prometheus.NewDesc( "redfish_up", "Overall Redfish scrape status (1 = success, 0 = failure)", @@ -69,7 +64,6 @@ func NewRedfishCollector(client RedfishAPI) *RedfishCollector { nil, ), - // Temperature metrics readingCelsiusDesc: prometheus.NewDesc( "redfish_temperature_reading_celsius", "Current temperature reading", @@ -122,15 +116,13 @@ func NewRedfishCollector(client RedfishAPI) *RedfishCollector { } func (c *RedfishCollector) Describe(ch chan<- *prometheus.Desc) { - // Power + ch <- c.powerConsumedDesc ch <- c.powerCapacityDesc - // Health ch <- c.redfishUpDesc ch <- c.scrapeDurationDesc - // Temperature ch <- c.readingCelsiusDesc ch <- c.lowerThresholdCriticalDesc ch <- c.lowerThresholdFatalDesc @@ -144,9 +136,6 @@ func (c *RedfishCollector) Collect(ch chan<- prometheus.Metric) { start := time.Now() overallSuccess := true - //------------------------------------- - // 1. POWER ENDPOINT - //------------------------------------- var power models.PowerControl status, err := c.client.Get(redfish.Power_Endpoints[0], &power) @@ -165,9 +154,6 @@ func (c *RedfishCollector) Collect(ch chan<- prometheus.Metric) { ) } - //------------------------------------- - // 2. TEMPERATURE ENDPOINTS - //------------------------------------- for _, endpoint := range redfish.Temperature_Endpoints { var t models.Temperature status, err := c.client.Get(endpoint, &t) @@ -184,12 +170,8 @@ func (c *RedfishCollector) Collect(ch chan<- prometheus.Metric) { continue } - // Labels labels := []string{t.Name, t.PhysicalContext, strconv.Itoa(t.SensorNumber)} - //------------------------------------- - // Emit temperature metrics - //------------------------------------- if t.ReadingCelsius != nil { ch <- prometheus.MustNewConstMetric( c.readingCelsiusDesc, @@ -198,9 +180,7 @@ func (c *RedfishCollector) Collect(ch chan<- prometheus.Metric) { labels..., ) } - // ---- SAFE THRESHOLD EMITS ---- - // Lower Critical if t.LowerThresholdCritical != nil { ch <- prometheus.MustNewConstMetric( c.lowerThresholdCriticalDesc, @@ -210,7 +190,6 @@ func (c *RedfishCollector) Collect(ch chan<- prometheus.Metric) { ) } - // Lower Fatal if t.LowerThresholdFatal != nil { ch <- prometheus.MustNewConstMetric( c.lowerThresholdFatalDesc, @@ -220,7 +199,6 @@ func (c *RedfishCollector) Collect(ch chan<- prometheus.Metric) { ) } - // Upper Critical if t.UpperThresholdCritical != nil { ch <- prometheus.MustNewConstMetric( c.upperThresholdCriticalDesc, @@ -230,7 +208,6 @@ func (c *RedfishCollector) Collect(ch chan<- prometheus.Metric) { ) } - // Upper Fatal if t.UpperThresholdFatal != nil { ch <- prometheus.MustNewConstMetric( c.upperThresholdFatalDesc, @@ -240,7 +217,6 @@ func (c *RedfishCollector) Collect(ch chan<- prometheus.Metric) { ) } - // Lower Non-Critical if t.LowerThresholdNonCritical != nil { ch <- prometheus.MustNewConstMetric( c.lowerThresholdNonCriticalDesc, @@ -250,7 +226,6 @@ func (c *RedfishCollector) Collect(ch chan<- prometheus.Metric) { ) } - // Upper Non-Critical if t.UpperThresholdNonCritical != nil { ch <- prometheus.MustNewConstMetric( c.upperThresholdNonCriticalDesc, @@ -261,18 +236,12 @@ func (c *RedfishCollector) Collect(ch chan<- prometheus.Metric) { } } - //------------------------------------- - // 3. EXPORTER HEALTH METRIC - //------------------------------------- if overallSuccess { ch <- prometheus.MustNewConstMetric(c.redfishUpDesc, prometheus.GaugeValue, 1) } else { ch <- prometheus.MustNewConstMetric(c.redfishUpDesc, prometheus.GaugeValue, 0) } - //------------------------------------- - // 4. SCRAPE DURATION - //------------------------------------- duration := time.Since(start).Seconds() ch <- prometheus.MustNewConstMetric( c.scrapeDurationDesc,