Repository: NetApp/harvest
Pull request #1593 (2022-12-05 19:53:34)

feat: include inodes file usage in volume dashboard
Fixes: #1535
Thanks to @Falcon667 for raising


Patch:
diff --git a/cmd/collectors/ems/ems.go b/cmd/collectors/ems/ems.go
index 9ca5bd54..438b0053 100644
--- a/cmd/collectors/ems/ems.go
+++ b/cmd/collectors/ems/ems.go
@@ -318,7 +318,7 @@ func (e *Ems) PollInstance() (map[string]*matrix.Matrix, error) {
 		err              error
 		records          []gjson.Result
 		ok               bool
-		metr             matrix.Metric
+		metr             *matrix.Metric
 		bookendCacheSize int
 		metricTimestamp  float64
 	)
@@ -752,7 +752,7 @@ func (e *Ems) updateMatrix() {
 	var (
 		ok   bool
 		val  float64
-		metr matrix.Metric
+		metr *matrix.Metric
 	)
 
 	tempMap := make(map[string]*matrix.Matrix)
diff --git a/cmd/collectors/rest/plugins/certificate/certificate.go b/cmd/collectors/rest/plugins/certificate/certificate.go
index 2f8576cd..69bd0d4f 100644
--- a/cmd/collectors/rest/plugins/certificate/certificate.go
+++ b/cmd/collectors/rest/plugins/certificate/certificate.go
@@ -152,7 +152,7 @@ func (my *Certificate) setCertificateIssuerType(instance *matrix.Instance) {
 
 func (my *Certificate) setCertificateValidity(data *matrix.Matrix, instance *matrix.Instance) {
 	var (
-		expiryTimeMetric matrix.Metric
+		expiryTimeMetric *matrix.Metric
 	)
 
 	instance.SetLabel("certificateExpiryStatus", "unknown")
diff --git a/cmd/collectors/rest/plugins/qtree/qtree.go b/cmd/collectors/rest/plugins/qtree/qtree.go
index 021cacca..35b6ab62 100644
--- a/cmd/collectors/rest/plugins/qtree/qtree.go
+++ b/cmd/collectors/rest/plugins/qtree/qtree.go
@@ -96,7 +96,7 @@ func (my *Qtree) Init() error {
 			return err
 		}
 
-		my.Logger.Trace().Msgf("added metric: (%s) [%s] %s", metricName, display, metric)
+		my.Logger.Trace().Msgf("added metric: (%s) [%s] %v", metricName, display, metric)
 	}
 
 	my.Logger.Debug().Msgf("added data with %d metrics", len(my.data.GetMetrics()))
diff --git a/cmd/collectors/restperf/plugins/fcp/fcp.go b/cmd/collectors/restperf/plugins/fcp/fcp.go
index 5a6a4844..b5563728 100644
--- a/cmd/collectors/restperf/plugins/fcp/fcp.go
+++ b/cmd/collectors/restperf/plugins/fcp/fcp.go
@@ -17,9 +17,9 @@ func New(p *plugin.AbstractPlugin) plugin.Plugin {
 	return &Fcp{AbstractPlugin: p}
 }
 
-func (me *Fcp) Run(data *matrix.Matrix) ([]*matrix.Matrix, error) {
+func (f *Fcp) Run(data *matrix.Matrix) ([]*matrix.Matrix, error) {
 
-	var rx, tx, util, read, write matrix.Metric
+	var rx, tx, util, read, write *matrix.Metric
 	var err error
 
 	if read = data.GetMetric("read_data"); read == nil {
@@ -63,7 +63,7 @@ func (me *Fcp) Run(data *matrix.Matrix) ([]*matrix.Matrix, error) {
 		var err error
 
 		if speed, err = strconv.Atoi(instance.GetLabel("speed")); err != nil {
-			me.Logger.Debug().Msgf("skip, can't convert speed (%s) to numeric", s)
+			f.Logger.Debug().Msgf("skip, can't convert speed (%s) to numeric", s)
 		}
 
 		if speed != 0 {
@@ -75,7 +75,7 @@ func (me *Fcp) Run(data *matrix.Matrix) ([]*matrix.Matrix, error) {
 				rxPercent = rxBytes / float64(speed)
 				err := rx.SetValueFloat64(instance, rxPercent)
 				if err != nil {
-					me.Logger.Error().Stack().Err(err).Msg("error")
+					f.Logger.Error().Stack().Err(err).Msg("error")
 				}
 			}
 
@@ -83,14 +83,14 @@ func (me *Fcp) Run(data *matrix.Matrix) ([]*matrix.Matrix, error) {
 				txPercent = txBytes / float64(speed)
 				err := tx.SetValueFloat64(instance, txPercent)
 				if err != nil {
-					me.Logger.Error().Stack().Err(err).Msg("error")
+					f.Logger.Error().Stack().Err(err).Msg("error")
 				}
 			}
 
 			if rxOk || txOk {
 				err := util.SetValueFloat64(instance, math.Max(rxPercent, txPercent))
 				if err != nil {
-					me.Logger.Error().Stack().Err(err).Msg("error")
+					f.Logger.Error().Stack().Err(err).Msg("error")
 				}
 			}
 		}
diff --git a/cmd/collectors/restperf/plugins/nic/nic.go b/cmd/collectors/restperf/plugins/nic/nic.go
index 5be97cd7..618d27e7 100644
--- a/cmd/collectors/restperf/plugins/nic/nic.go
+++ b/cmd/collectors/restperf/plugins/nic/nic.go
@@ -32,7 +32,7 @@ func New(p *plugin.AbstractPlugin) plugin.Plugin {
 // Run speed label is reported in bits-per-second and rx/tx is reported as bytes-per-second
 func (me *Nic) Run(data *matrix.Matrix) ([]*matrix.Matrix, error) {
 
-	var read, write, rx, tx, util matrix.Metric
+	var read, write, rx, tx, util *matrix.Metric
 	var err error
 
 	if read = data.GetMetric("receive_bytes"); read == nil {
diff --git a/cmd/collectors/restperf/restperf.go b/cmd/collectors/restperf/restperf.go
index 7c7ccbd6..29fad60d 100644
--- a/cmd/collectors/restperf/restperf.go
+++ b/cmd/collectors/restperf/restperf.go
@@ -399,7 +399,7 @@ func (r *RestPerf) processWorkLoadCounter() (map[string]*matrix.Matrix, error) {
 			metr.SetExportable(metric.Exportable)
 		}
 
-		var service, wait, visits, ops matrix.Metric
+		var service, wait, visits, ops *matrix.Metric
 
 		if service = mat.GetMetric("service_time"); service == nil {
 			r.Logger.Error().Msg("metric [service_time] required to calculate workload missing")
@@ -473,7 +473,7 @@ func (r *RestPerf) PollData() (map[string]*matrix.Matrix, error) {
 		err               error
 		perfRecords       []rest.PerfRecord
 		instanceKeys      []string
-		resourceLatency   matrix.Metric // for workload* objects
+		resourceLatency   *matrix.Metric // for workload* objects
 		skips             int
 		instIndex         int
 	)
@@ -536,7 +536,7 @@ func (r *RestPerf) PollData() (map[string]*matrix.Matrix, error) {
 				instanceKey     string
 				instance        *matrix.Instance
 				isHistogram     bool
-				histogramMetric matrix.Metric
+				histogramMetric *matrix.Metric
 			)
 			instIndex++
 
@@ -821,10 +821,10 @@ func (r *RestPerf) PollData() (map[string]*matrix.Matrix, error) {
 	// cache raw data for next poll
 	cachedData := curMat.Clone(true, true, true)
 
-	orderedNonDenominatorMetrics := make([]matrix.Metric, 0, len(curMat.GetMetrics()))
+	orderedNonDenominatorMetrics := make([]*matrix.Metric, 0, len(curMat.GetMetrics()))
 	orderedNonDenominatorKeys := make([]string, 0, len(orderedNonDenominatorMetrics))
 
-	orderedDenominatorMetrics := make([]matrix.Metric, 0, len(curMat.GetMetrics()))
+	orderedDenominatorMetrics := make([]*matrix.Metric, 0, len(curMat.GetMetrics()))
 	orderedDenominatorKeys := make([]string, 0, len(orderedDenominatorMetrics))
 
 	for key, metric := range curMat.GetMetrics() {
@@ -856,7 +856,7 @@ func (r *RestPerf) PollData() (map[string]*matrix.Matrix, error) {
 		r.Logger.Error().Err(err).Msg("(timestamp) calculate delta:")
 	}
 
-	var base matrix.Metric
+	var base *matrix.Metric
 	var totalSkips int
 
 	for i, metric := range orderedMetrics {
@@ -995,7 +995,7 @@ func (r *RestPerf) PollData() (map[string]*matrix.Matrix, error) {
 func (r *RestPerf) getParentOpsCounters(data *matrix.Matrix) error {
 
 	var (
-		ops       matrix.Metric
+		ops       *matrix.Metric
 		object    string
 		dataQuery string
 		err       error
@@ -1072,7 +1072,7 @@ func (r *RestPerf) getParentOpsCounters(data *matrix.Matrix) error {
 	return nil
 }
 
-func (r *RestPerf) counterLookup(metric matrix.Metric, metricKey string) *counter {
+func (r *RestPerf) counterLookup(metric *matrix.Metric, metricKey string) *counter {
 	var c *counter
 
 	if metric.IsArray() {
diff --git a/cmd/collectors/storagegrid/plugins/tenant/tenant.go b/cmd/collectors/storagegrid/plugins/tenant/tenant.go
index b56ad56e..0ce8eaae 100644
--- a/cmd/collectors/storagegrid/plugins/tenant/tenant.go
+++ b/cmd/collectors/storagegrid/plugins/tenant/tenant.go
@@ -17,7 +17,7 @@ func New(p *plugin.AbstractPlugin) plugin.Plugin {
 func (t *Tenant) Run(data *matrix.Matrix) ([]*matrix.Matrix, error) {
 
 	var (
-		used, quota, usedPercent matrix.Metric
+		used, quota, usedPercent *matrix.Metric
 		err                      error
 	)
 
diff --git a/cmd/collectors/unix/main.go b/cmd/collectors/unix/main.go
index f7913751..3650605a 100644
--- a/cmd/collectors/unix/main.go
+++ b/cmd/collectors/unix/main.go
@@ -41,7 +41,7 @@ var clkTck float64
 
 // list of histograms provided by the collector, mapped
 // to function extracting them from a Process instance
-var _Histograms = map[string]func(matrix.Metric, string, *matrix.Instance, *Process){
+var _Histograms = map[string]func(*matrix.Metric, string, *matrix.Instance, *Process){
 	"cpu":    setCPU,
 	"memory": setMemory,
 	"io":     setIo,
@@ -50,7 +50,7 @@ var _Histograms = map[string]func(matrix.Metric, string, *matrix.Instance, *Proc
 }
 
 // list of (scalar) metrics
-var _Metrics = map[string]func(matrix.Metric, *matrix.Instance, *Process, *System){
+var _Metrics = map[string]func(*matrix.Metric, *matrix.Instance, *Process, *System){
 	"start_time":     setStartTime,
 	"cpu_percent":    setCPUPercent,
 	"memory_percent": setMemoryPercent,
@@ -186,7 +186,7 @@ func (u *Unix) Init(a *collector.AbstractCollector) error {
 func (u *Unix) loadMetrics(counters *node.Node) error {
 	var (
 		proc           *Process
-		metric         matrix.Metric
+		metric         *matrix.Metric
 		labels, wanted *set.Set
 		err            error
 	)
@@ -425,35 +425,35 @@ func (u *Unix) PollData() (map[string]*matrix.Matrix, error) {
 	return u.Matrix, nil
 }
 
-func setStartTime(m matrix.Metric, i *matrix.Instance, p *Process, s *System) {
+func setStartTime(m *matrix.Metric, i *matrix.Instance, p *Process, s *System) {
 	err := m.SetValueFloat64(i, p.startTime+s.bootTime)
 	if err != nil {
 		logging.Get().Error().Stack().Err(err).Msg("error")
 	}
 }
 
-func setNumThreads(m matrix.Metric, i *matrix.Instance, p *Process, _ *System) {
+func setNumThreads(m *matrix.Metric, i *matrix.Instance, p *Process, _ *System) {
 	err := m.SetValueUint64(i, p.numThreads)
 	if err != nil {
 		logging.Get().Error().Stack().Err(err).Msg("error")
 	}
 }
 
-func setNumFds(m matrix.Metric, i *matrix.Instance, p *Process, _ *System) {
+func setNumFds(m *matrix.Metric, i *matrix.Instance, p *Process, _ *System) {
 	err := m.SetValueUint64(i, p.numFds)
 	if err != nil {
 		logging.Get().Error().Stack().Err(err).Msg("error")
 	}
 }
 
-func setMemoryPercent(m matrix.Metric, i *matrix.Instance, p *Process, s *System) {
+func setMemoryPercent(m *matrix.Metric, i *matrix.Instance, p *Process, s *System) {
 	err := m.SetValueFloat64(i, float64(p.mem["rss"])/float64(s.memTotal)*100)
 	if err != nil {
 		logging.Get().Error().Stack().Err(err).Msg("error")
 	}
 }
 
-func setCPUPercent(m matrix.Metric, i *matrix.Instance, p *Process, _ *System) {
+func setCPUPercent(m *matrix.Metric, i *matrix.Instance, p *Process, _ *System) {
 	if p.elapsedTime != 0 {
 		err := m.SetValueFloat64(i, p.cpuTotal/p.elapsedTime*100)
 		if err != nil {
@@ -467,7 +467,7 @@ func setCPUPercent(m matrix.Metric, i *matrix.Instance, p *Process, _ *System) {
 	}
 }
 
-func setCPU(m matrix.Metric, l string, i *matrix.Instance, p *Process) {
+func setCPU(m *matrix.Metric, l string, i *matrix.Instance, p *Process) {
 	if value, ok := p.cpu[l]; ok {
 		err := m.SetValueFloat64(i, value)
 		if err != nil {
@@ -476,7 +476,7 @@ func setCPU(m matrix.Metric, l string, i *matrix.Instance, p *Process) {
 	}
 }
 
-func setMemory(m matrix.Metric, l string, i *matrix.Instance, p *Process) {
+func setMemory(m *matrix.Metric, l string, i *matrix.Instance, p *Process) {
 	if value, ok := p.mem[l]; ok {
 		err := m.SetValueUint64(i, value)
 		if err != nil {
@@ -485,7 +485,7 @@ func setMemory(m matrix.Metric, l string, i *matrix.Instance, p *Process) {
 	}
 }
 
-func setIo(m matrix.Metric, l string, i *matrix.Instance, p *Process) {
+func setIo(m *matrix.Metric, l string, i *matrix.Instance, p *Process) {
 	if value, ok := p.io[l]; ok {
 		err := m.SetValueUint64(i, value)
 		if err != nil {
@@ -494,7 +494,7 @@ func setIo(m matrix.Metric, l string, i *matrix.Instance, p *Process) {
 	}
 }
 
-func setNet(m matrix.Metric, l string, i *matrix.Instance, p *Process) {
+func setNet(m *matrix.Metric, l string, i *matrix.Instance, p *Process) {
 	if value, ok := p.net[l]; ok {
 		err := m.SetValueUint64(i, value)
 		if err != nil {
@@ -503,7 +503,7 @@ func setNet(m matrix.Metric, l string, i *matrix.Instance, p *Process) {
 	}
 }
 
-func setCtx(m matrix.Metric, l string, i *matrix.Instance, p *Process) {
+func setCtx(m *matrix.Metric, l string, i *matrix.Instance, p *Process) {
 	if value, ok := p.ctx[l]; ok {
 		err := m.SetValueUint64(i, value)
 		if err != nil {
diff --git a/cmd/collectors/zapi/plugins/certificate/certificate.go b/cmd/collectors/zapi/plugins/certificate/certificate.go
index 0bcb7f90..1454ec9d 100644
--- a/cmd/collectors/zapi/plugins/certificate/certificate.go
+++ b/cmd/collectors/zapi/plugins/certificate/certificate.go
@@ -159,7 +159,7 @@ func (my *Certificate) setCertificateIssuerType(instance *matrix.Instance) {
 
 func (my *Certificate) setCertificateValidity(data *matrix.Matrix, instance *matrix.Instance) {
 	var (
-		expiryTimeMetric matrix.Metric
+		expiryTimeMetric *matrix.Metric
 	)
 
 	instance.SetLabel("certificateExpiryStatus", "unknown")
diff --git a/cmd/collectors/zapi/plugins/qtree/qtree.go b/cmd/collectors/zapi/plugins/qtree/qtree.go
index e8b97337..d7315e08 100644
--- a/cmd/collectors/zapi/plugins/qtree/qtree.go
+++ b/cmd/collectors/zapi/plugins/qtree/qtree.go
@@ -85,7 +85,7 @@ func (my *Qtree) Init() error {
 			return err
 		}
 
-		my.Logger.Debug().Msgf("added metric: (%s) [%s] %s", metricName, display, metric)
+		my.Logger.Debug().Msgf("added metric: (%s) [%s] %v", metricName, display, metric)
 	}
 
 	my.Logger.Debug().Msgf("added data with %d metrics", len(my.data.GetMetrics()))
diff --git a/cmd/collectors/zapiperf/plugins/fcp/fcp.go b/cmd/collectors/zapiperf/plugins/fcp/fcp.go
index 8537be9f..5d6edff4 100644
--- a/cmd/collectors/zapiperf/plugins/fcp/fcp.go
+++ b/cmd/collectors/zapiperf/plugins/fcp/fcp.go
@@ -1,6 +1,7 @@
 /*
  * Copyright NetApp Inc, 2021 All rights reserved
  */
+
 package fcp
 
 import (
@@ -20,20 +21,20 @@ func New(p *plugin.AbstractPlugin) plugin.Plugin {
 	return &Fcp{AbstractPlugin: p}
 }
 
-func (me *Fcp) Run(data *matrix.Matrix) ([]*matrix.Matrix, error) {
+func (f *Fcp) Run(data *matrix.Matrix) ([]*matrix.Matrix, error) {
 
-	var rx, tx, util, read, write matrix.Metric
+	var rx, tx, util, read, write *matrix.Metric
 	var err error
 
 	if read = data.GetMetric("read_data"); read == nil {
-		// Check for 7 mode fcp counters, as they starts with fcp_.
+		// Check for 7 mode fcp counters, as they start with fcp_.
 		if read = data.GetMetric("fcp_read_data"); read == nil {
 			return nil, errs.New(errs.ErrNoMetric, "read_data")
 		}
 	}
 
 	if write = data.GetMetric("write_data"); write == nil {
-		// Check for 7 mode fcp counters, as they starts with fcp_.
+		// Check for 7 mode fcp counters, as they start with fcp_.
 		if write = data.GetMetric("fcp_write_data"); write == nil {
 			return nil, errs.New(errs.ErrNoMetric, "write_data")
 		}
@@ -72,7 +73,7 @@ func (me *Fcp) Run(data *matrix.Matrix) ([]*matrix.Matrix, error) {
 		var err error
 
 		if speed, err = strconv.Atoi(instance.GetLabel("speed")); err != nil {
-			me.Logger.Debug().Msgf("skip, can't convert speed (%s) to numeric", s)
+			f.Logger.Debug().Msgf("skip, can't convert speed (%s) to numeric", s)
 		}
 
 		if speed != 0 {
@@ -84,7 +85,7 @@ func (me *Fcp) Run(data *matrix.Matrix) ([]*matrix.Matrix, error) {
 				rxPercent = rxBytes / float64(speed)
 				err := rx.SetValueFloat64(instance, rxPercent)
 				if err != nil {
-					me.Logger.Error().Stack().Err(err).Msg("error")
+					f.Logger.Error().Stack().Err(err).Msg("error")
 				}
 			}
 
@@ -92,14 +93,14 @@ func (me *Fcp) Run(data *matrix.Matrix) ([]*matrix.Matrix, error) {
 				txPercent = txBytes / float64(speed)
 				err := tx.SetValueFloat64(instance, txPercent)
 				if err != nil {
-					me.Logger.Error().Stack().Err(err).Msg("error")
+					f.Logger.Error().Stack().Err(err).Msg("error")
 				}
 			}
 
 			if rxOk || txOk {
 				err := util.SetValueFloat64(instance, math.Max(rxPercent, txPercent))
 				if err != nil {
-					me.Logger.Error().Stack().Err(err).Msg("error")
+					f.Logger.Error().Stack().Err(err).Msg("error")
 				}
 			}
 		}
diff --git a/cmd/collectors/zapiperf/plugins/nic/nic.go b/cmd/collectors/zapiperf/plugins/nic/nic.go
index 4287c244..70292f6a 100644
--- a/cmd/collectors/zapiperf/plugins/nic/nic.go
+++ b/cmd/collectors/zapiperf/plugins/nic/nic.go
@@ -34,7 +34,7 @@ func New(p *plugin.AbstractPlugin) plugin.Plugin {
 // Run speed label is reported in bits-per-second and rx/tx is reported as bytes-per-second
 func (me *Nic) Run(data *matrix.Matrix) ([]*matrix.Matrix, error) {
 
-	var read, write, rx, tx, util matrix.Metric
+	var read, write, rx, tx, util *matrix.Metric
 	var err error
 
 	if read = data.GetMetric("rx_bytes"); read == nil {
diff --git a/cmd/collectors/zapiperf/zapiperf.go b/cmd/collectors/zapiperf/zapiperf.go
index 40e02931..1451ad7f 100644
--- a/cmd/collectors/zapiperf/zapiperf.go
+++ b/cmd/collectors/zapiperf/zapiperf.go
@@ -190,7 +190,7 @@ func (z *ZapiPerf) PollData() (map[string]*matrix.Matrix, error) {
 
 	var (
 		instanceKeys    []string
-		resourceLatency matrix.Metric // for workload* objects
+		resourceLatency *matrix.Metric // for workload* objects
 		err             error
 		skips           int
 	)
@@ -317,7 +317,7 @@ func (z *ZapiPerf) PollData() (map[string]*matrix.Matrix, error) {
 
 		// timestamp for batch instances
 		// ignore timestamp from ZAPI which is always integer
-		// we want float, since our poll interval can be float
+		// we want float, since our poll interval can be a float
 		ts := float64(time.Now().UnixNano()) / BILLION
 
 		for instIndex, i := range instances.GetChildren() {
@@ -553,7 +553,7 @@ func (z *ZapiPerf) PollData() (map[string]*matrix.Matrix, error) {
 	cachedData := curMat.Clone(true, true, true) // @TODO implement copy data
 
 	// order metrics, such that those requiring base counters are processed last
-	orderedMetrics := make([]matrix.Metric, 0, len(curMat.GetMetrics()))
+	orderedMetrics := make([]*matrix.Metric, 0, len(curMat.GetMetrics()))
 	orderedKeys := make([]string, 0, len(orderedMetrics))
 
 	for key, metric := range curMat.GetMetrics() {
@@ -576,7 +576,7 @@ func (z *ZapiPerf) PollData() (map[string]*matrix.Matrix, error) {
 		// @TODO terminate since other counters will be incorrect
 	}
 
-	var base matrix.Metric
+	var base *matrix.Metric
 	var totalSkips int
 
 	for i, metric := range orderedMetrics {
@@ -694,7 +694,7 @@ func (z *ZapiPerf) PollData() (map[string]*matrix.Matrix, error) {
 func (z *ZapiPerf) getParentOpsCounters(data *matrix.Matrix, KeyAttr string) (time.Duration, time.Duration, error) {
 
 	var (
-		ops          matrix.Metric
+		ops          *matrix.Metric
 		object       string
 		instanceKeys []string
 		apiT, parseT time.Duration
@@ -986,7 +986,7 @@ func (z *ZapiPerf) PollCounter() (map[string]*matrix.Matrix, error) {
 		// there original counters will be discarded
 		if z.Query == objWorkloadDetail || z.Query == objWorkloadDetailVolume {
 
-			var service, wait, visits, ops matrix.Metric
+			var service, wait, visits, ops *matrix.Metric
 			oldMetrics.Remove("service_time")
 			oldMetrics.Remove("wait_time")
 			oldMetrics.Remove("visits")
@@ -1132,7 +1132,7 @@ func (z *ZapiPerf) addCounter(counter *node.Node, name, display string, enabled
 			e                  string
 			description        string
 			isHistogram        bool
-			histogramMetric    matrix.Metric
+			histogramMetric    *matrix.Metric
 		)
 
 		description = strings.ToLower(counter.GetChildContentS("desc"))
@@ -1189,7 +1189,7 @@ func (z *ZapiPerf) addCounter(counter *node.Node, name, display string, enabled
 
 		for i, label := range labels {
 
-			var m matrix.Metric
+			var m *matrix.Metric
 
 			key := name + "." + label
 
@@ -1225,7 +1225,7 @@ func (z *ZapiPerf) addCounter(counter *node.Node, name, display string, enabled
 
 		// counter type is scalar
 	} else {
-		var m matrix.Metric
+		var m *matrix.Metric
 		if m = mat.GetMetric(name); m != nil {
 			z.Logger.Trace().Msgf("updating scalar metric [%s] attributes", name)
 		} else if m, err = mat.NewMetricFloat64(name, display); err == nil {
diff --git a/cmd/exporters/prometheus/prometheus.go b/cmd/exporters/prometheus/prometheus.go
index 4b1136b1..625f8a67 100644
--- a/cmd/exporters/prometheus/prometheus.go
+++ b/cmd/exporters/prometheus/prometheus.go
@@ -563,7 +563,7 @@ var numAndUnitRe = regexp.MustCompile(`(\d+)\s*(\w+)`)
 
 // normalizeHistogram tries to normalize ONTAP values by converting units to multiples of the smallest unit.
 // When the unit can not be determined, return an empty string
-func (p *Prometheus) normalizeHistogram(metric matrix.Metric, ontap string, object string) string {
+func (p *Prometheus) normalizeHistogram(metric *matrix.Metric, ontap string, object string) string {
 	numAndUnit := ontap
 	if strings.HasPrefix(ontap, "<") {
 		numAndUnit = ontap[1:]
@@ -601,7 +601,7 @@ func (p *Prometheus) normalizeHistogram(metric matrix.Metric, ontap string, obje
 	return strconv.FormatFloat(normal, 'f', -1, 64)
 }
 
-func histogramFromBucket(histograms map[string]*histogram, metric matrix.Metric) *histogram {
+func histogramFromBucket(histograms map[string]*histogram, metric *matrix.Metric) *histogram {
 	h, ok := histograms[metric.GetName()]
 	if ok {
 		return h
@@ -628,7 +628,7 @@ func escape(replacer *strings.Replacer, key string, value string) string {
 }
 
 type histogram struct {
-	metric matrix.Metric
+	metric *matrix.Metric
 	values []string
 }
 
diff --git a/cmd/poller/plugin/aggregator/aggregator.go b/cmd/poller/plugin/aggregator/aggregator.go
index b1d4e2e2..de9ba03c 100644
--- a/cmd/poller/plugin/aggregator/aggregator.go
+++ b/cmd/poller/plugin/aggregator/aggregator.go
@@ -136,7 +136,7 @@ func (a *Aggregator) Run(data *matrix.Matrix) ([]*matrix.Matrix, error) {
 	var (
 		objName, objKey string
 		objInstance     *matrix.Instance
-		objMetric       matrix.Metric
+		objMetric       *matrix.Metric
 		value           float64
 		ok              bool
 		err             error
diff --git a/cmd/poller/plugin/labelagent/label_agent.go b/cmd/poller/plugin/labelagent/label_agent.go
index 9ff3b89e..76fe4182 100644
--- a/cmd/poller/plugin/labelagent/label_agent.go
+++ b/cmd/poller/plugin/labelagent/label_agent.go
@@ -298,7 +298,7 @@ func (a *LabelAgent) includeRegex(matrix *matrix.Matrix) error {
 func (a *LabelAgent) mapValueToNum(m *matrix.Matrix) error {
 
 	var (
-		metric matrix.Metric
+		metric *matrix.Metric
 		err    error
 	)
 
@@ -330,7 +330,7 @@ func (a *LabelAgent) mapValueToNum(m *matrix.Matrix) error {
 
 func (a *LabelAgent) mapValueToNumRegex(m *matrix.Matrix) error {
 	var (
-		metric matrix.Metric
+		metric *matrix.Metric
 		err    error
 	)
 
diff --git a/cmd/poller/plugin/max/max.go b/cmd/poller/plugin/max/max.go
index 687b2ead..6f6408c6 100644
--- a/cmd/poller/plugin/max/max.go
+++ b/cmd/poller/plugin/max/max.go
@@ -145,7 +145,7 @@ func (m *Max) Run(data *matrix.Matrix) ([]*matrix.Matrix, error) {
 	var (
 		objName, objKey string
 		objInstance     *matrix.Instance
-		objMetric       matrix.Metric
+		objMetric       *matrix.Metric
 		value           float64
 		ok              bool
 		err             error
diff --git a/cmd/poller/plugin/metricagent/metric_agent.go b/cmd/poller/plugin/metricagent/metric_agent.go
index 146300f0..ff1bcebc 100644
--- a/cmd/poller/plugin/metricagent/metric_agent.go
+++ b/cmd/poller/plugin/metricagent/metric_agent.go
@@ -55,8 +55,8 @@ func (a *MetricAgent) Run(m *matrix.Matrix) ([]*matrix.Matrix, error) {
 func (a *MetricAgent) computeMetrics(m *matrix.Matrix) error {
 
 	var (
-		metric                    matrix.Metric
-		metricVal, firstMetricVal matrix.Metric
+		metric                    *matrix.Metric
+		metricVal, firstMetricVal *matrix.Metric
 		err                       error
 	)
 
@@ -131,7 +131,7 @@ func (a *MetricAgent) computeMetrics(m *matrix.Matrix) error {
 	return nil
 }
 
-func (a *MetricAgent) getMetric(m *matrix.Matrix, name string) matrix.Metric {
+func (a *MetricAgent) getMetric(m *matrix.Matrix, name string) *matrix.Metric {
 	metric := m.DisplayMetric(name)
 	if metric != nil {
 		return metric
diff --git a/grafana/dashboards/cmode/svm.json b/grafana/dashboards/cmode/svm.json
index abe6f6c0..e545b1d1 100644
--- a/grafana/dashboards/cmode/svm.json
+++ b/grafana/dashboards/cmode/svm.json
@@ -3040,8 +3040,7 @@
                 "lineWidth": 1,
                 "pointSize": 5,
                 "scaleDistribution": {
-                  "log": 2,
-                  "type": "log"
+                  "type": "linear"
                 },
                 "showPoints": "auto",
                 "spanNulls": true,
diff --git a/grafana/dashboards/cmode/volume.json b/grafana/dashboards/cmode/volume.json
index c9fe8002..bf3e74dc 100644
--- a/grafana/dashboards/cmode/volume.json
+++ b/grafana/dashboards/cmode/volume.json
@@ -71,7 +71,7 @@
   "gnetId": null,
   "graphTooltip": 1,
   "id": null,
-  "iteration": 1664349159212,
+  "iteration": 1670268827108,
   "links": [
     {
       "asDropdown": true,
@@ -1695,7 +1695,7 @@
             "h": 9,
             "w": 8,
             "x": 0,
-            "y": 38
+            "y": 3
           },
           "id": 30,
           "options": {
@@ -1785,7 +1785,7 @@
             "h": 9,
             "w": 8,
             "x": 8,
-            "y": 38
+            "y": 3
           },
           "id": 31,
           "options": {
@@ -1876,7 +1876,7 @@
             "h": 9,
             "w": 8,
             "x": 16,
-            "y": 38
+            "y": 3
           },
           "id": 32,
           "options": {
@@ -1966,7 +1966,7 @@
             "h": 9,
             "w": 8,
             "x": 0,
-            "y": 47
+            "y": 12
           },
           "id": 33,
           "options": {
@@ -2056,7 +2056,7 @@
             "h": 9,
             "w": 8,
             "x": 8,
-            "y": 47
+            "y": 12
           },
           "id": 34,
           "options": {
@@ -2146,7 +2146,7 @@
             "h": 9,
             "w": 8,
             "x": 16,
-            "y": 47
+            "y": 12
           },
           "id": 35,
           "options": {
@@ -2251,7 +2251,7 @@
             "h": 10,
             "w": 8,
             "x": 0,
-            "y": 16
+            "y": 4
           },
           "id": 72,
           "options": {
@@ -2341,7 +2341,7 @@
             "h": 10,
             "w": 8,
             "x": 8,
-            "y": 16
+            "y": 4
           },
           "id": 74,
           "options": {
@@ -2431,7 +2431,7 @@
             "h": 10,
             "w": 8,
             "x": 16,
-            "y": 16
+            "y": 4
           },
           "id": 76,
           "options": {
@@ -2521,7 +2521,7 @@
             "h": 10,
             "w": 8,
             "x": 0,
-            "y": 26
+            "y": 14
           },
           "id": 73,
           "options": {
@@ -2611,7 +2611,7 @@
             "h": 10,
             "w": 8,
             "x": 8,
-            "y": 26
+            "y": 14
           },
           "id": 75,
           "options": {
@@ -2701,7 +2701,7 @@
             "h": 10,
             "w": 8,
             "x": 16,
-            "y": 26
+            "y": 14
           },
           "id": 77,
           "options": {
@@ -2790,7 +2790,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 36
+            "y": 24
           },
           "id": 79,
           "options": {
@@ -2879,7 +2879,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 36
+            "y": 24
           },
           "id": 80,
           "options": {
@@ -2932,7 +2932,7 @@
             "h": 3,
             "w": 24,
             "x": 0,
-            "y": 17
+            "y": 5
           },
           "id": 91,
           "options": {
@@ -3001,7 +3001,7 @@
             "h": 11,
             "w": 8,
             "x": 0,
-            "y": 20
+            "y": 8
           },
           "id": 48,
           "options": {
@@ -3092,7 +3092,7 @@
             "h": 11,
             "w": 8,
             "x": 8,
-            "y": 20
+            "y": 8
           },
           "id": 50,
           "options": {
@@ -3183,7 +3183,7 @@
             "h": 11,
             "w": 8,
             "x": 16,
-            "y": 20
+            "y": 8
           },
           "id": 88,
           "options": {
@@ -3273,7 +3273,7 @@
             "h": 11,
             "w": 8,
             "x": 0,
-            "y": 31
+            "y": 19
           },
           "id": 54,
           "options": {
@@ -3363,7 +3363,7 @@
             "h": 11,
             "w": 8,
             "x": 8,
-            "y": 31
+            "y": 19
           },
           "id": 56,
           "options": {
@@ -3454,7 +3454,7 @@
             "h": 11,
             "w": 8,
             "x": 16,
-            "y": 31
+            "y": 19
           },
           "id": 89,
           "options": {
@@ -3544,7 +3544,7 @@
             "h": 11,
             "w": 8,
             "x": 0,
-            "y": 42
+            "y": 30
           },
           "id": 60,
           "options": {
@@ -3636,7 +3636,7 @@
             "h": 11,
             "w": 8,
             "x": 8,
-            "y": 42
+            "y": 30
           },
           "id": 62,
           "options": {
@@ -3726,7 +3726,7 @@
             "h": 11,
             "w": 8,
             "x": 16,
-            "y": 42
+            "y": 30
           },
           "id": 52,
           "options": {
@@ -3816,7 +3816,7 @@
             "h": 11,
             "w": 8,
             "x": 0,
-            "y": 53
+            "y": 41
           },
           "id": 64,
           "options": {
@@ -3906,7 +3906,7 @@
             "h": 11,
             "w": 8,
             "x": 8,
-            "y": 53
+            "y": 41
           },
           "id": 58,
           "options": {
@@ -4007,7 +4007,7 @@
             "h": 12,
             "w": 12,
             "x": 0,
-            "y": 41
+            "y": 6
           },
           "id": 83,
           "options": {
@@ -4123,7 +4123,7 @@
             "h": 12,
             "w": 12,
             "x": 12,
-            "y": 41
+            "y": 6
           },
           "id": 84,
           "options": {
@@ -4231,7 +4231,7 @@
             "h": 12,
             "w": 12,
             "x": 0,
-            "y": 53
+            "y": 18
           },
           "id": 87,
           "options": {
@@ -4347,7 +4347,7 @@
             "h": 12,
             "w": 12,
             "x": 12,
-            "y": 53
+            "y": 18
           },
           "id": 85,
           "options": {
@@ -4468,7 +4468,7 @@
             "h": 12,
             "w": 12,
             "x": 0,
-            "y": 19
+            "y": 7
           },
           "id": 93,
           "options": {
@@ -4560,7 +4560,7 @@
             "h": 12,
             "w": 12,
             "x": 12,
-            "y": 19
+            "y": 7
           },
           "id": 94,
           "options": {
@@ -4651,7 +4651,7 @@
             "h": 12,
             "w": 12,
             "x": 0,
-            "y": 31
+            "y": 19
           },
           "id": 95,
           "options": {
@@ -4743,7 +4743,7 @@
             "h": 12,
             "w": 12,
             "x": 12,
-            "y": 31
+            "y": 19
           },
           "id": 96,
           "options": {
@@ -4779,6 +4779,7 @@
           "type": "timeseries"
         },
         {
+          "description": "Average request count, per volume, in the chosen time window, to object storage.  Not scaled per second, but a cumulative count (delta) since the previous poll.",
           "fieldConfig": {
             "defaults": {
               "color": {
@@ -4815,7 +4816,7 @@
             "h": 12,
             "w": 12,
             "x": 0,
-            "y": 43
+            "y": 31
           },
           "id": 90,
           "options": {
@@ -4828,8 +4829,7 @@
             },
             "showHeader": true
           },
-          "pluginVersion": "8.4.11",
-          "description": "Average request count, per volume, in the chosen time window, to object storage.  Not scaled per second, but a cumulative count (delta) since the previous poll.",
+          "pluginVersion": "8.1.8",
           "targets": [
             {
               "exemplar": false,
@@ -4873,6 +4873,281 @@
       ],
       "title": "Top Volume FabricPool Drilldown",
       "type": "row"
+    },
+    {
+      "collapsed": true,
+      "datasource": null,
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 19
+      },
+      "id": 98,
+      "panels": [
+        {
+          "datasource": null,
+          "fieldConfig": {
+            "defaults": {
+              "color": {
+                "mode": "palette-classic"
+              },
+              "custom": {
+                "axisLabel": "",
+                "axisPlacement": "auto",
+                "barAlignment": 0,
+                "drawStyle": "line",
+                "fillOpacity": 10,
+                "gradientMode": "none",
+                "hideFrom": {
+                  "legend": false,
+                  "tooltip": false,
+                  "viz": false
+                },
+                "lineInterpolation": "linear",
+                "lineWidth": 1,
+                "pointSize": 5,
+                "scaleDistribution": {
+                  "type": "linear"
+                },
+                "showPoints": "auto",
+                "spanNulls": true,
+                "stacking": {
+                  "group": "A",
+                  "mode": "none"
+                },
+                "thresholdsStyle": {
+                  "mode": "off"
+                }
+              },
+              "mappings": [],
+              "thresholds": {
+                "mode": "absolute",
+                "steps": [
+                  {
+                    "color": "green",
+                    "value": null
+                  },
+                  {
+                    "color": "red",
+                    "value": 80
+                  }
+                ]
+              },
+              "unit": "short"
+            },
+            "overrides": []
+          },
+          "gridPos": {
+            "h": 8,
+            "w": 12,
+            "x": 0,
+            "y": 8
+          },
+          "id": 100,
+          "options": {
+            "legend": {
+              "calcs": [
+                "mean",
+                "lastNotNull",
+                "max"
+              ],
+              "displayMode": "table",
+              "placement": "bottom"
+            },
+            "tooltip": {
+              "mode": "single"
+            }
+          },
+          "targets": [
+            {
+              "exemplar": true,
+              "expr": "topk($TopResources, volume_inode_files_used{datacenter=~\"$Datacenter\",cluster=~\"$Cluster\",svm=~\"$SVM\",volume=~\"$TopVolumeInodeFilesUsed\"})",
+              "hide": false,
+              "interval": "",
+              "legendFormat": "{{svm}} - {{volume}}",
+              "refId": "A"
+            }
+          ],
+          "title": "Inode Files Used",
+          "type": "timeseries"
+        },
+        {
+          "datasource": null,
+          "fieldConfig": {
+            "defaults": {
+              "color": {
+                "mode": "palette-classic"
+              },
+              "custom": {
+                "axisLabel": "",
+                "axisPlacement": "auto",
+                "barAlignment": 0,
+                "drawStyle": "line",
+                "fillOpacity": 10,
+                "gradientMode": "none",
+                "hideFrom": {
+                  "legend": false,
+                  "tooltip": false,
+                  "viz": false
+                },
+                "lineInterpolation": "linear",
+                "lineWidth": 1,
+                "pointSize": 5,
+                "scaleDistribution": {
+                  "type": "linear"
+                },
+                "showPoints": "auto",
+                "spanNulls": true,
+                "stacking": {
+                  "group": "A",
+                  "mode": "none"
+                },
+                "thresholdsStyle": {
+                  "mode": "off"
+                }
+              },
+              "mappings": [],
+              "thresholds": {
+                "mode": "absolute",
+                "steps": [
+                  {
+                    "color": "green",
+                    "value": null
+                  },
+                  {
+                    "color": "red",
+                    "value": 80
+                  }
+                ]
+              },
+              "unit": "short"
+            },
+            "overrides": []
+          },
+          "gridPos": {
+            "h": 8,
+            "w": 12,
+            "x": 12,
+            "y": 8
+          },
+          "id": 102,
+          "options": {
+            "legend": {
+              "calcs": [
+                "mean",
+                "lastNotNull",
+                "max"
+              ],
+              "displayMode": "table",
+              "placement": "bottom"
+            },
+            "tooltip": {
+              "mode": "single"
+            }
+          },
+          "targets": [
+            {
+              "exemplar": true,
+              "expr": "topk($TopResources, volume_inode_files_total{datacenter=~\"$Datacenter\",cluster=~\"$Cluster\",svm=~\"$SVM\",volume=~\"$TopVolumeInodeFiles\"})",
+              "interval": "",
+              "legendFormat": "{{svm}} - {{volume}}",
+              "refId": "A"
+            }
+          ],
+          "title": "Inode Files Total",
+          "type": "timeseries"
+        },
+        {
+          "datasource": null,
+          "fieldConfig": {
+            "defaults": {
+              "color": {
+                "mode": "palette-classic"
+              },
+              "custom": {
+                "axisLabel": "",
+                "axisPlacement": "auto",
+                "barAlignment": 0,
+                "drawStyle": "line",
+                "fillOpacity": 10,
+                "gradientMode": "none",
+                "hideFrom": {
+                  "legend": false,
+                  "tooltip": false,
+                  "viz": false
+                },
+                "lineInterpolation": "linear",
+                "lineWidth": 1,
+                "pointSize": 5,
+                "scaleDistribution": {
+                  "type": "linear"
+                },
+                "showPoints": "auto",
+                "spanNulls": true,
+                "stacking": {
+                  "group": "A",
+                  "mode": "none"
+                },
+                "thresholdsStyle": {
+                  "mode": "off"
+                }
+              },
+              "mappings": [],
+              "min": 0,
+              "thresholds": {
+                "mode": "absolute",
+                "steps": [
+                  {
+                    "color": "green",
+                    "value": null
+                  },
+                  {
+                    "color": "red",
+                    "value": 80
+                  }
+                ]
+              },
+              "unit": "percent"
+            },
+            "overrides": []
+          },
+          "gridPos": {
+            "h": 8,
+            "w": 24,
+            "x": 0,
+            "y": 16
+          },
+          "id": 101,
+          "options": {
+            "legend": {
+              "calcs": [
+                "mean",
+                "lastNotNull",
+                "max"
+              ],
+              "displayMode": "table",
+              "placement": "bottom"
+            },
+            "tooltip": {
+              "mode": "single"
+            }
+          },
+          "targets": [
+            {
+              "exemplar": true,
+              "expr": "topk($TopResources, volume_inode_used_percent{datacenter=~\"$Datacenter\",cluster=~\"$Cluster\",svm=~\"$SVM\",volume=~\"$TopVolumeInodeUsedPercent\"})",
+              "interval": "",
+              "legendFormat": "{{svm}} - {{volume}}",
+              "refId": "A"
+            }
+          ],
+          "title": "Inode Files Used Percentage",
+          "type": "timeseries"
+        }
+      ],
+      "title": "Top Inode Drilldown",
+      "type": "row"
     }
   ],
   "refresh": "1m",
@@ -5748,9 +6023,13 @@
       {
         "allValue": null,
         "current": {},
+        "datasource": "${DS_PROMETHEUS}",
         "definition": "query_result(topk($TopResources, sum by (volume) (avg_over_time(fabricpool_cloud_bin_operation{datacenter=~\"$Datacenter\",cluster=~\"$Cluster\", svm=~\"$SVM\", volume=~\"$Volume\"}[${__range}]))))",
+        "description": null,
+        "error": null,
         "hide": 2,
         "includeAll": true,
+        "label": null,
         "multi": true,
         "name": "TopCloudBinOperation",
         "options": [],
@@ -5767,9 +6046,13 @@
       {
         "allValue": null,
         "current": {},
+        "datasource": "${DS_PROMETHEUS}",
         "definition": "query_result(topk($TopResources,avg_over_time(fabricpool_cloud_bin_op_latency_average{datacenter=~\"$Datacenter\",cluster=~\"$Cluster\", svm=~\"$SVM\",volume=~\"$Volume\",metric=\"GET\"}[${__range}])))",
+        "description": null,
+        "error": null,
         "hide": 2,
         "includeAll": true,
+        "label": null,
         "multi": true,
         "name": "TopCloudBinOpGetLatency",
         "options": [],
@@ -5786,9 +6069,13 @@
       {
         "allValue": null,
         "current": {},
+        "datasource": "${DS_PROMETHEUS}",
         "definition": "query_result(topk($TopResources,avg_over_time(fabricpool_cloud_bin_operation{datacenter=~\"$Datacenter\",cluster=~\"$Cluster\", svm=~\"$SVM\",volume=~\"$Volume\",metric=\"GET\"}[${__range}])))",
+        "description": null,
+        "error": null,
         "hide": 2,
         "includeAll": true,
+        "label": null,
         "multi": true,
         "name": "TopCloudBinOperationGet",
         "options": [],
@@ -5805,9 +6092,13 @@
       {
         "allValue": null,
         "current": {},
+        "datasource": "${DS_PROMETHEUS}",
         "definition": "query_result(topk($TopResources,avg_over_time(fabricpool_cloud_bin_op_latency_average{datacenter=~\"$Datacenter\",cluster=~\"$Cluster\", svm=~\"$SVM\",volume=~\"$Volume\",metric=\"PUT\"}[${__range}])))",
+        "description": null,
+        "error": null,
         "hide": 2,
         "includeAll": true,
+        "label": null,
         "multi": true,
         "name": "TopCloudBinOpPutLatency",
         "options": [],
@@ -5824,9 +6115,13 @@
       {
         "allValue": null,
         "current": {},
+        "datasource": "${DS_PROMETHEUS}",
         "definition": "query_result(topk($TopResources,avg_over_time(fabricpool_cloud_bin_operation{datacenter=~\"$Datacenter\",cluster=~\"$Cluster\", svm=~\"$SVM\",volume=~\"$Volume\",metric=\"PUT\"}[${__range}])))",
+        "description": null,
+        "error": null,
         "hide": 2,
         "includeAll": true,
+        "label": null,
         "multi": true,
         "name": "TopCloudBinOperationPut",
         "options": [],
@@ -5839,6 +6134,75 @@
         "skipUrlSync": false,
         "sort": 0,
         "type": "query"
+      },
+      {
+        "allValue": null,
+        "current": {},
+        "datasource": "${DS_PROMETHEUS}",
+        "definition": "query_result(topk($TopResources, avg_over_time(volume_inode_files_total{datacenter=~\"$Datacenter\",cluster=~\"$Cluster\",svm=~\"$SVM\",volume=~\"$Volume\"}[${__range}])))",
+        "description": null,
+        "error": null,
+        "hide": 2,
+        "includeAll": true,
+        "label": null,
+        "multi": true,
+        "name": "TopVolumeInodeFiles",
+        "options": [],
+        "query": {
+          "query": "query_result(topk($TopResources, avg_over_time(volume_inode_files_total{datacenter=~\"$Datacenter\",cluster=~\"$Cluster\",svm=~\"$SVM\",volume=~\"$Volume\"}[${__range}])))",
+          "refId": "StandardVariableQuery"
+        },
+        "refresh": 1,
+        "regex": ".*volume=\\\"(.*?)\\\".*",
+        "skipUrlSync": false,
+        "sort": 0,
+        "type": "query"
+      },
+      {
+        "allValue": null,
+        "current": {},
+        "datasource": "${DS_PROMETHEUS}",
+        "definition": "query_result(topk($TopResources, avg_over_time(volume_inode_files_used{datacenter=~\"$Datacenter\",cluster=~\"$Cluster\",svm=~\"$SVM\",volume=~\"$Volume\"}[${__range}])))",
+        "description": null,
+        "error": null,
+        "hide": 2,
+        "includeAll": true,
+        "label": null,
+        "multi": true,
+        "name": "TopVolumeInodeFilesUsed",
+        "options": [],
+        "query": {
+          "query": "query_result(topk($TopResources, avg_over_time(volume_inode_files_used{datacenter=~\"$Datacenter\",cluster=~\"$Cluster\",svm=~\"$SVM\",volume=~\"$Volume\"}[${__range}])))",
+          "refId": "StandardVariableQuery"
+        },
+        "refresh": 1,
+        "regex": ".*volume=\\\"(.*?)\\\".*",
+        "skipUrlSync": false,
+        "sort": 0,
+        "type": "query"
+      },
+      {
+        "allValue": null,
+        "current": {},
+        "datasource": "${DS_PROMETHEUS}",
+        "definition": "query_result(topk($TopResources, avg_over_time(volume_inode_used_percent{datacenter=~\"$Datacenter\",cluster=~\"$Cluster\",svm=~\"$SVM\",volume=~\"$Volume\"}[${__range}])))",
+        "description": null,
+        "error": null,
+        "hide": 2,
+        "includeAll": true,
+        "label": null,
+        "multi": true,
+        "name": "TopVolumeInodeUsedPercent",
+        "options": [],
+        "query": {
+          "query": "query_result(topk($TopResources, avg_over_time(volume_inode_used_percent{datacenter=~\"$Datacenter\",cluster=~\"$Cluster\",svm=~\"$SVM\",volume=~\"$Volume\"}[${__range}])))",
+          "refId": "StandardVariableQuery"
+        },
+        "refresh": 1,
+        "regex": ".*volume=\\\"(.*?)\\\".*",
+        "skipUrlSync": false,
+        "sort": 0,
+        "type": "query"
       }
     ]
   },
@@ -5862,5 +6226,5 @@
   "timezone": "",
   "title": "ONTAP: Volume",
   "uid": "",
-  "version": 7
-}
+  "version": 8
+}
\ No newline at end of file
diff --git a/pkg/matrix/matrix.go b/pkg/matrix/matrix.go
index 52ff07ab..f5e0c426 100644
--- a/pkg/matrix/matrix.go
+++ b/pkg/matrix/matrix.go
@@ -12,7 +12,6 @@ package matrix
 
 import (
 	"fmt"
-	"github.com/netapp/harvest/v2/pkg/color"
 	"github.com/netapp/harvest/v2/pkg/dict"
 	"github.com/netapp/harvest/v2/pkg/errs"
 	"github.com/netapp/harvest/v2/pkg/tree/node"
@@ -25,8 +24,8 @@ type Matrix struct {
 	Identifier     string
 	globalLabels   *dict.Dict
 	instances      map[string]*Instance
-	metrics        map[string]Metric // ONTAP metric name => metric (in templates, this is left side)
-	displayMetrics map[string]string // display name of metric to => metric name (in templates, this is right side)
+	metrics        map[string]*Metric // ONTAP metric name => metric (in templates, this is left side)
+	displayMetrics map[string]string  // display name of metric to => metric name (in templates, this is right side)
 	exportOptions  *node.Node
 	exportable     bool
 }
@@ -35,7 +34,7 @@ func New(uuid, object string, identifier string) *Matrix {
 	me := Matrix{UUID: uuid, Object: object, Identifier: identifier}
 	me.globalLabels = dict.New()
 	me.instances = make(map[string]*Instance, 0)
-	me.metrics = make(map[string]Metric, 0)
+	me.metrics = make(map[string]*Metric, 0)
 	me.displayMetrics = make(map[string]string, 0)
 	me.exportable = true
 	return &me
@@ -49,7 +48,7 @@ func (m *Matrix) Print() {
 	fmt.Println()
 
 	for key, metric := range m.GetMetrics() {
-		fmt.Printf("(%s%s%s%s) (type=%s) (exportable=%v) values= ", color.Bold, color.Cyan, key, color.End, metric.GetType(), metric.IsExportable())
+		fmt.Printf("(%s) (type=%s) (exportable=%v) values= ", key, metric.GetType(), metric.IsExportable())
 		metric.Print()
 		fmt.Println()
 	}
@@ -97,45 +96,45 @@ func (m *Matrix) Reset() {
 	}
 }
 
-func (m *Matrix) DisplayMetric(name string) Metric {
+func (m *Matrix) DisplayMetric(name string) *Metric {
 	if metricKey, has := m.displayMetrics[name]; has {
 		return m.GetMetric(metricKey)
 	}
 	return nil
 }
 
-func (m *Matrix) GetMetric(key string) Metric {
+func (m *Matrix) GetMetric(key string) *Metric {
 	if metric, has := m.metrics[key]; has {
 		return metric
 	}
 	return nil
 }
 
-func (m *Matrix) GetMetrics() map[string]Metric {
+func (m *Matrix) GetMetrics() map[string]*Metric {
 	return m.metrics
 }
 
-func (m *Matrix) NewMetricInt64(key string, display ...string) (Metric, error) {
-	metric := &MetricInt64{AbstractMetric: newAbstract(key, "int64", display...)}
+func (m *Matrix) NewMetricInt64(key string, display ...string) (*Metric, error) {
+	metric := newAbstract(key, "int64", display...)
 	return metric, m.addMetric(key, metric)
 }
 
-func (m *Matrix) NewMetricUint8(key string, display ...string) (Metric, error) {
-	metric := &MetricUint8{AbstractMetric: newAbstract(key, "uint8", display...)}
+func (m *Matrix) NewMetricUint8(key string, display ...string) (*Metric, error) {
+	metric := newAbstract(key, "uint8", display...)
 	return metric, m.addMetric(key, metric)
 }
 
-func (m *Matrix) NewMetricUint64(key string, display ...string) (Metric, error) {
-	metric := &MetricUint64{AbstractMetric: newAbstract(key, "uint64", display...)}
+func (m *Matrix) NewMetricUint64(key string, display ...string) (*Metric, error) {
+	metric := newAbstract(key, "uint64", display...)
 	return metric, m.addMetric(key, metric)
 }
 
-func (m *Matrix) NewMetricFloat64(key string, display ...string) (Metric, error) {
-	metric := &MetricFloat64{AbstractMetric: newAbstract(key, "float64", display...)}
+func (m *Matrix) NewMetricFloat64(key string, display ...string) (*Metric, error) {
+	metric := newAbstract(key, "float64", display...)
 	return metric, m.addMetric(key, metric)
 }
 
-func (m *Matrix) NewMetricType(key string, dataType string, display ...string) (Metric, error) {
+func (m *Matrix) NewMetricType(key string, dataType string, display ...string) (*Metric, error) {
 
 	switch dataType {
 	case "int64":
@@ -151,15 +150,15 @@ func (m *Matrix) NewMetricType(key string, dataType string, display ...string) (
 	}
 }
 
-func newAbstract(key string, dataType string, display ...string) *AbstractMetric {
+func newAbstract(key string, dataType string, display ...string) *Metric {
 	name := key
 	if len(display) > 0 {
 		name = display[0]
 	}
-	return &AbstractMetric{name: name, dtype: dataType, exportable: true}
+	return &Metric{name: name, dataType: dataType, exportable: true}
 }
 
-func (m *Matrix) addMetric(key string, metric Metric) error {
+func (m *Matrix) addMetric(key string, metric *Metric) error {
 	if _, has := m.metrics[key]; has { // Fail if a metric with the same key already exists
 		return errs.New(ErrDuplicateMetricKey, key)
 	}
@@ -179,7 +178,7 @@ func (m *Matrix) RemoveExceptMetric(key string) {
 	if !ok {
 		return
 	}
-	m.metrics = make(map[string]Metric)
+	m.metrics = make(map[string]*Metric)
 	m.displayMetrics = make(map[string]string)
 	_ = m.addMetric(key, prev)
 }
diff --git a/pkg/matrix/metric.go b/pkg/matrix/metric.go
index 70bfe822..c0d267c4 100644
--- a/pkg/matrix/metric.go
+++ b/pkg/matrix/metric.go
@@ -12,84 +12,16 @@ Examples:
 package matrix
 
 import (
+	"fmt"
 	"github.com/netapp/harvest/v2/pkg/dict"
 	"github.com/netapp/harvest/v2/pkg/errs"
 	"github.com/netapp/harvest/v2/pkg/logging"
+	"strconv"
 )
 
-type Metric interface {
-	// methods related to metric attributes
-
-	GetName() string
-	GetType() string
-	SetLabel(string, string)
-	SetLabels(*dict.Dict)
-	GetLabel(string) string
-	GetLabels() *dict.Dict
-	HasLabels() bool
-	IsExportable() bool
-	SetExportable(bool)
-	GetProperty() string
-	SetProperty(string)
-	GetComment() string
-	SetComment(string)
-	IsArray() bool
-	SetArray(bool)
-	IsHistogram() bool
-	SetHistogram(bool)
-	Clone(bool) Metric
-	SetBuckets(*[]string)
-	Buckets() *[]string
-
-	// methods for resizing metric storage
-
-	Reset(int)
-	Remove(int)
-	Append()
-
-	// methods for writing to metric storage
-
-	SetValueInt64(*Instance, int64) error
-	SetValueUint8(*Instance, uint8) error
-	SetValueUint64(*Instance, uint64) error
-	SetValueFloat64(*Instance, float64) error
-	SetValueString(*Instance, string) error
-	SetValueBytes(*Instance, []byte) error
-	SetValueBool(*Instance, bool) error
-
-	AddValueInt64(*Instance, int64) error
-	AddValueUint8(*Instance, uint8) error
-	AddValueUint64(*Instance, uint64) error
-	AddValueFloat64(*Instance, float64) error
-	AddValueString(*Instance, string) error
-
-	SetValueNAN(*Instance)
-	// methods for reading from metric storage
-
-	GetValueInt(*Instance) (int, bool)
-	GetValueInt64(*Instance) (int64, bool)
-	GetValueUint8(*Instance) (uint8, bool)
-	GetValueUint64(*Instance) (uint64, bool)
-	GetValueFloat64(*Instance) (float64, bool)
-	GetValueString(*Instance) (string, bool)
-	GetValueBytes(*Instance) ([]byte, bool)
-	GetRecords() []bool
-	GetValuesFloat64() []float64
-
-	// methods for doing vector arithmetics
-	// currently only supported for float64!
-
-	Delta(Metric, *Matrix, *Matrix, *logging.Logger) (int, error)
-	Divide(Metric, *logging.Logger) (int, error)
-	DivideWithThreshold(Metric, int, *logging.Logger) (int, error)
-	MultiplyByScalar(uint, *logging.Logger) (int, error)
-	// Print is used for debugging
-	Print()
-}
-
-type AbstractMetric struct {
+type Metric struct {
 	name       string
-	dtype      string
+	dataType   string
 	property   string
 	comment    string
 	array      bool
@@ -98,12 +30,13 @@ type AbstractMetric struct {
 	labels     *dict.Dict
 	buckets    *[]string
 	record     []bool
+	values     []float64
 }
 
-func (m *AbstractMetric) Clone(deep bool) *AbstractMetric {
-	clone := AbstractMetric{
+func (m *Metric) Clone(deep bool) *Metric {
+	clone := Metric{
 		name:       m.name,
-		dtype:      m.dtype,
+		dataType:   m.dataType,
 		property:   m.property,
 		comment:    m.comment,
 		exportable: m.exportable,
@@ -119,120 +52,407 @@ func (m *AbstractMetric) Clone(deep bool) *AbstractMetric {
 			clone.record = make([]bool, len(m.record))
 			copy(clone.record, m.record)
 		}
+		if len(m.values) != 0 {
+			clone.values = make([]float64, len(m.values))
+			copy(clone.values, m.values)
+		}
 	}
 	return &clone
 }
 
-func (m *AbstractMetric) GetName() string {
+func (m *Metric) GetName() string {
 	return m.name
 }
 
-func (m *AbstractMetric) IsExportable() bool {
+func (m *Metric) IsExportable() bool {
 	return m.exportable
 }
 
-func (m *AbstractMetric) SetExportable(b bool) {
+func (m *Metric) SetExportable(b bool) {
 	m.exportable = b
 }
 
-func (m *AbstractMetric) GetType() string {
-	return m.dtype
+func (m *Metric) GetType() string {
+	return m.dataType
 }
 
-func (m *AbstractMetric) GetProperty() string {
+func (m *Metric) GetProperty() string {
 	return m.property
 }
 
-func (m *AbstractMetric) SetProperty(p string) {
+func (m *Metric) SetProperty(p string) {
 	m.property = p
 }
 
-func (m *AbstractMetric) GetComment() string {
+func (m *Metric) GetComment() string {
 	return m.comment
 }
 
-func (m *AbstractMetric) SetComment(c string) {
+func (m *Metric) SetComment(c string) {
 	m.comment = c
 }
 
-func (m *AbstractMetric) IsArray() bool {
+func (m *Metric) IsArray() bool {
 	return m.array
 }
 
-func (m *AbstractMetric) SetArray(c bool) {
+func (m *Metric) SetArray(c bool) {
 	m.array = c
 }
 
-func (m *AbstractMetric) SetLabel(key, value string) {
+func (m *Metric) SetLabel(key, value string) {
 	if m.labels == nil {
 		m.labels = dict.New()
 	}
 	m.labels.Set(key, value)
 }
 
-func (m *AbstractMetric) SetHistogram(b bool) {
+func (m *Metric) SetHistogram(b bool) {
 	m.histogram = b
 }
 
-func (m *AbstractMetric) IsHistogram() bool {
+func (m *Metric) IsHistogram() bool {
 	return m.histogram
 }
 
-func (m *AbstractMetric) Buckets() *[]string {
+func (m *Metric) Buckets() *[]string {
 	return m.buckets
 }
 
-func (m *AbstractMetric) SetBuckets(buckets *[]string) {
+func (m *Metric) SetBuckets(buckets *[]string) {
 	m.buckets = buckets
 }
 
-func (m *AbstractMetric) SetLabels(labels *dict.Dict) {
+func (m *Metric) SetLabels(labels *dict.Dict) {
 	m.labels = labels
 }
 
-func (m *AbstractMetric) GetLabel(key string) string {
+func (m *Metric) GetLabel(key string) string {
 	if m.labels != nil {
 		return m.labels.Get(key)
 	}
 	return ""
 }
 
-func (m *AbstractMetric) GetLabels() *dict.Dict {
+func (m *Metric) GetLabels() *dict.Dict {
 	return m.labels
 
 }
-func (m *AbstractMetric) HasLabels() bool {
+func (m *Metric) HasLabels() bool {
 	return m.labels != nil && m.labels.Size() != 0
 }
 
-func (m *AbstractMetric) GetRecords() []bool {
+func (m *Metric) GetRecords() []bool {
 	return m.record
 }
 
-func (m *AbstractMetric) SetValueNAN(i *Instance) {
+func (m *Metric) SetValueNAN(i *Instance) {
 	m.record[i.index] = false
 }
 
-func (m *AbstractMetric) Delta(Metric, *Matrix, *Matrix, *logging.Logger) (int, error) {
-	return 0, errs.New(errs.ErrImplement, m.dtype)
+// Storage resizing methods
+
+func (m *Metric) Reset(size int) {
+	m.record = make([]bool, size)
+	m.values = make([]float64, size)
+}
+
+func (m *Metric) Append() {
+	m.record = append(m.record, false)
+	m.values = append(m.values, 0)
+}
+
+// Remove element at index, shift everything to the left
+func (m *Metric) Remove(index int) {
+	for i := index; i < len(m.values)-1; i++ {
+		m.record[i] = m.record[i+1]
+		m.values[i] = m.values[i+1]
+	}
+	m.record = m.record[:len(m.record)-1]
+	m.values = m.values[:len(m.values)-1]
+}
+
+// Write methods
+
+func (m *Metric) SetValueInt64(i *Instance, v int64) error {
+	m.record[i.index] = true
+	m.values[i.index] = float64(v)
+	return nil
+}
+
+func (m *Metric) SetValueUint8(i *Instance, v uint8) error {
+	m.record[i.index] = true
+	m.values[i.index] = float64(v)
+	return nil
 }
 
-func (m *AbstractMetric) Divide(Metric, *logging.Logger) (int, error) {
-	return 0, errs.New(errs.ErrImplement, m.dtype)
+func (m *Metric) SetValueUint64(i *Instance, v uint64) error {
+	m.record[i.index] = true
+	m.values[i.index] = float64(v)
+	return nil
 }
 
-func (m *AbstractMetric) DivideWithThreshold(Metric, int, *logging.Logger) (int, error) {
-	return 0, errs.New(errs.ErrImplement, m.dtype)
+func (m *Metric) SetValueFloat64(i *Instance, v float64) error {
+	m.record[i.index] = true
+	m.values[i.index] = v
+	return nil
 }
 
-func (m *AbstractMetric) MultiplyByScalar(uint, *logging.Logger) (int, error) {
-	return 0, errs.New(errs.ErrImplement, m.dtype)
+func (m *Metric) SetValueString(i *Instance, v string) error {
+	var x float64
+	var err error
+	if x, err = strconv.ParseFloat(v, 64); err == nil {
+		m.record[i.index] = true
+		m.values[i.index] = x
+		return nil
+	}
+	return err
+}
+
+func (m *Metric) SetValueBytes(i *Instance, v []byte) error {
+	return m.SetValueString(i, string(v))
 }
 
-func (m *AbstractMetric) AddValueString(*Instance, string) error {
-	return errs.New(errs.ErrImplement, m.dtype)
+func (m *Metric) AddValueInt64(i *Instance, n int64) error {
+	v, _ := m.GetValueInt64(i)
+	return m.SetValueInt64(i, v+n)
 }
 
-func (m *AbstractMetric) SetValueBool(*Instance, bool) error {
-	return errs.New(errs.ErrImplement, m.dtype)
+func (m *Metric) AddValueUint8(i *Instance, n uint8) error {
+	v, _ := m.GetValueUint8(i)
+	return m.SetValueUint8(i, v+n)
+}
+
+func (m *Metric) AddValueUint64(i *Instance, n uint64) error {
+	v, _ := m.GetValueUint64(i)
+	return m.SetValueUint64(i, v+n)
+}
+
+func (m *Metric) AddValueFloat64(i *Instance, n float64) error {
+	v, _ := m.GetValueFloat64(i)
+	return m.SetValueFloat64(i, v+n)
+}
+
+func (m *Metric) AddValueString(i *Instance, v string) error {
+	var (
+		x, n float64
+		err  error
+		has  bool
+	)
+	if x, err = strconv.ParseFloat(v, 64); err != nil {
+		return err
+	}
+	if n, has = m.GetValueFloat64(i); has {
+		return m.SetValueFloat64(i, x+n)
+	}
+	return m.SetValueFloat64(i, x)
+}
+
+// Read methods
+
+func (m *Metric) GetValueInt(i *Instance) (int, bool) {
+	v := m.values[i.index]
+	val := int(v)
+	return val, m.record[i.index]
+}
+
+func (m *Metric) GetValueInt64(i *Instance) (int64, bool) {
+	v := m.values[i.index]
+	val := int64(v)
+	return val, m.record[i.index]
+}
+
+func (m *Metric) GetValueUint8(i *Instance) (uint8, bool) {
+	v := m.values[i.index]
+	return uint8(v), m.record[i.index]
+}
+
+func (m *Metric) GetValueUint64(i *Instance) (uint64, bool) {
+	v := m.values[i.index]
+	val := uint64(v)
+	return val, m.record[i.index]
+}
+
+func (m *Metric) GetValueFloat64(i *Instance) (float64, bool) {
+	v := m.values[i.index]
+	return v, m.record[i.index]
+}
+
+func (m *Metric) GetValueString(i *Instance) (string, bool) {
+	v := m.values[i.index]
+	return strconv.FormatFloat(v, 'f', -1, 64), m.record[i.index]
+}
+
+func (m *Metric) GetValueBytes(i *Instance) ([]byte, bool) {
+	s, ok := m.GetValueString(i)
+	return []byte(s), ok
+}
+
+// Delta vector arithmetics
+func (m *Metric) Delta(prevMetric *Metric, prevMat *Matrix, curMat *Matrix, logger *logging.Logger) (int, error) {
+	var skips int
+	prevRaw := prevMetric.values
+	prevRecord := prevMetric.GetRecords()
+	for key, currInstance := range curMat.GetInstances() {
+		// check if this instance key exists in previous matrix
+		prevInstance := prevMat.GetInstance(key)
+		currIndex := currInstance.index
+		curRaw := m.values[currIndex]
+		if prevInstance != nil {
+			prevIndex := prevInstance.index
+			if m.record[currIndex] && prevRecord[prevIndex] {
+				m.values[currIndex] -= prevRaw[prevIndex]
+				// Sometimes ONTAP sends spurious zeroes or values less than the previous poll.
+				// Detect and don't publish negative deltas or the subsequent poll will show a large spike.
+				isInvalidZero := (curRaw == 0 || prevRaw[prevIndex] == 0) && m.values[prevIndex] != 0
+				isNegative := m.values[currIndex] < 0
+				if isInvalidZero || isNegative {
+					m.record[currIndex] = false
+					skips++
+					logger.Trace().
+						Str("metric", m.GetName()).
+						Float64("currentRaw", curRaw).
+						Float64("previousRaw", prevRaw[prevIndex]).
+						Str("instKey", key).
+						Msg("Negative cooked value")
+				}
+			} else {
+				m.record[currIndex] = false
+				skips++
+				logger.Trace().
+					Str("metric", m.GetName()).
+					Float64("currentRaw", curRaw).
+					Float64("previousRaw", prevRaw[prevIndex]).
+					Str("instKey", key).
+					Msg("Delta calculation skipped")
+			}
+		} else {
+			m.record[currIndex] = false
+			skips++
+			logger.Trace().
+				Str("metric", m.GetName()).
+				Float64("currentRaw", curRaw).
+				Str("instKey", key).
+				Msg("New instance added")
+		}
+	}
+	return skips, nil
+}
+
+func (m *Metric) Divide(s *Metric, logger *logging.Logger) (int, error) {
+	var skips int
+	sValues := s.values
+	sRecord := s.GetRecords()
+	if len(m.values) != len(sValues) {
+		return 0, errs.New(ErrUnequalVectors, fmt.Sprintf("numerator=%d, denominator=%d", len(m.values), len(sValues)))
+	}
+	for i := 0; i < len(m.values); i++ {
+		if m.record[i] && sRecord[i] {
+			if sValues[i] != 0 {
+				// Don't pass along the value if the numerator or denominator is < 0
+				// A denominator of zero is fine
+				if m.values[i] < 0 || sValues[i] < 0 {
+					m.record[i] = false
+					skips++
+					logger.Trace().
+						Str("metric", m.GetName()).
+						Float64("numerator", m.values[i]).
+						Float64("denominator", sValues[i]).
+						Msg("Divide calculation skipped")
+				}
+				m.values[i] /= sValues[i]
+			} else {
+				m.values[i] = 0
+			}
+		} else {
+			m.record[i] = false
+			skips++
+			logger.Trace().
+				Str("metric", m.GetName()).
+				Float64("numerator", m.values[i]).
+				Float64("denominator", sValues[i]).
+				Msg("Divide calculation skipped")
+		}
+	}
+	return skips, nil
+}
+
+func (m *Metric) DivideWithThreshold(s *Metric, t int, logger *logging.Logger) (int, error) {
+	var skips int
+	x := float64(t)
+	sValues := s.values
+	sRecord := s.GetRecords()
+	if len(m.values) != len(sValues) {
+		return 0, errs.New(ErrUnequalVectors, fmt.Sprintf("numerator=%d, denominator=%d", len(m.values), len(sValues)))
+	}
+	for i := 0; i < len(m.values); i++ {
+		v := m.values[i]
+		// Don't pass along the value if the numerator or denominator is < 0
+		// It's important to check sValues[i] < 0 and allow a zero so pass=true and m.values[i] remains unchanged
+		if m.values[i] < 0 || sValues[i] < 0 {
+			m.record[i] = false
+			skips++
+			logger.Trace().
+				Str("metric", m.GetName()).
+				Float64("numerator", v).
+				Float64("denominator", sValues[i]).
+				Msg("Negative values")
+			return skips, nil
+		}
+		if m.record[i] && sRecord[i] {
+			if sValues[i] >= x {
+				m.values[i] /= sValues[i]
+			} else {
+				m.values[i] = 0
+			}
+		} else {
+			m.record[i] = false
+			skips++
+			logger.Trace().
+				Str("metric", m.GetName()).
+				Float64("numerator", m.values[i]).
+				Float64("denominator", sValues[i]).
+				Msg("Divide threshold calculation skipped")
+		}
+	}
+	return skips, nil
+}
+
+func (m *Metric) MultiplyByScalar(s uint, logger *logging.Logger) (int, error) {
+	var skips int
+	x := float64(s)
+	for i := 0; i < len(m.values); i++ {
+		if m.record[i] {
+			// if current is <= 0
+			if m.values[i] < 0 {
+				m.record[i] = false
+				skips++
+				logger.Trace().
+					Str("metric", m.GetName()).
+					Float64("currentRaw", m.values[i]).
+					Uint("scalar", s).
+					Msg("Negative value")
+			}
+			m.values[i] *= x
+		} else {
+			m.record[i] = false
+			skips++
+			logger.Trace().
+				Str("metric", m.GetName()).
+				Float64("currentRaw", m.values[i]).
+				Uint("scalar", s).
+				Msg("Scalar multiplication skipped")
+		}
+	}
+	return skips, nil
+}
+
+func (m *Metric) Print() {
+	for i := range m.values {
+		if m.record[i] {
+			fmt.Printf("%s%v ", " ", m.values[i])
+		} else {
+			fmt.Printf("%s%v ", "!", m.values[i])
+		}
+	}
 }
diff --git a/pkg/matrix/metric_float64.go b/pkg/matrix/metric_float64.go
deleted file mode 100644
index 7d401674..00000000
--- a/pkg/matrix/metric_float64.go
+++ /dev/null
@@ -1,294 +0,0 @@
-/*
- * Copyright NetApp Inc, 2021 All rights reserved
- */
-
-package matrix
-
-import (
-	"fmt"
-	"github.com/netapp/harvest/v2/pkg/color"
-	"github.com/netapp/harvest/v2/pkg/errs"
-	"github.com/netapp/harvest/v2/pkg/logging"
-	"strconv"
-)
-
-type MetricFloat64 struct {
-	*AbstractMetric
-	values []float64
-}
-
-func (m *MetricFloat64) Clone(deep bool) Metric {
-	clone := MetricFloat64{AbstractMetric: m.AbstractMetric.Clone(deep)}
-	if deep && len(m.values) != 0 {
-		clone.values = make([]float64, len(m.values))
-		copy(clone.values, m.values)
-	}
-	return &clone
-}
-
-// Storage resizing methods
-
-func (m *MetricFloat64) Reset(size int) {
-	m.record = make([]bool, size)
-	m.values = make([]float64, size)
-}
-
-func (m *MetricFloat64) Append() {
-	m.record = append(m.record, false)
-	m.values = append(m.values, 0)
-}
-
-// Remove element at index, shift everything to the left
-func (m *MetricFloat64) Remove(index int) {
-	for i := index; i < len(m.values)-1; i++ {
-		m.record[i] = m.record[i+1]
-		m.values[i] = m.values[i+1]
-	}
-	m.record = m.record[:len(m.record)-1]
-	m.values = m.values[:len(m.values)-1]
-}
-
-// Write methods
-
-func (m *MetricFloat64) SetValueInt64(i *Instance, v int64) error {
-	m.record[i.index] = true
-	m.values[i.index] = float64(v)
-	return nil
-}
-
-func (m *MetricFloat64) SetValueUint8(i *Instance, v uint8) error {
-	m.record[i.index] = true
-	m.values[i.index] = float64(v)
-	return nil
-}
-
-func (m *MetricFloat64) SetValueUint64(i *Instance, v uint64) error {
-	m.record[i.index] = true
-	m.values[i.index] = float64(v)
-	return nil
-}
-
-func (m *MetricFloat64) SetValueFloat64(i *Instance, v float64) error {
-	m.record[i.index] = true
-	m.values[i.index] = v
-	return nil
-}
-
-func (m *MetricFloat64) SetValueString(i *Instance, v string) error {
-	var x float64
-	var err error
-	if x, err = strconv.ParseFloat(v, 64); err == nil {
-		m.record[i.index] = true
-		m.values[i.index] = x
-		return nil
-	}
-	return err
-}
-
-func (m *MetricFloat64) SetValueBytes(i *Instance, v []byte) error {
-	return m.SetValueString(i, string(v))
-}
-
-func (m *MetricFloat64) AddValueInt64(i *Instance, n int64) error {
-	v, _ := m.GetValueInt64(i)
-	return m.SetValueInt64(i, v+n)
-}
-
-func (m *MetricFloat64) AddValueUint8(i *Instance, n uint8) error {
-	v, _ := m.GetValueUint8(i)
-	return m.SetValueUint8(i, v+n)
-}
-
-func (m *MetricFloat64) AddValueUint64(i *Instance, n uint64) error {
-	v, _ := m.GetValueUint64(i)
-	return m.SetValueUint64(i, v+n)
-}
-
-func (m *MetricFloat64) AddValueFloat64(i *Instance, n float64) error {
-	v, _ := m.GetValueFloat64(i)
-	return m.SetValueFloat64(i, v+n)
-}
-
-func (m *MetricFloat64) AddValueString(i *Instance, v string) error {
-	var (
-		x, n float64
-		err  error
-		has  bool
-	)
-	if x, err = strconv.ParseFloat(v, 64); err != nil {
-		return err
-	}
-	if n, has = m.GetValueFloat64(i); has {
-		return m.SetValueFloat64(i, x+n)
-	}
-	return m.SetValueFloat64(i, x)
-}
-
-// Read methods
-
-func (m *MetricFloat64) GetValueInt(i *Instance) (int, bool) {
-	return int(m.values[i.index]), m.record[i.index]
-}
-
-func (m *MetricFloat64) GetValueInt64(i *Instance) (int64, bool) {
-	return int64(m.values[i.index]), m.record[i.index]
-}
-
-func (m *MetricFloat64) GetValueUint8(i *Instance) (uint8, bool) {
-	return uint8(m.values[i.index]), m.record[i.index]
-}
-
-func (m *MetricFloat64) GetValueUint64(i *Instance) (uint64, bool) {
-	return uint64(m.values[i.index]), m.record[i.index]
-}
-
-func (m *MetricFloat64) GetValueFloat64(i *Instance) (float64, bool) {
-	return m.values[i.index], m.record[i.index]
-}
-
-func (m *MetricFloat64) GetValueString(i *Instance) (string, bool) {
-	return strconv.FormatFloat(m.values[i.index], 'f', -1, 64), m.record[i.index]
-}
-
-func (m *MetricFloat64) GetValueBytes(i *Instance) ([]byte, bool) {
-	s, ok := m.GetValueString(i)
-	return []byte(s), ok
-}
-
-// vector arithmetics
-
-func (m *MetricFloat64) GetValuesFloat64() []float64 {
-	return m.values
-}
-
-func (m *MetricFloat64) Delta(prevMetric Metric, prevMat *Matrix, curMat *Matrix, logger *logging.Logger) (int, error) {
-	var skips int
-	prevRaw := prevMetric.GetValuesFloat64()
-	prevRecord := prevMetric.GetRecords()
-	for key, currInstance := range curMat.GetInstances() {
-		// check if this instance key exists in previous matrix
-		prevInstance := prevMat.GetInstance(key)
-		currIndex := currInstance.index
-		curRaw := m.values[currIndex]
-		if prevInstance != nil {
-			prevIndex := prevInstance.index
-			if m.record[currIndex] && prevRecord[prevIndex] {
-				m.values[currIndex] -= prevRaw[prevIndex]
-				// Sometimes ONTAP sends spurious zeroes or values less than the previous poll.
-				// Detect and don't publish negative deltas or the subsequent poll will show a large spike.
-				isInvalidZero := (curRaw == 0 || prevRaw[prevIndex] == 0) && m.values[prevIndex] != 0
-				isNegative := m.values[currIndex] < 0
-				if isInvalidZero || isNegative {
-					m.record[currIndex] = false
-					skips++
-					logger.Trace().
-						Str("metric", m.GetName()).
-						Float64("currentRaw", curRaw).
-						Float64("previousRaw", prevRaw[prevIndex]).
-						Str("instKey", key).
-						Msg("Negative cooked value")
-				}
-			}
-		} else {
-			m.record[currIndex] = false
-			skips++
-			logger.Trace().
-				Str("metric", m.GetName()).
-				Float64("currentRaw", curRaw).
-				Str("instKey", key).
-				Msg("New instance added")
-		}
-	}
-	return skips, nil
-}
-
-func (m *MetricFloat64) Divide(s Metric, logger *logging.Logger) (int, error) {
-	var skips int
-	sValues := s.GetValuesFloat64()
-	sRecord := s.GetRecords()
-	if len(m.values) != len(sValues) {
-		return 0, errs.New(ErrUnequalVectors, fmt.Sprintf("numerator=%d, denominator=%d", len(m.values), len(sValues)))
-	}
-	for i := 0; i < len(m.values); i++ {
-		if m.record[i] && sRecord[i] {
-			if sValues[i] != 0 {
-				// Don't pass along the value if the numerator or denominator is < 0
-				// A denominator of zero is fine
-				if m.values[i] < 0 || sValues[i] < 0 {
-					m.record[i] = false
-					skips++
-					logger.Trace().
-						Str("metric", m.GetName()).
-						Float64("numerator", m.values[i]).
-						Float64("denominator", sValues[i]).
-						Msg("No pass values")
-				}
-				m.values[i] /= sValues[i]
-			} else {
-				m.values[i] = 0
-			}
-		}
-	}
-	return skips, nil
-}
-
-func (m *MetricFloat64) DivideWithThreshold(s Metric, t int, logger *logging.Logger) (int, error) {
-	var skips int
-	x := float64(t)
-	sValues := s.GetValuesFloat64()
-	sRecord := s.GetRecords()
-	if len(m.values) != len(sValues) {
-		return 0, errs.New(ErrUnequalVectors, fmt.Sprintf("numerator=%d, denominator=%d", len(m.values), len(sValues)))
-	}
-	for i := 0; i < len(m.values); i++ {
-		v := m.values[i]
-		// Don't pass along the value if the numerator or denominator is < 0
-		// It's important to check sValues[i] < 0 and allow a zero so pass=true and m.values[i] remains unchanged
-		if m.values[i] < 0 || sValues[i] < 0 {
-			m.record[i] = false
-			skips++
-			logger.Trace().
-				Str("metric", m.GetName()).
-				Float64("numerator", v).
-				Float64("denominator", sValues[i]).
-				Msg("Negative values")
-		}
-		if m.record[i] && sRecord[i] && sValues[i] >= x {
-			m.values[i] /= sValues[i]
-		} else {
-			m.values[i] = 0
-		}
-	}
-	return skips, nil
-}
-
-func (m *MetricFloat64) MultiplyByScalar(s uint, logger *logging.Logger) (int, error) {
-	var skips int
-	x := float64(s)
-	for i := 0; i < len(m.values); i++ {
-		if m.record[i] {
-			// if current is <= 0
-			if m.values[i] < 0 {
-				m.record[i] = false
-				skips++
-				logger.Trace().
-					Str("metric", m.GetName()).
-					Float64("currentRaw", m.values[i]).
-					Uint("scalar", s).
-					Msg("Negative value")
-			}
-			m.values[i] *= x
-		}
-	}
-	return skips, nil
-}
-
-func (m *MetricFloat64) Print() {
-	for i := range m.values {
-		if m.record[i] {
-			fmt.Printf("%s%v%s ", color.Green, m.values[i], color.End)
-		} else {
-			fmt.Printf("%s%v%s ", color.Red, m.values[i], color.End)
-		}
-	}
-}
diff --git a/pkg/matrix/metric_int64.go b/pkg/matrix/metric_int64.go
deleted file mode 100644
index f083f870..00000000
--- a/pkg/matrix/metric_int64.go
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright NetApp Inc, 2021 All rights reserved
- */
-
-package matrix
-
-import (
-	"fmt"
-	"github.com/netapp/harvest/v2/pkg/color"
-	"strconv"
-)
-
-type MetricInt64 struct {
-	*AbstractMetric
-	values []int64
-}
-
-func (m *MetricInt64) Clone(deep bool) Metric {
-	clone := MetricInt64{AbstractMetric: m.AbstractMetric.Clone(deep)}
-	if deep && len(m.values) != 0 {
-		clone.values = make([]int64, len(m.values))
-		copy(clone.values, m.values)
-	}
-	return &clone
-}
-
-// Storage resizing methods
-
-func (m *MetricInt64) Reset(size int) {
-	m.record = make([]bool, size)
-	m.values = make([]int64, size)
-}
-
-func (m *MetricInt64) Append() {
-	m.record = append(m.record, false)
-	m.values = append(m.values, 0)
-}
-
-// Remove element at index, shift everything to the left
-func (m *MetricInt64) Remove(index int) {
-	for i := index; i < len(m.values)-1; i++ {
-		m.record[i] = m.record[i+1]
-		m.values[i] = m.values[i+1]
-	}
-	m.record = m.record[:len(m.record)-1]
-	m.values = m.values[:len(m.values)-1]
-}
-
-// Write methods
-
-func (m *MetricInt64) SetValueInt64(i *Instance, v int64) error {
-	m.record[i.index] = true
-	m.values[i.index] = v
-	return nil
-}
-
-func (m *MetricInt64) SetValueUint8(i *Instance, v uint8) error {
-	m.record[i.index] = true
-	m.values[i.index] = int64(v)
-	return nil
-}
-
-func (m *MetricInt64) SetValueUint64(i *Instance, v uint64) error {
-	m.record[i.index] = true
-	m.values[i.index] = int64(v)
-	return nil
-}
-
-func (m *MetricInt64) SetValueFloat64(i *Instance, v float64) error {
-	m.record[i.index] = true
-	m.values[i.index] = int64(v)
-	return nil
-}
-
-func (m *MetricInt64) SetValueString(i *Instance, v string) error {
-	var x int64
-	var err error
-	if x, err = strconv.ParseInt(v, 10, 64); err == nil {
-		m.record[i.index] = true
-		m.values[i.index] = x
-		return nil
-	}
-	return err
-}
-
-func (m *MetricInt64) SetValueBytes(i *Instance, v []byte) error {
-	return m.SetValueString(i, string(v))
-}
-
-func (m *MetricInt64) AddValueInt64(i *Instance, n int64) error {
-	v, _ := m.GetValueInt64(i)
-	return m.SetValueInt64(i, v+n)
-}
-
-func (m *MetricInt64) AddValueUint8(i *Instance, n uint8) error {
-	v, _ := m.GetValueUint8(i)
-	return m.SetValueUint8(i, v+n)
-}
-
-func (m *MetricInt64) AddValueUint64(i *Instance, n uint64) error {
-	v, _ := m.GetValueUint64(i)
-	return m.SetValueUint64(i, v+n)
-}
-
-func (m *MetricInt64) AddValueFloat64(i *Instance, n float64) error {
-	v, _ := m.GetValueFloat64(i)
-	return m.SetValueFloat64(i, v+n)
-}
-
-// Read methods
-
-func (m *MetricInt64) GetValueInt(i *Instance) (int, bool) {
-	return int(m.values[i.index]), m.record[i.index]
-}
-
-func (m *MetricInt64) GetValueInt64(i *Instance) (int64, bool) {
-	return m.values[i.index], m.record[i.index]
-}
-
-func (m *MetricInt64) GetValueUint8(i *Instance) (uint8, bool) {
-	return uint8(m.values[i.index]), m.record[i.index]
-}
-
-func (m *MetricInt64) GetValueUint64(i *Instance) (uint64, bool) {
-	return uint64(m.values[i.index]), m.record[i.index]
-}
-
-func (m *MetricInt64) GetValueFloat64(i *Instance) (float64, bool) {
-	return float64(m.values[i.index]), m.record[i.index]
-}
-
-func (m *MetricInt64) GetValueString(i *Instance) (string, bool) {
-	return strconv.FormatInt(m.values[i.index], 10), m.record[i.index]
-}
-
-func (m *MetricInt64) GetValueBytes(i *Instance) ([]byte, bool) {
-	s, ok := m.GetValueString(i)
-	return []byte(s), ok
-}
-
-// vector arithmetics
-
-func (m *MetricInt64) GetValuesFloat64() []float64 {
-	f := make([]float64, len(m.values))
-	for i, v := range m.values {
-		f[i] = float64(v)
-	}
-	return f
-}
-
-// debug
-
-func (m *MetricInt64) Print() {
-	for i := range m.values {
-		if m.record[i] {
-			fmt.Printf("%s%v%s ", color.Green, m.values[i], color.End)
-		} else {
-			fmt.Printf("%s%v%s ", color.Red, m.values[i], color.End)
-		}
-	}
-}
diff --git a/pkg/matrix/metric_uint64.go b/pkg/matrix/metric_uint64.go
deleted file mode 100644
index 10f9d75a..00000000
--- a/pkg/matrix/metric_uint64.go
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright NetApp Inc, 2021 All rights reserved
- */
-
-package matrix
-
-import (
-	"fmt"
-	"github.com/netapp/harvest/v2/pkg/color"
-	"github.com/netapp/harvest/v2/pkg/errs"
-	"strconv"
-)
-
-type MetricUint64 struct {
-	*AbstractMetric
-	values []uint64
-}
-
-func (m *MetricUint64) Clone(deep bool) Metric {
-	clone := MetricUint64{AbstractMetric: m.AbstractMetric.Clone(deep)}
-	if deep && len(m.values) != 0 {
-		clone.values = make([]uint64, len(m.values))
-		copy(clone.values, m.values)
-	}
-	return &clone
-}
-
-// Storage resizing methods
-
-func (m *MetricUint64) Reset(size int) {
-	m.record = make([]bool, size)
-	m.values = make([]uint64, size)
-}
-
-func (m *MetricUint64) Append() {
-	m.record = append(m.record, false)
-	m.values = append(m.values, 0)
-}
-
-// Remove element at index, shift everything to the left
-func (m *MetricUint64) Remove(index int) {
-	for i := index; i < len(m.values)-1; i++ {
-		m.record[i] = m.record[i+1]
-		m.values[i] = m.values[i+1]
-	}
-	m.record = m.record[:len(m.record)-1]
-	m.values = m.values[:len(m.values)-1]
-}
-
-// Write methods
-
-func (m *MetricUint64) SetValueInt64(i *Instance, v int64) error {
-	if v >= 0 {
-		m.record[i.index] = true
-		m.values[i.index] = uint64(v)
-		return nil
-	}
-	return errs.New(ErrOverflow, fmt.Sprintf("convert int64 (%d) to uint64", v))
-}
-
-func (m *MetricUint64) SetValueUint8(i *Instance, v uint8) error {
-	m.record[i.index] = true
-	m.values[i.index] = uint64(v)
-	return nil
-}
-
-func (m *MetricUint64) SetValueUint64(i *Instance, v uint64) error {
-	m.record[i.index] = true
-	m.values[i.index] = v
-	return nil
-}
-
-func (m *MetricUint64) SetValueFloat64(i *Instance, v float64) error {
-	if v >= 0 {
-		m.record[i.index] = true
-		m.values[i.index] = uint64(v)
-		return nil
-	}
-	return errs.New(ErrOverflow, fmt.Sprintf("convert float64 (%f) to uint64", v))
-}
-
-func (m *MetricUint64) SetValueString(i *Instance, v string) error {
-	var x uint64
-	var err error
-	if x, err = strconv.ParseUint(v, 10, 64); err == nil {
-		m.record[i.index] = true
-		m.values[i.index] = x
-		return nil
-	}
-	return err
-}
-
-func (m *MetricUint64) SetValueBytes(i *Instance, v []byte) error {
-	return m.SetValueString(i, string(v))
-}
-
-func (m *MetricUint64) AddValueInt64(i *Instance, n int64) error {
-	v, _ := m.GetValueInt64(i)
-	return m.SetValueInt64(i, v+n)
-}
-
-func (m *MetricUint64) AddValueUint8(i *Instance, n uint8) error {
-	v, _ := m.GetValueUint8(i)
-	return m.SetValueUint8(i, v+n)
-}
-
-func (m *MetricUint64) AddValueUint64(i *Instance, n uint64) error {
-	v, _ := m.GetValueUint64(i)
-	return m.SetValueUint64(i, v+n)
-}
-
-func (m *MetricUint64) AddValueFloat64(i *Instance, n float64) error {
-	v, _ := m.GetValueFloat64(i)
-	return m.SetValueFloat64(i, v+n)
-}
-
-// Read methods
-
-func (m *MetricUint64) GetValueInt(i *Instance) (int, bool) {
-	return int(m.values[i.index]), m.record[i.index]
-}
-
-func (m *MetricUint64) GetValueInt64(i *Instance) (int64, bool) {
-	return int64(m.values[i.index]), m.record[i.index]
-}
-
-func (m *MetricUint64) GetValueUint8(i *Instance) (uint8, bool) {
-	return uint8(m.values[i.index]), m.record[i.index]
-}
-
-func (m *MetricUint64) GetValueUint64(i *Instance) (uint64, bool) {
-	return m.values[i.index], m.record[i.index]
-}
-
-func (m *MetricUint64) GetValueFloat64(i *Instance) (float64, bool) {
-	return float64(m.values[i.index]), m.record[i.index]
-}
-
-func (m *MetricUint64) GetValueString(i *Instance) (string, bool) {
-	return strconv.FormatUint(m.values[i.index], 10), m.record[i.index]
-}
-
-func (m *MetricUint64) GetValueBytes(i *Instance) ([]byte, bool) {
-	s, ok := m.GetValueString(i)
-	return []byte(s), ok
-}
-
-// vector arithmetics
-
-func (m *MetricUint64) GetValuesFloat64() []float64 {
-	f := make([]float64, len(m.values))
-	for i, v := range m.values {
-		f[i] = float64(v)
-	}
-	return f
-}
-
-// debug
-
-func (m *MetricUint64) Print() {
-	for i := range m.values {
-		if m.record[i] {
-			fmt.Printf("%s%v%s ", color.Green, m.values[i], color.End)
-		} else {
-			fmt.Printf("%s%v%s ", color.Red, m.values[i], color.End)
-		}
-	}
-}
diff --git a/pkg/matrix/metric_uint8.go b/pkg/matrix/metric_uint8.go
deleted file mode 100644
index 831faffb..00000000
--- a/pkg/matrix/metric_uint8.go
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright NetApp Inc, 2021 All rights reserved
- */
-
-package matrix
-
-import (
-	"fmt"
-	"github.com/netapp/harvest/v2/pkg/color"
-	"github.com/netapp/harvest/v2/pkg/errs"
-	"strconv"
-)
-
-type MetricUint8 struct {
-	*AbstractMetric
-	values []uint8
-}
-
-func (u *MetricUint8) Clone(deep bool) Metric {
-	clone := MetricUint8{AbstractMetric: u.AbstractMetric.Clone(deep)}
-	if deep && len(u.values) != 0 {
-		clone.values = make([]uint8, len(u.values))
-		copy(clone.values, u.values)
-	}
-	return &clone
-}
-
-// Storage resizing methods
-
-func (u *MetricUint8) Reset(size int) {
-	u.record = make([]bool, size)
-	u.values = make([]uint8, size)
-}
-
-func (u *MetricUint8) Append() {
-	u.record = append(u.record, false)
-	u.values = append(u.values, 0)
-}
-
-// Remove element at index, shift everything to the left
-func (u *MetricUint8) Remove(index int) {
-	for i := index; i < len(u.values)-1; i++ {
-		u.record[i] = u.record[i+1]
-		u.values[i] = u.values[i+1]
-	}
-	u.record = u.record[:len(u.record)-1]
-	u.values = u.values[:len(u.values)-1]
-}
-
-// Write methods
-
-func (u *MetricUint8) SetValueInt64(i *Instance, v int64) error {
-	if v >= 0 {
-		u.record[i.index] = true
-		u.values[i.index] = uint8(v)
-		return nil
-	}
-	return errs.New(ErrOverflow, fmt.Sprintf("convert int64 (%d) to uint32", v))
-}
-
-func (u *MetricUint8) SetValueBool(i *Instance, v bool) error {
-	u.record[i.index] = true
-	if v {
-		u.values[i.index] = 1
-	} else {
-		u.values[i.index] = 0
-	}
-	return nil
-}
-
-func (u *MetricUint8) SetValueUint8(i *Instance, v uint8) error {
-	u.record[i.index] = true
-	u.values[i.index] = v
-	return nil
-}
-
-func (u *MetricUint8) SetValueUint64(i *Instance, v uint64) error {
-	u.record[i.index] = true
-	u.values[i.index] = uint8(v)
-	return nil
-}
-
-func (u *MetricUint8) SetValueFloat64(i *Instance, v float64) error {
-	if v >= 0 {
-		u.record[i.index] = true
-		u.values[i.index] = uint8(v)
-		return nil
-	}
-	return errs.New(ErrOverflow, fmt.Sprintf("convert float64 (%f) to uint8", v))
-}
-
-func (u *MetricUint8) SetValueString(i *Instance, v string) error {
-	var x uint64
-	var err error
-	if x, err = strconv.ParseUint(v, 10, 8); err == nil {
-		u.record[i.index] = true
-		u.values[i.index] = uint8(x)
-		return nil
-	}
-	return err
-}
-
-func (u *MetricUint8) SetValueBytes(i *Instance, v []byte) error {
-	return u.SetValueString(i, string(v))
-}
-
-func (u *MetricUint8) AddValueInt64(i *Instance, n int64) error {
-	m, _ := u.GetValueInt64(i)
-	return u.SetValueInt64(i, m+n)
-}
-
-func (u *MetricUint8) AddValueUint8(i *Instance, n uint8) error {
-	m, _ := u.GetValueUint8(i)
-	return u.SetValueUint8(i, m+n)
-}
-
-func (u *MetricUint8) AddValueUint64(i *Instance, n uint64) error {
-	m, _ := u.GetValueUint64(i)
-	return u.SetValueUint64(i, m+n)
-}
-
-func (u *MetricUint8) AddValueFloat64(i *Instance, n float64) error {
-	m, _ := u.GetValueFloat64(i)
-	return u.SetValueFloat64(i, m+n)
-}
-
-// Read methods
-
-func (u *MetricUint8) GetValueInt(i *Instance) (int, bool) {
-	return int(u.values[i.index]), u.record[i.index]
-}
-
-func (u *MetricUint8) GetValueInt64(i *Instance) (int64, bool) {
-	return int64(u.values[i.index]), u.record[i.index]
-}
-
-func (u *MetricUint8) GetValueUint8(i *Instance) (uint8, bool) {
-	return u.values[i.index], u.record[i.index]
-}
-
-func (u *MetricUint8) GetValueUint64(i *Instance) (uint64, bool) {
-	return uint64(u.values[i.index]), u.record[i.index]
-}
-
-func (u *MetricUint8) GetValueFloat64(i *Instance) (float64, bool) {
-	return float64(u.values[i.index]), u.record[i.index]
-}
-
-func (u *MetricUint8) GetValueString(i *Instance) (string, bool) {
-	return strconv.FormatUint(uint64(u.values[i.index]), 10), u.record[i.index]
-}
-
-func (u *MetricUint8) GetValueBytes(i *Instance) ([]byte, bool) {
-	s, ok := u.GetValueString(i)
-	return []byte(s), ok
-}
-
-// vector arithmetics
-
-func (u *MetricUint8) GetValuesFloat64() []float64 {
-	f := make([]float64, len(u.values))
-	for i, v := range u.values {
-		f[i] = float64(v)
-	}
-	return f
-}
-
-// debug
-
-func (u *MetricUint8) Print() {
-	for i := range u.values {
-		if u.record[i] {
-			fmt.Printf("%s%v%s ", color.Green, u.values[i], color.End)
-		} else {
-			fmt.Printf("%s%v%s ", color.Red, u.values[i], color.End)
-		}
-	}
-}


Test patch:
diff --git a/cmd/poller/plugin/aggregator/aggregator_test.go b/cmd/poller/plugin/aggregator/aggregator_test.go
index 36c1806b..10efd3e0 100644
--- a/cmd/poller/plugin/aggregator/aggregator_test.go
+++ b/cmd/poller/plugin/aggregator/aggregator_test.go
@@ -29,7 +29,7 @@ func TestInitPlugin(t *testing.T) {
 
 func TestRuleSimpleAggregation(t *testing.T) {
 
-	// create artifical data
+	// create artificial data
 	m = matrix.New("", "", "")
 	var n *matrix.Matrix
 
@@ -172,7 +172,7 @@ func TestComplexRuleRegex(t *testing.T) {
 
 	var n *matrix.Matrix
 	var A, B, C, D, instance *matrix.Instance
-	var metricA matrix.Metric
+	var metricA *matrix.Metric
 	var err error
 
 	params := node.NewS("Aggregator")
diff --git a/cmd/poller/plugin/labelagent/label_agent_test.go b/cmd/poller/plugin/labelagent/label_agent_test.go
index b62605cd..d0db5775 100644
--- a/cmd/poller/plugin/labelagent/label_agent_test.go
+++ b/cmd/poller/plugin/labelagent/label_agent_test.go
@@ -307,7 +307,7 @@ func TestValueToNumRule(t *testing.T) {
 
 	var (
 		instanceA, instanceB  *matrix.Instance
-		status, stage, outage matrix.Metric
+		status, stage, outage *matrix.Metric
 		v, expected           uint8
 		ok                    bool
 		err                   error
@@ -407,7 +407,7 @@ func TestValueToNumRegexRule(t *testing.T) {
 
 	var (
 		instanceA, instanceB   *matrix.Instance
-		status, output, result matrix.Metric
+		status, output, result *matrix.Metric
 		v, expected            uint8
 		ok                     bool
 		err                    error
diff --git a/cmd/poller/plugin/metricagent/metric_agent_test.go b/cmd/poller/plugin/metricagent/metric_agent_test.go
index 97158d85..7051fae0 100644
--- a/cmd/poller/plugin/metricagent/metric_agent_test.go
+++ b/cmd/poller/plugin/metricagent/metric_agent_test.go
@@ -44,11 +44,11 @@ func TestComputeMetricsRule(t *testing.T) {
 
 	var (
 		instanceA, instanceB                                                *matrix.Instance
-		metricAvail, metricUsed, metricTotal                                matrix.Metric
-		metricDiskP, metricDiskS, metricDiskH, metricDiskTotal              matrix.Metric
-		metricFiles, metricFilesUsed, metricFilesAvailable                  matrix.Metric
-		metricBytesPSector, metricSectorCount, metricTotalBytes             matrix.Metric
-		metricBytesTransferred, metricTotalDuration, metricTransmissionRate matrix.Metric
+		metricAvail, metricUsed, metricTotal                                *matrix.Metric
+		metricDiskP, metricDiskS, metricDiskH, metricDiskTotal              *matrix.Metric
+		metricFiles, metricFilesUsed, metricFilesAvailable                  *matrix.Metric
+		metricBytesPSector, metricSectorCount, metricTotalBytes             *matrix.Metric
+		metricBytesTransferred, metricTotalDuration, metricTransmissionRate *matrix.Metric
 		expected                                                            float64
 		err                                                                 error
 	)
diff --git a/cmd/poller/plugin/test/plugin_test.go b/cmd/poller/plugin/test/plugin_test.go
index 66e694b7..e8819d9b 100644
--- a/cmd/poller/plugin/test/plugin_test.go
+++ b/cmd/poller/plugin/test/plugin_test.go
@@ -112,7 +112,7 @@ func TestMultipleRule(t *testing.T) {
 		t.Errorf("metric [status]: value for InstanceA is %s, expected %s", instanceA.GetLabel("B"), "xyz")
 	}
 
-	var status matrix.Metric
+	var status *matrix.Metric
 	var expected uint8
 	if status = m.GetMetric("new_status"); status == nil {
 		t.Error("metric [status] missing")
diff --git a/pkg/matrix/metric_float64_test.go b/pkg/matrix/metric_test.go
similarity index 64%
rename from pkg/matrix/metric_float64_test.go
rename to pkg/matrix/metric_test.go
index 3c1ae0fe..4042ec85 100644
--- a/pkg/matrix/metric_float64_test.go
+++ b/pkg/matrix/metric_test.go
@@ -58,6 +58,28 @@ func setupMatrix(previousRaw float64, currentRaw float64, mop matrixOp) (*Matrix
 	return m, m1
 }
 
+func setupMatrixAdv(previousRaw []float64, currentRaw []float64) (*Matrix, *Matrix) {
+	prevMat := New("Test", "test", "test")
+	averageLatency, _ := prevMat.NewMetricFloat64("average_latency")
+	totalOps, _ := prevMat.NewMetricFloat64("total_ops")
+	names := instanceNames[sameInstance]
+	for _, instanceName := range names.prev {
+		instance, _ := prevMat.NewInstance(instanceName)
+		_ = averageLatency.SetValueFloat64(instance, previousRaw[0])
+		_ = totalOps.SetValueFloat64(instance, previousRaw[1])
+	}
+
+	currentMat := New("Test", "test", "test")
+	averageLatency1, _ := currentMat.NewMetricFloat64("average_latency")
+	totalOps1, _ := currentMat.NewMetricFloat64("total_ops")
+	for _, instanceName := range names.cur {
+		instance, _ := currentMat.NewInstance(instanceName)
+		_ = averageLatency1.SetValueFloat64(instance, currentRaw[0])
+		_ = totalOps1.SetValueFloat64(instance, currentRaw[1])
+	}
+	return prevMat, currentMat
+}
+
 type test struct {
 	name      string
 	curRaw    float64
@@ -69,6 +91,16 @@ type test struct {
 	record    []bool
 }
 
+type testAdv struct {
+	name      string
+	curRaw    []float64
+	prevRaw   []float64
+	cooked    []float64
+	skips     int
+	threshold int
+	record    []bool
+}
+
 func TestMetricFloat64_Delta(t *testing.T) {
 	testDelta(t, sameInstance)
 	testDelta(t, deleteInstance)
@@ -141,14 +173,36 @@ func TestMetricFloat64_Divide(t *testing.T) {
 			matrixTest(t, tt, numerator, skips, err)
 		})
 	}
+
+	testsAdv := []testAdv{
+		{prevRaw: []float64{10, 5}, curRaw: []float64{20, 10}, cooked: []float64{2}, skips: 0, record: []bool{true}, name: "normal"},
+		{prevRaw: []float64{10, 5}, curRaw: []float64{20, 0}, cooked: []float64{10}, skips: 1, record: []bool{false}, name: "denominator record false"},
+		{prevRaw: []float64{10, 5}, curRaw: []float64{5, 10}, cooked: []float64{-5}, skips: 1, record: []bool{false}, name: "numerator record false"},
+		{prevRaw: []float64{10, 5}, curRaw: []float64{20, 0}, cooked: []float64{10}, skips: 1, record: []bool{false}, name: "numerator denominator record false"},
+	}
+
+	for _, tt := range testsAdv {
+		t.Run(tt.name, func(t *testing.T) {
+			prevMat, curMat := setupMatrixAdv(tt.prevRaw, tt.curRaw)
+			for k := range curMat.GetMetrics() {
+				_, err := curMat.GetMetric(k).Delta(prevMat.GetMetric(k), prevMat, curMat, logging.Get())
+				if err != nil {
+					t.Error("unexpected error", err)
+					return
+				}
+			}
+			skips, err := curMat.GetMetric("average_latency").Divide(curMat.GetMetric("total_ops"), logging.Get())
+			matrixTestAdv(t, tt, curMat, skips, err)
+		})
+	}
 }
 
 func TestMetricFloat64_DivideWithThreshold(t *testing.T) {
 	tests := []test{
 		{prevRaw: 20, curRaw: 10, threshold: 5, cooked: []float64{2}, skips: 0, record: []bool{true}, name: "normal"},
 		{prevRaw: 20, curRaw: 10, threshold: 15, cooked: []float64{0}, skips: 0, record: []bool{true}, name: "normal < threshold"},
-		{prevRaw: 20, curRaw: -10, threshold: 5, cooked: []float64{0}, skips: 1, record: []bool{false}, name: "bug negative den"},
-		{prevRaw: -20, curRaw: -10, threshold: 5, cooked: []float64{0}, skips: 1, record: []bool{false}, name: "bug negative both"},
+		{prevRaw: 20, curRaw: -10, threshold: 5, cooked: []float64{20}, skips: 1, record: []bool{false}, name: "bug negative den"},
+		{prevRaw: -20, curRaw: -10, threshold: 5, cooked: []float64{-20}, skips: 1, record: []bool{false}, name: "bug negative both"},
 		{prevRaw: 20, curRaw: 0, threshold: 0, cooked: []float64{math.Inf(1)}, skips: 0, record: []bool{true}, name: "allow no threshold"},
 		{prevRaw: 0, curRaw: 0, threshold: 5, cooked: []float64{0}, skips: 0, record: []bool{true}, name: "allow zero both"},
 	}
@@ -161,6 +215,28 @@ func TestMetricFloat64_DivideWithThreshold(t *testing.T) {
 			matrixTest(t, tt, numerator, skips, err)
 		})
 	}
+
+	testsAdv := []testAdv{
+		{prevRaw: []float64{10, 5}, curRaw: []float64{20, 10}, threshold: 1, cooked: []float64{2}, skips: 0, record: []bool{true}, name: "normal"},
+		{prevRaw: []float64{10, 5}, curRaw: []float64{20, 0}, threshold: 1, cooked: []float64{10}, skips: 1, record: []bool{false}, name: "denominator record false"},
+		{prevRaw: []float64{10, 5}, curRaw: []float64{5, 10}, threshold: 1, cooked: []float64{-5}, skips: 1, record: []bool{false}, name: "numerator record false"},
+		{prevRaw: []float64{10, 5}, curRaw: []float64{20, 0}, threshold: 1, cooked: []float64{10}, skips: 1, record: []bool{false}, name: "numerator denominator record false"},
+	}
+
+	for _, tt := range testsAdv {
+		t.Run(tt.name, func(t *testing.T) {
+			prevMat, curMat := setupMatrixAdv(tt.prevRaw, tt.curRaw)
+			for k := range curMat.GetMetrics() {
+				_, err := curMat.GetMetric(k).Delta(prevMat.GetMetric(k), prevMat, curMat, logging.Get())
+				if err != nil {
+					t.Error("unexpected error", err)
+					return
+				}
+			}
+			skips, err := curMat.GetMetric("average_latency").DivideWithThreshold(curMat.GetMetric("total_ops"), tt.threshold, logging.Get())
+			matrixTestAdv(t, tt, curMat, skips, err)
+		})
+	}
 }
 
 func TestMetricFloat64_MultiplyByScalar(t *testing.T) {
@@ -178,6 +254,30 @@ func TestMetricFloat64_MultiplyByScalar(t *testing.T) {
 	}
 }
 
+func matrixTestAdv(t *testing.T, tt testAdv, cur *Matrix, skips int, err error) {
+	if err != nil {
+		t.Error("unexpected error", err)
+		return
+	}
+	if skips != tt.skips {
+		t.Errorf("skips expected = %d, got %d", tt.skips, skips)
+	}
+
+	cooked := cur.GetMetric("average_latency").values
+	for i := range cooked {
+		if cooked[i] != tt.cooked[i] {
+			t.Errorf("cooked expected = %v, got %v", tt.cooked, cooked)
+		}
+	}
+
+	record := cur.GetMetric("average_latency").GetRecords()
+	for i := range record {
+		if record[i] != tt.record[i] {
+			t.Errorf("record expected = %t, got %t", tt.record, record)
+		}
+	}
+}
+
 func matrixTest(t *testing.T, tt test, cur *Matrix, skips int, err error) {
 	if err != nil {
 		t.Error("unexpected error", err)
@@ -186,7 +286,7 @@ func matrixTest(t *testing.T, tt test, cur *Matrix, skips int, err error) {
 	if skips != tt.skips {
 		t.Errorf("skips expected = %d, got %d", tt.skips, skips)
 	}
-	cooked := cur.GetMetric("speed").GetValuesFloat64()
+	cooked := cur.GetMetric("speed").values
 	for i := range cooked {
 		if cooked[i] != tt.cooked[i] {
 			t.Errorf("cooked expected = %v, got %v", tt.cooked, cooked)