Repository: NetApp/harvest
Pull request #709 (2021-11-29 15:59:09)

feat: fix unit changes and threshold settings for mcc

Fixes : #685 


Details :

1. Enable sorting for all variables
2. Change threshold limit for "Volume Average Latency" and "FCVI Write Latency"
3.  Update "MetroCluster Disk Drilldown" Panels User Reads & User Writes  to use IOPS unit
4. Microseconds for "MetroCluster Disk Drilldown" Latency Panels and  "MetroCluster FCVI Drilldown" Panel "FCVI Latency" 
5. MB/s for "Disk and Tape Adapter Drilldown" Panel "Adapter Read Data" and "MetroCluster FibreBridge/Array Drilldown" Panel "FibreBridge/Array WWPN - Read Data" 


Patch:
diff --git a/cmd/collectors/zapiperf/zapiperf.go b/cmd/collectors/zapiperf/zapiperf.go
index 815c33d7..db7fdd7e 100644
--- a/cmd/collectors/zapiperf/zapiperf.go
+++ b/cmd/collectors/zapiperf/zapiperf.go
@@ -1191,7 +1191,7 @@ func parseHistogramLabels(elem *node.Node) ([]string, string) {
 	return labels, msg
 }
 
-// Update instance cache
+// PollInstance updates instance cache
 func (me *ZapiPerf) PollInstance() (*matrix.Matrix, error) {
 
 	var (
@@ -1254,7 +1254,10 @@ func (me *ZapiPerf) PollInstance() (*matrix.Matrix, error) {
 	for {
 
 		if results, batchTag, err = me.Client.InvokeBatchRequest(request, batchTag); err != nil {
-			me.Logger.Error().Stack().Err(err).Msg("instance request")
+			me.Logger.Error().Err(err).
+				Str("request", request.GetNameS()).
+				Str("batchTag", batchTag).
+				Msg("InvokeBatchRequest failed")
 			break
 		}
 
diff --git a/cmd/exporters/influxdb/influxdb.go b/cmd/exporters/influxdb/influxdb.go
index 26f2c915..6b59d153 100644
--- a/cmd/exporters/influxdb/influxdb.go
+++ b/cmd/exporters/influxdb/influxdb.go
@@ -11,6 +11,7 @@ import (
 	"goharvest2/pkg/color"
 	"goharvest2/pkg/errors"
 	"goharvest2/pkg/matrix"
+	"io"
 	"io/ioutil"
 	"net/http"
 	url2 "net/url"
@@ -166,9 +167,12 @@ func (e *InfluxDB) Export(data *matrix.Matrix) error {
 				e.Logger.Debug().Msgf("M= [%s%s%s]", color.Blue, m, color.End)
 			}
 			return nil
-			// otherwise to the actual export: send to the DB
+			// otherwise, to the actual export: send to the DB
 		} else if err = e.Emit(metrics); err != nil {
-			e.Logger.Error().Stack().Err(err).Msgf("(%s.%s) --> %s", data.Object, data.UUID)
+			e.Logger.Error().Stack().Err(err).
+				Str("object", data.Object).
+				Str("uuid", data.UUID).
+				Msg("Failed to emit metrics")
 			return err
 		}
 	}
@@ -208,7 +212,7 @@ func (e *InfluxDB) Emit(data [][]byte) error {
 	}
 
 	if response.StatusCode != expectedResponseCode {
-		defer response.Body.Close()
+		defer func(Body io.ReadCloser) { _ = Body.Close() }(response.Body)
 		if body, err := ioutil.ReadAll(response.Body); err != nil {
 			return errors.New(errors.API_RESPONSE, err.Error())
 		} else {
diff --git a/cmd/poller/collector/helpers.go b/cmd/poller/collector/helpers.go
index b8b7f4b2..a531277b 100644
--- a/cmd/poller/collector/helpers.go
+++ b/cmd/poller/collector/helpers.go
@@ -29,12 +29,12 @@ import (
 )
 
 // ImportTemplate retrieves the config (template) of a collector, arguments are:
-// @confDir			- path of Harvest config durectory (usually /etc/harvest)
+// @confDir			- path of Harvest config directory (usually /etc/harvest)
 // @confFn			- template filename (e.g. default.yaml or custom.yaml)
 // @collectorName	- name of the collector
 func ImportTemplate(confPath, confFn, collectorName string) (*node.Node, error) {
 	fp := path.Join(confPath, "conf/", strings.ToLower(collectorName), confFn)
-	return tree.Import("yaml", fp)
+	return tree.ImportYaml(fp)
 }
 
 // ImportSubTemplate retrieves the best matching subtemplate of a collector object.
@@ -105,7 +105,7 @@ func (c *AbstractCollector) ImportSubTemplate(model, filename string, ver [3]int
 			verS, err := version.NewVersion(verWithDots)
 			if err != nil {
 				c.Logger.Trace().Msgf("error parsing ONTAP version: %s err: %s", verWithDots, err)
-				return nil, errors.New("No best-fitting subtemplate version found")
+				return nil, errors.New("no best-fitting subtemplate version found")
 			}
 			// get closest index
 			idx := getClosestIndex(versions, verS)
@@ -115,18 +115,18 @@ func (c *AbstractCollector) ImportSubTemplate(model, filename string, ver [3]int
 		}
 
 		if selectedVersion == "" {
-			return nil, errors.New("No best-fit template found")
+			return nil, errors.New("no best-fit template found")
 		}
 
 		subTemplateFp = path.Join(pathPrefix, selectedVersion, f)
 		c.Logger.Info().Msgf("best-fit template [%s] for [%s]", subTemplateFp, verWithDots)
 		if finalTemplate == nil {
-			finalTemplate, err = tree.Import("yaml", subTemplateFp)
+			finalTemplate, err = tree.ImportYaml(subTemplateFp)
 			if err == nil {
 				finalTemplate.PreprocessTemplate()
 			}
 		} else {
-			tempTemplate, err = tree.Import("yaml", subTemplateFp)
+			tempTemplate, err = tree.ImportYaml(subTemplateFp)
 			if err == nil {
 				tempTemplate.PreprocessTemplate()
 				// merge templates
diff --git a/cmd/poller/plugin/README.md b/cmd/poller/plugin/README.md
index 897b8042..d4e2ca93 100644
--- a/cmd/poller/plugin/README.md
+++ b/cmd/poller/plugin/README.md
@@ -33,7 +33,7 @@ plugins:
 # will aggregate a new Matrix based on target label LABEL
 ```
 
-If you want to speficy which labels should be included in the new instances, you can add those space-seperated after `LABEL`:
+If you want to specify which labels should be included in the new instances, you can add those space-seperated after `LABEL`:
 
 ```yaml
     - LABEL LABEL1,LABEL2
@@ -47,7 +47,7 @@ Or include all labels:
     - LABEL ...
 # copy all labels of the original instance
 ```
-By default aggregated metrics will be prefixed with `LABEL`. For example if the object of the original Matrix is `volume` (meaning metrics are prefixed with `volume_`) and `LABEL` is `aggr`, then the metric `volume_read_ops` will become `aggr_volume_read_ops`, etc. You can override this by providing the `<>OBJ` using the following syntax:
+By default, aggregated metrics will be prefixed with `LABEL`. For example if the object of the original Matrix is `volume` (meaning metrics are prefixed with `volume_`) and `LABEL` is `aggr`, then the metric `volume_read_ops` will become `aggr_volume_read_ops`, etc. You can override this by providing the `<>OBJ` using the following syntax:
 
 ```yaml
     - LABEL<>OBJ
@@ -71,14 +71,13 @@ Examples:
 ```yaml
 plugins:
   Aggregator:
+    # will aggregate metrics of the aggregate. The labels "node" and "type" are included in the new instances
     - aggr node type
-# will aggregate metrics of Anggregate level and make sure
-# labels "node" and "type" are included in the new instances
+    # aggregate instances if label "type" has value "flexgroup"
+    # include all original labels
     - type<flexgroup> ...
-# aggregate instances if label "type" has value "flexgroup"
-# include all original labels
+    # aggregate all instances if value of "volume" ends with underscore and 4 digits
     - volume<`_\d{4}$`>
-# aggregate all instances if value of "volume" ends with underscore and 4 digits
 ```
 
 ### Aggregation rules
@@ -94,7 +93,7 @@ The plugin tries to intelligently aggregate metrics based on a few rules:
 - **Ignore** - metrics created by some plugins, such as value_to_num by LabelAgent
 
 # LabelAgent
-LabelAgent allows to manipulate instance labels based on rules. You can define multiple rules, here is an example of what you could add to the yaml file of a collector:
+LabelAgent are used to manipulate instance labels based on rules. You can define multiple rules, here is an example of what you could add to the yaml file of a collector:
 
 
 ```yaml
@@ -130,23 +129,23 @@ Rule syntax:
 
 ```yaml
 split: LABEL `SEP` LABEL1,LABEL2,LABEL3
-# source label - seperator - comma-seperated target labels
+# source label - separator - comma-seperated target labels
 ```
 
-Splits the value of a given label by seperator `SEP` and creates new labels if their number matches to the number of target labels defined in rule. To discard a subvalue, just add a redundant `,` in the names of the target labels. 
+Splits the value of a given label by separator `SEP` and creates new labels if their number matches to the number of target labels defined in rule. To discard a subvalue, just add a redundant `,` in the names of the target labels. 
 
 Example:
 
 ```yaml
 split: node `/` ,aggr,plex,disk
-# will split the value of "node" using seperator "/"
+# will split the value of "node" using separator "/"
 # will expect 4 values: first will be discarded, remaining
 # three will be stored as labels "aggr", "plex" and "disk"
 ```
 
 ## split_regex
 
-Does the same as `split_regex` but uses a regular expression instead of a seperator.
+Does the same as `split_regex` but uses a regular expression instead of a separator.
 
 Rule syntax:
 
@@ -170,7 +169,7 @@ Rule syntax:
 
 ```yaml
 split_pairs: LABEL `SEP1` `SEP2`
-# source label - pair seperator - key-value seperator
+# source label - pair separator - key-value separator
 ```
 
 Extracts key-value pairs from the value of source label `LABEL`. Note that you need to add these keys in the export options, otherwise they will not be exported.
@@ -186,13 +185,13 @@ split_pairs: comment ` ` `:`
 
 ## join
 
-Join multiple label values using seperator `SEP` and create a new label. 
+Join multiple label values using separator `SEP` and create a new label. 
 
 Rule syntax:
 
 ```yaml
 join: LABEL `SEP` LABEL1,LABEL2,LABEL3
-# target label - seperator - comma-seperated source labels
+# target label - separator - comma-seperated source labels
 ```
 
 Example:
@@ -307,7 +306,7 @@ value_mapping was deprecated in 21.11 and removed in 22.02. Use [value_to_num ma
 
 ## value_to_num
 
-Maps values of a given label to a numeric metric (of type `uint8`).
+Map values of a given label to a numeric metric (of type `uint8`).
 This rule maps values of a given label to a numeric metric (of type `unit8`). Healthy is mapped to 1 and all non-healthy values are mapped to 0.
 
 This is handy to manipulate the data in the DB or Grafana (e.g. change color based on status or create alert).
@@ -318,8 +317,8 @@ Rule syntax:
 
 ```yaml
 value_to_num: METRIC LABEL ZAPI_VALUE REST_VALUE `N`
-# maps values of LABEL to 1 if it is ZAPI_VALUE or REST_VALUE
-# otherwise value of METRIC is set to N
+# map values of LABEL to 1 if it is ZAPI_VALUE or REST_VALUE
+# otherwise, value of METRIC is set to N
 ```
 The default value `N` is optional, if no default value is given and the label value does not match any of the given values, the metric value will not be set.
 
@@ -341,6 +340,6 @@ value_to_num: status state up online `4`
 ```yaml
 value_to_num: status outage - - `0` #ok_value is empty value. 
 # metric value will be set to 1 if "outage" is empty, if it's any other value, it will be set to the default, 0
-# '-' is a special symbol in this mapping and it will be converted to blank while processing.
+# '-' is a special symbol in this mapping, and it will be converted to blank while processing.
 ```
 
diff --git a/cmd/poller/plugin/label_agent/parse_rules.go b/cmd/poller/plugin/label_agent/parse_rules.go
index 10e2ca17..d4cb8a18 100644
--- a/cmd/poller/plugin/label_agent/parse_rules.go
+++ b/cmd/poller/plugin/label_agent/parse_rules.go
@@ -55,7 +55,9 @@ func (me *LabelAgent) parseRules() int {
 			case "value_to_num":
 				me.parseValueToNumRule(rule)
 			default:
-				me.Logger.Warn().Msgf("unknown rule (%s)", name)
+				me.Logger.Warn().
+					Str("object", me.ParentParams.GetChildContentS("object")).
+					Str("name", name).Msg("Unknown rule name")
 			}
 		}
 	}
diff --git a/cmd/tools/doctor/doctor.go b/cmd/tools/doctor/doctor.go
index 8af716d3..ed20b6dd 100644
--- a/cmd/tools/doctor/doctor.go
+++ b/cmd/tools/doctor/doctor.go
@@ -49,12 +49,12 @@ func doMergeCmd(_ *cobra.Command, _ []string) {
 }
 
 func doMerge(path1 string, path2 string) {
-	template, err := tree.Import("yaml", path1)
+	template, err := tree.ImportYaml(path1)
 	if err != nil {
 		fmt.Printf("error reading template file [%s]. err=%+v\n", path1, err)
 		return
 	}
-	subTemplate, err := tree.Import("yaml", path2)
+	subTemplate, err := tree.ImportYaml(path2)
 	if err != nil {
 		fmt.Printf("error reading template file [%s] err=%+v\n", path2, err)
 		return
diff --git a/cmd/tools/zapi/get_attrs.go b/cmd/tools/zapi/get_attrs.go
index 1250e568..9c43f304 100644
--- a/cmd/tools/zapi/get_attrs.go
+++ b/cmd/tools/zapi/get_attrs.go
@@ -44,12 +44,12 @@ func getAttrs(c *client.Client, a *Args) (*node.Node, error) {
 	}
 
 	fmt.Println("############################        INPUT        ##########################")
-	input.Print(0)
+	fmt.Println(input.Print(0))
 	fmt.Println()
 	fmt.Println()
 
 	fmt.Println("############################        OUPUT        ##########################")
-	output.Print(0)
+	fmt.Println(output.Print(0))
 	fmt.Println()
 	fmt.Println()
 
diff --git a/cmd/tools/zapi/show.go b/cmd/tools/zapi/show.go
index 8d99d017..81fe04d6 100644
--- a/cmd/tools/zapi/show.go
+++ b/cmd/tools/zapi/show.go
@@ -34,11 +34,11 @@ func show(n *node.Node, args *Args) {
 }
 
 func showSystem(n *node.Node) {
-	n.Print(0)
+	fmt.Println(n.Print(0))
 }
 
 func showApis(n *node.Node) {
-	n.Print(0)
+	fmt.Println(n.Print(0))
 }
 
 func showObjects(item *node.Node) {
@@ -54,19 +54,19 @@ func showObjects(item *node.Node) {
 }
 
 func showAttrs(n *node.Node) {
-	n.Print(0)
+	fmt.Println(n.Print(0))
 }
 
 func showCounters(n *node.Node) {
-	n.Print(0)
+	fmt.Println(n.Print(0))
 }
 
 func showCounter(n *node.Node) {
-	n.Print(0)
+	fmt.Println(n.Print(0))
 }
 
 func showInstances(n *node.Node) {
-	n.Print(0)
+	fmt.Println(n.Print(0))
 }
 
 func showData(n *node.Node, a *Args) {
@@ -76,6 +76,6 @@ func showData(n *node.Node, a *Args) {
 		fmt.Println(string(n.Content))
 		fmt.Println("</root>")
 	} else {
-		n.Print(0)
+		fmt.Println(n.Print(0))
 	}
 }
diff --git a/conf/rest/9.13.0/volume.yaml b/conf/rest/9.13.0/volume.yaml
index 84d273f4..e2cd94e5 100644
--- a/conf/rest/9.13.0/volume.yaml
+++ b/conf/rest/9.13.0/volume.yaml
@@ -57,7 +57,7 @@ plugins:
     replace:
       - style style `flexgroup_constituent` `flexgroup`
   Aggregator:
-    volume<style=flexgroup>volume node,svm,aggr,style
+    - volume<style=flexgroup>volume node,svm,aggr,style
 
 export_options:
   instance_keys:
diff --git a/conf/zapi/7mode/8.6.0/shelf.yaml b/conf/zapi/7mode/8.6.0/shelf.yaml
index ba6c6ef6..28d9a7fb 100644
--- a/conf/zapi/7mode/8.6.0/shelf.yaml
+++ b/conf/zapi/7mode/8.6.0/shelf.yaml
@@ -26,56 +26,56 @@ plugins:
   Shelf:
     objects:
       - cooling-element-list => fan:
-        cooling-element-info:
-          - ^^cooling-element-number => fan_id
-          - ^location  #fake
-          - rpm
-#          - ^cooling-element-is-error
-#          - ^cooling-element-is-not-installed
+          cooling-element-info:
+            - ^^cooling-element-number => fan_id
+            - ^location  #fake
+            - rpm
+  #          - ^cooling-element-is-error
+  #          - ^cooling-element-is-not-installed
       - current-sensor-list => sensor:
-        current-sensor-info:
-          - ^^current-sensor-no => sensor_id
-          - ^location #fake
-          - sensor-reading => reading
-#        - ^is-sensor-error
-#        - ^is-sensor-not-installed
-#        - ^sensor-condition
+          current-sensor-info:
+            - ^^current-sensor-no => sensor_id
+            - ^location #fake
+            - sensor-reading => reading
+  #        - ^is-sensor-error
+  #        - ^is-sensor-not-installed
+  #        - ^sensor-condition
       - temp-sensor-list => temperature:
-        temp-sensor-info:
-          - ^^temp-sensor-element-no => sensor_id
-          - temp-sensor-current-temperature => reading
-          - ^temp-sensor-hi-critical => high_critical
-          - ^temp-sensor-hi-warning => high_warning
-          - ^temp-sensor-is-ambient => temp_is_ambient
-          - ^temp-sensor-low-critical => low_critical
-          - ^temp-sensor-low-warning => low_warning
-#        - ^temp-sensor-is-error
-#        - ^temp-sensor-is-not-installed
-#        - ^temp-sensor-current-condition
+          temp-sensor-info:
+            - ^^temp-sensor-element-no => sensor_id
+            - temp-sensor-current-temperature => reading
+            - ^temp-sensor-hi-critical => high_critical
+            - ^temp-sensor-hi-warning => high_warning
+            - ^temp-sensor-is-ambient => temp_is_ambient
+            - ^temp-sensor-low-critical => low_critical
+            - ^temp-sensor-low-warning => low_warning
+  #        - ^temp-sensor-is-error
+  #        - ^temp-sensor-is-not-installed
+  #        - ^temp-sensor-current-condition
       - voltage-sensor-list => voltage:
-        voltage-sensor-info:
-          - ^^voltage-sensor-no => sensor_id
-          - sensor-reading => reading
-          - ^location #fake
-#        - ^is-sensor-error
-#        - ^is-sensor-not-installed
-#        - ^sensor-condition
+          voltage-sensor-info:
+            - ^^voltage-sensor-no => sensor_id
+            - sensor-reading => reading
+            - ^location #fake
+  #        - ^is-sensor-error
+  #        - ^is-sensor-not-installed
+  #        - ^sensor-condition
 
       - power-supply-list => psu:
-        power-supply-info:
-          - ^^power-supply-element-number => psu_id
-          - ^power-supply-part-no => part_number
-          - ^power-supply-serial-no => serial
-        #  - power-supply-swap-count => psu_swap_count
-          - ^power-supply-type => type
-        # fake counters to match CDOT
-          - ^enabled
-          - ^location
-#        - ^is-auto-power-reset-enabled
-#        - ^is-power-reset-capable
-#        - ^power-supply-firmware-revision
-#        - ^power-supply-is-error
-#        - ^power-supply-is-not-installed
+          power-supply-info:
+            - ^^power-supply-element-number => psu_id
+            - ^power-supply-part-no => part_number
+            - ^power-supply-serial-no => serial
+          #  - power-supply-swap-count => psu_swap_count
+            - ^power-supply-type => type
+          # fake counters to match CDOT
+            - ^enabled
+            - ^location
+  #        - ^is-auto-power-reset-enabled
+  #        - ^is-power-reset-capable
+  #        - ^power-supply-firmware-revision
+  #        - ^power-supply-is-error
+  #        - ^power-supply-is-not-installed
   LabelAgent:
     # metric label zapi_value rest_value `default_value`
     value_to_num:
diff --git a/conf/zapi/cdot/9.8.0/shelf.yaml b/conf/zapi/cdot/9.8.0/shelf.yaml
index 12f57419..bcc08560 100644
--- a/conf/zapi/cdot/9.8.0/shelf.yaml
+++ b/conf/zapi/cdot/9.8.0/shelf.yaml
@@ -20,47 +20,47 @@ plugins:
   Shelf:
     objects:
       - cooling-fans => fan:
-        storage-shelf-cooling-fan-info:
-          - ^^fan-id
-          - ^fan-location => location
-          - ^fan-op-status => status
-          - fan-rpm => rpm
+          storage-shelf-cooling-fan-info:
+            - ^^fan-id
+            - ^fan-location => location
+            - ^fan-op-status => status
+            - fan-rpm => rpm
       - current-sensors => sensor:
-        storage-shelf-current-sensor-info:
-          - ^^current-sensor-id    => sensor_id
-          - ^current-sensor-location => location
-          - ^current-op-status => status
-          - current-sensor-reading => reading
+          storage-shelf-current-sensor-info:
+            - ^^current-sensor-id    => sensor_id
+            - ^current-sensor-location => location
+            - ^current-op-status => status
+            - current-sensor-reading => reading
       - power-supply-units => psu:
-        storage-shelf-power-supply-unit-info:
-#          - ^psu-crest-factor => crest_factor
-#          - ^psu-fw-version => fw_version
-          - ^^psu-id
-          - ^psu-is-enabled => enabled
-          - ^psu-location => location
-          - ^psu-part-number => part_number
-          - psu-power-drawn => power_drawn
-          - psu-power-rating => power_rating
-#          - ^psu-reset-capable => reset_capable
-          - ^psu-serial-number => serial
-          - ^psu-type => type
-          - ^psu-op-status => status
+          storage-shelf-power-supply-unit-info:
+  #          - ^psu-crest-factor => crest_factor
+  #          - ^psu-fw-version => fw_version
+            - ^^psu-id
+            - ^psu-is-enabled => enabled
+            - ^psu-location => location
+            - ^psu-part-number => part_number
+            - psu-power-drawn => power_drawn
+            - psu-power-rating => power_rating
+  #          - ^psu-reset-capable => reset_capable
+            - ^psu-serial-number => serial
+            - ^psu-type => type
+            - ^psu-op-status => status
       - temperature-sensors => temperature:
-        storage-shelf-temperature-sensor-info:
-          - ^high-critical-threshold => high_critical
-          - ^high-warning-threshold => high_warning
-          - ^temp-is-ambient
-          - ^temp-low-critical-threshold => low_critical
-          - ^temp-low-warning-threshold => low_warning
-          - ^^temp-sensor-id => sensor_id
-          - ^temp-sens-op-status => status
-          - temp-sensor-reading => reading
+          storage-shelf-temperature-sensor-info:
+            - ^high-critical-threshold => high_critical
+            - ^high-warning-threshold => high_warning
+            - ^temp-is-ambient
+            - ^temp-low-critical-threshold => low_critical
+            - ^temp-low-warning-threshold => low_warning
+            - ^^temp-sensor-id => sensor_id
+            - ^temp-sens-op-status => status
+            - temp-sensor-reading => reading
       - voltage-sensors => voltage:
-        storage-shelf-voltage-sensor-info:
-          - ^^voltage-sensor-id => sensor_id
-          - ^voltage-sensor-location => location
-          - ^voltage-op-status => status
-          - voltage-sensor-reading => reading
+          storage-shelf-voltage-sensor-info:
+            - ^^voltage-sensor-id => sensor_id
+            - ^voltage-sensor-location => location
+            - ^voltage-op-status => status
+            - voltage-sensor-reading => reading
 
   LabelAgent:
     # metric label zapi_value rest_value `default_value`
diff --git a/conf/zapi/cdot/9.8.0/volume.yaml b/conf/zapi/cdot/9.8.0/volume.yaml
index cbc16c0b..fffca1bf 100644
--- a/conf/zapi/cdot/9.8.0/volume.yaml
+++ b/conf/zapi/cdot/9.8.0/volume.yaml
@@ -68,7 +68,7 @@ plugins:
     replace:
       - style style `flexgroup_constituent` `flexgroup`
   Aggregator:
-    volume<style=flexgroup>volume node,svm,aggr,style
+    - volume<style=flexgroup>volume node,svm,aggr,style
 
 export_options:
   instance_keys:
diff --git a/conf/zapiperf/cdot/9.8.0/workload.yaml b/conf/zapiperf/cdot/9.8.0/workload.yaml
index 5ed2404c..df2b12a0 100644
--- a/conf/zapiperf/cdot/9.8.0/workload.yaml
+++ b/conf/zapiperf/cdot/9.8.0/workload.yaml
@@ -15,6 +15,7 @@ schedule:
   - data: 180s
 
 counters:
+  - concurrency
   - instance_name
   - instance_uuid
   - latency
diff --git a/grafana/dashboards/cmode/harvest_dashboard_mcc_cluster.json b/grafana/dashboards/cmode/harvest_dashboard_mcc_cluster.json
index 878c3f2a..62b70fca 100644
--- a/grafana/dashboards/cmode/harvest_dashboard_mcc_cluster.json
+++ b/grafana/dashboards/cmode/harvest_dashboard_mcc_cluster.json
@@ -61,7 +61,9 @@
       }
     ]
   },
+  "description": "",
   "editable": true,
+  "fiscalYearStartMonth": 0,
   "gnetId": null,
   "graphTooltip": 1,
   "id": null,
@@ -112,11 +114,11 @@
               },
               {
                 "color": "rgba(245, 150, 40, 0.73)",
-                "value": 20
+                "value": 60
               },
               {
                 "color": "rgba(225, 40, 40, 0.59)",
-                "value": 30
+                "value": 80
               }
             ]
           },
@@ -192,11 +194,11 @@
               },
               {
                 "color": "rgba(245, 150, 40, 0.73)",
-                "value": 2
+                "value": 60
               },
               {
                 "color": "rgba(225, 40, 40, 0.59)",
-                "value": 10
+                "value": 80
               }
             ]
           },
@@ -910,6 +912,12 @@
       "decimals": 2,
       "editable": true,
       "error": false,
+      "fieldConfig": {
+        "defaults": {
+          "unit": "µs"
+        },
+        "overrides": []
+      },
       "fill": 1,
       "fillGradient": 0,
       "grid": {},
@@ -1460,7 +1468,7 @@
       "error": false,
       "fieldConfig": {
         "defaults": {
-          "unit": "µs"
+          "unit": "iops"
         },
         "overrides": []
       },
@@ -1537,7 +1545,7 @@
       },
       "yaxes": [
         {
-          "format": "µs",
+          "format": "iops",
           "label": "IOPs",
           "logBase": 1,
           "max": null,
@@ -1567,7 +1575,7 @@
       "error": false,
       "fieldConfig": {
         "defaults": {
-          "unit": "µs"
+          "unit": "iops"
         },
         "overrides": []
       },
@@ -1644,7 +1652,7 @@
       },
       "yaxes": [
         {
-          "format": "µs",
+          "format": "iops",
           "label": "IOPs",
           "logBase": 1,
           "max": null,
@@ -1675,6 +1683,12 @@
       "datasource": "${DS_PROMETHEUS}",
       "editable": true,
       "error": false,
+      "fieldConfig": {
+        "defaults": {
+          "unit": "µs"
+        },
+        "overrides": []
+      },
       "fill": 1,
       "fillGradient": 0,
       "grid": {},
@@ -1762,7 +1776,7 @@
       },
       "yaxes": [
         {
-          "format": "ms",
+          "format": "µs",
           "label": "",
           "logBase": 1,
           "max": null,
@@ -1794,6 +1808,12 @@
       "datasource": "${DS_PROMETHEUS}",
       "editable": true,
       "error": false,
+      "fieldConfig": {
+        "defaults": {
+          "unit": "µs"
+        },
+        "overrides": []
+      },
       "fill": 1,
       "fillGradient": 0,
       "grid": {},
@@ -1881,7 +1901,7 @@
       },
       "yaxes": [
         {
-          "format": "ms",
+          "format": "µs",
           "label": "",
           "logBase": 1,
           "max": null,
@@ -1903,7 +1923,7 @@
       "zerofill": true
     },
     {
-      "collapsed": false,
+      "collapsed": true,
       "datasource": "${DS_PROMETHEUS}",
       "gridPos": {
         "h": 1,
@@ -1912,510 +1932,513 @@
         "y": 47
       },
       "id": 92,
-      "panels": [],
+      "panels": [
+        {
+          "aliasColors": {},
+          "annotate": {
+            "enable": false
+          },
+          "bars": false,
+          "dashLength": 10,
+          "dashes": false,
+          "datasource": "${DS_PROMETHEUS}",
+          "editable": true,
+          "fieldConfig": {
+            "defaults": {
+              "unit": "Bps"
+            },
+            "overrides": []
+          },
+          "fill": 1,
+          "fillGradient": 0,
+          "grid": {
+            "max": null,
+            "min": 0
+          },
+          "gridPos": {
+            "h": 8,
+            "w": 12,
+            "x": 0,
+            "y": 16
+          },
+          "hiddenSeries": false,
+          "id": 75,
+          "legend": {
+            "alignAsTable": true,
+            "avg": true,
+            "current": true,
+            "hideEmpty": true,
+            "max": true,
+            "min": false,
+            "rightSide": false,
+            "show": true,
+            "total": false,
+            "values": true
+          },
+          "lines": true,
+          "linewidth": 2,
+          "links": [],
+          "loadingEditor": false,
+          "nullPointMode": "connected",
+          "options": {
+            "alertThreshold": true
+          },
+          "percentage": false,
+          "pluginVersion": "8.2.1",
+          "pointradius": 5,
+          "points": false,
+          "renderer": "flot",
+          "resolution": 100,
+          "scale": 1,
+          "seriesOverrides": [],
+          "spaceLength": 10,
+          "stack": false,
+          "steppedLine": false,
+          "targets": [
+            {
+              "exemplar": true,
+              "expr": "topk($TopResources,hostadapter_bytes_read{cluster=\"$Cluster\", datacenter=\"$Datacenter\", node=~\"$Node\"}) by (node)",
+              "hide": false,
+              "interval": "",
+              "legendFormat": "{{node}} - {{hostadapter}}",
+              "refId": "B",
+              "textEditor": true
+            }
+          ],
+          "thresholds": [],
+          "timeFrom": null,
+          "timeRegions": [],
+          "timeShift": null,
+          "title": "Adapter Read Data",
+          "tooltip": {
+            "msResolution": true,
+            "query_as_alias": true,
+            "shared": true,
+            "sort": 0,
+            "value_type": "cumulative"
+          },
+          "type": "graph",
+          "xaxis": {
+            "buckets": null,
+            "mode": "time",
+            "name": null,
+            "show": true,
+            "values": []
+          },
+          "yaxes": [
+            {
+              "format": "Bps",
+              "label": "MB/s",
+              "logBase": 1,
+              "max": null,
+              "min": 0,
+              "show": true
+            },
+            {
+              "format": "short",
+              "logBase": 1,
+              "max": null,
+              "min": null,
+              "show": true
+            }
+          ],
+          "yaxis": {
+            "align": false,
+            "alignLevel": null
+          },
+          "zerofill": true
+        },
+        {
+          "aliasColors": {},
+          "annotate": {
+            "enable": false
+          },
+          "bars": false,
+          "dashLength": 10,
+          "dashes": false,
+          "datasource": "${DS_PROMETHEUS}",
+          "editable": true,
+          "fieldConfig": {
+            "defaults": {
+              "unit": "Bps"
+            },
+            "overrides": []
+          },
+          "fill": 1,
+          "fillGradient": 0,
+          "grid": {
+            "max": null,
+            "min": 0
+          },
+          "gridPos": {
+            "h": 8,
+            "w": 12,
+            "x": 12,
+            "y": 16
+          },
+          "hiddenSeries": false,
+          "id": 76,
+          "legend": {
+            "alignAsTable": true,
+            "avg": true,
+            "current": true,
+            "hideEmpty": true,
+            "max": true,
+            "min": false,
+            "rightSide": false,
+            "show": true,
+            "total": false,
+            "values": true
+          },
+          "lines": true,
+          "linewidth": 2,
+          "links": [],
+          "loadingEditor": false,
+          "nullPointMode": "connected",
+          "options": {
+            "alertThreshold": true
+          },
+          "percentage": false,
+          "pluginVersion": "8.2.1",
+          "pointradius": 5,
+          "points": false,
+          "renderer": "flot",
+          "resolution": 100,
+          "scale": 1,
+          "seriesOverrides": [],
+          "spaceLength": 10,
+          "stack": false,
+          "steppedLine": false,
+          "targets": [
+            {
+              "exemplar": true,
+              "expr": "topk($TopResources,hostadapter_bytes_written{cluster=\"$Cluster\", datacenter=\"$Datacenter\", node=~\"$Node\"}) by (node)",
+              "hide": false,
+              "interval": "",
+              "legendFormat": "{{node}} - {{hostadapter}}",
+              "refId": "B",
+              "textEditor": false
+            }
+          ],
+          "thresholds": [],
+          "timeFrom": null,
+          "timeRegions": [],
+          "timeShift": null,
+          "title": "Adapter Write Data",
+          "tooltip": {
+            "msResolution": true,
+            "query_as_alias": true,
+            "shared": true,
+            "sort": 0,
+            "value_type": "cumulative"
+          },
+          "type": "graph",
+          "xaxis": {
+            "buckets": null,
+            "mode": "time",
+            "name": null,
+            "show": true,
+            "values": []
+          },
+          "yaxes": [
+            {
+              "format": "Bps",
+              "label": "MB/s",
+              "logBase": 1,
+              "max": null,
+              "min": 0,
+              "show": true
+            },
+            {
+              "format": "short",
+              "logBase": 1,
+              "max": null,
+              "min": null,
+              "show": true
+            }
+          ],
+          "yaxis": {
+            "align": false,
+            "alignLevel": null
+          },
+          "zerofill": true
+        }
+      ],
       "repeat": null,
       "title": "Disk and Tape Adapter Drilldown",
       "type": "row"
     },
     {
-      "aliasColors": {},
-      "annotate": {
-        "enable": false
-      },
-      "bars": false,
-      "dashLength": 10,
-      "dashes": false,
+      "collapsed": true,
       "datasource": "${DS_PROMETHEUS}",
-      "editable": true,
-      "fieldConfig": {
-        "defaults": {
-          "unit": "µs"
-        },
-        "overrides": []
-      },
-      "fill": 1,
-      "fillGradient": 0,
-      "grid": {
-        "max": null,
-        "min": 0
-      },
       "gridPos": {
-        "h": 8,
-        "w": 12,
+        "h": 1,
+        "w": 24,
         "x": 0,
         "y": 48
       },
-      "hiddenSeries": false,
-      "id": 75,
-      "legend": {
-        "alignAsTable": true,
-        "avg": true,
-        "current": true,
-        "hideEmpty": true,
-        "max": true,
-        "min": false,
-        "rightSide": false,
-        "show": true,
-        "total": false,
-        "values": true
-      },
-      "lines": true,
-      "linewidth": 2,
-      "links": [],
-      "loadingEditor": false,
-      "nullPointMode": "connected",
-      "options": {
-        "alertThreshold": true
-      },
-      "percentage": false,
-      "pluginVersion": "8.2.1",
-      "pointradius": 5,
-      "points": false,
-      "renderer": "flot",
-      "resolution": 100,
-      "scale": 1,
-      "seriesOverrides": [],
-      "spaceLength": 10,
-      "stack": false,
-      "steppedLine": false,
-      "targets": [
-        {
-          "exemplar": true,
-          "expr": "topk($TopResources,hostadapter_bytes_read{cluster=\"$Cluster\", datacenter=\"$Datacenter\", node=~\"$Node\"}) by (node)",
-          "hide": false,
-          "interval": "",
-          "legendFormat": "{{node}} - {{hostadapter}}",
-          "refId": "B",
-          "textEditor": true
-        }
-      ],
-      "thresholds": [],
-      "timeFrom": null,
-      "timeRegions": [],
-      "timeShift": null,
-      "title": "Adapter Read Data",
-      "tooltip": {
-        "msResolution": true,
-        "query_as_alias": true,
-        "shared": true,
-        "sort": 0,
-        "value_type": "cumulative"
-      },
-      "type": "graph",
-      "xaxis": {
-        "buckets": null,
-        "mode": "time",
-        "name": null,
-        "show": true,
-        "values": []
-      },
-      "yaxes": [
+      "id": 93,
+      "panels": [
         {
-          "format": "µs",
-          "label": "MB/s",
-          "logBase": 1,
-          "max": null,
-          "min": 0,
-          "show": true
+          "aliasColors": {},
+          "annotate": {
+            "enable": false
+          },
+          "bars": false,
+          "dashLength": 10,
+          "dashes": false,
+          "datasource": "${DS_PROMETHEUS}",
+          "editable": true,
+          "error": false,
+          "fieldConfig": {
+            "defaults": {
+              "unit": "Bps"
+            },
+            "overrides": []
+          },
+          "fill": 1,
+          "fillGradient": 0,
+          "grid": {},
+          "gridPos": {
+            "h": 9,
+            "w": 12,
+            "x": 0,
+            "y": 17
+          },
+          "height": "",
+          "hiddenSeries": false,
+          "id": 86,
+          "legend": {
+            "alignAsTable": true,
+            "avg": true,
+            "current": true,
+            "max": true,
+            "min": false,
+            "show": true,
+            "total": false,
+            "values": true
+          },
+          "lines": true,
+          "linewidth": 2,
+          "links": [],
+          "nullPointMode": "connected",
+          "options": {
+            "alertThreshold": true
+          },
+          "percentage": false,
+          "pluginVersion": "8.2.1",
+          "pointradius": 2,
+          "points": false,
+          "renderer": "flot",
+          "resolution": 100,
+          "scale": 1,
+          "seriesOverrides": [],
+          "spaceLength": 10,
+          "stack": false,
+          "steppedLine": false,
+          "targets": [
+            {
+              "exemplar": true,
+              "expr": "hostadapter_bytes_read{datacenter=\"$Datacenter\",cluster=\"$Cluster\",node=~\"$Node\"}",
+              "interval": "",
+              "legendFormat": "{{node}}.{{hostadapter}}",
+              "refId": "A",
+              "textEditor": false
+            }
+          ],
+          "thresholds": [],
+          "timeFrom": null,
+          "timeRegions": [],
+          "timeShift": null,
+          "title": "FibreBridge/Array WWPN - Read Data",
+          "tooltip": {
+            "msResolution": false,
+            "query_as_alias": true,
+            "shared": true,
+            "sort": 0,
+            "value_type": "cumulative"
+          },
+          "type": "graph",
+          "xaxis": {
+            "buckets": null,
+            "mode": "time",
+            "name": null,
+            "show": true,
+            "values": []
+          },
+          "yaxes": [
+            {
+              "format": "Bps",
+              "label": "",
+              "logBase": 1,
+              "max": null,
+              "min": 0,
+              "show": true
+            },
+            {
+              "format": "short",
+              "logBase": 1,
+              "max": null,
+              "min": null,
+              "show": true
+            }
+          ],
+          "yaxis": {
+            "align": false,
+            "alignLevel": null
+          },
+          "zerofill": true
         },
         {
-          "format": "short",
-          "logBase": 1,
-          "max": null,
-          "min": null,
-          "show": true
+          "aliasColors": {},
+          "annotate": {
+            "enable": false
+          },
+          "bars": false,
+          "dashLength": 10,
+          "dashes": false,
+          "datasource": "${DS_PROMETHEUS}",
+          "decimals": 2,
+          "editable": true,
+          "error": false,
+          "fieldConfig": {
+            "defaults": {
+              "unit": "Bps"
+            },
+            "overrides": []
+          },
+          "fill": 1,
+          "fillGradient": 0,
+          "grid": {},
+          "gridPos": {
+            "h": 9,
+            "w": 12,
+            "x": 12,
+            "y": 17
+          },
+          "height": "",
+          "hiddenSeries": false,
+          "id": 85,
+          "legend": {
+            "alignAsTable": true,
+            "avg": true,
+            "current": true,
+            "max": true,
+            "min": false,
+            "show": true,
+            "total": false,
+            "values": true
+          },
+          "lines": true,
+          "linewidth": 2,
+          "links": [],
+          "nullPointMode": "connected",
+          "options": {
+            "alertThreshold": true
+          },
+          "percentage": false,
+          "pluginVersion": "8.2.1",
+          "pointradius": 2,
+          "points": false,
+          "renderer": "flot",
+          "resolution": 100,
+          "scale": 1,
+          "seriesOverrides": [],
+          "spaceLength": 10,
+          "stack": false,
+          "steppedLine": false,
+          "targets": [
+            {
+              "exemplar": true,
+              "expr": "hostadapter_bytes_written{datacenter=\"$Datacenter\",cluster=\"$Cluster\",node=~\"$Node\"}",
+              "interval": "",
+              "legendFormat": "{{node}}.{{hostadapter}}",
+              "refId": "A",
+              "textEditor": false
+            }
+          ],
+          "thresholds": [],
+          "timeFrom": null,
+          "timeRegions": [],
+          "timeShift": null,
+          "title": "FibreBridge/Array WWPN - Write Data",
+          "tooltip": {
+            "msResolution": true,
+            "query_as_alias": true,
+            "shared": true,
+            "sort": 0,
+            "value_type": "cumulative"
+          },
+          "type": "graph",
+          "xaxis": {
+            "buckets": null,
+            "mode": "time",
+            "name": null,
+            "show": true,
+            "values": []
+          },
+          "yaxes": [
+            {
+              "format": "Bps",
+              "label": "",
+              "logBase": 1,
+              "max": null,
+              "min": 0,
+              "show": true
+            },
+            {
+              "format": "short",
+              "logBase": 1,
+              "max": null,
+              "min": null,
+              "show": true
+            }
+          ],
+          "yaxis": {
+            "align": false,
+            "alignLevel": null
+          },
+          "zerofill": true
         }
       ],
-      "yaxis": {
-        "align": false,
-        "alignLevel": null
-      },
-      "zerofill": true
-    },
-    {
-      "aliasColors": {},
-      "annotate": {
-        "enable": false
-      },
-      "bars": false,
-      "dashLength": 10,
-      "dashes": false,
-      "datasource": "${DS_PROMETHEUS}",
-      "editable": true,
-      "fieldConfig": {
-        "defaults": {
-          "unit": "Bps"
+      "repeat": null,
+      "title": "MetroCluster FibreBridge/Array Drilldown",
+      "type": "row"
+    }
+  ],
+  "refresh": "",
+  "schemaVersion": 31,
+  "style": "dark",
+  "tags": [],
+  "templating": {
+    "list": [
+      {
+        "current": {
+          "selected": false,
+          "text": "Prometheus",
+          "value": "Prometheus"
         },
-        "overrides": []
-      },
-      "fill": 1,
-      "fillGradient": 0,
-      "grid": {
-        "max": null,
-        "min": 0
-      },
-      "gridPos": {
-        "h": 8,
-        "w": 12,
-        "x": 12,
-        "y": 48
-      },
-      "hiddenSeries": false,
-      "id": 76,
-      "legend": {
-        "alignAsTable": true,
-        "avg": true,
-        "current": true,
-        "hideEmpty": true,
-        "max": true,
-        "min": false,
-        "rightSide": false,
-        "show": true,
-        "total": false,
-        "values": true
-      },
-      "lines": true,
-      "linewidth": 2,
-      "links": [],
-      "loadingEditor": false,
-      "nullPointMode": "connected",
-      "options": {
-        "alertThreshold": true
-      },
-      "percentage": false,
-      "pluginVersion": "8.2.1",
-      "pointradius": 5,
-      "points": false,
-      "renderer": "flot",
-      "resolution": 100,
-      "scale": 1,
-      "seriesOverrides": [],
-      "spaceLength": 10,
-      "stack": false,
-      "steppedLine": false,
-      "targets": [
-        {
-          "exemplar": true,
-          "expr": "topk($TopResources,hostadapter_bytes_written{cluster=\"$Cluster\", datacenter=\"$Datacenter\", node=~\"$Node\"}) by (node)",
-          "hide": false,
-          "interval": "",
-          "legendFormat": "{{node}} - {{hostadapter}}",
-          "refId": "B",
-          "textEditor": false
-        }
-      ],
-      "thresholds": [],
-      "timeFrom": null,
-      "timeRegions": [],
-      "timeShift": null,
-      "title": "Adapter Write Data",
-      "tooltip": {
-        "msResolution": true,
-        "query_as_alias": true,
-        "shared": true,
-        "sort": 0,
-        "value_type": "cumulative"
-      },
-      "type": "graph",
-      "xaxis": {
-        "buckets": null,
-        "mode": "time",
-        "name": null,
-        "show": true,
-        "values": []
-      },
-      "yaxes": [
-        {
-          "format": "Bps",
-          "label": "MB/s",
-          "logBase": 1,
-          "max": null,
-          "min": 0,
-          "show": true
-        },
-        {
-          "format": "short",
-          "logBase": 1,
-          "max": null,
-          "min": null,
-          "show": true
-        }
-      ],
-      "yaxis": {
-        "align": false,
-        "alignLevel": null
-      },
-      "zerofill": true
-    },
-    {
-      "collapsed": false,
-      "datasource": "${DS_PROMETHEUS}",
-      "gridPos": {
-        "h": 1,
-        "w": 24,
-        "x": 0,
-        "y": 56
-      },
-      "id": 93,
-      "panels": [],
-      "repeat": null,
-      "title": "MetroCluster FibreBridge/Array Drilldown",
-      "type": "row"
-    },
-    {
-      "aliasColors": {},
-      "annotate": {
-        "enable": false
-      },
-      "bars": false,
-      "dashLength": 10,
-      "dashes": false,
-      "datasource": "${DS_PROMETHEUS}",
-      "editable": true,
-      "error": false,
-      "fieldConfig": {
-        "defaults": {
-          "unit": "µs"
-        },
-        "overrides": []
-      },
-      "fill": 1,
-      "fillGradient": 0,
-      "grid": {},
-      "gridPos": {
-        "h": 9,
-        "w": 12,
-        "x": 0,
-        "y": 57
-      },
-      "height": "",
-      "hiddenSeries": false,
-      "id": 86,
-      "legend": {
-        "alignAsTable": true,
-        "avg": true,
-        "current": true,
-        "max": true,
-        "min": false,
-        "show": true,
-        "total": false,
-        "values": true
-      },
-      "lines": true,
-      "linewidth": 2,
-      "links": [],
-      "nullPointMode": "connected",
-      "options": {
-        "alertThreshold": true
-      },
-      "percentage": false,
-      "pluginVersion": "8.2.1",
-      "pointradius": 2,
-      "points": false,
-      "renderer": "flot",
-      "resolution": 100,
-      "scale": 1,
-      "seriesOverrides": [],
-      "spaceLength": 10,
-      "stack": false,
-      "steppedLine": false,
-      "targets": [
-        {
-          "exemplar": true,
-          "expr": "hostadapter_bytes_read{datacenter=\"$Datacenter\",cluster=\"$Cluster\",node=~\"$Node\"}",
-          "interval": "",
-          "legendFormat": "{{node}}.{{hostadapter}}",
-          "refId": "A",
-          "textEditor": false
-        }
-      ],
-      "thresholds": [],
-      "timeFrom": null,
-      "timeRegions": [],
-      "timeShift": null,
-      "title": "FibreBridge/Array WWPN - Read Data",
-      "tooltip": {
-        "msResolution": false,
-        "query_as_alias": true,
-        "shared": true,
-        "sort": 0,
-        "value_type": "cumulative"
-      },
-      "type": "graph",
-      "xaxis": {
-        "buckets": null,
-        "mode": "time",
-        "name": null,
-        "show": true,
-        "values": []
-      },
-      "yaxes": [
-        {
-          "format": "µs",
-          "label": "",
-          "logBase": 1,
-          "max": null,
-          "min": 0,
-          "show": true
-        },
-        {
-          "format": "short",
-          "logBase": 1,
-          "max": null,
-          "min": null,
-          "show": true
-        }
-      ],
-      "yaxis": {
-        "align": false,
-        "alignLevel": null
-      },
-      "zerofill": true
-    },
-    {
-      "aliasColors": {},
-      "annotate": {
-        "enable": false
-      },
-      "bars": false,
-      "dashLength": 10,
-      "dashes": false,
-      "datasource": "${DS_PROMETHEUS}",
-      "decimals": 2,
-      "editable": true,
-      "error": false,
-      "fieldConfig": {
-        "defaults": {
-          "unit": "Bps"
-        },
-        "overrides": []
-      },
-      "fill": 1,
-      "fillGradient": 0,
-      "grid": {},
-      "gridPos": {
-        "h": 9,
-        "w": 12,
-        "x": 12,
-        "y": 57
-      },
-      "height": "",
-      "hiddenSeries": false,
-      "id": 85,
-      "legend": {
-        "alignAsTable": true,
-        "avg": true,
-        "current": true,
-        "max": true,
-        "min": false,
-        "show": true,
-        "total": false,
-        "values": true
-      },
-      "lines": true,
-      "linewidth": 2,
-      "links": [],
-      "nullPointMode": "connected",
-      "options": {
-        "alertThreshold": true
-      },
-      "percentage": false,
-      "pluginVersion": "8.2.1",
-      "pointradius": 2,
-      "points": false,
-      "renderer": "flot",
-      "resolution": 100,
-      "scale": 1,
-      "seriesOverrides": [],
-      "spaceLength": 10,
-      "stack": false,
-      "steppedLine": false,
-      "targets": [
-        {
-          "exemplar": true,
-          "expr": "hostadapter_bytes_written{datacenter=\"$Datacenter\",cluster=\"$Cluster\",node=~\"$Node\"}",
-          "interval": "",
-          "legendFormat": "{{node}}.{{hostadapter}}",
-          "refId": "A",
-          "textEditor": false
-        }
-      ],
-      "thresholds": [],
-      "timeFrom": null,
-      "timeRegions": [],
-      "timeShift": null,
-      "title": "FibreBridge/Array WWPN - Write Data",
-      "tooltip": {
-        "msResolution": true,
-        "query_as_alias": true,
-        "shared": true,
-        "sort": 0,
-        "value_type": "cumulative"
-      },
-      "type": "graph",
-      "xaxis": {
-        "buckets": null,
-        "mode": "time",
-        "name": null,
-        "show": true,
-        "values": []
-      },
-      "yaxes": [
-        {
-          "format": "Bps",
-          "label": "",
-          "logBase": 1,
-          "max": null,
-          "min": 0,
-          "show": true
-        },
-        {
-          "format": "short",
-          "logBase": 1,
-          "max": null,
-          "min": null,
-          "show": true
-        }
-      ],
-      "yaxis": {
-        "align": false,
-        "alignLevel": null
-      },
-      "zerofill": true
-    }
-  ],
-  "refresh": "",
-  "schemaVersion": 31,
-  "style": "dark",
-  "templating": {
-    "list": [
-      {
-        "current": {
-          "selected": false,
-          "text": "Prometheus",
-          "value": "Prometheus"
-        },
-        "description": null,
-        "error": null,
-        "hide": 2,
-        "includeAll": false,
-        "label": "Data Source",
-        "multi": false,
-        "name": "DS_PROMETHEUS",
-        "options": [],
-        "query": "prometheus",
-        "refresh": 1,
-        "regex": "",
-        "skipUrlSync": false,
-        "type": "datasource"
+        "description": null,
+        "error": null,
+        "hide": 2,
+        "includeAll": false,
+        "label": "Data Source",
+        "multi": false,
+        "name": "DS_PROMETHEUS",
+        "options": [],
+        "query": "prometheus",
+        "refresh": 1,
+        "regex": "",
+        "skipUrlSync": false,
+        "type": "datasource"
       },
       {
         "allFormat": "wildcard",
         "allValue": null,
         "current": {
           "selected": false,
-          "text": "mcc",
-          "value": "mcc"
+          "text": "mcc_cluster",
+          "value": "mcc_cluster"
         },
         "datasource": "${DS_PROMETHEUS}",
         "definition": "label_values(node_labels{system_type!=\"7mode\"}, datacenter)",
@@ -2436,7 +2459,7 @@
         "refresh_on_load": false,
         "regex": "",
         "skipUrlSync": false,
-        "sort": 0,
+        "sort": 1,
         "tagValuesQuery": "",
         "tagsQuery": "",
         "type": "query",
@@ -2447,8 +2470,8 @@
         "allValue": null,
         "current": {
           "selected": false,
-          "text": "sti8300mcc261262_siteA",
-          "value": "sti8300mcc261262_siteA"
+          "text": "stiA800mccip-20201_siteA",
+          "value": "stiA800mccip-20201_siteA"
         },
         "datasource": "${DS_PROMETHEUS}",
         "definition": "label_values(aggr_new_status{system_type!=\"7mode\",datacenter=\"$Datacenter\"}, cluster)",
@@ -2469,7 +2492,7 @@
         "refresh_on_load": false,
         "regex": "",
         "skipUrlSync": false,
-        "sort": 0,
+        "sort": 1,
         "tagValuesQuery": "",
         "tagsQuery": "",
         "type": "query",
@@ -2499,7 +2522,7 @@
         "refresh": 1,
         "regex": "",
         "skipUrlSync": false,
-        "sort": 0,
+        "sort": 1,
         "type": "query"
       },
       {
@@ -2529,7 +2552,7 @@
         "refresh_on_load": false,
         "regex": "/[0-9]/",
         "skipUrlSync": false,
-        "sort": 0,
+        "sort": 1,
         "tagValuesQuery": "",
         "tagsQuery": "",
         "type": "query",
@@ -2654,5 +2677,5 @@
   "timezone": "browser",
   "title": "NetApp Detail: MetroCluster",
   "uid": "kryI9avnk",
-  "version": 1
+  "version": 2
 }
\ No newline at end of file
diff --git a/jenkins/artifacts/jenkinsfile b/jenkins/artifacts/jenkinsfile
index bfee5370..e0a487e5 100644
--- a/jenkins/artifacts/jenkinsfile
+++ b/jenkins/artifacts/jenkinsfile
@@ -5,7 +5,7 @@ pipeline {
         string(name: 'VERSION', defaultValue: '', description: '[Optional] RPM Version should match Harvest version with rc or nightly stripped. If not filled default is YY.mm.ddHH. See https://github.com/NetApp/harvest/wiki/Release-Checklist#version-names for details')
         string(name: 'RELEASE', defaultValue: 'nightly', description: '[Optional] Example: nightly (default) See https://github.com/NetApp/harvest-private/wiki/Release-Checklist#rpm-and-debian-names for details ')
         string(name: 'BRANCH', defaultValue: 'main', description: '[Optional] Branch name to clone. Default (main) ')
-		string(name: 'ASUP_MAKE_TARGET', defaultValue: 'build', description: 'make target for AutoSupport binary build|production')
+		string(name: 'ASUP_MAKE_TARGET', defaultValue: 'production', description: 'make target for AutoSupport binary build|production')
 		string(name: 'DOCKER_PUBLISH', defaultValue: 'false', description: 'true to publish to dockerhub')
 		string(name: 'RUN_TEST', defaultValue: 'true', description: 'false to skip test')
 		string(name: 'OVERWRITE_DOCKER_LATEST_TAG', defaultValue: 'false', description: 'true to overwrite latest tag at dockerhub. Works only if DOCKER_PUBLISH is true')
@@ -14,7 +14,7 @@ pipeline {
     environment {
         GIT_TOKEN = credentials('GIT_TOKEN')
         VERSION =sh (returnStdout: true, script: """
-        [ -n \"${params.VERSION}\" ] && echo \"${params.VERSION}\" || date +%Y.%m.%d%H | cut -c 3-
+        [ -n \"${params.VERSION}\" ] && echo \"${params.VERSION}\" || date +%Y.%m.%d | cut -c 3-
         """).trim()
         RELEASE =sh (returnStdout: true, script: """
         echo \"${params.RELEASE}\"
@@ -179,7 +179,28 @@ pipeline {
             }
         }
       }
+
+      stage('Publish Nightly Build to GitHub') {
+        when {
+            expression {
+                return params.RELEASE == 'nightly' && env.BRANCH == 'main' && params.ASUP_MAKE_TARGET == 'production'
+            }
+        }
+        steps {
+           sh '''
+           targetLocation=$targetParentLocation$VERSION-$RELEASE-$BRANCH
+           wget -q -O /opt/home/gh.tar.gz "https://github.com/cli/cli/releases/download/v2.2.0/gh_2.2.0_linux_386.tar.gz"
+           tar -C /opt/home -xzf /opt/home/gh.tar.gz
+           echo $GIT_TOKEN > mytoken.txt
+           /opt/home/gh_2.2.0_linux_386/bin/gh auth login --with-token < mytoken.txt
+           /opt/home/gh_2.2.0_linux_386/bin/gh release delete nightly -y || true
+           /opt/home/gh_2.2.0_linux_386/bin/gh release create nightly $targetLocation/*.rpm $targetLocation/*.deb $targetLocation/*.gz --notes "Harvest Nightly Release Builds" --title "Harvest Nightly Release" --prerelease
+            '''
+            }
+        }
     }
+
+
     post {
         failure {
             sendNotification("FAILED")
diff --git a/pkg/tree/node/node.go b/pkg/tree/node/node.go
index 5ecbc1a6..7fe28cf7 100644
--- a/pkg/tree/node/node.go
+++ b/pkg/tree/node/node.go
@@ -8,7 +8,6 @@ import (
 	"bytes"
 	"encoding/xml"
 	"fmt"
-	"goharvest2/pkg/color"
 	"goharvest2/pkg/util"
 	"regexp"
 	"strings"
@@ -372,10 +371,15 @@ func simpleName(s string) string {
 	return wordRegex.FindString(s)
 }
 
-func (n *Node) Print(depth int) {
+func (n *Node) Print(depth int) string {
+	builder := strings.Builder{}
+	n.printN(depth, &builder)
+	return builder.String()
+}
+
+func (n *Node) printN(depth int, b *strings.Builder) {
 	name := "* "
 	content := " *"
-
 	if n.GetNameS() != "" {
 		name = n.GetNameS()
 	}
@@ -383,10 +387,10 @@ func (n *Node) Print(depth int) {
 	if len(n.GetContentS()) > 0 && n.GetContentS()[0] != '<' {
 		content = n.GetContentS()
 	}
-	fname := fmt.Sprintf("%s[%s]", strings.Repeat("  ", depth), color.Colorize(name, color.Cyan))
-	fmt.Printf("%-50s - %35s\n", fname, color.Colorize(content, color.Green))
+	fname := fmt.Sprintf("%s[%s]", strings.Repeat("  ", depth), name)
+	b.WriteString(fmt.Sprintf("%-50s - %35s\n", fname, content))
 	for _, child := range n.Children {
-		child.Print(depth + 1)
+		child.printN(depth+1, b)
 	}
 }
 
diff --git a/pkg/tree/tree.go b/pkg/tree/tree.go
index 9c32c28a..caf881b0 100644
--- a/pkg/tree/tree.go
+++ b/pkg/tree/tree.go
@@ -4,66 +4,52 @@
 package tree
 
 import (
-	"errors"
-	"goharvest2/pkg/tree/json"
 	"goharvest2/pkg/tree/node"
 	"goharvest2/pkg/tree/xml"
-	"goharvest2/pkg/tree/yaml"
+	y3 "gopkg.in/yaml.v3"
 	"io/ioutil"
 )
 
-func Print(n *node.Node) {
-	n.Print(0)
-}
-
-func Import(format, filepath string) (*node.Node, error) {
-
+func ImportYaml(filepath string) (*node.Node, error) {
 	data, err := ioutil.ReadFile(filepath)
 
 	if err != nil {
 		return nil, err
 	}
 
-	switch format {
-	case "yaml":
-		return yaml.Load(data)
-	case "xml":
-		return xml.Load(data)
-	case "json":
-		return json.Load(data)
+	root := y3.Node{}
+	err = y3.Unmarshal(data, &root)
+	if err != nil || len(root.Content) == 0 {
+		return nil, err
 	}
-
-	return nil, errors.New("unknown format: " + format)
+	r := node.New([]byte("Root"))
+	consume(r, "", root.Content[0])
+	return r, nil
 }
 
-func Export(n *node.Node, format, filepath string) error {
-
-	var data []byte
-	var err error
-
-	switch format {
-	case "yaml":
-		data, err = yaml.Dump(n)
-	case "xml":
-		data, err = xml.Dump(n)
-	case "json":
-		data = json.Dump(n)
-	default:
-		err = errors.New("unknown format: " + format)
+func consume(r *node.Node, key string, y *y3.Node) {
+	if y.Kind == y3.ScalarNode {
+		r.NewChildS(key, y.Value)
+	} else if y.Kind == y3.MappingNode {
+		var s = r
+		if key != "" {
+			s = r.NewChildS(key, "")
+		}
+		for i := 0; i < len(y.Content); i += 2 {
+			k := y.Content[i].Value
+			// special case to handle incorrectly indented LabelAgent
+			if k == "LabelAgent" && y.Content[i+1].Kind == y3.ScalarNode {
+				s = r.NewChildS(k, "")
+				continue
+			}
+			consume(s, k, y.Content[i+1])
+		}
+	} else { // sequence
+		s := r.NewChildS(key, "")
+		for _, child := range y.Content {
+			consume(s, "", child)
+		}
 	}
-
-	if err == nil {
-		err = ioutil.WriteFile(filepath, data, 0644)
-	}
-	return err
-}
-
-func LoadYaml(data []byte) (*node.Node, error) {
-	return yaml.Load(data)
-}
-
-func DumpYaml(n *node.Node) ([]byte, error) {
-	return yaml.Dump(n)
 }
 
 func LoadXml(data []byte) (*node.Node, error) {
diff --git a/pkg/tree/yaml/yaml.go b/pkg/tree/yaml/yaml.go
index aa084468..ef94c7d9 100644
--- a/pkg/tree/yaml/yaml.go
+++ b/pkg/tree/yaml/yaml.go
@@ -1,114 +1,14 @@
 /*
  * Copyright NetApp Inc, 2021 All rights reserved
  */
+
 package yaml
 
 import (
 	"bytes"
-	"errors"
 	"goharvest2/pkg/tree/node"
-	"strconv"
 )
 
-func Load(data []byte) (*node.Node, error) {
-	root := node.New([]byte("Root"))
-	// be tolerant against tabs, replace with 2 spaces
-	return root, parse(root, bytes.Split(bytes.ReplaceAll(data, []byte("\t"), []byte("  ")), []byte("\n")), 0, 0)
-}
-
-func parse(node *node.Node, lines [][]byte, index, depth int) error {
-	//fmt.Printf(" -> %d\n", index)
-	// Reached end of file
-	if index == len(lines) {
-		return nil
-	}
-
-	// Indentation should be 2 spaces
-	// Stop if invalid indentation
-	if depth%2 != 0 {
-		return errors.New("inconsistent indentation, line: " + strconv.Itoa(index+1))
-	}
-
-	// parse current line
-	depthNew, key, value := parseLine(lines[index])
-	//fmt.Printf("  depth=%d, depthNew=%d, key=%s, value=%s\n", depth, depthNew, string(key), string(value))
-
-	// If line is empty, jump to next line
-	if len(key) == 0 && len(value) == 0 {
-		return parse(node, lines, index+1, depth)
-	}
-
-	// Indentation is same, so parse for current node
-	if depthNew == depth {
-
-		child := node.NewChild(key, value)
-		// no key, means key was defined on previous line
-		// means value is element of list
-		if len(key) == 0 {
-			return parse(node, lines, index+1, depth)
-		} else {
-			// key with no value, means we expect a list of values on next line(s)
-			if len(value) == 0 {
-				return parse(child, lines, index+1, depth+2)
-			} else { // single-value child
-				return parse(node, lines, index+1, depth)
-			}
-		}
-		// Jump back to parent node
-	} else if depthNew < depth {
-		return parse(node.GetParent(), lines, index, depth-2)
-	} else { // Current depth can't be larger than previous
-		return errors.New("invalid indentation, line: " + strconv.Itoa(index+1))
-	}
-}
-
-func parseLine(line []byte) (int, []byte, []byte) {
-	/* variables hold indices of:
-	   start = position of first non-whitespace character, i.e. where indentation ends
-	   mid = position of key-value separator (first occurance of ":")
-	   end = end of line or position where comment starts (#....) */
-	var start, mid, end int
-	var key, value []byte
-
-	start = -1
-
-	for end = 0; end < len(line); end += 1 {
-		// stop if comment starts
-		if line[end] == '#' {
-			break
-		}
-		// first non-space character
-		if start == -1 && line[end] != ' ' {
-			start = end
-		}
-		// first seperator character
-		if mid == 0 && line[end] == ':' {
-			mid = end
-		}
-	}
-
-	if start == -1 {
-		start = 0
-	}
-
-	//fmt.Printf(" <%d> start=%d, mid=%d, end=%d\n", len(line), start, mid, end)
-
-	// no seperator, means line only has value
-	if mid == 0 {
-		value = line[start:end]
-		// seperator is last character, means only key
-	} else if mid+1 == end {
-		key = line[start:mid]
-		// both present
-	} else {
-		key = line[start:mid]
-		value = line[mid+2 : end]
-	}
-	key = bytes.TrimPrefix(key, []byte("- "))
-	value = bytes.TrimPrefix(value, []byte("- "))
-	return start, bytes.Trim(key, " "), bytes.Trim(value, " ")
-}
-
 func Dump(root *node.Node) ([]byte, error) {
 	data := make([][]byte, 0)
 	for _, n := range root.GetChildren() {


Test patch:
diff --git a/pkg/test/mergeTemplates/21.08.0_lun_merge_21.08.0_extended.yaml b/pkg/test/mergeTemplates/21.08.0_lun_merge_21.08.0_extended.yaml
index 02ee311e..b09f12b5 100644
--- a/pkg/test/mergeTemplates/21.08.0_lun_merge_21.08.0_extended.yaml
+++ b/pkg/test/mergeTemplates/21.08.0_lun_merge_21.08.0_extended.yaml
@@ -20,7 +20,6 @@ plugins:
       - path `/` ,,,lun
     new_mapping:
       - xyz
-  - nic
   Aggregator:
     - node<>node_cpu1
     - nodetest
diff --git a/pkg/test/mergeTemplates/lun_merge_21.08.0_extended.yaml b/pkg/test/mergeTemplates/lun_merge_21.08.0_extended.yaml
index e878905a..8b72813a 100644
--- a/pkg/test/mergeTemplates/lun_merge_21.08.0_extended.yaml
+++ b/pkg/test/mergeTemplates/lun_merge_21.08.0_extended.yaml
@@ -24,7 +24,6 @@ plugins:
     - aggr
     - node<>node_cpu1
     - nodetest
-  - nic
 export_options:
   instance_keys:
     - node
diff --git a/pkg/test/node_test.go b/pkg/test/node_test.go
index d1c90348..917f8670 100644
--- a/pkg/test/node_test.go
+++ b/pkg/test/node_test.go
@@ -10,8 +10,8 @@ import (
 
 // Merge default.yaml and custom.yaml
 func TestNode_Merge(t *testing.T) {
-	defaultTemplate, _ := tree.Import("yaml", "testdata/default_collector.yaml")
-	customTemplate, _ := tree.Import("yaml", "testdata/extend_collector.yaml")
+	defaultTemplate, _ := tree.ImportYaml("testdata/default_collector.yaml")
+	customTemplate, _ := tree.ImportYaml("testdata/extend_collector.yaml")
 	defaultTemplate.Merge(customTemplate, []string{"objects"})
 
 	// count number of objects post merge
@@ -53,8 +53,8 @@ func TestNode_Merge(t *testing.T) {
 // merge collector templates for 21.08.6+ versions
 // change is LabelAgent child will have list of rules instead of key-value pair
 func TestNode_MergeCollector(t *testing.T) {
-	defaultTemplate, _ := tree.Import("yaml", "testdata/lun.yaml")
-	customTemplate, _ := tree.Import("yaml", "testdata/extend_lun.yaml")
+	defaultTemplate, _ := tree.ImportYaml("testdata/lun.yaml")
+	customTemplate, _ := tree.ImportYaml("testdata/extend_lun.yaml")
 	defaultTemplate.PreprocessTemplate()
 	customTemplate.PreprocessTemplate()
 	defaultTemplate.Merge(customTemplate, nil)
@@ -206,8 +206,8 @@ func TestNode_MergeCollector(t *testing.T) {
 // Merge collector templates where custom templates are from 21.08.6 and before
 // LabelAgent child did have key-value pair of rules instead of a list
 func TestNode_MergeCollectorOld(t *testing.T) {
-	defaultTemplate, _ := tree.Import("yaml", "testdata/lun.yaml")
-	customTemplate, _ := tree.Import("yaml", "testdata/21.08.0_extend_lun.yaml")
+	defaultTemplate, _ := tree.ImportYaml("testdata/lun.yaml")
+	customTemplate, _ := tree.ImportYaml("testdata/21.08.0_extend_lun.yaml")
 	defaultTemplate.PreprocessTemplate()
 	customTemplate.PreprocessTemplate()
 	defaultTemplate.Merge(customTemplate, nil)
@@ -295,7 +295,7 @@ func TestNode_PreProcessCollector(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			template, _ := tree.Import("yaml", tt.sourceFile)
+			template, _ := tree.ImportYaml(tt.sourceFile)
 			template.PreprocessTemplate()
 			got, _ := yaml.Dump(template)
 			expected, _ := ioutil.ReadFile(tt.compareFile)
@@ -343,8 +343,8 @@ func TestNode_PreProcessMergeCollector(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			baseTemplate, _ := tree.Import("yaml", tt.baseTemplate)
-			extendTemplate, _ := tree.Import("yaml", tt.extendTemplate)
+			baseTemplate, _ := tree.ImportYaml(tt.baseTemplate)
+			extendTemplate, _ := tree.ImportYaml(tt.extendTemplate)
 			baseTemplate.PreprocessTemplate()
 			extendTemplate.PreprocessTemplate()
 			baseTemplate.Merge(extendTemplate, nil)
diff --git a/pkg/test/preProcessResultData/p_21.08.0_extend_lun.yaml b/pkg/test/preProcessResultData/p_21.08.0_extend_lun.yaml
index d975cc3d..e5441c96 100644
--- a/pkg/test/preProcessResultData/p_21.08.0_extend_lun.yaml
+++ b/pkg/test/preProcessResultData/p_21.08.0_extend_lun.yaml
@@ -1,5 +1,4 @@
 plugins:
-  - nic
   LabelAgent:
     value_to_num:
       - new_status state online online `0`
diff --git a/pkg/test/testdata/21.08.0_extend_lun.yaml b/pkg/test/testdata/21.08.0_extend_lun.yaml
index 6bfa82fc..b7cc81f3 100644
--- a/pkg/test/testdata/21.08.0_extend_lun.yaml
+++ b/pkg/test/testdata/21.08.0_extend_lun.yaml
@@ -1,5 +1,4 @@
 plugins:
-  - nic
   LabelAgent:
     value_to_num: new_status state online online `0`
     new_mapping: xyz
diff --git a/pkg/tree/json/json_test.go b/pkg/tree/json/json_test.go
index a2204b28..f6b347a9 100644
--- a/pkg/tree/json/json_test.go
+++ b/pkg/tree/json/json_test.go
@@ -4,6 +4,7 @@
 package json
 
 import (
+	"fmt"
 	"goharvest2/pkg/tree/node"
 	"testing"
 )
@@ -30,7 +31,7 @@ func TestLoad(t *testing.T) {
 		t.Logf("parsed node with %d children:", len(root.GetChildren()))
 	}
 
-	root.Print(0)
+	fmt.Println(root.Print(0))
 }
 
 func TestDump(t *testing.T) {
@@ -46,7 +47,7 @@ func TestDump(t *testing.T) {
 	root.NewChildS("admin", "true")
 
 	t.Logf("dumping node:")
-	root.Print(0)
+	fmt.Println(root.Print(0))
 
 	dump = Dump(root)