From 45fa9d798da6e352b8716724533afa5be02b9728 Mon Sep 17 00:00:00 2001
From: Aliaksandr Valialkin <valyala@victoriametrics.com>
Date: Tue, 14 Jun 2022 17:46:16 +0300
Subject: [PATCH] app/vmselect: accept `focusLabel` query arg at
 /api/v1/status/tsdb

---
 app/vmselect/netstorage/netstorage.go         | 171 ++++--------------
 app/vmselect/prometheus/prometheus.go         |  42 ++---
 .../prometheus/tsdb_status_response.qtpl      |   1 +
 .../prometheus/tsdb_status_response.qtpl.go   |  96 +++++-----
 app/vmstorage/transport/server.go             |  87 +++------
 docs/Single-server-VictoriaMetrics.md         |   4 +-
 lib/storage/index_db.go                       |  46 +++--
 lib/storage/index_db_test.go                  |  64 +++++--
 lib/storage/storage.go                        |   6 +-
 9 files changed, 208 insertions(+), 309 deletions(-)

diff --git a/app/vmselect/netstorage/netstorage.go b/app/vmselect/netstorage/netstorage.go
index 252b4eb407..7e46489cf9 100644
--- a/app/vmselect/netstorage/netstorage.go
+++ b/app/vmselect/netstorage/netstorage.go
@@ -930,14 +930,16 @@ func deduplicateStrings(a []string) []string {
 	return a
 }
 
