Merge branch 'public-single-node' into pmm-6401-read-prometheus-data-files

This commit is contained in:
Aliaksandr Valialkin 2023-03-27 15:37:34 -07:00
commit 740638ad30
No known key found for this signature in database
GPG key ID: A72BEC6CD3D0DED1
40 changed files with 1174 additions and 194 deletions

View file

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

View file

@ -747,7 +747,8 @@ func QueryHandler(qt *querytracer.Tracer, startTime time.Time, w http.ResponseWr
} else {
queryOffset = 0
}
ec := promql.EvalConfig{
qs := &promql.QueryStats{}
ec := &promql.EvalConfig{
Start: start,
End: start,
Step: step,
@ -762,8 +763,10 @@ func QueryHandler(qt *querytracer.Tracer, startTime time.Time, w http.ResponseWr
GetRequestURI: func() string {
return httpserver.GetRequestURI(r)
},
QueryStats: qs,
}
result, err := promql.Exec(qt, &ec, query, true)
result, err := promql.Exec(qt, ec, query, true)
if err != nil {
return fmt.Errorf("error when executing query=%q for (time=%d, step=%d): %w", query, start, step, err)
}
@ -786,7 +789,8 @@ func QueryHandler(qt *querytracer.Tracer, startTime time.Time, w http.ResponseWr
qtDone := func() {
qt.Donef("query=%s, time=%d: series=%d", query, start, len(result))
}
WriteQueryResponse(bw, result, qt, qtDone)
WriteQueryResponse(bw, result, qt, qtDone, qs)
if err := bw.Flush(); err != nil {
return fmt.Errorf("cannot flush query response to remote client: %w", err)
}
@ -851,7 +855,8 @@ func queryRangeHandler(qt *querytracer.Tracer, startTime time.Time, w http.Respo
start, end = promql.AdjustStartEnd(start, end, step)
}
ec := promql.EvalConfig{
qs := &promql.QueryStats{}
ec := &promql.EvalConfig{
Start: start,
End: end,
Step: step,
@ -866,8 +871,10 @@ func queryRangeHandler(qt *querytracer.Tracer, startTime time.Time, w http.Respo
GetRequestURI: func() string {
return httpserver.GetRequestURI(r)
},
QueryStats: qs,
}
result, err := promql.Exec(qt, &ec, query, false)
result, err := promql.Exec(qt, ec, query, false)
if err != nil {
return err
}
@ -891,7 +898,7 @@ func queryRangeHandler(qt *querytracer.Tracer, startTime time.Time, w http.Respo
qtDone := func() {
qt.Donef("start=%d, end=%d, step=%d, query=%q: series=%d", start, end, step, query, len(result))
}
WriteQueryRangeResponse(bw, result, qt, qtDone)
WriteQueryRangeResponse(bw, result, qt, qtDone, qs)
if err := bw.Flush(); err != nil {
return fmt.Errorf("cannot send query range response to remote client: %w", err)
}

View file

@ -1,12 +1,13 @@
{% import (
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/netstorage"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/promql"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/querytracer"
) %}
{% stripspace %}
QueryRangeResponse generates response for /api/v1/query_range.
See https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries
{% func QueryRangeResponse(rs []netstorage.Result, qt *querytracer.Tracer, qtDone func()) %}
{% func QueryRangeResponse(rs []netstorage.Result, qt *querytracer.Tracer, qtDone func(), qs *promql.QueryStats) %}
{
{% code
seriesCount := len(rs)
@ -26,6 +27,9 @@ See https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries
{% endfor %}
{% endif %}
]
},
"stats":{
"seriesFetched": "{%d qs.SeriesFetched %}"
}
{% code
qt.Printf("generate /api/v1/query_range response for series=%d, points=%d", seriesCount, pointsCount)

View file

@ -7,133 +7,138 @@ package prometheus
//line app/vmselect/prometheus/query_range_response.qtpl:1
import (
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/netstorage"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/promql"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/querytracer"
)
// QueryRangeResponse generates response for /api/v1/query_range.See https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries
//line app/vmselect/prometheus/query_range_response.qtpl:9
//line app/vmselect/prometheus/query_range_response.qtpl:10
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line app/vmselect/prometheus/query_range_response.qtpl:9
//line app/vmselect/prometheus/query_range_response.qtpl:10
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line app/vmselect/prometheus/query_range_response.qtpl:9
func StreamQueryRangeResponse(qw422016 *qt422016.Writer, rs []netstorage.Result, qt *querytracer.Tracer, qtDone func()) {
//line app/vmselect/prometheus/query_range_response.qtpl:9
//line app/vmselect/prometheus/query_range_response.qtpl:10
func StreamQueryRangeResponse(qw422016 *qt422016.Writer, rs []netstorage.Result, qt *querytracer.Tracer, qtDone func(), qs *promql.QueryStats) {
//line app/vmselect/prometheus/query_range_response.qtpl:10
qw422016.N().S(`{`)
//line app/vmselect/prometheus/query_range_response.qtpl:12
//line app/vmselect/prometheus/query_range_response.qtpl:13
seriesCount := len(rs)
pointsCount := 0
//line app/vmselect/prometheus/query_range_response.qtpl:14
//line app/vmselect/prometheus/query_range_response.qtpl:15
qw422016.N().S(`"status":"success","data":{"resultType":"matrix","result":[`)
//line app/vmselect/prometheus/query_range_response.qtpl:19
if len(rs) > 0 {
//line app/vmselect/prometheus/query_range_response.qtpl:20
streamqueryRangeLine(qw422016, &rs[0])
if len(rs) > 0 {
//line app/vmselect/prometheus/query_range_response.qtpl:21
streamqueryRangeLine(qw422016, &rs[0])
//line app/vmselect/prometheus/query_range_response.qtpl:22
pointsCount += len(rs[0].Values)
//line app/vmselect/prometheus/query_range_response.qtpl:22
//line app/vmselect/prometheus/query_range_response.qtpl:23
rs = rs[1:]
//line app/vmselect/prometheus/query_range_response.qtpl:23
for i := range rs {
//line app/vmselect/prometheus/query_range_response.qtpl:23
qw422016.N().S(`,`)
//line app/vmselect/prometheus/query_range_response.qtpl:24
streamqueryRangeLine(qw422016, &rs[i])
for i := range rs {
//line app/vmselect/prometheus/query_range_response.qtpl:24
qw422016.N().S(`,`)
//line app/vmselect/prometheus/query_range_response.qtpl:25
streamqueryRangeLine(qw422016, &rs[i])
//line app/vmselect/prometheus/query_range_response.qtpl:26
pointsCount += len(rs[i].Values)
//line app/vmselect/prometheus/query_range_response.qtpl:26
//line app/vmselect/prometheus/query_range_response.qtpl:27
}
//line app/vmselect/prometheus/query_range_response.qtpl:27
//line app/vmselect/prometheus/query_range_response.qtpl:28
}
//line app/vmselect/prometheus/query_range_response.qtpl:27
qw422016.N().S(`]}`)
//line app/vmselect/prometheus/query_range_response.qtpl:31
//line app/vmselect/prometheus/query_range_response.qtpl:28
qw422016.N().S(`]},"stats":{"seriesFetched": "`)
//line app/vmselect/prometheus/query_range_response.qtpl:32
qw422016.N().D(qs.SeriesFetched)
//line app/vmselect/prometheus/query_range_response.qtpl:32
qw422016.N().S(`"}`)
//line app/vmselect/prometheus/query_range_response.qtpl:35
qt.Printf("generate /api/v1/query_range response for series=%d, points=%d", seriesCount, pointsCount)
qtDone()
//line app/vmselect/prometheus/query_range_response.qtpl:34
//line app/vmselect/prometheus/query_range_response.qtpl:38
streamdumpQueryTrace(qw422016, qt)
//line app/vmselect/prometheus/query_range_response.qtpl:34
qw422016.N().S(`}`)
//line app/vmselect/prometheus/query_range_response.qtpl:36
}
//line app/vmselect/prometheus/query_range_response.qtpl:36
func WriteQueryRangeResponse(qq422016 qtio422016.Writer, rs []netstorage.Result, qt *querytracer.Tracer, qtDone func()) {
//line app/vmselect/prometheus/query_range_response.qtpl:36
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmselect/prometheus/query_range_response.qtpl:36
StreamQueryRangeResponse(qw422016, rs, qt, qtDone)
//line app/vmselect/prometheus/query_range_response.qtpl:36
qt422016.ReleaseWriter(qw422016)
//line app/vmselect/prometheus/query_range_response.qtpl:36
}
//line app/vmselect/prometheus/query_range_response.qtpl:36
func QueryRangeResponse(rs []netstorage.Result, qt *querytracer.Tracer, qtDone func()) string {
//line app/vmselect/prometheus/query_range_response.qtpl:36
qb422016 := qt422016.AcquireByteBuffer()
//line app/vmselect/prometheus/query_range_response.qtpl:36
WriteQueryRangeResponse(qb422016, rs, qt, qtDone)
//line app/vmselect/prometheus/query_range_response.qtpl:36
qs422016 := string(qb422016.B)
//line app/vmselect/prometheus/query_range_response.qtpl:36
qt422016.ReleaseByteBuffer(qb422016)
//line app/vmselect/prometheus/query_range_response.qtpl:36
return qs422016
//line app/vmselect/prometheus/query_range_response.qtpl:36
}
//line app/vmselect/prometheus/query_range_response.qtpl:38
qw422016.N().S(`}`)
//line app/vmselect/prometheus/query_range_response.qtpl:40
}
//line app/vmselect/prometheus/query_range_response.qtpl:40
func WriteQueryRangeResponse(qq422016 qtio422016.Writer, rs []netstorage.Result, qt *querytracer.Tracer, qtDone func(), qs *promql.QueryStats) {
//line app/vmselect/prometheus/query_range_response.qtpl:40
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmselect/prometheus/query_range_response.qtpl:40
StreamQueryRangeResponse(qw422016, rs, qt, qtDone, qs)
//line app/vmselect/prometheus/query_range_response.qtpl:40
qt422016.ReleaseWriter(qw422016)
//line app/vmselect/prometheus/query_range_response.qtpl:40
}
//line app/vmselect/prometheus/query_range_response.qtpl:40
func QueryRangeResponse(rs []netstorage.Result, qt *querytracer.Tracer, qtDone func(), qs *promql.QueryStats) string {
//line app/vmselect/prometheus/query_range_response.qtpl:40
qb422016 := qt422016.AcquireByteBuffer()
//line app/vmselect/prometheus/query_range_response.qtpl:40
WriteQueryRangeResponse(qb422016, rs, qt, qtDone, qs)
//line app/vmselect/prometheus/query_range_response.qtpl:40
qs422016 := string(qb422016.B)
//line app/vmselect/prometheus/query_range_response.qtpl:40
qt422016.ReleaseByteBuffer(qb422016)
//line app/vmselect/prometheus/query_range_response.qtpl:40
return qs422016
//line app/vmselect/prometheus/query_range_response.qtpl:40
}
//line app/vmselect/prometheus/query_range_response.qtpl:42
func streamqueryRangeLine(qw422016 *qt422016.Writer, r *netstorage.Result) {
//line app/vmselect/prometheus/query_range_response.qtpl:38
//line app/vmselect/prometheus/query_range_response.qtpl:42
qw422016.N().S(`{"metric":`)
//line app/vmselect/prometheus/query_range_response.qtpl:40
//line app/vmselect/prometheus/query_range_response.qtpl:44
streammetricNameObject(qw422016, &r.MetricName)
//line app/vmselect/prometheus/query_range_response.qtpl:40
//line app/vmselect/prometheus/query_range_response.qtpl:44
qw422016.N().S(`,"values":`)
//line app/vmselect/prometheus/query_range_response.qtpl:41
//line app/vmselect/prometheus/query_range_response.qtpl:45
streamvaluesWithTimestamps(qw422016, r.Values, r.Timestamps)
//line app/vmselect/prometheus/query_range_response.qtpl:41
//line app/vmselect/prometheus/query_range_response.qtpl:45
qw422016.N().S(`}`)
//line app/vmselect/prometheus/query_range_response.qtpl:43
//line app/vmselect/prometheus/query_range_response.qtpl:47
}
//line app/vmselect/prometheus/query_range_response.qtpl:43
//line app/vmselect/prometheus/query_range_response.qtpl:47
func writequeryRangeLine(qq422016 qtio422016.Writer, r *netstorage.Result) {
//line app/vmselect/prometheus/query_range_response.qtpl:43
//line app/vmselect/prometheus/query_range_response.qtpl:47
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmselect/prometheus/query_range_response.qtpl:43
//line app/vmselect/prometheus/query_range_response.qtpl:47
streamqueryRangeLine(qw422016, r)
//line app/vmselect/prometheus/query_range_response.qtpl:43
//line app/vmselect/prometheus/query_range_response.qtpl:47
qt422016.ReleaseWriter(qw422016)
//line app/vmselect/prometheus/query_range_response.qtpl:43
//line app/vmselect/prometheus/query_range_response.qtpl:47
}
//line app/vmselect/prometheus/query_range_response.qtpl:43
//line app/vmselect/prometheus/query_range_response.qtpl:47
func queryRangeLine(r *netstorage.Result) string {
//line app/vmselect/prometheus/query_range_response.qtpl:43
//line app/vmselect/prometheus/query_range_response.qtpl:47
qb422016 := qt422016.AcquireByteBuffer()
//line app/vmselect/prometheus/query_range_response.qtpl:43
//line app/vmselect/prometheus/query_range_response.qtpl:47
writequeryRangeLine(qb422016, r)
//line app/vmselect/prometheus/query_range_response.qtpl:43
//line app/vmselect/prometheus/query_range_response.qtpl:47
qs422016 := string(qb422016.B)
//line app/vmselect/prometheus/query_range_response.qtpl:43
//line app/vmselect/prometheus/query_range_response.qtpl:47
qt422016.ReleaseByteBuffer(qb422016)
//line app/vmselect/prometheus/query_range_response.qtpl:43
//line app/vmselect/prometheus/query_range_response.qtpl:47
return qs422016
//line app/vmselect/prometheus/query_range_response.qtpl:43
//line app/vmselect/prometheus/query_range_response.qtpl:47
}

View file

@ -1,12 +1,13 @@
{% import (
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/netstorage"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/promql"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/querytracer"
) %}
{% stripspace %}
QueryResponse generates response for /api/v1/query.
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(), qs *promql.QueryStats) %}
{
{% code seriesCount := len(rs) %}
"status":"success",
@ -28,6 +29,9 @@ See https://prometheus.io/docs/prometheus/latest/querying/api/#instant-queries
{% endfor %}
{% endif %}
]
},
"stats":{
"seriesFetched": "{%d qs.SeriesFetched %}"
}
{% code
qt.Printf("generate /api/v1/query response for series=%d", seriesCount)

View file

@ -7,102 +7,107 @@ package prometheus
//line app/vmselect/prometheus/query_response.qtpl:1
import (
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/netstorage"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/promql"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/querytracer"
)
// QueryResponse generates response for /api/v1/query.See https://prometheus.io/docs/prometheus/latest/querying/api/#instant-queries
//line app/vmselect/prometheus/query_response.qtpl:9
//line app/vmselect/prometheus/query_response.qtpl:10
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line app/vmselect/prometheus/query_response.qtpl:9
//line app/vmselect/prometheus/query_response.qtpl:10
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line app/vmselect/prometheus/query_response.qtpl:9
func StreamQueryResponse(qw422016 *qt422016.Writer, rs []netstorage.Result, qt *querytracer.Tracer, qtDone func()) {
//line app/vmselect/prometheus/query_response.qtpl:9
//line app/vmselect/prometheus/query_response.qtpl:10
func StreamQueryResponse(qw422016 *qt422016.Writer, rs []netstorage.Result, qt *querytracer.Tracer, qtDone func(), qs *promql.QueryStats) {
//line app/vmselect/prometheus/query_response.qtpl:10
qw422016.N().S(`{`)
//line app/vmselect/prometheus/query_response.qtpl:11
//line app/vmselect/prometheus/query_response.qtpl:12
seriesCount := len(rs)
//line app/vmselect/prometheus/query_response.qtpl:11
//line app/vmselect/prometheus/query_response.qtpl:12
qw422016.N().S(`"status":"success","data":{"resultType":"vector","result":[`)
//line app/vmselect/prometheus/query_response.qtpl:16
//line app/vmselect/prometheus/query_response.qtpl:17
if len(rs) > 0 {
//line app/vmselect/prometheus/query_response.qtpl:16
//line app/vmselect/prometheus/query_response.qtpl:17
qw422016.N().S(`{"metric":`)
//line app/vmselect/prometheus/query_response.qtpl:18
//line app/vmselect/prometheus/query_response.qtpl:19
streammetricNameObject(qw422016, &rs[0].MetricName)
//line app/vmselect/prometheus/query_response.qtpl:18
//line app/vmselect/prometheus/query_response.qtpl:19
qw422016.N().S(`,"value":`)
//line app/vmselect/prometheus/query_response.qtpl:19
//line app/vmselect/prometheus/query_response.qtpl:20
streammetricRow(qw422016, rs[0].Timestamps[0], rs[0].Values[0])
//line app/vmselect/prometheus/query_response.qtpl:19
//line app/vmselect/prometheus/query_response.qtpl:20
qw422016.N().S(`}`)
//line app/vmselect/prometheus/query_response.qtpl:21
//line app/vmselect/prometheus/query_response.qtpl:22
rs = rs[1:]
//line app/vmselect/prometheus/query_response.qtpl:22
for i := range rs {
//line app/vmselect/prometheus/query_response.qtpl:23
for i := range rs {
//line app/vmselect/prometheus/query_response.qtpl:24
r := &rs[i]
//line app/vmselect/prometheus/query_response.qtpl:23
//line app/vmselect/prometheus/query_response.qtpl:24
qw422016.N().S(`,{"metric":`)
//line app/vmselect/prometheus/query_response.qtpl:25
//line app/vmselect/prometheus/query_response.qtpl:26
streammetricNameObject(qw422016, &r.MetricName)
//line app/vmselect/prometheus/query_response.qtpl:25
//line app/vmselect/prometheus/query_response.qtpl:26
qw422016.N().S(`,"value":`)
//line app/vmselect/prometheus/query_response.qtpl:26
//line app/vmselect/prometheus/query_response.qtpl:27
streammetricRow(qw422016, r.Timestamps[0], r.Values[0])
//line app/vmselect/prometheus/query_response.qtpl:26
//line app/vmselect/prometheus/query_response.qtpl:27
qw422016.N().S(`}`)
//line app/vmselect/prometheus/query_response.qtpl:28
//line app/vmselect/prometheus/query_response.qtpl:29
}
//line app/vmselect/prometheus/query_response.qtpl:29
//line app/vmselect/prometheus/query_response.qtpl:30
}
//line app/vmselect/prometheus/query_response.qtpl:29
qw422016.N().S(`]}`)
//line app/vmselect/prometheus/query_response.qtpl:33
//line app/vmselect/prometheus/query_response.qtpl:30
qw422016.N().S(`]},"stats":{"seriesFetched": "`)
//line app/vmselect/prometheus/query_response.qtpl:34
qw422016.N().D(qs.SeriesFetched)
//line app/vmselect/prometheus/query_response.qtpl:34
qw422016.N().S(`"}`)
//line app/vmselect/prometheus/query_response.qtpl:37
qt.Printf("generate /api/v1/query response for series=%d", seriesCount)
qtDone()
//line app/vmselect/prometheus/query_response.qtpl:36
//line app/vmselect/prometheus/query_response.qtpl:40
streamdumpQueryTrace(qw422016, qt)
//line app/vmselect/prometheus/query_response.qtpl:36
//line app/vmselect/prometheus/query_response.qtpl:40
qw422016.N().S(`}`)
//line app/vmselect/prometheus/query_response.qtpl:38
//line app/vmselect/prometheus/query_response.qtpl:42
}
//line app/vmselect/prometheus/query_response.qtpl:38
func WriteQueryResponse(qq422016 qtio422016.Writer, rs []netstorage.Result, qt *querytracer.Tracer, qtDone func()) {
//line app/vmselect/prometheus/query_response.qtpl:38
//line app/vmselect/prometheus/query_response.qtpl:42
func WriteQueryResponse(qq422016 qtio422016.Writer, rs []netstorage.Result, qt *querytracer.Tracer, qtDone func(), qs *promql.QueryStats) {
//line app/vmselect/prometheus/query_response.qtpl:42
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmselect/prometheus/query_response.qtpl:38
StreamQueryResponse(qw422016, rs, qt, qtDone)
//line app/vmselect/prometheus/query_response.qtpl:38
//line app/vmselect/prometheus/query_response.qtpl:42
StreamQueryResponse(qw422016, rs, qt, qtDone, qs)
//line app/vmselect/prometheus/query_response.qtpl:42
qt422016.ReleaseWriter(qw422016)
//line app/vmselect/prometheus/query_response.qtpl:38
//line app/vmselect/prometheus/query_response.qtpl:42
}
//line app/vmselect/prometheus/query_response.qtpl:38
func QueryResponse(rs []netstorage.Result, qt *querytracer.Tracer, qtDone func()) string {
//line app/vmselect/prometheus/query_response.qtpl:38
//line app/vmselect/prometheus/query_response.qtpl:42
func QueryResponse(rs []netstorage.Result, qt *querytracer.Tracer, qtDone func(), qs *promql.QueryStats) string {
//line app/vmselect/prometheus/query_response.qtpl:42
qb422016 := qt422016.AcquireByteBuffer()
//line app/vmselect/prometheus/query_response.qtpl:38
WriteQueryResponse(qb422016, rs, qt, qtDone)
//line app/vmselect/prometheus/query_response.qtpl:38
//line app/vmselect/prometheus/query_response.qtpl:42
WriteQueryResponse(qb422016, rs, qt, qtDone, qs)
//line app/vmselect/prometheus/query_response.qtpl:42
qs422016 := string(qb422016.B)
//line app/vmselect/prometheus/query_response.qtpl:38
//line app/vmselect/prometheus/query_response.qtpl:42
qt422016.ReleaseByteBuffer(qb422016)
//line app/vmselect/prometheus/query_response.qtpl:38
//line app/vmselect/prometheus/query_response.qtpl:42
return qs422016
//line app/vmselect/prometheus/query_response.qtpl:38
//line app/vmselect/prometheus/query_response.qtpl:42
}

View file

@ -132,6 +132,12 @@ type EvalConfig struct {
// The request URI isn't stored here because its' construction may take non-trivial amounts of CPU.
GetRequestURI func() string
// QueryStats contains various stats for the currently executed query.
//
// The caller must initialize the QueryStats if it needs the stats.
// Otherwise the stats isn't collected.
QueryStats *QueryStats
timestamps []int64
timestampsOnce sync.Once
}
@ -150,11 +156,24 @@ func copyEvalConfig(src *EvalConfig) *EvalConfig {
ec.RoundDigits = src.RoundDigits
ec.EnforcedTagFilterss = src.EnforcedTagFilterss
ec.GetRequestURI = src.GetRequestURI
ec.QueryStats = src.QueryStats
// do not copy src.timestamps - they must be generated again.
return &ec
}
// QueryStats contains various stats for the query.
type QueryStats struct {
// SeriesFetched contains the number of series fetched from storage during the query evaluation.
SeriesFetched int
}
func (qs *QueryStats) addSeriesFetched(n int) {
if qs != nil {
qs.SeriesFetched += n
}
}
func (ec *EvalConfig) validate() {
if ec.Start > ec.End {
logger.Panicf("BUG: start cannot exceed end; got %d vs %d", ec.Start, ec.End)
@ -1077,6 +1096,7 @@ func evalRollupFuncWithMetricExpr(qt *querytracer.Tracer, ec *EvalConfig, funcNa
tss := mergeTimeseries(tssCached, nil, start, ec)
return tss, nil
}
ec.QueryStats.addSeriesFetched(rssLen)
// Verify timeseries fit available memory after the rollup.
// Take into account points from tssCached.

View file

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

View file

@ -1,14 +1,14 @@
{
"files": {
"main.css": "./static/css/main.2c709f8b.css",
"main.js": "./static/js/main.34430dca.js",
"main.css": "./static/css/main.e0a028b9.css",
"main.js": "./static/js/main.e5645090.js",
"static/js/27.c1ccfd29.chunk.js": "./static/js/27.c1ccfd29.chunk.js",
"static/media/Lato-Regular.ttf": "./static/media/Lato-Regular.d714fec1633b69a9c2e9.ttf",
"static/media/Lato-Bold.ttf": "./static/media/Lato-Bold.32360ba4b57802daa4d6.ttf",
"index.html": "./index.html"
},
"entrypoints": [
"static/css/main.2c709f8b.css",
"static/js/main.34430dca.js"
"static/css/main.e0a028b9.css",
"static/js/main.e5645090.js"
]
}

View file

@ -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,maximum-scale=1,user-scalable=no"/><meta name="theme-color" content="#000000"/><meta name="description" content="UI for VictoriaMetrics"/><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><script src="./dashboards/index.js" type="module"></script><meta name="twitter:card" content="summary_large_image"><meta name="twitter:image" content="./preview.jpg"><meta name="twitter:title" content="UI for VictoriaMetrics"><meta name="twitter:description" content="Explore and troubleshoot your VictoriaMetrics data"><meta name="twitter:site" content="@VictoriaMetrics"><meta property="og:title" content="Metric explorer for VictoriaMetrics"><meta property="og:description" content="Explore and troubleshoot your VictoriaMetrics data"><meta property="og:image" content="./preview.jpg"><meta property="og:type" content="website"><script defer="defer" src="./static/js/main.34430dca.js"></script><link href="./static/css/main.2c709f8b.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,maximum-scale=1,user-scalable=no"/><meta name="theme-color" content="#000000"/><meta name="description" content="UI for VictoriaMetrics"/><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><script src="./dashboards/index.js" type="module"></script><meta name="twitter:card" content="summary_large_image"><meta name="twitter:image" content="./preview.jpg"><meta name="twitter:title" content="UI for VictoriaMetrics"><meta name="twitter:description" content="Explore and troubleshoot your VictoriaMetrics data"><meta name="twitter:site" content="@VictoriaMetrics"><meta property="og:title" content="Metric explorer for VictoriaMetrics"><meta property="og:description" content="Explore and troubleshoot your VictoriaMetrics data"><meta property="og:image" content="./preview.jpg"><meta property="og:type" content="website"><script defer="defer" src="./static/js/main.e5645090.js"></script><link href="./static/css/main.e0a028b9.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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -51,6 +51,13 @@ $chart-tooltip-y: -1 * ($padding-small + $chart-tooltip-half-icon);
color: $color-white;
cursor: move;
}
&__date {
&_range {
display: grid;
gap: 2px;
}
}
}
&-data {

View file

@ -0,0 +1,160 @@
import React, { FC, useEffect, useMemo, useRef, useState } from "preact/compat";
import uPlot from "uplot";
import ReactDOM from "react-dom";
import Button from "../../Main/Button/Button";
import { CloseIcon, DragIcon } from "../../Main/Icons";
import classNames from "classnames";
import { MouseEvent as ReactMouseEvent } from "react";
import "../ChartTooltip/style.scss";
export interface TooltipHeatmapProps {
cursor: {left: number, top: number}
startDate: string,
endDate: string,
metricName: string,
fields: string[],
value: number,
valueFormat: string
}
export interface ChartTooltipHeatmapProps extends TooltipHeatmapProps {
id: string,
u: uPlot,
unit?: string,
isSticky?: boolean,
tooltipOffset: { left: number, top: number },
onClose?: (id: string) => void
}
const ChartTooltipHeatmap: FC<ChartTooltipHeatmapProps> = ({
u,
id,
unit = "",
cursor,
tooltipOffset,
isSticky,
onClose,
startDate,
endDate,
metricName,
fields,
valueFormat,
value
}) => {
const tooltipRef = useRef<HTMLDivElement>(null);
const [position, setPosition] = useState({ top: -999, left: -999 });
const [moving, setMoving] = useState(false);
const [moved, setMoved] = useState(false);
const targetPortal = useMemo(() => u.root.querySelector(".u-wrap"), [u]);
const handleClose = () => {
onClose && onClose(id);
};
const handleMouseDown = (e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => {
setMoved(true);
setMoving(true);
const { clientX, clientY } = e;
setPosition({ top: clientY, left: clientX });
};
const handleMouseMove = (e: MouseEvent) => {
if (!moving) return;
const { clientX, clientY } = e;
setPosition({ top: clientY, left: clientX });
};
const handleMouseUp = () => {
setMoving(false);
};
const calcPosition = () => {
if (!tooltipRef.current) return;
const topOnChart = cursor.top;
const leftOnChart = cursor.left;
const { width: tooltipWidth, height: tooltipHeight } = tooltipRef.current.getBoundingClientRect();
const { width, height } = u.over.getBoundingClientRect();
const margin = 10;
const overflowX = leftOnChart + tooltipWidth >= width ? tooltipWidth + (2 * margin) : 0;
const overflowY = topOnChart + tooltipHeight >= height ? tooltipHeight + (2 * margin) : 0;
setPosition({
top: topOnChart + tooltipOffset.top + margin - overflowY,
left: leftOnChart + tooltipOffset.left + margin - overflowX
});
};
useEffect(calcPosition, [u, cursor, tooltipOffset, tooltipRef]);
useEffect(() => {
if (moving) {
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
}
return () => {
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
};
}, [moving]);
if (!targetPortal || !cursor.left || !cursor.top || !value) return null;
return ReactDOM.createPortal((
<div
className={classNames({
"vm-chart-tooltip": true,
"vm-chart-tooltip_sticky": isSticky,
"vm-chart-tooltip_moved": moved
})}
ref={tooltipRef}
style={position}
>
<div className="vm-chart-tooltip-header">
<div className="vm-chart-tooltip-header__date vm-chart-tooltip-header__date_range">
<span>{startDate}</span>
<span>{endDate}</span>
</div>
{isSticky && (
<>
<Button
className="vm-chart-tooltip-header__drag"
variant="text"
size="small"
startIcon={<DragIcon/>}
onMouseDown={handleMouseDown}
/>
<Button
className="vm-chart-tooltip-header__close"
variant="text"
size="small"
startIcon={<CloseIcon/>}
onClick={handleClose}
/>
</>
)}
</div>
<div className="vm-chart-tooltip-data">
<p>
{metricName}:
<b className="vm-chart-tooltip-data__value">{valueFormat}</b>
{unit}
</p>
</div>
{!!fields.length && (
<div className="vm-chart-tooltip-info">
{fields.map((f, i) => (
<div key={`${f}_${i}`}>{f}</div>
))}
</div>
)}
</div>
), targetPortal);
};
export default ChartTooltipHeatmap;

View file

@ -0,0 +1,383 @@
import React, { FC, useCallback, useEffect, useMemo, useRef, useState } from "preact/compat";
import uPlot, {
AlignedData as uPlotData,
Options as uPlotOptions,
Range
} from "uplot";
import { defaultOptions, sizeAxis } from "../../../utils/uplot/helpers";
import { dragChart } from "../../../utils/uplot/events";
import { getAxes } from "../../../utils/uplot/axes";
import { MetricResult } from "../../../api/types";
import { dateFromSeconds, formatDateForNativeInput, limitsDurations } from "../../../utils/time";
import throttle from "lodash.throttle";
import useResize from "../../../hooks/useResize";
import { TimeParams } from "../../../types";
import { YaxisState } from "../../../state/graph/reducer";
import "uplot/dist/uPlot.min.css";
import classNames from "classnames";
import dayjs from "dayjs";
import { useAppState } from "../../../state/common/StateContext";
import { heatmapPaths } from "../../../utils/uplot/heatmap";
import { DATE_FULL_TIMEZONE_FORMAT } from "../../../constants/date";
import ChartTooltipHeatmap, {
ChartTooltipHeatmapProps,
TooltipHeatmapProps
} from "../ChartTooltipHeatmap/ChartTooltipHeatmap";
export interface HeatmapChartProps {
metrics: MetricResult[];
data: uPlotData;
period: TimeParams;
yaxis: YaxisState;
unit?: string;
setPeriod: ({ from, to }: {from: Date, to: Date}) => void;
container: HTMLDivElement | null;
height?: number;
onChangeLegend: (val: number) => void;
}
enum typeChartUpdate {xRange = "xRange", yRange = "yRange"}
const HeatmapChart: FC<HeatmapChartProps> = ({
data,
metrics = [],
period,
yaxis,
unit,
setPeriod,
container,
height,
onChangeLegend,
}) => {
const { isDarkTheme } = useAppState();
const uPlotRef = useRef<HTMLDivElement>(null);
const [isPanning, setPanning] = useState(false);
const [xRange, setXRange] = useState({ min: period.start, max: period.end });
const [uPlotInst, setUPlotInst] = useState<uPlot>();
const [startTouchDistance, setStartTouchDistance] = useState(0);
const layoutSize = useResize(container);
const [tooltipProps, setTooltipProps] = useState<TooltipHeatmapProps | null>(null);
const [tooltipOffset, setTooltipOffset] = useState({ left: 0, top: 0 });
const [stickyTooltips, setStickyToolTips] = useState<ChartTooltipHeatmapProps[]>([]);
const tooltipId = useMemo(() => {
return `${tooltipProps?.fields.join(",")}_${tooltipProps?.startDate}`;
}, [tooltipProps]);
const setScale = ({ min, max }: { min: number, max: number }): void => {
if (isNaN(min) || isNaN(max)) return;
setPeriod({
from: dayjs(min * 1000).toDate(),
to: dayjs(max * 1000).toDate()
});
};
const throttledSetScale = useCallback(throttle(setScale, 500), []);
const setPlotScale = ({ u, min, max }: { u: uPlot, min: number, max: number }) => {
const delta = (max - min) * 1000;
if ((delta < limitsDurations.min) || (delta > limitsDurations.max)) return;
u.setScale("x", { min, max });
setXRange({ min, max });
throttledSetScale({ min, max });
};
const onReadyChart = (u: uPlot) => {
const factor = 0.9;
setTooltipOffset({
left: parseFloat(u.over.style.left),
top: parseFloat(u.over.style.top)
});
u.over.addEventListener("mousedown", e => {
const { ctrlKey, metaKey, button } = e;
const leftClick = button === 0;
const leftClickWithMeta = leftClick && (ctrlKey || metaKey);
if (leftClickWithMeta) {
// drag pan
dragChart({ u, e, setPanning, setPlotScale, factor });
}
});
u.over.addEventListener("touchstart", e => {
dragChart({ u, e, setPanning, setPlotScale, factor });
});
u.over.addEventListener("wheel", e => {
if (!e.ctrlKey && !e.metaKey) return;
e.preventDefault();
const { width } = u.over.getBoundingClientRect();
const zoomPos = u.cursor.left && u.cursor.left > 0 ? u.cursor.left : 0;
const xVal = u.posToVal(zoomPos, "x");
const oxRange = (u.scales.x.max || 0) - (u.scales.x.min || 0);
const nxRange = e.deltaY < 0 ? oxRange * factor : oxRange / factor;
const min = xVal - (zoomPos / width) * nxRange;
const max = min + nxRange;
u.batch(() => setPlotScale({ u, min, max }));
});
};
const handleKeyDown = (e: KeyboardEvent) => {
const { target, ctrlKey, metaKey, key } = e;
const isInput = target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement;
if (!uPlotInst || isInput) return;
const minus = key === "-";
const plus = key === "+" || key === "=";
if ((minus || plus) && !(ctrlKey || metaKey)) {
e.preventDefault();
const factor = (xRange.max - xRange.min) / 10 * (plus ? 1 : -1);
setPlotScale({
u: uPlotInst,
min: xRange.min + factor,
max: xRange.max - factor
});
}
};
const handleClick = () => {
if (!tooltipProps) return;
const id = `${tooltipProps?.fields.join(",")}_${tooltipProps?.startDate}`;
const props = {
id,
unit,
tooltipOffset,
...tooltipProps
};
if (!stickyTooltips.find(t => t.id === id)) {
const res = JSON.parse(JSON.stringify(props));
setStickyToolTips(prev => [...prev, res]);
}
};
const handleUnStick = (id:string) => {
setStickyToolTips(prev => prev.filter(t => t.id !== id));
};
const setCursor = (u: uPlot) => {
const left = u.cursor.left && u.cursor.left > 0 ? u.cursor.left : 0;
const top = u.cursor.top && u.cursor.top > 0 ? u.cursor.top : 0;
const xArr = (u.data[1][0] || []) as number[];
if (!Array.isArray(xArr)) return;
const xVal = u.posToVal(left, "x");
const yVal = u.posToVal(top, "y");
const xIdx = xArr.findIndex((t, i) => xVal >= t && xVal < xArr[i + 1]) || -1;
const second = xArr[xIdx + 1];
const result = metrics[Math.round(yVal)];
if (!result) {
setTooltipProps(null);
return;
}
const metric = result?.metric;
const metricName = metric["__name__"] || "value";
const labelNames = Object.keys(metric).filter(x => x != "__name__");
const fields = labelNames.map(key => `${key}=${JSON.stringify(metric[key])}`);
const [endTime = 0, value = ""] = result.values.find(v => v[0] === second) || [];
const valueFormat = `${+value}%`;
const startTime = xArr[xIdx];
const startDate = dayjs(startTime * 1000).tz().format(DATE_FULL_TIMEZONE_FORMAT);
const endDate = dayjs(endTime * 1000).tz().format(DATE_FULL_TIMEZONE_FORMAT);
setTooltipProps({
cursor: { left, top },
startDate,
endDate,
metricName,
fields,
value: +value,
valueFormat: valueFormat,
});
};
const getRangeX = (): Range.MinMax => [xRange.min, xRange.max];
const axes = getAxes( [{}], unit);
const options: uPlotOptions = {
...defaultOptions,
mode: 2,
tzDate: ts => dayjs(formatDateForNativeInput(dateFromSeconds(ts))).local().toDate(),
series: [
{},
{
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
paths: heatmapPaths(),
facets: [
{
scale: "x",
auto: true,
sorted: 1,
},
{
scale: "y",
auto: true,
},
],
},
],
axes: [
...axes,
{
scale: "y",
stroke: axes[0].stroke,
font: axes[0].font,
size: sizeAxis,
splits: metrics.map((m, i) => i),
values: metrics.map(m => Object.entries(m.metric).map(e => `${e[0]}=${JSON.stringify(e[1])}`)[0]),
}
],
scales: {
x: {
time: true,
},
y: {
log: 2,
time: false,
range: (self, initMin, initMax) => [initMin - 1, initMax + 1]
}
},
width: layoutSize.width || 400,
height: height || 500,
plugins: [{ hooks: { ready: onReadyChart, setCursor } }],
hooks: {
setSelect: [
(u) => {
const min = u.posToVal(u.select.left, "x");
const max = u.posToVal(u.select.left + u.select.width, "x");
setPlotScale({ u, min, max });
}
]
},
};
const updateChart = (type: typeChartUpdate): void => {
if (!uPlotInst) return;
switch (type) {
case typeChartUpdate.xRange:
uPlotInst.scales.x.range = getRangeX;
break;
}
if (!isPanning) uPlotInst.redraw();
};
useEffect(() => setXRange({ min: period.start, max: period.end }), [period]);
useEffect(() => {
setStickyToolTips([]);
setTooltipProps(null);
if (!uPlotRef.current || !layoutSize.width || !layoutSize.height) return;
const u = new uPlot(options, data, uPlotRef.current);
setUPlotInst(u);
setXRange({ min: period.start, max: period.end });
return u.destroy;
}, [uPlotRef.current, layoutSize, height, isDarkTheme, data]);
useEffect(() => {
window.addEventListener("keydown", handleKeyDown);
return () => {
window.removeEventListener("keydown", handleKeyDown);
};
}, [xRange]);
const handleTouchStart = (e: TouchEvent) => {
if (e.touches.length !== 2) return;
e.preventDefault();
const dx = e.touches[0].clientX - e.touches[1].clientX;
const dy = e.touches[0].clientY - e.touches[1].clientY;
setStartTouchDistance(Math.sqrt(dx * dx + dy * dy));
};
const handleTouchMove = (e: TouchEvent) => {
if (e.touches.length !== 2 || !uPlotInst) return;
e.preventDefault();
const dx = e.touches[0].clientX - e.touches[1].clientX;
const dy = e.touches[0].clientY - e.touches[1].clientY;
const endTouchDistance = Math.sqrt(dx * dx + dy * dy);
const diffDistance = startTouchDistance - endTouchDistance;
const max = (uPlotInst.scales.x.max || xRange.max);
const min = (uPlotInst.scales.x.min || xRange.min);
const dur = max - min;
const dir = (diffDistance > 0 ? -1 : 1);
const zoomFactor = dur / 50 * dir;
uPlotInst.batch(() => setPlotScale({
u: uPlotInst,
min: min + zoomFactor,
max: max - zoomFactor
}));
};
useEffect(() => {
window.addEventListener("touchmove", handleTouchMove);
window.addEventListener("touchstart", handleTouchStart);
return () => {
window.removeEventListener("touchmove", handleTouchMove);
window.removeEventListener("touchstart", handleTouchStart);
};
}, [uPlotInst, startTouchDistance]);
useEffect(() => updateChart(typeChartUpdate.xRange), [xRange]);
useEffect(() => updateChart(typeChartUpdate.yRange), [yaxis]);
useEffect(() => {
const show = !!tooltipProps?.value;
if (show) window.addEventListener("click", handleClick);
return () => {
window.removeEventListener("click", handleClick);
};
}, [tooltipProps, stickyTooltips]);
useEffect(() => {
onChangeLegend(tooltipProps?.value || 0);
}, [tooltipProps]);
return (
<div
className={classNames({
"vm-line-chart": true,
"vm-line-chart_panning": isPanning
})}
style={{
minWidth: `${layoutSize.width || 400}px`,
minHeight: `${height || 500}px`
}}
>
<div
className="vm-line-chart__u-plot"
ref={uPlotRef}
/>
{uPlotInst && tooltipProps && (
<ChartTooltipHeatmap
{...tooltipProps}
unit={unit}
u={uPlotInst}
tooltipOffset={tooltipOffset}
id={tooltipId}
/>
)}
{uPlotInst && stickyTooltips.map(t => (
<ChartTooltipHeatmap
{...t}
isSticky
u={uPlotInst}
key={t.id}
onClose={handleUnStick}
/>
))}
</div>
);
};
export default HeatmapChart;

View file

@ -0,0 +1,46 @@
import React, { FC, useEffect, useState } from "preact/compat";
import { gradMetal16 } from "../../../utils/uplot/heatmap";
import "./style.scss";
interface LegendHeatmapProps {
min: number
max: number
value?: number
}
const LegendHeatmap: FC<LegendHeatmapProps> = ({ min, max, value }) => {
const [percent, setPercent] = useState(0);
const [valueFormat, setValueFormat] = useState("");
const [minFormat, setMinFormat] = useState("");
const [maxFormat, setMaxFormat] = useState("");
useEffect(() => {
setPercent(value ? (value - min) / (max - min) * 100 : 0);
setValueFormat(value ? `${value}%` : "");
setMinFormat(`${min}%`);
setMaxFormat(`${max}%`);
}, [value, min, max]);
return (
<div className="vm-legend-heatmap">
<div
className="vm-legend-heatmap-gradient"
style={{ background: `linear-gradient(to right, ${gradMetal16.join(", ")})` }}
>
{!!value && (
<div
className="vm-legend-heatmap-gradient__value"
style={{ left: `${percent}%` }}
>
<span>{valueFormat}</span>
</div>
)}
</div>
<div className="vm-legend-heatmap__value">{minFormat}</div>
<div className="vm-legend-heatmap__value">{maxFormat}</div>
</div>
);
};
export default LegendHeatmap;

View file

@ -0,0 +1,55 @@
@use "src/styles/variables" as *;
.vm-legend-heatmap {
display: inline-grid;
grid-template-columns: auto auto;
align-items: center;
justify-content: space-between;
gap: 4px;
&__value {
color: $color-text;
font-size: $font-size-small;
&:last-child {
text-align: right;
}
}
&-gradient {
$gradient-height: $font-size-small;
display: flex;
align-items: center;
justify-content: center;
position: relative;
grid-column: 1/-1;
height: $gradient-height;
width: 200px;
&__value {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
top: -2px;
border-radius: 50%;
border: 2px solid $color-text;
width: calc($gradient-height + 4px);
height: calc($gradient-height + 4px);
transform: translateX(calc(($gradient-height/-2) - 2px));
transition: left 100ms ease;
span {
position: absolute;
top: calc($gradient-height + 6px);
left: auto;
padding: 4px 8px;
color: $color-text;
font-size: $font-size-small;
background-color: $color-background-block;
box-shadow: $box-shadow;
}
}
}
}

View file

@ -36,7 +36,7 @@ export interface LineChartProps {
height?: number;
}
enum typeChartUpdate {xRange = "xRange", yRange = "yRange", data = "data"}
enum typeChartUpdate {xRange = "xRange", yRange = "yRange"}
const LineChart: FC<LineChartProps> = ({
data,
@ -215,9 +215,6 @@ const LineChart: FC<LineChartProps> = ({
uPlotInst.scales[axis].range = (u: uPlot, min = 0, max = 1) => getRangeY(u, min, max, axis);
});
break;
case typeChartUpdate.data:
uPlotInst.setData(data);
break;
}
if (!isPanning) uPlotInst.redraw();
};
@ -283,7 +280,6 @@ const LineChart: FC<LineChartProps> = ({
};
}, [uPlotInst, startTouchDistance]);
useEffect(() => updateChart(typeChartUpdate.data), [data]);
useEffect(() => updateChart(typeChartUpdate.xRange), [xRange]);
useEffect(() => updateChart(typeChartUpdate.yRange), [yaxis]);

View file

@ -1,10 +1,10 @@
import React, { FC, useEffect, useRef, useState } from "preact/compat";
import React, { FC, useEffect, useMemo, useRef, useState } from "preact/compat";
import { ArrowDownIcon, RestartIcon, TimelineIcon } from "../../Main/Icons";
import TextField from "../../Main/TextField/TextField";
import Button from "../../Main/Button/Button";
import Tooltip from "../../Main/Tooltip/Tooltip";
import { ErrorTypes } from "../../../types";
import { supportedDurations } from "../../../utils/time";
import { getStepFromDuration, supportedDurations } from "../../../utils/time";
import { useTimeState } from "../../../state/time/TimeStateContext";
import { useGraphDispatch, useGraphState } from "../../../state/graph/GraphStateContext";
import usePrevious from "../../../hooks/usePrevious";
@ -18,12 +18,15 @@ const StepConfigurator: FC = () => {
const appModeEnable = getAppModeEnable();
const { isMobile } = useDeviceDetect();
const { customStep: value } = useGraphState();
const { period: { step: defaultStep } } = useTimeState();
const { customStep: value, isHistogram } = useGraphState();
const { period: { step, end, start } } = useTimeState();
const graphDispatch = useGraphDispatch();
const { period: duration } = useTimeState();
const prevDuration = usePrevious(duration.end - duration.start);
const prevDuration = usePrevious(end - start);
const defaultStep = useMemo(() => {
return getStepFromDuration(end - start, isHistogram);
}, [step, isHistogram]);
const [openOptions, setOpenOptions] = useState(false);
const [customStep, setCustomStep] = useState(value || defaultStep);
@ -94,12 +97,16 @@ const StepConfigurator: FC = () => {
}, [defaultStep]);
useEffect(() => {
const dur = duration.end - duration.start;
const dur = end - start;
if (dur === prevDuration || !prevDuration) return;
if (defaultStep) {
handleApply(defaultStep);
}
}, [duration, prevDuration, defaultStep]);
}, [end, start, prevDuration, defaultStep]);
useEffect(() => {
if (step === value || step === defaultStep) handleApply(defaultStep);
}, [isHistogram]);
return (
<div

View file

@ -3,6 +3,7 @@ import { MetricResult } from "../../../api/types";
import LineChart from "../../Chart/LineChart/LineChart";
import { AlignedData as uPlotData, Series as uPlotSeries } from "uplot";
import Legend from "../../Chart/Legend/Legend";
import LegendHeatmap from "../../Chart/LegendHeatmap/LegendHeatmap";
import { getHideSeries, getLegendItem, getSeriesItemContext } from "../../../utils/uplot/series";
import { getLimitsYAxis, getMinMaxBuffer, getTimeSeries } from "../../../utils/uplot/axes";
import { LegendItemType } from "../../../utils/uplot/types";
@ -11,8 +12,10 @@ import { AxisRange, YaxisState } from "../../../state/graph/reducer";
import { getAvgFromArray, getMaxFromArray, getMinFromArray } from "../../../utils/math";
import classNames from "classnames";
import { useTimeState } from "../../../state/time/TimeStateContext";
import HeatmapChart from "../../Chart/HeatmapChart/HeatmapChart";
import "./style.scss";
import { promValueToNumber } from "../../../utils/metric";
import { normalizeData } from "../../../utils/uplot/heatmap";
import useDeviceDetect from "../../../hooks/useDeviceDetect";
export interface GraphViewProps {
@ -28,10 +31,11 @@ export interface GraphViewProps {
setPeriod: ({ from, to }: {from: Date, to: Date}) => void
fullWidth?: boolean
height?: number
isHistogram?: boolean
}
const GraphView: FC<GraphViewProps> = ({
data = [],
data: dataRaw = [],
period,
customStep,
query,
@ -42,20 +46,24 @@ const GraphView: FC<GraphViewProps> = ({
setPeriod,
alias = [],
fullWidth = true,
height
height,
isHistogram
}) => {
const { isMobile } = useDeviceDetect();
const { timezone } = useTimeState();
const currentStep = useMemo(() => customStep || period.step || "1s", [period.step, customStep]);
const data = useMemo(() => normalizeData(dataRaw, isHistogram), [isHistogram, dataRaw]);
const getSeriesItem = useCallback(getSeriesItemContext(), [data]);
const [dataChart, setDataChart] = useState<uPlotData>([[]]);
const [series, setSeries] = useState<uPlotSeries[]>([]);
const [legend, setLegend] = useState<LegendItemType[]>([]);
const [hideSeries, setHideSeries] = useState<string[]>([]);
const [legendValue, setLegendValue] = useState(0);
const setLimitsYaxis = (values: {[key: string]: number[]}) => {
const limits = getLimitsYAxis(values);
const limits = getLimitsYAxis(values, !isHistogram);
setYaxisLimits(limits);
};
@ -63,6 +71,32 @@ const GraphView: FC<GraphViewProps> = ({
setHideSeries(getHideSeries({ hideSeries, legend, metaKey, series }));
};
const handleChangeLegend = (val: number) => {
setLegendValue(val);
};
const prepareHistogramData = (data: (number | null)[][]) => {
const values = data.slice(1, data.length);
const xs: (number | null | undefined)[] = [];
const counts: (number | null | undefined)[] = [];
values.forEach((arr, indexRow) => {
arr.forEach((v, indexValue) => {
const targetIndex = (indexValue * values.length) + indexRow;
counts[targetIndex] = v;
});
});
data[0].forEach(t => {
const arr = new Array(values.length).fill(t);
xs.push(...arr);
});
const ys = new Array(xs.length).fill(0).map((n, i) => i % (values.length));
return [null, [xs, ys, counts]];
};
useEffect(() => {
const tempTimes: number[] = [];
const tempValues: {[key: string]: number[]} = {};
@ -111,10 +145,11 @@ const GraphView: FC<GraphViewProps> = ({
});
timeDataSeries.unshift(timeSeries);
setLimitsYaxis(tempValues);
setDataChart(timeDataSeries as uPlotData);
const result = isHistogram ? prepareHistogramData(timeDataSeries) : timeDataSeries;
setDataChart(result as uPlotData);
setSeries(tempSeries);
setLegend(tempLegend);
}, [data, timezone]);
}, [data, timezone, isHistogram]);
useEffect(() => {
const tempLegend: LegendItemType[] = [];
@ -139,7 +174,7 @@ const GraphView: FC<GraphViewProps> = ({
})}
ref={containerRef}
>
{containerRef?.current &&
{containerRef?.current && !isHistogram && (
<LineChart
data={dataChart}
series={series}
@ -150,12 +185,35 @@ const GraphView: FC<GraphViewProps> = ({
setPeriod={setPeriod}
container={containerRef?.current}
height={height}
/>}
{showLegend && <Legend
labels={legend}
query={query}
onChange={onChangeLegend}
/>}
/>
)}
{containerRef?.current && isHistogram && (
<HeatmapChart
data={dataChart}
metrics={data}
period={period}
yaxis={yaxis}
unit={unit}
setPeriod={setPeriod}
container={containerRef?.current}
height={height}
onChangeLegend={handleChangeLegend}
/>
)}
{!isHistogram && showLegend && (
<Legend
labels={legend}
query={query}
onChange={onChangeLegend}
/>
)}
{isHistogram && showLegend && (
<LegendHeatmap
min={yaxis.limits.range[1][0] || 0}
max={yaxis.limits.range[1][1] || 0}
value={legendValue}
/>
)}
</div>
);
};

View file

@ -10,6 +10,7 @@ import Trace from "../components/TraceQuery/Trace";
import { useQueryState } from "../state/query/QueryStateContext";
import { useTimeState } from "../state/time/TimeStateContext";
import { useCustomPanelState } from "../state/customPanel/CustomPanelStateContext";
import { isHistogramData } from "../utils/metric";
interface FetchQueryParams {
predefinedQuery?: string[]
@ -29,6 +30,7 @@ interface FetchQueryReturn {
queryErrors: (ErrorTypes | string)[],
warning?: string,
traces?: Trace[],
isHistogram: boolean,
}
interface FetchDataParams {
@ -62,6 +64,7 @@ export const useFetchQuery = ({
const [queryErrors, setQueryErrors] = useState<(ErrorTypes | string)[]>([]);
const [warning, setWarning] = useState<string>();
const [fetchQueue, setFetchQueue] = useState<AbortController[]>([]);
const [isHistogram, setIsHistogram] = useState(false);
const fetchData = async ({
fetchUrl,
@ -118,6 +121,7 @@ export const useFetchQuery = ({
const limitText = `Showing ${seriesLimit} series out of ${totalLength} series due to performance reasons. Please narrow down the query, so it returns less series`;
setWarning(totalLength > seriesLimit ? limitText : "");
setIsHistogram(isDisplayChart && isHistogramData(tempData));
isDisplayChart ? setGraphData(tempData as MetricResult[]) : setLiveData(tempData as InstantMetricResult[]);
setTraces(tempTraces);
} catch (e) {
@ -178,5 +182,5 @@ export const useFetchQuery = ({
setFetchQueue(fetchQueue.filter(f => !f.signal.aborted));
}, [fetchQueue]);
return { fetchUrl, isLoading, graphData, liveData, error, queryErrors, warning, traces };
return { fetchUrl, isLoading, graphData, liveData, error, queryErrors, warning, traces, isHistogram };
};

View file

@ -42,7 +42,7 @@ const CustomPanel: FC = () => {
const { queryOptions } = useFetchQueryOptions();
const {
isLoading, liveData, graphData, error, queryErrors, warning, traces
isLoading, liveData, graphData, error, queryErrors, warning, traces, isHistogram
} = useFetchQuery({
visible: true,
customStep,
@ -93,6 +93,10 @@ const CustomPanel: FC = () => {
setShowAllSeries(false);
}, [query]);
useEffect(() => {
graphDispatch({ type: "SET_IS_HISTOGRAM", payload: isHistogram });
}, [isHistogram]);
return (
<div
className={classNames({
@ -143,7 +147,7 @@ const CustomPanel: FC = () => {
>
<div className="vm-custom-panel-body-header">
<DisplayTypeSwitch/>
{displayType === "chart" && (
{displayType === "chart" && !isHistogram && (
<GraphSettings
yaxis={yaxis}
setYaxisLimits={setYaxisLimits}
@ -168,6 +172,7 @@ const CustomPanel: FC = () => {
setYaxisLimits={setYaxisLimits}
setPeriod={setPeriod}
height={isMobile ? window.innerHeight * 0.5 : 500}
isHistogram={isHistogram}
/>
)}
{liveData && (displayType === "code") && (

View file

@ -14,18 +14,21 @@ export interface YaxisState {
export interface GraphState {
customStep: string
yaxis: YaxisState
isHistogram: boolean
}
export type GraphAction =
| { type: "TOGGLE_ENABLE_YAXIS_LIMITS" }
| { type: "SET_YAXIS_LIMITS", payload: AxisRange }
| { type: "SET_CUSTOM_STEP", payload: string}
| { type: "SET_IS_HISTOGRAM", payload: boolean }
export const initialGraphState: GraphState = {
customStep: getQueryStringValue("g0.step_input", "") as string,
yaxis: {
limits: { enable: false, range: { "1": [0, 0] } }
}
},
isHistogram: false
};
export function reducer(state: GraphState, action: GraphAction): GraphState {
@ -57,6 +60,11 @@ export function reducer(state: GraphState, action: GraphAction): GraphState {
}
}
};
case "SET_IS_HISTOGRAM":
return {
...state,
isHistogram: action.payload
};
default:
throw new Error();
}

View file

@ -1,3 +1,5 @@
import { ArrayRGB } from "./uplot/types";
export const baseContrastColors = [
"#e54040",
"#32a9dc",
@ -56,3 +58,15 @@ export const getContrastColor = (value: string) => {
const yiq = ((r*299)+(g*587)+(b*114))/1000;
return yiq >= 128 ? "#000000" : "#FFFFFF";
};
export const generateGradient = (start: ArrayRGB, end: ArrayRGB, steps: number) => {
const gradient = [];
for (let i = 0; i < steps; i++) {
const k = (i / (steps - 1));
const r = start[0] + (end[0] - start[0]) * k;
const g = start[1] + (end[1] - start[1]) * k;
const b = start[2] + (end[2] - start[2]) * k;
gradient.push([r,g,b].map(n => Math.round(n)).join(", "));
}
return gradient.map(c => `rgb(${c})`);
};

View file

@ -28,3 +28,17 @@ export const promValueToNumber = (s: string): number => {
return parseFloat(s);
}
};
export const isHistogramData = (result: MetricBase[]) => {
if (result.length < 2) return false;
const histogramNames = ["le", "vmrange"];
return result.every(r => {
const keys = Object.keys(r.metric);
const labels = Object.keys(r.metric).filter(n => !histogramNames.includes(n));
const byName = keys.length > labels.length;
const byLabels = labels.every(l => r.metric[l] === result[0].metric[l]);
return byName && byLabels;
});
};

View file

@ -5,6 +5,7 @@ import { DATE_ISO_FORMAT } from "../constants/date";
import timezones from "../constants/timezones";
const MAX_ITEMS_PER_CHART = window.innerWidth / 4;
const MAX_ITEMS_PER_HISTOGRAM = window.innerWidth / 40;
export const limitsDurations = { min: 1, max: 1.578e+11 }; // min: 1 ms, max: 5 years
@ -83,17 +84,19 @@ export const getSecondsFromDuration = (dur: string) => {
return dayjs.duration(durObject).asSeconds();
};
export const getStepFromDuration = (dur: number, histogram?: boolean) => {
const size = histogram ? MAX_ITEMS_PER_HISTOGRAM : MAX_ITEMS_PER_CHART;
return roundStep(dur / size);
};
export const getTimeperiodForDuration = (dur: string, date?: Date): TimeParams => {
const n = (date || dayjs().toDate()).valueOf() / 1000;
const delta = getSecondsFromDuration(dur);
const rawStep = delta / MAX_ITEMS_PER_CHART;
const step = roundStep(rawStep);
return {
start: n - delta,
end: n,
step: step,
step: getStepFromDuration(delta),
date: formatDateToUTC(date || dayjs().toDate())
};
};

View file

@ -19,15 +19,17 @@ const timeValues = [
];
export const getAxes = (series: Series[], unit?: string): Axis[] => Array.from(new Set(series.map(s => s.scale))).map(a => {
const font = "10px Arial";
const stroke = getCssVariable("color-text");
const axis = {
scale: a,
show: true,
size: sizeAxis,
stroke: getCssVariable("color-text"),
font: "10px Arial",
stroke,
font,
values: (u: uPlot, ticks: number[]) => formatTicks(u, ticks, unit)
};
if (!a) return { space: 80, values: timeValues, stroke: getCssVariable("color-text") };
if (!a) return { space: 80, values: timeValues, stroke, font };
if (!(Number(a) % 2)) return { ...axis, side: 1 };
return axis;
});
@ -66,12 +68,12 @@ export const getMinMaxBuffer = (min: number | null, max: number | null): [number
return [min - padding, max + padding];
};
export const getLimitsYAxis = (values: { [key: string]: number[] }): AxisRange => {
export const getLimitsYAxis = (values: { [key: string]: number[] }, buffer: boolean): AxisRange => {
const result: AxisRange = {};
const numbers = Object.values(values).flat();
const key = "1";
const min = getMinFromArray(numbers);
const max = getMaxFromArray(numbers);
result[key] = getMinMaxBuffer(min, max);
const min = getMinFromArray(numbers) || 0;
const max = getMaxFromArray(numbers) || 1;
result[key] = buffer ? getMinMaxBuffer(min, max) : [min, max];
return result;
};

View file

@ -0,0 +1,151 @@
import uPlot from "uplot";
import { generateGradient } from "../color";
import { MetricResult } from "../../api/types";
// 16-color gradient from "rgb(246, 226, 219)" to "rgb(127, 39, 4)"
export const gradMetal16 = generateGradient([246, 226, 219], [127, 39, 4], 16);
export const countsToFills = (u: uPlot, seriesIdx: number) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const counts = u.data[seriesIdx][2] as number[];
const palette = gradMetal16;
const hideThreshold = 0;
let minCount = Infinity;
let maxCount = -Infinity;
for (let i = 0; i < counts.length; i++) {
if (counts[i] > hideThreshold) {
minCount = Math.min(minCount, counts[i]);
maxCount = Math.max(maxCount, counts[i]);
}
}
const range = maxCount - minCount;
const paletteSize = palette.length;
const indexedFills = Array(counts.length);
for (let i = 0; i < counts.length; i++) {
indexedFills[i] = counts[i] === 0
? -1
: Math.min(paletteSize - 1, Math.floor((paletteSize * (counts[i] - minCount)) / range));
}
return indexedFills;
};
export const heatmapPaths = () => (u: uPlot, seriesIdx: number) => {
const cellGap = Math.round(devicePixelRatio);
uPlot.orient(u, seriesIdx, (
series,
dataX,
dataY,
scaleX,
scaleY,
valToPosX,
valToPosY,
xOff,
yOff,
xDim,
yDim,
moveTo,
lineTo,
rect
) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const [xs, ys, counts] = u.data[seriesIdx] as [number[], number[], number[]];
const dlen = xs.length;
// fill colors are mapped from interpolating densities / counts along some gradient
// (should be quantized to 64 colors/levels max. e.g. 16)
const fills = countsToFills(u, seriesIdx);
const fillPalette = gradMetal16 ?? [...Array.from(new Set(fills))];
const fillPaths = fillPalette.map(() => new Path2D());
// detect x and y bin qtys by detecting layout repetition in x & y data
const yBinQty = dlen - ys.lastIndexOf(ys[0]);
const xBinQty = dlen / yBinQty;
const yBinIncr = ys[1] - ys[0];
const xBinIncr = xs[yBinQty] - xs[0];
// uniform tile sizes based on zoom level
const xSize = (valToPosX(xBinIncr, scaleX, xDim, xOff) - valToPosX(0, scaleX, xDim, xOff)) - cellGap;
const ySize = (valToPosY(yBinIncr, scaleY, yDim, yOff) - valToPosY(0, scaleY, yDim, yOff)) + cellGap;
// pre-compute x and y offsets
const cys = ys.slice(0, yBinQty).map((y: number) => {
return Math.round(valToPosY(y, scaleY, yDim, yOff) - ySize / 2);
});
const cxs = Array.from({ length: xBinQty }, (v, i) => {
return Math.round(valToPosX(xs[i * yBinQty], scaleX, xDim, xOff) - xSize);
});
for (let i = 0; i < dlen; i++) {
// filter out 0 counts and out of view
if (
counts[i] > 0 &&
xs[i] >= (scaleX.min || -Infinity) && xs[i] <= (scaleX.max || Infinity) &&
ys[i] >= (scaleY.min || -Infinity) && ys[i] <= (scaleY.max || Infinity)
) {
const cx = cxs[~~(i / yBinQty)];
const cy = cys[i % yBinQty];
const fillPath = fillPaths[fills[i]];
rect(fillPath, cx, cy, xSize, ySize);
}
}
u.ctx.save();
u.ctx.rect(u.bbox.left, u.bbox.top, u.bbox.width, u.bbox.height);
u.ctx.clip();
fillPaths.forEach((p, i) => {
u.ctx.fillStyle = fillPalette[i];
u.ctx.fill(p);
});
u.ctx.restore();
});
};
export const convertPrometheusToVictoriaMetrics = (buckets: MetricResult[]): MetricResult[] => {
if (!buckets.every(a => a.metric.le)) return buckets;
const sortedBuckets = buckets.sort((a,b) => parseFloat(a.metric.le) - parseFloat(b.metric.le));
const group = buckets[0]?.group || 1;
let prevBucket: MetricResult = { metric: { le: "0" }, values: [], group };
const result: MetricResult[] = [];
for (const bucket of sortedBuckets) {
const vmrange = `${prevBucket.metric.le}..${bucket.metric.le}`;
const values: [number, string][] = [];
for (const [timestamp, value] of bucket.values) {
const prevVal = prevBucket.values.find(v => v[0] === timestamp)?.[1] || 0;
const newVal = (+value) - (+prevVal);
values.push([timestamp, `${newVal}`]);
}
result.push({ metric: { vmrange }, values, group });
prevBucket = bucket;
}
return result;
};
export const normalizeData = (buckets: MetricResult[], isHistogram?: boolean): MetricResult[] => {
if (!isHistogram) return buckets;
const vmBuckets = convertPrometheusToVictoriaMetrics(buckets);
const allValues = vmBuckets.map(b => b.values).flat();
return vmBuckets.map(bucket => {
const values = bucket.values.map((v) => {
const totalHits = allValues.filter(av => av[0] === v[0]).reduce((bucketSum, v) => bucketSum + +v[1], 0);
return [v[0], `${Math.round((+v[1] / totalHits) * 100)}`];
});
return { ...bucket, values };
}) as MetricResult[];
};

View file

@ -80,7 +80,7 @@ export const sizeAxis = (u: uPlot, values: string[], axisIdx: number, cycleNum:
let axisSize = 6 + (axis?.ticks?.size || 0) + (axis.gap || 0);
const longestVal = (values ?? []).reduce((acc, val) => val.length > acc.length ? val : acc, "");
const longestVal = (values ?? []).reduce((acc, val) => val?.length > acc.length ? val : acc, "");
if (longestVal != "") axisSize += getTextWidth(longestVal, "10px Arial");
return Math.ceil(axisSize);

View file

@ -51,3 +51,5 @@ export interface Fill {
unit: number,
values: (u: { data: number[][]; }) => string[],
}
export type ArrayRGB = [number, number, number]

View file

@ -25,6 +25,7 @@ created by v1.90.0 or newer versions. The solution is to upgrade to v1.90.0 or n
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): add support for [VictoriaMetrics remote write protocol](https://docs.victoriametrics.com/vmagent.html#victoriametrics-remote-write-protocol) when [sending / receiving data to / from Kafka](https://docs.victoriametrics.com/vmagent.html#kafka-integration). This protocol allows saving egress network bandwidth costs when sending data from `vmagent` to `Kafka` located in another datacenter or availability zone. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1225).
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): add `-kafka.consumer.topic.concurrency` command-line flag. It controls the number of Kafka consumer workers to use by `vmagent`. It should eliminate the need to start multiple `vmagent` instances to improve data transfer rate. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1957).
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): add support for [Kafka producer and consumer](https://docs.victoriametrics.com/vmagent.html#kafka-integration) on `arm64` machines. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2271).
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): automatically draw a heatmap graph when the query selects a single [histogram](https://docs.victoriametrics.com/keyConcepts.html#histogram). This simplifies analyzing histograms. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3384).
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add support for drag'n'drop and paste from clipboard in the "Trace analyzer" page. See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3971).
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): hide messages longer than 3 lines in the trace. You can view the full message by clicking on the `show more` button. See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3971).
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add the ability to manually input date and time when selecting a time range. See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3968).

View file

@ -615,7 +615,7 @@ This function is supported by PromQL.
on the given lookbehind window `d` and returns them in time series with `rollup="min"`, `rollup="max"` and `rollup="avg"` additional labels.
These values are calculated individually per each time series returned from the given [series_selector](https://docs.victoriametrics.com/keyConcepts.html#filtering).
Optional 2nd argument `min`, `max` or `avg` can be passed to keep only one calculation result and without adding a label.
Optional 2nd argument `"min"`, `"max"` or `"avg"` can be passed to keep only one calculation result and without adding a label.
#### rollup_candlestick
@ -624,7 +624,7 @@ over raw samples on the given lookbehind window `d` and returns them in time ser
The calculations are performed individually per each time series returned
from the given [series_selector](https://docs.victoriametrics.com/keyConcepts.html#filtering). This function is useful for financial applications.
Optional 2nd argument `min`, `max` or `avg` can be passed to keep only one calculation result and without adding a label.
Optional 2nd argument `"min"`, `"max"` or `"avg"` can be passed to keep only one calculation result and without adding a label.
#### rollup_delta
@ -633,7 +633,7 @@ on the given lookbehind window `d` and returns `min`, `max` and `avg` values for
and returns them in time series with `rollup="min"`, `rollup="max"` and `rollup="avg"` additional labels.
The calculations are performed individually per each time series returned from the given [series_selector](https://docs.victoriametrics.com/keyConcepts.html#filtering).
Optional 2nd argument `min`, `max` or `avg` can be passed to keep only one calculation result and without adding a label.
Optional 2nd argument `"min"`, `"max"` or `"avg"` can be passed to keep only one calculation result and without adding a label.
Metric names are stripped from the resulting rollups. Add [keep_metric_names](#keep_metric_names) modifier in order to keep metric names.
@ -646,7 +646,7 @@ for adjacent raw samples on the given lookbehind window `d` and returns `min`, `
and returns them in time series with `rollup="min"`, `rollup="max"` and `rollup="avg"` additional labels.
The calculations are performed individually per each time series returned from the given [series_selector](https://docs.victoriametrics.com/keyConcepts.html#filtering).
Optional 2nd argument `min`, `max` or `avg` can be passed to keep only one calculation result and without adding a label.
Optional 2nd argument `"min"`, `"max"` or `"avg"` can be passed to keep only one calculation result and without adding a label.
Metric names are stripped from the resulting rollups. Add [keep_metric_names](#keep_metric_names) modifier in order to keep metric names.
@ -657,7 +657,7 @@ on the given lookbehind window `d` and returns `min`, `max` and `avg` values for
and returns them in time series with `rollup="min"`, `rollup="max"` and `rollup="avg"` additional labels.
The calculations are performed individually per each time series returned from the given [series_selector](https://docs.victoriametrics.com/keyConcepts.html#filtering).
Optional 2nd argument `min`, `max` or `avg` can be passed to keep only one calculation result and without adding a label.
Optional 2nd argument `"min"`, `"max"` or `"avg"` can be passed to keep only one calculation result and without adding a label.
Metric names are stripped from the resulting rollups. Add [keep_metric_names](#keep_metric_names) modifier in order to keep metric names. See also [rollup_delta](#rollup_delta).
@ -670,7 +670,7 @@ and returns them in time series with `rollup="min"`, `rollup="max"` and `rollup=
See [this article](https://valyala.medium.com/why-irate-from-prometheus-doesnt-capture-spikes-45f9896d7832) in order to undertand better
when to use `rollup_rate()`.
Optional 2nd argument `min`, `max` or `avg` can be passed to keep only one calculation result and without adding a label.
Optional 2nd argument `"min"`, `"max"` or `"avg"` can be passed to keep only one calculation result and without adding a label.
The calculations are performed individually per each time series returned from the given [series_selector](https://docs.victoriametrics.com/keyConcepts.html#filtering).
@ -684,7 +684,7 @@ adjacent raw samples on the given lookbehind window `d` and returns `min`, `max`
and returns them in time series with `rollup="min"`, `rollup="max"` and `rollup="avg"` additional labels.
The calculations are performed individually per each time series returned from the given [series_selector](https://docs.victoriametrics.com/keyConcepts.html#filtering).
Optional 2nd argument `min`, `max` or `avg` can be passed to keep only one calculation result and without adding a label.
Optional 2nd argument `"min"`, `"max"` or `"avg"` can be passed to keep only one calculation result and without adding a label.
Metric names are stripped from the resulting rollups. Add [keep_metric_names](#keep_metric_names) modifier in order to keep metric names. See also [scrape_interval](#scrape_interval).

6
go.mod
View file

@ -32,7 +32,7 @@ require (
github.com/oklog/ulid v1.3.1
github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/prometheus v0.43.0
github.com/urfave/cli/v2 v2.25.0
github.com/urfave/cli/v2 v2.25.1
github.com/valyala/fastjson v1.6.4
github.com/valyala/fastrand v1.1.0
github.com/valyala/fasttemplate v1.2.2
@ -54,7 +54,7 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/internal v1.2.0 // indirect
github.com/VividCortex/ewma v1.2.0 // indirect
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
github.com/aws/aws-sdk-go v1.44.229 // indirect
github.com/aws/aws-sdk-go v1.44.230 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.13.18 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.1 // indirect
@ -113,7 +113,7 @@ require (
golang.org/x/time v0.3.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230323212658-478b75c54725 // indirect
google.golang.org/genproto v0.0.0-20230327215041-6ac7f18bb9d5 // indirect
google.golang.org/grpc v1.54.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

12
go.sum
View file

@ -86,8 +86,8 @@ github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu
github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA=
github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/aws/aws-sdk-go v1.44.229 h1:lku0ZSHRzj/qtFVM//QE8VjV6kvJ6CFijDZSsjNaD9A=
github.com/aws/aws-sdk-go v1.44.229/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go v1.44.230 h1:dcn7TjLyx/31I+0XytMGYRxDc756BRUzsSYVcSyKZlk=
github.com/aws/aws-sdk-go v1.44.230/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go-v2 v1.17.7 h1:CLSjnhJSTSogvqUGhIC6LqFKATMRexcxLZ0i/Nzk9Eg=
github.com/aws/aws-sdk-go-v2 v1.17.7/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 h1:dK82zF6kkPeCo8J1e+tGx4JdvDIQzj7ygIoLg8WMuGs=
@ -416,8 +416,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/urfave/cli/v2 v2.25.0 h1:ykdZKuQey2zq0yin/l7JOm9Mh+pg72ngYMeB0ABn6q8=
github.com/urfave/cli/v2 v2.25.0/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
github.com/urfave/cli/v2 v2.25.1 h1:zw8dSP7ghX0Gmm8vugrs6q9Ku0wzweqPyshy+syu9Gw=
github.com/urfave/cli/v2 v2.25.1/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus=
@ -731,8 +731,8 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20230323212658-478b75c54725 h1:VmCWItVXcKboEMCwZaWge+1JLiTCQSngZeINF+wzO+g=
google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=
google.golang.org/genproto v0.0.0-20230327215041-6ac7f18bb9d5 h1:Kd6tRRHXw8z4TlPlWi+NaK10gsePL6GdZBQChptOLGA=
google.golang.org/genproto v0.0.0-20230327215041-6ac7f18bb9d5/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=

View file

@ -5,4 +5,4 @@ package aws
const SDKName = "aws-sdk-go"
// SDKVersion is the version of this SDK
const SDKVersion = "1.44.229"
const SDKVersion = "1.44.230"

View file

@ -135,6 +135,7 @@ func (c *Command) setup(ctx *Context) {
if scmd.HelpName == "" {
scmd.HelpName = fmt.Sprintf("%s %s", c.HelpName, scmd.Name)
}
scmd.separator = c.separator
newCmds = append(newCmds, scmd)
}
c.Subcommands = newCmds

6
vendor/modules.txt vendored
View file

@ -81,7 +81,7 @@ github.com/VividCortex/ewma
# github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137
## explicit; go 1.15
github.com/alecthomas/units
# github.com/aws/aws-sdk-go v1.44.229
# github.com/aws/aws-sdk-go v1.44.230
## explicit; go 1.11
github.com/aws/aws-sdk-go/aws
github.com/aws/aws-sdk-go/aws/awserr
@ -448,7 +448,7 @@ github.com/russross/blackfriday/v2
## explicit; go 1.13
github.com/stretchr/testify/assert
github.com/stretchr/testify/require
# github.com/urfave/cli/v2 v2.25.0
# github.com/urfave/cli/v2 v2.25.1
## explicit; go 1.18
github.com/urfave/cli/v2
# github.com/valyala/bytebufferpool v1.0.0
@ -607,7 +607,7 @@ google.golang.org/appengine/internal/socket
google.golang.org/appengine/internal/urlfetch
google.golang.org/appengine/socket
google.golang.org/appengine/urlfetch
# google.golang.org/genproto v0.0.0-20230323212658-478b75c54725
# google.golang.org/genproto v0.0.0-20230327215041-6ac7f18bb9d5
## explicit; go 1.19
google.golang.org/genproto/googleapis/api
google.golang.org/genproto/googleapis/api/annotations