mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2025-01-10 15:14:09 +00:00
Merge branch 'public-single-node' into pmm-6401-read-prometheus-data-files
This commit is contained in:
commit
8632b8200e
29 changed files with 574 additions and 417 deletions
|
@ -268,6 +268,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 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.
|
||||
|
||||
|
@ -275,8 +277,6 @@ By default cardinality explorer analyzes time series for the current date. It pr
|
|||
By default all the time series for the selected date are analyzed. It is possible to narrow down the analysis to series
|
||||
matching the specified [series selector](https://prometheus.io/docs/prometheus/latest/querying/basics/#time-series-selectors).
|
||||
|
||||
Cardinality explorer takes into account [deleted time series](#how-to-delete-time-series), because they stay in the inverted index for up to [-retentionPeriod](#retention). This means that the deleted time series take RAM, CPU, disk IO and disk space for the inverted index in the same way as other time series.
|
||||
|
||||
Cardinality explorer is built on top of [/api/v1/status/tsdb](#tsdb-stats).
|
||||
|
||||
See [cardinality explorer playground](https://play.victoriametrics.com/select/accounting/1/6a716b0f-38bc-4856-90ce-448fd713e3fe/prometheus/graph/#/cardinality).
|
||||
|
@ -1442,6 +1442,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.
|
||||
|
||||
|
|
|
@ -845,25 +845,11 @@ func GetTagValueSuffixes(qt *querytracer.Tracer, tr storage.TimeRange, tagKey, t
|
|||
return suffixes, nil
|
||||
}
|
||||
|
||||
// GetTSDBStatusForDate returns tsdb status according to https://prometheus.io/docs/prometheus/latest/querying/api/#tsdb-stats
|
||||
func GetTSDBStatusForDate(qt *querytracer.Tracer, deadline searchutils.Deadline, date uint64, topN, maxMetrics int) (*storage.TSDBStatus, error) {
|
||||
qt = qt.NewChild("get tsdb stats for date=%d, topN=%d", date, topN)
|
||||
defer qt.Done()
|
||||
if deadline.Exceeded() {
|
||||
return nil, fmt.Errorf("timeout exceeded before starting the query processing: %s", deadline.String())
|
||||
}
|
||||
status, err := vmstorage.GetTSDBStatusForDate(qt, date, topN, maxMetrics, deadline.Deadline())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error during tsdb status request: %w", err)
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
|
||||
// GetTSDBStatusWithFilters returns tsdb status according to https://prometheus.io/docs/prometheus/latest/querying/api/#tsdb-stats
|
||||
// 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 GetTSDBStatusWithFilters(qt *querytracer.Tracer, deadline searchutils.Deadline, sq *storage.SearchQuery, topN int) (*storage.TSDBStatus, error) {
|
||||
qt = qt.NewChild("get tsdb stats: %s, topN=%d", sq, topN)
|
||||
func GetTSDBStatus(qt *querytracer.Tracer, sq *storage.SearchQuery, focusLabel string, topN int, deadline searchutils.Deadline) (*storage.TSDBStatus, error) {
|
||||
qt = qt.NewChild("get tsdb stats: %s, focusLabel=%q, topN=%d", sq, focusLabel, topN)
|
||||
defer qt.Done()
|
||||
if deadline.Exceeded() {
|
||||
return nil, fmt.Errorf("timeout exceeded before starting the query processing: %s", deadline.String())
|
||||
|
@ -877,9 +863,9 @@ func GetTSDBStatusWithFilters(qt *querytracer.Tracer, deadline searchutils.Deadl
|
|||
return nil, err
|
||||
}
|
||||
date := uint64(tr.MinTimestamp) / (3600 * 24 * 1000)
|
||||
status, err := vmstorage.GetTSDBStatusWithFiltersForDate(qt, tfss, date, topN, sq.MaxMetrics, deadline.Deadline())
|
||||
status, err := vmstorage.GetTSDBStatus(qt, tfss, date, focusLabel, topN, sq.MaxMetrics, deadline.Deadline())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error during tsdb status with filters request: %w", err)
|
||||
return nil, fmt.Errorf("error during tsdb status request: %w", err)
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
|
|
|
@ -490,12 +490,17 @@ func TSDBStatusHandler(qt *querytracer.Tracer, startTime time.Time, w http.Respo
|
|||
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 {
|
||||
|
@ -511,18 +516,14 @@ func TSDBStatusHandler(qt *querytracer.Tracer, startTime time.Time, w http.Respo
|
|||
}
|
||||
topN = n
|
||||
}
|
||||
var status *storage.TSDBStatus
|
||||
if len(cp.filterss) == 0 {
|
||||
status, err = netstorage.GetTSDBStatusForDate(qt, 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, err = tsdbStatusWithMatches(qt, 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(start, end, cp.filterss, *maxTSDBStatusSeries)
|
||||
status, err := netstorage.GetTSDBStatus(qt, sq, focusLabel, topN, cp.deadline)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot obtain tsdb stats: %w", err)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
bw := bufferedwriter.Get(w)
|
||||
defer bufferedwriter.Put(bw)
|
||||
|
@ -533,17 +534,6 @@ func TSDBStatusHandler(qt *querytracer.Tracer, startTime time.Time, w http.Respo
|
|||
return nil
|
||||
}
|
||||
|
||||
func tsdbStatusWithMatches(qt *querytracer.Tracer, filterss [][]storage.TagFilter, date uint64, topN, maxMetrics int, deadline searchutils.Deadline) (*storage.TSDBStatus, error) {
|
||||
start := int64(date*secsPerDay) * 1000
|
||||
end := int64(date*secsPerDay+secsPerDay) * 1000
|
||||
sq := storage.NewSearchQuery(start, end, filterss, maxMetrics)
|
||||
status, err := netstorage.GetTSDBStatusWithFilters(qt, deadline, sq, topN)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
|
||||
var tsdbStatusDuration = metrics.NewSummary(`vm_request_duration_seconds{path="/api/v1/status/tsdb"}`)
|
||||
|
||||
// LabelsHandler processes /api/v1/labels request.
|
||||
|
|
|
@ -12,6 +12,8 @@ TSDBStatusResponse generates response for /api/v1/status/tsdb .
|
|||
"totalSeries": {%dul= status.TotalSeries %},
|
||||
"totalLabelValuePairs": {%dul= status.TotalLabelValuePairs %},
|
||||
"seriesCountByMetricName":{%= tsdbStatusEntries(status.SeriesCountByMetricName) %},
|
||||
"seriesCountByLabelName":{%= tsdbStatusEntries(status.SeriesCountByLabelName) %},
|
||||
"seriesCountByFocusLabelValue":{%= tsdbStatusEntries(status.SeriesCountByFocusLabelValue) %},
|
||||
"seriesCountByLabelValuePair":{%= tsdbStatusEntries(status.SeriesCountByLabelValuePair) %},
|
||||
"labelValueCountByLabelName":{%= tsdbStatusEntries(status.LabelValueCountByLabelName) %}
|
||||
}
|
||||
|
|
|
@ -40,102 +40,110 @@ func StreamTSDBStatusResponse(qw422016 *qt422016.Writer, status *storage.TSDBSta
|
|||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:14
|
||||
streamtsdbStatusEntries(qw422016, status.SeriesCountByMetricName)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:14
|
||||
qw422016.N().S(`,"seriesCountByLabelName":`)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:15
|
||||
streamtsdbStatusEntries(qw422016, status.SeriesCountByLabelName)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:15
|
||||
qw422016.N().S(`,"seriesCountByFocusLabelValue":`)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:16
|
||||
streamtsdbStatusEntries(qw422016, status.SeriesCountByFocusLabelValue)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:16
|
||||
qw422016.N().S(`,"seriesCountByLabelValuePair":`)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:15
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:17
|
||||
streamtsdbStatusEntries(qw422016, status.SeriesCountByLabelValuePair)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:15
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:17
|
||||
qw422016.N().S(`,"labelValueCountByLabelName":`)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:16
|
||||
streamtsdbStatusEntries(qw422016, status.LabelValueCountByLabelName)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:16
|
||||
qw422016.N().S(`}`)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:18
|
||||
streamtsdbStatusEntries(qw422016, status.LabelValueCountByLabelName)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:18
|
||||
qw422016.N().S(`}`)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:20
|
||||
qt.Done()
|
||||
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:19
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:21
|
||||
streamdumpQueryTrace(qw422016, qt)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:19
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:21
|
||||
qw422016.N().S(`}`)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:21
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:23
|
||||
}
|
||||
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:21
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:23
|
||||
func WriteTSDBStatusResponse(qq422016 qtio422016.Writer, status *storage.TSDBStatus, qt *querytracer.Tracer) {
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:21
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:23
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:21
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:23
|
||||
StreamTSDBStatusResponse(qw422016, status, qt)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:21
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:23
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:21
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:23
|
||||
}
|
||||
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:21
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:23
|
||||
func TSDBStatusResponse(status *storage.TSDBStatus, qt *querytracer.Tracer) string {
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:21
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:23
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:21
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:23
|
||||
WriteTSDBStatusResponse(qb422016, status, qt)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:21
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:23
|
||||
qs422016 := string(qb422016.B)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:21
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:23
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:21
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:23
|
||||
return qs422016
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:21
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:23
|
||||
}
|
||||
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:23
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:25
|
||||
func streamtsdbStatusEntries(qw422016 *qt422016.Writer, a []storage.TopHeapEntry) {
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:23
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:25
|
||||
qw422016.N().S(`[`)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:25
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:27
|
||||
for i, e := range a {
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:25
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:27
|
||||
qw422016.N().S(`{"name":`)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:27
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:29
|
||||
qw422016.N().Q(e.Name)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:27
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:29
|
||||
qw422016.N().S(`,"value":`)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:28
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:30
|
||||
qw422016.N().D(int(e.Count))
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:28
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:30
|
||||
qw422016.N().S(`}`)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:30
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:32
|
||||
if i+1 < len(a) {
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:30
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:32
|
||||
qw422016.N().S(`,`)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:30
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:32
|
||||
}
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:31
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:33
|
||||
}
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:31
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:33
|
||||
qw422016.N().S(`]`)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:33
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:35
|
||||
}
|
||||
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:33
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:35
|
||||
func writetsdbStatusEntries(qq422016 qtio422016.Writer, a []storage.TopHeapEntry) {
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:33
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:35
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:33
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:35
|
||||
streamtsdbStatusEntries(qw422016, a)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:33
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:35
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:33
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:35
|
||||
}
|
||||
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:33
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:35
|
||||
func tsdbStatusEntries(a []storage.TopHeapEntry) string {
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:33
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:35
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:33
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:35
|
||||
writetsdbStatusEntries(qb422016, a)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:33
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:35
|
||||
qs422016 := string(qb422016.B)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:33
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:35
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:33
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:35
|
||||
return qs422016
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:33
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:35
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"files": {
|
||||
"main.css": "./static/css/main.7e6d0c89.css",
|
||||
"main.js": "./static/js/main.42cb1c78.js",
|
||||
"main.js": "./static/js/main.f7185a13.js",
|
||||
"static/js/27.939f971b.chunk.js": "./static/js/27.939f971b.chunk.js",
|
||||
"index.html": "./index.html"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.7e6d0c89.css",
|
||||
"static/js/main.42cb1c78.js"
|
||||
"static/js/main.f7185a13.js"
|
||||
]
|
||||
}
|
|
@ -1 +1 @@
|
|||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="VM-UI is a metric explorer for Victoria Metrics"/><link rel="apple-touch-icon" href="./apple-touch-icon.png"/><link rel="icon" type="image/png" sizes="32x32" href="./favicon-32x32.png"><link rel="manifest" href="./manifest.json"/><title>VM UI</title><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"/><script src="./dashboards/index.js" type="module"></script><script defer="defer" src="./static/js/main.42cb1c78.js"></script><link href="./static/css/main.7e6d0c89.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="VM-UI is a metric explorer for Victoria Metrics"/><link rel="apple-touch-icon" href="./apple-touch-icon.png"/><link rel="icon" type="image/png" sizes="32x32" href="./favicon-32x32.png"><link rel="manifest" href="./manifest.json"/><title>VM UI</title><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"/><script src="./dashboards/index.js" type="module"></script><script defer="defer" src="./static/js/main.f7185a13.js"></script><link href="./static/css/main.7e6d0c89.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
File diff suppressed because one or more lines are too long
|
@ -218,18 +218,10 @@ func SearchGraphitePaths(tr storage.TimeRange, query []byte, maxPaths int, deadl
|
|||
return paths, err
|
||||
}
|
||||
|
||||
// GetTSDBStatusForDate returns TSDB status for the given date.
|
||||
func GetTSDBStatusForDate(qt *querytracer.Tracer, date uint64, topN, maxMetrics int, deadline uint64) (*storage.TSDBStatus, error) {
|
||||
// GetTSDBStatus returns TSDB status for given filters on the given date.
|
||||
func GetTSDBStatus(qt *querytracer.Tracer, tfss []*storage.TagFilters, date uint64, focusLabel string, topN, maxMetrics int, deadline uint64) (*storage.TSDBStatus, error) {
|
||||
WG.Add(1)
|
||||
status, err := Storage.GetTSDBStatusWithFiltersForDate(qt, nil, date, topN, maxMetrics, deadline)
|
||||
WG.Done()
|
||||
return status, err
|
||||
}
|
||||
|
||||
// GetTSDBStatusWithFiltersForDate returns TSDB status for given filters on the given date.
|
||||
func GetTSDBStatusWithFiltersForDate(qt *querytracer.Tracer, tfss []*storage.TagFilters, date uint64, topN, maxMetrics int, deadline uint64) (*storage.TSDBStatus, error) {
|
||||
WG.Add(1)
|
||||
status, err := Storage.GetTSDBStatusWithFiltersForDate(qt, tfss, date, topN, maxMetrics, deadline)
|
||||
status, err := Storage.GetTSDBStatus(qt, tfss, date, focusLabel, topN, maxMetrics, deadline)
|
||||
WG.Done()
|
||||
return status, err
|
||||
}
|
||||
|
|
22
app/vmui/packages/vmui/package-lock.json
generated
22
app/vmui/packages/vmui/package-lock.json
generated
|
@ -17808,16 +17808,6 @@
|
|||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"node_modules/svgo/node_modules/nth-check": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz",
|
||||
"integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"boolbase": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/symbol-tree": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
|
||||
|
@ -32702,7 +32692,7 @@
|
|||
"boolbase": "^1.0.0",
|
||||
"css-what": "^3.2.1",
|
||||
"domutils": "^1.7.0",
|
||||
"nth-check": "^1.0.2"
|
||||
"nth-check": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"css-what": {
|
||||
|
@ -32753,16 +32743,6 @@
|
|||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"nth-check": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz",
|
||||
"integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"boolbase": "~1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -9,7 +9,7 @@ const BarChart: FC<BarChartProps> = ({
|
|||
configs}) => {
|
||||
|
||||
const uPlotRef = useRef<HTMLDivElement>(null);
|
||||
const [isPanning, setPanning] = useState(false);
|
||||
const [isPanning] = useState(false);
|
||||
const [uPlotInst, setUPlotInst] = useState<uPlot>();
|
||||
const layoutSize = useResize(container);
|
||||
|
||||
|
|
|
@ -20,6 +20,10 @@ export interface CardinalityConfiguratorProps {
|
|||
query: string;
|
||||
topN: number;
|
||||
error?: ErrorTypes | string;
|
||||
totalSeries: number;
|
||||
totalLabelValuePairs: number;
|
||||
date: string | null;
|
||||
match: string | null;
|
||||
}
|
||||
|
||||
const CardinalityConfigurator: FC<CardinalityConfiguratorProps> = ({
|
||||
|
@ -29,7 +33,12 @@ const CardinalityConfigurator: FC<CardinalityConfiguratorProps> = ({
|
|||
onSetHistory,
|
||||
onRunQuery,
|
||||
onSetQuery,
|
||||
onTopNChange }) => {
|
||||
onTopNChange,
|
||||
totalSeries,
|
||||
totalLabelValuePairs,
|
||||
date,
|
||||
match
|
||||
}) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const {queryControls: {autocomplete}} = useAppState();
|
||||
const {queryOptions} = useFetchQueryOptions();
|
||||
|
@ -41,36 +50,40 @@ const CardinalityConfigurator: FC<CardinalityConfiguratorProps> = ({
|
|||
|
||||
return <Box boxShadow="rgba(99, 99, 99, 0.2) 0px 2px 8px 0px;" p={4} pb={2} mb={2}>
|
||||
<Box>
|
||||
<Box display="grid" gridTemplateColumns="1fr auto auto" gap="4px" width="100%" mb={0}>
|
||||
<Box display="grid" gridTemplateColumns="1fr auto auto" gap="4px" width="50%" mb={4}>
|
||||
<QueryEditor
|
||||
query={query} index={0} autocomplete={autocomplete} queryOptions={queryOptions}
|
||||
error={error} setHistoryIndex={onSetHistory} runQuery={onRunQuery} setQuery={onSetQuery}
|
||||
label={"Arbitrary time series selector"}
|
||||
label={"Time series selector"}
|
||||
/>
|
||||
<Tooltip title="Execute Query">
|
||||
<IconButton onClick={onRunQuery} sx={{height: "49px", width: "49px"}}>
|
||||
<PlayCircleOutlineIcon/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Box display="flex" alignItems="center">
|
||||
<Box ml={2}>
|
||||
<TextField
|
||||
label="Number of entries per table"
|
||||
type="number"
|
||||
size="small"
|
||||
variant="outlined"
|
||||
value={topN}
|
||||
error={topN < 1}
|
||||
helperText={topN < 1 ? "Number must be bigger than zero" : " "}
|
||||
onChange={onTopNChange}/>
|
||||
</Box>
|
||||
<Tooltip title="Execute Query">
|
||||
<IconButton onClick={onRunQuery} sx={{height: "49px", width: "49px"}}>
|
||||
<PlayCircleOutlineIcon/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Box>
|
||||
<FormControlLabel label="Enable autocomplete"
|
||||
control={<BasicSwitch checked={autocomplete} onChange={onChangeAutocomplete}/>}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box display="flex" alignItems="center" mt={3} mr={"53px"}>
|
||||
<Box>
|
||||
<FormControlLabel label="Enable autocomplete"
|
||||
control={<BasicSwitch checked={autocomplete} onChange={onChangeAutocomplete}/>}
|
||||
/>
|
||||
</Box>
|
||||
<Box ml={2}>
|
||||
<TextField
|
||||
label="Number of top entries"
|
||||
type="number"
|
||||
size="small"
|
||||
variant="outlined"
|
||||
value={topN}
|
||||
error={topN < 1}
|
||||
helperText={topN < 1 ? "Number must be bigger than zero" : " "}
|
||||
onChange={onTopNChange}/>
|
||||
</Box>
|
||||
<Box>
|
||||
Analyzed <b>{totalSeries}</b> series with <b>{totalLabelValuePairs}</b> label=value pairs
|
||||
at <b>{date}</b> {match && <span>for series selector <b>{match}</b></span>}. Show top {topN} entries per table.
|
||||
</Box>
|
||||
</Box>;
|
||||
};
|
||||
|
|
|
@ -1,26 +1,20 @@
|
|||
import React, {ChangeEvent, FC, useState} from "react";
|
||||
import {SyntheticEvent} from "react";
|
||||
import {Typography, Grid, Alert, Box, Tabs, Tab, Tooltip} from "@mui/material";
|
||||
import TableChartIcon from "@mui/icons-material/TableChart";
|
||||
import ShowChartIcon from "@mui/icons-material/ShowChart";
|
||||
import {Alert} from "@mui/material";
|
||||
import {useFetchQuery} from "../../hooks/useCardinalityFetch";
|
||||
import EnhancedTable from "../Table/Table";
|
||||
import {TSDBStatus, TopHeapEntry, DefaultState, Tabs as TabsType, Containers} from "./types";
|
||||
import {
|
||||
defaultHeadCells,
|
||||
headCellsWithProgress,
|
||||
SPINNER_TITLE,
|
||||
METRIC_NAMES_HEADERS,
|
||||
LABEL_NAMES_HEADERS,
|
||||
LABEL_VALUE_PAIRS_HEADERS,
|
||||
LABELS_WITH_UNIQUE_VALUES_HEADERS,
|
||||
spinnerContainerStyles
|
||||
} from "./consts";
|
||||
import {defaultProperties, progressCount, queryUpdater, tableTitles} from "./helpers";
|
||||
import {defaultProperties, queryUpdater} from "./helpers";
|
||||
import {Data} from "../Table/types";
|
||||
import BarChart from "../BarChart/BarChart";
|
||||
import CardinalityConfigurator from "./CardinalityConfigurator/CardinalityConfigurator";
|
||||
import {barOptions} from "../BarChart/consts";
|
||||
import Spinner from "../common/Spinner";
|
||||
import TabPanel from "../TabPanel/TabPanel";
|
||||
import {useCardinalityDispatch, useCardinalityState} from "../../state/cardinality/CardinalityStateContext";
|
||||
import {tableCells} from "./TableCells/TableCells";
|
||||
import MetricsContent from "./MetricsContent/MetricsContent";
|
||||
|
||||
const CardinalityPanel: FC = () => {
|
||||
const cardinalityDispatch = useCardinalityDispatch();
|
||||
|
@ -80,76 +74,61 @@ const CardinalityPanel: FC = () => {
|
|||
height={"800px"}
|
||||
containerStyles={spinnerContainerStyles("100%")}
|
||||
title={<Alert color="error" severity="error" sx={{whiteSpace: "pre-wrap", mt: 2}}>
|
||||
{SPINNER_TITLE}
|
||||
Please wait while cardinality stats is calculated. This may take some time if the db contains big number of time series
|
||||
</Alert>}
|
||||
/>}
|
||||
<CardinalityConfigurator error={configError} query={query} onRunQuery={onRunQuery} onSetQuery={onSetQuery}
|
||||
onSetHistory={onSetHistory} onTopNChange={onTopNChange} topN={topN} />
|
||||
onSetHistory={onSetHistory} onTopNChange={onTopNChange} topN={topN} date={date} match={match}
|
||||
totalSeries={tsdbStatus.totalSeries} totalLabelValuePairs={tsdbStatus.totalLabelValuePairs}/>
|
||||
{error && <Alert color="error" severity="error" sx={{whiteSpace: "pre-wrap", mt: 2}}>{error}</Alert>}
|
||||
{<Box m={2}>
|
||||
Analyzed <b>{tsdbStatus.totalSeries}</b> series and <b>{tsdbStatus.totalLabelValuePairs}</b> label=value pairs
|
||||
at <b>{date}</b> {match && <span>for series selector <b>{match}</b></span>}. Show top {topN} entries per table.
|
||||
</Box>}
|
||||
{Object.keys(tsdbStatus).map((key ) => {
|
||||
if (key == "totalSeries" || key == "totalLabelValuePairs") return null;
|
||||
const tableTitle = tableTitles[key];
|
||||
const rows = tsdbStatus[key as keyof TSDBStatus] as unknown as Data[];
|
||||
rows.forEach((row) => {
|
||||
progressCount(tsdbStatus.totalSeries, key, row);
|
||||
row.actions = "0";
|
||||
});
|
||||
const headerCells = (key == "seriesCountByMetricName" || key == "seriesCountByLabelValuePair") ? headCellsWithProgress : defaultHeadCells;
|
||||
return (
|
||||
<>
|
||||
<Grid container spacing={2} sx={{px: 2}}>
|
||||
<Grid item xs={12} md={12} lg={12} key={key}>
|
||||
<Typography gutterBottom variant="h5" component="h5">
|
||||
{tableTitle}
|
||||
</Typography>
|
||||
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
|
||||
<Tabs
|
||||
value={stateTabs[key as keyof DefaultState]}
|
||||
onChange={handleTabChange} aria-label="basic tabs example">
|
||||
{defaultProps.tabs[key as keyof TabsType].map((title: string, i: number) =>
|
||||
<Tab
|
||||
key={title}
|
||||
label={title}
|
||||
aria-controls={`tabpanel-${i}`}
|
||||
id={key}
|
||||
iconPosition={"start"}
|
||||
icon={ i === 0 ? <TableChartIcon /> : <ShowChartIcon /> } />
|
||||
)}
|
||||
</Tabs>
|
||||
</Box>
|
||||
{defaultProps.tabs[key as keyof TabsType].map((_,idx) =>
|
||||
<div
|
||||
ref={defaultProps.containerRefs[key as keyof Containers<HTMLDivElement>]}
|
||||
style={{width: "100%", paddingRight: idx !== 0 ? "40px" : 0 }} key={`${key}-${idx}`}>
|
||||
<TabPanel value={stateTabs[key as keyof DefaultState]} index={idx}>
|
||||
{stateTabs[key as keyof DefaultState] === 0 ? <EnhancedTable
|
||||
rows={rows}
|
||||
headerCells={headerCells}
|
||||
defaultSortColumn={"value"}
|
||||
tableCells={(row) => tableCells(row,date,handleFilterClick(key))}
|
||||
/>: <BarChart
|
||||
data={[
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
rows.map((v) => v.name),
|
||||
rows.map((v) => v.value),
|
||||
rows.map((_, i) => i % 12 == 0 ? 1 : i % 10 == 0 ? 2 : 0),
|
||||
]}
|
||||
container={defaultProps.containerRefs[key as keyof Containers<HTMLDivElement>]?.current}
|
||||
configs={barOptions}
|
||||
/>}
|
||||
</TabPanel>
|
||||
</div>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
<MetricsContent
|
||||
sectionTitle={"Metric names with the highest number of series"}
|
||||
activeTab={stateTabs.seriesCountByMetricName}
|
||||
rows={tsdbStatus.seriesCountByMetricName as unknown as Data[]}
|
||||
onChange={handleTabChange}
|
||||
onActionClick={handleFilterClick("seriesCountByMetricName")}
|
||||
tabs={defaultProps.tabs.seriesCountByMetricName}
|
||||
chartContainer={defaultProps.containerRefs.seriesCountByMetricName}
|
||||
totalSeries={tsdbStatus.totalSeries}
|
||||
tabId={"seriesCountByMetricName"}
|
||||
tableHeaderCells={METRIC_NAMES_HEADERS}
|
||||
/>
|
||||
<MetricsContent
|
||||
sectionTitle={"Labels with the highest number of series"}
|
||||
activeTab={stateTabs.seriesCountByLabelName}
|
||||
rows={tsdbStatus.seriesCountByLabelName as unknown as Data[]}
|
||||
onChange={handleTabChange}
|
||||
onActionClick={handleFilterClick("seriesCountByLabelName")}
|
||||
tabs={defaultProps.tabs.seriesCountByLabelName}
|
||||
chartContainer={defaultProps.containerRefs.seriesCountByLabelName}
|
||||
totalSeries={tsdbStatus.totalSeries}
|
||||
tabId={"seriesCountByLabelName"}
|
||||
tableHeaderCells={LABEL_NAMES_HEADERS}
|
||||
/>
|
||||
<MetricsContent
|
||||
sectionTitle={"Label=value pairs with the highest number of series"}
|
||||
activeTab={stateTabs.seriesCountByLabelValuePair}
|
||||
rows={tsdbStatus.seriesCountByLabelValuePair as unknown as Data[]}
|
||||
onChange={handleTabChange}
|
||||
onActionClick={handleFilterClick("seriesCountByLabelValuePair")}
|
||||
tabs={defaultProps.tabs.seriesCountByLabelValuePair}
|
||||
chartContainer={defaultProps.containerRefs.seriesCountByLabelValuePair}
|
||||
totalSeries={tsdbStatus.totalSeries}
|
||||
tabId={"seriesCountByLabelValuePair"}
|
||||
tableHeaderCells={LABEL_VALUE_PAIRS_HEADERS}
|
||||
/>
|
||||
<MetricsContent
|
||||
sectionTitle={"Labels with the highest number of unique values"}
|
||||
activeTab={stateTabs.labelValueCountByLabelName}
|
||||
rows={tsdbStatus.labelValueCountByLabelName as unknown as Data[]}
|
||||
onChange={handleTabChange}
|
||||
onActionClick={handleFilterClick("labelValueCountByLabelName")}
|
||||
tabs={defaultProps.tabs.labelValueCountByLabelName}
|
||||
chartContainer={defaultProps.containerRefs.labelValueCountByLabelName}
|
||||
totalSeries={-1}
|
||||
tabId={"labelValueCountByLabelName"}
|
||||
tableHeaderCells={LABELS_WITH_UNIQUE_VALUES_HEADERS}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
import {FC} from "react";
|
||||
import {Box, Grid, Tab, Tabs, Typography} from "@mui/material";
|
||||
import TableChartIcon from "@mui/icons-material/TableChart";
|
||||
import ShowChartIcon from "@mui/icons-material/ShowChart";
|
||||
import TabPanel from "../../TabPanel/TabPanel";
|
||||
import EnhancedTable from "../../Table/Table";
|
||||
import TableCells from "../TableCells/TableCells";
|
||||
import BarChart from "../../BarChart/BarChart";
|
||||
import {barOptions} from "../../BarChart/consts";
|
||||
import React, {SyntheticEvent} from "react";
|
||||
import {Data, HeadCell} from "../../Table/types";
|
||||
import {MutableRef} from "preact/hooks";
|
||||
|
||||
interface MetricsProperties {
|
||||
rows: Data[];
|
||||
activeTab: number;
|
||||
onChange: (e: SyntheticEvent, newValue: number) => void;
|
||||
onActionClick: (e: SyntheticEvent) => void;
|
||||
tabs: string[];
|
||||
chartContainer: MutableRef<HTMLDivElement> | undefined;
|
||||
totalSeries: number,
|
||||
tabId: string;
|
||||
sectionTitle: string;
|
||||
tableHeaderCells: HeadCell[];
|
||||
}
|
||||
|
||||
const MetricsContent: FC<MetricsProperties> = ({
|
||||
rows,
|
||||
activeTab,
|
||||
onChange,
|
||||
tabs,
|
||||
chartContainer,
|
||||
totalSeries,
|
||||
tabId,
|
||||
onActionClick,
|
||||
sectionTitle,
|
||||
tableHeaderCells
|
||||
}) => {
|
||||
const tableCells = (row: Data) => (
|
||||
<TableCells
|
||||
row={row}
|
||||
totalSeries={totalSeries}
|
||||
onActionClick={onActionClick}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<Grid container spacing={2} sx={{px: 2}}>
|
||||
<Grid item xs={12} md={12} lg={12}>
|
||||
<Typography gutterBottom variant="h5" component="h5">{sectionTitle}</Typography>
|
||||
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onChange={onChange} aria-label="basic tabs example">
|
||||
{tabs.map((title: string, i: number) =>
|
||||
<Tab
|
||||
key={title}
|
||||
label={title}
|
||||
aria-controls={`tabpanel-${i}`}
|
||||
id={tabId}
|
||||
iconPosition={"start"}
|
||||
icon={ i === 0 ? <TableChartIcon /> : <ShowChartIcon /> } />
|
||||
)}
|
||||
</Tabs>
|
||||
</Box>
|
||||
{tabs.map((_,idx) =>
|
||||
<div
|
||||
ref={chartContainer}
|
||||
style={{width: "100%", paddingRight: idx !== 0 ? "40px" : 0 }} key={`chart-${idx}`}>
|
||||
<TabPanel value={activeTab} index={idx}>
|
||||
{activeTab === 0 ? <EnhancedTable
|
||||
rows={rows}
|
||||
headerCells={tableHeaderCells}
|
||||
defaultSortColumn={"value"}
|
||||
tableCells={tableCells}
|
||||
/>: <BarChart
|
||||
data={[
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
rows.map((v) => v.name),
|
||||
rows.map((v) => v.value),
|
||||
rows.map((_, i) => i % 12 == 0 ? 1 : i % 10 == 0 ? 2 : 0),
|
||||
]}
|
||||
container={chartContainer?.current || null}
|
||||
configs={barOptions}
|
||||
/>}
|
||||
</TabPanel>
|
||||
</div>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MetricsContent;
|
|
@ -1,50 +1,39 @@
|
|||
import {SyntheticEvent} from "react";
|
||||
import React, {FC} from "preact/compat";
|
||||
import {TableCell, ButtonGroup} from "@mui/material";
|
||||
import {Data} from "../../Table/types";
|
||||
import {BorderLinearProgressWithLabel} from "../../BorderLineProgress/BorderLinearProgress";
|
||||
import React from "preact/compat";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import PlayCircleOutlineIcon from "@mui/icons-material/PlayCircleOutline";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import {SyntheticEvent} from "react";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
export const tableCells = (
|
||||
interface CardinalityTableCells {
|
||||
row: Data,
|
||||
date: string | null,
|
||||
onFilterClick: (e: SyntheticEvent) => void) => {
|
||||
const pathname = window.location.pathname;
|
||||
const withday = dayjs(date).add(1, "day").toDate();
|
||||
return Object.keys(row).map((key, idx) => {
|
||||
if (idx === 0) {
|
||||
return (<TableCell component="th" scope="row" key={key}>
|
||||
{row[key as keyof Data]}
|
||||
</TableCell>);
|
||||
}
|
||||
if (key === "progressValue") {
|
||||
return (
|
||||
<TableCell key={key}>
|
||||
<BorderLinearProgressWithLabel
|
||||
variant="determinate"
|
||||
value={row[key as keyof Data] as number}
|
||||
/>
|
||||
</TableCell>
|
||||
);
|
||||
}
|
||||
if (key === "actions") {
|
||||
const title = `Filter by ${row.name}`;
|
||||
return (<TableCell key={key}>
|
||||
<ButtonGroup variant="contained">
|
||||
<Tooltip title={title}>
|
||||
<IconButton
|
||||
id={row.name}
|
||||
onClick={onFilterClick}
|
||||
sx={{height: "20px", width: "20px"}}>
|
||||
<PlayCircleOutlineIcon/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</ButtonGroup>
|
||||
</TableCell>);
|
||||
}
|
||||
return (<TableCell key={key}>{row[key as keyof Data]}</TableCell>);
|
||||
});
|
||||
totalSeries: number;
|
||||
onActionClick: (e: SyntheticEvent) => void;
|
||||
}
|
||||
|
||||
const TableCells: FC<CardinalityTableCells> = ({ row, totalSeries, onActionClick }) => {
|
||||
const progress = totalSeries > 0 ? row.value / totalSeries * 100 : -1;
|
||||
return <>
|
||||
<TableCell key={row.name}>{row.name}</TableCell>
|
||||
<TableCell key={row.value}>{row.value}</TableCell>
|
||||
{progress > 0 ? <TableCell key={row.progressValue}>
|
||||
<BorderLinearProgressWithLabel variant="determinate" value={progress} />
|
||||
</TableCell> : null}
|
||||
<TableCell key={"action"}>
|
||||
<ButtonGroup variant="contained">
|
||||
<Tooltip title={`Filter by ${row.name}`}>
|
||||
<IconButton
|
||||
id={row.name}
|
||||
onClick={onActionClick}
|
||||
sx={{height: "20px", width: "20px"}}>
|
||||
<PlayCircleOutlineIcon/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</ButtonGroup>
|
||||
</TableCell>
|
||||
</>;
|
||||
};
|
||||
|
||||
export default TableCells;
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import {HeadCell} from "../Table/types";
|
||||
|
||||
export const headCellsWithProgress = [
|
||||
export const METRIC_NAMES_HEADERS = [
|
||||
{
|
||||
disablePadding: false,
|
||||
id: "name",
|
||||
label: "Name",
|
||||
label: "Metric name",
|
||||
numeric: false,
|
||||
},
|
||||
{
|
||||
disablePadding: false,
|
||||
id: "value",
|
||||
label: "Value",
|
||||
label: "Number of series",
|
||||
numeric: false,
|
||||
},
|
||||
{
|
||||
|
@ -27,7 +27,80 @@ export const headCellsWithProgress = [
|
|||
}
|
||||
] as HeadCell[];
|
||||
|
||||
export const defaultHeadCells = headCellsWithProgress.filter((head) => head.id!=="percentage");
|
||||
export const LABEL_NAMES_HEADERS = [
|
||||
{
|
||||
disablePadding: false,
|
||||
id: "name",
|
||||
label: "Label name",
|
||||
numeric: false,
|
||||
},
|
||||
{
|
||||
disablePadding: false,
|
||||
id: "value",
|
||||
label: "Number of series",
|
||||
numeric: false,
|
||||
},
|
||||
{
|
||||
disablePadding: false,
|
||||
id: "percentage",
|
||||
label: "Percent of series",
|
||||
numeric: false,
|
||||
},
|
||||
{
|
||||
disablePadding: false,
|
||||
id: "action",
|
||||
label: "Action",
|
||||
numeric: false,
|
||||
}
|
||||
] as HeadCell[];
|
||||
|
||||
export const LABEL_VALUE_PAIRS_HEADERS = [
|
||||
{
|
||||
disablePadding: false,
|
||||
id: "name",
|
||||
label: "Label=value pair",
|
||||
numeric: false,
|
||||
},
|
||||
{
|
||||
disablePadding: false,
|
||||
id: "value",
|
||||
label: "Number of series",
|
||||
numeric: false,
|
||||
},
|
||||
{
|
||||
disablePadding: false,
|
||||
id: "percentage",
|
||||
label: "Percent of series",
|
||||
numeric: false,
|
||||
},
|
||||
{
|
||||
disablePadding: false,
|
||||
id: "action",
|
||||
label: "Action",
|
||||
numeric: false,
|
||||
}
|
||||
]as HeadCell[];
|
||||
|
||||
export const LABELS_WITH_UNIQUE_VALUES_HEADERS = [
|
||||
{
|
||||
disablePadding: false,
|
||||
id: "name",
|
||||
label: "Label name",
|
||||
numeric: false,
|
||||
},
|
||||
{
|
||||
disablePadding: false,
|
||||
id: "value",
|
||||
label: "Number of unique values",
|
||||
numeric: false,
|
||||
},
|
||||
{
|
||||
disablePadding: false,
|
||||
id: "action",
|
||||
label: "Action",
|
||||
numeric: false,
|
||||
}
|
||||
] as HeadCell[];
|
||||
|
||||
export const spinnerContainerStyles = (height: string) => {
|
||||
return {
|
||||
|
@ -40,5 +113,3 @@ export const spinnerContainerStyles = (height: string) => {
|
|||
zIndex: 1000,
|
||||
};
|
||||
};
|
||||
|
||||
export const SPINNER_TITLE = "Please wait while cardinality stats is calculated. This may take some time if the db contains big number of time series";
|
||||
|
|
|
@ -1,38 +1,24 @@
|
|||
import {Containers, DefaultState, QueryUpdater, Tabs, TSDBStatus, TypographyFunctions} from "./types";
|
||||
import {Data} from "../Table/types";
|
||||
import {Containers, DefaultState, QueryUpdater, Tabs, TSDBStatus} from "./types";
|
||||
import {useRef} from "preact/compat";
|
||||
|
||||
export const tableTitles: {[key: string]: string} = {
|
||||
"seriesCountByMetricName": "Metric names with the highest number of series",
|
||||
"seriesCountByLabelValuePair": "Label=value pairs with the highest number of series",
|
||||
"labelValueCountByLabelName": "Labels with the highest number of unique values",
|
||||
};
|
||||
|
||||
export const queryUpdater: QueryUpdater = {
|
||||
labelValueCountByLabelName: (query: string): string => `{${query}!=""}`,
|
||||
seriesCountByMetricName: (query: string): string => {
|
||||
return getSeriesSelector("__name__", query);
|
||||
},
|
||||
seriesCountByLabelName: (query: string): string => `{${query}!=""}`,
|
||||
seriesCountByLabelValuePair: (query: string): string => {
|
||||
const a = query.split("=");
|
||||
const label = a[0];
|
||||
const value = a.slice(1).join("=");
|
||||
return getSeriesSelector(label, value);
|
||||
},
|
||||
seriesCountByMetricName: (query: string): string => {
|
||||
return getSeriesSelector("__name__", query);
|
||||
},
|
||||
labelValueCountByLabelName: (query: string): string => `{${query}!=""}`,
|
||||
};
|
||||
|
||||
const getSeriesSelector = (label: string, value: string): string => {
|
||||
return "{" + label + "=" + JSON.stringify(value) + "}";
|
||||
};
|
||||
|
||||
export const progressCount = (totalSeries: number, key: string, row: Data): Data => {
|
||||
if (key === "seriesCountByMetricName" || key === "seriesCountByLabelValuePair") {
|
||||
row.progressValue = row.value / totalSeries * 100;
|
||||
return row;
|
||||
}
|
||||
return row;
|
||||
};
|
||||
|
||||
export const defaultProperties = (tsdbStatus: TSDBStatus) => {
|
||||
return Object.keys(tsdbStatus).reduce((acc, key) => {
|
||||
if (key === "totalSeries" || key === "totalLabelValuePairs") return acc;
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import {MutableRef} from "preact/hooks";
|
||||
|
||||
export interface TSDBStatus {
|
||||
labelValueCountByLabelName: TopHeapEntry[];
|
||||
seriesCountByLabelValuePair: TopHeapEntry[];
|
||||
seriesCountByMetricName: TopHeapEntry[];
|
||||
totalSeries: number;
|
||||
totalLabelValuePairs: number;
|
||||
seriesCountByMetricName: TopHeapEntry[];
|
||||
seriesCountByLabelName: TopHeapEntry[];
|
||||
seriesCountByLabelValuePair: TopHeapEntry[];
|
||||
labelValueCountByLabelName: TopHeapEntry[];
|
||||
}
|
||||
|
||||
export interface TopHeapEntry {
|
||||
|
@ -13,28 +14,27 @@ export interface TopHeapEntry {
|
|||
count: number;
|
||||
}
|
||||
|
||||
export type TypographyFunctions = {
|
||||
[key: string]: (value: number) => string,
|
||||
}
|
||||
|
||||
export type QueryUpdater = {
|
||||
[key: string]: (query: string) => string,
|
||||
}
|
||||
|
||||
export interface Tabs {
|
||||
labelValueCountByLabelName: string[];
|
||||
seriesCountByLabelValuePair: string[];
|
||||
seriesCountByMetricName: string[];
|
||||
seriesCountByLabelName: string[];
|
||||
seriesCountByLabelValuePair: string[];
|
||||
labelValueCountByLabelName: string[];
|
||||
}
|
||||
|
||||
export interface Containers<T> {
|
||||
labelValueCountByLabelName: MutableRef<T>;
|
||||
seriesCountByLabelValuePair: MutableRef<T>;
|
||||
seriesCountByMetricName: MutableRef<T>;
|
||||
seriesCountByLabelName: MutableRef<T>;
|
||||
seriesCountByLabelValuePair: MutableRef<T>;
|
||||
labelValueCountByLabelName: MutableRef<T>;
|
||||
}
|
||||
|
||||
export interface DefaultState {
|
||||
labelValueCountByLabelName: number;
|
||||
seriesCountByLabelValuePair: number;
|
||||
seriesCountByMetricName: number;
|
||||
seriesCountByLabelName: number;
|
||||
seriesCountByLabelValuePair: number;
|
||||
labelValueCountByLabelName: number;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {Box, Paper, Table, TableBody, TableCell, TableContainer, TablePagination, TableRow,} from "@mui/material";
|
||||
import React, {FC, useState} from "preact/compat";
|
||||
import {ChangeEvent, MouseEvent, SyntheticEvent} from "react";
|
||||
import {ChangeEvent, MouseEvent} from "react";
|
||||
import {Data, Order, TableProps,} from "./types";
|
||||
import {EnhancedTableHead} from "./TableHead";
|
||||
import {getComparator, stableSort} from "./helpers";
|
||||
|
@ -37,7 +37,7 @@ const EnhancedTable: FC<TableProps> = ({
|
|||
setSelected([]);
|
||||
};
|
||||
|
||||
const handleClick = (event: SyntheticEvent, name: string) => {
|
||||
const handleClick = (name: string) => () => {
|
||||
const selectedIndex = selected.indexOf(name);
|
||||
let newSelected: readonly string[] = [];
|
||||
|
||||
|
@ -101,7 +101,7 @@ const EnhancedTable: FC<TableProps> = ({
|
|||
return (
|
||||
<TableRow
|
||||
hover
|
||||
onClick={(event) => handleClick(event, row.name)}
|
||||
onClick={handleClick(row.name)}
|
||||
role="checkbox"
|
||||
aria-checked={isItemSelected}
|
||||
tabIndex={-1}
|
||||
|
|
|
@ -10,7 +10,7 @@ export function descendingComparator<T>(a: T, b: T, orderBy: keyof T) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
export function getComparator<Key extends keyof any>(
|
||||
export function getComparator<Key extends (string | number | symbol)>(
|
||||
order: Order,
|
||||
orderBy: Key,
|
||||
): (
|
||||
|
|
|
@ -23,7 +23,7 @@ export interface TableProps {
|
|||
rows: Data[];
|
||||
headerCells: HeadCell[],
|
||||
defaultSortColumn: keyof Data,
|
||||
tableCells: (row: Data) => ReactNode[],
|
||||
tableCells: (row: Data) => ReactNode,
|
||||
isPagingEnabled?: boolean,
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ const defaultTSDBStatus = {
|
|||
totalSeries: 0,
|
||||
totalLabelValuePairs: 0,
|
||||
seriesCountByMetricName: [],
|
||||
seriesCountByLabelName: [],
|
||||
seriesCountByLabelValuePair: [],
|
||||
labelValueCountByLabelName: [],
|
||||
};
|
||||
|
|
|
@ -34,6 +34,7 @@ The following tip changes can be tested by building VictoriaMetrics components f
|
|||
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): expose `/api/v1/status/config` endpoint in the same way as Prometheus does. See [these docs](https://prometheus.io/docs/prometheus/latest/querying/api/#config).
|
||||
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): add `-promscrape.suppressScrapeErrorsDelay` command-line flag, which can be used for delaying and aggregating the logging of per-target scrape errors. This may reduce the amounts of logs when `vmagent` scrapes many unreliable targets. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2575). Thanks to @jelmd for [the initial implementation](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/2576).
|
||||
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): add `-promscrape.cluster.name` command-line flag, which allows proper data de-duplication when the same target is scraped from multiple [vmagent clusters](https://docs.victoriametrics.com/vmagent.html#scraping-big-number-of-targets). See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2679).
|
||||
* FEATURE: [VictoriaMetrics enterprise](https://victoriametrics.com/products/enterprise/): expose `vm_downsampling_partitions_scheduled` and `vm_downsampling_partitions_scheduled_size_bytes` metrics, which can be used for tracking the progress of initial [downsampling](https://docs.victoriametrics.com/#downsampling) for historical data. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2612).
|
||||
|
||||
* BUGFIX: support for data ingestion in [DataDog format](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#how-to-send-data-from-datadog-agent) from legacy clients / agents. See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/2670). Thanks to @elProxy for the fix.
|
||||
* BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): do not expose `vm_promscrape_service_discovery_duration_seconds_bucket` metric for unused service discovery types. This reduces the number of metrics exported at `http://vmagent:8429/metrics`. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2671).
|
||||
|
@ -42,6 +43,7 @@ The following tip changes can be tested by building VictoriaMetrics components f
|
|||
* BUGFIX: deny [background merge](https://valyala.medium.com/how-victoriametrics-makes-instant-snapshots-for-multi-terabyte-time-series-data-e1f3fb0e0282) when the storage enters read-only mode, e.g. when free disk space becomes lower than `-storage.minFreeDiskSpaceBytes`. Background merge needs additional disk space, so it could result in `no space left on device` errors. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2603).
|
||||
* BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui): properly apply the selected time range when auto-refresh is enabled. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2693).
|
||||
* BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui): properly update the url with vmui state when new query is entered. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2692).
|
||||
* BUGFIX: [Graphite render API](https://docs.victoriametrics.com/#graphite-render-api-usage): properly calculate sample timestamps when `moving*()` functions such as [movingAverage()](https://graphite.readthedocs.io/en/stable/functions.html#graphite.render.functions.movingAverage) are applied over [summarize()](https://graphite.readthedocs.io/en/stable/functions.html#graphite.render.functions.summarize).
|
||||
|
||||
## [v1.77.2](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.77.2)
|
||||
|
||||
|
|
|
@ -268,6 +268,7 @@ 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 label=name pairs with the highest number of series.
|
||||
- To identify labels with the highest number of unique values.
|
||||
|
||||
|
@ -275,8 +276,6 @@ By default cardinality explorer analyzes time series for the current date. It pr
|
|||
By default all the time series for the selected date are analyzed. It is possible to narrow down the analysis to series
|
||||
matching the specified [series selector](https://prometheus.io/docs/prometheus/latest/querying/basics/#time-series-selectors).
|
||||
|
||||
Cardinality explorer takes into account [deleted time series](#how-to-delete-time-series), because they stay in the inverted index for up to [-retentionPeriod](#retention). This means that the deleted time series take RAM, CPU, disk IO and disk space for the inverted index in the same way as other time series.
|
||||
|
||||
Cardinality explorer is built on top of [/api/v1/status/tsdb](#tsdb-stats).
|
||||
|
||||
See [cardinality explorer playground](https://play.victoriametrics.com/select/accounting/1/6a716b0f-38bc-4856-90ce-448fd713e3fe/prometheus/graph/#/cardinality).
|
||||
|
|
|
@ -272,6 +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 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.
|
||||
|
||||
|
@ -279,8 +281,6 @@ By default cardinality explorer analyzes time series for the current date. It pr
|
|||
By default all the time series for the selected date are analyzed. It is possible to narrow down the analysis to series
|
||||
matching the specified [series selector](https://prometheus.io/docs/prometheus/latest/querying/basics/#time-series-selectors).
|
||||
|
||||
Cardinality explorer takes into account [deleted time series](#how-to-delete-time-series), because they stay in the inverted index for up to [-retentionPeriod](#retention). This means that the deleted time series take RAM, CPU, disk IO and disk space for the inverted index in the same way as other time series.
|
||||
|
||||
Cardinality explorer is built on top of [/api/v1/status/tsdb](#tsdb-stats).
|
||||
|
||||
See [cardinality explorer playground](https://play.victoriametrics.com/select/accounting/1/6a716b0f-38bc-4856-90ce-448fd713e3fe/prometheus/graph/#/cardinality).
|
||||
|
@ -1446,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.
|
||||
|
||||
|
|
|
@ -834,10 +834,7 @@ func (is *indexSearch) searchLabelNamesWithFiltersOnDate(qt *querytracer.Tracer,
|
|||
if err := mp.Init(item, nsPrefixExpected); err != nil {
|
||||
return err
|
||||
}
|
||||
if mp.IsDeletedTag(dmis) {
|
||||
continue
|
||||
}
|
||||
if mp.GetMatchingSeriesCount(filter) == 0 {
|
||||
if mp.GetMatchingSeriesCount(filter, dmis) == 0 {
|
||||
continue
|
||||
}
|
||||
labelName := mp.Tag.Key
|
||||
|
@ -1000,10 +997,7 @@ func (is *indexSearch) searchLabelValuesWithFiltersOnDate(qt *querytracer.Tracer
|
|||
if err := mp.Init(item, nsPrefixExpected); err != nil {
|
||||
return err
|
||||
}
|
||||
if mp.IsDeletedTag(dmis) {
|
||||
continue
|
||||
}
|
||||
if mp.GetMatchingSeriesCount(filter) == 0 {
|
||||
if mp.GetMatchingSeriesCount(filter, dmis) == 0 {
|
||||
continue
|
||||
}
|
||||
labelValue := mp.Tag.Value
|
||||
|
@ -1150,7 +1144,7 @@ func (is *indexSearch) searchTagValueSuffixesForPrefix(tvss map[string]struct{},
|
|||
if err := mp.Init(item, nsPrefix); err != nil {
|
||||
return err
|
||||
}
|
||||
if mp.IsDeletedTag(dmis) {
|
||||
if mp.GetMatchingSeriesCount(nil, dmis) == 0 {
|
||||
continue
|
||||
}
|
||||
tagValue := mp.Tag.Value
|
||||
|
@ -1245,11 +1239,11 @@ func (is *indexSearch) getSeriesCount() (uint64, error) {
|
|||
return metricIDsLen, nil
|
||||
}
|
||||
|
||||
// GetTSDBStatusWithFiltersForDate returns topN entries for tsdb status for the given tfss and the given date.
|
||||
func (db *indexDB) GetTSDBStatusWithFiltersForDate(qt *querytracer.Tracer, 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, 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(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 {
|
||||
|
@ -1261,7 +1255,7 @@ func (db *indexDB) GetTSDBStatusWithFiltersForDate(qt *querytracer.Tracer, tfss
|
|||
ok := db.doExtDB(func(extDB *indexDB) {
|
||||
qtChild := qt.NewChild("collect tsdb stats in the previous indexdb")
|
||||
is := extDB.getIndexSearch(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)
|
||||
})
|
||||
|
@ -1271,8 +1265,8 @@ func (db *indexDB) GetTSDBStatusWithFiltersForDate(qt *querytracer.Tracer, tfss
|
|||
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
|
||||
|
@ -1284,13 +1278,17 @@ func (is *indexSearch) getTSDBStatusWithFiltersForDate(qt *querytracer.Tracer, t
|
|||
ts := &is.ts
|
||||
kb := &is.kb
|
||||
mp := &is.mp
|
||||
thLabelValueCountByLabelName := newTopHeap(topN)
|
||||
thSeriesCountByLabelValuePair := newTopHeap(topN)
|
||||
dmis := is.db.s.getDeletedMetricIDs()
|
||||
thSeriesCountByMetricName := newTopHeap(topN)
|
||||
var tmp, labelName, labelNameValue []byte
|
||||
thSeriesCountByLabelName := newTopHeap(topN)
|
||||
thSeriesCountByFocusLabelValue := newTopHeap(topN)
|
||||
thSeriesCountByLabelValuePair := newTopHeap(topN)
|
||||
thLabelValueCountByLabelName := newTopHeap(topN)
|
||||
var tmp, prevLabelName, prevLabelValuePair []byte
|
||||
var labelValueCountByLabelName, seriesCountByLabelValuePair uint64
|
||||
var totalSeries, totalLabelValuePairs uint64
|
||||
var totalSeries, labelSeries, totalLabelValuePairs uint64
|
||||
nameEqualBytes := []byte("__name__=")
|
||||
focusLabelEqualBytes := []byte(focusLabel + "=")
|
||||
|
||||
loopsPaceLimiter := 0
|
||||
nsPrefixExpected := byte(nsPrefixDateTagToMetricIDs)
|
||||
|
@ -1314,69 +1312,87 @@ func (is *indexSearch) getTSDBStatusWithFiltersForDate(qt *querytracer.Tracer, t
|
|||
if err := mp.Init(item, nsPrefixExpected); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
matchingSeriesCount := mp.GetMatchingSeriesCount(filter)
|
||||
matchingSeriesCount := mp.GetMatchingSeriesCount(filter, dmis)
|
||||
if matchingSeriesCount == 0 {
|
||||
// Skip rows without matching metricIDs.
|
||||
continue
|
||||
}
|
||||
tmp = append(tmp[:0], mp.Tag.Key...)
|
||||
tagKey := tmp
|
||||
if isArtificialTagKey(tagKey) {
|
||||
labelName := tmp
|
||||
if isArtificialTagKey(labelName) {
|
||||
// Skip artificially created tag keys.
|
||||
kb.B = append(kb.B[:0], prefix...)
|
||||
if len(tagKey) > 0 && tagKey[0] == compositeTagKeyPrefix {
|
||||
if len(labelName) > 0 && labelName[0] == compositeTagKeyPrefix {
|
||||
kb.B = append(kb.B, compositeTagKeyPrefix)
|
||||
} else {
|
||||
kb.B = marshalTagValue(kb.B, tagKey)
|
||||
kb.B = marshalTagValue(kb.B, labelName)
|
||||
}
|
||||
kb.B[len(kb.B)-1]++
|
||||
ts.Seek(kb.B)
|
||||
continue
|
||||
}
|
||||
if len(tagKey) == 0 {
|
||||
tagKey = append(tagKey, "__name__"...)
|
||||
tmp = tagKey
|
||||
if len(labelName) == 0 {
|
||||
labelName = append(labelName, "__name__"...)
|
||||
tmp = labelName
|
||||
}
|
||||
if string(labelName) == "__name__" {
|
||||
totalSeries += uint64(matchingSeriesCount)
|
||||
}
|
||||
tmp = append(tmp, '=')
|
||||
tmp = append(tmp, mp.Tag.Value...)
|
||||
tagKeyValue := tmp
|
||||
if string(tagKey) == "__name__" {
|
||||
totalSeries += uint64(matchingSeriesCount)
|
||||
labelValuePair := tmp
|
||||
if len(prevLabelName) == 0 {
|
||||
prevLabelName = append(prevLabelName[:0], labelName...)
|
||||
}
|
||||
if !bytes.Equal(tagKey, labelName) {
|
||||
thLabelValueCountByLabelName.pushIfNonEmpty(labelName, labelValueCountByLabelName)
|
||||
if string(labelName) != string(prevLabelName) {
|
||||
thLabelValueCountByLabelName.push(prevLabelName, labelValueCountByLabelName)
|
||||
thSeriesCountByLabelName.push(prevLabelName, labelSeries)
|
||||
labelSeries = 0
|
||||
labelValueCountByLabelName = 0
|
||||
labelName = append(labelName[:0], tagKey...)
|
||||
prevLabelName = append(prevLabelName[:0], labelName...)
|
||||
}
|
||||
if !bytes.Equal(tagKeyValue, labelNameValue) {
|
||||
thSeriesCountByLabelValuePair.pushIfNonEmpty(labelNameValue, seriesCountByLabelValuePair)
|
||||
if bytes.HasPrefix(labelNameValue, nameEqualBytes) {
|
||||
thSeriesCountByMetricName.pushIfNonEmpty(labelNameValue[len(nameEqualBytes):], seriesCountByLabelValuePair)
|
||||
if len(prevLabelValuePair) == 0 {
|
||||
prevLabelValuePair = append(prevLabelValuePair[:0], labelValuePair...)
|
||||
labelValueCountByLabelName++
|
||||
}
|
||||
if string(labelValuePair) != string(prevLabelValuePair) {
|
||||
thSeriesCountByLabelValuePair.push(prevLabelValuePair, seriesCountByLabelValuePair)
|
||||
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++
|
||||
labelNameValue = append(labelNameValue[:0], tagKeyValue...)
|
||||
prevLabelValuePair = append(prevLabelValuePair[:0], labelValuePair...)
|
||||
}
|
||||
// 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.
|
||||
labelSeries += uint64(matchingSeriesCount)
|
||||
seriesCountByLabelValuePair += uint64(matchingSeriesCount)
|
||||
totalLabelValuePairs += uint64(matchingSeriesCount)
|
||||
}
|
||||
if err := ts.Error(); err != nil {
|
||||
return nil, fmt.Errorf("error when counting time series by metric names: %w", err)
|
||||
}
|
||||
thLabelValueCountByLabelName.pushIfNonEmpty(labelName, labelValueCountByLabelName)
|
||||
thSeriesCountByLabelValuePair.pushIfNonEmpty(labelNameValue, seriesCountByLabelValuePair)
|
||||
if bytes.HasPrefix(labelNameValue, nameEqualBytes) {
|
||||
thSeriesCountByMetricName.pushIfNonEmpty(labelNameValue[len(nameEqualBytes):], seriesCountByLabelValuePair)
|
||||
thLabelValueCountByLabelName.push(prevLabelName, labelValueCountByLabelName)
|
||||
thSeriesCountByLabelName.push(prevLabelName, labelSeries)
|
||||
thSeriesCountByLabelValuePair.push(prevLabelValuePair, seriesCountByLabelValuePair)
|
||||
if bytes.HasPrefix(prevLabelValuePair, nameEqualBytes) {
|
||||
thSeriesCountByMetricName.push(prevLabelValuePair[len(nameEqualBytes):], seriesCountByLabelValuePair)
|
||||
}
|
||||
if bytes.HasPrefix(prevLabelValuePair, focusLabelEqualBytes) {
|
||||
thSeriesCountByFocusLabelValue.push(prevLabelValuePair[len(focusLabelEqualBytes):], seriesCountByLabelValuePair)
|
||||
}
|
||||
status := &TSDBStatus{
|
||||
SeriesCountByMetricName: thSeriesCountByMetricName.getSortedResult(),
|
||||
LabelValueCountByLabelName: thLabelValueCountByLabelName.getSortedResult(),
|
||||
SeriesCountByLabelValuePair: thSeriesCountByLabelValuePair.getSortedResult(),
|
||||
TotalSeries: totalSeries,
|
||||
TotalLabelValuePairs: totalLabelValuePairs,
|
||||
TotalSeries: totalSeries,
|
||||
TotalLabelValuePairs: totalLabelValuePairs,
|
||||
SeriesCountByMetricName: thSeriesCountByMetricName.getSortedResult(),
|
||||
SeriesCountByLabelName: thSeriesCountByLabelName.getSortedResult(),
|
||||
SeriesCountByFocusLabelValue: thSeriesCountByFocusLabelValue.getSortedResult(),
|
||||
SeriesCountByLabelValuePair: thSeriesCountByLabelValuePair.getSortedResult(),
|
||||
LabelValueCountByLabelName: thLabelValueCountByLabelName.getSortedResult(),
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
|
@ -1385,11 +1401,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
|
||||
LabelValueCountByLabelName []TopHeapEntry
|
||||
SeriesCountByLabelValuePair []TopHeapEntry
|
||||
TotalSeries uint64
|
||||
TotalLabelValuePairs uint64
|
||||
SeriesCountByMetricName []TopHeapEntry
|
||||
SeriesCountByLabelName []TopHeapEntry
|
||||
SeriesCountByFocusLabelValue []TopHeapEntry
|
||||
SeriesCountByLabelValuePair []TopHeapEntry
|
||||
LabelValueCountByLabelName []TopHeapEntry
|
||||
}
|
||||
|
||||
func (status *TSDBStatus) hasEntries() bool {
|
||||
|
@ -1415,7 +1433,7 @@ type TopHeapEntry struct {
|
|||
Count uint64
|
||||
}
|
||||
|
||||
func (th *topHeap) pushIfNonEmpty(name []byte, count uint64) {
|
||||
func (th *topHeap) push(name []byte, count uint64) {
|
||||
if count == 0 {
|
||||
return
|
||||
}
|
||||
|
@ -3108,39 +3126,27 @@ func (mp *tagToMetricIDsRowParser) ParseMetricIDs() {
|
|||
mp.metricIDsParsed = true
|
||||
}
|
||||
|
||||
// GetMatchingSeriesCount returns the number of series in mp, which match metricIDs from the given filter.
|
||||
// GetMatchingSeriesCount returns the number of series in mp, which match metricIDs from the given filter
|
||||
// and do not match metricIDs from negativeFilter.
|
||||
//
|
||||
// if filter is empty, then all series in mp are taken into account.
|
||||
func (mp *tagToMetricIDsRowParser) GetMatchingSeriesCount(filter *uint64set.Set) int {
|
||||
if filter == nil {
|
||||
func (mp *tagToMetricIDsRowParser) GetMatchingSeriesCount(filter, negativeFilter *uint64set.Set) int {
|
||||
if filter == nil && negativeFilter.Len() == 0 {
|
||||
return mp.MetricIDsLen()
|
||||
}
|
||||
mp.ParseMetricIDs()
|
||||
n := 0
|
||||
for _, metricID := range mp.MetricIDs {
|
||||
if filter.Has(metricID) {
|
||||
if filter != nil && !filter.Has(metricID) {
|
||||
continue
|
||||
}
|
||||
if !negativeFilter.Has(metricID) {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// IsDeletedTag verifies whether the tag from mp is deleted according to dmis.
|
||||
//
|
||||
// dmis must contain deleted MetricIDs.
|
||||
func (mp *tagToMetricIDsRowParser) IsDeletedTag(dmis *uint64set.Set) bool {
|
||||
if dmis.Len() == 0 {
|
||||
return false
|
||||
}
|
||||
mp.ParseMetricIDs()
|
||||
for _, metricID := range mp.MetricIDs {
|
||||
if !dmis.Has(metricID) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func mergeTagToMetricIDsRows(data []byte, items []mergeset.Item) ([]byte, []mergeset.Item) {
|
||||
data, items = mergeTagToMetricIDsRowsInternal(data, items, nsPrefixTagToMetricIDs)
|
||||
data, items = mergeTagToMetricIDsRowsInternal(data, items, nsPrefixDateTagToMetricIDs)
|
||||
|
|
|
@ -1807,10 +1807,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, nil, baseDate, 5, 1e6, noDeadline)
|
||||
// Check GetTSDBStatus with nil filters.
|
||||
status, err := db.GetTSDBStatus(nil, 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")
|
||||
|
@ -1824,6 +1824,36 @@ func TestSearchTSIDWithTimeRange(t *testing.T) {
|
|||
if !reflect.DeepEqual(status.SeriesCountByMetricName, expectedSeriesCountByMetricName) {
|
||||
t.Fatalf("unexpected SeriesCountByMetricName;\ngot\n%v\nwant\n%v", status.SeriesCountByMetricName, expectedSeriesCountByMetricName)
|
||||
}
|
||||
expectedSeriesCountByLabelName := []TopHeapEntry{
|
||||
{
|
||||
Name: "__name__",
|
||||
Count: 1000,
|
||||
},
|
||||
{
|
||||
Name: "constant",
|
||||
Count: 1000,
|
||||
},
|
||||
{
|
||||
Name: "day",
|
||||
Count: 1000,
|
||||
},
|
||||
{
|
||||
Name: "uniqueid",
|
||||
Count: 1000,
|
||||
},
|
||||
}
|
||||
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",
|
||||
|
@ -1879,14 +1909,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()
|
||||
if err := tfs.Add([]byte("day"), []byte("0"), false, false); err != nil {
|
||||
t.Fatalf("cannot add filter: %s", err)
|
||||
}
|
||||
status, err = db.GetTSDBStatusWithFiltersForDate(nil, []*TagFilters{tfs}, baseDate, 5, 1e6, noDeadline)
|
||||
status, err = db.GetTSDBStatus(nil, []*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")
|
||||
|
@ -1909,10 +1939,10 @@ func TestSearchTSIDWithTimeRange(t *testing.T) {
|
|||
t.Fatalf("unexpected TotalLabelValuePairs; got %d; want %d", status.TotalLabelValuePairs, expectedLabelValuePairs)
|
||||
}
|
||||
|
||||
// Check GetTSDBStatusWithFiltersOnDate, which matches all the series on a global time range
|
||||
status, err = db.GetTSDBStatusWithFiltersForDate(nil, nil, 0, 5, 1e6, noDeadline)
|
||||
// Check GetTSDBStatus, which matches all the series on a global time range
|
||||
status, err = db.GetTSDBStatus(nil, 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")
|
||||
|
@ -1934,15 +1964,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()
|
||||
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, []*TagFilters{tfs}, baseDate, 5, 1e6, noDeadline)
|
||||
status, err = db.GetTSDBStatus(nil, []*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")
|
||||
|
@ -1965,10 +2020,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, []*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, []*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")
|
||||
|
|
|
@ -1467,9 +1467,9 @@ func (s *Storage) GetSeriesCount(deadline uint64) (uint64, error) {
|
|||
return s.idb().GetSeriesCount(deadline)
|
||||
}
|
||||
|
||||
// GetTSDBStatusWithFiltersForDate returns TSDB status data for /api/v1/status/tsdb with match[] filters.
|
||||
func (s *Storage) GetTSDBStatusWithFiltersForDate(qt *querytracer.Tracer, tfss []*TagFilters, date uint64, topN, maxMetrics int, deadline uint64) (*TSDBStatus, error) {
|
||||
return s.idb().GetTSDBStatusWithFiltersForDate(qt, tfss, date, topN, maxMetrics, deadline)
|
||||
// GetTSDBStatus returns TSDB status data for /api/v1/status/tsdb
|
||||
func (s *Storage) GetTSDBStatus(qt *querytracer.Tracer, tfss []*TagFilters, date uint64, focusLabel string, topN, maxMetrics int, deadline uint64) (*TSDBStatus, error) {
|
||||
return s.idb().GetTSDBStatus(qt, tfss, date, focusLabel, topN, maxMetrics, deadline)
|
||||
}
|
||||
|
||||
// MetricRow is a metric to insert into storage.
|
||||
|
|
Loading…
Reference in a new issue