From 5aa38e07871bcdf8b11292527548aadc2686ee0d Mon Sep 17 00:00:00 2001 From: rahulguptajss Date: Fri, 3 Apr 2026 14:35:15 +0530 Subject: [PATCH 1/5] feat: ESeries ssd-cache --- cmd/collectors/eseries/eseries.go | 3 + .../ssdcachecapacity/ssdcachecapacity.go | 365 ++ cmd/collectors/eseries/rest/client.go | 6 - cmd/collectors/eseries/rest/url_builder.go | 17 +- cmd/collectors/eseriesperf/eseriesperf.go | 60 +- .../eseriesperf/eseriesperf_test.go | 355 ++ .../plugins/ssdcachestats/ssdcachestats.go | 169 + .../ssdcachestats/ssdcachestats_test.go | 502 +++ .../eseriesperf/testdata/ssd_cache1.json | 54 + .../eseriesperf/testdata/ssd_cache2.json | 54 + .../testdata/ssd_cache_single_controller.json | 28 + .../testdata/ssd_cache_zero_io.json | 54 + cmd/tools/generate/eseries_counter.yaml | 270 ++ conf/eseries/11.80.0/ssd_cache.yaml | 30 + conf/eseries/default.yaml | 1 + conf/eseriesperf/11.80.0/ssd_cache.yaml | 37 + conf/eseriesperf/default.yaml | 1 + .../static_counter_definitions.yaml | 45 + docs/cisco-switch-metrics.md | 2 +- docs/eseries-metrics.md | 617 ++- docs/ontap-metrics.md | 2 +- docs/storagegrid-metrics.md | 2 +- grafana/dashboards/eseries/ssd_cache.json | 3892 +++++++++++++++++ mcp/metadata/eseries_metrics.json | 33 + 24 files changed, 6585 insertions(+), 14 deletions(-) create mode 100644 cmd/collectors/eseries/plugins/ssdcachecapacity/ssdcachecapacity.go create mode 100644 cmd/collectors/eseriesperf/plugins/ssdcachestats/ssdcachestats.go create mode 100644 cmd/collectors/eseriesperf/plugins/ssdcachestats/ssdcachestats_test.go create mode 100644 cmd/collectors/eseriesperf/testdata/ssd_cache1.json create mode 100644 cmd/collectors/eseriesperf/testdata/ssd_cache2.json create mode 100644 cmd/collectors/eseriesperf/testdata/ssd_cache_single_controller.json create mode 100644 cmd/collectors/eseriesperf/testdata/ssd_cache_zero_io.json create mode 100644 conf/eseries/11.80.0/ssd_cache.yaml create mode 100644 conf/eseriesperf/11.80.0/ssd_cache.yaml create mode 100644 grafana/dashboards/eseries/ssd_cache.json diff --git a/cmd/collectors/eseries/eseries.go b/cmd/collectors/eseries/eseries.go index 0a97914ef..01fb64cc1 100644 --- a/cmd/collectors/eseries/eseries.go +++ b/cmd/collectors/eseries/eseries.go @@ -7,6 +7,7 @@ import ( "github.com/netapp/harvest/v2/cmd/collectors/eseries/plugins/hardware" "github.com/netapp/harvest/v2/cmd/collectors/eseries/plugins/host" + "github.com/netapp/harvest/v2/cmd/collectors/eseries/plugins/ssdcachecapacity" "github.com/netapp/harvest/v2/cmd/collectors/eseries/plugins/volume" "github.com/netapp/harvest/v2/cmd/collectors/eseries/plugins/volumemapping" "github.com/netapp/harvest/v2/cmd/collectors/eseries/rest" @@ -393,6 +394,8 @@ func (e *ESeries) LoadPlugin(kind string, abc *plugin.AbstractPlugin) plugin.Plu return hardware.New(abc) case "Host": return host.New(abc) + case "SsdCacheCapacity": + return ssdcachecapacity.New(abc) case "Volume": return volume.New(abc) case "VolumeMapping": diff --git a/cmd/collectors/eseries/plugins/ssdcachecapacity/ssdcachecapacity.go b/cmd/collectors/eseries/plugins/ssdcachecapacity/ssdcachecapacity.go new file mode 100644 index 000000000..e373c4631 --- /dev/null +++ b/cmd/collectors/eseries/plugins/ssdcachecapacity/ssdcachecapacity.go @@ -0,0 +1,365 @@ +package ssdcachecapacity + +import ( + "fmt" + "log/slog" + "strconv" + "time" + + "github.com/netapp/harvest/v2/cmd/collectors/eseries/rest" + "github.com/netapp/harvest/v2/cmd/poller/plugin" + "github.com/netapp/harvest/v2/pkg/auth" + "github.com/netapp/harvest/v2/pkg/collector" + "github.com/netapp/harvest/v2/pkg/conf" + "github.com/netapp/harvest/v2/pkg/matrix" + "github.com/netapp/harvest/v2/pkg/slogx" + "github.com/netapp/harvest/v2/pkg/tree/node" + "github.com/netapp/harvest/v2/third_party/tidwall/gjson" +) + +const ( + volumeMatrixName = "eseries_ssd_cache_volume" + driveMatrixName = "eseries_ssd_cache_drive" +) + +type driveData struct { + location string + rawCapacity float64 +} + +type SsdCacheCapacity struct { + *plugin.AbstractPlugin + client *rest.Client + schedule int + volumeNames map[string]string + driveInfo map[string]driveData + volumeMat *matrix.Matrix + driveMat *matrix.Matrix + maxFlashCacheSize float64 +} + +func New(p *plugin.AbstractPlugin) plugin.Plugin { + return &SsdCacheCapacity{AbstractPlugin: p} +} + +func (s *SsdCacheCapacity) Init(remote conf.Remote) error { + if err := s.InitAbc(); err != nil { + return err + } + + timeout, _ := time.ParseDuration(rest.DefaultTimeout) + poller, err := conf.PollerNamed(s.Options.Poller) + if err != nil { + return err + } + + credentials := auth.NewCredentials(poller, s.SLogger) + if s.client, err = rest.New(poller, timeout, credentials, ""); err != nil { + return err + } + + if err := s.client.Init(1, remote); err != nil { + return err + } + + s.volumeNames = make(map[string]string) + s.driveInfo = make(map[string]driveData) + s.schedule = s.SetPluginInterval() + + s.initVolumeMatrix() + s.initDriveMatrix() + + return nil +} + +func (s *SsdCacheCapacity) initVolumeMatrix() { + mat := matrix.New(s.Parent+"."+volumeMatrixName, volumeMatrixName, volumeMatrixName) + exportOptions := node.NewS("export_options") + + instanceKeys := exportOptions.NewChildS("instance_keys", "") + instanceKeys.NewChildS("", "ssd_cache") + instanceKeys.NewChildS("", "ssd_cache_id") + instanceKeys.NewChildS("", "volume") + + instanceLabels := exportOptions.NewChildS("instance_labels", "") + instanceLabels.NewChildS("", "volume") + + mat.SetExportOptions(exportOptions) + + s.volumeMat = mat +} + +func (s *SsdCacheCapacity) initDriveMatrix() { + mat := matrix.New(s.Parent+"."+driveMatrixName, driveMatrixName, driveMatrixName) + exportOptions := node.NewS("export_options") + + instanceKeys := exportOptions.NewChildS("instance_keys", "") + instanceKeys.NewChildS("", "ssd_cache") + instanceKeys.NewChildS("", "ssd_cache_id") + instanceKeys.NewChildS("", "drive") + + instanceLabels := exportOptions.NewChildS("instance_labels", "") + instanceLabels.NewChildS("", "drive") + + mat.SetExportOptions(exportOptions) + _, _ = mat.NewMetricFloat64("raw_capacity") + + s.driveMat = mat +} + +func (s *SsdCacheCapacity) Run(dataMap map[string]*matrix.Matrix) ([]*matrix.Matrix, *collector.Metadata, error) { + data := dataMap[s.Object] + + arrayID := s.ParentParams.GetChildContentS("array_id") + if arrayID == "" { + s.SLogger.Warn("arrayID not found in ParentParams, skipping SSD cache capacity") + return nil, nil, nil + } + + if s.schedule >= s.PluginInvocationRate { + s.schedule = 0 + s.rebuildCaches(arrayID) + } + s.schedule++ + + globalLabels := data.GetGlobalLabels() + s.volumeMat.PurgeInstances() + s.volumeMat.Reset() + s.volumeMat.SetGlobalLabels(globalLabels) + + s.driveMat.PurgeInstances() + s.driveMat.Reset() + s.driveMat.SetGlobalLabels(globalLabels) + + s.populateMappings(data) + s.populateCapacityMetrics(data) + + totalInstances := len(s.volumeMat.GetInstances()) + len(s.driveMat.GetInstances()) + s.SLogger.Debug("SsdCacheCapacity plugin", + slog.Int("volumes", len(s.volumeMat.GetInstances())), + slog.Int("drives", len(s.driveMat.GetInstances())), + ) + + metadata := &collector.Metadata{} + //nolint:gosec + metadata.PluginInstances = uint64(totalInstances) + + return []*matrix.Matrix{s.volumeMat, s.driveMat}, metadata, nil +} + +func (s *SsdCacheCapacity) rebuildCaches(arrayID string) { + s.volumeNames = make(map[string]string) + s.driveInfo = make(map[string]driveData) + + if err := s.buildVolumeCache(arrayID); err != nil { + s.SLogger.Warn("Failed to build volume cache", slogx.Err(err)) + } + + if err := s.buildDriveCache(arrayID); err != nil { + s.SLogger.Warn("Failed to build drive cache", slogx.Err(err)) + } + + if err := s.buildCapabilitiesCache(arrayID); err != nil { + s.SLogger.Warn("Failed to build capabilities cache", slogx.Err(err)) + } +} + +func (s *SsdCacheCapacity) buildCapabilitiesCache(arrayID string) error { + apiPath := s.client.APIPath + "/storage-systems/" + arrayID + "/capabilities" + results, err := s.client.Fetch(apiPath, nil) + if err != nil { + return fmt.Errorf("failed to fetch capabilities: %w", err) + } + + if len(results) == 0 { + return fmt.Errorf("empty capabilities response for array %s", arrayID) + } + + maxFlashCacheSizeStr := results[0].Get("featureParameters.maxFlashCacheSize").ClonedString() + if maxFlashCacheSizeStr == "" { + s.SLogger.Warn("featureParameters.maxFlashCacheSize not found in capabilities response", + slog.String("array_id", arrayID)) + return nil + } + + v, parseErr := strconv.ParseFloat(maxFlashCacheSizeStr, 64) + if parseErr != nil { + s.SLogger.Warn("Failed to parse maxFlashCacheSize", + slog.String("value", maxFlashCacheSizeStr), slogx.Err(parseErr)) + return nil + } + + s.maxFlashCacheSize = v + s.SLogger.Debug("Built capabilities cache", + slog.Float64("maxFlashCacheSize", v)) + return nil +} + +func (s *SsdCacheCapacity) buildVolumeCache(arrayID string) error { + apiPath := s.client.APIPath + "/storage-systems/" + arrayID + "/volumes" + volumes, err := s.client.Fetch(apiPath, nil) + if err != nil { + return fmt.Errorf("failed to fetch volumes: %w", err) + } + + for _, vol := range volumes { + id := vol.Get("id").ClonedString() + if id == "" { + id = vol.Get("volumeRef").ClonedString() + } + name := vol.Get("name").ClonedString() + if id != "" && name != "" { + s.volumeNames[id] = name + } + } + + s.SLogger.Debug("Built volume cache", slog.Int("count", len(s.volumeNames))) + return nil +} + +func (s *SsdCacheCapacity) buildDriveCache(arrayID string) error { + apiPath := s.client.APIPath + "/storage-systems/" + arrayID + "/drives" + drives, err := s.client.Fetch(apiPath, nil) + if err != nil { + return fmt.Errorf("failed to fetch drives: %w", err) + } + + for _, drive := range drives { + id := drive.Get("id").ClonedString() + if id == "" { + id = drive.Get("driveRef").ClonedString() + } + if id == "" { + continue + } + + location := drive.Get("physicalLocation.label").ClonedString() + if location == "" { + slot := drive.Get("physicalLocation.slot").ClonedString() + if slot != "" { + location = "Slot " + slot + } else { + location = id + } + } + + var rawCap float64 + if capStr := drive.Get("rawCapacity").ClonedString(); capStr != "" { + if v, parseErr := strconv.ParseFloat(capStr, 64); parseErr == nil { + rawCap = v + } + } + + s.driveInfo[id] = driveData{location: location, rawCapacity: rawCap} + } + + s.SLogger.Debug("Built drive cache", slog.Int("count", len(s.driveInfo))) + return nil +} + +func (s *SsdCacheCapacity) populateMappings(data *matrix.Matrix) { + driveCapMetric := s.driveMat.GetMetric("raw_capacity") + + for cacheID, cacheInstance := range data.GetInstances() { + cacheName := cacheInstance.GetLabel("ssd_cache") + if cacheName == "" { + cacheName = cacheID + } + + volumeIDsJSON := cacheInstance.GetLabel("cached_volume_ids") + if volumeIDsJSON != "" && volumeIDsJSON != "[]" { + parsed := gjson.Parse(volumeIDsJSON) + parsed.ForEach(func(_, vol gjson.Result) bool { + volID := vol.ClonedString() + if volID == "" { + return true + } + volName := volID + if n, ok := s.volumeNames[volID]; ok { + volName = n + } + instKey := cacheID + "_" + volID + inst, err := s.volumeMat.NewInstance(instKey) + if err != nil { + s.SLogger.Error("Failed to create volume instance", slogx.Err(err), slog.String("key", instKey)) + return true + } + inst.SetLabelTrimmed("ssd_cache", cacheName) + inst.SetLabelTrimmed("ssd_cache_id", cacheID) + inst.SetLabelTrimmed("volume", volName) + return true + }) + } + + driveIDsJSON := cacheInstance.GetLabel("drive_ids") + if driveIDsJSON != "" && driveIDsJSON != "[]" { + parsed := gjson.Parse(driveIDsJSON) + parsed.ForEach(func(_, drv gjson.Result) bool { + driveID := drv.ClonedString() + if driveID == "" { + return true + } + location := driveID + var rawCap float64 + if info, ok := s.driveInfo[driveID]; ok { + location = info.location + rawCap = info.rawCapacity + } + instKey := cacheID + "_" + driveID + inst, err := s.driveMat.NewInstance(instKey) + if err != nil { + s.SLogger.Error("Failed to create drive instance", slogx.Err(err), slog.String("key", instKey)) + return true + } + inst.SetLabelTrimmed("ssd_cache", cacheName) + inst.SetLabelTrimmed("ssd_cache_id", cacheID) + inst.SetLabelTrimmed("drive", location) + if driveCapMetric != nil { + driveCapMetric.SetValueFloat64(inst, rawCap) + } + return true + }) + } + } +} + +func (s *SsdCacheCapacity) populateCapacityMetrics(data *matrix.Matrix) { + if s.maxFlashCacheSize == 0 { + s.SLogger.Debug("maxFlashCacheSize is 0, skipping capacity metrics") + return + } + + maxCapMetric := data.GetMetric("max_capacity") + if maxCapMetric == nil { + var err error + maxCapMetric, err = data.NewMetricFloat64("max_capacity") + if err != nil { + s.SLogger.Error("Failed to create max_capacity metric", slogx.Err(err)) + return + } + } + + additionalCapMetric := data.GetMetric("additional_capacity") + if additionalCapMetric == nil { + var err error + additionalCapMetric, err = data.NewMetricFloat64("additional_capacity") + if err != nil { + s.SLogger.Error("Failed to create additional_capacity metric", slogx.Err(err)) + return + } + } + + usedCapMetric := data.GetMetric("usedCapacity") + + for _, instance := range data.GetInstances() { + maxCapMetric.SetValueFloat64(instance, s.maxFlashCacheSize) + + var usedCap float64 + if usedCapMetric != nil { + if v, ok := usedCapMetric.GetValueFloat64(instance); ok { + usedCap = v + } + } + additionalCapMetric.SetValueFloat64(instance, s.maxFlashCacheSize-usedCap) + } +} diff --git a/cmd/collectors/eseries/rest/client.go b/cmd/collectors/eseries/rest/client.go index f0850c0f6..6bdbe20bf 100644 --- a/cmd/collectors/eseries/rest/client.go +++ b/cmd/collectors/eseries/rest/client.go @@ -262,12 +262,6 @@ func (c *Client) get(endpoint string, headers ...map[string]string) ([]gjson.Res } if res.StatusCode != http.StatusOK { - c.Logger.Error( - "API request failed", - slog.Int("status", res.StatusCode), - slog.String("url", url), - slog.String("body", string(innerBody)), - ) return nil, errs.NewRest(). StatusCode(res.StatusCode). API(endpoint). diff --git a/cmd/collectors/eseries/rest/url_builder.go b/cmd/collectors/eseries/rest/url_builder.go index 26a3464b1..6203f4cbe 100644 --- a/cmd/collectors/eseries/rest/url_builder.go +++ b/cmd/collectors/eseries/rest/url_builder.go @@ -5,9 +5,10 @@ import ( ) type URLBuilder struct { - apiPath string - arrayID string - filters []string + apiPath string + arrayID string + ssdCacheID string + filters []string } func NewURLBuilder() *URLBuilder { @@ -24,6 +25,11 @@ func (b *URLBuilder) ArrayID(arrayID string) *URLBuilder { return b } +func (b *URLBuilder) SsdCacheID(ssdCacheID string) *URLBuilder { + b.ssdCacheID = ssdCacheID + return b +} + func (b *URLBuilder) Filter(filters []string) *URLBuilder { b.filters = filters return b @@ -37,6 +43,11 @@ func (b *URLBuilder) Build() string { url = strings.ReplaceAll(url, "{array_id}", b.arrayID) } + // Replace {ssd_cache_id} placeholder if ssdCacheID is set + if b.ssdCacheID != "" { + url = strings.ReplaceAll(url, "{ssd_cache_id}", b.ssdCacheID) + } + if len(b.filters) > 0 { separator := "?" if strings.Contains(url, "?") { diff --git a/cmd/collectors/eseriesperf/eseriesperf.go b/cmd/collectors/eseriesperf/eseriesperf.go index a286809ee..8d401f778 100644 --- a/cmd/collectors/eseriesperf/eseriesperf.go +++ b/cmd/collectors/eseriesperf/eseriesperf.go @@ -1,6 +1,7 @@ package eseriesperf import ( + "fmt" "log/slog" "os" "strconv" @@ -11,6 +12,7 @@ import ( "github.com/netapp/harvest/v2/cmd/collectors/eseries/rest" "github.com/netapp/harvest/v2/cmd/collectors/eseriesperf/plugins/cachehitratio" "github.com/netapp/harvest/v2/cmd/collectors/eseriesperf/plugins/controller" + "github.com/netapp/harvest/v2/cmd/collectors/eseriesperf/plugins/ssdcachestats" "github.com/netapp/harvest/v2/cmd/poller/collector" "github.com/netapp/harvest/v2/cmd/poller/plugin" "github.com/netapp/harvest/v2/pkg/conf" @@ -26,6 +28,7 @@ type EseriesPerf struct { perfProp *perfProp pollDataCalls int recordsToSave int + ssdCacheID string } type counter struct { @@ -181,6 +184,16 @@ func (ep *EseriesPerf) buildCounters() { } } + // Fallback: look for timestamp metric (e.g., statistics.timestamp for SSD cache) + if timestampMetric == "" { + for metricName := range ep.Prop.Metrics { + if strings.HasSuffix(metricName, "timestamp") { + timestampMetric = metricName + break + } + } + } + if timestampMetric != "" { if _, exists := ep.Prop.Metrics[timestampMetric]; !exists { ep.Prop.Metrics[timestampMetric] = &eseries.Metric{ @@ -209,7 +222,7 @@ func (ep *EseriesPerf) buildCounters() { } // Handle timestamp metric - if strings.Contains(k, "observedTimeInMS") { + if strings.Contains(k, "observedTimeInMS") || k == timestampMetric { ep.perfProp.timestampMetricName = k } @@ -243,6 +256,8 @@ func (ep *EseriesPerf) LoadPlugin(kind string, p *plugin.AbstractPlugin) plugin. return cachehitratio.New(p) case "Controller": return controller.New(p) + case "SsdCacheStats": + return ssdcachestats.New(p) default: ep.Logger.Info("No eseries plugin found", slog.String("kind", kind)) } @@ -253,6 +268,41 @@ func (ep *EseriesPerf) PollCounter() (map[string]*matrix.Matrix, error) { return ep.ESeries.PollCounter() } +// discoverSsdCacheID fetches the SSD cache ID from the E-Series system. +// There can only be one SSD cache per E-Series system. +// Called every PollData cycle to handle SSD cache creation/removal. +func (ep *EseriesPerf) discoverSsdCacheID() error { + systemID := ep.GetArray() + endpoint := fmt.Sprintf("%s/storage-systems/%s/ssd-caches", ep.Client.APIPath, systemID) + + results, err := ep.Client.Fetch(endpoint, nil) + if err != nil { + ep.ssdCacheID = "" + return fmt.Errorf("failed to fetch ssd-caches: %w", err) + } + + if len(results) == 0 { + ep.ssdCacheID = "" + return errs.New(errs.ErrNoInstance, "no SSD cache found") + } + + ep.ssdCacheID = results[0].Get("id").ClonedString() + if ep.ssdCacheID == "" { + return errs.New(errs.ErrNoInstance, "SSD cache missing id") + } + + cacheName := results[0].Get("name").ClonedString() + ep.Logger.Debug("discovered SSD cache", + slog.String("ssdCacheID", ep.ssdCacheID), + slog.String("name", cacheName)) + + mat := ep.Matrix[ep.Object] + mat.SetGlobalLabel("ssd_cache_id", ep.ssdCacheID) + mat.SetGlobalLabel("ssd_cache", cacheName) + + return nil +} + func (ep *EseriesPerf) PollData() (map[string]*matrix.Matrix, error) { var ( count uint64 @@ -292,6 +342,13 @@ func (ep *EseriesPerf) PollData() (map[string]*matrix.Matrix, error) { systemID := ep.GetArray() + objType := ep.Params.GetChildContentS("type") + if objType == "ssd_cache" { + if err := ep.discoverSsdCacheID(); err != nil { + return nil, err + } + } + // Build query - filters are intentionally disabled when using shared cache // to prevent cache poisoning (subset of filtered data being cached for all consumers) // See applyFilter() in template.go for the filter-disabling logic @@ -300,6 +357,7 @@ func (ep *EseriesPerf) PollData() (map[string]*matrix.Matrix, error) { query := rest.NewURLBuilder(). APIPath(ep.Prop.Query). ArrayID(systemID). + SsdCacheID(ep.ssdCacheID). Filter(filters). Build() diff --git a/cmd/collectors/eseriesperf/eseriesperf_test.go b/cmd/collectors/eseriesperf/eseriesperf_test.go index e9c1e4b56..40e2e2b8b 100644 --- a/cmd/collectors/eseriesperf/eseriesperf_test.go +++ b/cmd/collectors/eseriesperf/eseriesperf_test.go @@ -73,6 +73,15 @@ func jsonToPerfData(path string) []gjson.Result { return []gjson.Result{result} } +func jsonToArrayPerfData(path string) []gjson.Result { + data, err := os.ReadFile(path) + if err != nil { + panic(err) + } + parsed := gjson.ParseBytes(data) + return parsed.Array() +} + func TestEseriesPerf_Init(t *testing.T) { tests := []struct { name string @@ -507,3 +516,349 @@ func TestEseriesPerf_MultipleObjectTypes(t *testing.T) { }) } } + +func TestEseriesPerf_Init_SsdCache(t *testing.T) { + ep := newEseriesPerf("SsdCache", "ssd_cache.yaml") + + assert.NotNil(t, ep) + assert.NotNil(t, ep.perfProp) + assert.NotNil(t, ep.perfProp.counterInfo) + assert.True(t, len(ep.perfProp.counterInfo) > 0) + + // Timestamp metric should be statistics.timestamp (not observedTimeInMS) + if ep.perfProp.timestampMetricName != "statistics.timestamp" { + t.Errorf("timestampMetricName = %q, want %q", ep.perfProp.timestampMetricName, "statistics.timestamp") + } + + // SSD cache should not have utilization calculation (that's for Drive) + assert.False(t, ep.perfProp.calculateUtilization) +} + +func TestEseriesPerf_buildCounters_SsdCache(t *testing.T) { + ep := newEseriesPerf("SsdCache", "ssd_cache.yaml") + + numCounters := len(ep.perfProp.counterInfo) + // 22 counters: 1 delta (timestamp), 16 rate, 4 raw + 1 auto-added timestamp + if numCounters < 21 { + t.Errorf("expected at least 21 counters, got %d", numCounters) + } + + var rateCount, deltaCount, rawCount int + for _, ctr := range ep.perfProp.counterInfo { + switch ctr.counterType { + case "rate": + rateCount++ + case "delta": + deltaCount++ + case "raw": + rawCount++ + case "average", "percent": + t.Errorf("unexpected counter type %q for SSD cache counter %s", ctr.counterType, ctr.name) + } + + // SSD cache counters should have no denominators (no average/percent types) + if ctr.denominator != "" { + t.Errorf("counter %s should have no denominator, got %q", ctr.name, ctr.denominator) + } + } + + if rateCount < 16 { + t.Errorf("expected at least 16 rate counters, got %d", rateCount) + } + if deltaCount < 1 { + t.Errorf("expected at least 1 delta counter (timestamp), got %d", deltaCount) + } + if rawCount < 4 { + t.Errorf("expected at least 4 raw counters (byte metrics), got %d", rawCount) + } +} + +func TestEseriesPerf_PollData_SsdCache(t *testing.T) { + ep := newEseriesPerf("SsdCache", "ssd_cache.yaml") + + mat := ep.Matrix[ep.Object] + mat.SetGlobalLabel("array_id", "test-system") + mat.SetGlobalLabel("array", "test") + + // First poll — establishes baseline + pollData1 := jsonToArrayPerfData("testdata/ssd_cache1.json") + count1, partial1 := ep.pollData(mat, pollData1, set.New()) + + assert.True(t, count1 > 0) + assert.Equal(t, len(mat.GetInstances()), 2) // 2 controllers + assert.Equal(t, partial1, uint64(0)) + + // First cookCounters returns nil (cache empty) + got, err := ep.cookCounters(mat, mat) + assert.Nil(t, err) + assert.Nil(t, got) + assert.False(t, ep.perfProp.isCacheEmpty) + + // Second poll + pollData2 := jsonToArrayPerfData("testdata/ssd_cache2.json") + prevMat := mat.Clone(matrix.With{Data: true, Metrics: true, Instances: true, ExportInstances: true}) + curMat := prevMat.Clone(matrix.With{Data: false, Metrics: true, Instances: true, ExportInstances: true}) + curMat.Reset() + + count2, partial2 := ep.pollData(curMat, pollData2, set.New()) + assert.True(t, count2 > 0) + assert.Equal(t, partial2, uint64(0)) + + // Cook counters — should now produce results + got, err = ep.cookCounters(curMat, prevMat) + assert.Nil(t, err) + assert.NotNil(t, got) + + resultMat := got[ep.Object] + assert.NotNil(t, resultMat) + + // Verify rate metrics for controller 1 + // Delta: reads=3852, timestamp=72 → rate=53.5/s + ctrl1 := resultMat.GetInstance("070000000000000000000001") + if ctrl1 == nil { + t.Fatal("controller 1 instance not found") + } + + readOps := resultMat.GetMetric("statistics.reads") + if readOps == nil { + t.Fatal("statistics.reads metric not found") + } + + readOpsVal, readOpsOk := readOps.GetValueFloat64(ctrl1) + if !readOpsOk { + t.Fatal("could not get read_ops value for controller 1") + } + // Delta: reads=3852, timestamp=72 + assert.Equal(t, readOpsVal, 3852.0/72.0) + + writeOps := resultMat.GetMetric("statistics.writes") + if writeOps != nil { + writeOpsVal, ok := writeOps.GetValueFloat64(ctrl1) + if ok { + // Delta: writes=1717, timestamp=72 + assert.Equal(t, writeOpsVal, 1717.0/72.0) + } + } + + // Verify rate for controller 2 + ctrl2 := resultMat.GetInstance("070000000000000000000002") + if ctrl2 == nil { + t.Fatal("controller 2 instance not found") + } + + readOpsVal2, readOpsOk2 := readOps.GetValueFloat64(ctrl2) + if !readOpsOk2 { + t.Fatal("could not get read_ops value for controller 2") + } + // Delta: reads=3965, timestamp=71 + assert.Equal(t, readOpsVal2, 3965.0/71.0) + + // Verify raw metrics are NOT transformed (current-value passthrough) + availableBytes := resultMat.GetMetric("statistics.availableBytes") + if availableBytes != nil { + val, ok := availableBytes.GetValueFloat64(ctrl1) + if ok { + assert.Equal(t, val, 1599784091648.0) + } + } + + // Verify timestamp metric is NOT exportable + timestamp := resultMat.GetMetric("statistics.timestamp") + if timestamp != nil { + assert.False(t, timestamp.IsExportable()) + } + + // Verify instances are exportable + for _, inst := range resultMat.GetInstances() { + if !inst.IsExportable() { + t.Error("instance should be exportable") + } + } +} + +func TestEseriesPerf_SsdCache_NoTimestampConversion(t *testing.T) { + ep := newEseriesPerf("SsdCache", "ssd_cache.yaml") + + mat := ep.Matrix[ep.Object] + mat.SetGlobalLabel("array_id", "test-system") + mat.SetGlobalLabel("array", "test") + + pollData := jsonToArrayPerfData("testdata/ssd_cache1.json") + ep.pollData(mat, pollData, set.New()) + + // statistics.timestamp values are already in seconds (not milliseconds) + // They should be stored as-is, not divided by 1000 + timestampMetric := mat.GetMetric("statistics.timestamp") + if timestampMetric == nil { + t.Skip("timestamp metric not found") + } + + for _, instance := range mat.GetInstances() { + value, ok := timestampMetric.GetValueFloat64(instance) + if !ok { + continue + } + // Original value: 1773809538 (seconds since epoch) + // Should NOT be divided by 1000 — these are already seconds + if value < 1000000000.0 { + t.Errorf("timestamp should be >= 1e9 (seconds), got %f — appears to have been divided", value) + } + assert.Equal(t, value, 1773809538.0) + } +} + +func TestEseriesPerf_SsdCache_ZeroIO(t *testing.T) { + ep := newEseriesPerf("SsdCache", "ssd_cache.yaml") + + mat := ep.Matrix[ep.Object] + mat.SetGlobalLabel("array_id", "test-system") + mat.SetGlobalLabel("array", "test") + + // Both polls use zero I/O data — deltas will be 0 + pollData1 := jsonToArrayPerfData("testdata/ssd_cache_zero_io.json") + ep.pollData(mat, pollData1, set.New()) + _, _ = ep.cookCounters(mat, mat) + + prevMat := mat.Clone(matrix.With{Data: true, Metrics: true, Instances: true, ExportInstances: true}) + curMat := prevMat.Clone(matrix.With{Data: false, Metrics: true, Instances: true, ExportInstances: true}) + curMat.Reset() + + pollData2 := jsonToArrayPerfData("testdata/ssd_cache_zero_io.json") + ep.pollData(curMat, pollData2, set.New()) + + // Should not panic with zero deltas / zero denominators + _, err := ep.cookCounters(curMat, prevMat) + assert.Nil(t, err) +} + +func TestEseriesPerf_SsdCache_SingleController(t *testing.T) { + ep := newEseriesPerf("SsdCache", "ssd_cache.yaml") + + mat := ep.Matrix[ep.Object] + mat.SetGlobalLabel("array_id", "test-system") + mat.SetGlobalLabel("array", "test") + + pollData := jsonToArrayPerfData("testdata/ssd_cache_single_controller.json") + count, _ := ep.pollData(mat, pollData, set.New()) + + assert.True(t, count > 0) + assert.Equal(t, len(mat.GetInstances()), 1) +} + +func TestEseriesPerf_SsdCache_NegativeDelta(t *testing.T) { + ep := newEseriesPerf("SsdCache", "ssd_cache.yaml") + + mat := ep.Matrix[ep.Object] + mat.SetGlobalLabel("array_id", "test-system") + mat.SetGlobalLabel("array", "test") + + // Load higher counters first (poll2), then lower counters (poll1) + // This simulates a counter reset scenario + pollData2 := jsonToArrayPerfData("testdata/ssd_cache2.json") + ep.pollData(mat, pollData2, set.New()) + _, _ = ep.cookCounters(mat, mat) + + prevMat := mat.Clone(matrix.With{Data: true, Metrics: true, Instances: true, ExportInstances: true}) + curMat := prevMat.Clone(matrix.With{Data: false, Metrics: true, Instances: true, ExportInstances: true}) + curMat.Reset() + + // Now poll with lower values — simulates counter reset + pollData1 := jsonToArrayPerfData("testdata/ssd_cache1.json") + ep.pollData(curMat, pollData1, set.New()) + + got, err := ep.cookCounters(curMat, prevMat) + assert.Nil(t, err) + + if got != nil { + resultMat := got[ep.Object] + if resultMat != nil { + for _, inst := range resultMat.GetInstances() { + readOps := resultMat.GetMetric("statistics.reads") + if readOps != nil { + val, ok := readOps.GetValueFloat64(inst) + if ok && val < 0 { + t.Error("should not have negative rate values after delta protection") + } + } + } + } + } +} + +func TestEseriesPerf_SsdCache_NegativeDelta_SkipOnCounterReset(t *testing.T) { + ep := newEseriesPerf("SsdCache", "ssd_cache.yaml") + + mat := ep.Matrix[ep.Object] + mat.SetGlobalLabel("array_id", "test-system") + mat.SetGlobalLabel("array", "test") + + pollData1 := jsonToArrayPerfData("testdata/ssd_cache1.json") + count1, partial1 := ep.pollData(mat, pollData1, set.New()) + assert.True(t, count1 > 0) + assert.Equal(t, len(mat.GetInstances()), 2) + assert.Equal(t, partial1, uint64(0)) + + got, err := ep.cookCounters(mat, mat) + assert.Nil(t, err) + assert.Nil(t, got) + assert.False(t, ep.perfProp.isCacheEmpty) + + prevMat := mat.Clone(matrix.With{Data: true, Metrics: true, Instances: true, ExportInstances: true}) + curMat := prevMat.Clone(matrix.With{Data: false, Metrics: true, Instances: true, ExportInstances: true}) + curMat.Reset() + + pollData2 := jsonToArrayPerfData("testdata/ssd_cache_zero_io.json") + count2, partial2 := ep.pollData(curMat, pollData2, set.New()) + assert.True(t, count2 > 0) + assert.Equal(t, partial2, uint64(0)) + + got, err = ep.cookCounters(curMat, prevMat) + assert.Nil(t, err) + + resultMat := got[ep.Object] + + skippedMetrics := 0 + for _, metricKey := range []string{"statistics.reads", "statistics.writes", "statistics.fullCacheHits", "statistics.partialCacheHits"} { + m := resultMat.GetMetric(metricKey) + if m == nil { + continue + } + for _, inst := range resultMat.GetInstances() { + _, ok := m.GetValueFloat64(inst) + if !ok { + skippedMetrics++ + } + } + } + if skippedMetrics == 0 { + t.Error("expected rate metrics to have no recorded value (skipped) after negative delta from counter reset to zero") + } + + for _, metricKey := range []string{"statistics.reads", "statistics.writes", "statistics.fullCacheHits"} { + m := resultMat.GetMetric(metricKey) + if m == nil { + continue + } + for _, inst := range resultMat.GetInstances() { + val, ok := m.GetValueFloat64(inst) + if ok && val < 0 { + t.Errorf("metric %s has negative value %f — negative delta must be suppressed", metricKey, val) + } + } + } + + // Raw metrics (byte values) are NOT delta-ed — they pass through unchanged + // and must still have valid values after the negative delta cycle. + availBytes := resultMat.GetMetric("statistics.availableBytes") + if availBytes != nil { + for _, inst := range resultMat.GetInstances() { + val, ok := availBytes.GetValueFloat64(inst) + if !ok { + t.Error("raw metric statistics.availableBytes should still have a recorded value") + } + if val <= 0 { + t.Errorf("statistics.availableBytes should be positive, got %f", val) + } + } + } +} diff --git a/cmd/collectors/eseriesperf/plugins/ssdcachestats/ssdcachestats.go b/cmd/collectors/eseriesperf/plugins/ssdcachestats/ssdcachestats.go new file mode 100644 index 000000000..67b605a14 --- /dev/null +++ b/cmd/collectors/eseriesperf/plugins/ssdcachestats/ssdcachestats.go @@ -0,0 +1,169 @@ +package ssdcachestats + +import ( + "log/slog" + + "github.com/netapp/harvest/v2/cmd/poller/plugin" + "github.com/netapp/harvest/v2/pkg/collector" + "github.com/netapp/harvest/v2/pkg/conf" + "github.com/netapp/harvest/v2/pkg/matrix" +) + +type SsdCacheStats struct { + *plugin.AbstractPlugin +} + +func New(p *plugin.AbstractPlugin) plugin.Plugin { + return &SsdCacheStats{AbstractPlugin: p} +} + +func (s *SsdCacheStats) Init(_ conf.Remote) error { + return s.InitAbc() +} + +func (s *SsdCacheStats) Run(dataMap map[string]*matrix.Matrix) ([]*matrix.Matrix, *collector.Metadata, error) { + data := dataMap[s.Object] + if data == nil { + s.SLogger.Debug("No data matrix available") + return nil, nil, nil + } + + s.calculateCacheHitPercent(data) + s.calculateCacheAllocationPercent(data) + s.calculateCacheUtilizationPercent(data) + + s.computePercent(data, "statistics.fullCacheHits", "statistics.reads", "full_cache_hit_percent") + s.computePercent(data, "statistics.partialCacheHits", "statistics.reads", "partial_cache_hit_percent") + s.computePercent(data, "statistics.completeCacheMiss", "statistics.reads", "complete_cache_miss_percent") + s.computePercent(data, "statistics.populateOnReads", "statistics.reads", "populate_on_read_percent") + + s.computePercent(data, "statistics.fullCacheHitBlocks", "statistics.readBlocks", "full_cache_hit_block_percent") + s.computePercent(data, "statistics.partialCacheHitBlocks", "statistics.readBlocks", "partial_cache_hit_block_percent") + s.computePercent(data, "statistics.completeCacheMissBlocks", "statistics.readBlocks", "complete_cache_miss_block_percent") + s.computePercent(data, "statistics.populateOnReadBlocks", "statistics.readBlocks", "populate_on_read_block_percent") + + s.computePercent(data, "statistics.populateOnWrites", "statistics.writes", "populate_on_write_percent") + s.computePercent(data, "statistics.invalidates", "statistics.writes", "invalidate_percent") + + s.computePercent(data, "statistics.populateOnWriteBlocks", "statistics.writeBlocks", "populate_on_write_block_percent") + + return nil, nil, nil +} + +func (s *SsdCacheStats) computePercent(data *matrix.Matrix, numeratorKey, denominatorKey, resultName string) { + numerator := data.GetMetric(numeratorKey) + denominator := data.GetMetric(denominatorKey) + + if numerator == nil || denominator == nil { + return + } + + result, err := data.NewMetricFloat64(resultName) + if err != nil { + s.SLogger.Warn("Failed to create metric", slog.String("metric", resultName), slog.String("error", err.Error())) + return + } + + for _, instance := range data.GetInstances() { + numVal, numOk := numerator.GetValueFloat64(instance) + denVal, denOk := denominator.GetValueFloat64(instance) + + if numOk && denOk && denVal > 0 { + result.SetValueFloat64(instance, (numVal/denVal)*100.0) + } + } +} + +// calculateCacheHitPercent computes cache hit percentage: +// fullCacheHits / (reads + writes) * 100 +func (s *SsdCacheStats) calculateCacheHitPercent(data *matrix.Matrix) { + reads := data.GetMetric("statistics.reads") + writes := data.GetMetric("statistics.writes") + fullCacheHits := data.GetMetric("statistics.fullCacheHits") + + if reads == nil || writes == nil || fullCacheHits == nil { + s.SLogger.Debug("Missing metrics for cache hit percent") + return + } + + cacheHitPct, err := data.NewMetricFloat64("hit_percent") + if err != nil { + s.SLogger.Warn("Failed to create hit_percent metric", slog.String("error", err.Error())) + return + } + + for _, instance := range data.GetInstances() { + readsVal, readsOk := reads.GetValueFloat64(instance) + writesVal, writesOk := writes.GetValueFloat64(instance) + fullHitsVal, fullOk := fullCacheHits.GetValueFloat64(instance) + + if readsOk && writesOk && fullOk { + totalIOs := readsVal + writesVal + if totalIOs > 0 { + pct := (fullHitsVal / totalIOs) * 100.0 + cacheHitPct.SetValueFloat64(instance, pct) + } + } + } +} + +// calculateCacheAllocationPercent computes cache allocation percentage: +// allocatedBytes / (allocatedBytes + availableBytes) * 100 +func (s *SsdCacheStats) calculateCacheAllocationPercent(data *matrix.Matrix) { + allocated := data.GetMetric("statistics.allocatedBytes") + available := data.GetMetric("statistics.availableBytes") + + if allocated == nil || available == nil { + s.SLogger.Debug("Missing metrics for cache allocation percent") + return + } + + allocPct, err := data.NewMetricFloat64("allocation_percent") + if err != nil { + s.SLogger.Warn("Failed to create allocation_percent metric", slog.String("error", err.Error())) + return + } + + for _, instance := range data.GetInstances() { + allocVal, allocOk := allocated.GetValueFloat64(instance) + availVal, availOk := available.GetValueFloat64(instance) + + if allocOk && availOk { + total := allocVal + availVal + if total > 0 { + pct := (allocVal / total) * 100.0 + allocPct.SetValueFloat64(instance, pct) + } + } + } +} + +// calculateCacheUtilizationPercent computes cache utilization percentage: +// (populatedCleanBytes + populatedDirtyBytes) / allocatedBytes * 100 +func (s *SsdCacheStats) calculateCacheUtilizationPercent(data *matrix.Matrix) { + clean := data.GetMetric("statistics.populatedCleanBytes") + dirty := data.GetMetric("statistics.populatedDirtyBytes") + allocated := data.GetMetric("statistics.allocatedBytes") + + if clean == nil || dirty == nil || allocated == nil { + s.SLogger.Debug("Missing metrics for cache utilization percent") + return + } + + utilPct, err := data.NewMetricFloat64("utilization_percent") + if err != nil { + s.SLogger.Warn("Failed to create utilization_percent metric", slog.String("error", err.Error())) + return + } + + for _, instance := range data.GetInstances() { + cleanVal, cleanOk := clean.GetValueFloat64(instance) + dirtyVal, dirtyOk := dirty.GetValueFloat64(instance) + allocVal, allocOk := allocated.GetValueFloat64(instance) + + if cleanOk && dirtyOk && allocOk && allocVal > 0 { + pct := ((cleanVal + dirtyVal) / allocVal) * 100.0 + utilPct.SetValueFloat64(instance, pct) + } + } +} diff --git a/cmd/collectors/eseriesperf/plugins/ssdcachestats/ssdcachestats_test.go b/cmd/collectors/eseriesperf/plugins/ssdcachestats/ssdcachestats_test.go new file mode 100644 index 000000000..c2c8dfa15 --- /dev/null +++ b/cmd/collectors/eseriesperf/plugins/ssdcachestats/ssdcachestats_test.go @@ -0,0 +1,502 @@ +package ssdcachestats + +import ( + "testing" + + "github.com/netapp/harvest/v2/assert" + "github.com/netapp/harvest/v2/cmd/poller/plugin" + "github.com/netapp/harvest/v2/pkg/matrix" + "github.com/netapp/harvest/v2/pkg/tree/node" +) + +func createMockPlugin() *SsdCacheStats { + params := node.NewS("SsdCacheStats") + p := plugin.New("EseriesPerf", nil, params, nil, "eseries_ssd_cache", nil) + _ = p.InitAbc() + return &SsdCacheStats{AbstractPlugin: p} +} + +func createSsdCacheMatrix(t *testing.T) *matrix.Matrix { + t.Helper() + mat := matrix.New("test", "eseries_ssd_cache", "eseries_ssd_cache") + _, _ = mat.NewMetricFloat64("statistics.reads") + _, _ = mat.NewMetricFloat64("statistics.writes") + _, _ = mat.NewMetricFloat64("statistics.readBlocks") + _, _ = mat.NewMetricFloat64("statistics.writeBlocks") + _, _ = mat.NewMetricFloat64("statistics.fullCacheHits") + _, _ = mat.NewMetricFloat64("statistics.fullCacheHitBlocks") + _, _ = mat.NewMetricFloat64("statistics.partialCacheHits") + _, _ = mat.NewMetricFloat64("statistics.partialCacheHitBlocks") + _, _ = mat.NewMetricFloat64("statistics.completeCacheMiss") + _, _ = mat.NewMetricFloat64("statistics.completeCacheMissBlocks") + _, _ = mat.NewMetricFloat64("statistics.populateOnReads") + _, _ = mat.NewMetricFloat64("statistics.populateOnReadBlocks") + _, _ = mat.NewMetricFloat64("statistics.populateOnWrites") + _, _ = mat.NewMetricFloat64("statistics.populateOnWriteBlocks") + _, _ = mat.NewMetricFloat64("statistics.invalidates") + _, _ = mat.NewMetricFloat64("statistics.recycles") + _, _ = mat.NewMetricFloat64("statistics.availableBytes") + _, _ = mat.NewMetricFloat64("statistics.allocatedBytes") + _, _ = mat.NewMetricFloat64("statistics.populatedCleanBytes") + _, _ = mat.NewMetricFloat64("statistics.populatedDirtyBytes") + return mat +} + +func setMetric(t *testing.T, mat *matrix.Matrix, name string, inst *matrix.Instance, val float64) { + t.Helper() + m := mat.GetMetric(name) + if m == nil { + t.Fatalf("metric %s not found", name) + } + m.SetValueFloat64(inst, val) +} + +func TestSsdCacheStats_CacheHitPercent(t *testing.T) { + p := createMockPlugin() + + tests := []struct { + name string + reads float64 + writes float64 + fullCacheHits float64 + wantSet bool + }{ + { + name: "Normal case", + reads: 53.5, + writes: 23.847, + fullCacheHits: 17.028, + wantSet: true, + }, + { + name: "High hit rate", + reads: 100.0, + writes: 0.0, + fullCacheHits: 90.0, + wantSet: true, + }, + { + name: "Zero reads and writes", + reads: 0.0, + writes: 0.0, + fullCacheHits: 0.0, + wantSet: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mat := createSsdCacheMatrix(t) + inst, _ := mat.NewInstance("ctrl1") + + setMetric(t, mat, "statistics.reads", inst, tt.reads) + setMetric(t, mat, "statistics.writes", inst, tt.writes) + setMetric(t, mat, "statistics.fullCacheHits", inst, tt.fullCacheHits) + + p.calculateCacheHitPercent(mat) + + hitPct := mat.GetMetric("hit_percent") + if hitPct == nil { + t.Fatal("hit_percent metric was not created") + } + + val, ok := hitPct.GetValueFloat64(inst) + if tt.wantSet { + if !ok { + t.Fatal("expected hit_percent value to be set") + } + assert.Equal(t, val, (tt.fullCacheHits/(tt.reads+tt.writes))*100.0) + } else if ok && val != 0 { + t.Errorf("expected no value or zero for hit_percent, got %f", val) + } + }) + } +} + +func TestSsdCacheStats_AllocationPercent(t *testing.T) { + p := createMockPlugin() + + tests := []struct { + name string + allocated float64 + available float64 + wantSet bool + }{ + { + name: "Normal allocation", + allocated: 13316915200.0, + available: 1599784091648.0, + wantSet: true, + }, + { + name: "Fully allocated", + allocated: 100.0, + available: 0.0, + wantSet: true, + }, + { + name: "Zero total", + allocated: 0.0, + available: 0.0, + wantSet: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mat := createSsdCacheMatrix(t) + inst, _ := mat.NewInstance("ctrl1") + + setMetric(t, mat, "statistics.allocatedBytes", inst, tt.allocated) + setMetric(t, mat, "statistics.availableBytes", inst, tt.available) + + p.calculateCacheAllocationPercent(mat) + + allocPct := mat.GetMetric("allocation_percent") + if allocPct == nil { + t.Fatal("allocation_percent metric was not created") + } + + val, ok := allocPct.GetValueFloat64(inst) + if tt.wantSet { + if !ok { + t.Fatal("expected allocation_percent value to be set") + } + assert.Equal(t, val, (tt.allocated/(tt.allocated+tt.available))*100.0) + } else if ok && val != 0 { + t.Errorf("expected no value or zero, got %f", val) + } + }) + } +} + +func TestSsdCacheStats_UtilizationPercent(t *testing.T) { + p := createMockPlugin() + + tests := []struct { + name string + clean float64 + dirty float64 + allocated float64 + wantSet bool + }{ + { + name: "Normal utilization", + clean: 3460448256.0, + dirty: 0.0, + allocated: 13316915200.0, + wantSet: true, + }, + { + name: "With dirty bytes", + clean: 3000000000.0, + dirty: 1000000000.0, + allocated: 10000000000.0, + wantSet: true, + }, + { + name: "Zero allocated", + clean: 100.0, + dirty: 50.0, + allocated: 0.0, + wantSet: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mat := createSsdCacheMatrix(t) + inst, _ := mat.NewInstance("ctrl1") + + setMetric(t, mat, "statistics.populatedCleanBytes", inst, tt.clean) + setMetric(t, mat, "statistics.populatedDirtyBytes", inst, tt.dirty) + setMetric(t, mat, "statistics.allocatedBytes", inst, tt.allocated) + + p.calculateCacheUtilizationPercent(mat) + + utilPct := mat.GetMetric("utilization_percent") + if utilPct == nil { + t.Fatal("utilization_percent metric was not created") + } + + val, ok := utilPct.GetValueFloat64(inst) + if tt.wantSet { + if !ok { + t.Fatal("expected utilization_percent value to be set") + } + assert.Equal(t, val, ((tt.clean+tt.dirty)/tt.allocated)*100.0) + } else if ok && val != 0 { + t.Errorf("expected no value or zero, got %f", val) + } + }) + } +} + +func TestSsdCacheStats_ComputePercent(t *testing.T) { + p := createMockPlugin() + + tests := []struct { + name string + numKey string + denKey string + resultName string + numVal float64 + denVal float64 + wantSet bool + }{ + { + name: "full_cache_hit_percent", + numKey: "statistics.fullCacheHits", + denKey: "statistics.reads", + resultName: "full_cache_hit_percent", + numVal: 17.028, + denVal: 53.5, + wantSet: true, + }, + { + name: "partial_cache_hit_percent", + numKey: "statistics.partialCacheHits", + denKey: "statistics.reads", + resultName: "partial_cache_hit_percent", + numVal: 36.472, + denVal: 53.5, + wantSet: true, + }, + { + name: "invalidate_percent writes based", + numKey: "statistics.invalidates", + denKey: "statistics.writes", + resultName: "invalidate_percent", + numVal: 23.847, + denVal: 23.847, + wantSet: true, + }, + { + name: "zero denominator", + numKey: "statistics.fullCacheHits", + denKey: "statistics.reads", + resultName: "zero_test_percent", + numVal: 10.0, + denVal: 0.0, + wantSet: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mat := createSsdCacheMatrix(t) + inst, _ := mat.NewInstance("ctrl1") + + setMetric(t, mat, tt.numKey, inst, tt.numVal) + setMetric(t, mat, tt.denKey, inst, tt.denVal) + + p.computePercent(mat, tt.numKey, tt.denKey, tt.resultName) + + result := mat.GetMetric(tt.resultName) + if result == nil { + t.Fatalf("metric %s was not created", tt.resultName) + } + + val, ok := result.GetValueFloat64(inst) + if tt.wantSet { + if !ok { + t.Fatalf("expected %s value to be set", tt.resultName) + } + assert.Equal(t, val, (tt.numVal/tt.denVal)*100.0) + } else if ok && val != 0 { + t.Errorf("expected no value or zero for %s, got %f", tt.resultName, val) + } + }) + } +} + +func TestSsdCacheStats_ZeroDenominator(t *testing.T) { + p := createMockPlugin() + mat := createSsdCacheMatrix(t) + + inst, _ := mat.NewInstance("ctrl1") + for _, name := range []string{ + "statistics.reads", "statistics.writes", + "statistics.readBlocks", "statistics.writeBlocks", + "statistics.fullCacheHits", "statistics.fullCacheHitBlocks", + "statistics.partialCacheHits", "statistics.partialCacheHitBlocks", + "statistics.completeCacheMiss", "statistics.completeCacheMissBlocks", + "statistics.populateOnReads", "statistics.populateOnReadBlocks", + "statistics.populateOnWrites", "statistics.populateOnWriteBlocks", + "statistics.invalidates", "statistics.recycles", + "statistics.availableBytes", "statistics.allocatedBytes", + "statistics.populatedCleanBytes", "statistics.populatedDirtyBytes", + } { + setMetric(t, mat, name, inst, 0.0) + } + + dataMap := map[string]*matrix.Matrix{"eseries_ssd_cache": mat} + result, metadata, err := p.Run(dataMap) + + if err != nil { + t.Fatalf("Run() returned error: %v", err) + } + if result != nil { + t.Error("Run() should return nil matrices") + } + if metadata != nil { + t.Error("Run() should return nil metadata") + } + + hitPct := mat.GetMetric("hit_percent") + if hitPct != nil { + val, ok := hitPct.GetValueFloat64(inst) + if ok && val != 0 { + t.Errorf("hit_percent should be unset for zero denominators, got %f", val) + } + } +} + +func TestSsdCacheStats_MissingMetrics(t *testing.T) { + p := createMockPlugin() + + mat := matrix.New("test", "eseries_ssd_cache", "eseries_ssd_cache") + _, _ = mat.NewInstance("ctrl1") + + dataMap := map[string]*matrix.Matrix{"eseries_ssd_cache": mat} + + result, metadata, err := p.Run(dataMap) + + if err != nil { + t.Fatalf("Run() returned error: %v", err) + } + if result != nil { + t.Error("Run() should return nil matrices") + } + if metadata != nil { + t.Error("Run() should return nil metadata") + } +} + +func TestSsdCacheStats_Run(t *testing.T) { + p := createMockPlugin() + mat := createSsdCacheMatrix(t) + + ctrl1, _ := mat.NewInstance("ctrl1") + ctrl2, _ := mat.NewInstance("ctrl2") + + setMetric(t, mat, "statistics.reads", ctrl1, 53.5) + setMetric(t, mat, "statistics.writes", ctrl1, 23.847) + setMetric(t, mat, "statistics.readBlocks", ctrl1, 41343.36) + setMetric(t, mat, "statistics.writeBlocks", ctrl1, 36389.22) + setMetric(t, mat, "statistics.fullCacheHits", ctrl1, 17.028) + setMetric(t, mat, "statistics.fullCacheHitBlocks", ctrl1, 1958.19) + setMetric(t, mat, "statistics.partialCacheHits", ctrl1, 36.472) + setMetric(t, mat, "statistics.partialCacheHitBlocks", ctrl1, 39382.72) + setMetric(t, mat, "statistics.completeCacheMiss", ctrl1, 0.0) + setMetric(t, mat, "statistics.completeCacheMissBlocks", ctrl1, 0.0) + setMetric(t, mat, "statistics.populateOnReads", ctrl1, 9.097) + setMetric(t, mat, "statistics.populateOnReadBlocks", ctrl1, 10678.44) + setMetric(t, mat, "statistics.populateOnWrites", ctrl1, 1.556) + setMetric(t, mat, "statistics.populateOnWriteBlocks", ctrl1, 2724.61) + setMetric(t, mat, "statistics.invalidates", ctrl1, 23.847) + setMetric(t, mat, "statistics.recycles", ctrl1, 0.0) + setMetric(t, mat, "statistics.availableBytes", ctrl1, 1599784091648.0) + setMetric(t, mat, "statistics.allocatedBytes", ctrl1, 13316915200.0) + setMetric(t, mat, "statistics.populatedCleanBytes", ctrl1, 3460448256.0) + setMetric(t, mat, "statistics.populatedDirtyBytes", ctrl1, 0.0) + + setMetric(t, mat, "statistics.reads", ctrl2, 55.845) + setMetric(t, mat, "statistics.writes", ctrl2, 25.592) + setMetric(t, mat, "statistics.readBlocks", ctrl2, 47025.38) + setMetric(t, mat, "statistics.writeBlocks", ctrl2, 39547.97) + setMetric(t, mat, "statistics.fullCacheHits", ctrl2, 10.901) + setMetric(t, mat, "statistics.fullCacheHitBlocks", ctrl2, 953.07) + setMetric(t, mat, "statistics.partialCacheHits", ctrl2, 44.930) + setMetric(t, mat, "statistics.partialCacheHitBlocks", ctrl2, 46072.42) + setMetric(t, mat, "statistics.completeCacheMiss", ctrl2, 0.0) + setMetric(t, mat, "statistics.completeCacheMissBlocks", ctrl2, 0.0) + setMetric(t, mat, "statistics.populateOnReads", ctrl2, 7.521) + setMetric(t, mat, "statistics.populateOnReadBlocks", ctrl2, 8976.68) + setMetric(t, mat, "statistics.populateOnWrites", ctrl2, 0.592) + setMetric(t, mat, "statistics.populateOnWriteBlocks", ctrl2, 1012.73) + setMetric(t, mat, "statistics.invalidates", ctrl2, 25.592) + setMetric(t, mat, "statistics.recycles", ctrl2, 0.0) + setMetric(t, mat, "statistics.availableBytes", ctrl2, 1599784091648.0) + setMetric(t, mat, "statistics.allocatedBytes", ctrl2, 13421772800.0) + setMetric(t, mat, "statistics.populatedCleanBytes", ctrl2, 1750466560.0) + setMetric(t, mat, "statistics.populatedDirtyBytes", ctrl2, 0.0) + + dataMap := map[string]*matrix.Matrix{"eseries_ssd_cache": mat} + result, metadata, err := p.Run(dataMap) + + if err != nil { + t.Fatalf("Run() returned error: %v", err) + } + if result != nil { + t.Error("Run() should return nil matrices") + } + if metadata != nil { + t.Error("Run() should return nil metadata") + } + + expectedMetrics := []string{ + "hit_percent", + "allocation_percent", + "utilization_percent", + "full_cache_hit_percent", + "partial_cache_hit_percent", + "complete_cache_miss_percent", + "populate_on_read_percent", + "full_cache_hit_block_percent", + "partial_cache_hit_block_percent", + "complete_cache_miss_block_percent", + "populate_on_read_block_percent", + "populate_on_write_percent", + "invalidate_percent", + "populate_on_write_block_percent", + } + + for _, name := range expectedMetrics { + if mat.GetMetric(name) == nil { + t.Errorf("metric %s was not created by Run()", name) + } + } + + checkMetricValue := func(name string, inst *matrix.Instance, expected float64) { + t.Helper() + m := mat.GetMetric(name) + if m == nil { + t.Errorf("metric %s not found", name) + return + } + val, ok := m.GetValueFloat64(inst) + if !ok { + t.Errorf("no value for %s", name) + return + } + assert.Equal(t, val, expected) + } + + reads1, writes1, hits1 := 53.5, 23.847, 17.028 + alloc1, avail1, clean1 := 13316915200.0, 1599784091648.0, 3460448256.0 + reads2, writes2, hits2 := 55.845, 25.592, 10.901 + checkMetricValue("hit_percent", ctrl1, (hits1/(reads1+writes1))*100.0) + checkMetricValue("allocation_percent", ctrl1, (alloc1/(alloc1+avail1))*100.0) + checkMetricValue("utilization_percent", ctrl1, (clean1/alloc1)*100.0) + checkMetricValue("full_cache_hit_percent", ctrl1, (hits1/reads1)*100.0) + checkMetricValue("invalidate_percent", ctrl1, 100.0) + checkMetricValue("hit_percent", ctrl2, (hits2/(reads2+writes2))*100.0) +} + +func TestSsdCacheStats_NilDataMap(t *testing.T) { + p := createMockPlugin() + + dataMap := map[string]*matrix.Matrix{"wrong_object": matrix.New("x", "x", "x")} + result, metadata, err := p.Run(dataMap) + + if err != nil { + t.Fatalf("Run() returned error: %v", err) + } + if result != nil { + t.Error("Run() should return nil for missing object") + } + if metadata != nil { + t.Error("Run() should return nil metadata") + } +} diff --git a/cmd/collectors/eseriesperf/testdata/ssd_cache1.json b/cmd/collectors/eseriesperf/testdata/ssd_cache1.json new file mode 100644 index 000000000..1fae607f0 --- /dev/null +++ b/cmd/collectors/eseriesperf/testdata/ssd_cache1.json @@ -0,0 +1,54 @@ +[ + { + "controllerId": "070000000000000000000001", + "statistics": { + "timestamp": "1773809538", + "reads": "133714", + "readBlocks": "118255320", + "writes": "74606", + "writeBlocks": "115136236", + "fullCacheHits": "39936", + "fullCacheHitBlocks": "6754778", + "partialCacheHits": "93778", + "partialCacheHitBlocks": "111500542", + "completeCacheMiss": "0", + "completeCacheMissBlocks": "0", + "populateOnReads": "25374", + "populateOnReadBlocks": "32872000", + "populateOnWrites": "6293", + "populateOnWriteBlocks": "10504192", + "invalidates": "74606", + "recycles": "0", + "availableBytes": "1599784091648", + "allocatedBytes": "13316915200", + "populatedCleanBytes": "3433431040", + "populatedDirtyBytes": "0" + } + }, + { + "controllerId": "070000000000000000000002", + "statistics": { + "timestamp": "1773809538", + "reads": "123028", + "readBlocks": "108836415", + "writes": "69611", + "writeBlocks": "107436489", + "fullCacheHits": "30660", + "fullCacheHitBlocks": "4059429", + "partialCacheHits": "92368", + "partialCacheHitBlocks": "104776986", + "completeCacheMiss": "0", + "completeCacheMissBlocks": "0", + "populateOnReads": "21081", + "populateOnReadBlocks": "26726288", + "populateOnWrites": "4328", + "populateOnWriteBlocks": "7356640", + "invalidates": "69611", + "recycles": "0", + "availableBytes": "1599784091648", + "allocatedBytes": "13421772800", + "populatedCleanBytes": "1748148224", + "populatedDirtyBytes": "0" + } + } +] \ No newline at end of file diff --git a/cmd/collectors/eseriesperf/testdata/ssd_cache2.json b/cmd/collectors/eseriesperf/testdata/ssd_cache2.json new file mode 100644 index 000000000..1c1ff4e02 --- /dev/null +++ b/cmd/collectors/eseriesperf/testdata/ssd_cache2.json @@ -0,0 +1,54 @@ +[ + { + "controllerId": "070000000000000000000001", + "statistics": { + "timestamp": "1773809610", + "reads": "137566", + "readBlocks": "121231842", + "writes": "76323", + "writeBlocks": "117756260", + "fullCacheHits": "41162", + "fullCacheHitBlocks": "6895748", + "partialCacheHits": "96404", + "partialCacheHitBlocks": "114336094", + "completeCacheMiss": "0", + "completeCacheMissBlocks": "0", + "populateOnReads": "26029", + "populateOnReadBlocks": "33640480", + "populateOnWrites": "6405", + "populateOnWriteBlocks": "10700304", + "invalidates": "76323", + "recycles": "0", + "availableBytes": "1599784091648", + "allocatedBytes": "13316915200", + "populatedCleanBytes": "3460448256", + "populatedDirtyBytes": "0" + } + }, + { + "controllerId": "070000000000000000000002", + "statistics": { + "timestamp": "1773809609", + "reads": "126993", + "readBlocks": "112175017", + "writes": "71428", + "writeBlocks": "110244351", + "fullCacheHits": "31434", + "fullCacheHitBlocks": "4127111", + "partialCacheHits": "95559", + "partialCacheHitBlocks": "108047906", + "completeCacheMiss": "0", + "completeCacheMissBlocks": "0", + "populateOnReads": "21615", + "populateOnReadBlocks": "27363504", + "populateOnWrites": "4370", + "populateOnWriteBlocks": "7428576", + "invalidates": "71428", + "recycles": "0", + "availableBytes": "1599784091648", + "allocatedBytes": "13421772800", + "populatedCleanBytes": "1750466560", + "populatedDirtyBytes": "0" + } + } +] \ No newline at end of file diff --git a/cmd/collectors/eseriesperf/testdata/ssd_cache_single_controller.json b/cmd/collectors/eseriesperf/testdata/ssd_cache_single_controller.json new file mode 100644 index 000000000..c5e4af983 --- /dev/null +++ b/cmd/collectors/eseriesperf/testdata/ssd_cache_single_controller.json @@ -0,0 +1,28 @@ +[ + { + "controllerId": "070000000000000000000001", + "statistics": { + "timestamp": "1773809538", + "reads": "133714", + "readBlocks": "118255320", + "writes": "74606", + "writeBlocks": "115136236", + "fullCacheHits": "39936", + "fullCacheHitBlocks": "6754778", + "partialCacheHits": "93778", + "partialCacheHitBlocks": "111500542", + "completeCacheMiss": "0", + "completeCacheMissBlocks": "0", + "populateOnReads": "25374", + "populateOnReadBlocks": "32872000", + "populateOnWrites": "6293", + "populateOnWriteBlocks": "10504192", + "invalidates": "74606", + "recycles": "0", + "availableBytes": "1599784091648", + "allocatedBytes": "13316915200", + "populatedCleanBytes": "3433431040", + "populatedDirtyBytes": "0" + } + } +] \ No newline at end of file diff --git a/cmd/collectors/eseriesperf/testdata/ssd_cache_zero_io.json b/cmd/collectors/eseriesperf/testdata/ssd_cache_zero_io.json new file mode 100644 index 000000000..02b5b85ee --- /dev/null +++ b/cmd/collectors/eseriesperf/testdata/ssd_cache_zero_io.json @@ -0,0 +1,54 @@ +[ + { + "controllerId": "070000000000000000000001", + "statistics": { + "timestamp": "1773809598", + "reads": "0", + "readBlocks": "0", + "writes": "0", + "writeBlocks": "0", + "fullCacheHits": "0", + "fullCacheHitBlocks": "0", + "partialCacheHits": "0", + "partialCacheHitBlocks": "0", + "completeCacheMiss": "0", + "completeCacheMissBlocks": "0", + "populateOnReads": "0", + "populateOnReadBlocks": "0", + "populateOnWrites": "0", + "populateOnWriteBlocks": "0", + "invalidates": "0", + "recycles": "0", + "availableBytes": "1599784091648", + "allocatedBytes": "13316915200", + "populatedCleanBytes": "0", + "populatedDirtyBytes": "0" + } + }, + { + "controllerId": "070000000000000000000002", + "statistics": { + "timestamp": "1773809598", + "reads": "0", + "readBlocks": "0", + "writes": "0", + "writeBlocks": "0", + "fullCacheHits": "0", + "fullCacheHitBlocks": "0", + "partialCacheHits": "0", + "partialCacheHitBlocks": "0", + "completeCacheMiss": "0", + "completeCacheMissBlocks": "0", + "populateOnReads": "0", + "populateOnReadBlocks": "0", + "populateOnWrites": "0", + "populateOnWriteBlocks": "0", + "invalidates": "0", + "recycles": "0", + "availableBytes": "1599784091648", + "allocatedBytes": "13421772800", + "populatedCleanBytes": "0", + "populatedDirtyBytes": "0" + } + } +] \ No newline at end of file diff --git a/cmd/tools/generate/eseries_counter.yaml b/cmd/tools/generate/eseries_counter.yaml index 3ece11e5b..b5b8a8999 100644 --- a/cmd/tools/generate/eseries_counter.yaml +++ b/cmd/tools/generate/eseries_counter.yaml @@ -484,6 +484,276 @@ counters: ESeriesCounter: drives.ssdWearLife.percentEnduranceUsed Template: conf/eseries/11.80.0/hardware.yaml (Hardware plugin) + # ============================================================================= + # SSD Cache Metrics (Eseries Collector - Configuration) + # ============================================================================= + - Name: eseries_ssd_cache_labels + Description: This metric provides information about SSD caches. + APIs: + - API: REST + Endpoint: storage-systems/{array_id}/ssd-caches + ESeriesCounter: Harvest generated + Template: conf/eseries/11.80.0/ssd_cache.yaml + + - Name: eseries_ssd_cache_current_capacity + Description: Current used capacity of the SSD cache in bytes + APIs: + - API: REST + Endpoint: storage-systems/{array_id}/ssd-caches + ESeriesCounter: usedCapacity + Template: conf/eseries/11.80.0/ssd_cache.yaml + + - Name: eseries_ssd_cache_max_capacity + Description: Maximum SSD cache capacity allowed for the array in bytes + APIs: + - API: REST + Endpoint: storage-systems/{array_id}/capabilities + ESeriesCounter: featureParameters.maxFlashCacheSize + Template: conf/eseries/11.80.0/ssd_cache.yaml (SsdCacheCapacity plugin) + + - Name: eseries_ssd_cache_additional_capacity + Description: Additional SSD cache capacity that can still be added to the array in bytes (maximum capacity minus current capacity) + APIs: + - API: REST + Endpoint: storage-systems/{array_id}/capabilities + ESeriesCounter: Harvest generated + Template: conf/eseries/11.80.0/ssd_cache.yaml (SsdCacheCapacity plugin) + + - Name: eseries_ssd_cache_drive_raw_capacity + Description: Raw capacity of each drive contributing to the SSD cache in bytes + APIs: + - API: REST + Endpoint: storage-systems/{array_id}/drives + ESeriesCounter: rawCapacity + Template: conf/eseries/11.80.0/ssd_cache.yaml (SsdCacheCapacity plugin) + + - Name: eseries_ssd_cache_drive_labels + Description: This metric provides information about drives assigned to an SSD cache. + APIs: + - API: REST + Endpoint: storage-systems/{array_id}/drives + ESeriesCounter: Harvest generated + Template: conf/eseries/11.80.0/ssd_cache.yaml (SsdCacheCapacity plugin) + + - Name: eseries_ssd_cache_volume_labels + Description: This metric provides information about volumes mapped to an SSD cache. + APIs: + - API: REST + Endpoint: storage-systems/{array_id}/volumes + ESeriesCounter: Harvest generated + Template: conf/eseries/11.80.0/ssd_cache.yaml (SsdCacheCapacity plugin) + + # ============================================================================= + # SSD Cache Performance Metrics (EseriesPerf Collector) + # ============================================================================= + - Name: eseries_ssd_cache_allocated_size + Description: Allocated size of the SSD cache per controller in bytes + APIs: + - API: REST + Endpoint: storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics + ESeriesCounter: statistics.allocatedBytes + Template: conf/eseriesperf/11.80.0/ssd_cache.yaml + + - Name: eseries_ssd_cache_available_size + Description: Available size of the SSD cache per controller in bytes + APIs: + - API: REST + Endpoint: storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics + ESeriesCounter: statistics.availableBytes + Template: conf/eseriesperf/11.80.0/ssd_cache.yaml + + - Name: eseries_ssd_cache_populated_clean_size + Description: Amount of clean (unmodified) data populated in the SSD cache per controller in bytes + APIs: + - API: REST + Endpoint: storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics + ESeriesCounter: statistics.populatedCleanBytes + Template: conf/eseriesperf/11.80.0/ssd_cache.yaml + + - Name: eseries_ssd_cache_populated_dirty_size + Description: Amount of dirty (modified) data populated in the SSD cache per controller in bytes + APIs: + - API: REST + Endpoint: storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics + ESeriesCounter: statistics.populatedDirtyBytes + Template: conf/eseriesperf/11.80.0/ssd_cache.yaml + + - Name: eseries_ssd_cache_read_ops + Description: SSD cache read operations per second per controller + APIs: + - API: REST + Endpoint: storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics + ESeriesCounter: statistics.reads + Template: conf/eseriesperf/11.80.0/ssd_cache.yaml + + - Name: eseries_ssd_cache_write_ops + Description: SSD cache write operations per second per controller + APIs: + - API: REST + Endpoint: storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics + ESeriesCounter: statistics.writes + Template: conf/eseriesperf/11.80.0/ssd_cache.yaml + + - Name: eseries_ssd_cache_read_block_ops + Description: SSD cache read block operations per second per controller + APIs: + - API: REST + Endpoint: storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics + ESeriesCounter: statistics.readBlocks + Template: conf/eseriesperf/11.80.0/ssd_cache.yaml + + - Name: eseries_ssd_cache_write_block_ops + Description: SSD cache write block operations per second per controller + APIs: + - API: REST + Endpoint: storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics + ESeriesCounter: statistics.writeBlocks + Template: conf/eseriesperf/11.80.0/ssd_cache.yaml + + - Name: eseries_ssd_cache_full_cache_hit_ops + Description: Number of full cache hit operations per controller + APIs: + - API: REST + Endpoint: storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics + ESeriesCounter: statistics.fullCacheHits + Template: conf/eseriesperf/11.80.0/ssd_cache.yaml + + - Name: eseries_ssd_cache_full_cache_hit_block_ops + Description: Number of full cache hit block operations per controller + APIs: + - API: REST + Endpoint: storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics + ESeriesCounter: statistics.fullCacheHitBlocks + Template: conf/eseriesperf/11.80.0/ssd_cache.yaml + + - Name: eseries_ssd_cache_partial_cache_hit_ops + Description: Number of partial cache hit operations per controller + APIs: + - API: REST + Endpoint: storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics + ESeriesCounter: statistics.partialCacheHits + Template: conf/eseriesperf/11.80.0/ssd_cache.yaml + + - Name: eseries_ssd_cache_partial_cache_hit_block_ops + Description: Number of partial cache hit block operations per controller + APIs: + - API: REST + Endpoint: storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics + ESeriesCounter: statistics.partialCacheHitBlocks + Template: conf/eseriesperf/11.80.0/ssd_cache.yaml + + - Name: eseries_ssd_cache_complete_cache_miss_ops + Description: Number of complete cache miss operations per controller + APIs: + - API: REST + Endpoint: storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics + ESeriesCounter: statistics.completeCacheMiss + Template: conf/eseriesperf/11.80.0/ssd_cache.yaml + + - Name: eseries_ssd_cache_complete_cache_miss_block_ops + Description: Number of complete cache miss block operations per controller + APIs: + - API: REST + Endpoint: storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics + ESeriesCounter: statistics.completeCacheMissBlocks + Template: conf/eseriesperf/11.80.0/ssd_cache.yaml + + - Name: eseries_ssd_cache_populate_on_read_ops + Description: Number of populate-on-read operations per controller + APIs: + - API: REST + Endpoint: storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics + ESeriesCounter: statistics.populateOnReads + Template: conf/eseriesperf/11.80.0/ssd_cache.yaml + + - Name: eseries_ssd_cache_populate_on_read_block_ops + Description: Number of populate-on-read block operations per controller + APIs: + - API: REST + Endpoint: storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics + ESeriesCounter: statistics.populateOnReadBlocks + Template: conf/eseriesperf/11.80.0/ssd_cache.yaml + + - Name: eseries_ssd_cache_populate_on_write_ops + Description: Number of populate-on-write operations per controller + APIs: + - API: REST + Endpoint: storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics + ESeriesCounter: statistics.populateOnWrites + Template: conf/eseriesperf/11.80.0/ssd_cache.yaml + + - Name: eseries_ssd_cache_populate_on_write_block_ops + Description: Number of populate-on-write block operations per controller + APIs: + - API: REST + Endpoint: storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics + ESeriesCounter: statistics.populateOnWriteBlocks + Template: conf/eseriesperf/11.80.0/ssd_cache.yaml + + - Name: eseries_ssd_cache_invalidate_ops + Description: Number of cache invalidate operations per controller + APIs: + - API: REST + Endpoint: storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics + ESeriesCounter: statistics.invalidates + Template: conf/eseriesperf/11.80.0/ssd_cache.yaml + + - Name: eseries_ssd_cache_recycle_ops + Description: Number of cache recycle operations per controller + APIs: + - API: REST + Endpoint: storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics + ESeriesCounter: statistics.recycles + Template: conf/eseriesperf/11.80.0/ssd_cache.yaml + + - Name: eseries_ssd_cache_hit_percent + Description: SSD cache hit percentage per controller, calculated as full cache hits divided by total I/O operations (reads + writes) + APIs: + - API: REST + Endpoint: storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics + ESeriesCounter: Harvest generated + Template: conf/eseriesperf/11.80.0/ssd_cache.yaml (SsdCacheStats plugin) + + - Name: eseries_ssd_cache_allocation_percent + Description: SSD cache allocation percentage per controller, calculated as allocated bytes divided by total cache size (allocated + available bytes) + APIs: + - API: REST + Endpoint: storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics + ESeriesCounter: Harvest generated + Template: conf/eseriesperf/11.80.0/ssd_cache.yaml (SsdCacheStats plugin) + + - Name: eseries_ssd_cache_utilization_percent + Description: SSD cache utilization percentage per controller, calculated as populated data (clean + dirty bytes) divided by allocated bytes + APIs: + - API: REST + Endpoint: storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics + ESeriesCounter: Harvest generated + Template: conf/eseriesperf/11.80.0/ssd_cache.yaml (SsdCacheStats plugin) + + - Name: eseries_ssd_cache_full_cache_hit_percent + Description: Percentage of read operations that resulted in a full cache hit per controller + APIs: + - API: REST + Endpoint: storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics + ESeriesCounter: Harvest generated + Template: conf/eseriesperf/11.80.0/ssd_cache.yaml (SsdCacheStats plugin) + + - Name: eseries_ssd_cache_partial_cache_hit_percent + Description: Percentage of read operations that resulted in a partial cache hit per controller + APIs: + - API: REST + Endpoint: storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics + ESeriesCounter: Harvest generated + Template: conf/eseriesperf/11.80.0/ssd_cache.yaml (SsdCacheStats plugin) + + - Name: eseries_ssd_cache_complete_cache_miss_percent + Description: Percentage of read operations that resulted in a complete cache miss per controller + APIs: + - API: REST + Endpoint: storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics + ESeriesCounter: Harvest generated + Template: conf/eseriesperf/11.80.0/ssd_cache.yaml (SsdCacheStats plugin) + # ============================================================================= # SFP Metrics (Hardware Plugin) # ============================================================================= diff --git a/conf/eseries/11.80.0/ssd_cache.yaml b/conf/eseries/11.80.0/ssd_cache.yaml new file mode 100644 index 000000000..7b7cba3f3 --- /dev/null +++ b/conf/eseries/11.80.0/ssd_cache.yaml @@ -0,0 +1,30 @@ +name: SsdCache +query: storage-systems/{array_id}/ssd-caches +object: eseries_ssd_cache + +counters: + - ^^id => ssd_cache_id + - ^cachedVolumesIds => cached_volume_ids + - ^driveIds => drive_ids + - ^name => ssd_cache + - ^protectionInformationCapable => da_capable + - ^securityLevel => security_level + - ^securityType => security_type + - ^status + - ^type + - ^wwn + - usedCapacity => current_capacity + +plugins: + - SsdCacheCapacity + +export_options: + instance_keys: + - ssd_cache + - ssd_cache_id + instance_labels: + - status + - type + - security_level + - security_type + - da_capable diff --git a/conf/eseries/default.yaml b/conf/eseries/default.yaml index 1b0e15ccd..33b8d5396 100644 --- a/conf/eseries/default.yaml +++ b/conf/eseries/default.yaml @@ -9,3 +9,4 @@ objects: Array: array.yaml Host: host.yaml Hardware: hardware.yaml + SsdCache: ssd_cache.yaml diff --git a/conf/eseriesperf/11.80.0/ssd_cache.yaml b/conf/eseriesperf/11.80.0/ssd_cache.yaml new file mode 100644 index 000000000..248ee87ba --- /dev/null +++ b/conf/eseriesperf/11.80.0/ssd_cache.yaml @@ -0,0 +1,37 @@ +name: SsdCache +query: storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics +object: eseries_ssd_cache +type: ssd_cache + +counters: + - ^^controllerId => controller_id + - statistics.allocatedBytes => allocated_size + - statistics.availableBytes => available_size + - statistics.completeCacheMiss => complete_cache_miss_ops + - statistics.completeCacheMissBlocks => complete_cache_miss_block_ops + - statistics.fullCacheHitBlocks => full_cache_hit_block_ops + - statistics.fullCacheHits => full_cache_hit_ops + - statistics.invalidates => invalidate_ops + - statistics.partialCacheHitBlocks => partial_cache_hit_block_ops + - statistics.partialCacheHits => partial_cache_hit_ops + - statistics.populateOnReadBlocks => populate_on_read_block_ops + - statistics.populateOnReads => populate_on_read_ops + - statistics.populateOnWriteBlocks => populate_on_write_block_ops + - statistics.populateOnWrites => populate_on_write_ops + - statistics.populatedCleanBytes => populated_clean_size + - statistics.populatedDirtyBytes => populated_dirty_size + - statistics.readBlocks => read_block_ops + - statistics.reads => read_ops + - statistics.recycles => recycle_ops + - statistics.timestamp => timestamp + - statistics.writeBlocks => write_block_ops + - statistics.writes => write_ops + +plugins: + - Controller + - SsdCacheStats + +export_options: + instance_keys: + - controller + - controller_id diff --git a/conf/eseriesperf/default.yaml b/conf/eseriesperf/default.yaml index b7ca3d1d2..7a8b0e884 100644 --- a/conf/eseriesperf/default.yaml +++ b/conf/eseriesperf/default.yaml @@ -10,3 +10,4 @@ objects: # Drive: drive.yaml # Pool: pool.yaml Array: array.yaml + SsdCache: ssd_cache.yaml diff --git a/conf/eseriesperf/static_counter_definitions.yaml b/conf/eseriesperf/static_counter_definitions.yaml index db95c95e1..f31477039 100644 --- a/conf/eseriesperf/static_counter_definitions.yaml +++ b/conf/eseriesperf/static_counter_definitions.yaml @@ -112,3 +112,48 @@ objects: type: rate - name: cacheHitsIopsTotal type: rate + + eseries_ssd_cache: + counter_definitions: + - name: statistics.timestamp + type: delta + - name: statistics.reads + type: rate + - name: statistics.writes + type: rate + - name: statistics.readBlocks + type: rate + - name: statistics.writeBlocks + type: rate + - name: statistics.fullCacheHits + type: rate + - name: statistics.fullCacheHitBlocks + type: rate + - name: statistics.partialCacheHits + type: rate + - name: statistics.partialCacheHitBlocks + type: rate + - name: statistics.completeCacheMiss + type: rate + - name: statistics.completeCacheMissBlocks + type: rate + - name: statistics.populateOnReads + type: rate + - name: statistics.populateOnReadBlocks + type: rate + - name: statistics.populateOnWrites + type: rate + - name: statistics.populateOnWriteBlocks + type: rate + - name: statistics.invalidates + type: rate + - name: statistics.recycles + type: rate + - name: statistics.availableBytes + type: raw + - name: statistics.allocatedBytes + type: raw + - name: statistics.populatedCleanBytes + type: raw + - name: statistics.populatedDirtyBytes + type: raw diff --git a/docs/cisco-switch-metrics.md b/docs/cisco-switch-metrics.md index dfa2f477f..9bf653701 100644 --- a/docs/cisco-switch-metrics.md +++ b/docs/cisco-switch-metrics.md @@ -5,7 +5,7 @@ These can be generated on demand by running `bin/harvest grafana metrics`. See [#1577](https://github.com/NetApp/harvest/issues/1577#issue-1471478260) for details. ``` -Creation Date : 2026-Mar-16 +Creation Date : 2026-Apr-03 NX-OS Version: 9.3.12 ``` diff --git a/docs/eseries-metrics.md b/docs/eseries-metrics.md index 4bfebe540..9a3e4be28 100644 --- a/docs/eseries-metrics.md +++ b/docs/eseries-metrics.md @@ -5,7 +5,7 @@ These can be generated on demand by running `bin/harvest grafana metrics`. See [#1577](https://github.com/NetApp/harvest/issues/1577#issue-1471478260) for details. ``` -Creation Date : 2026-Mar-16 +Creation Date : 2026-Apr-03 E-Series Version: 11.80.0 ``` @@ -802,6 +802,621 @@ The `eseries_sfp_labels` metric is visualized in the following Grafana dashboard +### eseries_ssd_cache_additional_capacity + +Additional SSD cache capacity that can still be added to the array in bytes (maximum capacity minus current capacity) + + +| API | Endpoint | Metric | Template | +|--------|----------|--------|---------| +| REST | `storage-systems/{array_id}/capabilities` | `Harvest generated` | conf/eseries/11.80.0/ssd_cache.yaml (SsdCacheCapacity plugin) | + +The `eseries_ssd_cache_additional_capacity` metric is visualized in the following Grafana dashboards: + +/// html | div.grafana-table +| Dashboard | Row | Type | Panel | +|--------|----------|--------|--------| +| E-Series: SSD Cache | Capacity | table | [SSD Cache Overview](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=11) | +| E-Series: SSD Cache | Capacity | timeseries | [Top $TopResources Additional Capacity Allowed](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=18) | +/// + + + +### eseries_ssd_cache_allocated_size + +Allocated size of the SSD cache per controller in bytes + + +| API | Endpoint | Metric | Template | +|--------|----------|--------|---------| +| REST | `storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics` | `statistics.allocatedBytes` | conf/eseriesperf/11.80.0/ssd_cache.yaml | + +The `eseries_ssd_cache_allocated_size` metric is visualized in the following Grafana dashboards: + +/// html | div.grafana-table +| Dashboard | Row | Type | Panel | +|--------|----------|--------|--------| +| E-Series: SSD Cache | Capacity | timeseries | [Top $TopResources Allocated Size](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=13) | +/// + + + +### eseries_ssd_cache_allocation_percent + +SSD cache allocation percentage per controller, calculated as allocated bytes divided by total cache size (allocated + available bytes) + + +| API | Endpoint | Metric | Template | +|--------|----------|--------|---------| +| REST | `storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics` | `Harvest generated` | conf/eseriesperf/11.80.0/ssd_cache.yaml (SsdCacheStats plugin) | + +The `eseries_ssd_cache_allocation_percent` metric is visualized in the following Grafana dashboards: + +/// html | div.grafana-table +| Dashboard | Row | Type | Panel | +|--------|----------|--------|--------| +| E-Series: SSD Cache | Highlights | timeseries | [Top $TopResources Cache Allocation %](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=83) | +| E-Series: SSD Cache | Capacity | timeseries | [Top $TopResources Allocation %](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=15) | +/// + + + +### eseries_ssd_cache_available_size + +Available size of the SSD cache per controller in bytes + + +| API | Endpoint | Metric | Template | +|--------|----------|--------|---------| +| REST | `storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics` | `statistics.availableBytes` | conf/eseriesperf/11.80.0/ssd_cache.yaml | + +The `eseries_ssd_cache_available_size` metric is visualized in the following Grafana dashboards: + +/// html | div.grafana-table +| Dashboard | Row | Type | Panel | +|--------|----------|--------|--------| +| E-Series: SSD Cache | Capacity | timeseries | [Top $TopResources Available Size](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=14) | +/// + + + +### eseries_ssd_cache_complete_cache_miss_block_ops + +Number of complete cache miss block operations per controller + + +| API | Endpoint | Metric | Template | +|--------|----------|--------|---------| +| REST | `storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics` | `statistics.completeCacheMissBlocks` | conf/eseriesperf/11.80.0/ssd_cache.yaml | + +The `eseries_ssd_cache_complete_cache_miss_block_ops` metric is visualized in the following Grafana dashboards: + +/// html | div.grafana-table +| Dashboard | Row | Type | Panel | +|--------|----------|--------|--------| +| E-Series: SSD Cache | Block Operations | timeseries | [Top $TopResources Cache Miss Block Ops/s](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=65) | +/// + + + +### eseries_ssd_cache_complete_cache_miss_ops + +Number of complete cache miss operations per controller + + +| API | Endpoint | Metric | Template | +|--------|----------|--------|---------| +| REST | `storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics` | `statistics.completeCacheMiss` | conf/eseriesperf/11.80.0/ssd_cache.yaml | + +The `eseries_ssd_cache_complete_cache_miss_ops` metric is visualized in the following Grafana dashboards: + +/// html | div.grafana-table +| Dashboard | Row | Type | Panel | +|--------|----------|--------|--------| +| E-Series: SSD Cache | IOPS | timeseries | [Top $TopResources Cache Miss IOPS](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=55) | +/// + + + +### eseries_ssd_cache_complete_cache_miss_percent + +Percentage of read operations that resulted in a complete cache miss per controller + + +| API | Endpoint | Metric | Template | +|--------|----------|--------|---------| +| REST | `storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics` | `Harvest generated` | conf/eseriesperf/11.80.0/ssd_cache.yaml (SsdCacheStats plugin) | + +The `eseries_ssd_cache_complete_cache_miss_percent` metric is visualized in the following Grafana dashboards: + +/// html | div.grafana-table +| Dashboard | Row | Type | Panel | +|--------|----------|--------|--------| +| E-Series: SSD Cache | Cache Hit Performance | timeseries | [Top $TopResources Complete Cache Miss %](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=44) | +/// + + + +### eseries_ssd_cache_current_capacity + +Current used capacity of the SSD cache in bytes + + +| API | Endpoint | Metric | Template | +|--------|----------|--------|---------| +| REST | `storage-systems/{array_id}/ssd-caches` | `usedCapacity` | conf/eseries/11.80.0/ssd_cache.yaml | + +The `eseries_ssd_cache_current_capacity` metric is visualized in the following Grafana dashboards: + +/// html | div.grafana-table +| Dashboard | Row | Type | Panel | +|--------|----------|--------|--------| +| E-Series: SSD Cache | Capacity | table | [SSD Cache Overview](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=11) | +| E-Series: SSD Cache | Capacity | timeseries | [Top $TopResources Current Capacity](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=12) | +/// + + + +### eseries_ssd_cache_drive_labels + +This metric provides information about drives assigned to an SSD cache. + + +| API | Endpoint | Metric | Template | +|--------|----------|--------|---------| +| REST | `storage-systems/{array_id}/drives` | `Harvest generated` | conf/eseries/11.80.0/ssd_cache.yaml (SsdCacheCapacity plugin) | + +The `eseries_ssd_cache_drive_labels` metric is visualized in the following Grafana dashboards: + +/// html | div.grafana-table +| Dashboard | Row | Type | Panel | +|--------|----------|--------|--------| +| E-Series: SSD Cache | Drives | table | [SSD Cache Drives](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=21) | +/// + + + +### eseries_ssd_cache_drive_raw_capacity + +Raw capacity of each drive contributing to the SSD cache in bytes + + +| API | Endpoint | Metric | Template | +|--------|----------|--------|---------| +| REST | `storage-systems/{array_id}/drives` | `rawCapacity` | conf/eseries/11.80.0/ssd_cache.yaml (SsdCacheCapacity plugin) | + +The `eseries_ssd_cache_drive_raw_capacity` metric is visualized in the following Grafana dashboards: + +/// html | div.grafana-table +| Dashboard | Row | Type | Panel | +|--------|----------|--------|--------| +| E-Series: SSD Cache | Drives | table | [SSD Cache Drives](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=21) | +/// + + + +### eseries_ssd_cache_full_cache_hit_block_ops + +Number of full cache hit block operations per controller + + +| API | Endpoint | Metric | Template | +|--------|----------|--------|---------| +| REST | `storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics` | `statistics.fullCacheHitBlocks` | conf/eseriesperf/11.80.0/ssd_cache.yaml | + +The `eseries_ssd_cache_full_cache_hit_block_ops` metric is visualized in the following Grafana dashboards: + +/// html | div.grafana-table +| Dashboard | Row | Type | Panel | +|--------|----------|--------|--------| +| E-Series: SSD Cache | Block Operations | timeseries | [Top $TopResources Full Cache Hit Block Ops/s](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=63) | +/// + + + +### eseries_ssd_cache_full_cache_hit_ops + +Number of full cache hit operations per controller + + +| API | Endpoint | Metric | Template | +|--------|----------|--------|---------| +| REST | `storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics` | `statistics.fullCacheHits` | conf/eseriesperf/11.80.0/ssd_cache.yaml | + +The `eseries_ssd_cache_full_cache_hit_ops` metric is visualized in the following Grafana dashboards: + +/// html | div.grafana-table +| Dashboard | Row | Type | Panel | +|--------|----------|--------|--------| +| E-Series: SSD Cache | IOPS | timeseries | [Top $TopResources Full Cache Hit IOPS](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=53) | +/// + + + +### eseries_ssd_cache_full_cache_hit_percent + +Percentage of read operations that resulted in a full cache hit per controller + + +| API | Endpoint | Metric | Template | +|--------|----------|--------|---------| +| REST | `storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics` | `Harvest generated` | conf/eseriesperf/11.80.0/ssd_cache.yaml (SsdCacheStats plugin) | + +The `eseries_ssd_cache_full_cache_hit_percent` metric is visualized in the following Grafana dashboards: + +/// html | div.grafana-table +| Dashboard | Row | Type | Panel | +|--------|----------|--------|--------| +| E-Series: SSD Cache | Cache Hit Performance | timeseries | [Top $TopResources Full Cache Hit %](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=42) | +/// + + + +### eseries_ssd_cache_hit_percent + +SSD cache hit percentage per controller, calculated as full cache hits divided by total I/O operations (reads + writes) + + +| API | Endpoint | Metric | Template | +|--------|----------|--------|---------| +| REST | `storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics` | `Harvest generated` | conf/eseriesperf/11.80.0/ssd_cache.yaml (SsdCacheStats plugin) | + +The `eseries_ssd_cache_hit_percent` metric is visualized in the following Grafana dashboards: + +/// html | div.grafana-table +| Dashboard | Row | Type | Panel | +|--------|----------|--------|--------| +| E-Series: SSD Cache | Highlights | timeseries | [Top $TopResources Cache Hit %](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=2) | +| E-Series: SSD Cache | Cache Hit Performance | timeseries | [Top $TopResources Cache Hit %](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=41) | +/// + + + +### eseries_ssd_cache_invalidate_ops + +Number of cache invalidate operations per controller + + +| API | Endpoint | Metric | Template | +|--------|----------|--------|---------| +| REST | `storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics` | `statistics.invalidates` | conf/eseriesperf/11.80.0/ssd_cache.yaml | + +The `eseries_ssd_cache_invalidate_ops` metric is visualized in the following Grafana dashboards: + +/// html | div.grafana-table +| Dashboard | Row | Type | Panel | +|--------|----------|--------|--------| +| E-Series: SSD Cache | Cache Sizing | timeseries | [Top $TopResources Invalidate Ops/s](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=73) | +/// + + + +### eseries_ssd_cache_labels + +This metric provides information about SSD caches. + + +| API | Endpoint | Metric | Template | +|--------|----------|--------|---------| +| REST | `storage-systems/{array_id}/ssd-caches` | `Harvest generated` | conf/eseries/11.80.0/ssd_cache.yaml | + +The `eseries_ssd_cache_labels` metric is visualized in the following Grafana dashboards: + +/// html | div.grafana-table +| Dashboard | Row | Type | Panel | +|--------|----------|--------|--------| +| E-Series: SSD Cache | Capacity | table | [SSD Cache Overview](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=11) | +/// + + + +### eseries_ssd_cache_max_capacity + +Maximum SSD cache capacity allowed for the array in bytes + + +| API | Endpoint | Metric | Template | +|--------|----------|--------|---------| +| REST | `storage-systems/{array_id}/capabilities` | `featureParameters.maxFlashCacheSize` | conf/eseries/11.80.0/ssd_cache.yaml (SsdCacheCapacity plugin) | + +The `eseries_ssd_cache_max_capacity` metric is visualized in the following Grafana dashboards: + +/// html | div.grafana-table +| Dashboard | Row | Type | Panel | +|--------|----------|--------|--------| +| E-Series: SSD Cache | Capacity | table | [SSD Cache Overview](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=11) | +| E-Series: SSD Cache | Capacity | timeseries | [Top $TopResources Maximum Capacity Allowed](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=17) | +/// + + + +### eseries_ssd_cache_partial_cache_hit_block_ops + +Number of partial cache hit block operations per controller + + +| API | Endpoint | Metric | Template | +|--------|----------|--------|---------| +| REST | `storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics` | `statistics.partialCacheHitBlocks` | conf/eseriesperf/11.80.0/ssd_cache.yaml | + +The `eseries_ssd_cache_partial_cache_hit_block_ops` metric is visualized in the following Grafana dashboards: + +/// html | div.grafana-table +| Dashboard | Row | Type | Panel | +|--------|----------|--------|--------| +| E-Series: SSD Cache | Block Operations | timeseries | [Top $TopResources Partial Cache Hit Block Ops/s](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=64) | +/// + + + +### eseries_ssd_cache_partial_cache_hit_ops + +Number of partial cache hit operations per controller + + +| API | Endpoint | Metric | Template | +|--------|----------|--------|---------| +| REST | `storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics` | `statistics.partialCacheHits` | conf/eseriesperf/11.80.0/ssd_cache.yaml | + +The `eseries_ssd_cache_partial_cache_hit_ops` metric is visualized in the following Grafana dashboards: + +/// html | div.grafana-table +| Dashboard | Row | Type | Panel | +|--------|----------|--------|--------| +| E-Series: SSD Cache | IOPS | timeseries | [Top $TopResources Partial Cache Hit IOPS](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=54) | +/// + + + +### eseries_ssd_cache_partial_cache_hit_percent + +Percentage of read operations that resulted in a partial cache hit per controller + + +| API | Endpoint | Metric | Template | +|--------|----------|--------|---------| +| REST | `storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics` | `Harvest generated` | conf/eseriesperf/11.80.0/ssd_cache.yaml (SsdCacheStats plugin) | + +The `eseries_ssd_cache_partial_cache_hit_percent` metric is visualized in the following Grafana dashboards: + +/// html | div.grafana-table +| Dashboard | Row | Type | Panel | +|--------|----------|--------|--------| +| E-Series: SSD Cache | Cache Hit Performance | timeseries | [Top $TopResources Partial Cache Hit %](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=43) | +/// + + + +### eseries_ssd_cache_populate_on_read_block_ops + +Number of populate-on-read block operations per controller + + +| API | Endpoint | Metric | Template | +|--------|----------|--------|---------| +| REST | `storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics` | `statistics.populateOnReadBlocks` | conf/eseriesperf/11.80.0/ssd_cache.yaml | + + +### eseries_ssd_cache_populate_on_read_ops + +Number of populate-on-read operations per controller + + +| API | Endpoint | Metric | Template | +|--------|----------|--------|---------| +| REST | `storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics` | `statistics.populateOnReads` | conf/eseriesperf/11.80.0/ssd_cache.yaml | + +The `eseries_ssd_cache_populate_on_read_ops` metric is visualized in the following Grafana dashboards: + +/// html | div.grafana-table +| Dashboard | Row | Type | Panel | +|--------|----------|--------|--------| +| E-Series: SSD Cache | IOPS | timeseries | [Top $TopResources Populate-on-Read IOPS](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=56) | +/// + + + +### eseries_ssd_cache_populate_on_write_block_ops + +Number of populate-on-write block operations per controller + + +| API | Endpoint | Metric | Template | +|--------|----------|--------|---------| +| REST | `storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics` | `statistics.populateOnWriteBlocks` | conf/eseriesperf/11.80.0/ssd_cache.yaml | + + +### eseries_ssd_cache_populate_on_write_ops + +Number of populate-on-write operations per controller + + +| API | Endpoint | Metric | Template | +|--------|----------|--------|---------| +| REST | `storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics` | `statistics.populateOnWrites` | conf/eseriesperf/11.80.0/ssd_cache.yaml | + +The `eseries_ssd_cache_populate_on_write_ops` metric is visualized in the following Grafana dashboards: + +/// html | div.grafana-table +| Dashboard | Row | Type | Panel | +|--------|----------|--------|--------| +| E-Series: SSD Cache | IOPS | timeseries | [Top $TopResources Populate-on-Write IOPS](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=57) | +/// + + + +### eseries_ssd_cache_populated_clean_size + +Amount of clean (unmodified) data populated in the SSD cache per controller in bytes + + +| API | Endpoint | Metric | Template | +|--------|----------|--------|---------| +| REST | `storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics` | `statistics.populatedCleanBytes` | conf/eseriesperf/11.80.0/ssd_cache.yaml | + +The `eseries_ssd_cache_populated_clean_size` metric is visualized in the following Grafana dashboards: + +/// html | div.grafana-table +| Dashboard | Row | Type | Panel | +|--------|----------|--------|--------| +| E-Series: SSD Cache | Cache Sizing | timeseries | [Top $TopResources Populated Clean Size](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=71) | +/// + + + +### eseries_ssd_cache_populated_dirty_size + +Amount of dirty (modified) data populated in the SSD cache per controller in bytes + + +| API | Endpoint | Metric | Template | +|--------|----------|--------|---------| +| REST | `storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics` | `statistics.populatedDirtyBytes` | conf/eseriesperf/11.80.0/ssd_cache.yaml | + +The `eseries_ssd_cache_populated_dirty_size` metric is visualized in the following Grafana dashboards: + +/// html | div.grafana-table +| Dashboard | Row | Type | Panel | +|--------|----------|--------|--------| +| E-Series: SSD Cache | Cache Sizing | timeseries | [Top $TopResources Populated Dirty Size](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=72) | +/// + + + +### eseries_ssd_cache_read_block_ops + +SSD cache read block operations per second per controller + + +| API | Endpoint | Metric | Template | +|--------|----------|--------|---------| +| REST | `storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics` | `statistics.readBlocks` | conf/eseriesperf/11.80.0/ssd_cache.yaml | + +The `eseries_ssd_cache_read_block_ops` metric is visualized in the following Grafana dashboards: + +/// html | div.grafana-table +| Dashboard | Row | Type | Panel | +|--------|----------|--------|--------| +| E-Series: SSD Cache | Block Operations | timeseries | [Top $TopResources Read Block Ops/s](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=61) | +/// + + + +### eseries_ssd_cache_read_ops + +SSD cache read operations per second per controller + + +| API | Endpoint | Metric | Template | +|--------|----------|--------|---------| +| REST | `storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics` | `statistics.reads` | conf/eseriesperf/11.80.0/ssd_cache.yaml | + +The `eseries_ssd_cache_read_ops` metric is visualized in the following Grafana dashboards: + +/// html | div.grafana-table +| Dashboard | Row | Type | Panel | +|--------|----------|--------|--------| +| E-Series: SSD Cache | IOPS | timeseries | [Top $TopResources Read IOPS](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=51) | +/// + + + +### eseries_ssd_cache_recycle_ops + +Number of cache recycle operations per controller + + +| API | Endpoint | Metric | Template | +|--------|----------|--------|---------| +| REST | `storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics` | `statistics.recycles` | conf/eseriesperf/11.80.0/ssd_cache.yaml | + +The `eseries_ssd_cache_recycle_ops` metric is visualized in the following Grafana dashboards: + +/// html | div.grafana-table +| Dashboard | Row | Type | Panel | +|--------|----------|--------|--------| +| E-Series: SSD Cache | Cache Sizing | timeseries | [Top $TopResources Recycle Ops/s](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=74) | +/// + + + +### eseries_ssd_cache_utilization_percent + +SSD cache utilization percentage per controller, calculated as populated data (clean + dirty bytes) divided by allocated bytes + + +| API | Endpoint | Metric | Template | +|--------|----------|--------|---------| +| REST | `storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics` | `Harvest generated` | conf/eseriesperf/11.80.0/ssd_cache.yaml (SsdCacheStats plugin) | + +The `eseries_ssd_cache_utilization_percent` metric is visualized in the following Grafana dashboards: + +/// html | div.grafana-table +| Dashboard | Row | Type | Panel | +|--------|----------|--------|--------| +| E-Series: SSD Cache | Highlights | timeseries | [Top $TopResources Cache Utilization %](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=84) | +| E-Series: SSD Cache | Capacity | timeseries | [Top $TopResources Utilization %](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=16) | +/// + + + +### eseries_ssd_cache_volume_labels + +This metric provides information about volumes mapped to an SSD cache. + + +| API | Endpoint | Metric | Template | +|--------|----------|--------|---------| +| REST | `storage-systems/{array_id}/volumes` | `Harvest generated` | conf/eseries/11.80.0/ssd_cache.yaml (SsdCacheCapacity plugin) | + +The `eseries_ssd_cache_volume_labels` metric is visualized in the following Grafana dashboards: + +/// html | div.grafana-table +| Dashboard | Row | Type | Panel | +|--------|----------|--------|--------| +| E-Series: SSD Cache | Cached Volumes | table | [Cached Volumes](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=31) | +/// + + + +### eseries_ssd_cache_write_block_ops + +SSD cache write block operations per second per controller + + +| API | Endpoint | Metric | Template | +|--------|----------|--------|---------| +| REST | `storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics` | `statistics.writeBlocks` | conf/eseriesperf/11.80.0/ssd_cache.yaml | + +The `eseries_ssd_cache_write_block_ops` metric is visualized in the following Grafana dashboards: + +/// html | div.grafana-table +| Dashboard | Row | Type | Panel | +|--------|----------|--------|--------| +| E-Series: SSD Cache | Block Operations | timeseries | [Top $TopResources Write Block Ops/s](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=62) | +/// + + + +### eseries_ssd_cache_write_ops + +SSD cache write operations per second per controller + + +| API | Endpoint | Metric | Template | +|--------|----------|--------|---------| +| REST | `storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics` | `statistics.writes` | conf/eseriesperf/11.80.0/ssd_cache.yaml | + +The `eseries_ssd_cache_write_ops` metric is visualized in the following Grafana dashboards: + +/// html | div.grafana-table +| Dashboard | Row | Type | Panel | +|--------|----------|--------|--------| +| E-Series: SSD Cache | IOPS | timeseries | [Top $TopResources Write IOPS](/d/eseries-ssd-cache/e-series3a-ssd cache?orgId=1&viewPanel=52) | +/// + + + ### eseries_thermal_sensor_labels This metric provides information about thermal sensors. diff --git a/docs/ontap-metrics.md b/docs/ontap-metrics.md index 62c573863..de49c1610 100644 --- a/docs/ontap-metrics.md +++ b/docs/ontap-metrics.md @@ -7,7 +7,7 @@ These can be generated on demand by running `bin/harvest grafana metrics`. See - More information about ONTAP REST performance counters can be found [here](https://docs.netapp.com/us-en/ontap-pcmap-9121/index.html). ``` -Creation Date : 2026-Mar-16 +Creation Date : 2026-Apr-03 ONTAP Version: 9.16.1 ``` diff --git a/docs/storagegrid-metrics.md b/docs/storagegrid-metrics.md index 3b4c1696a..062771eff 100644 --- a/docs/storagegrid-metrics.md +++ b/docs/storagegrid-metrics.md @@ -5,7 +5,7 @@ These can be generated on demand by running `bin/harvest grafana metrics`. See [#1577](https://github.com/NetApp/harvest/issues/1577#issue-1471478260) for details. ``` -Creation Date : 2026-Mar-16 +Creation Date : 2026-Apr-03 StorageGrid Version: 11.6.0 ``` diff --git a/grafana/dashboards/eseries/ssd_cache.json b/grafana/dashboards/eseries/ssd_cache.json new file mode 100644 index 000000000..2df858787 --- /dev/null +++ b/grafana/dashboards/eseries/ssd_cache.json @@ -0,0 +1,3892 @@ +{ + "__elements": {}, + "__inputs": [ + { + "description": "", + "label": "prometheus", + "name": "DS_PROMETHEUS", + "pluginId": "prometheus", + "pluginName": "Prometheus", + "type": "datasource" + } + ], + "__requires": [ + { + "id": "grafana", + "name": "Grafana", + "type": "grafana", + "version": "12.3.2" + }, + { + "id": "prometheus", + "name": "Prometheus", + "type": "datasource", + "version": "1.0.0" + }, + { + "id": "table", + "name": "Table", + "type": "panel", + "version": "" + }, + { + "id": "timeseries", + "name": "Time series", + "type": "panel", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "description": "E-Series SSD Cache - Capacity and Performance", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "id": null, + "links": [ + { + "asDropdown": true, + "icon": "external link", + "includeVars": true, + "keepTime": true, + "tags": [ + "eseries", + "harvest" + ], + "targetBlank": false, + "title": "Related Dashboards", + "tooltip": "", + "type": "dashboards", + "url": "" + } + ], + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "panels": [], + "title": "Highlights", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Overall SSD cache hit percentage (full cache hits / total read+write ops).", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 1 + }, + "id": 2, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "eseries_ssd_cache_hit_percent{array=~\"$Array\",datacenter=~\"$Datacenter\"}\nand on (datacenter, array, controller, ssd_cache_id, ssd_cache)\n topk(\n $TopResources,\n max by (datacenter, array, controller, ssd_cache_id, ssd_cache) (\n avg_over_time(eseries_ssd_cache_hit_percent{array=~\"$Array\",datacenter=~\"$Datacenter\"}[3h] @ end())\n )\n )", + "interval": "", + "legendFormat": "{{array}} - {{controller}} - {{ssd_cache}}", + "range": true, + "refId": "A" + } + ], + "title": "Top $TopResources Cache Hit %", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Percentage of SSD cache capacity currently allocated.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 1 + }, + "id": 83, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "max by (datacenter, array, controller, ssd_cache_id, ssd_cache) (\n eseries_ssd_cache_allocation_percent{array=~\"$Array\",datacenter=~\"$Datacenter\"}\n )\nand on (datacenter, array, controller, ssd_cache_id, ssd_cache)\n topk(\n $TopResources,\n max by (datacenter, array, controller, ssd_cache_id, ssd_cache) (\n avg_over_time(\n eseries_ssd_cache_allocation_percent{array=~\"$Array\",datacenter=~\"$Datacenter\"}[3h] @ end()\n )\n )\n )", + "interval": "", + "legendFormat": "{{array}} - {{controller}} - {{ssd_cache}}", + "range": true, + "refId": "A" + } + ], + "title": "Top $TopResources Cache Allocation %", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Percentage of allocated SSD cache that is actively used (populated dirty / allocated).", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 1 + }, + "id": 84, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "max by (datacenter, array, controller, ssd_cache_id, ssd_cache) (\n eseries_ssd_cache_utilization_percent{array=~\"$Array\",datacenter=~\"$Datacenter\"}\n )\nand on (datacenter, array, controller, ssd_cache_id, ssd_cache)\n topk(\n $TopResources,\n max by (datacenter, array, controller, ssd_cache_id, ssd_cache) (\n avg_over_time(\n eseries_ssd_cache_utilization_percent{array=~\"$Array\",datacenter=~\"$Datacenter\"}[3h] @ end()\n )\n )\n )", + "interval": "", + "legendFormat": "{{array}} - {{controller}} - {{ssd_cache}}", + "range": true, + "refId": "A" + } + ], + "title": "Top $TopResources Cache Utilization %", + "type": "timeseries" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 9 + }, + "id": 10, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "SSD cache configuration and capacity summary per array.", + "fieldConfig": { + "defaults": { + "custom": { + "align": "left", + "cellOptions": { + "type": "auto" + }, + "filterable": true, + "footer": { + "reducers": [] + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgb(31, 176, 196)", + "value": 0 + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Current Capacity" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Available Size" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Maximum Capacity Allowed" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Additional Capacity Allowed" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "DA Capable" + }, + "properties": [ + { + "id": "mappings", + "value": [ + { + "options": { + "false": { + "index": 1, + "text": "No" + }, + "true": { + "index": 0, + "text": "Yes" + } + }, + "type": "value" + } + ] + } + ] + } + ] + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 10 + }, + "id": 11, + "interval": "1m", + "maxDataPoints": 2, + "options": { + "cellHeight": "sm", + "showHeader": true, + "sortBy": [ + { + "desc": false, + "displayName": "Array" + } + ] + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "label_join(\n eseries_ssd_cache_labels{array=~\"$Array\",datacenter=~\"$Datacenter\"},\n \"unique_id\",\n \"-\",\n \"datacenter\",\n \"array\",\n \"ssd_cache_id\"\n)", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "label_join(\n eseries_ssd_cache_current_capacity{array=~\"$Array\",datacenter=~\"$Datacenter\"},\n \"unique_id\",\n \"-\",\n \"datacenter\",\n \"array\",\n \"ssd_cache_id\"\n)", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "label_join(\n eseries_ssd_cache_max_capacity{array=~\"$Array\",datacenter=~\"$Datacenter\"},\n \"unique_id\",\n \"-\",\n \"datacenter\",\n \"array\",\n \"ssd_cache_id\"\n)", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "E" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "label_join(\n eseries_ssd_cache_additional_capacity{array=~\"$Array\",datacenter=~\"$Datacenter\"},\n \"unique_id\",\n \"-\",\n \"datacenter\",\n \"array\",\n \"ssd_cache_id\"\n)", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "F" + } + ], + "title": "SSD Cache Overview", + "transformations": [ + { + "id": "seriesToColumns", + "options": { + "byField": "unique_id" + } + }, + { + "id": "renameByRegex", + "options": { + "regex": "(.*) 1$", + "renamePattern": "$1" + } + }, + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": [ + "array", + "datacenter", + "ssd_cache", + "status", + "type", + "security_level", + "da_capable", + "Value #B", + "Value #C", + "Value #D", + "Value #E", + "Value #F", + "Value #G" + ] + } + } + }, + { + "id": "organize", + "options": { + "excludeByName": { + "array 2": true, + "array 3": true, + "array 4": true, + "array 5": true, + "array 6": true, + "array 7": true, + "datacenter 2": true, + "datacenter 3": true, + "datacenter 4": true, + "datacenter 5": true, + "datacenter 6": true, + "datacenter 7": true, + "ssd_cache 2": true, + "ssd_cache 3": true, + "ssd_cache 4": true + }, + "includeByName": {}, + "indexByName": { + "Value #B": 8, + "Value #D": 9, + "Value #E": 7, + "Value #F": 10, + "Value #G": 11, + "array": 1, + "array 2": 12, + "array 3": 15, + "array 4": 17, + "array 5": 20, + "array 6": 23, + "da_capable": 6, + "datacenter": 0, + "datacenter 2": 13, + "datacenter 3": 16, + "datacenter 4": 18, + "datacenter 5": 21, + "datacenter 6": 24, + "security_level": 5, + "ssd_cache": 2, + "ssd_cache 2": 14, + "ssd_cache 3": 19, + "ssd_cache 4": 22, + "status": 3, + "type": 4 + }, + "renameByName": { + "Value #B": "Current Capacity", + "Value #C": "", + "Value #D": "", + "Value #E": "Maximum Capacity Allowed", + "Value #F": "Additional Capacity Allowed", + "Value #G": "", + "array": "Array", + "da_capable": "DA Capable", + "datacenter": "Datacenter", + "security_level": "Security Level", + "ssd_cache": "Cache Name", + "status": "Status", + "type": "Type" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Maximum SSD cache capacity allowed per array.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 20 + }, + "id": 17, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "eseries_ssd_cache_max_capacity{array=~\"$Array\",datacenter=~\"$Datacenter\"}\nand on (datacenter, array, ssd_cache)\n topk(\n $TopResources,\n max by (datacenter, array, ssd_cache) (\n avg_over_time(eseries_ssd_cache_max_capacity{array=~\"$Array\",datacenter=~\"$Datacenter\"}[3h] @ end())\n )\n )", + "interval": "", + "legendFormat": "{{array}} - {{ssd_cache}}", + "refId": "A" + } + ], + "title": "Top $TopResources Maximum Capacity Allowed", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "SSD cache current capacity over time.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 20 + }, + "id": 12, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "eseries_ssd_cache_current_capacity{array=~\"$Array\",datacenter=~\"$Datacenter\"}\nand on (datacenter, array, ssd_cache)\n topk(\n $TopResources,\n max by (datacenter, array, ssd_cache) (\n avg_over_time(\n eseries_ssd_cache_current_capacity{array=~\"$Array\",datacenter=~\"$Datacenter\"}[3h] @ end()\n )\n )\n )", + "interval": "", + "legendFormat": "{{array}} - {{ssd_cache}}", + "refId": "A" + } + ], + "title": "Top $TopResources Current Capacity", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Additional SSD cache capacity that can still be added per array.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 28 + }, + "id": 18, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "eseries_ssd_cache_additional_capacity{array=~\"$Array\",datacenter=~\"$Datacenter\"}\nand on (datacenter, array, ssd_cache)\n topk(\n $TopResources,\n max by (datacenter, array, ssd_cache) (\n avg_over_time(\n eseries_ssd_cache_additional_capacity{array=~\"$Array\",datacenter=~\"$Datacenter\"}[3h] @ end()\n )\n )\n )", + "interval": "", + "legendFormat": "{{array}} - {{ssd_cache}}", + "refId": "A" + } + ], + "title": "Top $TopResources Additional Capacity Allowed", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "SSD cache allocated size.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 28 + }, + "id": 13, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "max by (array, datacenter, ssd_cache) (\n eseries_ssd_cache_allocated_size{array=~\"$Array\",datacenter=~\"$Datacenter\"}\n )\nand on (datacenter, array, ssd_cache)\n topk(\n $TopResources,\n max by (datacenter, array, ssd_cache) (\n avg_over_time(\n eseries_ssd_cache_allocated_size{array=~\"$Array\",datacenter=~\"$Datacenter\"}[3h] @ end()\n )\n )\n )", + "interval": "", + "legendFormat": "{{array}} - {{ssd_cache}}", + "range": true, + "refId": "A" + } + ], + "title": "Top $TopResources Allocated Size", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "SSD cache available (free) size.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 36 + }, + "id": 14, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "max by (array, datacenter, ssd_cache) (\n eseries_ssd_cache_available_size{array=~\"$Array\",datacenter=~\"$Datacenter\"}\n )\nand on (datacenter, array, ssd_cache)\n topk(\n $TopResources,\n max by (datacenter, array, ssd_cache) (\n avg_over_time(\n eseries_ssd_cache_available_size{array=~\"$Array\",datacenter=~\"$Datacenter\"}[3h] @ end()\n )\n )\n )", + "interval": "", + "legendFormat": "{{array}} - {{ssd_cache}}", + "range": true, + "refId": "A" + } + ], + "title": "Top $TopResources Available Size", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Percentage of SSD cache capacity that is allocated.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 36 + }, + "id": 15, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "max by (array, datacenter, ssd_cache) (\n eseries_ssd_cache_allocation_percent{array=~\"$Array\",datacenter=~\"$Datacenter\"}\n )\nand on (datacenter, array, ssd_cache)\n topk(\n $TopResources,\n max by (datacenter, array, ssd_cache) (\n avg_over_time(\n eseries_ssd_cache_allocation_percent{array=~\"$Array\",datacenter=~\"$Datacenter\"}[3h] @ end()\n )\n )\n )", + "interval": "", + "legendFormat": "{{array}} - {{ssd_cache}}", + "range": true, + "refId": "A" + } + ], + "title": "Top $TopResources Allocation %", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Percentage of allocated SSD cache that is actually populated with data.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 44 + }, + "id": 16, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "max by (array, datacenter, ssd_cache) (\n eseries_ssd_cache_utilization_percent{array=~\"$Array\",datacenter=~\"$Datacenter\"}\n )\nand on (datacenter, array, ssd_cache)\n topk(\n $TopResources,\n max by (datacenter, array, ssd_cache) (\n avg_over_time(\n eseries_ssd_cache_utilization_percent{array=~\"$Array\",datacenter=~\"$Datacenter\"}[3h] @ end()\n )\n )\n )", + "interval": "", + "legendFormat": "{{array}} - {{ssd_cache}}", + "range": true, + "refId": "A" + } + ], + "title": "Top $TopResources Utilization %", + "type": "timeseries" + } + ], + "title": "Capacity", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 10 + }, + "id": 20, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "SSD drives contributing to the cache, with raw capacity per drive.", + "fieldConfig": { + "defaults": { + "custom": { + "align": "left", + "cellOptions": { + "type": "auto" + }, + "filterable": true, + "footer": { + "reducers": [] + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgb(31, 176, 196)", + "value": 0 + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Raw Capacity" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + } + ] + }, + "gridPos": { + "h": 12, + "w": 24, + "x": 0, + "y": 165 + }, + "id": 21, + "interval": "1m", + "maxDataPoints": 2, + "options": { + "cellHeight": "sm", + "showHeader": true, + "sortBy": [ + { + "desc": false, + "displayName": "Drive" + } + ] + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "label_join(\n eseries_ssd_cache_drive_labels{array=~\"$Array\",datacenter=~\"$Datacenter\"},\n \"unique_id\",\n \"-\",\n \"datacenter\",\n \"array\",\n \"ssd_cache\",\n \"ssd_cache_id\",\n \"drive\"\n)", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "label_join(\n eseries_ssd_cache_drive_raw_capacity{array=~\"$Array\",datacenter=~\"$Datacenter\"},\n \"unique_id\",\n \"-\",\n \"datacenter\",\n \"array\",\n \"ssd_cache\",\n \"ssd_cache_id\",\n \"drive\"\n)", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "B" + } + ], + "title": "SSD Cache Drives", + "transformations": [ + { + "id": "seriesToColumns", + "options": { + "byField": "unique_id" + } + }, + { + "id": "renameByRegex", + "options": { + "regex": "(.*) 1$", + "renamePattern": "$1" + } + }, + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": [ + "datacenter", + "array", + "ssd_cache", + "drive", + "Value #B" + ] + } + } + }, + { + "id": "organize", + "options": { + "excludeByName": {}, + "indexByName": { + "Value #B": 4, + "array": 1, + "datacenter": 0, + "drive": 3, + "ssd_cache": 2 + }, + "renameByName": { + "Value #B": "Raw Capacity", + "array": "Array", + "datacenter": "Datacenter", + "drive": "Drive", + "ssd_cache": "Cache" + } + } + } + ], + "type": "table" + } + ], + "title": "Drives", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 11 + }, + "id": 30, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Volumes that are cached by the SSD cache.", + "fieldConfig": { + "defaults": { + "custom": { + "align": "left", + "cellOptions": { + "type": "auto" + }, + "filterable": true, + "footer": { + "reducers": [] + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgb(31, 176, 196)", + "value": 0 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 24, + "x": 0, + "y": 194 + }, + "id": 31, + "interval": "1m", + "maxDataPoints": 2, + "options": { + "cellHeight": "sm", + "showHeader": true, + "sortBy": [ + { + "desc": false, + "displayName": "Volume" + } + ] + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "eseries_ssd_cache_volume_labels{array=~\"$Array\",datacenter=~\"$Datacenter\"}", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Cached Volumes", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true, + "Value": true, + "__name__": true, + "instance": true, + "job": true, + "ssd_cache_id": true + }, + "indexByName": { + "array": 1, + "datacenter": 0, + "ssd_cache": 2, + "volume": 3 + }, + "renameByName": { + "array": "Array", + "datacenter": "Datacenter", + "ssd_cache": "Cache", + "volume": "Volume" + } + } + } + ], + "type": "table" + } + ], + "title": "Cached Volumes", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 12 + }, + "id": 40, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Full cache hits as a percentage of total read+write operations.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 0, + "y": 195 + }, + "id": 41, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "eseries_ssd_cache_hit_percent{array=~\"$Array\",datacenter=~\"$Datacenter\"}\nand on (datacenter, array, controller)\n topk(\n $TopResources,\n max by (datacenter, array, controller) (\n avg_over_time(eseries_ssd_cache_hit_percent{array=~\"$Array\",datacenter=~\"$Datacenter\"}[3h] @ end())\n )\n )", + "interval": "", + "legendFormat": "{{array}} - {{controller}} - {{ssd_cache}}", + "refId": "A" + } + ], + "title": "Top $TopResources Cache Hit %", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Full cache hits as a percentage of read operations.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 12, + "y": 195 + }, + "id": 42, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "eseries_ssd_cache_full_cache_hit_percent{array=~\"$Array\",datacenter=~\"$Datacenter\"}\nand on (datacenter, array, controller)\n topk(\n $TopResources,\n max by (datacenter, array, controller) (\n avg_over_time(\n eseries_ssd_cache_full_cache_hit_percent{array=~\"$Array\",datacenter=~\"$Datacenter\"}[3h] @ end()\n )\n )\n )", + "interval": "", + "legendFormat": "{{array}} - {{controller}} - {{ssd_cache}}", + "refId": "A" + } + ], + "title": "Top $TopResources Full Cache Hit %", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Partial cache hits as a percentage of read operations.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 0, + "y": 243 + }, + "id": 43, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "eseries_ssd_cache_partial_cache_hit_percent{array=~\"$Array\",datacenter=~\"$Datacenter\"}\nand on (datacenter, array, controller)\n topk(\n $TopResources,\n max by (datacenter, array, controller) (\n avg_over_time(\n eseries_ssd_cache_partial_cache_hit_percent{array=~\"$Array\",datacenter=~\"$Datacenter\"}[3h] @ end()\n )\n )\n )", + "interval": "", + "legendFormat": "{{array}} - {{controller}} - {{ssd_cache}}", + "refId": "A" + } + ], + "title": "Top $TopResources Partial Cache Hit %", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Complete cache misses as a percentage of read operations.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 12, + "y": 243 + }, + "id": 44, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "eseries_ssd_cache_complete_cache_miss_percent{array=~\"$Array\",datacenter=~\"$Datacenter\"}\nand on (datacenter, array, controller)\n topk(\n $TopResources,\n max by (datacenter, array, controller) (\n avg_over_time(\n eseries_ssd_cache_complete_cache_miss_percent{array=~\"$Array\",datacenter=~\"$Datacenter\"}[3h] @ end()\n )\n )\n )", + "interval": "", + "legendFormat": "{{array}} - {{controller}} - {{ssd_cache}}", + "refId": "A" + } + ], + "title": "Top $TopResources Complete Cache Miss %", + "type": "timeseries" + } + ], + "title": "Cache Hit Performance", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 13 + }, + "id": 50, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "SSD cache read operations per second per controller.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "iops" + }, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 0, + "y": 196 + }, + "id": 51, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "eseries_ssd_cache_read_ops{array=~\"$Array\",datacenter=~\"$Datacenter\"}\nand on (datacenter, array, controller)\n topk(\n $TopResources,\n max by (datacenter, array, controller) (\n avg_over_time(eseries_ssd_cache_read_ops{array=~\"$Array\",datacenter=~\"$Datacenter\"}[3h] @ end())\n )\n )", + "interval": "", + "legendFormat": "{{array}} - {{controller}} - {{ssd_cache}}", + "refId": "A" + } + ], + "title": "Top $TopResources Read IOPS", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "SSD cache write operations per second per controller.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "iops" + }, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 12, + "y": 196 + }, + "id": 52, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "eseries_ssd_cache_write_ops{array=~\"$Array\",datacenter=~\"$Datacenter\"}\nand on (datacenter, array, controller)\n topk(\n $TopResources,\n max by (datacenter, array, controller) (\n avg_over_time(eseries_ssd_cache_write_ops{array=~\"$Array\",datacenter=~\"$Datacenter\"}[3h] @ end())\n )\n )", + "interval": "", + "legendFormat": "{{array}} - {{controller}} - {{ssd_cache}}", + "refId": "A" + } + ], + "title": "Top $TopResources Write IOPS", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Full cache hit operations per second per controller.", + "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" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "iops" + }, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 8, + "x": 0, + "y": 208 + }, + "id": 53, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.1.8", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "eseries_ssd_cache_full_cache_hit_ops{array=~\"$Array\",datacenter=~\"$Datacenter\"}\nand on (datacenter, array, controller)\n topk(\n $TopResources,\n max by (datacenter, array, controller) (\n avg_over_time(\n eseries_ssd_cache_full_cache_hit_ops{array=~\"$Array\",datacenter=~\"$Datacenter\"}[3h] @ end()\n )\n )\n )", + "interval": "", + "legendFormat": "{{array}} - {{controller}} - {{ssd_cache}}", + "refId": "A" + } + ], + "title": "Top $TopResources Full Cache Hit IOPS", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Partial cache hit operations per second per controller.", + "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" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "iops" + }, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 8, + "x": 8, + "y": 208 + }, + "id": 54, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.1.8", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "eseries_ssd_cache_partial_cache_hit_ops{array=~\"$Array\",datacenter=~\"$Datacenter\"}\nand on (datacenter, array, controller)\n topk(\n $TopResources,\n max by (datacenter, array, controller) (\n avg_over_time(\n eseries_ssd_cache_partial_cache_hit_ops{array=~\"$Array\",datacenter=~\"$Datacenter\"}[3h] @ end()\n )\n )\n )", + "interval": "", + "legendFormat": "{{array}} - {{controller}} - {{ssd_cache}}", + "refId": "A" + } + ], + "title": "Top $TopResources Partial Cache Hit IOPS", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Complete cache miss operations per second per controller.", + "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" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "iops" + }, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 8, + "x": 16, + "y": 208 + }, + "id": 55, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.1.8", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "eseries_ssd_cache_complete_cache_miss_ops{array=~\"$Array\",datacenter=~\"$Datacenter\"}\nand on (datacenter, array, controller)\n topk(\n $TopResources,\n max by (datacenter, array, controller) (\n avg_over_time(\n eseries_ssd_cache_complete_cache_miss_ops{array=~\"$Array\",datacenter=~\"$Datacenter\"}[3h] @ end()\n )\n )\n )", + "interval": "", + "legendFormat": "{{array}} - {{controller}} - {{ssd_cache}}", + "refId": "A" + } + ], + "title": "Top $TopResources Cache Miss IOPS", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Populate-on-read operations per second: cache misses that trigger an SSD cache fill from disk.", + "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" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "iops" + }, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 0, + "y": 220 + }, + "id": 56, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.1.8", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "eseries_ssd_cache_populate_on_read_ops{array=~\"$Array\",datacenter=~\"$Datacenter\"}\nand on (datacenter, array, controller)\n topk(\n $TopResources,\n max by (datacenter, array, controller) (\n avg_over_time(\n eseries_ssd_cache_populate_on_read_ops{array=~\"$Array\",datacenter=~\"$Datacenter\"}[3h] @ end()\n )\n )\n )", + "interval": "", + "legendFormat": "{{array}} - {{controller}} - {{ssd_cache}}", + "refId": "A" + } + ], + "title": "Top $TopResources Populate-on-Read IOPS", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Populate-on-write operations per second: write IOs that also fill in the SSD cache.", + "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" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "iops" + }, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 12, + "y": 220 + }, + "id": 57, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.1.8", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "eseries_ssd_cache_populate_on_write_ops{array=~\"$Array\",datacenter=~\"$Datacenter\"}\nand on (datacenter, array, controller)\n topk(\n $TopResources,\n max by (datacenter, array, controller) (\n avg_over_time(\n eseries_ssd_cache_populate_on_write_ops{array=~\"$Array\",datacenter=~\"$Datacenter\"}[3h] @ end()\n )\n )\n )", + "interval": "", + "legendFormat": "{{array}} - {{controller}} - {{ssd_cache}}", + "refId": "A" + } + ], + "title": "Top $TopResources Populate-on-Write IOPS", + "type": "timeseries" + } + ], + "title": "IOPS", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 14 + }, + "id": 60, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Read block operations (cache block reads) per second per controller.", + "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" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "iops" + }, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 0, + "y": 555 + }, + "id": 61, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.1.8", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "eseries_ssd_cache_read_block_ops{array=~\"$Array\",datacenter=~\"$Datacenter\"}\nand on (datacenter, array, controller)\n topk(\n $TopResources,\n max by (datacenter, array, controller) (\n avg_over_time(\n eseries_ssd_cache_read_block_ops{array=~\"$Array\",datacenter=~\"$Datacenter\"}[3h] @ end()\n )\n )\n )", + "interval": "", + "legendFormat": "{{array}} - {{controller}} - {{ssd_cache}}", + "refId": "A" + } + ], + "title": "Top $TopResources Read Block Ops/s", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Write block operations (cache block writes) per second per controller.", + "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" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "iops" + }, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 12, + "y": 555 + }, + "id": 62, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.1.8", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "eseries_ssd_cache_write_block_ops{array=~\"$Array\",datacenter=~\"$Datacenter\"}\nand on (datacenter, array, controller)\n topk(\n $TopResources,\n max by (datacenter, array, controller) (\n avg_over_time(\n eseries_ssd_cache_write_block_ops{array=~\"$Array\",datacenter=~\"$Datacenter\"}[3h] @ end()\n )\n )\n )", + "interval": "", + "legendFormat": "{{array}} - {{controller}} - {{ssd_cache}}", + "refId": "A" + } + ], + "title": "Top $TopResources Write Block Ops/s", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Full cache hit block operations per second per controller.", + "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" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "iops" + }, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 8, + "x": 0, + "y": 567 + }, + "id": 63, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.1.8", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "eseries_ssd_cache_full_cache_hit_block_ops{array=~\"$Array\",datacenter=~\"$Datacenter\"}\nand on (datacenter, array, controller)\n topk(\n $TopResources,\n max by (datacenter, array, controller) (\n avg_over_time(\n eseries_ssd_cache_full_cache_hit_block_ops{array=~\"$Array\",datacenter=~\"$Datacenter\"}[3h] @ end()\n )\n )\n )", + "interval": "", + "legendFormat": "{{array}} - {{controller}} - {{ssd_cache}}", + "refId": "A" + } + ], + "title": "Top $TopResources Full Cache Hit Block Ops/s", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Partial cache hit block operations per second per controller.", + "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" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "iops" + }, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 8, + "x": 8, + "y": 567 + }, + "id": 64, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.1.8", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "eseries_ssd_cache_partial_cache_hit_block_ops{array=~\"$Array\",datacenter=~\"$Datacenter\"}\nand on (datacenter, array, controller)\n topk(\n $TopResources,\n max by (datacenter, array, controller) (\n avg_over_time(\n eseries_ssd_cache_partial_cache_hit_block_ops{array=~\"$Array\",datacenter=~\"$Datacenter\"}[3h] @ end()\n )\n )\n )", + "interval": "", + "legendFormat": "{{array}} - {{controller}} - {{ssd_cache}}", + "refId": "A" + } + ], + "title": "Top $TopResources Partial Cache Hit Block Ops/s", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Complete cache miss block operations per second per controller.", + "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" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "iops" + }, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 8, + "x": 16, + "y": 567 + }, + "id": 65, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.1.8", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "eseries_ssd_cache_complete_cache_miss_block_ops{array=~\"$Array\",datacenter=~\"$Datacenter\"}\nand on (datacenter, array, controller)\n topk(\n $TopResources,\n max by (datacenter, array, controller) (\n avg_over_time(\n eseries_ssd_cache_complete_cache_miss_block_ops{array=~\"$Array\",datacenter=~\"$Datacenter\"}[3h] @ end()\n )\n )\n )", + "interval": "", + "legendFormat": "{{array}} - {{controller}} - {{ssd_cache}}", + "refId": "A" + } + ], + "title": "Top $TopResources Cache Miss Block Ops/s", + "type": "timeseries" + } + ], + "title": "Block Operations", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 15 + }, + "id": 70, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Cache clean population (valid cached data not yet written back to disk).", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 170 + }, + "id": 71, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "max by (array, datacenter, ssd_cache) (\n eseries_ssd_cache_populated_clean_size{array=~\"$Array\",datacenter=~\"$Datacenter\"}\n )\nand on (datacenter, array, ssd_cache)\n topk(\n $TopResources,\n max by (datacenter, array, ssd_cache) (\n avg_over_time(\n eseries_ssd_cache_populated_clean_size{array=~\"$Array\",datacenter=~\"$Datacenter\"}[3h] @ end()\n )\n )\n )", + "interval": "", + "legendFormat": "{{array}} - {{ssd_cache}}", + "refId": "A" + } + ], + "title": "Top $TopResources Populated Clean Size", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Cache dirty population (modified data in cache not yet flushed to disk).", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 170 + }, + "id": 72, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "max by (array, datacenter, ssd_cache) (\n eseries_ssd_cache_populated_dirty_size{array=~\"$Array\",datacenter=~\"$Datacenter\"}\n )\nand on (datacenter, array, ssd_cache)\n topk(\n $TopResources,\n max by (datacenter, array, ssd_cache) (\n avg_over_time(\n eseries_ssd_cache_populated_dirty_size{array=~\"$Array\",datacenter=~\"$Datacenter\"}[3h] @ end()\n )\n )\n )", + "interval": "", + "legendFormat": "{{array}} - {{ssd_cache}}", + "refId": "A" + } + ], + "title": "Top $TopResources Populated Dirty Size", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Cache invalidate operations per second: cache blocks explicitly invalidated.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "iops" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 178 + }, + "id": 73, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "eseries_ssd_cache_invalidate_ops{array=~\"$Array\",datacenter=~\"$Datacenter\"}\nand on (datacenter, array, controller)\n topk(\n $TopResources,\n max by (datacenter, array, controller) (\n avg_over_time(\n eseries_ssd_cache_invalidate_ops{array=~\"$Array\",datacenter=~\"$Datacenter\"}[3h] @ end()\n )\n )\n )", + "interval": "", + "legendFormat": "{{array}} - {{controller}} - {{ssd_cache}}", + "refId": "A" + } + ], + "title": "Top $TopResources Invalidate Ops/s", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Cache recycle operations per second: cache blocks recycled/evicted to free space.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "iops" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 178 + }, + "id": 74, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "eseries_ssd_cache_recycle_ops{array=~\"$Array\",datacenter=~\"$Datacenter\"}\nand on (datacenter, array, controller)\n topk(\n $TopResources,\n max by (datacenter, array, controller) (\n avg_over_time(eseries_ssd_cache_recycle_ops{array=~\"$Array\",datacenter=~\"$Datacenter\"}[3h] @ end())\n )\n )", + "interval": "", + "legendFormat": "{{array}} - {{controller}} - {{ssd_cache}}", + "refId": "A" + } + ], + "title": "Top $TopResources Recycle Ops/s", + "type": "timeseries" + } + ], + "title": "Cache Sizing", + "type": "row" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "tags": [ + "harvest", + "eseries" + ], + "templating": { + "list": [ + { + "current": {}, + "hide": 2, + "includeAll": false, + "label": "Data Source", + "name": "DS_PROMETHEUS", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "type": "datasource" + }, + { + "allValue": ".*", + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "definition": "label_values(eseries_ssd_cache_labels, datacenter)", + "includeAll": true, + "multi": true, + "name": "Datacenter", + "options": [], + "query": { + "query": "label_values(eseries_ssd_cache_labels, datacenter)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "sort": 7, + "type": "query" + }, + { + "allValue": ".*", + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "definition": "label_values(eseries_ssd_cache_labels{datacenter=~\"$Datacenter\"}, array)", + "includeAll": true, + "multi": true, + "name": "Array", + "options": [], + "query": { + "query": "label_values(eseries_ssd_cache_labels{datacenter=~\"$Datacenter\"}, array)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "sort": 7, + "type": "query" + }, + { + "current": { + "text": "5", + "value": "5" + }, + "includeAll": false, + "name": "TopResources", + "options": [ + { + "selected": false, + "text": "1", + "value": "1" + }, + { + "selected": false, + "text": "2", + "value": "2" + }, + { + "selected": false, + "text": "3", + "value": "3" + }, + { + "selected": false, + "text": "4", + "value": "4" + }, + { + "selected": true, + "text": "5", + "value": "5" + }, + { + "selected": false, + "text": "6", + "value": "6" + }, + { + "selected": false, + "text": "8", + "value": "8" + }, + { + "selected": false, + "text": "10", + "value": "10" + }, + { + "selected": false, + "text": "15", + "value": "15" + }, + { + "selected": false, + "text": "25", + "value": "25" + }, + { + "selected": false, + "text": "50", + "value": "50" + }, + { + "selected": false, + "text": "100", + "value": "100" + } + ], + "query": "1,2,3,4,5,6,8,10,15,25,50,100", + "type": "custom" + } + ] + }, + "time": { + "from": "now-3h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "E-Series: SSD Cache", + "uid": "eseries-ssd-cache", + "version": 1, + "weekStart": "" +} diff --git a/mcp/metadata/eseries_metrics.json b/mcp/metadata/eseries_metrics.json index 691c41062..215c33987 100644 --- a/mcp/metadata/eseries_metrics.json +++ b/mcp/metadata/eseries_metrics.json @@ -40,6 +40,39 @@ "eseries_host_labels": "This metric provides information about hosts connected to the storage array.", "eseries_power_supply_labels": "This metric provides information about power supplies.", "eseries_sfp_labels": "This metric provides information about SFP transceivers.", + "eseries_ssd_cache_additional_capacity": "Additional SSD cache capacity that can still be added to the array in bytes (maximum capacity minus current capacity)", + "eseries_ssd_cache_allocated_size": "Allocated size of the SSD cache per controller in bytes", + "eseries_ssd_cache_allocation_percent": "SSD cache allocation percentage per controller, calculated as allocated bytes divided by total cache size (allocated + available bytes)", + "eseries_ssd_cache_available_size": "Available size of the SSD cache per controller in bytes", + "eseries_ssd_cache_complete_cache_miss_block_ops": "Number of complete cache miss block operations per controller", + "eseries_ssd_cache_complete_cache_miss_ops": "Number of complete cache miss operations per controller", + "eseries_ssd_cache_complete_cache_miss_percent": "Percentage of read operations that resulted in a complete cache miss per controller", + "eseries_ssd_cache_current_capacity": "Current used capacity of the SSD cache in bytes", + "eseries_ssd_cache_drive_labels": "This metric provides information about drives assigned to an SSD cache.", + "eseries_ssd_cache_drive_raw_capacity": "Raw capacity of each drive contributing to the SSD cache in bytes", + "eseries_ssd_cache_full_cache_hit_block_ops": "Number of full cache hit block operations per controller", + "eseries_ssd_cache_full_cache_hit_ops": "Number of full cache hit operations per controller", + "eseries_ssd_cache_full_cache_hit_percent": "Percentage of read operations that resulted in a full cache hit per controller", + "eseries_ssd_cache_hit_percent": "SSD cache hit percentage per controller, calculated as full cache hits divided by total I/O operations (reads + writes)", + "eseries_ssd_cache_invalidate_ops": "Number of cache invalidate operations per controller", + "eseries_ssd_cache_labels": "This metric provides information about SSD caches.", + "eseries_ssd_cache_max_capacity": "Maximum SSD cache capacity allowed for the array in bytes", + "eseries_ssd_cache_partial_cache_hit_block_ops": "Number of partial cache hit block operations per controller", + "eseries_ssd_cache_partial_cache_hit_ops": "Number of partial cache hit operations per controller", + "eseries_ssd_cache_partial_cache_hit_percent": "Percentage of read operations that resulted in a partial cache hit per controller", + "eseries_ssd_cache_populate_on_read_block_ops": "Number of populate-on-read block operations per controller", + "eseries_ssd_cache_populate_on_read_ops": "Number of populate-on-read operations per controller", + "eseries_ssd_cache_populate_on_write_block_ops": "Number of populate-on-write block operations per controller", + "eseries_ssd_cache_populate_on_write_ops": "Number of populate-on-write operations per controller", + "eseries_ssd_cache_populated_clean_size": "Amount of clean (unmodified) data populated in the SSD cache per controller in bytes", + "eseries_ssd_cache_populated_dirty_size": "Amount of dirty (modified) data populated in the SSD cache per controller in bytes", + "eseries_ssd_cache_read_block_ops": "SSD cache read block operations per second per controller", + "eseries_ssd_cache_read_ops": "SSD cache read operations per second per controller", + "eseries_ssd_cache_recycle_ops": "Number of cache recycle operations per controller", + "eseries_ssd_cache_utilization_percent": "SSD cache utilization percentage per controller, calculated as populated data (clean + dirty bytes) divided by allocated bytes", + "eseries_ssd_cache_volume_labels": "This metric provides information about volumes mapped to an SSD cache.", + "eseries_ssd_cache_write_block_ops": "SSD cache write block operations per second per controller", + "eseries_ssd_cache_write_ops": "SSD cache write operations per second per controller", "eseries_thermal_sensor_labels": "This metric provides information about thermal sensors.", "eseries_volume_allocated_capacity": "Allocated capacity of the volume in bytes", "eseries_volume_block_size": "Block size of the volume in bytes", From 364818a5fa5985ececf3d7892a8ab0356e88b7ee Mon Sep 17 00:00:00 2001 From: rahulguptajss Date: Fri, 3 Apr 2026 16:03:42 +0530 Subject: [PATCH 2/5] feat: ESeries ssd-cache --- .../plugins/ssdcachestats/ssdcachestats.go | 11 -------- .../ssdcachestats/ssdcachestats_test.go | 28 ++----------------- conf/eseriesperf/11.80.0/ssd_cache.yaml | 2 -- docs/eseries-metrics.md | 20 ------------- mcp/metadata/eseries_metrics.json | 2 -- 5 files changed, 2 insertions(+), 61 deletions(-) diff --git a/cmd/collectors/eseriesperf/plugins/ssdcachestats/ssdcachestats.go b/cmd/collectors/eseriesperf/plugins/ssdcachestats/ssdcachestats.go index 67b605a14..b5abd4828 100644 --- a/cmd/collectors/eseriesperf/plugins/ssdcachestats/ssdcachestats.go +++ b/cmd/collectors/eseriesperf/plugins/ssdcachestats/ssdcachestats.go @@ -35,17 +35,6 @@ func (s *SsdCacheStats) Run(dataMap map[string]*matrix.Matrix) ([]*matrix.Matrix s.computePercent(data, "statistics.fullCacheHits", "statistics.reads", "full_cache_hit_percent") s.computePercent(data, "statistics.partialCacheHits", "statistics.reads", "partial_cache_hit_percent") s.computePercent(data, "statistics.completeCacheMiss", "statistics.reads", "complete_cache_miss_percent") - s.computePercent(data, "statistics.populateOnReads", "statistics.reads", "populate_on_read_percent") - - s.computePercent(data, "statistics.fullCacheHitBlocks", "statistics.readBlocks", "full_cache_hit_block_percent") - s.computePercent(data, "statistics.partialCacheHitBlocks", "statistics.readBlocks", "partial_cache_hit_block_percent") - s.computePercent(data, "statistics.completeCacheMissBlocks", "statistics.readBlocks", "complete_cache_miss_block_percent") - s.computePercent(data, "statistics.populateOnReadBlocks", "statistics.readBlocks", "populate_on_read_block_percent") - - s.computePercent(data, "statistics.populateOnWrites", "statistics.writes", "populate_on_write_percent") - s.computePercent(data, "statistics.invalidates", "statistics.writes", "invalidate_percent") - - s.computePercent(data, "statistics.populateOnWriteBlocks", "statistics.writeBlocks", "populate_on_write_block_percent") return nil, nil, nil } diff --git a/cmd/collectors/eseriesperf/plugins/ssdcachestats/ssdcachestats_test.go b/cmd/collectors/eseriesperf/plugins/ssdcachestats/ssdcachestats_test.go index c2c8dfa15..170630380 100644 --- a/cmd/collectors/eseriesperf/plugins/ssdcachestats/ssdcachestats_test.go +++ b/cmd/collectors/eseriesperf/plugins/ssdcachestats/ssdcachestats_test.go @@ -30,9 +30,7 @@ func createSsdCacheMatrix(t *testing.T) *matrix.Matrix { _, _ = mat.NewMetricFloat64("statistics.completeCacheMiss") _, _ = mat.NewMetricFloat64("statistics.completeCacheMissBlocks") _, _ = mat.NewMetricFloat64("statistics.populateOnReads") - _, _ = mat.NewMetricFloat64("statistics.populateOnReadBlocks") _, _ = mat.NewMetricFloat64("statistics.populateOnWrites") - _, _ = mat.NewMetricFloat64("statistics.populateOnWriteBlocks") _, _ = mat.NewMetricFloat64("statistics.invalidates") _, _ = mat.NewMetricFloat64("statistics.recycles") _, _ = mat.NewMetricFloat64("statistics.availableBytes") @@ -262,15 +260,6 @@ func TestSsdCacheStats_ComputePercent(t *testing.T) { denVal: 53.5, wantSet: true, }, - { - name: "invalidate_percent writes based", - numKey: "statistics.invalidates", - denKey: "statistics.writes", - resultName: "invalidate_percent", - numVal: 23.847, - denVal: 23.847, - wantSet: true, - }, { name: "zero denominator", numKey: "statistics.fullCacheHits", @@ -321,8 +310,8 @@ func TestSsdCacheStats_ZeroDenominator(t *testing.T) { "statistics.fullCacheHits", "statistics.fullCacheHitBlocks", "statistics.partialCacheHits", "statistics.partialCacheHitBlocks", "statistics.completeCacheMiss", "statistics.completeCacheMissBlocks", - "statistics.populateOnReads", "statistics.populateOnReadBlocks", - "statistics.populateOnWrites", "statistics.populateOnWriteBlocks", + "statistics.populateOnReads", + "statistics.populateOnWrites", "statistics.invalidates", "statistics.recycles", "statistics.availableBytes", "statistics.allocatedBytes", "statistics.populatedCleanBytes", "statistics.populatedDirtyBytes", @@ -391,9 +380,7 @@ func TestSsdCacheStats_Run(t *testing.T) { setMetric(t, mat, "statistics.completeCacheMiss", ctrl1, 0.0) setMetric(t, mat, "statistics.completeCacheMissBlocks", ctrl1, 0.0) setMetric(t, mat, "statistics.populateOnReads", ctrl1, 9.097) - setMetric(t, mat, "statistics.populateOnReadBlocks", ctrl1, 10678.44) setMetric(t, mat, "statistics.populateOnWrites", ctrl1, 1.556) - setMetric(t, mat, "statistics.populateOnWriteBlocks", ctrl1, 2724.61) setMetric(t, mat, "statistics.invalidates", ctrl1, 23.847) setMetric(t, mat, "statistics.recycles", ctrl1, 0.0) setMetric(t, mat, "statistics.availableBytes", ctrl1, 1599784091648.0) @@ -412,9 +399,7 @@ func TestSsdCacheStats_Run(t *testing.T) { setMetric(t, mat, "statistics.completeCacheMiss", ctrl2, 0.0) setMetric(t, mat, "statistics.completeCacheMissBlocks", ctrl2, 0.0) setMetric(t, mat, "statistics.populateOnReads", ctrl2, 7.521) - setMetric(t, mat, "statistics.populateOnReadBlocks", ctrl2, 8976.68) setMetric(t, mat, "statistics.populateOnWrites", ctrl2, 0.592) - setMetric(t, mat, "statistics.populateOnWriteBlocks", ctrl2, 1012.73) setMetric(t, mat, "statistics.invalidates", ctrl2, 25.592) setMetric(t, mat, "statistics.recycles", ctrl2, 0.0) setMetric(t, mat, "statistics.availableBytes", ctrl2, 1599784091648.0) @@ -442,14 +427,6 @@ func TestSsdCacheStats_Run(t *testing.T) { "full_cache_hit_percent", "partial_cache_hit_percent", "complete_cache_miss_percent", - "populate_on_read_percent", - "full_cache_hit_block_percent", - "partial_cache_hit_block_percent", - "complete_cache_miss_block_percent", - "populate_on_read_block_percent", - "populate_on_write_percent", - "invalidate_percent", - "populate_on_write_block_percent", } for _, name := range expectedMetrics { @@ -480,7 +457,6 @@ func TestSsdCacheStats_Run(t *testing.T) { checkMetricValue("allocation_percent", ctrl1, (alloc1/(alloc1+avail1))*100.0) checkMetricValue("utilization_percent", ctrl1, (clean1/alloc1)*100.0) checkMetricValue("full_cache_hit_percent", ctrl1, (hits1/reads1)*100.0) - checkMetricValue("invalidate_percent", ctrl1, 100.0) checkMetricValue("hit_percent", ctrl2, (hits2/(reads2+writes2))*100.0) } diff --git a/conf/eseriesperf/11.80.0/ssd_cache.yaml b/conf/eseriesperf/11.80.0/ssd_cache.yaml index 248ee87ba..1d20a92f5 100644 --- a/conf/eseriesperf/11.80.0/ssd_cache.yaml +++ b/conf/eseriesperf/11.80.0/ssd_cache.yaml @@ -14,9 +14,7 @@ counters: - statistics.invalidates => invalidate_ops - statistics.partialCacheHitBlocks => partial_cache_hit_block_ops - statistics.partialCacheHits => partial_cache_hit_ops - - statistics.populateOnReadBlocks => populate_on_read_block_ops - statistics.populateOnReads => populate_on_read_ops - - statistics.populateOnWriteBlocks => populate_on_write_block_ops - statistics.populateOnWrites => populate_on_write_ops - statistics.populatedCleanBytes => populated_clean_size - statistics.populatedDirtyBytes => populated_dirty_size diff --git a/docs/eseries-metrics.md b/docs/eseries-metrics.md index 9a3e4be28..054057855 100644 --- a/docs/eseries-metrics.md +++ b/docs/eseries-metrics.md @@ -1187,16 +1187,6 @@ The `eseries_ssd_cache_partial_cache_hit_percent` metric is visualized in the fo -### eseries_ssd_cache_populate_on_read_block_ops - -Number of populate-on-read block operations per controller - - -| API | Endpoint | Metric | Template | -|--------|----------|--------|---------| -| REST | `storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics` | `statistics.populateOnReadBlocks` | conf/eseriesperf/11.80.0/ssd_cache.yaml | - - ### eseries_ssd_cache_populate_on_read_ops Number of populate-on-read operations per controller @@ -1216,16 +1206,6 @@ The `eseries_ssd_cache_populate_on_read_ops` metric is visualized in the followi -### eseries_ssd_cache_populate_on_write_block_ops - -Number of populate-on-write block operations per controller - - -| API | Endpoint | Metric | Template | -|--------|----------|--------|---------| -| REST | `storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics` | `statistics.populateOnWriteBlocks` | conf/eseriesperf/11.80.0/ssd_cache.yaml | - - ### eseries_ssd_cache_populate_on_write_ops Number of populate-on-write operations per controller diff --git a/mcp/metadata/eseries_metrics.json b/mcp/metadata/eseries_metrics.json index 215c33987..6c88cd682 100644 --- a/mcp/metadata/eseries_metrics.json +++ b/mcp/metadata/eseries_metrics.json @@ -60,9 +60,7 @@ "eseries_ssd_cache_partial_cache_hit_block_ops": "Number of partial cache hit block operations per controller", "eseries_ssd_cache_partial_cache_hit_ops": "Number of partial cache hit operations per controller", "eseries_ssd_cache_partial_cache_hit_percent": "Percentage of read operations that resulted in a partial cache hit per controller", - "eseries_ssd_cache_populate_on_read_block_ops": "Number of populate-on-read block operations per controller", "eseries_ssd_cache_populate_on_read_ops": "Number of populate-on-read operations per controller", - "eseries_ssd_cache_populate_on_write_block_ops": "Number of populate-on-write block operations per controller", "eseries_ssd_cache_populate_on_write_ops": "Number of populate-on-write operations per controller", "eseries_ssd_cache_populated_clean_size": "Amount of clean (unmodified) data populated in the SSD cache per controller in bytes", "eseries_ssd_cache_populated_dirty_size": "Amount of dirty (modified) data populated in the SSD cache per controller in bytes", From a6a18c66a03794953af20d3157ed944e77eaca27 Mon Sep 17 00:00:00 2001 From: rahulguptajss Date: Fri, 3 Apr 2026 16:04:06 +0530 Subject: [PATCH 3/5] feat: ESeries ssd-cache --- cmd/tools/generate/eseries_counter.yaml | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/cmd/tools/generate/eseries_counter.yaml b/cmd/tools/generate/eseries_counter.yaml index b5b8a8999..15b5dfd2b 100644 --- a/cmd/tools/generate/eseries_counter.yaml +++ b/cmd/tools/generate/eseries_counter.yaml @@ -666,14 +666,6 @@ counters: ESeriesCounter: statistics.populateOnReads Template: conf/eseriesperf/11.80.0/ssd_cache.yaml - - Name: eseries_ssd_cache_populate_on_read_block_ops - Description: Number of populate-on-read block operations per controller - APIs: - - API: REST - Endpoint: storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics - ESeriesCounter: statistics.populateOnReadBlocks - Template: conf/eseriesperf/11.80.0/ssd_cache.yaml - - Name: eseries_ssd_cache_populate_on_write_ops Description: Number of populate-on-write operations per controller APIs: @@ -682,14 +674,6 @@ counters: ESeriesCounter: statistics.populateOnWrites Template: conf/eseriesperf/11.80.0/ssd_cache.yaml - - Name: eseries_ssd_cache_populate_on_write_block_ops - Description: Number of populate-on-write block operations per controller - APIs: - - API: REST - Endpoint: storage-systems/{array_id}/ssd-caches/{ssd_cache_id}/statistics - ESeriesCounter: statistics.populateOnWriteBlocks - Template: conf/eseriesperf/11.80.0/ssd_cache.yaml - - Name: eseries_ssd_cache_invalidate_ops Description: Number of cache invalidate operations per controller APIs: From a9f7e5d7c83022dfa8a87adec8938677f1b79f0b Mon Sep 17 00:00:00 2001 From: rahulguptajss Date: Fri, 3 Apr 2026 16:11:40 +0530 Subject: [PATCH 4/5] feat: ESeries ssd-cache --- cmd/collectors/eseriesperf/eseriesperf_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/collectors/eseriesperf/eseriesperf_test.go b/cmd/collectors/eseriesperf/eseriesperf_test.go index 40e2e2b8b..7188d9c11 100644 --- a/cmd/collectors/eseriesperf/eseriesperf_test.go +++ b/cmd/collectors/eseriesperf/eseriesperf_test.go @@ -538,9 +538,9 @@ func TestEseriesPerf_buildCounters_SsdCache(t *testing.T) { ep := newEseriesPerf("SsdCache", "ssd_cache.yaml") numCounters := len(ep.perfProp.counterInfo) - // 22 counters: 1 delta (timestamp), 16 rate, 4 raw + 1 auto-added timestamp - if numCounters < 21 { - t.Errorf("expected at least 21 counters, got %d", numCounters) + // 20 counters: 1 delta (timestamp), 14 rate, 4 raw + 1 auto-added timestamp + if numCounters < 19 { + t.Errorf("expected at least 19 counters, got %d", numCounters) } var rateCount, deltaCount, rawCount int @@ -562,8 +562,8 @@ func TestEseriesPerf_buildCounters_SsdCache(t *testing.T) { } } - if rateCount < 16 { - t.Errorf("expected at least 16 rate counters, got %d", rateCount) + if rateCount < 14 { + t.Errorf("expected at least 14 rate counters, got %d", rateCount) } if deltaCount < 1 { t.Errorf("expected at least 1 delta counter (timestamp), got %d", deltaCount) From 01fcb2e14ec832e7e46c612072c78264fc1b3f20 Mon Sep 17 00:00:00 2001 From: rahulguptajss Date: Fri, 3 Apr 2026 16:20:43 +0530 Subject: [PATCH 5/5] feat: ESeries ssd-cache --- .../eseries/plugins/ssdcachecapacity/ssdcachecapacity.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cmd/collectors/eseries/plugins/ssdcachecapacity/ssdcachecapacity.go b/cmd/collectors/eseries/plugins/ssdcachecapacity/ssdcachecapacity.go index e373c4631..22d88dead 100644 --- a/cmd/collectors/eseries/plugins/ssdcachecapacity/ssdcachecapacity.go +++ b/cmd/collectors/eseries/plugins/ssdcachecapacity/ssdcachecapacity.go @@ -125,11 +125,15 @@ func (s *SsdCacheCapacity) Run(dataMap map[string]*matrix.Matrix) ([]*matrix.Mat globalLabels := data.GetGlobalLabels() s.volumeMat.PurgeInstances() s.volumeMat.Reset() - s.volumeMat.SetGlobalLabels(globalLabels) + for k, v := range globalLabels { + s.volumeMat.SetGlobalLabel(k, v) + } s.driveMat.PurgeInstances() s.driveMat.Reset() - s.driveMat.SetGlobalLabels(globalLabels) + for k, v := range globalLabels { + s.driveMat.SetGlobalLabel(k, v) + } s.populateMappings(data) s.populateCapacityMetrics(data)