app/vmselect: export seriesFetched stat for /query responses (#3925)

The change adds a new field `seriesFetched` to EvalConfig object.
Since EvalConfig object can be copied inside `Exec`,
`seriesFetched` is a pointer which can be updated by all copied
objects.

The reason for having stats is that other components, like vmalert,
could benefit from this information.

Signed-off-by: hagen1778 <roman@victoriametrics.com>
Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
This commit is contained in:
Roman Khavronenko 2023-03-27 17:51:33 +02:00 committed by Aliaksandr Valialkin
parent 3214b1c315
commit 4021aa11b5
No known key found for this signature in database
GPG key ID: A72BEC6CD3D0DED1
6 changed files with 72 additions and 24 deletions

View file

@ -227,7 +227,7 @@ func StreamListGroups(qw422016 *qt422016.Writer, r *http.Request, groups []APIGr
//line app/vmalert/web.qtpl:55 //line app/vmalert/web.qtpl:55
qw422016.N().FPrec(g.Interval, 0) qw422016.N().FPrec(g.Interval, 0)
//line app/vmalert/web.qtpl:55 //line app/vmalert/web.qtpl:55
qw422016.N().S(`s) #</a> qw422016.N().S(`s) #</a>
`) `)
//line app/vmalert/web.qtpl:56 //line app/vmalert/web.qtpl:56
if rNotOk[g.ID] > 0 { if rNotOk[g.ID] > 0 {

View file

@ -786,7 +786,8 @@ func QueryHandler(qt *querytracer.Tracer, startTime time.Time, w http.ResponseWr
qtDone := func() { qtDone := func() {
qt.Donef("query=%s, time=%d: series=%d", query, start, len(result)) qt.Donef("query=%s, time=%d: series=%d", query, start, len(result))
} }
WriteQueryResponse(bw, result, qt, qtDone)
WriteQueryResponse(bw, result, qt, qtDone, ec.SeriesFetched())
if err := bw.Flush(); err != nil { if err := bw.Flush(); err != nil {
return fmt.Errorf("cannot flush query response to remote client: %w", err) return fmt.Errorf("cannot flush query response to remote client: %w", err)
} }

View file

@ -6,7 +6,7 @@
{% stripspace %} {% stripspace %}
QueryResponse generates response for /api/v1/query. QueryResponse generates response for /api/v1/query.
See https://prometheus.io/docs/prometheus/latest/querying/api/#instant-queries See https://prometheus.io/docs/prometheus/latest/querying/api/#instant-queries
{% func QueryResponse(rs []netstorage.Result, qt *querytracer.Tracer, qtDone func()) %} {% func QueryResponse(rs []netstorage.Result, qt *querytracer.Tracer, qtDone func(), seriesFetched int) %}
{ {
{% code seriesCount := len(rs) %} {% code seriesCount := len(rs) %}
"status":"success", "status":"success",
@ -28,6 +28,9 @@ See https://prometheus.io/docs/prometheus/latest/querying/api/#instant-queries
{% endfor %} {% endfor %}
{% endif %} {% endif %}
] ]
},
"stats":{
"seriesFetched": "{%d seriesFetched %}"
} }
{% code {% code
qt.Printf("generate /api/v1/query response for series=%d", seriesCount) qt.Printf("generate /api/v1/query response for series=%d", seriesCount)

View file

@ -26,7 +26,7 @@ var (
) )
//line app/vmselect/prometheus/query_response.qtpl:9 //line app/vmselect/prometheus/query_response.qtpl:9
func StreamQueryResponse(qw422016 *qt422016.Writer, rs []netstorage.Result, qt *querytracer.Tracer, qtDone func()) { func StreamQueryResponse(qw422016 *qt422016.Writer, rs []netstorage.Result, qt *querytracer.Tracer, qtDone func(), seriesFetched int) {
//line app/vmselect/prometheus/query_response.qtpl:9 //line app/vmselect/prometheus/query_response.qtpl:9
qw422016.N().S(`{`) qw422016.N().S(`{`)
//line app/vmselect/prometheus/query_response.qtpl:11 //line app/vmselect/prometheus/query_response.qtpl:11
@ -69,40 +69,44 @@ func StreamQueryResponse(qw422016 *qt422016.Writer, rs []netstorage.Result, qt *
//line app/vmselect/prometheus/query_response.qtpl:29 //line app/vmselect/prometheus/query_response.qtpl:29
} }
//line app/vmselect/prometheus/query_response.qtpl:29 //line app/vmselect/prometheus/query_response.qtpl:29
qw422016.N().S(`]}`) qw422016.N().S(`]},"stats":{"seriesFetched": "`)
//line app/vmselect/prometheus/query_response.qtpl:33 //line app/vmselect/prometheus/query_response.qtpl:33
qw422016.N().D(seriesFetched)
//line app/vmselect/prometheus/query_response.qtpl:33
qw422016.N().S(`"}`)
//line app/vmselect/prometheus/query_response.qtpl:36
qt.Printf("generate /api/v1/query response for series=%d", seriesCount) qt.Printf("generate /api/v1/query response for series=%d", seriesCount)
qtDone() qtDone()
//line app/vmselect/prometheus/query_response.qtpl:36 //line app/vmselect/prometheus/query_response.qtpl:39
streamdumpQueryTrace(qw422016, qt) streamdumpQueryTrace(qw422016, qt)
//line app/vmselect/prometheus/query_response.qtpl:36 //line app/vmselect/prometheus/query_response.qtpl:39
qw422016.N().S(`}`) qw422016.N().S(`}`)
//line app/vmselect/prometheus/query_response.qtpl:38 //line app/vmselect/prometheus/query_response.qtpl:41
} }
//line app/vmselect/prometheus/query_response.qtpl:38 //line app/vmselect/prometheus/query_response.qtpl:41
func WriteQueryResponse(qq422016 qtio422016.Writer, rs []netstorage.Result, qt *querytracer.Tracer, qtDone func()) { func WriteQueryResponse(qq422016 qtio422016.Writer, rs []netstorage.Result, qt *querytracer.Tracer, qtDone func(), seriesFetched int) {
//line app/vmselect/prometheus/query_response.qtpl:38 //line app/vmselect/prometheus/query_response.qtpl:41
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmselect/prometheus/query_response.qtpl:38 //line app/vmselect/prometheus/query_response.qtpl:41
StreamQueryResponse(qw422016, rs, qt, qtDone) StreamQueryResponse(qw422016, rs, qt, qtDone, seriesFetched)
//line app/vmselect/prometheus/query_response.qtpl:38 //line app/vmselect/prometheus/query_response.qtpl:41
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line app/vmselect/prometheus/query_response.qtpl:38 //line app/vmselect/prometheus/query_response.qtpl:41
} }
//line app/vmselect/prometheus/query_response.qtpl:38 //line app/vmselect/prometheus/query_response.qtpl:41
func QueryResponse(rs []netstorage.Result, qt *querytracer.Tracer, qtDone func()) string { func QueryResponse(rs []netstorage.Result, qt *querytracer.Tracer, qtDone func(), seriesFetched int) string {
//line app/vmselect/prometheus/query_response.qtpl:38 //line app/vmselect/prometheus/query_response.qtpl:41
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line app/vmselect/prometheus/query_response.qtpl:38 //line app/vmselect/prometheus/query_response.qtpl:41
WriteQueryResponse(qb422016, rs, qt, qtDone) WriteQueryResponse(qb422016, rs, qt, qtDone, seriesFetched)
//line app/vmselect/prometheus/query_response.qtpl:38 //line app/vmselect/prometheus/query_response.qtpl:41
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line app/vmselect/prometheus/query_response.qtpl:38 //line app/vmselect/prometheus/query_response.qtpl:41
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line app/vmselect/prometheus/query_response.qtpl:38 //line app/vmselect/prometheus/query_response.qtpl:41
return qs422016 return qs422016
//line app/vmselect/prometheus/query_response.qtpl:38 //line app/vmselect/prometheus/query_response.qtpl:41
} }

View file

@ -134,6 +134,12 @@ type EvalConfig struct {
timestamps []int64 timestamps []int64
timestampsOnce sync.Once timestampsOnce sync.Once
// seriesFetched stores the number of time series fetched
// from the storage during the evaluation.
// It is defined as a pointer since EvalConfig can be forked
// during the evaluation but we want to keep its state.
seriesFetched *int
} }
// copyEvalConfig returns src copy. // copyEvalConfig returns src copy.
@ -151,10 +157,28 @@ func copyEvalConfig(src *EvalConfig) *EvalConfig {
ec.EnforcedTagFilterss = src.EnforcedTagFilterss ec.EnforcedTagFilterss = src.EnforcedTagFilterss
ec.GetRequestURI = src.GetRequestURI ec.GetRequestURI = src.GetRequestURI
ec.seriesFetched = src.seriesFetched
// do not copy src.timestamps - they must be generated again. // do not copy src.timestamps - they must be generated again.
return &ec return &ec
} }
func (ec *EvalConfig) addStats(series int) {
if ec.seriesFetched == nil {
ec.seriesFetched = new(int)
}
*ec.seriesFetched += series
}
// SeriesFetched returns the number of series fetched from storages
// during the evaluation.
func (ec *EvalConfig) SeriesFetched() int {
if ec.seriesFetched == nil {
return 0
}
return *ec.seriesFetched
}
func (ec *EvalConfig) validate() { func (ec *EvalConfig) validate() {
if ec.Start > ec.End { if ec.Start > ec.End {
logger.Panicf("BUG: start cannot exceed end; got %d vs %d", ec.Start, ec.End) logger.Panicf("BUG: start cannot exceed end; got %d vs %d", ec.Start, ec.End)
@ -1077,6 +1101,7 @@ func evalRollupFuncWithMetricExpr(qt *querytracer.Tracer, ec *EvalConfig, funcNa
tss := mergeTimeseries(tssCached, nil, start, ec) tss := mergeTimeseries(tssCached, nil, start, ec)
return tss, nil return tss, nil
} }
ec.addStats(rssLen)
// Verify timeseries fit available memory after the rollup. // Verify timeseries fit available memory after the rollup.
// Take into account points from tssCached. // Take into account points from tssCached.

View file

@ -76,3 +76,18 @@ func TestValidateMaxPointsPerSeriesSuccess(t *testing.T) {
f(1659962171908, 1659966077742, 5000, 800) f(1659962171908, 1659966077742, 5000, 800)
f(1659962150000, 1659966070000, 10000, 393) f(1659962150000, 1659966070000, 10000, 393)
} }
func TestEvalConfig_SeriesFetched(t *testing.T) {
ec := &EvalConfig{}
ec.addStats(1)
if ec.SeriesFetched() != 1 {
t.Fatalf("expected to get 1; got %d instead", ec.SeriesFetched())
}
ecNew := copyEvalConfig(ec)
ecNew.addStats(3)
if ec.SeriesFetched() != 4 {
t.Fatalf("expected to get 4; got %d instead", ec.SeriesFetched())
}
}