mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-05-17 08:36:55 +03:00
lib/storage: enhance TSDB status response
This commit adds new fields - `requestsCount` and `lastRequestTimestamp` to series count be metric names stats. It allows to display an additional stats at explore cardinality page. Stats will only be added if `storage.trackMetricNameStats` flag is set. This change requires an update to RPC protocol in order to properly marshal data. In addition, this commit adds integration tests to TSDB stats API. Related issue: https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6145
This commit is contained in:
@@ -11,7 +11,7 @@ TSDBStatusResponse generates response for /api/v1/status/tsdb .
|
||||
"data":{
|
||||
"totalSeries": {%dul= status.TotalSeries %},
|
||||
"totalLabelValuePairs": {%dul= status.TotalLabelValuePairs %},
|
||||
"seriesCountByMetricName":{%= tsdbStatusEntries(status.SeriesCountByMetricName) %},
|
||||
"seriesCountByMetricName":{%= tsdbStatusMetricNameEntries(status.SeriesCountByMetricName,status.SeriesQueryStatsByMetricName) %},
|
||||
"seriesCountByLabelName":{%= tsdbStatusEntries(status.SeriesCountByLabelName) %},
|
||||
"seriesCountByFocusLabelValue":{%= tsdbStatusEntries(status.SeriesCountByFocusLabelValue) %},
|
||||
"seriesCountByLabelValuePair":{%= tsdbStatusEntries(status.SeriesCountByLabelValuePair) %},
|
||||
@@ -34,4 +34,32 @@ TSDBStatusResponse generates response for /api/v1/status/tsdb .
|
||||
]
|
||||
{% endfunc %}
|
||||
|
||||
{% func tsdbStatusMetricNameEntries(a []storage.TopHeapEntry, queryStats []storage.MetricNamesStatsRecord) %}
|
||||
{% code
|
||||
queryStatsByMetricName := make(map[string]storage.MetricNamesStatsRecord,len(queryStats))
|
||||
for _, record := range queryStats{
|
||||
queryStatsByMetricName[record.MetricName] = record
|
||||
}
|
||||
%}
|
||||
[
|
||||
{% for i, e := range a %}
|
||||
{
|
||||
{% code
|
||||
entry, ok := queryStatsByMetricName[e.Name]
|
||||
%}
|
||||
"name":{%q= e.Name %},
|
||||
{% if !ok %}
|
||||
"value":{%d= int(e.Count) %}
|
||||
{% else %}
|
||||
"value":{%d= int(e.Count) %},
|
||||
"requestsCount":{%d= int(entry.RequestsCount) %},
|
||||
"lastRequestTimestamp":{%d= int(entry.LastRequestTs) %}
|
||||
{% endif %}
|
||||
}
|
||||
{% if i+1 < len(a) %},{% endif %}
|
||||
{% endfor %}
|
||||
]
|
||||
{% endfunc %}
|
||||
|
||||
|
||||
{% endstripspace %}
|
||||
|
||||
@@ -37,9 +37,9 @@ func StreamTSDBStatusResponse(qw422016 *qt422016.Writer, status *storage.TSDBSta
|
||||
qw422016.N().DUL(status.TotalLabelValuePairs)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:13
|
||||
qw422016.N().S(`,"seriesCountByMetricName":`)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:14
|
||||
streamtsdbStatusEntries(qw422016, status.SeriesCountByMetricName)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:14
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:15
|
||||
streamtsdbStatusMetricNameEntries(qw422016, status.SeriesCountByMetricName, status.SeriesQueryStatsByMetricName)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:15
|
||||
qw422016.N().S(`,"seriesCountByLabelName":`)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:15
|
||||
streamtsdbStatusEntries(qw422016, status.SeriesCountByLabelName)
|
||||
@@ -147,3 +147,89 @@ func tsdbStatusEntries(a []storage.TopHeapEntry) string {
|
||||
return qs422016
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:35
|
||||
}
|
||||
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:38
|
||||
func streamtsdbStatusMetricNameEntries(qw422016 *qt422016.Writer, a []storage.TopHeapEntry, queryStats []storage.MetricNamesStatsRecord) {
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:40
|
||||
queryStatsByMetricName := make(map[string]storage.MetricNamesStatsRecord, len(queryStats))
|
||||
for _, record := range queryStats {
|
||||
queryStatsByMetricName[record.MetricName] = record
|
||||
}
|
||||
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:44
|
||||
qw422016.N().S(`[`)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:46
|
||||
for i, e := range a {
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:46
|
||||
qw422016.N().S(`{`)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:49
|
||||
entry, ok := queryStatsByMetricName[e.Name]
|
||||
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:50
|
||||
qw422016.N().S(`"name":`)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:51
|
||||
qw422016.N().Q(e.Name)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:51
|
||||
qw422016.N().S(`,`)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:52
|
||||
if !ok {
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:52
|
||||
qw422016.N().S(`"value":`)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:53
|
||||
qw422016.N().D(int(e.Count))
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:54
|
||||
} else {
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:54
|
||||
qw422016.N().S(`"value":`)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:55
|
||||
qw422016.N().D(int(e.Count))
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:55
|
||||
qw422016.N().S(`,"requestsCount":`)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:56
|
||||
qw422016.N().D(int(entry.RequestsCount))
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:56
|
||||
qw422016.N().S(`,"lastRequestTimestamp":`)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:57
|
||||
qw422016.N().D(int(entry.LastRequestTs))
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:58
|
||||
}
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:58
|
||||
qw422016.N().S(`}`)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:60
|
||||
if i+1 < len(a) {
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:60
|
||||
qw422016.N().S(`,`)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:60
|
||||
}
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:61
|
||||
}
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:61
|
||||
qw422016.N().S(`]`)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:63
|
||||
}
|
||||
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:63
|
||||
func writetsdbStatusMetricNameEntries(qq422016 qtio422016.Writer, a []storage.TopHeapEntry, queryStats []storage.MetricNamesStatsRecord) {
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:63
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:63
|
||||
streamtsdbStatusMetricNameEntries(qw422016, a, queryStats)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:63
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:63
|
||||
}
|
||||
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:63
|
||||
func tsdbStatusMetricNameEntries(a []storage.TopHeapEntry, queryStats []storage.MetricNamesStatsRecord) string {
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:63
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:63
|
||||
writetsdbStatusMetricNameEntries(qb422016, a, queryStats)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:63
|
||||
qs422016 := string(qb422016.B)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:63
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:63
|
||||
return qs422016
|
||||
//line app/vmselect/prometheus/tsdb_status_response.qtpl:63
|
||||
}
|
||||
|
||||
@@ -344,3 +344,53 @@ type SnapshotDeleteResponse struct {
|
||||
type SnapshotDeleteAllResponse struct {
|
||||
Status string
|
||||
}
|
||||
|
||||
// TSDBStatusResponse is an in-memory reprensentation of the json response
|
||||
// returned by the /prometheus/api/v1/status/tsdb endpoint.
|
||||
type TSDBStatusResponse struct {
|
||||
IsPartial bool
|
||||
Data TSDBStatusResponseData
|
||||
}
|
||||
|
||||
// Sort performs sorting of stats entries
|
||||
func (tsr *TSDBStatusResponse) Sort() {
|
||||
sortTSDBStatusResponseEntries(tsr.Data.SeriesCountByLabelName)
|
||||
sortTSDBStatusResponseEntries(tsr.Data.SeriesCountByFocusLabelValue)
|
||||
sortTSDBStatusResponseEntries(tsr.Data.SeriesCountByLabelValuePair)
|
||||
sortTSDBStatusResponseEntries(tsr.Data.LabelValueCountByLabelName)
|
||||
}
|
||||
|
||||
// TSDBStatusResponseData is a part of TSDBStatusResponse
|
||||
type TSDBStatusResponseData struct {
|
||||
TotalSeries int
|
||||
TotalLabelValuePairs int
|
||||
SeriesCountByMetricName []TSDBStatusResponseMetricNameEntry
|
||||
SeriesCountByLabelName []TSDBStatusResponseEntry
|
||||
SeriesCountByFocusLabelValue []TSDBStatusResponseEntry
|
||||
SeriesCountByLabelValuePair []TSDBStatusResponseEntry
|
||||
LabelValueCountByLabelName []TSDBStatusResponseEntry
|
||||
}
|
||||
|
||||
// TSDBStatusResponseEntry defines stats entry for TSDBStatusResponseData
|
||||
type TSDBStatusResponseEntry struct {
|
||||
Name string
|
||||
Count int
|
||||
}
|
||||
|
||||
// TSDBStatusResponseMetricNameEntry defines metric names stats entry for TSDBStatusResponseData
|
||||
type TSDBStatusResponseMetricNameEntry struct {
|
||||
Name string
|
||||
Count int
|
||||
RequestsCount int
|
||||
LastRequestTimestamp int
|
||||
}
|
||||
|
||||
func sortTSDBStatusResponseEntries(entries []TSDBStatusResponseEntry) {
|
||||
sort.Slice(entries, func(i, j int) bool {
|
||||
left, right := entries[i], entries[j]
|
||||
if left.Count == right.Count {
|
||||
return left.Name < right.Name
|
||||
}
|
||||
return left.Count < right.Count
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/apptest"
|
||||
at "github.com/VictoriaMetrics/VictoriaMetrics/apptest"
|
||||
@@ -19,6 +20,7 @@ func TestSingleMetricNamesStats(t *testing.T) {
|
||||
|
||||
const ingestDateTime = `2024-02-05T08:57:36.700Z`
|
||||
const ingestTimestamp = ` 1707123456700`
|
||||
const date = `2024-02-05`
|
||||
dataSet := []string{
|
||||
`metric_name_1{label="foo"} 10`,
|
||||
`metric_name_1{label="bar"} 10`,
|
||||
@@ -29,6 +31,7 @@ func TestSingleMetricNamesStats(t *testing.T) {
|
||||
for idx := range dataSet {
|
||||
dataSet[idx] += ingestTimestamp
|
||||
}
|
||||
tsdbMetricNameEntryCmpOpts := cmpopts.IgnoreFields(apptest.TSDBStatusResponseMetricNameEntry{}, "LastRequestTimestamp")
|
||||
|
||||
sut.PrometheusAPIV1ImportPrometheus(t, dataSet, at.QueryOpts{})
|
||||
sut.ForceFlush(t)
|
||||
@@ -60,6 +63,31 @@ func TestSingleMetricNamesStats(t *testing.T) {
|
||||
t.Errorf("unexpected response (-want, +got):\n%s", diff)
|
||||
}
|
||||
|
||||
expectedStatsResponse := apptest.TSDBStatusResponse{
|
||||
Data: at.TSDBStatusResponseData{
|
||||
TotalSeries: 5,
|
||||
TotalLabelValuePairs: 10,
|
||||
SeriesCountByMetricName: []apptest.TSDBStatusResponseMetricNameEntry{
|
||||
{Name: "metric_name_1", RequestsCount: 3},
|
||||
{Name: "metric_name_2", RequestsCount: 1},
|
||||
{Name: "metric_name_3", RequestsCount: 1},
|
||||
},
|
||||
SeriesCountByLabelName: []apptest.TSDBStatusResponseEntry{{Name: "__name__"}, {Name: "label"}},
|
||||
SeriesCountByFocusLabelValue: []apptest.TSDBStatusResponseEntry{},
|
||||
SeriesCountByLabelValuePair: []apptest.TSDBStatusResponseEntry{
|
||||
{Name: "__name__=metric_name_1"}, {Name: "label=baz"},
|
||||
{Name: "__name__=metric_name_2"}, {Name: "__name__=metric_name_3"},
|
||||
{Name: "label=bar"}, {Name: "label=foo"},
|
||||
},
|
||||
LabelValueCountByLabelName: []apptest.TSDBStatusResponseEntry{{Name: "__name__"}, {Name: "label"}},
|
||||
},
|
||||
}
|
||||
expectedStatsResponse.Sort()
|
||||
gotStatus := sut.APIV1StatusTSDB(t, "", date, "", apptest.QueryOpts{})
|
||||
if diff := cmp.Diff(expectedStatsResponse, gotStatus, tsdbMetricNameEntryCmpOpts); diff != "" {
|
||||
t.Errorf("unexpected APIV1StatusTSDB response (-want, +got):\n%s", diff)
|
||||
}
|
||||
|
||||
// perform query request for single metric and check counter increase
|
||||
sut.PrometheusAPIV1Query(t, `metric_name_2`, at.QueryOpts{Time: ingestDateTime})
|
||||
expected = apptest.MetricNamesStatsResponse{
|
||||
@@ -129,6 +157,7 @@ func TestClusterMetricNamesStats(t *testing.T) {
|
||||
|
||||
const ingestDateTime = `2024-02-05T08:57:36.700Z`
|
||||
const ingestTimestamp = ` 1707123456700`
|
||||
const date = `2024-02-05`
|
||||
dataSet := []string{
|
||||
`metric_name_1{label="foo"} 10`,
|
||||
`metric_name_1{label="bar"} 10`,
|
||||
@@ -140,6 +169,8 @@ func TestClusterMetricNamesStats(t *testing.T) {
|
||||
dataSet[idx] += ingestTimestamp
|
||||
}
|
||||
|
||||
tsdbMetricNameEntryCmpOpts := cmpopts.IgnoreFields(apptest.TSDBStatusResponseMetricNameEntry{}, "LastRequestTimestamp")
|
||||
|
||||
// ingest per tenant data and verify it with search
|
||||
tenantIDs := []string{"1:1", "1:15", "15:15"}
|
||||
for _, tenantID := range tenantIDs {
|
||||
@@ -176,6 +207,31 @@ func TestClusterMetricNamesStats(t *testing.T) {
|
||||
if diff := cmp.Diff(expected, gotStats); diff != "" {
|
||||
t.Errorf("unexpected response tenant: %s (-want, +got):\n%s", tenantID, diff)
|
||||
}
|
||||
|
||||
expectedStatsResponse := apptest.TSDBStatusResponse{
|
||||
Data: at.TSDBStatusResponseData{
|
||||
TotalSeries: 5,
|
||||
TotalLabelValuePairs: 10,
|
||||
SeriesCountByMetricName: []apptest.TSDBStatusResponseMetricNameEntry{
|
||||
{Name: "metric_name_1", RequestsCount: 3},
|
||||
{Name: "metric_name_2", RequestsCount: 1},
|
||||
{Name: "metric_name_3", RequestsCount: 1},
|
||||
},
|
||||
SeriesCountByLabelName: []apptest.TSDBStatusResponseEntry{{Name: "__name__"}, {Name: "label"}},
|
||||
SeriesCountByFocusLabelValue: []apptest.TSDBStatusResponseEntry{},
|
||||
SeriesCountByLabelValuePair: []apptest.TSDBStatusResponseEntry{
|
||||
{Name: "__name__=metric_name_1"}, {Name: "label=baz"},
|
||||
{Name: "__name__=metric_name_2"}, {Name: "__name__=metric_name_3"},
|
||||
{Name: "label=bar"}, {Name: "label=foo"},
|
||||
},
|
||||
LabelValueCountByLabelName: []apptest.TSDBStatusResponseEntry{{Name: "__name__"}, {Name: "label"}},
|
||||
},
|
||||
}
|
||||
expectedStatsResponse.Sort()
|
||||
gotStatus := vmselect.APIV1StatusTSDB(t, "", date, "", apptest.QueryOpts{Tenant: tenantID})
|
||||
if diff := cmp.Diff(expectedStatsResponse, gotStatus, tsdbMetricNameEntryCmpOpts); diff != "" {
|
||||
t.Errorf("unexpected APIV1StatusTSDB response tenant: %s (-want, +got):\n%s", tenantID, diff)
|
||||
}
|
||||
}
|
||||
|
||||
// verify multitenant stats
|
||||
|
||||
@@ -174,6 +174,37 @@ func (app *Vmselect) MetricNamesStatsReset(t *testing.T, opts QueryOpts) {
|
||||
}
|
||||
}
|
||||
|
||||
// APIV1StatusTSDB sends a query to a /prometheus/api/v1/status/tsdb
|
||||
// //
|
||||
// See https://docs.victoriametrics.com/#tsdb-stats
|
||||
func (app *Vmselect) APIV1StatusTSDB(t *testing.T, matchQuery string, date string, topN string, opts QueryOpts) TSDBStatusResponse {
|
||||
t.Helper()
|
||||
|
||||
seriesURL := fmt.Sprintf("http://%s/select/%s/prometheus/api/v1/status/tsdb", app.httpListenAddr, opts.getTenant())
|
||||
values := opts.asURLValues()
|
||||
addNonEmpty := func(name, value string) {
|
||||
if len(value) == 0 {
|
||||
return
|
||||
}
|
||||
values.Add(name, value)
|
||||
}
|
||||
addNonEmpty("match[]", matchQuery)
|
||||
addNonEmpty("topN", topN)
|
||||
addNonEmpty("date", date)
|
||||
|
||||
res, statusCode := app.cli.PostForm(t, seriesURL, values)
|
||||
if statusCode != http.StatusOK {
|
||||
t.Fatalf("unexpected status code: got %d, want %d, resp text=%q", statusCode, http.StatusOK, res)
|
||||
}
|
||||
|
||||
var status TSDBStatusResponse
|
||||
if err := json.Unmarshal([]byte(res), &status); err != nil {
|
||||
t.Fatalf("could not unmarshal tsdb status response data:\n%s\n err: %v", res, err)
|
||||
}
|
||||
status.Sort()
|
||||
return status
|
||||
}
|
||||
|
||||
// String returns the string representation of the vmselect app state.
|
||||
func (app *Vmselect) String() string {
|
||||
return fmt.Sprintf("{app: %s httpListenAddr: %q}", app.app, app.httpListenAddr)
|
||||
|
||||
@@ -350,6 +350,37 @@ func (app *Vmsingle) SnapshotDeleteAll(t *testing.T) *SnapshotDeleteAllResponse
|
||||
return &res
|
||||
}
|
||||
|
||||
// APIV1StatusTSDB sends a query to a /prometheus/api/v1/status/tsdb
|
||||
// //
|
||||
// See https://docs.victoriametrics.com/#tsdb-stats
|
||||
func (app *Vmsingle) APIV1StatusTSDB(t *testing.T, matchQuery string, date string, topN string, opts QueryOpts) TSDBStatusResponse {
|
||||
t.Helper()
|
||||
|
||||
seriesURL := fmt.Sprintf("http://%s/prometheus/api/v1/status/tsdb", app.httpListenAddr)
|
||||
values := opts.asURLValues()
|
||||
addNonEmpty := func(name, value string) {
|
||||
if len(value) == 0 {
|
||||
return
|
||||
}
|
||||
values.Add(name, value)
|
||||
}
|
||||
addNonEmpty("match[]", matchQuery)
|
||||
addNonEmpty("topN", topN)
|
||||
addNonEmpty("date", date)
|
||||
|
||||
res, statusCode := app.cli.PostForm(t, seriesURL, values)
|
||||
if statusCode != http.StatusOK {
|
||||
t.Fatalf("unexpected status code: got %d, want %d, resp text=%q", statusCode, http.StatusOK, res)
|
||||
}
|
||||
|
||||
var status TSDBStatusResponse
|
||||
if err := json.Unmarshal([]byte(res), &status); err != nil {
|
||||
t.Fatalf("could not unmarshal tsdb status response data:\n%s\n err: %v", res, err)
|
||||
}
|
||||
status.Sort()
|
||||
return status
|
||||
}
|
||||
|
||||
// HTTPAddr returns the address at which the vmstorage process is listening
|
||||
// for http connections.
|
||||
func (app *Vmsingle) HTTPAddr() string {
|
||||
|
||||
@@ -2410,6 +2410,8 @@ due to [replication](#replication) or [rerouting](https://docs.victoriametrics.c
|
||||
|
||||
VictoriaMetrics provides UI on top of `/api/v1/status/tsdb` - see [cardinality explorer docs](#cardinality-explorer).
|
||||
|
||||
VictoriaMetrics enhances Prometheus stats with `requestsCount` and `lastRequestTimestamp` for `seriesCountByMetricName`. This stats added if [tracking metric names stats](https://docs.victoriametrics.com/#track-ingested-metrics-usage) is configured.
|
||||
|
||||
## Query tracing
|
||||
|
||||
VictoriaMetrics supports query tracing, which can be used for determining bottlenecks during query processing.
|
||||
|
||||
@@ -18,10 +18,15 @@ See also [LTS releases](https://docs.victoriametrics.com/lts-releases/).
|
||||
|
||||
## tip
|
||||
|
||||
** Update Note 1: Updated the RPC cluster protocol version for the [TSDB status API](https://docs.victoriametrics.com/#tsdb-stats) from `tsdbStatus_v5` to `tsdbStatus_v6`.
|
||||
The TSDB Status API will be temporarily unavailable during the version upgrade process.
|
||||
This update requires both vmselect and vmstorage components to be running version v1.116.0 or higher for full compatibility.
|
||||
|
||||
* FEATURE: [vmsingle](https://docs.victoriametrics.com/single-server-victoriametrics/) and `vmselect` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/cluster-victoriametrics/): add command-line flag `-search.logSlowQueryStats`. This flag is available only in VictoriaMetrics [enterprise](https://docs.victoriametrics.com/enterprise/). See the following [docs](https://docs.victoriametrics.com/query-stats) for details.
|
||||
* FEATURE: all the VictoriaMetrics components: mask `authKey` value from log messages. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5973) for details.
|
||||
* FEATURE: [vmsingle](https://docs.victoriametrics.com/single-server-victoriametrics/), [vmagent](https://docs.victoriametrics.com/vmagent/): add helpful hints to the unexpected EOF error message in the write concurrency limiter. See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/8704) for details.
|
||||
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent/): use [VM remote write protocol](https://docs.victoriametrics.com/vmagent/#victoriametrics-remote-write-protocol) by default with automatic downgrade in runtime to Prometheus protocol when needed. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/8462) for details.
|
||||
* FEATURE: [Single-node VictoriaMetrics](https://docs.victoriametrics.com/) and [vmstorage](https://docs.victoriametrics.com/cluster-victoriametrics/): add additional metric name stats to TSDB Status API response. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6145) for details and this [doc](https://docs.victoriametrics.com/#tsdb-stats).
|
||||
|
||||
* BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent/): properly init [enterprise](https://docs.victoriametrics.com/enterprise/) version for `linux/arm` and non-CGO buids. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6019) for details.
|
||||
* BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent/): remote write client sets correct content encoding header based on actual body content, rather than relying on configuration. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/8650).
|
||||
|
||||
@@ -1461,6 +1461,7 @@ type TSDBStatus struct {
|
||||
SeriesCountByFocusLabelValue []TopHeapEntry
|
||||
SeriesCountByLabelValuePair []TopHeapEntry
|
||||
LabelValueCountByLabelName []TopHeapEntry
|
||||
SeriesQueryStatsByMetricName []MetricNamesStatsRecord
|
||||
}
|
||||
|
||||
func (status *TSDBStatus) hasEntries() bool {
|
||||
|
||||
@@ -391,6 +391,33 @@ func (mt *Tracker) GetStatsForTenant(accountID, projectID uint32, limit, le int,
|
||||
return result
|
||||
}
|
||||
|
||||
// GetStatRecordsForNames returns stats records for the given metric names and tenant
|
||||
func (mt *Tracker) GetStatRecordsForNames(accountID, projectID uint32, metricNames []string) []StatRecord {
|
||||
if mt == nil {
|
||||
return nil
|
||||
}
|
||||
mt.mu.RLock()
|
||||
records := make([]StatRecord, 0, len(metricNames))
|
||||
for _, mn := range metricNames {
|
||||
sk := statKey{
|
||||
accountID: accountID,
|
||||
projectID: projectID,
|
||||
metricName: mn,
|
||||
}
|
||||
si, ok := mt.store[sk]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
records = append(records, StatRecord{
|
||||
MetricName: mn,
|
||||
RequestsCount: si.requestsCount.Load(),
|
||||
LastRequestTs: si.lastRequestTs.Load(),
|
||||
})
|
||||
}
|
||||
mt.mu.RUnlock()
|
||||
return records
|
||||
}
|
||||
|
||||
// GetStats returns stats response for the tracked metrics
|
||||
//
|
||||
// DeduplicateMergeRecords must be called at cluster version on returned result.
|
||||
|
||||
@@ -1705,7 +1705,19 @@ func (s *Storage) GetTSDBStatus(qt *querytracer.Tracer, tfss []*TagFilters, date
|
||||
if s.disablePerDayIndex {
|
||||
date = globalIndexDate
|
||||
}
|
||||
return idb.GetTSDBStatus(qt, tfss, date, focusLabel, topN, maxMetrics, deadline)
|
||||
res, err := idb.GetTSDBStatus(qt, tfss, date, focusLabel, topN, maxMetrics, deadline)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.metricsTracker != nil && len(res.SeriesCountByMetricName) > 0 {
|
||||
// for performance reason always check if metricsTracker is configured
|
||||
names := make([]string, len(res.SeriesCountByMetricName))
|
||||
for idx, mns := range res.SeriesCountByMetricName {
|
||||
names[idx] = mns.Name
|
||||
}
|
||||
res.SeriesQueryStatsByMetricName = s.metricsTracker.GetStatRecordsForNames(0, 0, names)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// MetricRow is a metric to insert into storage.
|
||||
@@ -2957,6 +2969,9 @@ func (s *Storage) wasMetricIDMissingBefore(metricID uint64) bool {
|
||||
// MetricNamesStatsResponse contains metric names usage stats API response
|
||||
type MetricNamesStatsResponse = metricnamestats.StatsResult
|
||||
|
||||
// MetricNamesStatsRecord represents record at MetricNamesStatsResponse
|
||||
type MetricNamesStatsRecord = metricnamestats.StatRecord
|
||||
|
||||
// GetMetricNamesStats returns metric names usage stats with given limit and le predicate
|
||||
func (s *Storage) GetMetricNamesStats(_ *querytracer.Tracer, limit, le int, matchPattern string) MetricNamesStatsResponse {
|
||||
return s.metricsTracker.GetStats(limit, le, matchPattern)
|
||||
|
||||
Reference in New Issue
Block a user