Repository: NetApp/harvest
Pull request #2211 (2023-07-18 11:44:29)

feat: AFF250 Power calculation


Patch:
diff --git a/cmd/collectors/commonutils.go b/cmd/collectors/commonutils.go
index ece8f6af..dbe0134b 100644
--- a/cmd/collectors/commonutils.go
+++ b/cmd/collectors/commonutils.go
@@ -16,6 +16,13 @@ const (
 	MaxAllowedTimeDrift = 10 * time.Second
 )
 
+var validUnits = map[string]bool{
+	"mW":    true,
+	"W":     true,
+	"mW*hr": true,
+	"W*hr":  true,
+}
+
 func InvokeRestCall(client *rest.Client, href string, logger *logging.Logger) ([]gjson.Result, error) {
 	result, err := rest.Fetch(client, href)
 	if err != nil {
@@ -192,3 +199,7 @@ func UpdateLagTime(instance *matrix.Instance, lastTransferSize *matrix.Metric, l
 		}
 	}
 }
+
+func IsValidUnit(unit string) bool {
+	return validUnits[unit]
+}
diff --git a/cmd/collectors/rest/plugins/sensor/sensor.go b/cmd/collectors/rest/plugins/sensor/sensor.go
index 57a0d125..695aa74b 100644
--- a/cmd/collectors/rest/plugins/sensor/sensor.go
+++ b/cmd/collectors/rest/plugins/sensor/sensor.go
@@ -6,6 +6,7 @@ package sensor
 
 import (
 	"fmt"
+	"github.com/netapp/harvest/v2/cmd/collectors"
 	"github.com/netapp/harvest/v2/cmd/poller/plugin"
 	"github.com/netapp/harvest/v2/pkg/dict"
 	"github.com/netapp/harvest/v2/pkg/matrix"
@@ -156,7 +157,7 @@ func (my *Sensor) calculateEnvironmentMetrics(data *matrix.Matrix) ([]*matrix.Ma
 
 				if isPowerMatch {
 					if value, ok := metric.GetValueFloat64(instance); ok {
-						if sensorUnit != "mW" && sensorUnit != "W" {
+						if !collectors.IsValidUnit(sensorUnit) {
 							my.Logger.Warn().Str("unit", sensorUnit).Float64("value", value).Msg("unknown power unit")
 						} else {
 							if sensorEnvironmentMetricMap[iKey].powerSensor == nil {
@@ -197,6 +198,8 @@ func (my *Sensor) calculateEnvironmentMetrics(data *matrix.Matrix) ([]*matrix.Ma
 			Msg("sensor excluded")
 	}
 
+	whrSensors := make(map[string]*sensorValue)
+
 	for key, v := range sensorEnvironmentMetricMap {
 		instance, err := my.data.NewInstance(key)
 		if err != nil {
@@ -212,13 +215,16 @@ func (my *Sensor) calculateEnvironmentMetrics(data *matrix.Matrix) ([]*matrix.Ma
 				var sumPower float64
 				if len(v.powerSensor) > 0 {
 					for _, v1 := range v.powerSensor {
-						if v1.unit == "mW" {
+						if v1.unit == "mW" || v1.unit == "mW*hr" {
 							sumPower += v1.value / 1000
-						} else if v1.unit == "W" {
+						} else if v1.unit == "W" || v1.unit == "W*hr" {
 							sumPower += v1.value
 						} else {
 							my.Logger.Warn().Str("unit", v1.unit).Float64("value", v1.value).Msg("unknown power unit")
 						}
+						if v1.unit == "mW*hr" || v1.unit == "W*hr" {
+							whrSensors[v1.name] = v1
+						}
 					}
 				} else if len(v.voltageSensor) > 0 && len(v.voltageSensor) == len(v.currentSensor) {
 					// sort voltage keys
@@ -333,5 +339,15 @@ func (my *Sensor) calculateEnvironmentMetrics(data *matrix.Matrix) ([]*matrix.Ma
 			}
 		}
 	}
+
+	if len(whrSensors) > 0 {
+		var whrSensorsStr string
+		for _, v := range whrSensors {
+			whrSensorsStr += " sensor:" + fmt.Sprintf("%v", *v)
+		}
+		my.Logger.Info().Str("sensor", whrSensorsStr).
+			Msg("sensor with *hr units")
+	}
+
 	return []*matrix.Matrix{my.data}, nil
 }
diff --git a/cmd/collectors/zapi/plugins/sensor/sensor.go b/cmd/collectors/zapi/plugins/sensor/sensor.go
index 7dd46c54..051642b8 100644
--- a/cmd/collectors/zapi/plugins/sensor/sensor.go
+++ b/cmd/collectors/zapi/plugins/sensor/sensor.go
@@ -6,6 +6,7 @@ package sensor
 
 import (
 	"fmt"
+	"github.com/netapp/harvest/v2/cmd/collectors"
 	"github.com/netapp/harvest/v2/cmd/poller/plugin"
 	"github.com/netapp/harvest/v2/pkg/dict"
 	"github.com/netapp/harvest/v2/pkg/matrix"
@@ -160,7 +161,7 @@ func (my *Sensor) calculateEnvironmentMetrics(data *matrix.Matrix) ([]*matrix.Ma
 
 				if isPowerMatch {
 					if value, ok := metric.GetValueFloat64(instance); ok {
-						if sensorUnit != "mW" && sensorUnit != "W" {
+						if !collectors.IsValidUnit(sensorUnit) {
 							my.Logger.Warn().Str("unit", sensorUnit).Float64("value", value).Msg("unknown power unit")
 						} else {
 							if sensorEnvironmentMetricMap[iKey].powerSensor == nil {
@@ -201,6 +202,8 @@ func (my *Sensor) calculateEnvironmentMetrics(data *matrix.Matrix) ([]*matrix.Ma
 			Msg("sensor excluded")
 	}
 
+	whrSensors := make(map[string]*sensorValue)
+
 	for key, v := range sensorEnvironmentMetricMap {
 		instance, err := my.data.NewInstance(key)
 		if err != nil {
@@ -216,12 +219,15 @@ func (my *Sensor) calculateEnvironmentMetrics(data *matrix.Matrix) ([]*matrix.Ma
 				var sumPower float64
 				if len(v.powerSensor) > 0 {
 					for _, v1 := range v.powerSensor {
-						if v1.unit == "mW" {
+						if v1.unit == "mW" || v1.unit == "mW*hr" {
 							sumPower += v1.value / 1000
-						} else if v1.unit == "W" {
+						} else if v1.unit == "W" || v1.unit == "W*hr" {
 							sumPower += v1.value
 						} else {
-							my.Logger.Warn().Str("node", key).Str("unit", v1.unit).Float64("value", v1.value).Msg("unknown power unit")
+							my.Logger.Warn().Str("node", key).Str("name", v1.name).Str("unit", v1.unit).Float64("value", v1.value).Msg("unknown power unit")
+						}
+						if v1.unit == "mW*hr" || v1.unit == "W*hr" {
+							whrSensors[v1.name] = v1
 						}
 					}
 				} else if len(v.voltageSensor) > 0 && len(v.voltageSensor) == len(v.currentSensor) {
@@ -338,5 +344,15 @@ func (my *Sensor) calculateEnvironmentMetrics(data *matrix.Matrix) ([]*matrix.Ma
 			}
 		}
 	}
+
+	if len(whrSensors) > 0 {
+		var whrSensorsStr string
+		for _, v := range whrSensors {
+			whrSensorsStr += " sensor:" + fmt.Sprintf("%v", *v)
+		}
+		my.Logger.Info().Str("sensor", whrSensorsStr).
+			Msg("sensor with *hr units")
+	}
+
 	return []*matrix.Matrix{my.data}, nil
 }
diff --git a/cmd/collectors/zapi/plugins/shelf/shelf.go b/cmd/collectors/zapi/plugins/shelf/shelf.go
index 69a1ee0d..82d0ba31 100644
--- a/cmd/collectors/zapi/plugins/shelf/shelf.go
+++ b/cmd/collectors/zapi/plugins/shelf/shelf.go
@@ -56,7 +56,7 @@ func (my *Shelf) Init() error {
 
 	objects := my.Params.GetChildS("objects")
 	if objects == nil {
-		return errs.New(errs.ErrMissingParams, "objects")
+		return errs.New(errs.ErrMissingParam, "objects")
 	}
 
 	for _, obj := range objects.GetChildren() {
diff --git a/cmd/collectors/zapiperf/plugins/disk/disk.go b/cmd/collectors/zapiperf/plugins/disk/disk.go
index 942eaec6..37f43bea 100644
--- a/cmd/collectors/zapiperf/plugins/disk/disk.go
+++ b/cmd/collectors/zapiperf/plugins/disk/disk.go
@@ -135,7 +135,7 @@ func (d *Disk) Init() error {
 
 	objects := d.Params.GetChildS("objects")
 	if objects == nil {
-		return errs.New(errs.ErrMissingParams, "objects")
+		return errs.New(errs.ErrMissingParam, "objects")
 	}
 
 	for _, obj := range objects.GetChildren() {
diff --git a/grafana/dashboards/cmode/power.json b/grafana/dashboards/cmode/power.json
index cf329ef3..fcaf7248 100644
--- a/grafana/dashboards/cmode/power.json
+++ b/grafana/dashboards/cmode/power.json
@@ -34,6 +34,12 @@
       "type": "panel",
       "version": ""
     },
+    {
+      "id": "text",
+      "name": "Text",
+      "type": "panel",
+      "version": ""
+    },
     {
       "id": "timeseries",
       "name": "Time series",
@@ -65,7 +71,7 @@
   "gnetId": null,
   "graphTooltip": 1,
   "id": null,
-  "iteration": 1675871743493,
+  "iteration": 1689849302689,
   "links": [
     {
       "asDropdown": true,
@@ -92,6 +98,36 @@
         "x": 0,
         "y": 0
       },
+      "id": 99,
+      "panels": [],
+      "title": "Important Information",
+      "type": "row"
+    },
+    {
+      "datasource": "${DS_PROMETHEUS}",
+      "gridPos": {
+        "h": 2,
+        "w": 24,
+        "x": 0,
+        "y": 1
+      },
+      "id": 101,
+      "options": {
+        "content": "If an ONTAP system has **embedded** shelves, you may not see power and temperature information for those shelves since power and temperature are calculated at the cluster level.",
+        "mode": "markdown"
+      },
+      "pluginVersion": "8.1.8",
+      "type": "text"
+    },
+    {
+      "collapsed": false,
+      "datasource": "${DS_PROMETHEUS}",
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 3
+      },
       "id": 15,
       "panels": [],
       "title": "Highlights",
@@ -124,7 +160,7 @@
         "h": 10,
         "w": 9,
         "x": 0,
-        "y": 1
+        "y": 4
       },
       "id": 21,
       "links": [],
@@ -190,7 +226,7 @@
         "h": 5,
         "w": 7,
         "x": 9,
-        "y": 1
+        "y": 4
       },
       "id": 71,
       "options": {
@@ -265,7 +301,7 @@
         "h": 5,
         "w": 4,
         "x": 16,
-        "y": 1
+        "y": 4
       },
       "id": 68,
       "options": {
@@ -324,7 +360,7 @@
         "h": 5,
         "w": 4,
         "x": 20,
-        "y": 1
+        "y": 4
       },
       "id": 70,
       "options": {
@@ -383,7 +419,7 @@
         "h": 5,
         "w": 7,
         "x": 9,
-        "y": 6
+        "y": 9
       },
       "id": 96,
       "options": {
@@ -458,7 +494,7 @@
         "h": 5,
         "w": 4,
         "x": 16,
-        "y": 6
+        "y": 9
       },
       "id": 64,
       "options": {
@@ -518,7 +554,7 @@
         "h": 5,
         "w": 4,
         "x": 20,
-        "y": 6
+        "y": 9
       },
       "id": 97,
       "options": {
@@ -644,7 +680,7 @@
         "h": 8,
         "w": 24,
         "x": 0,
-        "y": 11
+        "y": 14
       },
       "id": 82,
       "options": {
@@ -751,7 +787,7 @@
         "h": 8,
         "w": 13,
         "x": 0,
-        "y": 19
+        "y": 22
       },
       "id": 73,
       "options": {
@@ -842,7 +878,7 @@
         "h": 8,
         "w": 11,
         "x": 13,
-        "y": 19
+        "y": 22
       },
       "id": 74,
       "options": {
@@ -932,7 +968,7 @@
         "h": 8,
         "w": 13,
         "x": 0,
-        "y": 27
+        "y": 30
       },
       "id": 79,
       "options": {
@@ -1023,7 +1059,7 @@
         "h": 8,
         "w": 11,
         "x": 13,
-        "y": 27
+        "y": 30
       },
       "id": 88,
       "options": {
@@ -1114,7 +1150,7 @@
         "h": 8,
         "w": 13,
         "x": 0,
-        "y": 35
+        "y": 38
       },
       "id": 87,
       "options": {
@@ -1205,7 +1241,7 @@
         "h": 8,
         "w": 11,
         "x": 13,
-        "y": 35
+        "y": 38
       },
       "id": 89,
       "options": {
@@ -1296,7 +1332,7 @@
         "h": 8,
         "w": 13,
         "x": 0,
-        "y": 43
+        "y": 46
       },
       "id": 94,
       "options": {
@@ -1344,7 +1380,7 @@
         "h": 1,
         "w": 24,
         "x": 0,
-        "y": 51
+        "y": 54
       },
       "id": 93,
       "panels": [
@@ -1732,7 +1768,7 @@
         "h": 1,
         "w": 24,
         "x": 0,
-        "y": 52
+        "y": 55
       },
       "id": 84,
       "panels": [
@@ -1857,7 +1893,7 @@
         "h": 1,
         "w": 24,
         "x": 0,
-        "y": 53
+        "y": 56
       },
       "id": 76,
       "panels": [
@@ -2394,7 +2430,7 @@
         "h": 1,
         "w": 24,
         "x": 0,
-        "y": 54
+        "y": 57
       },
       "id": 78,
       "panels": [
@@ -3285,7 +3321,7 @@
         "allValue": null,
         "current": {},
         "datasource": "${DS_PROMETHEUS}",
-        "definition": "query_result(topk($TopResources, avg_over_time(shelf_power{datacenter=~\"$Datacenter\",cluster=~\"$Cluster\"}[${__range}])))",
+        "definition": "query_result(topk($TopResources, avg_over_time(shelf_average_temperature{datacenter=~\"$Datacenter\",cluster=~\"$Cluster\"}[${__range}])))",
         "description": null,
         "error": null,
         "hide": 2,
@@ -3295,7 +3331,7 @@
         "name": "TopShelfAvgTemp",
         "options": [],
         "query": {
-          "query": "query_result(topk($TopResources, avg_over_time(shelf_power{datacenter=~\"$Datacenter\",cluster=~\"$Cluster\"}[${__range}])))",
+          "query": "query_result(topk($TopResources, avg_over_time(shelf_average_temperature{datacenter=~\"$Datacenter\",cluster=~\"$Cluster\"}[${__range}])))",
           "refId": "StandardVariableQuery"
         },
         "refresh": 2,
@@ -3360,5 +3396,5 @@
   "timezone": "",
   "title": "ONTAP: Power",
   "uid": "",
-  "version": 8
+  "version": 9
 }
diff --git a/pkg/errs/errors.go b/pkg/errs/errors.go
index 2ff1104c..40046e41 100644
--- a/pkg/errs/errors.go
+++ b/pkg/errs/errors.go
@@ -23,7 +23,6 @@ const (
 	ErrInvalidItem        = harvestError("invalid item")
 	ErrInvalidParam       = harvestError("invalid parameter")
 	ErrMissingParam       = harvestError("missing parameter")
-	ErrMissingParams      = harvestError("missing parameter")
 	ErrNoCollector        = harvestError("no collectors")
 	ErrNoInstance         = harvestError("no instances")
 	ErrNoMetric           = harvestError("no metrics")


Test patch:
diff --git a/cmd/tools/grafana/dashboard_test.go b/cmd/tools/grafana/dashboard_test.go
index 82230b9b..91c30732 100644
--- a/cmd/tools/grafana/dashboard_test.go
+++ b/cmd/tools/grafana/dashboard_test.go
@@ -798,6 +798,7 @@ func TestOnlyHighlightsExpanded(t *testing.T) {
 		"cmode/workload.json":         2,
 		"cmode/smb.json":              2,
 		"cmode/health.json":           2,
+		"cmode/power.json":            2,
 		"storagegrid/fabricpool.json": 2,
 	}
 	// count number of expanded sections in dashboard and ensure num expanded = 1