From 832651c6c25ef259a7868c4d3e97ce9b751ae762 Mon Sep 17 00:00:00 2001 From: Aliaksandr Valialkin Date: Wed, 12 May 2021 16:32:48 +0300 Subject: [PATCH] app/vmselect: follow up after 8a0678678b10ccee7a28b687b373ec36307e6038 Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1168 --- README.md | 19 ++- app/vmselect/netstorage/netstorage.go | 9 +- app/vmselect/prometheus/prometheus.go | 5 +- app/vmstorage/main.go | 6 +- docs/CHANGELOG.md | 2 + docs/Cluster-VictoriaMetrics.md | 4 +- docs/Single-server-VictoriaMetrics.md | 19 ++- lib/storage/index_db.go | 186 ++++++++++++++++---------- lib/storage/index_db_test.go | 13 +- lib/storage/storage.go | 6 +- 10 files changed, 161 insertions(+), 108 deletions(-) diff --git a/README.md b/README.md index 343228845..cccfd8f52 100644 --- a/README.md +++ b/README.md @@ -549,9 +549,7 @@ VictoriaMetrics supports the following handlers from [Prometheus querying API](h * [/api/v1/series](https://prometheus.io/docs/prometheus/latest/querying/api/#finding-series-by-label-matchers) * [/api/v1/labels](https://prometheus.io/docs/prometheus/latest/querying/api/#getting-label-names) * [/api/v1/label/.../values](https://prometheus.io/docs/prometheus/latest/querying/api/#querying-label-values) -* [/api/v1/status/tsdb](https://prometheus.io/docs/prometheus/latest/querying/api/#tsdb-stats). VictoriaMetrics accepts optional `topN=N` and `date=YYYY-MM-DD` - query args for this handler, where `N` is the number of top entries to return in the response and `YYYY-MM-DD` is the date for collecting the stats. - By default top 10 entries are returned and the stats is collected for the current day. +* [/api/v1/status/tsdb](https://prometheus.io/docs/prometheus/latest/querying/api/#tsdb-stats). See [these docs](#tsdb-stats) for details. * [/api/v1/targets](https://prometheus.io/docs/prometheus/latest/querying/api/#targets) - see [these docs](#how-to-scrape-prometheus-exporters-such-as-node-exporter) for more details. These handlers can be queried from Prometheus-compatible clients such as Grafana or curl. @@ -1324,6 +1322,16 @@ VictoriaMetrics also exposes currently running queries with their execution time See the example of alerting rules for VM components [here](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/deployment/docker/alerts.yml). + +## TSDB stats + +VictoriaMetrics retuns TSDB stats at `/api/v1/status/tsdb` page in the way similar to Prometheus - see [these Prometheus docs](https://prometheus.io/docs/prometheus/latest/querying/api/#tsdb-stats). VictoriaMetrics accepts the following optional query args at `/api/v1/status/tsdb` page: + * `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. + * `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. + + ## Troubleshooting * It is recommended to use default command-line flag values (i.e. don't set them explicitly) until the need @@ -1380,10 +1388,7 @@ See the example of alerting rules for VM components [here](https://github.com/Vi It may be needed in order to suppress default gap filling algorithm used by VictoriaMetrics - by default it assumes each time series is continuous instead of discrete, so it fills gaps between real samples with regular intervals. -* Metrics and labels leading to high cardinality or high churn rate can be determined at `/api/v1/status/tsdb` page. - See [these docs](https://prometheus.io/docs/prometheus/latest/querying/api/#tsdb-stats) for details. - VictoriaMetrics accepts optional `date=YYYY-MM-DD` and `topN=42` args on this page. By default `date` equals to the current date, - while `topN` equals to 10. +* Metrics and labels leading to high cardinality or high churn rate can be determined at `/api/v1/status/tsdb` page. See [these docs](#tsdb-stats) for details. * New time series can be logged if `-logNewSeries` command-line flag is passed to VictoriaMetrics. diff --git a/app/vmselect/netstorage/netstorage.go b/app/vmselect/netstorage/netstorage.go index 2ab9bbec5..07c7f7a59 100644 --- a/app/vmselect/netstorage/netstorage.go +++ b/app/vmselect/netstorage/netstorage.go @@ -696,21 +696,24 @@ func GetTSDBStatusForDate(deadline searchutils.Deadline, date uint64, topN int) } // 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(deadline searchutils.Deadline, sq *storage.SearchQuery, topN int) (*storage.TSDBStatus, error) { if deadline.Exceeded() { return nil, fmt.Errorf("timeout exceeded before starting the query processing: %s", deadline.String()) } tr := storage.TimeRange{ - MaxTimestamp: sq.MaxTimestamp, MinTimestamp: sq.MinTimestamp, + MaxTimestamp: sq.MaxTimestamp, } tfss, err := setupTfss(tr, sq.TagFilterss, deadline) if err != nil { return nil, err } - status, err := vmstorage.GetTSDBStatusWithFilters(tr, tfss, topN, *maxMetricsPerSearch, deadline.Deadline()) + date := uint64(tr.MinTimestamp) / (3600 * 24 * 1000) + status, err := vmstorage.GetTSDBStatusWithFiltersForDate(tfss, date, topN, deadline.Deadline()) if err != nil { - return nil, fmt.Errorf("error during tsdb status request: %w", err) + return nil, fmt.Errorf("error during tsdb status with filters request: %w", err) } return status, nil } diff --git a/app/vmselect/prometheus/prometheus.go b/app/vmselect/prometheus/prometheus.go index 8847dd077..0328a9648 100644 --- a/app/vmselect/prometheus/prometheus.go +++ b/app/vmselect/prometheus/prometheus.go @@ -633,6 +633,8 @@ const secsPerDay = 3600 * 24 // TSDBStatusHandler processes /api/v1/status/tsdb request. // // See https://prometheus.io/docs/prometheus/latest/querying/api/#tsdb-stats +// +// It can accept `match[]` filters in order to narrow down the search. func TSDBStatusHandler(startTime time.Time, w http.ResponseWriter, r *http.Request) error { deadline := searchutils.GetDeadlineForStatusRequest(r, startTime) if err := r.ParseForm(); err != nil { @@ -677,7 +679,7 @@ func TSDBStatusHandler(startTime time.Time, w http.ResponseWriter, r *http.Reque } else { status, err = tsdbStatusWithMatches(matches, etf, date, topN, deadline) if err != nil { - return fmt.Errorf("cannot tsdb status with matches for date=%d, topN=%d: %w", date, topN, err) + return fmt.Errorf("cannot obtain tsdb status with matches for date=%d, topN=%d: %w", date, topN, err) } } w.Header().Set("Content-Type", "application/json; charset=utf-8") @@ -696,7 +698,6 @@ func tsdbStatusWithMatches(matches []string, etf []storage.TagFilter, date uint6 if err != nil { return nil, err } - tagFilterss = addEnforcedFiltersToTagFilterss(tagFilterss, etf) if len(tagFilterss) == 0 { logger.Panicf("BUG: tagFilterss must be non-empty") diff --git a/app/vmstorage/main.go b/app/vmstorage/main.go index 485857f33..7b052a206 100644 --- a/app/vmstorage/main.go +++ b/app/vmstorage/main.go @@ -213,10 +213,10 @@ func GetTSDBStatusForDate(date uint64, topN int, deadline uint64) (*storage.TSDB return status, err } -// GetTSDBStatusWithFilters returns TSDB status for given filters. -func GetTSDBStatusWithFilters(tr storage.TimeRange, tfss []*storage.TagFilters, topN, maxMetrics int, deadline uint64) (*storage.TSDBStatus, error) { +// GetTSDBStatusWithFiltersForDate returns TSDB status for given filters on the given date. +func GetTSDBStatusWithFiltersForDate(tfss []*storage.TagFilters, date uint64, topN int, deadline uint64) (*storage.TSDBStatus, error) { WG.Add(1) - status, err := Storage.GetTSDBStatusForDateWithFilters(tfss, tr, maxMetrics, deadline, topN) + status, err := Storage.GetTSDBStatusWithFiltersForDate(tfss, date, topN, deadline) WG.Done() return status, err } diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 32ea4b5dc..f21ccd808 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -8,6 +8,8 @@ sort: 15 * FEATURE: vmalert: add flag to control behaviour on startup for state restore errors. Such errors were returned and logged before as well. But now user can specify whether to just log these errors (`-remoteRead.ignoreRestoreErrors=true`) or to stop the process (`-remoteRead.ignoreRestoreErrors=false`). The latter is important when VM isn't ready yet to serve queries from vmalert and it needs to wait. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1252). * FEATURE: vmalert: add ability to pass `round_digits` query arg to datasource via `-datasource.roundDigits` command-line flag. This can be used for limiting the number of decimal digits after the point in recording rule results. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/525). * FEATURE: return `X-Server-Hostname` header in http responses of all the VictoriaMetrics components. This should simplify tracing the origin server behind a load balancer or behind auth proxy during troubleshooting. +* FEATURE: vmselect: allow to use 2x more memory for query processing at `vmselect` nodes in [VictoriaMetrics cluster](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html). This should allow processing heavy queries without the need to increase RAM size at `vmselect` nodes. +* FEATURE: add ability to filter `/api/v1/status/tsdb` output with arbitrary [time series selectors](https://prometheus.io/docs/prometheus/latest/querying/basics/#time-series-selectors) passed via `match[]` query args. See [these docs](https://docs.victoriametrics.com/#tsdb-stats) and [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1168) for details. * BUGFIX: vmagent: fix possible race when refreshing `role: endpoints` and `role: endpointslices` scrape targets in `kubernetes_sd_config`. Prevoiusly `pod` objects could be updated after the related `endpoints` object update. This could lead to missing scrape targets. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1240). * BUGFIX: properly remove stale parts outside the configured retention if `-retentionPeriod` is smaller than one month. Previously stale parts could remain active for up to a month after they go outside the retention. diff --git a/docs/Cluster-VictoriaMetrics.md b/docs/Cluster-VictoriaMetrics.md index ec971cad2..128cc06a1 100644 --- a/docs/Cluster-VictoriaMetrics.md +++ b/docs/Cluster-VictoriaMetrics.md @@ -201,9 +201,7 @@ It is recommended setting up alerts in [vmalert](https://docs.victoriametrics.co - `api/v1/export/native` - exports raw data in native binary format. It may be imported into another VictoriaMetrics via `api/v1/import/native` (see above). - `api/v1/export/csv` - exports data in CSV. It may be imported into another VictoriaMetrics via `api/v1/import/csv` (see above). - `api/v1/series/count` - returns the total number of series. - - `api/v1/status/tsdb` - for time series stats. See [these docs](https://prometheus.io/docs/prometheus/latest/querying/api/#tsdb-stats) for details. - VictoriaMetrics accepts optional `topN=N` and `date=YYYY-MM-DD` query args for this handler, where `N` is the number of top entries to return in the response - and `YYYY-MM-DD` is the date for collecting the stats. By default the stats is collected for the current day. + - `api/v1/status/tsdb` - for time series stats. See [these docs](https://docs.victoriametrics.com/#tsdb-stats) for details. - `api/v1/status/active_queries` - for currently executed active queries. Note that every `vmselect` maintains an independent list of active queries, which is returned in the response. - `api/v1/status/top_queries` - for listing the most frequently executed queries and queries taking the most duration. diff --git a/docs/Single-server-VictoriaMetrics.md b/docs/Single-server-VictoriaMetrics.md index 64657442e..93ce45acc 100644 --- a/docs/Single-server-VictoriaMetrics.md +++ b/docs/Single-server-VictoriaMetrics.md @@ -553,9 +553,7 @@ VictoriaMetrics supports the following handlers from [Prometheus querying API](h * [/api/v1/series](https://prometheus.io/docs/prometheus/latest/querying/api/#finding-series-by-label-matchers) * [/api/v1/labels](https://prometheus.io/docs/prometheus/latest/querying/api/#getting-label-names) * [/api/v1/label/.../values](https://prometheus.io/docs/prometheus/latest/querying/api/#querying-label-values) -* [/api/v1/status/tsdb](https://prometheus.io/docs/prometheus/latest/querying/api/#tsdb-stats). VictoriaMetrics accepts optional `topN=N` and `date=YYYY-MM-DD` - query args for this handler, where `N` is the number of top entries to return in the response and `YYYY-MM-DD` is the date for collecting the stats. - By default top 10 entries are returned and the stats is collected for the current day. +* [/api/v1/status/tsdb](https://prometheus.io/docs/prometheus/latest/querying/api/#tsdb-stats). See [these docs](#tsdb-stats) for details. * [/api/v1/targets](https://prometheus.io/docs/prometheus/latest/querying/api/#targets) - see [these docs](#how-to-scrape-prometheus-exporters-such-as-node-exporter) for more details. These handlers can be queried from Prometheus-compatible clients such as Grafana or curl. @@ -1328,6 +1326,16 @@ VictoriaMetrics also exposes currently running queries with their execution time See the example of alerting rules for VM components [here](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/deployment/docker/alerts.yml). + +## TSDB stats + +VictoriaMetrics retuns TSDB stats at `/api/v1/status/tsdb` page in the way similar to Prometheus - see [these Prometheus docs](https://prometheus.io/docs/prometheus/latest/querying/api/#tsdb-stats). VictoriaMetrics accepts the following optional query args at `/api/v1/status/tsdb` page: + * `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. + * `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. + + ## Troubleshooting * It is recommended to use default command-line flag values (i.e. don't set them explicitly) until the need @@ -1384,10 +1392,7 @@ See the example of alerting rules for VM components [here](https://github.com/Vi It may be needed in order to suppress default gap filling algorithm used by VictoriaMetrics - by default it assumes each time series is continuous instead of discrete, so it fills gaps between real samples with regular intervals. -* Metrics and labels leading to high cardinality or high churn rate can be determined at `/api/v1/status/tsdb` page. - See [these docs](https://prometheus.io/docs/prometheus/latest/querying/api/#tsdb-stats) for details. - VictoriaMetrics accepts optional `date=YYYY-MM-DD` and `topN=42` args on this page. By default `date` equals to the current date, - while `topN` equals to 10. +* Metrics and labels leading to high cardinality or high churn rate can be determined at `/api/v1/status/tsdb` page. See [these docs](#tsdb-stats) for details. * New time series can be logged if `-logNewSeries` command-line flag is passed to VictoriaMetrics. diff --git a/lib/storage/index_db.go b/lib/storage/index_db.go index 393dca868..5938932f7 100644 --- a/lib/storage/index_db.go +++ b/lib/storage/index_db.go @@ -1309,10 +1309,10 @@ func (is *indexSearch) getSeriesCount() (uint64, error) { return metricIDsLen, nil } -// GetTSDBStatusWithFiltersOnTimeRange returns topN entries for tsdb status for given TSIDs. -func (db *indexDB) GetTSDBStatusWithFiltersOnTimeRange(tfss []*TagFilters, tr TimeRange, maxMetrics, topN int, deadline uint64) (*TSDBStatus, error) { +// GetTSDBStatusWithFiltersForDate returns topN entries for tsdb status for the given tfss and the given date. +func (db *indexDB) GetTSDBStatusWithFiltersForDate(tfss []*TagFilters, date uint64, topN int, deadline uint64) (*TSDBStatus, error) { is := db.getIndexSearch(deadline) - status, err := is.GetTSDBStatusWithFiltersOnTimeRange(tfss, tr, maxMetrics, topN, deadline) + status, err := is.getTSDBStatusWithFiltersForDate(tfss, date, topN, deadline) db.putIndexSearch(is) if err != nil { return nil, err @@ -1322,7 +1322,7 @@ func (db *indexDB) GetTSDBStatusWithFiltersOnTimeRange(tfss []*TagFilters, tr Ti } ok := db.doExtDB(func(extDB *indexDB) { is := extDB.getIndexSearch(deadline) - status, err = is.GetTSDBStatusWithFiltersOnTimeRange(tfss, tr, maxMetrics, topN, deadline) + status, err = is.getTSDBStatusWithFiltersForDate(tfss, date, topN, deadline) extDB.putIndexSearch(is) }) if ok && err != nil { @@ -1331,81 +1331,116 @@ func (db *indexDB) GetTSDBStatusWithFiltersOnTimeRange(tfss []*TagFilters, tr Ti return status, nil } -// GetTSDBStatusWithFiltersOnTimeRange returns topN entries for tsdb status for given TSIDs. -func (is *indexSearch) GetTSDBStatusWithFiltersOnTimeRange(tfss []*TagFilters, tr TimeRange, maxMetrics, topN int, deadline uint64) (*TSDBStatus, error) { - metricIDs, err := is.searchMetricIDs(tfss, tr, maxMetrics) +// getTSDBStatusWithFiltersForDate returns topN entries for tsdb status for the given tfss and the given date. +func (is *indexSearch) getTSDBStatusWithFiltersForDate(tfss []*TagFilters, date uint64, topN int, deadline uint64) (*TSDBStatus, error) { + tr := TimeRange{ + MinTimestamp: int64(date) * msecPerDay, + MaxTimestamp: int64(date+1) * msecPerDay, + } + metricIDs, err := is.searchMetricIDsInternal(tfss, tr, 2e9) if err != nil { return nil, err } + if metricIDs.Len() == 0 { + // Nothing found. + return &TSDBStatus{}, nil + } + // The code below must be in sync with getTSDBStatusForDate + ts := &is.ts + kb := &is.kb + mp := &is.mp thLabelValueCountByLabelName := newTopHeap(topN) thSeriesCountByLabelValuePair := newTopHeap(topN) thSeriesCountByMetricName := newTopHeap(topN) + var tmp, labelName, labelNameValue []byte + var labelValueCountByLabelName, seriesCountByLabelValuePair uint64 + nameEqualBytes := []byte("__name__=") - var metricName, tmpMetricName, labelPairs []byte - var mn MetricName - var metricNameCnt uint64 - metricNameLabel := "__name__" - tmpPairs := []byte(metricNameLabel) - - // holds uniq count values per label name. - cntByUniqLabelValues := make(map[string]uint64, len(metricIDs)) - // holds count for label=value uniq pairs. - cntLabelPairs := make(map[string]uint64, len(metricIDs)) - for i := range metricIDs { - if i&paceLimiterSlowIterationsMask == 0 { - if err := checkSearchDeadlineAndPace(deadline); err != nil { + loopsPaceLimiter := 0 + kb.B = is.marshalCommonPrefix(kb.B[:0], nsPrefixDateTagToMetricIDs) + kb.B = encoding.MarshalUint64(kb.B, date) + prefix := kb.B + ts.Seek(prefix) + for ts.NextItem() { + if loopsPaceLimiter&paceLimiterFastIterationsMask == 0 { + if err := checkSearchDeadlineAndPace(is.deadline); err != nil { return nil, err } } - mID := metricIDs[i] - metricName, err = is.searchMetricName(metricName[:0], mID) - if err == io.EOF { - continue + loopsPaceLimiter++ + item := ts.Item + if !bytes.HasPrefix(item, prefix) { + break } - if err != nil { + if err := mp.Init(item, nsPrefixDateTagToMetricIDs); err != nil { return nil, err } - if err = mn.Unmarshal(metricName); err != nil { - return nil, fmt.Errorf("cannot unmarshal metricName=%q: %w", metricName, err) - } - if !bytes.Equal(tmpMetricName, mn.MetricGroup) { - tmpMetricName = mn.MetricGroup - cntByUniqLabelValues[metricNameLabel]++ - tmpPairs = append(tmpPairs[:len(metricNameLabel)], '=') - tmpPairs = append(tmpPairs, mn.MetricGroup...) - thSeriesCountByMetricName.pushIfNonEmpty(tmpMetricName, metricNameCnt) - metricNameCnt = 0 - } - cntLabelPairs[string(tmpPairs)]++ - metricNameCnt++ - for j := range mn.Tags { - tag := mn.Tags[j] - labelPairs = append(labelPairs[:0], tag.Key...) - labelPairs = append(labelPairs, '=') - labelPairs = append(labelPairs, tag.Value...) - // if label pairs not seen, its uniq value for given label. - if _, ok := cntLabelPairs[string(labelPairs)]; ok { - cntLabelPairs[string(labelPairs)]++ - } else { - cntLabelPairs[string(labelPairs)]++ - cntByUniqLabelValues[string(tag.Key)]++ + mp.ParseMetricIDs() + matchingSeriesCount := 0 + for _, metricID := range mp.MetricIDs { + if metricIDs.Has(metricID) { + matchingSeriesCount++ } } + if matchingSeriesCount == 0 { + // Skip rows without matching metricIDs. + continue + } + tail := item[len(prefix):] + var err error + tail, tmp, err = unmarshalTagValue(tmp[:0], tail) + if err != nil { + return nil, fmt.Errorf("cannot unmarshal tag key from line %q: %w", item, err) + } + if isArtificialTagKey(tmp) { + // Skip artificially created tag keys. + continue + } + if len(tmp) == 0 { + tmp = append(tmp, "__name__"...) + } + if !bytes.Equal(tmp, labelName) { + thLabelValueCountByLabelName.pushIfNonEmpty(labelName, labelValueCountByLabelName) + labelValueCountByLabelName = 0 + labelName = append(labelName[:0], tmp...) + } + tmp = append(tmp, '=') + tail, tmp, err = unmarshalTagValue(tmp, tail) + if err != nil { + return nil, fmt.Errorf("cannot unmarshal tag value from line %q: %w", item, err) + } + if !bytes.Equal(tmp, labelNameValue) { + thSeriesCountByLabelValuePair.pushIfNonEmpty(labelNameValue, seriesCountByLabelValuePair) + if bytes.HasPrefix(labelNameValue, nameEqualBytes) { + thSeriesCountByMetricName.pushIfNonEmpty(labelNameValue[len(nameEqualBytes):], seriesCountByLabelValuePair) + } + seriesCountByLabelValuePair = 0 + labelValueCountByLabelName++ + labelNameValue = append(labelNameValue[:0], tmp...) + } + if err := mp.InitOnlyTail(item, tail); err != nil { + return nil, err + } + // Take into account deleted timeseries too. + // It is OK if series can be counted multiple times in rare cases - + // the returned number is an estimation. + seriesCountByLabelValuePair += uint64(matchingSeriesCount) } - thSeriesCountByMetricName.pushIfNonEmpty(tmpMetricName, metricNameCnt) - for k, v := range cntLabelPairs { - thSeriesCountByLabelValuePair.pushIfNonEmpty([]byte(k), v) + if err := ts.Error(); err != nil { + return nil, fmt.Errorf("error when counting time series by metric names: %w", err) } - for k, v := range cntByUniqLabelValues { - thLabelValueCountByLabelName.pushIfNonEmpty([]byte(k), v) + thLabelValueCountByLabelName.pushIfNonEmpty(labelName, labelValueCountByLabelName) + thSeriesCountByLabelValuePair.pushIfNonEmpty(labelNameValue, seriesCountByLabelValuePair) + if bytes.HasPrefix(labelNameValue, nameEqualBytes) { + thSeriesCountByMetricName.pushIfNonEmpty(labelNameValue[len(nameEqualBytes):], seriesCountByLabelValuePair) } - status := TSDBStatus{ + status := &TSDBStatus{ SeriesCountByMetricName: thSeriesCountByMetricName.getSortedResult(), LabelValueCountByLabelName: thLabelValueCountByLabelName.getSortedResult(), SeriesCountByLabelValuePair: thSeriesCountByLabelValuePair.getSortedResult(), } - return &status, nil + return status, nil } // GetTSDBStatusForDate returns topN entries for tsdb status for the given date. @@ -1434,6 +1469,7 @@ func (db *indexDB) GetTSDBStatusForDate(date uint64, topN int, deadline uint64) } func (is *indexSearch) getTSDBStatusForDate(date uint64, topN int) (*TSDBStatus, error) { + // The code below must be in sync with getTSDBStatusWithFiltersForDate ts := &is.ts kb := &is.kb mp := &is.mp @@ -2389,21 +2425,9 @@ func matchTagFilters(mn *MetricName, tfs []*tagFilter, kb *bytesutil.ByteBuffer) } func (is *indexSearch) searchMetricIDs(tfss []*TagFilters, tr TimeRange, maxMetrics int) ([]uint64, error) { - metricIDs := &uint64set.Set{} - for _, tfs := range tfss { - if len(tfs.tfs) == 0 { - // An empty filters must be equivalent to `{__name__!=""}` - tfs = NewTagFilters() - if err := tfs.Add(nil, nil, true, false); err != nil { - logger.Panicf(`BUG: cannot add {__name__!=""} filter: %s`, err) - } - } - if err := is.updateMetricIDsForTagFilters(metricIDs, tfs, tr, maxMetrics+1); err != nil { - return nil, err - } - if metricIDs.Len() > maxMetrics { - return nil, fmt.Errorf("the number of matching unique timeseries exceeds %d; either narrow down the search or increase -search.maxUniqueTimeseries", maxMetrics) - } + metricIDs, err := is.searchMetricIDsInternal(tfss, tr, maxMetrics) + if err != nil { + return nil, err } if metricIDs.Len() == 0 { // Nothing found @@ -2427,6 +2451,26 @@ func (is *indexSearch) searchMetricIDs(tfss []*TagFilters, tr TimeRange, maxMetr return sortedMetricIDs, nil } +func (is *indexSearch) searchMetricIDsInternal(tfss []*TagFilters, tr TimeRange, maxMetrics int) (*uint64set.Set, error) { + metricIDs := &uint64set.Set{} + for _, tfs := range tfss { + if len(tfs.tfs) == 0 { + // An empty filters must be equivalent to `{__name__!=""}` + tfs = NewTagFilters() + if err := tfs.Add(nil, nil, true, false); err != nil { + logger.Panicf(`BUG: cannot add {__name__!=""} filter: %s`, err) + } + } + if err := is.updateMetricIDsForTagFilters(metricIDs, tfs, tr, maxMetrics+1); err != nil { + return nil, err + } + if metricIDs.Len() > maxMetrics { + return nil, fmt.Errorf("the number of matching unique timeseries exceeds %d; either narrow down the search or increase -search.maxUniqueTimeseries", maxMetrics) + } + } + return metricIDs, nil +} + func (is *indexSearch) updateMetricIDsForTagFilters(metricIDs *uint64set.Set, tfs *TagFilters, tr TimeRange, maxMetrics int) error { err := is.tryUpdatingMetricIDsForDateRange(metricIDs, tfs, tr, maxMetrics) if err == nil { diff --git a/lib/storage/index_db_test.go b/lib/storage/index_db_test.go index a879f5529..a9bbbe847 100644 --- a/lib/storage/index_db_test.go +++ b/lib/storage/index_db_test.go @@ -1692,19 +1692,14 @@ func TestSearchTSIDWithTimeRange(t *testing.T) { t.Fatalf("unexpected SeriesCountByLabelValuePair;\ngot\n%v\nwant\n%v", status.SeriesCountByLabelValuePair, expectedSeriesCountByLabelValuePair) } - // Perform a search across all the days, should match all metrics - tr = TimeRange{ - MinTimestamp: int64(now), - MaxTimestamp: int64(now - msecPerDay*days), - } - + // Check GetTSDBStatusWithFiltersForDate tfs = NewTagFilters() - if err := tfs.Add([]byte("day"), []byte("3"), false, false); err != nil { + if err := tfs.Add([]byte("day"), []byte("0"), false, false); err != nil { t.Fatalf("cannot add filter: %s", err) } - status, err = db.GetTSDBStatusWithFiltersOnTimeRange([]*TagFilters{tfs}, tr, 10000, 5, noDeadline) + status, err = db.GetTSDBStatusWithFiltersForDate([]*TagFilters{tfs}, baseDate, 5, noDeadline) if err != nil { - t.Fatalf("error in GetTSDBStatusForDate: %s", err) + t.Fatalf("error in GetTSDBStatusWithFiltersForDate: %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 f24f7af76..772204ca4 100644 --- a/lib/storage/storage.go +++ b/lib/storage/storage.go @@ -1248,9 +1248,9 @@ func (s *Storage) GetTSDBStatusForDate(date uint64, topN int, deadline uint64) ( return s.idb().GetTSDBStatusForDate(date, topN, deadline) } -// GetTSDBStatusForDateWithFilters special function for /api/v1/status/tsdb with match[] filters. -func (s *Storage) GetTSDBStatusForDateWithFilters(tfss []*TagFilters, tr TimeRange, maxMetrics int, deadline uint64, topN int) (*TSDBStatus, error) { - return s.idb().GetTSDBStatusWithFiltersOnTimeRange(tfss, tr, maxMetrics, topN, deadline) +// GetTSDBStatusWithFiltersForDate returns TSDB status data for /api/v1/status/tsdb with match[] filters. +func (s *Storage) GetTSDBStatusWithFiltersForDate(tfss []*TagFilters, date uint64, topN int, deadline uint64) (*TSDBStatus, error) { + return s.idb().GetTSDBStatusWithFiltersForDate(tfss, date, topN, deadline) } // MetricRow is a metric to insert into storage.