-// GetTSDBStatusForDate returns tsdb status according to https://prometheus.io/docs/prometheus/latest/querying/api/#tsdb-stats
-func GetTSDBStatusForDate(qt *querytracer.Tracer, at *auth.Token, denyPartialResponse bool,
-	deadline searchutils.Deadline, date uint64, topN, maxMetrics int) (*storage.TSDBStatus, bool, error) {
-	qt = qt.NewChild("get tsdb stats for date=%d, topN=%d", date, topN)
+// GetTSDBStatus returns tsdb status according to https://prometheus.io/docs/prometheus/latest/querying/api/#tsdb-stats
+//
+// It accepts aribtrary filters on time series in sq.
+func GetTSDBStatus(qt *querytracer.Tracer, at *auth.Token, denyPartialResponse bool, sq *storage.SearchQuery, focusLabel string, topN int, deadline searchutils.Deadline) (*storage.TSDBStatus, bool, error) {
+	qt = qt.NewChild("get tsdb stats: %s, focusLabel=%q, topN=%d", sq, focusLabel, topN)
 	defer qt.Done()
 	if deadline.Exceeded() {
 		return nil, false, fmt.Errorf("timeout exceeded before starting the query processing: %s", deadline.String())
 	}
+	requestData := sq.Marshal(nil)
 	// Send the query to all the storage nodes in parallel.
 	type nodeResult struct {
 		status *storage.TSDBStatus
@@ -945,7 +947,7 @@ func GetTSDBStatusForDate(qt *querytracer.Tracer, at *auth.Token, denyPartialRes
 	}
 	snr := startStorageNodesRequest(qt, denyPartialResponse, func(qt *querytracer.Tracer, idx int, sn *storageNode) interface{} {
 		sn.tsdbStatusRequests.Inc()
-		status, err := sn.getTSDBStatusForDate(qt, at.AccountID, at.ProjectID, date, topN, maxMetrics, deadline)
+		status, err := sn.getTSDBStatus(qt, requestData, focusLabel, topN, deadline)
 		if err != nil {
 			sn.tsdbStatusErrors.Inc()
 			err = fmt.Errorf("cannot obtain tsdb status from vmstorage %s: %w", sn.connPool.Addr(), err)
@@ -1026,53 +1028,6 @@ func toTopHeapEntries(m map[string]uint64, topN int) []storage.TopHeapEntry {
 	return a
 }
 
-// GetTSDBStatusWithFilters returns tsdb status according to https://prometheus.io/docs/prometheus/latest/querying/api/#tsdb-stats
-//
-// It accepts aribtrary filters on time series in sq.
-func GetTSDBStatusWithFilters(qt *querytracer.Tracer, at *auth.Token, denyPartialResponse bool,
-	deadline searchutils.Deadline, sq *storage.SearchQuery, topN int) (*storage.TSDBStatus, bool, error) {
-	qt = qt.NewChild("get tsdb stats: %s, topN=%d", sq, topN)
-	defer qt.Done()
-	if deadline.Exceeded() {
-		return nil, false, fmt.Errorf("timeout exceeded before starting the query processing: %s", deadline.String())
-	}
-	requestData := sq.Marshal(nil)
-	// Send the query to all the storage nodes in parallel.
-	type nodeResult struct {
-		status *storage.TSDBStatus
-		err    error
-	}
-	snr := startStorageNodesRequest(qt, denyPartialResponse, func(qt *querytracer.Tracer, idx int, sn *storageNode) interface{} {
-		sn.tsdbStatusWithFiltersRequests.Inc()
-		status, err := sn.getTSDBStatusWithFilters(qt, requestData, topN, deadline)
-		if err != nil {
-			sn.tsdbStatusWithFiltersErrors.Inc()
-			err = fmt.Errorf("cannot obtain tsdb status with filters from vmstorage %s: %w", sn.connPool.Addr(), err)
-		}
-		return &nodeResult{
-			status: status,
-			err:    err,
-		}
-	})
-
-	// Collect results.
-	var statuses []*storage.TSDBStatus
-	isPartial, err := snr.collectResults(partialTSDBStatusResults, func(result interface{}) error {
-		nr := result.(*nodeResult)
-		if nr.err != nil {
-			return nr.err
-		}
-		statuses = append(statuses, nr.status)
-		return nil
-	})
-	if err != nil {
-		return nil, isPartial, fmt.Errorf("cannot fetch tsdb status with filters from vmstorage nodes: %w", err)
-	}
-
-	status := mergeTSDBStatuses(statuses, topN)
-	return status, isPartial, nil
-}
-
 // GetSeriesCount returns the number of unique series for the given at.
 func GetSeriesCount(qt *querytracer.Tracer, at *auth.Token, denyPartialResponse bool, deadline searchutils.Deadline) (uint64, bool, error) {
 	qt = qt.NewChild("get series count")
@@ -1499,12 +1454,6 @@ type storageNode struct {
 	// The number of errors during requests to tsdb status.
 	tsdbStatusErrors *metrics.Counter
 
-	// The number of requests to tsdb status.
-	tsdbStatusWithFiltersRequests *metrics.Counter
-
-	// The number of errors during requests to tsdb status.
-	tsdbStatusWithFiltersErrors *metrics.Counter
-
 	// The number of requests to seriesCount.
 	seriesCountRequests *metrics.Counter
 
@@ -1605,34 +1554,17 @@ func (sn *storageNode) getTagValueSuffixes(qt *querytracer.Tracer, accountID, pr
 	return suffixes, nil
 }
 
-func (sn *storageNode) getTSDBStatusForDate(qt *querytracer.Tracer, accountID, projectID uint32,
-	date uint64, topN, maxMetrics int, deadline searchutils.Deadline) (*storage.TSDBStatus, error) {
+func (sn *storageNode) getTSDBStatus(qt *querytracer.Tracer, requestData []byte, focusLabel string, topN int, deadline searchutils.Deadline) (*storage.TSDBStatus, error) {
 	var status *storage.TSDBStatus
 	f := func(bc *handshake.BufferedConn) error {
-		st, err := sn.getTSDBStatusForDateOnConn(bc, accountID, projectID, date, topN, maxMetrics)
+		st, err := sn.getTSDBStatusOnConn(bc, requestData, focusLabel, topN)
 		if err != nil {
 			return err
 		}
 		status = st
 		return nil
 	}
-	if err := sn.execOnConnWithPossibleRetry(qt, "tsdbStatus_v4", f, deadline); err != nil {
-		return nil, err
-	}
-	return status, nil
-}
-
-func (sn *storageNode) getTSDBStatusWithFilters(qt *querytracer.Tracer, requestData []byte, topN int, deadline searchutils.Deadline) (*storage.TSDBStatus, error) {
-	var status *storage.TSDBStatus
-	f := func(bc *handshake.BufferedConn) error {
-		st, err := sn.getTSDBStatusWithFiltersOnConn(bc, requestData, topN)
-		if err != nil {
-			return err
-		}
-		status = st
-		return nil
-	}
-	if err := sn.execOnConnWithPossibleRetry(qt, "tsdbStatusWithFilters_v3", f, deadline); err != nil {
+	if err := sn.execOnConnWithPossibleRetry(qt, "tsdbStatus_v5", f, deadline); err != nil {
 		return nil, err
 	}
 	return status, nil
@@ -2007,51 +1939,20 @@ func (sn *storageNode) getTagValueSuffixesOnConn(bc *handshake.BufferedConn, acc
 	return suffixes, nil
 }
 
-func (sn *storageNode) getTSDBStatusForDateOnConn(bc *handshake.BufferedConn, accountID, projectID uint32, date uint64, topN, maxMetrics int) (*storage.TSDBStatus, error) {
-	// Send the request to sn.
-	if err := sendAccountIDProjectID(bc, accountID, projectID); err != nil {
-		return nil, err
-	}
-	// date shouldn't exceed 32 bits, so send it as uint32.
-	if err := writeUint32(bc, uint32(date)); err != nil {
-		return nil, fmt.Errorf("cannot send date=%d to conn: %w", date, err)
-	}
-	// topN shouldn't exceed 32 bits, so send it as uint32.
-	if err := writeUint32(bc, uint32(topN)); err != nil {
-		return nil, fmt.Errorf("cannot send topN=%d to conn: %w", topN, err)
-	}
-	// maxMetrics shouldn't exceed 32 bits, so send it as uint32.
-	if err := writeUint32(bc, uint32(maxMetrics)); err != nil {
-		return nil, fmt.Errorf("cannot send maxMetrics=%d to conn: %w", maxMetrics, err)
-	}
-	if err := bc.Flush(); err != nil {
-		return nil, fmt.Errorf("cannot flush tsdbStatus args to conn: %w", err)
-	}
-
-	// Read response error.
-	buf, err := readBytes(nil, bc, maxErrorMessageSize)
-	if err != nil {
-		return nil, fmt.Errorf("cannot read error message: %w", err)
-	}
-	if len(buf) > 0 {
-		return nil, newErrRemote(buf)
-	}
-
-	// Read response
-	return readTSDBStatus(bc)
-}
-
-func (sn *storageNode) getTSDBStatusWithFiltersOnConn(bc *handshake.BufferedConn, requestData []byte, topN int) (*storage.TSDBStatus, error) {
+func (sn *storageNode) getTSDBStatusOnConn(bc *handshake.BufferedConn, requestData []byte, focusLabel string, topN int) (*storage.TSDBStatus, error) {
 	// Send the request to sn.
 	if err := writeBytes(bc, requestData); err != nil {
 		return nil, fmt.Errorf("cannot write requestData: %w", err)
 	}
+	if err := writeBytes(bc, []byte(focusLabel)); err != nil {
+		return nil, fmt.Errorf("cannot write focusLabel=%q: %w", focusLabel, err)
+	}
 	// topN shouldn't exceed 32 bits, so send it as uint32.
 	if err := writeUint32(bc, uint32(topN)); err != nil {
 		return nil, fmt.Errorf("cannot send topN=%d to conn: %w", topN, err)
 	}
 	if err := bc.Flush(); err != nil {
-		return nil, fmt.Errorf("cannot flush tsdbStatusWithFilters args to conn: %w", err)
+		return nil, fmt.Errorf("cannot flush tsdbStatus args to conn: %w", err)
 	}
 
 	// Read response error.
@@ -2358,28 +2259,26 @@ func InitStorageNodes(addrs []string) {
 
 			concurrentQueries: metrics.NewCounter(fmt.Sprintf(`vm_concurrent_queries{name="vmselect", addr=%q}`, addr)),
 
-			registerMetricNamesRequests:   metrics.NewCounter(fmt.Sprintf(`vm_requests_total{action="registerMetricNames", type="rpcClient", name="vmselect", addr=%q}`, addr)),
-			registerMetricNamesErrors:     metrics.NewCounter(fmt.Sprintf(`vm_request_errors_total{action="registerMetricNames", type="rpcClient", name="vmselect", addr=%q}`, addr)),
-			deleteSeriesRequests:          metrics.NewCounter(fmt.Sprintf(`vm_requests_total{action="deleteSeries", type="rpcClient", name="vmselect", addr=%q}`, addr)),
-			deleteSeriesErrors:            metrics.NewCounter(fmt.Sprintf(`vm_request_errors_total{action="deleteSeries", type="rpcClient", name="vmselect", addr=%q}`, addr)),
-			labelNamesRequests:            metrics.NewCounter(fmt.Sprintf(`vm_requests_total{action="labelNames", type="rpcClient", name="vmselect", addr=%q}`, addr)),
-			labelNamesErrors:              metrics.NewCounter(fmt.Sprintf(`vm_request_errors_total{action="labelNames", type="rpcClient", name="vmselect", addr=%q}`, addr)),
-			labelValuesRequests:           metrics.NewCounter(fmt.Sprintf(`vm_requests_total{action="labelValues", type="rpcClient", name="vmselect", addr=%q}`, addr)),
-			labelValuesErrors:             metrics.NewCounter(fmt.Sprintf(`vm_request_errors_total{action="labelValues", type="rpcClient", name="vmselect", addr=%q}`, addr)),
-			tagValueSuffixesRequests:      metrics.NewCounter(fmt.Sprintf(`vm_requests_total{action="tagValueSuffixes", type="rpcClient", name="vmselect", addr=%q}`, addr)),
-			tagValueSuffixesErrors:        metrics.NewCounter(fmt.Sprintf(`vm_request_errors_total{action="tagValueSuffixes", type="rpcClient", name="vmselect", addr=%q}`, addr)),
-			tsdbStatusRequests:            metrics.NewCounter(fmt.Sprintf(`vm_requests_total{action="tsdbStatus", type="rpcClient", name="vmselect", addr=%q}`, addr)),
-			tsdbStatusErrors:              metrics.NewCounter(fmt.Sprintf(`vm_request_errors_total{action="tsdbStatus", type="rpcClient", name="vmselect", addr=%q}`, addr)),
-			tsdbStatusWithFiltersRequests: metrics.NewCounter(fmt.Sprintf(`vm_requests_total{action="tsdbStatusWithFilters", type="rpcClient", name="vmselect", addr=%q}`, addr)),
-			tsdbStatusWithFiltersErrors:   metrics.NewCounter(fmt.Sprintf(`vm_request_errors_total{action="tsdbStatusWithFilters", type="rpcClient", name="vmselect", addr=%q}`, addr)),
-			seriesCountRequests:           metrics.NewCounter(fmt.Sprintf(`vm_requests_total{action="seriesCount", type="rpcClient", name="vmselect", addr=%q}`, addr)),
-			seriesCountErrors:             metrics.NewCounter(fmt.Sprintf(`vm_request_errors_total{action="seriesCount", type="rpcClient", name="vmselect", addr=%q}`, addr)),
-			searchMetricNamesRequests:     metrics.NewCounter(fmt.Sprintf(`vm_requests_total{action="searchMetricNames", type="rpcClient", name="vmselect", addr=%q}`, addr)),
-			searchRequests:                metrics.NewCounter(fmt.Sprintf(`vm_requests_total{action="search", type="rpcClient", name="vmselect", addr=%q}`, addr)),
-			searchMetricNamesErrors:       metrics.NewCounter(fmt.Sprintf(`vm_request_errors_total{action="searchMetricNames", type="rpcClient", name="vmselect", addr=%q}`, addr)),
-			searchErrors:                  metrics.NewCounter(fmt.Sprintf(`vm_request_errors_total{action="search", type="rpcClient", name="vmselect", addr=%q}`, addr)),
-			metricBlocksRead:              metrics.NewCounter(fmt.Sprintf(`vm_metric_blocks_read_total{name="vmselect", addr=%q}`, addr)),
-			metricRowsRead:                metrics.NewCounter(fmt.Sprintf(`vm_metric_rows_read_total{name="vmselect", addr=%q}`, addr)),
+			registerMetricNamesRequests: metrics.NewCounter(fmt.Sprintf(`vm_requests_total{action="registerMetricNames", type="rpcClient", name="vmselect", addr=%q}`, addr)),
+			registerMetricNamesErrors:   metrics.NewCounter(fmt.Sprintf(`vm_request_errors_total{action="registerMetricNames", type="rpcClient", name="vmselect", addr=%q}`, addr)),
+			deleteSeriesRequests:        metrics.NewCounter(fmt.Sprintf(`vm_requests_total{action="deleteSeries", type="rpcClient", name="vmselect", addr=%q}`, addr)),
+			deleteSeriesErrors:          metrics.NewCounter(fmt.Sprintf(`vm_request_errors_total{action="deleteSeries", type="rpcClient", name="vmselect", addr=%q}`, addr)),
+			labelNamesRequests:          metrics.NewCounter(fmt.Sprintf(`vm_requests_total{action="labelNames", type="rpcClient", name="vmselect", addr=%q}`, addr)),
+			labelNamesErrors:            metrics.NewCounter(fmt.Sprintf(`vm_request_errors_total{action="labelNames", type="rpcClient", name="vmselect", addr=%q}`, addr)),
+			labelValuesRequests:         metrics.NewCounter(fmt.Sprintf(`vm_requests_total{action="labelValues", type="rpcClient", name="vmselect", addr=%q}`, addr)),
+			labelValuesErrors:           metrics.NewCounter(fmt.Sprintf(`vm_request_errors_total{action="labelValues", type="rpcClient", name="vmselect", addr=%q}`, addr)),
+			tagValueSuffixesRequests:    metrics.NewCounter(fmt.Sprintf(`vm_requests_total{action="tagValueSuffixes", type="rpcClient", name="vmselect", addr=%q}`, addr)),
+			tagValueSuffixesErrors:      metrics.NewCounter(fmt.Sprintf(`vm_request_errors_total{action="tagValueSuffixes", type="rpcClient", name="vmselect", addr=%q}`, addr)),
+			tsdbStatusRequests:          metrics.NewCounter(fmt.Sprintf(`vm_requests_total{action="tsdbStatus", type="rpcClient", name="vmselect", addr=%q}`, addr)),
+			tsdbStatusErrors:            metrics.NewCounter(fmt.Sprintf(`vm_request_errors_total{action="tsdbStatus", type="rpcClient", name="vmselect", addr=%q}`, addr)),
+			seriesCountRequests:         metrics.NewCounter(fmt.Sprintf(`vm_requests_total{action="seriesCount", type="rpcClient", name="vmselect", addr=%q}`, addr)),
+			seriesCountErrors:           metrics.NewCounter(fmt.Sprintf(`vm_request_errors_total{action="seriesCount", type="rpcClient", name="vmselect", addr=%q}`, addr)),
+			searchMetricNamesRequests:   metrics.NewCounter(fmt.Sprintf(`vm_requests_total{action="searchMetricNames", type="rpcClient", name="vmselect", addr=%q}`, addr)),
+			searchRequests:              metrics.NewCounter(fmt.Sprintf(`vm_requests_total{action="search", type="rpcClient", name="vmselect", addr=%q}`, addr)),
+			searchMetricNamesErrors:     metrics.NewCounter(fmt.Sprintf(`vm_request_errors_total{action="searchMetricNames", type="rpcClient", name="vmselect", addr=%q}`, addr)),
+			searchErrors:                metrics.NewCounter(fmt.Sprintf(`vm_request_errors_total{action="search", type="rpcClient", name="vmselect", addr=%q}`, addr)),
+			metricBlocksRead:            metrics.NewCounter(fmt.Sprintf(`vm_metric_blocks_read_total{name="vmselect", addr=%q}`, addr)),
+			metricRowsRead:              metrics.NewCounter(fmt.Sprintf(`vm_metric_rows_read_total{name="vmselect", addr=%q}`, addr)),
 		}
 		storageNodes = append(storageNodes, sn)
 	}
diff --git a/app/vmselect/prometheus/prometheus.go b/app/vmselect/prometheus/prometheus.go
index e1a3156d8d..8fc3ec9a4f 100644
--- a/app/vmselect/prometheus/prometheus.go
+++ b/app/vmselect/prometheus/prometheus.go
@@ -550,12 +550,17 @@ func TSDBStatusHandler(qt *querytracer.Tracer, startTime time.Time, at *auth.Tok
 	date := fasttime.UnixDate()
 	dateStr := r.FormValue("date")
 	if len(dateStr) > 0 {
-		t, err := time.Parse("2006-01-02", dateStr)
-		if err != nil {
-			return fmt.Errorf("cannot parse `date` arg %q: %w", dateStr, err)
+		if dateStr == "0" {
+			date = 0
+		} else {
+			t, err := time.Parse("2006-01-02", dateStr)
+			if err != nil {
+				return fmt.Errorf("cannot parse `date` arg %q: %w", dateStr, err)
+			}
+			date = uint64(t.Unix()) / secsPerDay
 		}
-		date = uint64(t.Unix()) / secsPerDay
 	}
+	focusLabel := r.FormValue("focusLabel")
 	topN := 10
 	topNStr := r.FormValue("topN")
 	if len(topNStr) > 0 {
@@ -572,18 +577,12 @@ func TSDBStatusHandler(qt *querytracer.Tracer, startTime time.Time, at *auth.Tok
 		topN = n
 	}
 	denyPartialResponse := searchutils.GetDenyPartialResponse(r)
-	var status *storage.TSDBStatus
-	var isPartial bool
-	if len(cp.filterss) == 0 {
-		status, isPartial, err = netstorage.GetTSDBStatusForDate(qt, at, denyPartialResponse, cp.deadline, date, topN, *maxTSDBStatusSeries)
-		if err != nil {
-			return fmt.Errorf(`cannot obtain tsdb status for date=%d, topN=%d: %w`, date, topN, err)
-		}
-	} else {
-		status, isPartial, err = tsdbStatusWithMatches(qt, at, denyPartialResponse, cp.filterss, date, topN, *maxTSDBStatusSeries, cp.deadline)
-		if err != nil {
-			return fmt.Errorf("cannot obtain tsdb status with matches for date=%d, topN=%d: %w", date, topN, err)
-		}
+	start := int64(date*secsPerDay) * 1000
+	end := int64((date+1)*secsPerDay)*1000 - 1
+	sq := storage.NewSearchQuery(at.AccountID, at.ProjectID, start, end, cp.filterss, *maxTSDBStatusSeries)
+	status, isPartial, err := netstorage.GetTSDBStatus(qt, at, denyPartialResponse, sq, focusLabel, topN, cp.deadline)
+	if err != nil {
+		return fmt.Errorf("cannot obtain tsdb stats: %w", err)
 	}
 
 	w.Header().Set("Content-Type", "application/json")
@@ -596,17 +595,6 @@ func TSDBStatusHandler(qt *querytracer.Tracer, startTime time.Time, at *auth.Tok
 	return nil
 }
 
-func tsdbStatusWithMatches(qt *querytracer.Tracer, at *auth.Token, denyPartialResponse bool, filterss [][]storage.TagFilter, date uint64, topN, maxMetrics int, deadline searchutils.Deadline) (*storage.TSDBStatus, bool, error) {
-	start := int64(date*secsPerDay) * 1000
-	end := int64(date*secsPerDay+secsPerDay) * 1000
-	sq := storage.NewSearchQuery(at.AccountID, at.ProjectID, start, end, filterss, maxMetrics)
-	status, isPartial, err := netstorage.GetTSDBStatusWithFilters(qt, at, denyPartialResponse, deadline, sq, topN)
-	if err != nil {
-		return nil, false, err
-	}
-	return status, isPartial, nil
-}
-
 var tsdbStatusDuration = metrics.NewSummary(`vm_request_duration_seconds{path="/api/v1/status/tsdb"}`)
 
 // LabelsHandler processes /api/v1/labels request.
diff --git a/app/vmselect/prometheus/tsdb_status_response.qtpl b/app/vmselect/prometheus/tsdb_status_response.qtpl
index 60055a1deb..48912be3b9 100644
--- a/app/vmselect/prometheus/tsdb_status_response.qtpl
+++ b/app/vmselect/prometheus/tsdb_status_response.qtpl
@@ -14,6 +14,7 @@ TSDBStatusResponse generates response for /api/v1/status/tsdb .
 		"totalLabelValuePairs": {%dul= status.TotalLabelValuePairs %},
 		"seriesCountByMetricName":{%= tsdbStatusEntries(status.SeriesCountByMetricName) %},
 		"seriesCountByLabelName":{%= tsdbStatusEntries(status.SeriesCountByLabelName) %},
+		"seriesCountByFocusLabelValue":{%= tsdbStatusEntries(status.SeriesCountByFocusLabelValue) %},
 		"seriesCountByLabelValuePair":{%= tsdbStatusEntries(status.SeriesCountByLabelValuePair) %},
 		"labelValueCountByLabelName":{%= tsdbStatusEntries(status.LabelValueCountByLabelName) %}
 	}
diff --git a/app/vmselect/prometheus/tsdb_status_response.qtpl.go b/app/vmselect/prometheus/tsdb_status_response.qtpl.go
index e6d37f4276..dcd4c7ed8b 100644
--- a/app/vmselect/prometheus/tsdb_status_response.qtpl.go
+++ b/app/vmselect/prometheus/tsdb_status_response.qtpl.go
@@ -56,102 +56,106 @@ func StreamTSDBStatusResponse(qw422016 *qt422016.Writer, isPartial bool, status
 //line app/vmselect/prometheus/tsdb_status_response.qtpl:16
 	streamtsdbStatusEntries(qw422016, status.SeriesCountByLabelName)
 //line app/vmselect/prometheus/tsdb_status_response.qtpl:16
+	qw422016.N().S(`,"seriesCountByFocusLabelValue":`)
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:17
+	streamtsdbStatusEntries(qw422016, status.SeriesCountByFocusLabelValue)
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:17
 	qw422016.N().S(`,"seriesCountByLabelValuePair":`)
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:17
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:18
 	streamtsdbStatusEntries(qw422016, status.SeriesCountByLabelValuePair)
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:17
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:18
 	qw422016.N().S(`,"labelValueCountByLabelName":`)
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:18
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:19
 	streamtsdbStatusEntries(qw422016, status.LabelValueCountByLabelName)
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:18
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:19
 	qw422016.N().S(`}`)
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:20
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:21
 	qt.Done()
 
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:21
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:22
 	streamdumpQueryTrace(qw422016, qt)
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:21
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:22
 	qw422016.N().S(`}`)
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:23
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:24
 }
 
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:23
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:24
 func WriteTSDBStatusResponse(qq422016 qtio422016.Writer, isPartial bool, status *storage.TSDBStatus, qt *querytracer.Tracer) {
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:23
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:24
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:23
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:24
 	StreamTSDBStatusResponse(qw422016, isPartial, status, qt)
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:23
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:24
 	qt422016.ReleaseWriter(qw422016)
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:23
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:24
 }
 
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:23
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:24
 func TSDBStatusResponse(isPartial bool, status *storage.TSDBStatus, qt *querytracer.Tracer) string {
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:23
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:24
 	qb422016 := qt422016.AcquireByteBuffer()
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:23
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:24
 	WriteTSDBStatusResponse(qb422016, isPartial, status, qt)
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:23
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:24
 	qs422016 := string(qb422016.B)
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:23
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:24
 	qt422016.ReleaseByteBuffer(qb422016)
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:23
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:24
 	return qs422016
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:23
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:24
 }
 
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:25
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:26
 func streamtsdbStatusEntries(qw422016 *qt422016.Writer, a []storage.TopHeapEntry) {
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:25
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:26
 	qw422016.N().S(`[`)
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:27
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:28
 	for i, e := range a {
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:27
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:28
 		qw422016.N().S(`{"name":`)
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:29
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:30
 		qw422016.N().Q(e.Name)
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:29
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:30
 		qw422016.N().S(`,"value":`)
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:30
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:31
 		qw422016.N().D(int(e.Count))
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:30
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:31
 		qw422016.N().S(`}`)
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:32
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:33
 		if i+1 < len(a) {
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:32
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:33
 			qw422016.N().S(`,`)
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:32
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:33
 		}
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:33
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:34
 	}
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:33
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:34
 	qw422016.N().S(`]`)
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:35
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:36
 }
 
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:35
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:36
 func writetsdbStatusEntries(qq422016 qtio422016.Writer, a []storage.TopHeapEntry) {
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:35
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:36
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:35
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:36
 	streamtsdbStatusEntries(qw422016, a)
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:35
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:36
 	qt422016.ReleaseWriter(qw422016)
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:35
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:36
 }
 
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:35
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:36
 func tsdbStatusEntries(a []storage.TopHeapEntry) string {
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:35
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:36
 	qb422016 := qt422016.AcquireByteBuffer()
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:35
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:36
 	writetsdbStatusEntries(qb422016, a)
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:35
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:36
 	qs422016 := string(qb422016.B)
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:35
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:36
 	qt422016.ReleaseByteBuffer(qb422016)
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:35
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:36
 	return qs422016
-//line app/vmselect/prometheus/tsdb_status_response.qtpl:35
+//line app/vmselect/prometheus/tsdb_status_response.qtpl:36
 }
diff --git a/app/vmstorage/transport/server.go b/app/vmstorage/transport/server.go
index a03d5c802d..657f20d3cc 100644
--- a/app/vmstorage/transport/server.go
+++ b/app/vmstorage/transport/server.go
@@ -539,10 +539,8 @@ func (s *Server) processVMSelectRPC(ctx *vmselectRequestCtx, rpcName string) err
 		return s.processVMSelectLabelNames(ctx)
 	case "seriesCount_v4":
 		return s.processVMSelectSeriesCount(ctx)
-	case "tsdbStatus_v4":
+	case "tsdbStatus_v5":
 		return s.processVMSelectTSDBStatus(ctx)
-	case "tsdbStatusWithFilters_v3":
-		return s.processVMSelectTSDBStatusWithFilters(ctx)
 	case "deleteMetrics_v5":
 		return s.processVMSelectDeleteMetrics(ctx)
 	case "registerMetricNames_v3":
@@ -827,29 +825,29 @@ func (s *Server) processVMSelectTSDBStatus(ctx *vmselectRequestCtx) error {
 	vmselectTSDBStatusRequests.Inc()
 
 	// Read request
-	accountID, projectID, err := ctx.readAccountIDProjectID()
-	if err != nil {
+	if err := ctx.readSearchQuery(); err != nil {
 		return err
 	}
-	date, err := ctx.readUint32()
-	if err != nil {
-		return fmt.Errorf("cannot read date: %w", err)
+	if err := ctx.readDataBufBytes(maxLabelValueSize); err != nil {
+		return fmt.Errorf("cannot read focusLabel: %w", err)
 	}
+	focusLabel := string(ctx.dataBuf)
 	topN, err := ctx.readUint32()
 	if err != nil {
 		return fmt.Errorf("cannot read topN: %w", err)
 	}
-	maxMetricsUint32, err := ctx.readUint32()
-	if err != nil {
-		return fmt.Errorf("cannot read MaxMetrics: %w", err)
-	}
-	maxMetrics := int(maxMetricsUint32)
-	if maxMetrics < 0 {
-		return fmt.Errorf("too big value for MaxMetrics=%d; must be smaller than 2e9", maxMetricsUint32)
-	}
 
 	// Execute the request
-	status, err := s.storage.GetTSDBStatusWithFiltersForDate(ctx.qt, accountID, projectID, nil, uint64(date), int(topN), maxMetrics, ctx.deadline)
+	tr := storage.TimeRange{
+		MinTimestamp: ctx.sq.MinTimestamp,
+		MaxTimestamp: ctx.sq.MaxTimestamp,
+	}
+	if err := ctx.setupTfss(s.storage, tr); err != nil {
+		return ctx.writeErrorMessage(err)
+	}
+	maxMetrics := ctx.getMaxMetrics()
+	date := uint64(ctx.sq.MinTimestamp) / (24 * 3600 * 1000)
+	status, err := s.storage.GetTSDBStatus(ctx.qt, ctx.sq.AccountID, ctx.sq.ProjectID, ctx.tfss, date, focusLabel, int(topN), maxMetrics, ctx.deadline)
 	if err != nil {
 		return ctx.writeErrorMessage(err)
 	}
@@ -882,42 +880,6 @@ func writeTSDBStatus(ctx *vmselectRequestCtx, status *storage.TSDBStatus) error
 	return nil
 }
 
-func (s *Server) processVMSelectTSDBStatusWithFilters(ctx *vmselectRequestCtx) error {
-	vmselectTSDBStatusWithFiltersRequests.Inc()
-
-	// Read request
-	if err := ctx.readSearchQuery(); err != nil {
-		return err
-	}
-	topN, err := ctx.readUint32()
-	if err != nil {
-		return fmt.Errorf("cannot read topN: %w", err)
-	}
-
-	// Execute the request
-	tr := storage.TimeRange{
-		MinTimestamp: ctx.sq.MinTimestamp,
-		MaxTimestamp: ctx.sq.MaxTimestamp,
-	}
-	if err := ctx.setupTfss(s.storage, tr); err != nil {
-		return ctx.writeErrorMessage(err)
-	}
-	maxMetrics := ctx.getMaxMetrics()
-	date := uint64(ctx.sq.MinTimestamp) / (24 * 3600 * 1000)
-	status, err := s.storage.GetTSDBStatusWithFiltersForDate(ctx.qt, ctx.sq.AccountID, ctx.sq.ProjectID, ctx.tfss, date, int(topN), maxMetrics, ctx.deadline)
-	if err != nil {
-		return ctx.writeErrorMessage(err)
-	}
-
-	// Send an empty error message to vmselect.
-	if err := ctx.writeString(""); err != nil {
-		return fmt.Errorf("cannot send empty error message: %w", err)
-	}
-
-	// Send status to vmselect.
-	return writeTSDBStatus(ctx, status)
-}
-
 func writeTopHeapEntries(ctx *vmselectRequestCtx, a []storage.TopHeapEntry) error {
 	if err := ctx.writeUint64(uint64(len(a))); err != nil {
 		return fmt.Errorf("cannot write topHeapEntries size: %w", err)
@@ -1062,16 +1024,15 @@ func checkTimeRange(s *storage.Storage, tr storage.TimeRange) error {
 }
 
 var (
-	vmselectRegisterMetricNamesRequests   = metrics.NewCounter(`vm_vmselect_rpc_requests_total{name="register_metric_names"}`)
-	vmselectDeleteMetricsRequests         = metrics.NewCounter(`vm_vmselect_rpc_requests_total{name="delete_metrics"}`)
-	vmselectLabelNamesRequests            = metrics.NewCounter(`vm_vmselect_rpc_requests_total{name="label_names"}`)
-	vmselectLabelValuesRequests           = metrics.NewCounter(`vm_vmselect_rpc_requests_total{name="label_values"}`)
-	vmselectTagValueSuffixesRequests      = metrics.NewCounter(`vm_vmselect_rpc_requests_total{name="tag_value_suffixes"}`)
-	vmselectSeriesCountRequests           = metrics.NewCounter(`vm_vmselect_rpc_requests_total{name="series_count"}`)
-	vmselectTSDBStatusRequests            = metrics.NewCounter(`vm_vmselect_rpc_requests_total{name="tsdb_status"}`)
-	vmselectTSDBStatusWithFiltersRequests = metrics.NewCounter(`vm_vmselect_rpc_requests_total{name="tsdb_status_with_filters"}`)
-	vmselectSearchMetricNamesRequests     = metrics.NewCounter(`vm_vmselect_rpc_requests_total{name="search_metric_names"}`)
-	vmselectSearchRequests                = metrics.NewCounter(`vm_vmselect_rpc_requests_total{name="search"}`)
+	vmselectRegisterMetricNamesRequests = metrics.NewCounter(`vm_vmselect_rpc_requests_total{name="register_metric_names"}`)
+	vmselectDeleteMetricsRequests       = metrics.NewCounter(`vm_vmselect_rpc_requests_total{name="delete_metrics"}`)
+	vmselectLabelNamesRequests          = metrics.NewCounter(`vm_vmselect_rpc_requests_total{name="label_names"}`)
+	vmselectLabelValuesRequests         = metrics.NewCounter(`vm_vmselect_rpc_requests_total{name="label_values"}`)
+	vmselectTagValueSuffixesRequests    = metrics.NewCounter(`vm_vmselect_rpc_requests_total{name="tag_value_suffixes"}`)
+	vmselectSeriesCountRequests         = metrics.NewCounter(`vm_vmselect_rpc_requests_total{name="series_count"}`)
+	vmselectTSDBStatusRequests          = metrics.NewCounter(`vm_vmselect_rpc_requests_total{name="tsdb_status"}`)
+	vmselectSearchMetricNamesRequests   = metrics.NewCounter(`vm_vmselect_rpc_requests_total{name="search_metric_names"}`)
+	vmselectSearchRequests              = metrics.NewCounter(`vm_vmselect_rpc_requests_total{name="search"}`)
 
 	vmselectMetricBlocksRead = metrics.NewCounter(`vm_vmselect_metric_blocks_read_total`)
 	vmselectMetricRowsRead   = metrics.NewCounter(`vm_vmselect_metric_rows_read_total`)
diff --git a/docs/Single-server-VictoriaMetrics.md b/docs/Single-server-VictoriaMetrics.md
index 9977f10767..85ab20b228 100644
--- a/docs/Single-server-VictoriaMetrics.md
+++ b/docs/Single-server-VictoriaMetrics.md
@@ -272,7 +272,8 @@ See the [example VMUI at VictoriaMetrics playground](https://play.victoriametric
 VictoriaMetrics provides an ability to explore time series cardinality at `cardinality` tab in [vmui](#vmui) in the following ways:
 
 - To identify metric names with the highest number of series.
-- To idnetify labels with the highest number of series.
+- To identify labels with the highest number of series.
+- To identify values with the highest number of series for the selected label (aka `focusLabel`).
 - To identify label=name pairs with the highest number of series.
 - To identify labels with the highest number of unique values.
 
@@ -1445,6 +1446,7 @@ VictoriaMetrics returns TSDB stats at `/api/v1/status/tsdb` page in the way simi
 
 * `topN=N` where `N` is the number of top entries to return in the response. By default top 10 entries are returned.
 * `date=YYYY-MM-DD` where `YYYY-MM-DD` is the date for collecting the stats. By default the stats is collected for the current day. Pass `date=1970-01-01` in order to collect global stats across all the days.
+* `focusLabel=LABEL_NAME` returns label values with the highest number of time series for the given `LABEL_NAME` in the `seriesCountByFocusLabelValue` list.
 * `match[]=SELECTOR` where `SELECTOR` is an arbitrary [time series selector](https://prometheus.io/docs/prometheus/latest/querying/basics/#time-series-selectors) for series to take into account during stats calculation. By default all the series are taken into account.
 * `extra_label=LABEL=VALUE`. See [these docs](#prometheus-querying-api-enhancements) for more details.
 
diff --git a/lib/storage/index_db.go b/lib/storage/index_db.go
index fe6e3fa9f5..5558533850 100644
--- a/lib/storage/index_db.go
+++ b/lib/storage/index_db.go
@@ -1263,11 +1263,11 @@ func (is *indexSearch) getSeriesCount() (uint64, error) {
 	return metricIDsLen, nil
 }
 
-// GetTSDBStatusWithFiltersForDate returns topN entries for tsdb status for the given tfss, date, accountID and projectID.
-func (db *indexDB) GetTSDBStatusWithFiltersForDate(qt *querytracer.Tracer, accountID, projectID uint32, tfss []*TagFilters, date uint64, topN, maxMetrics int, deadline uint64) (*TSDBStatus, error) {
+// GetTSDBStatus returns topN entries for tsdb status for the given tfss, date and focusLabel.
+func (db *indexDB) GetTSDBStatus(qt *querytracer.Tracer, accountID, projectID uint32, tfss []*TagFilters, date uint64, focusLabel string, topN, maxMetrics int, deadline uint64) (*TSDBStatus, error) {
 	qtChild := qt.NewChild("collect tsdb stats in the current indexdb")
 	is := db.getIndexSearch(accountID, projectID, deadline)
-	status, err := is.getTSDBStatusWithFiltersForDate(qtChild, tfss, date, topN, maxMetrics)
+	status, err := is.getTSDBStatus(qtChild, tfss, date, focusLabel, topN, maxMetrics)
 	qtChild.Done()
 	db.putIndexSearch(is)
 	if err != nil {
@@ -1279,7 +1279,7 @@ func (db *indexDB) GetTSDBStatusWithFiltersForDate(qt *querytracer.Tracer, accou
 	ok := db.doExtDB(func(extDB *indexDB) {
 		qtChild := qt.NewChild("collect tsdb stats in the previous indexdb")
 		is := extDB.getIndexSearch(accountID, projectID, deadline)
-		status, err = is.getTSDBStatusWithFiltersForDate(qtChild, tfss, date, topN, maxMetrics)
+		status, err = is.getTSDBStatus(qtChild, tfss, date, focusLabel, topN, maxMetrics)
 		qtChild.Done()
 		extDB.putIndexSearch(is)
 	})
@@ -1289,8 +1289,8 @@ func (db *indexDB) GetTSDBStatusWithFiltersForDate(qt *querytracer.Tracer, accou
 	return status, nil
 }
 
-// getTSDBStatusWithFiltersForDate returns topN entries for tsdb status for the given tfss and the given date.
-func (is *indexSearch) getTSDBStatusWithFiltersForDate(qt *querytracer.Tracer, tfss []*TagFilters, date uint64, topN, maxMetrics int) (*TSDBStatus, error) {
+// getTSDBStatus returns topN entries for tsdb status for the given tfss, date and focusLabel.
+func (is *indexSearch) getTSDBStatus(qt *querytracer.Tracer, tfss []*TagFilters, date uint64, focusLabel string, topN, maxMetrics int) (*TSDBStatus, error) {
 	filter, err := is.searchMetricIDsWithFiltersOnDate(qt, tfss, date, maxMetrics)
 	if err != nil {
 		return nil, err
@@ -1305,12 +1305,14 @@ func (is *indexSearch) getTSDBStatusWithFiltersForDate(qt *querytracer.Tracer, t
 	dmis := is.db.s.getDeletedMetricIDs()
 	thSeriesCountByMetricName := newTopHeap(topN)
 	thSeriesCountByLabelName := newTopHeap(topN)
+	thSeriesCountByFocusLabelValue := newTopHeap(topN)
 	thSeriesCountByLabelValuePair := newTopHeap(topN)
 	thLabelValueCountByLabelName := newTopHeap(topN)
 	var tmp, prevLabelName, prevLabelValuePair []byte
 	var labelValueCountByLabelName, seriesCountByLabelValuePair uint64
 	var totalSeries, labelSeries, totalLabelValuePairs uint64
 	nameEqualBytes := []byte("__name__=")
+	focusLabelEqualBytes := []byte(focusLabel + "=")
 
 	loopsPaceLimiter := 0
 	nsPrefixExpected := byte(nsPrefixDateTagToMetricIDs)
@@ -1382,6 +1384,9 @@ func (is *indexSearch) getTSDBStatusWithFiltersForDate(qt *querytracer.Tracer, t
 			if bytes.HasPrefix(prevLabelValuePair, nameEqualBytes) {
 				thSeriesCountByMetricName.push(prevLabelValuePair[len(nameEqualBytes):], seriesCountByLabelValuePair)
 			}
+			if bytes.HasPrefix(prevLabelValuePair, focusLabelEqualBytes) {
+				thSeriesCountByFocusLabelValue.push(prevLabelValuePair[len(focusLabelEqualBytes):], seriesCountByLabelValuePair)
+			}
 			seriesCountByLabelValuePair = 0
 			labelValueCountByLabelName++
 			prevLabelValuePair = append(prevLabelValuePair[:0], labelValuePair...)
@@ -1401,13 +1406,17 @@ func (is *indexSearch) getTSDBStatusWithFiltersForDate(qt *querytracer.Tracer, t
 	if bytes.HasPrefix(prevLabelValuePair, nameEqualBytes) {
 		thSeriesCountByMetricName.push(prevLabelValuePair[len(nameEqualBytes):], seriesCountByLabelValuePair)
 	}
+	if bytes.HasPrefix(prevLabelValuePair, focusLabelEqualBytes) {
+		thSeriesCountByFocusLabelValue.push(prevLabelValuePair[len(focusLabelEqualBytes):], seriesCountByLabelValuePair)
+	}
 	status := &TSDBStatus{
-		TotalSeries:                 totalSeries,
-		TotalLabelValuePairs:        totalLabelValuePairs,
-		SeriesCountByMetricName:     thSeriesCountByMetricName.getSortedResult(),
-		SeriesCountByLabelName:      thSeriesCountByLabelName.getSortedResult(),
-		SeriesCountByLabelValuePair: thSeriesCountByLabelValuePair.getSortedResult(),
-		LabelValueCountByLabelName:  thLabelValueCountByLabelName.getSortedResult(),
+		TotalSeries:                  totalSeries,
+		TotalLabelValuePairs:         totalLabelValuePairs,
+		SeriesCountByMetricName:      thSeriesCountByMetricName.getSortedResult(),
+		SeriesCountByLabelName:       thSeriesCountByLabelName.getSortedResult(),
+		SeriesCountByFocusLabelValue: thSeriesCountByFocusLabelValue.getSortedResult(),
+		SeriesCountByLabelValuePair:  thSeriesCountByLabelValuePair.getSortedResult(),
+		LabelValueCountByLabelName:   thLabelValueCountByLabelName.getSortedResult(),
 	}
 	return status, nil
 }
@@ -1416,12 +1425,13 @@ func (is *indexSearch) getTSDBStatusWithFiltersForDate(qt *querytracer.Tracer, t
 //
 // See https://prometheus.io/docs/prometheus/latest/querying/api/#tsdb-stats
 type TSDBStatus struct {
-	TotalSeries                 uint64
-	TotalLabelValuePairs        uint64
-	SeriesCountByMetricName     []TopHeapEntry
-	SeriesCountByLabelName      []TopHeapEntry
-	SeriesCountByLabelValuePair []TopHeapEntry
-	LabelValueCountByLabelName  []TopHeapEntry
+	TotalSeries                  uint64
+	TotalLabelValuePairs         uint64
+	SeriesCountByMetricName      []TopHeapEntry
+	SeriesCountByLabelName       []TopHeapEntry
+	SeriesCountByFocusLabelValue []TopHeapEntry
+	SeriesCountByLabelValuePair  []TopHeapEntry
+	LabelValueCountByLabelName   []TopHeapEntry
 }
 
 func (status *TSDBStatus) hasEntries() bool {
diff --git a/lib/storage/index_db_test.go b/lib/storage/index_db_test.go
index 8e7d61fa7e..d8c23d0e54 100644
--- a/lib/storage/index_db_test.go
+++ b/lib/storage/index_db_test.go
@@ -1887,10 +1887,10 @@ func TestSearchTSIDWithTimeRange(t *testing.T) {
 		t.Fatalf("expected %d time series for all days, got %d time series", metricsPerDay*days, len(matchedTSIDs))
 	}
 
-	// Check GetTSDBStatusWithFiltersForDate with nil filters.
-	status, err := db.GetTSDBStatusWithFiltersForDate(nil, accountID, projectID, nil, baseDate, 5, 1e6, noDeadline)
+	// Check GetTSDBStatus with nil filters.
+	status, err := db.GetTSDBStatus(nil, accountID, projectID, nil, baseDate, "day", 5, 1e6, noDeadline)
 	if err != nil {
-		t.Fatalf("error in GetTSDBStatusWithFiltersForDate with nil filters: %s", err)
+		t.Fatalf("error in GetTSDBStatus with nil filters: %s", err)
 	}
 	if !status.hasEntries() {
 		t.Fatalf("expecting non-empty TSDB status")
@@ -1925,6 +1925,15 @@ func TestSearchTSIDWithTimeRange(t *testing.T) {
 	if !reflect.DeepEqual(status.SeriesCountByLabelName, expectedSeriesCountByLabelName) {
 		t.Fatalf("unexpected SeriesCountByLabelName;\ngot\n%v\nwant\n%v", status.SeriesCountByLabelName, expectedSeriesCountByLabelName)
 	}
+	expectedSeriesCountByFocusLabelValue := []TopHeapEntry{
+		{
+			Name:  "0",
+			Count: 1000,
+		},
+	}
+	if !reflect.DeepEqual(status.SeriesCountByFocusLabelValue, expectedSeriesCountByFocusLabelValue) {
+		t.Fatalf("unexpected SeriesCountByFocusLabelValue;\ngot\n%v\nwant\n%v", status.SeriesCountByFocusLabelValue, expectedSeriesCountByFocusLabelValue)
+	}
 	expectedLabelValueCountByLabelName := []TopHeapEntry{
 		{
 			Name:  "uniqueid",
@@ -1980,14 +1989,14 @@ func TestSearchTSIDWithTimeRange(t *testing.T) {
 		t.Fatalf("unexpected TotalLabelValuePairs; got %d; want %d", status.TotalLabelValuePairs, expectedLabelValuePairs)
 	}
 
-	// Check GetTSDBStatusWithFiltersForDate with non-nil filter, which matches all the series
+	// Check GetTSDBStatus with non-nil filter, which matches all the series
 	tfs = NewTagFilters(accountID, projectID)
 	if err := tfs.Add([]byte("day"), []byte("0"), false, false); err != nil {
 		t.Fatalf("cannot add filter: %s", err)
 	}
-	status, err = db.GetTSDBStatusWithFiltersForDate(nil, accountID, projectID, []*TagFilters{tfs}, baseDate, 5, 1e6, noDeadline)
+	status, err = db.GetTSDBStatus(nil, accountID, projectID, []*TagFilters{tfs}, baseDate, "", 5, 1e6, noDeadline)
 	if err != nil {
-		t.Fatalf("error in GetTSDBStatusWithFiltersForDate: %s", err)
+		t.Fatalf("error in GetTSDBStatus: %s", err)
 	}
 	if !status.hasEntries() {
 		t.Fatalf("expecting non-empty TSDB status")
@@ -2010,10 +2019,10 @@ func TestSearchTSIDWithTimeRange(t *testing.T) {
 		t.Fatalf("unexpected TotalLabelValuePairs; got %d; want %d", status.TotalLabelValuePairs, expectedLabelValuePairs)
 	}
 
-	// Check GetTSDBStatusWithFiltersOnDate with non-nil filter, which matches all the series on a global time range
-	status, err = db.GetTSDBStatusWithFiltersForDate(nil, accountID, projectID, nil, 0, 5, 1e6, noDeadline)
+	// Check GetTSDBStatus with non-nil filter, which matches all the series on a global time range
+	status, err = db.GetTSDBStatus(nil, accountID, projectID, nil, 0, "day", 5, 1e6, noDeadline)
 	if err != nil {
-		t.Fatalf("error in GetTSDBStatusWithFiltersForDate: %s", err)
+		t.Fatalf("error in GetTSDBStatus: %s", err)
 	}
 	if !status.hasEntries() {
 		t.Fatalf("expecting non-empty TSDB status")
@@ -2035,15 +2044,40 @@ func TestSearchTSIDWithTimeRange(t *testing.T) {
 	if status.TotalLabelValuePairs != expectedLabelValuePairs {
 		t.Fatalf("unexpected TotalLabelValuePairs; got %d; want %d", status.TotalLabelValuePairs, expectedLabelValuePairs)
 	}
+	expectedSeriesCountByFocusLabelValue = []TopHeapEntry{
+		{
+			Name:  "0",
+			Count: 1000,
+		},
+		{
+			Name:  "1",
+			Count: 1000,
+		},
+		{
+			Name:  "2",
+			Count: 1000,
+		},
+		{
+			Name:  "3",
+			Count: 1000,
+		},
+		{
+			Name:  "4",
+			Count: 1000,
+		},
+	}
+	if !reflect.DeepEqual(status.SeriesCountByFocusLabelValue, expectedSeriesCountByFocusLabelValue) {
+		t.Fatalf("unexpected SeriesCountByFocusLabelValue;\ngot\n%v\nwant\n%v", status.SeriesCountByFocusLabelValue, expectedSeriesCountByFocusLabelValue)
+	}
 
-	// Check GetTSDBStatusWithFiltersForDate with non-nil filter, which matches only 3 series
+	// Check GetTSDBStatus with non-nil filter, which matches only 3 series
 	tfs = NewTagFilters(accountID, projectID)
 	if err := tfs.Add([]byte("uniqueid"), []byte("0|1|3"), false, true); err != nil {
 		t.Fatalf("cannot add filter: %s", err)
 	}
-	status, err = db.GetTSDBStatusWithFiltersForDate(nil, accountID, projectID, []*TagFilters{tfs}, baseDate, 5, 1e6, noDeadline)
+	status, err = db.GetTSDBStatus(nil, accountID, projectID, []*TagFilters{tfs}, baseDate, "", 5, 1e6, noDeadline)
 	if err != nil {
-		t.Fatalf("error in GetTSDBStatusWithFiltersForDate: %s", err)
+		t.Fatalf("error in GetTSDBStatus: %s", err)
 	}
 	if !status.hasEntries() {
 		t.Fatalf("expecting non-empty TSDB status")
@@ -2066,10 +2100,10 @@ func TestSearchTSIDWithTimeRange(t *testing.T) {
 		t.Fatalf("unexpected TotalLabelValuePairs; got %d; want %d", status.TotalLabelValuePairs, expectedLabelValuePairs)
 	}
 
-	// Check GetTSDBStatusWithFiltersForDate with non-nil filter on global time range, which matches only 15 series
-	status, err = db.GetTSDBStatusWithFiltersForDate(nil, accountID, projectID, []*TagFilters{tfs}, 0, 5, 1e6, noDeadline)
+	// Check GetTSDBStatus with non-nil filter on global time range, which matches only 15 series
+	status, err = db.GetTSDBStatus(nil, accountID, projectID, []*TagFilters{tfs}, 0, "", 5, 1e6, noDeadline)
 	if err != nil {
-		t.Fatalf("error in GetTSDBStatusWithFiltersForDate: %s", err)
+		t.Fatalf("error in GetTSDBStatus: %s", err)
 	}
 	if !status.hasEntries() {
 		t.Fatalf("expecting non-empty TSDB status")
diff --git a/lib/storage/storage.go b/lib/storage/storage.go
index f14ffaa451..c03211c568 100644
--- a/lib/storage/storage.go
+++ b/lib/storage/storage.go
@@ -1541,9 +1541,9 @@ func (s *Storage) GetSeriesCount(accountID, projectID uint32, deadline uint64) (
 	return s.idb().GetSeriesCount(accountID, projectID, deadline)
 }
 
-// GetTSDBStatusWithFiltersForDate returns TSDB status data for /api/v1/status/tsdb with match[] filters and the given (accountID, projectID).
-func (s *Storage) GetTSDBStatusWithFiltersForDate(qt *querytracer.Tracer, accountID, projectID uint32, tfss []*TagFilters, date uint64, topN, maxMetrics int, deadline uint64) (*TSDBStatus, error) {
-	return s.idb().GetTSDBStatusWithFiltersForDate(qt, accountID, projectID, tfss, date, topN, maxMetrics, deadline)
+// GetTSDBStatus returns TSDB status data for /api/v1/status/tsdb
+func (s *Storage) GetTSDBStatus(qt *querytracer.Tracer, accountID, projectID uint32, tfss []*TagFilters, date uint64, focusLabel string, topN, maxMetrics int, deadline uint64) (*TSDBStatus, error) {
+	return s.idb().GetTSDBStatus(qt, accountID, projectID, tfss, date, focusLabel, topN, maxMetrics, deadline)
 }
 
 // MetricRow is a metric to insert into storage.