Merge branch 'refs/heads/master' into vmui/logs/add-autocomplete

# Conflicts:
#	app/vmui/packages/vmui/src/components/Configurators/QueryEditor/QueryEditorAutocomplete.tsx
#	docs/VictoriaLogs/CHANGELOG.md
This commit is contained in:
Yury Molodov 2024-09-27 13:29:48 +02:00
commit b4fb8ea604
No known key found for this signature in database
GPG key ID: 79AD43149C47BDE7
1000 changed files with 73879 additions and 22860 deletions

View file

@ -467,9 +467,9 @@ benchmark-pure:
CGO_ENABLED=0 go test -bench=. ./app/...
vendor-update:
go get -u -d ./lib/...
go get -u -d ./app/...
go mod tidy -compat=1.22
go get -u ./lib/...
go get -u ./app/...
go mod tidy -compat=1.23
go mod vendor
app-local:

View file

@ -95,30 +95,31 @@ If you like VictoriaMetrics and want to contribute, then please [read these docs
## VictoriaMetrics Logo
[Zip](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/VM_logo.zip) contains three folders with different image orientations (main color and inverted version).
The provided [ZIP file](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/VM_logo.zip) contains three folders with different logo orientations. Each folder includes the following file types:
Files included in each folder:
* JPEG: Preview files
* PNG: Preview files with transparent background
* AI: Adobe Illustrator files
* 2 JPEG Preview files
* 2 PNG Preview files with transparent background
* 2 EPS Adobe Illustrator EPS10 files
### VictoriaMetrics Logo Usage Guidelines
### Logo Usage Guidelines
#### Font
#### Font used
* Lato Black
* Lato Regular
* Font Used: Lato Black
* Download here: [Lato Font](https://fonts.google.com/specimen/Lato)
#### Color Palette
* HEX [#110f0f](https://www.color-hex.com/color/110f0f)
* HEX [#ffffff](https://www.color-hex.com/color/ffffff)
* Black [#000000](https://www.color-hex.com/color/000000)
* Purple [#4d0e82](https://www.color-hex.com/color/4d0e82)
* Orange [#ff2e00](https://www.color-hex.com/color/ff2e00)
* White [#ffffff](https://www.color-hex.com/color/ffffff)
### We kindly ask
### Logo Usage Rules
* Please don't use any other font instead of suggested.
* To keep enough clear space around the logo.
* Do not change spacing, alignment, or relative locations of the design elements.
* Do not change the proportions for any of the design elements or the design itself.
You may resize as needed but must retain all proportions.
* Only use the Lato Black font as specified.
* Maintain sufficient clear space around the logo for visibility.
* Do not modify the spacing, alignment, or positioning of design elements.
* You may resize the logo as needed, but ensure all proportions remain intact.
Thank you for your cooperation!

Binary file not shown.

View file

@ -8,5 +8,5 @@ FROM $root_image
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
EXPOSE 9428
ENTRYPOINT ["/victoria-logs-prod"]
ARG TARGETARCH=non-existing
ARG TARGETARCH
COPY victoria-logs-linux-${TARGETARCH}-prod ./victoria-logs-prod

View file

@ -8,5 +8,5 @@ FROM $root_image
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
EXPOSE 8428
ENTRYPOINT ["/victoria-metrics-prod"]
ARG TARGETARCH=non-existing
ARG TARGETARCH
COPY victoria-metrics-linux-${TARGETARCH}-prod ./victoria-metrics-prod

View file

@ -76,14 +76,14 @@ func TestReadBulkRequest_Success(t *testing.T) {
data := `{"create":{"_index":"filebeat-8.8.0"}}
{"@timestamp":"2023-06-06T04:48:11.735Z","log":{"offset":71770,"file":{"path":"/var/log/auth.log"}},"message":"foobar"}
{"create":{"_index":"filebeat-8.8.0"}}
{"@timestamp":"2023-06-06T04:48:12.735Z","message":"baz"}
{"@timestamp":"2023-06-06 04:48:12.735+01:00","message":"baz"}
{"index":{"_index":"filebeat-8.8.0"}}
{"message":"xyz","@timestamp":"2023-06-06T04:48:13.735Z","x":"y"}
`
timeField := "@timestamp"
msgField := "message"
rowsExpected := 3
timestampsExpected := []int64{1686026891735000000, 1686026892735000000, 1686026893735000000}
timestampsExpected := []int64{1686026891735000000, 1686023292735000000, 1686026893735000000}
resultExpected := `{"@timestamp":"","log.offset":"71770","log.file.path":"/var/log/auth.log","_msg":"foobar"}
{"@timestamp":"","_msg":"baz"}
{"_msg":"xyz","@timestamp":"","x":"y"}`

View file

@ -176,6 +176,22 @@ func (lmp *logMessageProcessor) AddRow(timestamp int64, fields []logstorage.Fiel
return
}
// _msg field must be non-empty according to VictoriaLogs data model.
// See https://docs.victoriametrics.com/victorialogs/keyconcepts/#message-field
msgExist := false
for i := range fields {
if fields[i].Name == "_msg" {
msgExist = len(fields[i].Value) > 0
break
}
}
if !msgExist {
rf := logstorage.RowFormatter(fields)
logger.Warnf("dropping log line without _msg field; %s", rf)
rowsDroppedTotalMsgNotValid.Inc()
return
}
lmp.lr.MustAdd(lmp.cp.TenantID, timestamp, fields)
if lmp.cp.Debug {
s := lmp.lr.GetRowString(0)
@ -225,4 +241,5 @@ func (cp *CommonParams) NewLogMessageProcessor() LogMessageProcessor {
var (
rowsDroppedTotalDebug = metrics.NewCounter(`vl_rows_dropped_total{reason="debug"}`)
rowsDroppedTotalTooManyFields = metrics.NewCounter(`vl_rows_dropped_total{reason="too_many_fields"}`)
rowsDroppedTotalMsgNotValid = metrics.NewCounter(`vl_rows_dropped_total{reason="msg_not_exist"}`)
)

View file

@ -66,9 +66,6 @@ func TestExtractTimestampRFC3339NanoFromFields_Error(t *testing.T) {
f("foobar")
// no Z at the end
f("2024-06-18T23:37:20")
// incomplete time
f("2024-06-18")
f("2024-06-18T23:37")

View file

@ -23,13 +23,13 @@ func TestProcessStreamInternal_Success(t *testing.T) {
}
data := `{"@timestamp":"2023-06-06T04:48:11.735Z","log":{"offset":71770,"file":{"path":"/var/log/auth.log"}},"message":"foobar"}
{"@timestamp":"2023-06-06T04:48:12.735Z","message":"baz"}
{"message":"xyz","@timestamp":"2023-06-06T04:48:13.735Z","x":"y"}
{"@timestamp":"2023-06-06T04:48:12.735+01:00","message":"baz"}
{"message":"xyz","@timestamp":"2023-06-06 04:48:13.735Z","x":"y"}
`
timeField := "@timestamp"
msgField := "message"
rowsExpected := 3
timestampsExpected := []int64{1686026891735000000, 1686026892735000000, 1686026893735000000}
timestampsExpected := []int64{1686026891735000000, 1686023292735000000, 1686026893735000000}
resultExpected := `{"@timestamp":"","log.offset":"71770","log.file.path":"/var/log/auth.log","_msg":"foobar"}
{"@timestamp":"","_msg":"baz"}
{"_msg":"xyz","@timestamp":"","x":"y"}`

View file

@ -5,6 +5,7 @@ import (
"fmt"
"math"
"net/http"
"slices"
"sort"
"strconv"
"strings"
@ -44,6 +45,7 @@ func ProcessHitsRequest(ctx context.Context, w http.ResponseWriter, r *http.Requ
}
if step <= 0 {
httpserver.Errorf(w, r, "'step' must be bigger than zero")
return
}
// Obtain offset
@ -380,6 +382,8 @@ func ProcessStreamsRequest(ctx context.Context, w http.ResponseWriter, r *http.R
}
// ProcessLiveTailRequest processes live tailing request to /select/logsq/tail
//
// See https://docs.victoriametrics.com/victorialogs/querying/#live-tailing
func ProcessLiveTailRequest(ctx context.Context, w http.ResponseWriter, r *http.Request) {
liveTailRequests.Inc()
defer liveTailRequests.Dec()
@ -413,13 +417,17 @@ func ProcessLiveTailRequest(ctx context.Context, w http.ResponseWriter, r *http.
if !ok {
logger.Panicf("BUG: it is expected that http.ResponseWriter (%T) supports http.Flusher interface", w)
}
qOrig := q
for {
start := end - tailOffsetNsecs
end = time.Now().UnixNano()
qCopy := q.Clone()
qCopy.AddTimeFilter(start, end)
if err := vlstorage.RunQuery(ctxWithCancel, tenantIDs, qCopy, tp.writeBlock); err != nil {
q = qOrig.Clone(end)
q.AddTimeFilter(start, end)
// q.Optimize() call is needed for converting '*' into filterNoop.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6785#issuecomment-2358547733
q.Optimize()
if err := vlstorage.RunQuery(ctxWithCancel, tenantIDs, q, tp.writeBlock); err != nil {
httpserver.Errorf(w, r, "cannot execute tail query [%s]: %s", q, err)
return
}
@ -560,9 +568,212 @@ func (tp *tailProcessor) getTailRows() ([][]logstorage.Field, error) {
return tailRows, nil
}
// ProcessStatsQueryRangeRequest handles /select/logsql/stats_query_range request.
//
// See https://docs.victoriametrics.com/victorialogs/querying/#querying-log-range-stats
func ProcessStatsQueryRangeRequest(ctx context.Context, w http.ResponseWriter, r *http.Request) {
q, tenantIDs, err := parseCommonArgs(r)
if err != nil {
httpserver.SendPrometheusError(w, r, err)
return
}
// Obtain step
stepStr := r.FormValue("step")
if stepStr == "" {
stepStr = "1d"
}
step, err := promutils.ParseDuration(stepStr)
if err != nil {
err = fmt.Errorf("cannot parse 'step' arg: %s", err)
httpserver.SendPrometheusError(w, r, err)
return
}
if step <= 0 {
err := fmt.Errorf("'step' must be bigger than zero")
httpserver.SendPrometheusError(w, r, err)
return
}
// Obtain `by(...)` fields from the last `| stats` pipe in q.
// Add `_time:step` to the `by(...)` list.
byFields, err := q.GetStatsByFieldsAddGroupingByTime(int64(step))
if err != nil {
httpserver.SendPrometheusError(w, r, err)
return
}
q.Optimize()
m := make(map[string]*statsSeries)
var mLock sync.Mutex
writeBlock := func(_ uint, timestamps []int64, columns []logstorage.BlockColumn) {
clonedColumnNames := make([]string, len(columns))
for i, c := range columns {
clonedColumnNames[i] = strings.Clone(c.Name)
}
for i := range timestamps {
timestamp := q.GetTimestamp()
labels := make([]logstorage.Field, 0, len(byFields))
for j, c := range columns {
if c.Name == "_time" {
nsec, ok := logstorage.TryParseTimestampRFC3339Nano(c.Values[i])
if ok {
timestamp = nsec
continue
}
}
if slices.Contains(byFields, c.Name) {
labels = append(labels, logstorage.Field{
Name: clonedColumnNames[j],
Value: strings.Clone(c.Values[i]),
})
}
}
var dst []byte
for j, c := range columns {
if !slices.Contains(byFields, c.Name) {
name := clonedColumnNames[j]
dst = dst[:0]
dst = append(dst, name...)
dst = logstorage.MarshalFieldsToJSON(dst, labels)
key := string(dst)
p := statsPoint{
Timestamp: timestamp,
Value: strings.Clone(c.Values[i]),
}
mLock.Lock()
ss := m[key]
if ss == nil {
ss = &statsSeries{
key: key,
Name: name,
Labels: labels,
}
m[key] = ss
}
ss.Points = append(ss.Points, p)
mLock.Unlock()
}
}
}
}
if err := vlstorage.RunQuery(ctx, tenantIDs, q, writeBlock); err != nil {
err = fmt.Errorf("cannot execute query [%s]: %s", q, err)
httpserver.SendPrometheusError(w, r, err)
return
}
// Sort the collected stats by time
rows := make([]*statsSeries, 0, len(m))
for _, ss := range m {
points := ss.Points
sort.Slice(points, func(i, j int) bool {
return points[i].Timestamp < points[j].Timestamp
})
rows = append(rows, ss)
}
sort.Slice(rows, func(i, j int) bool {
return rows[i].key < rows[j].key
})
w.Header().Set("Content-Type", "application/json")
WriteStatsQueryRangeResponse(w, rows)
}
type statsSeries struct {
key string
Name string
Labels []logstorage.Field
Points []statsPoint
}
type statsPoint struct {
Timestamp int64
Value string
}
// ProcessStatsQueryRequest handles /select/logsql/stats_query request.
//
// See https://docs.victoriametrics.com/victorialogs/querying/#querying-log-stats
func ProcessStatsQueryRequest(ctx context.Context, w http.ResponseWriter, r *http.Request) {
q, tenantIDs, err := parseCommonArgs(r)
if err != nil {
httpserver.SendPrometheusError(w, r, err)
return
}
// Obtain `by(...)` fields from the last `| stats` pipe in q.
byFields, err := q.GetStatsByFields()
if err != nil {
httpserver.SendPrometheusError(w, r, err)
return
}
q.Optimize()
var rows []statsRow
var rowsLock sync.Mutex
timestamp := q.GetTimestamp()
writeBlock := func(_ uint, timestamps []int64, columns []logstorage.BlockColumn) {
clonedColumnNames := make([]string, len(columns))
for i, c := range columns {
clonedColumnNames[i] = strings.Clone(c.Name)
}
for i := range timestamps {
labels := make([]logstorage.Field, 0, len(byFields))
for j, c := range columns {
if slices.Contains(byFields, c.Name) {
labels = append(labels, logstorage.Field{
Name: clonedColumnNames[j],
Value: strings.Clone(c.Values[i]),
})
}
}
for j, c := range columns {
if !slices.Contains(byFields, c.Name) {
r := statsRow{
Name: clonedColumnNames[j],
Labels: labels,
Timestamp: timestamp,
Value: strings.Clone(c.Values[i]),
}
rowsLock.Lock()
rows = append(rows, r)
rowsLock.Unlock()
}
}
}
}
if err := vlstorage.RunQuery(ctx, tenantIDs, q, writeBlock); err != nil {
err = fmt.Errorf("cannot execute query [%s]: %s", q, err)
httpserver.SendPrometheusError(w, r, err)
return
}
w.Header().Set("Content-Type", "application/json")
WriteStatsQueryResponse(w, rows)
}
type statsRow struct {
Name string
Labels []logstorage.Field
Timestamp int64
Value string
}
// ProcessQueryRequest handles /select/logsql/query request.
//
// See https://docs.victoriametrics.com/victorialogs/querying/#http-api
// See https://docs.victoriametrics.com/victorialogs/querying/#querying-logs
func ProcessQueryRequest(ctx context.Context, w http.ResponseWriter, r *http.Request) {
q, tenantIDs, err := parseCommonArgs(r)
if err != nil {
@ -637,6 +848,7 @@ func getLastNQueryResults(ctx context.Context, tenantIDs []logstorage.TenantID,
limitUpper := 2 * limit
q.AddPipeLimit(uint64(limitUpper))
q.Optimize()
rows, err := getQueryResultsWithLimit(ctx, tenantIDs, q, limitUpper)
if err != nil {
return nil, err
@ -647,32 +859,62 @@ func getLastNQueryResults(ctx context.Context, tenantIDs []logstorage.TenantID,
return rows, nil
}
// Slow path - search for the time range containing up to limitUpper rows.
// Slow path - adjust time range for selecting up to limitUpper rows
start, end := q.GetFilterTimeRange()
d := end/2 - start/2
start += d
qOrig := q
for {
q = qOrig.Clone()
timestamp := qOrig.GetTimestamp()
q = qOrig.Clone(timestamp)
q.AddTimeFilter(start, end)
// q.Optimize() call is needed for converting '*' into filterNoop.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6785#issuecomment-2358547733
q.Optimize()
rows, err := getQueryResultsWithLimit(ctx, tenantIDs, q, limitUpper)
if err != nil {
return nil, err
}
if len(rows) >= limit && len(rows) < limitUpper || d == 0 {
if d == 0 || start >= end {
// The [start ... end] time range equals one nanosecond.
// Just return up to limit rows.
if len(rows) > limit {
rows = rows[:limit]
}
return rows, nil
}
dLastBit := d & 1
d /= 2
if len(rows) >= limitUpper {
// The number of found rows on the [start ... end] time range exceeds limitUpper,
// so reduce the time range to [start+d ... end].
start += d
continue
}
if len(rows) >= limit {
// The number of found rows is in the range [limit ... limitUpper).
// This means that found rows contains the needed limit rows with the biggest timestamps.
rows = getLastNRows(rows, limit)
return rows, nil
}
lastBit := d & 1
d /= 2
if len(rows) > limit {
start += d
} else {
start -= d + lastBit
// The number of found rows on [start ... end] time range is below the limit.
// This means the time range doesn't cover the needed logs, so it must be extended.
if len(rows) == 0 {
// The [start ... end] time range doesn't contain any rows, so change it to [start-d ... start).
end = start - 1
start -= d + dLastBit
continue
}
// The number of found rows on [start ... end] time range is bigger than 0 but smaller than limit.
// Increase the time range to [start-d ... end].
start -= d + dLastBit
}
}
@ -693,20 +935,25 @@ func getQueryResultsWithLimit(ctx context.Context, tenantIDs []logstorage.Tenant
var rows []row
var rowsLock sync.Mutex
writeBlock := func(_ uint, timestamps []int64, columns []logstorage.BlockColumn) {
rowsLock.Lock()
defer rowsLock.Unlock()
clonedColumnNames := make([]string, len(columns))
for i, c := range columns {
clonedColumnNames[i] = strings.Clone(c.Name)
}
for i, timestamp := range timestamps {
fields := make([]logstorage.Field, len(columns))
for j := range columns {
f := &fields[j]
f.Name = strings.Clone(columns[j].Name)
f.Name = clonedColumnNames[j]
f.Value = strings.Clone(columns[j].Values[i])
}
rowsLock.Lock()
rows = append(rows, row{
timestamp: timestamp,
fields: fields,
})
rowsLock.Unlock()
}
if len(rows) >= limit {
@ -728,9 +975,23 @@ func parseCommonArgs(r *http.Request) (*logstorage.Query, []logstorage.TenantID,
}
tenantIDs := []logstorage.TenantID{tenantID}
// Parse optional time arg
timestamp, okTime, err := getTimeNsec(r, "time")
if err != nil {
return nil, nil, err
}
if !okTime {
// If time arg is missing, then evaluate query at the current timestamp
timestamp = time.Now().UnixNano()
}
// decrease timestamp by one nanosecond in order to avoid capturing logs belonging
// to the first nanosecond at the next period of time (month, week, day, hour, etc.)
timestamp--
// Parse query
qStr := r.FormValue("query")
q, err := logstorage.ParseQuery(qStr)
q, err := logstorage.ParseQueryAtTimestamp(qStr, timestamp)
if err != nil {
return nil, nil, fmt.Errorf("cannot parse query [%s]: %s", qStr, err)
}

View file

@ -0,0 +1,52 @@
{% stripspace %}
// StatsQueryRangeResponse generates response for /select/logsql/stats_query_range
{% func StatsQueryRangeResponse(rows []*statsSeries) %}
{
"status":"success",
"data":{
"resultType":"matrix",
"result":[
{% if len(rows) > 0 %}
{%= formatStatsSeries(rows[0]) %}
{% code rows = rows[1:] %}
{% for i := range rows %}
,{%= formatStatsSeries(rows[i]) %}
{% endfor %}
{% endif %}
]
}
}
{% endfunc %}
{% func formatStatsSeries(ss *statsSeries) %}
{
"metric":{
"__name__":{%q= ss.Name %}
{% if len(ss.Labels) > 0 %}
{% for _, label := range ss.Labels %}
,{%q= label.Name %}:{%q= label.Value %}
{% endfor %}
{% endif %}
},
"values":[
{% code points := ss.Points %}
{% if len(points) > 0 %}
{%= formatStatsPoint(&points[0]) %}
{% code points = points[1:] %}
{% for i := range points %}
,{%= formatStatsPoint(&points[i]) %}
{% endfor %}
{% endif %}
]
}
{% endfunc %}
{% func formatStatsPoint(p *statsPoint) %}
[
{%f= float64(p.Timestamp)/1e9 %},
{%q= p.Value %}
]
{% endfunc %}
{% endstripspace %}

View file

@ -0,0 +1,188 @@
// Code generated by qtc from "stats_query_range_response.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.
// StatsQueryRangeResponse generates response for /select/logsql/stats_query_range
//line app/vlselect/logsql/stats_query_range_response.qtpl:4
package logsql
//line app/vlselect/logsql/stats_query_range_response.qtpl:4
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line app/vlselect/logsql/stats_query_range_response.qtpl:4
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line app/vlselect/logsql/stats_query_range_response.qtpl:4
func StreamStatsQueryRangeResponse(qw422016 *qt422016.Writer, rows []*statsSeries) {
//line app/vlselect/logsql/stats_query_range_response.qtpl:4
qw422016.N().S(`{"status":"success","data":{"resultType":"matrix","result":[`)
//line app/vlselect/logsql/stats_query_range_response.qtpl:10
if len(rows) > 0 {
//line app/vlselect/logsql/stats_query_range_response.qtpl:11
streamformatStatsSeries(qw422016, rows[0])
//line app/vlselect/logsql/stats_query_range_response.qtpl:12
rows = rows[1:]
//line app/vlselect/logsql/stats_query_range_response.qtpl:13
for i := range rows {
//line app/vlselect/logsql/stats_query_range_response.qtpl:13
qw422016.N().S(`,`)
//line app/vlselect/logsql/stats_query_range_response.qtpl:14
streamformatStatsSeries(qw422016, rows[i])
//line app/vlselect/logsql/stats_query_range_response.qtpl:15
}
//line app/vlselect/logsql/stats_query_range_response.qtpl:16
}
//line app/vlselect/logsql/stats_query_range_response.qtpl:16
qw422016.N().S(`]}}`)
//line app/vlselect/logsql/stats_query_range_response.qtpl:20
}
//line app/vlselect/logsql/stats_query_range_response.qtpl:20
func WriteStatsQueryRangeResponse(qq422016 qtio422016.Writer, rows []*statsSeries) {
//line app/vlselect/logsql/stats_query_range_response.qtpl:20
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vlselect/logsql/stats_query_range_response.qtpl:20
StreamStatsQueryRangeResponse(qw422016, rows)
//line app/vlselect/logsql/stats_query_range_response.qtpl:20
qt422016.ReleaseWriter(qw422016)
//line app/vlselect/logsql/stats_query_range_response.qtpl:20
}
//line app/vlselect/logsql/stats_query_range_response.qtpl:20
func StatsQueryRangeResponse(rows []*statsSeries) string {
//line app/vlselect/logsql/stats_query_range_response.qtpl:20
qb422016 := qt422016.AcquireByteBuffer()
//line app/vlselect/logsql/stats_query_range_response.qtpl:20
WriteStatsQueryRangeResponse(qb422016, rows)
//line app/vlselect/logsql/stats_query_range_response.qtpl:20
qs422016 := string(qb422016.B)
//line app/vlselect/logsql/stats_query_range_response.qtpl:20
qt422016.ReleaseByteBuffer(qb422016)
//line app/vlselect/logsql/stats_query_range_response.qtpl:20
return qs422016
//line app/vlselect/logsql/stats_query_range_response.qtpl:20
}
//line app/vlselect/logsql/stats_query_range_response.qtpl:22
func streamformatStatsSeries(qw422016 *qt422016.Writer, ss *statsSeries) {
//line app/vlselect/logsql/stats_query_range_response.qtpl:22
qw422016.N().S(`{"metric":{"__name__":`)
//line app/vlselect/logsql/stats_query_range_response.qtpl:25
qw422016.N().Q(ss.Name)
//line app/vlselect/logsql/stats_query_range_response.qtpl:26
if len(ss.Labels) > 0 {
//line app/vlselect/logsql/stats_query_range_response.qtpl:27
for _, label := range ss.Labels {
//line app/vlselect/logsql/stats_query_range_response.qtpl:27
qw422016.N().S(`,`)
//line app/vlselect/logsql/stats_query_range_response.qtpl:28
qw422016.N().Q(label.Name)
//line app/vlselect/logsql/stats_query_range_response.qtpl:28
qw422016.N().S(`:`)
//line app/vlselect/logsql/stats_query_range_response.qtpl:28
qw422016.N().Q(label.Value)
//line app/vlselect/logsql/stats_query_range_response.qtpl:29
}
//line app/vlselect/logsql/stats_query_range_response.qtpl:30
}
//line app/vlselect/logsql/stats_query_range_response.qtpl:30
qw422016.N().S(`},"values":[`)
//line app/vlselect/logsql/stats_query_range_response.qtpl:33
points := ss.Points
//line app/vlselect/logsql/stats_query_range_response.qtpl:34
if len(points) > 0 {
//line app/vlselect/logsql/stats_query_range_response.qtpl:35
streamformatStatsPoint(qw422016, &points[0])
//line app/vlselect/logsql/stats_query_range_response.qtpl:36
points = points[1:]
//line app/vlselect/logsql/stats_query_range_response.qtpl:37
for i := range points {
//line app/vlselect/logsql/stats_query_range_response.qtpl:37
qw422016.N().S(`,`)
//line app/vlselect/logsql/stats_query_range_response.qtpl:38
streamformatStatsPoint(qw422016, &points[i])
//line app/vlselect/logsql/stats_query_range_response.qtpl:39
}
//line app/vlselect/logsql/stats_query_range_response.qtpl:40
}
//line app/vlselect/logsql/stats_query_range_response.qtpl:40
qw422016.N().S(`]}`)
//line app/vlselect/logsql/stats_query_range_response.qtpl:43
}
//line app/vlselect/logsql/stats_query_range_response.qtpl:43
func writeformatStatsSeries(qq422016 qtio422016.Writer, ss *statsSeries) {
//line app/vlselect/logsql/stats_query_range_response.qtpl:43
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vlselect/logsql/stats_query_range_response.qtpl:43
streamformatStatsSeries(qw422016, ss)
//line app/vlselect/logsql/stats_query_range_response.qtpl:43
qt422016.ReleaseWriter(qw422016)
//line app/vlselect/logsql/stats_query_range_response.qtpl:43
}
//line app/vlselect/logsql/stats_query_range_response.qtpl:43
func formatStatsSeries(ss *statsSeries) string {
//line app/vlselect/logsql/stats_query_range_response.qtpl:43
qb422016 := qt422016.AcquireByteBuffer()
//line app/vlselect/logsql/stats_query_range_response.qtpl:43
writeformatStatsSeries(qb422016, ss)
//line app/vlselect/logsql/stats_query_range_response.qtpl:43
qs422016 := string(qb422016.B)
//line app/vlselect/logsql/stats_query_range_response.qtpl:43
qt422016.ReleaseByteBuffer(qb422016)
//line app/vlselect/logsql/stats_query_range_response.qtpl:43
return qs422016
//line app/vlselect/logsql/stats_query_range_response.qtpl:43
}
//line app/vlselect/logsql/stats_query_range_response.qtpl:45
func streamformatStatsPoint(qw422016 *qt422016.Writer, p *statsPoint) {
//line app/vlselect/logsql/stats_query_range_response.qtpl:45
qw422016.N().S(`[`)
//line app/vlselect/logsql/stats_query_range_response.qtpl:47
qw422016.N().F(float64(p.Timestamp) / 1e9)
//line app/vlselect/logsql/stats_query_range_response.qtpl:47
qw422016.N().S(`,`)
//line app/vlselect/logsql/stats_query_range_response.qtpl:48
qw422016.N().Q(p.Value)
//line app/vlselect/logsql/stats_query_range_response.qtpl:48
qw422016.N().S(`]`)
//line app/vlselect/logsql/stats_query_range_response.qtpl:50
}
//line app/vlselect/logsql/stats_query_range_response.qtpl:50
func writeformatStatsPoint(qq422016 qtio422016.Writer, p *statsPoint) {
//line app/vlselect/logsql/stats_query_range_response.qtpl:50
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vlselect/logsql/stats_query_range_response.qtpl:50
streamformatStatsPoint(qw422016, p)
//line app/vlselect/logsql/stats_query_range_response.qtpl:50
qt422016.ReleaseWriter(qw422016)
//line app/vlselect/logsql/stats_query_range_response.qtpl:50
}
//line app/vlselect/logsql/stats_query_range_response.qtpl:50
func formatStatsPoint(p *statsPoint) string {
//line app/vlselect/logsql/stats_query_range_response.qtpl:50
qb422016 := qt422016.AcquireByteBuffer()
//line app/vlselect/logsql/stats_query_range_response.qtpl:50
writeformatStatsPoint(qb422016, p)
//line app/vlselect/logsql/stats_query_range_response.qtpl:50
qs422016 := string(qb422016.B)
//line app/vlselect/logsql/stats_query_range_response.qtpl:50
qt422016.ReleaseByteBuffer(qb422016)
//line app/vlselect/logsql/stats_query_range_response.qtpl:50
return qs422016
//line app/vlselect/logsql/stats_query_range_response.qtpl:50
}

View file

@ -0,0 +1,36 @@
{% stripspace %}
// StatsQueryResponse generates response for /select/logsql/stats_query
{% func StatsQueryResponse(rows []statsRow) %}
{
"status":"success",
"data":{
"resultType":"vector",
"result":[
{% if len(rows) > 0 %}
{%= formatStatsRow(&rows[0]) %}
{% code rows = rows[1:] %}
{% for i := range rows %}
,{%= formatStatsRow(&rows[i]) %}
{% endfor %}
{% endif %}
]
}
}
{% endfunc %}
{% func formatStatsRow(r *statsRow) %}
{
"metric":{
"__name__":{%q= r.Name %}
{% if len(r.Labels) > 0 %}
{% for _, label := range r.Labels %}
,{%q= label.Name %}:{%q= label.Value %}
{% endfor %}
{% endif %}
},
"value":[{%f= float64(r.Timestamp)/1e9 %},{%q= r.Value %}]
}
{% endfunc %}
{% endstripspace %}

View file

@ -0,0 +1,133 @@
// Code generated by qtc from "stats_query_response.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.
// StatsQueryResponse generates response for /select/logsql/stats_query
//line app/vlselect/logsql/stats_query_response.qtpl:4
package logsql
//line app/vlselect/logsql/stats_query_response.qtpl:4
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line app/vlselect/logsql/stats_query_response.qtpl:4
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line app/vlselect/logsql/stats_query_response.qtpl:4
func StreamStatsQueryResponse(qw422016 *qt422016.Writer, rows []statsRow) {
//line app/vlselect/logsql/stats_query_response.qtpl:4
qw422016.N().S(`{"status":"success","data":{"resultType":"vector","result":[`)
//line app/vlselect/logsql/stats_query_response.qtpl:10
if len(rows) > 0 {
//line app/vlselect/logsql/stats_query_response.qtpl:11
streamformatStatsRow(qw422016, &rows[0])
//line app/vlselect/logsql/stats_query_response.qtpl:12
rows = rows[1:]
//line app/vlselect/logsql/stats_query_response.qtpl:13
for i := range rows {
//line app/vlselect/logsql/stats_query_response.qtpl:13
qw422016.N().S(`,`)
//line app/vlselect/logsql/stats_query_response.qtpl:14
streamformatStatsRow(qw422016, &rows[i])
//line app/vlselect/logsql/stats_query_response.qtpl:15
}
//line app/vlselect/logsql/stats_query_response.qtpl:16
}
//line app/vlselect/logsql/stats_query_response.qtpl:16
qw422016.N().S(`]}}`)
//line app/vlselect/logsql/stats_query_response.qtpl:20
}
//line app/vlselect/logsql/stats_query_response.qtpl:20
func WriteStatsQueryResponse(qq422016 qtio422016.Writer, rows []statsRow) {
//line app/vlselect/logsql/stats_query_response.qtpl:20
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vlselect/logsql/stats_query_response.qtpl:20
StreamStatsQueryResponse(qw422016, rows)
//line app/vlselect/logsql/stats_query_response.qtpl:20
qt422016.ReleaseWriter(qw422016)
//line app/vlselect/logsql/stats_query_response.qtpl:20
}
//line app/vlselect/logsql/stats_query_response.qtpl:20
func StatsQueryResponse(rows []statsRow) string {
//line app/vlselect/logsql/stats_query_response.qtpl:20
qb422016 := qt422016.AcquireByteBuffer()
//line app/vlselect/logsql/stats_query_response.qtpl:20
WriteStatsQueryResponse(qb422016, rows)
//line app/vlselect/logsql/stats_query_response.qtpl:20
qs422016 := string(qb422016.B)
//line app/vlselect/logsql/stats_query_response.qtpl:20
qt422016.ReleaseByteBuffer(qb422016)
//line app/vlselect/logsql/stats_query_response.qtpl:20
return qs422016
//line app/vlselect/logsql/stats_query_response.qtpl:20
}
//line app/vlselect/logsql/stats_query_response.qtpl:22
func streamformatStatsRow(qw422016 *qt422016.Writer, r *statsRow) {
//line app/vlselect/logsql/stats_query_response.qtpl:22
qw422016.N().S(`{"metric":{"__name__":`)
//line app/vlselect/logsql/stats_query_response.qtpl:25
qw422016.N().Q(r.Name)
//line app/vlselect/logsql/stats_query_response.qtpl:26
if len(r.Labels) > 0 {
//line app/vlselect/logsql/stats_query_response.qtpl:27
for _, label := range r.Labels {
//line app/vlselect/logsql/stats_query_response.qtpl:27
qw422016.N().S(`,`)
//line app/vlselect/logsql/stats_query_response.qtpl:28
qw422016.N().Q(label.Name)
//line app/vlselect/logsql/stats_query_response.qtpl:28
qw422016.N().S(`:`)
//line app/vlselect/logsql/stats_query_response.qtpl:28
qw422016.N().Q(label.Value)
//line app/vlselect/logsql/stats_query_response.qtpl:29
}
//line app/vlselect/logsql/stats_query_response.qtpl:30
}
//line app/vlselect/logsql/stats_query_response.qtpl:30
qw422016.N().S(`},"value":[`)
//line app/vlselect/logsql/stats_query_response.qtpl:32
qw422016.N().F(float64(r.Timestamp) / 1e9)
//line app/vlselect/logsql/stats_query_response.qtpl:32
qw422016.N().S(`,`)
//line app/vlselect/logsql/stats_query_response.qtpl:32
qw422016.N().Q(r.Value)
//line app/vlselect/logsql/stats_query_response.qtpl:32
qw422016.N().S(`]}`)
//line app/vlselect/logsql/stats_query_response.qtpl:34
}
//line app/vlselect/logsql/stats_query_response.qtpl:34
func writeformatStatsRow(qq422016 qtio422016.Writer, r *statsRow) {
//line app/vlselect/logsql/stats_query_response.qtpl:34
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vlselect/logsql/stats_query_response.qtpl:34
streamformatStatsRow(qw422016, r)
//line app/vlselect/logsql/stats_query_response.qtpl:34
qt422016.ReleaseWriter(qw422016)
//line app/vlselect/logsql/stats_query_response.qtpl:34
}
//line app/vlselect/logsql/stats_query_response.qtpl:34
func formatStatsRow(r *statsRow) string {
//line app/vlselect/logsql/stats_query_response.qtpl:34
qb422016 := qt422016.AcquireByteBuffer()
//line app/vlselect/logsql/stats_query_response.qtpl:34
writeformatStatsRow(qb422016, r)
//line app/vlselect/logsql/stats_query_response.qtpl:34
qs422016 := string(qb422016.B)
//line app/vlselect/logsql/stats_query_response.qtpl:34
qt422016.ReleaseByteBuffer(qb422016)
//line app/vlselect/logsql/stats_query_response.qtpl:34
return qs422016
//line app/vlselect/logsql/stats_query_response.qtpl:34
}

View file

@ -193,6 +193,14 @@ func processSelectRequest(ctx context.Context, w http.ResponseWriter, r *http.Re
logsqlQueryRequests.Inc()
logsql.ProcessQueryRequest(ctx, w, r)
return true
case "/select/logsql/stats_query":
logsqlStatsQueryRequests.Inc()
logsql.ProcessStatsQueryRequest(ctx, w, r)
return true
case "/select/logsql/stats_query_range":
logsqlStatsQueryRangeRequests.Inc()
logsql.ProcessStatsQueryRangeRequest(ctx, w, r)
return true
case "/select/logsql/stream_field_names":
logsqlStreamFieldNamesRequests.Inc()
logsql.ProcessStreamFieldNamesRequest(ctx, w, r)
@ -232,6 +240,8 @@ var (
logsqlFieldValuesRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/logsql/field_values"}`)
logsqlHitsRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/logsql/hits"}`)
logsqlQueryRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/logsql/query"}`)
logsqlStatsQueryRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/logsql/stats_query"}`)
logsqlStatsQueryRangeRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/logsql/stats_query_range"}`)
logsqlStreamFieldNamesRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/logsql/stream_field_names"}`)
logsqlStreamFieldValuesRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/logsql/stream_field_values"}`)
logsqlStreamIDsRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/logsql/stream_ids"}`)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View file

@ -1,13 +1,13 @@
{
"files": {
"main.css": "./static/css/main.c9cc37dd.css",
"main.js": "./static/js/main.7ae2e2c4.js",
"static/js/685.bebe1265.chunk.js": "./static/js/685.bebe1265.chunk.js",
"static/media/MetricsQL.md": "./static/media/MetricsQL.8c2e588d62b87f90dbf0.md",
"main.js": "./static/js/main.867f457f.js",
"static/js/685.f772060c.chunk.js": "./static/js/685.f772060c.chunk.js",
"static/media/MetricsQL.md": "./static/media/MetricsQL.a00044c91d9781cf8557.md",
"index.html": "./index.html"
},
"entrypoints": [
"static/css/main.c9cc37dd.css",
"static/js/main.7ae2e2c4.js"
"static/js/main.867f457f.js"
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1 @@
<svg width="48" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M24.5475 0C10.3246.0265251 1.11379 3.06365 4.40623 6.10077c0 0 12.32997 11.23333 16.58217 14.84083.8131.6896 2.1728 1.1936 3.5191 1.2201h.1199c1.3463-.0265 2.706-.5305 3.5191-1.2201 4.2522-3.5942 16.5422-14.84083 16.5422-14.84083C48.0478 3.06365 38.8636.0265251 24.6674 0" fill="#020202"/><path d="M28.1579 27.0159c-.8131.6896-2.1728 1.1936-3.5191 1.2201h-.12c-1.3463-.0265-2.7059-.5305-3.519-1.2201-2.9725-2.5067-13.35639-11.87-17.26201-15.3979v5.4112c0 .5968.22661 1.3793.6265 1.7506C7.00358 21.1936 17.2675 30.5437 20.9731 33.6737c.8132.6896 2.1728 1.1936 3.5191 1.2201h.12c1.3463-.0265 2.7059-.5305 3.519-1.2201 3.679-3.13 13.9429-12.4536 16.6089-14.8939.4132-.3713.6265-1.1538.6265-1.7506V11.618c-3.9323 3.5411-14.3162 12.931-17.2354 15.3979h.0267Z" fill="#020202"/><path d="M28.1579 39.748c-.8131.6897-2.1728 1.1937-3.5191 1.2202h-.12c-1.3463-.0265-2.7059-.5305-3.519-1.2202-2.9725-2.4933-13.35639-11.8567-17.26201-15.3978v5.4111c0 .5969.22661 1.3793.6265 1.7507C7.00358 33.9258 17.2675 43.2759 20.9731 46.4058c.8132.6897 2.1728 1.1937 3.5191 1.2202h.12c1.3463-.0265 2.7059-.5305 3.519-1.2202 3.679-3.1299 13.9429-12.4535 16.6089-14.8938.4132-.3714.6265-1.1538.6265-1.7507v-5.4111c-3.9323 3.5411-14.3162 12.931-17.2354 15.3978h.0267Z" fill="#020202"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

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=5"/><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><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.7ae2e2c4.js"></script><link href="./static/css/main.c9cc37dd.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.svg"/><link rel="apple-touch-icon" href="./favicon.svg"/><link rel="mask-icon" href="./favicon.svg" color="#000000"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=5"/><meta name="theme-color" content="#000000"/><meta name="description" content="Explore your log data with VictoriaLogs UI"/><link rel="manifest" href="./manifest.json"/><title>UI for VictoriaLogs</title><meta name="twitter:card" content="summary"><meta name="twitter:title" content="UI for VictoriaLogs"><meta name="twitter:site" content="@https://victoriametrics.com/products/victorialogs/"><meta name="twitter:description" content="Explore your log data with VictoriaLogs UI"><meta name="twitter:image" content="./preview.jpg"><meta property="og:type" content="website"><meta property="og:title" content="UI for VictoriaLogs"><meta property="og:url" content="https://victoriametrics.com/products/victorialogs/"><meta property="og:description" content="Explore your log data with VictoriaLogs UI"><script defer="defer" src="./static/js/main.867f457f.js"></script><link href="./static/css/main.c9cc37dd.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

View file

@ -1,16 +1,11 @@
{
"short_name": "Victoria Metrics UI",
"name": "Victoria Metrics UI is a metric explorer for Victoria Metrics",
"short_name": "vmui",
"name": "vmui",
"icons": [
{
"src": "favicon-32x32.png",
"sizes": "32x32",
"type": "image/png"
},
{
"src": "apple-touch-icon.png",
"type": "image/png",
"sizes": "192x192"
"src": "favicon.svg",
"sizes": "any",
"type": "image/svg+xml"
}
],
"start_url": ".",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

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

@ -5,7 +5,7 @@
*/
/**
* @remix-run/router v1.17.0
* @remix-run/router v1.19.2
*
* Copyright (c) Remix Software Inc.
*
@ -16,7 +16,7 @@
*/
/**
* React Router DOM v6.24.0
* React Router DOM v6.26.2
*
* Copyright (c) Remix Software Inc.
*
@ -27,7 +27,7 @@
*/
/**
* React Router v6.24.0
* React Router v6.26.2
*
* Copyright (c) Remix Software Inc.
*

View file

@ -103,7 +103,7 @@ The list of MetricsQL features on top of PromQL:
* Trailing commas on all the lists are allowed - label filters, function args and with expressions.
For instance, the following queries are valid: `m{foo="bar",}`, `f(a, b,)`, `WITH (x=y,) x`.
This simplifies maintenance of multi-line queries.
* Metric names and label names may contain any unicode letter. For example `температура{город="Київ"}` is a valid MetricsQL expression.
* Metric names and label names may contain any unicode letter. For example `ტემპერატურა{πόλη="Київ"}` is a valid MetricsQL expression.
* Metric names and labels names may contain escaped chars. For example, `foo\-bar{baz\=aa="b"}` is valid expression.
It returns time series with name `foo-bar` containing label `baz=aa` with value `b`.
Additionally, the following escape sequences are supported:

View file

@ -1,56 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<link rel="icon" href="%PUBLIC_URL%/favicon.ico"/>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5"/>
<meta name="theme-color" content="#000000"/>
<meta
name="description"
content="UI for VictoriaMetrics"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/apple-touch-icon.png"/>
<link rel="icon" type="image/png" sizes="32x32" href="%PUBLIC_URL%/favicon-32x32.png">
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json"/>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>VM UI</title>
<script src="%PUBLIC_URL%/dashboards/index.js" type="module"></script>
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="%PUBLIC_URL%/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="%PUBLIC_URL%/preview.jpg">
<meta property="og:type" content="website">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View file

@ -1,56 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<link rel="icon" href="%PUBLIC_URL%/favicon.ico"/>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5"/>
<meta name="theme-color" content="#000000"/>
<meta
name="description"
content="UI for VictoriaMetrics"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/apple-touch-icon.png"/>
<link rel="icon" type="image/png" sizes="32x32" href="%PUBLIC_URL%/favicon-32x32.png">
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json"/>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>VM UI</title>
<script src="%PUBLIC_URL%/dashboards/index.js" type="module"></script>
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="%PUBLIC_URL%/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="%PUBLIC_URL%/preview.jpg">
<meta property="og:type" content="website">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View file

@ -8,5 +8,5 @@ FROM $root_image
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
EXPOSE 8429
ENTRYPOINT ["/vmagent-prod"]
ARG TARGETARCH=non-existing
ARG TARGETARCH
COPY vmagent-linux-${TARGETARCH}-prod ./vmagent-prod

View file

@ -7,12 +7,11 @@ import (
"io"
"net/http"
"net/url"
"strconv"
"strings"
"sync"
"time"
"github.com/VictoriaMetrics/metrics"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/awsapi"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
@ -23,6 +22,7 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/ratelimiter"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timerpool"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timeutil"
"github.com/VictoriaMetrics/metrics"
)
var (
@ -463,10 +463,10 @@ again:
// Unexpected status code returned
retriesCount++
retryDuration *= 2
if retryDuration > maxRetryDuration {
retryDuration = maxRetryDuration
}
retryAfterHeader := parseRetryAfterHeader(resp.Header.Get("Retry-After"))
retryDuration = getRetryDuration(retryAfterHeader, retryDuration, maxRetryDuration)
// Handle response
body, err := io.ReadAll(resp.Body)
_ = resp.Body.Close()
if err != nil {
@ -488,3 +488,49 @@ again:
}
var remoteWriteRejectedLogger = logger.WithThrottler("remoteWriteRejected", 5*time.Second)
// getRetryDuration returns retry duration.
// retryAfterDuration has the highest priority.
// If retryAfterDuration is not specified, retryDuration gets doubled.
// retryDuration can't exceed maxRetryDuration.
//
// Also see: https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6097
func getRetryDuration(retryAfterDuration, retryDuration, maxRetryDuration time.Duration) time.Duration {
// retryAfterDuration has the highest priority duration
if retryAfterDuration > 0 {
return timeutil.AddJitterToDuration(retryAfterDuration)
}
// default backoff retry policy
retryDuration *= 2
if retryDuration > maxRetryDuration {
retryDuration = maxRetryDuration
}
return retryDuration
}
// parseRetryAfterHeader parses `Retry-After` value retrieved from HTTP response header.
// retryAfterString should be in either HTTP-date or a number of seconds.
// It will return time.Duration(0) if `retryAfterString` does not follow RFC 7231.
func parseRetryAfterHeader(retryAfterString string) (retryAfterDuration time.Duration) {
if retryAfterString == "" {
return retryAfterDuration
}
defer func() {
v := retryAfterDuration.Seconds()
logger.Infof("'Retry-After: %s' parsed into %.2f second(s)", retryAfterString, v)
}()
// Retry-After could be in "Mon, 02 Jan 2006 15:04:05 GMT" format.
if parsedTime, err := time.Parse(http.TimeFormat, retryAfterString); err == nil {
return time.Duration(time.Until(parsedTime).Seconds()) * time.Second
}
// Retry-After could be in seconds.
if seconds, err := strconv.Atoi(retryAfterString); err == nil {
return time.Duration(seconds) * time.Second
}
return 0
}

View file

@ -0,0 +1,99 @@
package remotewrite
import (
"math"
"net/http"
"testing"
"time"
)
func TestCalculateRetryDuration(t *testing.T) {
// `testFunc` call `calculateRetryDuration` for `n` times
// and evaluate if the result of `calculateRetryDuration` is
// 1. >= expectMinDuration
// 2. <= expectMinDuration + 10% (see timeutil.AddJitterToDuration)
f := func(retryAfterDuration, retryDuration time.Duration, n int, expectMinDuration time.Duration) {
t.Helper()
for i := 0; i < n; i++ {
retryDuration = getRetryDuration(retryAfterDuration, retryDuration, time.Minute)
}
expectMaxDuration := helper(expectMinDuration)
expectMinDuration = expectMinDuration - (1000 * time.Millisecond) // Avoid edge case when calculating time.Until(now)
if !(retryDuration >= expectMinDuration && retryDuration <= expectMaxDuration) {
t.Fatalf(
"incorrect retry duration, want (ms): [%d, %d], got (ms): %d",
expectMinDuration.Milliseconds(), expectMaxDuration.Milliseconds(),
retryDuration.Milliseconds(),
)
}
}
// Call calculateRetryDuration for 1 time.
{
// default backoff policy
f(0, time.Second, 1, 2*time.Second)
// default backoff policy exceed max limit"
f(0, 10*time.Minute, 1, time.Minute)
// retry after > default backoff policy
f(10*time.Second, 1*time.Second, 1, 10*time.Second)
// retry after < default backoff policy
f(1*time.Second, 10*time.Second, 1, 1*time.Second)
// retry after invalid and < default backoff policy
f(0, time.Second, 1, 2*time.Second)
}
// Call calculateRetryDuration for multiple times.
{
// default backoff policy 2 times
f(0, time.Second, 2, 4*time.Second)
// default backoff policy 3 times
f(0, time.Second, 3, 8*time.Second)
// default backoff policy N times exceed max limit
f(0, time.Second, 10, time.Minute)
// retry after 120s 1 times
f(120*time.Second, time.Second, 1, 120*time.Second)
// retry after 120s 2 times
f(120*time.Second, time.Second, 2, 120*time.Second)
}
}
func TestParseRetryAfterHeader(t *testing.T) {
f := func(retryAfterString string, expectResult time.Duration) {
t.Helper()
result := parseRetryAfterHeader(retryAfterString)
// expect `expectResult == result` when retryAfterString is in seconds or invalid
// expect the difference between result and expectResult to be lower than 10%
if !(expectResult == result || math.Abs(float64(expectResult-result))/float64(expectResult) < 0.10) {
t.Fatalf(
"incorrect retry after duration, want (ms): %d, got (ms): %d",
expectResult.Milliseconds(), result.Milliseconds(),
)
}
}
// retry after header in seconds
f("10", 10*time.Second)
// retry after header in date time
f(time.Now().Add(30*time.Second).UTC().Format(http.TimeFormat), 30*time.Second)
// retry after header invalid
f("invalid-retry-after", 0)
// retry after header not in GMT
f(time.Now().Add(10*time.Second).Format("Mon, 02 Jan 2006 15:04:05 FAKETZ"), 0)
}
// helper calculate the max possible time duration calculated by timeutil.AddJitterToDuration.
func helper(d time.Duration) time.Duration {
dv := d / 10
if dv > 10*time.Second {
dv = 10 * time.Second
}
return d + dv
}

View file

@ -3,6 +3,7 @@ package remotewrite
import (
"flag"
"fmt"
"strings"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
@ -56,6 +57,7 @@ var (
"See https://docs.victoriametrics.com/stream-aggregation/#ignore-aggregation-intervals-on-start")
streamAggrDropInputLabels = flagutil.NewArrayString("remoteWrite.streamAggr.dropInputLabels", "An optional list of labels to drop from samples "+
"before stream de-duplication and aggregation with -remoteWrite.streamAggr.config and -remoteWrite.streamAggr.dedupInterval at the corresponding -remoteWrite.url. "+
"Multiple labels per remoteWrite.url must be delimited by '^^': -remoteWrite.streamAggr.dropInputLabels='replica^^az,replica'. "+
"See https://docs.victoriametrics.com/stream-aggregation/#dropping-unneeded-labels")
)
@ -155,7 +157,11 @@ func (rwctx *remoteWriteCtx) initStreamAggrConfig() {
dedupInterval := streamAggrDedupInterval.GetOptionalArg(idx)
if dedupInterval > 0 {
alias := fmt.Sprintf("dedup-%d", idx+1)
rwctx.deduplicator = streamaggr.NewDeduplicator(rwctx.pushInternalTrackDropped, dedupInterval, *streamAggrDropInputLabels, alias)
var dropLabels []string
if streamAggrDropInputLabels.GetOptionalArg(idx) != "" {
dropLabels = strings.Split(streamAggrDropInputLabels.GetOptionalArg(idx), "^^")
}
rwctx.deduplicator = streamaggr.NewDeduplicator(rwctx.pushInternalTrackDropped, dedupInterval, dropLabels, alias)
}
}
@ -224,9 +230,13 @@ func newStreamAggrConfigPerURL(idx int, pushFunc streamaggr.PushFunc) (*streamag
if *showRemoteWriteURL {
alias = fmt.Sprintf("%d:%s", idx+1, remoteWriteURLs.GetOptionalArg(idx))
}
var dropLabels []string
if streamAggrDropInputLabels.GetOptionalArg(idx) != "" {
dropLabels = strings.Split(streamAggrDropInputLabels.GetOptionalArg(idx), "^^")
}
opts := &streamaggr.Options{
DedupInterval: streamAggrDedupInterval.GetOptionalArg(idx),
DropInputLabels: *streamAggrDropInputLabels,
DropInputLabels: dropLabels,
IgnoreOldSamples: streamAggrIgnoreOldSamples.GetOptionalArg(idx),
IgnoreFirstIntervals: streamAggrIgnoreFirstIntervals.GetOptionalArg(idx),
KeepInput: streamAggrKeepInput.GetOptionalArg(idx),

View file

@ -8,5 +8,5 @@ FROM $root_image
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
EXPOSE 8429
ENTRYPOINT ["/vmalert-tool-prod"]
ARG TARGETARCH=non-existing
ARG TARGETARCH
COPY vmalert-tool-linux-${TARGETARCH}-prod ./vmalert-tool-prod

View file

@ -8,5 +8,5 @@ FROM $root_image
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
EXPOSE 8880
ENTRYPOINT ["/vmalert-prod"]
ARG TARGETARCH=non-existing
ARG TARGETARCH
COPY vmalert-linux-${TARGETARCH}-prod ./vmalert-prod

View file

@ -187,7 +187,7 @@ func templateAnnotation(dst io.Writer, text string, data tplData, tmpl *textTpl.
return nil
}
func (a Alert) toPromLabels(relabelCfg *promrelabel.ParsedConfigs) []prompbmarshal.Label {
func (a Alert) applyRelabelingIfNeeded(relabelCfg *promrelabel.ParsedConfigs) []prompbmarshal.Label {
var labels []prompbmarshal.Label
for k, v := range a.Labels {
labels = append(labels, prompbmarshal.Label{

View file

@ -187,7 +187,7 @@ func TestAlert_toPromLabels(t *testing.T) {
fn := func(labels map[string]string, exp []prompbmarshal.Label, relabel *promrelabel.ParsedConfigs) {
t.Helper()
a := Alert{Labels: labels}
got := a.toPromLabels(relabel)
got := a.applyRelabelingIfNeeded(relabel)
if !reflect.DeepEqual(got, exp) {
t.Fatalf("expected to have: \n%v;\ngot:\n%v",
exp, got)

View file

@ -8,6 +8,8 @@
{% func amRequest(alerts []Alert, generatorURL func(Alert) string, relabelCfg *promrelabel.ParsedConfigs) %}
[
{% for i, alert := range alerts %}
{% code lbls := alert.applyRelabelingIfNeeded(relabelCfg) %}
{% if len(lbls) == 0 %} {% continue %} {% endif %}
{
"startsAt":{%q= alert.Start.Format(time.RFC3339Nano) %},
"generatorURL": {%q= generatorURL(alert) %},
@ -15,7 +17,6 @@
"endsAt":{%q= alert.End.Format(time.RFC3339Nano) %},
{% endif %}
"labels": {
{% code lbls := alert.toPromLabels(relabelCfg) %}
{% code ll := len(lbls) %}
{% for idx, l := range lbls %}
{%q= l.Name %}:{%q= l.Value %}{% if idx != ll-1 %}, {% endif %}

View file

@ -30,111 +30,117 @@ func streamamRequest(qw422016 *qt422016.Writer, alerts []Alert, generatorURL fun
qw422016.N().S(`[`)
//line app/vmalert/notifier/alertmanager_request.qtpl:10
for i, alert := range alerts {
//line app/vmalert/notifier/alertmanager_request.qtpl:10
qw422016.N().S(`{"startsAt":`)
//line app/vmalert/notifier/alertmanager_request.qtpl:12
qw422016.N().Q(alert.Start.Format(time.RFC3339Nano))
//line app/vmalert/notifier/alertmanager_request.qtpl:12
qw422016.N().S(`,"generatorURL":`)
//line app/vmalert/notifier/alertmanager_request.qtpl:13
qw422016.N().Q(generatorURL(alert))
//line app/vmalert/notifier/alertmanager_request.qtpl:13
qw422016.N().S(`,`)
//line app/vmalert/notifier/alertmanager_request.qtpl:14
if !alert.End.IsZero() {
//line app/vmalert/notifier/alertmanager_request.qtpl:14
qw422016.N().S(`"endsAt":`)
//line app/vmalert/notifier/alertmanager_request.qtpl:15
qw422016.N().Q(alert.End.Format(time.RFC3339Nano))
//line app/vmalert/notifier/alertmanager_request.qtpl:15
qw422016.N().S(`,`)
//line app/vmalert/notifier/alertmanager_request.qtpl:16
}
//line app/vmalert/notifier/alertmanager_request.qtpl:16
qw422016.N().S(`"labels": {`)
//line app/vmalert/notifier/alertmanager_request.qtpl:18
lbls := alert.toPromLabels(relabelCfg)
//line app/vmalert/notifier/alertmanager_request.qtpl:11
lbls := alert.applyRelabelingIfNeeded(relabelCfg)
//line app/vmalert/notifier/alertmanager_request.qtpl:19
//line app/vmalert/notifier/alertmanager_request.qtpl:12
if len(lbls) == 0 {
//line app/vmalert/notifier/alertmanager_request.qtpl:12
continue
//line app/vmalert/notifier/alertmanager_request.qtpl:12
}
//line app/vmalert/notifier/alertmanager_request.qtpl:12
qw422016.N().S(`{"startsAt":`)
//line app/vmalert/notifier/alertmanager_request.qtpl:14
qw422016.N().Q(alert.Start.Format(time.RFC3339Nano))
//line app/vmalert/notifier/alertmanager_request.qtpl:14
qw422016.N().S(`,"generatorURL":`)
//line app/vmalert/notifier/alertmanager_request.qtpl:15
qw422016.N().Q(generatorURL(alert))
//line app/vmalert/notifier/alertmanager_request.qtpl:15
qw422016.N().S(`,`)
//line app/vmalert/notifier/alertmanager_request.qtpl:16
if !alert.End.IsZero() {
//line app/vmalert/notifier/alertmanager_request.qtpl:16
qw422016.N().S(`"endsAt":`)
//line app/vmalert/notifier/alertmanager_request.qtpl:17
qw422016.N().Q(alert.End.Format(time.RFC3339Nano))
//line app/vmalert/notifier/alertmanager_request.qtpl:17
qw422016.N().S(`,`)
//line app/vmalert/notifier/alertmanager_request.qtpl:18
}
//line app/vmalert/notifier/alertmanager_request.qtpl:18
qw422016.N().S(`"labels": {`)
//line app/vmalert/notifier/alertmanager_request.qtpl:20
ll := len(lbls)
//line app/vmalert/notifier/alertmanager_request.qtpl:20
//line app/vmalert/notifier/alertmanager_request.qtpl:21
for idx, l := range lbls {
//line app/vmalert/notifier/alertmanager_request.qtpl:21
//line app/vmalert/notifier/alertmanager_request.qtpl:22
qw422016.N().Q(l.Name)
//line app/vmalert/notifier/alertmanager_request.qtpl:21
//line app/vmalert/notifier/alertmanager_request.qtpl:22
qw422016.N().S(`:`)
//line app/vmalert/notifier/alertmanager_request.qtpl:21
//line app/vmalert/notifier/alertmanager_request.qtpl:22
qw422016.N().Q(l.Value)
//line app/vmalert/notifier/alertmanager_request.qtpl:21
//line app/vmalert/notifier/alertmanager_request.qtpl:22
if idx != ll-1 {
//line app/vmalert/notifier/alertmanager_request.qtpl:21
//line app/vmalert/notifier/alertmanager_request.qtpl:22
qw422016.N().S(`,`)
//line app/vmalert/notifier/alertmanager_request.qtpl:21
//line app/vmalert/notifier/alertmanager_request.qtpl:22
}
//line app/vmalert/notifier/alertmanager_request.qtpl:22
//line app/vmalert/notifier/alertmanager_request.qtpl:23
}
//line app/vmalert/notifier/alertmanager_request.qtpl:22
//line app/vmalert/notifier/alertmanager_request.qtpl:23
qw422016.N().S(`},"annotations": {`)
//line app/vmalert/notifier/alertmanager_request.qtpl:25
//line app/vmalert/notifier/alertmanager_request.qtpl:26
c := len(alert.Annotations)
//line app/vmalert/notifier/alertmanager_request.qtpl:26
for k, v := range alert.Annotations {
//line app/vmalert/notifier/alertmanager_request.qtpl:27
for k, v := range alert.Annotations {
//line app/vmalert/notifier/alertmanager_request.qtpl:28
c = c - 1
//line app/vmalert/notifier/alertmanager_request.qtpl:28
//line app/vmalert/notifier/alertmanager_request.qtpl:29
qw422016.N().Q(k)
//line app/vmalert/notifier/alertmanager_request.qtpl:28
//line app/vmalert/notifier/alertmanager_request.qtpl:29
qw422016.N().S(`:`)
//line app/vmalert/notifier/alertmanager_request.qtpl:28
//line app/vmalert/notifier/alertmanager_request.qtpl:29
qw422016.N().Q(v)
//line app/vmalert/notifier/alertmanager_request.qtpl:28
//line app/vmalert/notifier/alertmanager_request.qtpl:29
if c > 0 {
//line app/vmalert/notifier/alertmanager_request.qtpl:28
//line app/vmalert/notifier/alertmanager_request.qtpl:29
qw422016.N().S(`,`)
//line app/vmalert/notifier/alertmanager_request.qtpl:28
//line app/vmalert/notifier/alertmanager_request.qtpl:29
}
//line app/vmalert/notifier/alertmanager_request.qtpl:29
//line app/vmalert/notifier/alertmanager_request.qtpl:30
}
//line app/vmalert/notifier/alertmanager_request.qtpl:29
//line app/vmalert/notifier/alertmanager_request.qtpl:30
qw422016.N().S(`}}`)
//line app/vmalert/notifier/alertmanager_request.qtpl:32
//line app/vmalert/notifier/alertmanager_request.qtpl:33
if i != len(alerts)-1 {
//line app/vmalert/notifier/alertmanager_request.qtpl:32
//line app/vmalert/notifier/alertmanager_request.qtpl:33
qw422016.N().S(`,`)
//line app/vmalert/notifier/alertmanager_request.qtpl:32
//line app/vmalert/notifier/alertmanager_request.qtpl:33
}
//line app/vmalert/notifier/alertmanager_request.qtpl:33
//line app/vmalert/notifier/alertmanager_request.qtpl:34
}
//line app/vmalert/notifier/alertmanager_request.qtpl:33
//line app/vmalert/notifier/alertmanager_request.qtpl:34
qw422016.N().S(`]`)
//line app/vmalert/notifier/alertmanager_request.qtpl:35
//line app/vmalert/notifier/alertmanager_request.qtpl:36
}
//line app/vmalert/notifier/alertmanager_request.qtpl:35
//line app/vmalert/notifier/alertmanager_request.qtpl:36
func writeamRequest(qq422016 qtio422016.Writer, alerts []Alert, generatorURL func(Alert) string, relabelCfg *promrelabel.ParsedConfigs) {
//line app/vmalert/notifier/alertmanager_request.qtpl:35
//line app/vmalert/notifier/alertmanager_request.qtpl:36
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmalert/notifier/alertmanager_request.qtpl:35
//line app/vmalert/notifier/alertmanager_request.qtpl:36
streamamRequest(qw422016, alerts, generatorURL, relabelCfg)
//line app/vmalert/notifier/alertmanager_request.qtpl:35
//line app/vmalert/notifier/alertmanager_request.qtpl:36
qt422016.ReleaseWriter(qw422016)
//line app/vmalert/notifier/alertmanager_request.qtpl:35
//line app/vmalert/notifier/alertmanager_request.qtpl:36
}
//line app/vmalert/notifier/alertmanager_request.qtpl:35
//line app/vmalert/notifier/alertmanager_request.qtpl:36
func amRequest(alerts []Alert, generatorURL func(Alert) string, relabelCfg *promrelabel.ParsedConfigs) string {
//line app/vmalert/notifier/alertmanager_request.qtpl:35
//line app/vmalert/notifier/alertmanager_request.qtpl:36
qb422016 := qt422016.AcquireByteBuffer()
//line app/vmalert/notifier/alertmanager_request.qtpl:35
//line app/vmalert/notifier/alertmanager_request.qtpl:36
writeamRequest(qb422016, alerts, generatorURL, relabelCfg)
//line app/vmalert/notifier/alertmanager_request.qtpl:35
//line app/vmalert/notifier/alertmanager_request.qtpl:36
qs422016 := string(qb422016.B)
//line app/vmalert/notifier/alertmanager_request.qtpl:35
//line app/vmalert/notifier/alertmanager_request.qtpl:36
qt422016.ReleaseByteBuffer(qb422016)
//line app/vmalert/notifier/alertmanager_request.qtpl:35
//line app/vmalert/notifier/alertmanager_request.qtpl:36
return qs422016
//line app/vmalert/notifier/alertmanager_request.qtpl:35
//line app/vmalert/notifier/alertmanager_request.qtpl:36
}

View file

@ -11,6 +11,7 @@ import (
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
)
func TestAlertManager_Addr(t *testing.T) {
@ -76,12 +77,33 @@ func TestAlertManager_Send(t *testing.T) {
if a[0].EndAt.IsZero() {
t.Fatalf("expected non-zero end time")
}
if len(a[0].Labels) != 1 {
t.Fatalf("expected 1 labels got %d", len(a[0].Labels))
}
if len(a[0].Annotations) != 2 {
t.Fatalf("expected 2 annotations got %d", len(a[0].Annotations))
}
if r.Header.Get(headerKey) != "bar" {
t.Fatalf("expected header %q to be set to %q; got %q instead", headerKey, headerValue, r.Header.Get(headerKey))
}
case 3:
if r.Header.Get(headerKey) != headerValue {
t.Fatalf("expected header %q to be set to %q; got %q instead", headerKey, headerValue, r.Header.Get(headerKey))
var a []struct {
Labels map[string]string `json:"labels"`
}
if err := json.NewDecoder(r.Body).Decode(&a); err != nil {
t.Fatalf("can not unmarshal data into alert %s", err)
}
if len(a) != 1 {
t.Fatalf("expected 1 alert in array got %d", len(a))
}
if len(a[0].Labels) != 3 {
t.Fatalf("expected 3 labels got %d", len(a[0].Labels))
}
if a[0].Labels["env"] != "prod" {
t.Fatalf("expected env label to be prod during relabeling, got %s", a[0].Labels["env"])
}
if r.Header.Get(headerKey) != "bar" {
t.Fatalf("expected header %q to be set to %q; got %q instead", headerKey, "bar", r.Header.Get(headerKey))
}
}
})
@ -95,31 +117,58 @@ func TestAlertManager_Send(t *testing.T) {
},
Headers: []string{fmt.Sprintf("%s:%s", headerKey, headerValue)},
}
parsedConfigs, err := promrelabel.ParseRelabelConfigsData([]byte(`
- action: drop
if: '{tenant="0"}'
regex: ".*"
- target_label: "env"
replacement: "prod"
if: '{tenant="1"}'
`))
if err != nil {
t.Fatalf("unexpected error when parse relabeling config: %s", err)
}
am, err := NewAlertManager(srv.URL+alertManagerPath, func(alert Alert) string {
return strconv.FormatUint(alert.GroupID, 10) + "/" + strconv.FormatUint(alert.ID, 10)
}, aCfg, nil, 0)
}, aCfg, parsedConfigs, 0)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if err := am.Send(context.Background(), []Alert{{}, {}}, nil); err == nil {
if err := am.Send(context.Background(), []Alert{{Labels: map[string]string{"a": "b"}}}, nil); err == nil {
t.Fatalf("expected connection error got nil")
}
if err := am.Send(context.Background(), []Alert{}, nil); err == nil {
if err := am.Send(context.Background(), []Alert{{Labels: map[string]string{"a": "b"}}}, nil); err == nil {
t.Fatalf("expected wrong http code error got nil")
}
if err := am.Send(context.Background(), []Alert{{
GroupID: 0,
Name: "alert0",
Start: time.Now().UTC(),
End: time.Now().UTC(),
Annotations: map[string]string{"a": "b", "c": "d", "e": "f"},
Labels: map[string]string{"alertname": "alert0"},
Annotations: map[string]string{"a": "b", "c": "d"},
}}, map[string]string{headerKey: "bar"}); err != nil {
t.Fatalf("unexpected error %s", err)
}
if c != 2 {
t.Fatalf("expected 2 calls(count from zero) to server got %d", c)
}
if err := am.Send(context.Background(), nil, map[string]string{headerKey: headerValue}); err != nil {
if err := am.Send(context.Background(), []Alert{
// drop tenant0 alert message during relabeling
{
Name: "alert1",
Labels: map[string]string{"rule": "test", "tenant": "0"},
},
{
Name: "alert2",
Labels: map[string]string{"rule": "test", "tenant": "1"},
},
}, map[string]string{headerKey: "bar"}); err != nil {
t.Fatalf("unexpected error %s", err)
}
if c != 3 {
t.Fatalf("expected 3 calls(count from zero) to server got %d", c)
}
}

View file

@ -34,9 +34,9 @@ var (
idleConnectionTimeout = flag.Duration("remoteWrite.idleConnTimeout", 50*time.Second, `Defines a duration for idle (keep-alive connections) to exist. Consider settings this value less to the value of "-http.idleConnTimeout". It must prevent possible "write: broken pipe" and "read: connection reset by peer" errors.`)
maxQueueSize = flag.Int("remoteWrite.maxQueueSize", 1e5, "Defines the max number of pending datapoints to remote write endpoint")
maxBatchSize = flag.Int("remoteWrite.maxBatchSize", 1e3, "Defines max number of timeseries to be flushed at once")
concurrency = flag.Int("remoteWrite.concurrency", 1, "Defines number of writers for concurrent writing into remote write endpoint")
maxQueueSize = flag.Int("remoteWrite.maxQueueSize", 1e6, "Defines the max number of pending datapoints to remote write endpoint")
maxBatchSize = flag.Int("remoteWrite.maxBatchSize", 1e4, "Defines max number of timeseries to be flushed at once")
concurrency = flag.Int("remoteWrite.concurrency", 4, "Defines number of writers for concurrent writing into remote write endpoint")
flushInterval = flag.Duration("remoteWrite.flushInterval", 5*time.Second, "Defines interval of flushes to remote write endpoint")
tlsInsecureSkipVerify = flag.Bool("remoteWrite.tlsInsecureSkipVerify", false, "Whether to skip tls verification when connecting to -remoteWrite.url")

View file

@ -335,6 +335,8 @@ func (ar *AlertingRule) execRange(ctx context.Context, start, end time.Time) ([]
// reset to Pending if there are gaps > EvalInterval between DPs
a.State = notifier.StatePending
a.ActiveAt = at
// re-template the annotations as active timestamp is changed
_, a.Annotations, _ = ar.expandTemplates(s, qFn, at)
a.Start = time.Time{}
} else if at.Sub(a.ActiveAt) >= ar.For && a.State != notifier.StateFiring {
a.State = notifier.StateFiring

View file

@ -289,7 +289,7 @@ func TestAlertingRule_Exec(t *testing.T) {
4: {{labels: []string{"name", "foo"}, alert: &notifier.Alert{State: notifier.StateFiring}}},
})
f(newTestAlertingRuleWithKeepFiring("for-pending=>firing=>keepfiring=>firing", defaultStep, defaultStep), [][]datasource.Metric{
f(newTestAlertingRuleWithCustomFields("for-pending=>firing=>keepfiring=>firing", defaultStep, 0, defaultStep, nil), [][]datasource.Metric{
{metricWithLabels(t, "name", "foo")},
{metricWithLabels(t, "name", "foo")},
// empty step to keep firing
@ -302,7 +302,7 @@ func TestAlertingRule_Exec(t *testing.T) {
3: {{labels: []string{"name", "foo"}, alert: &notifier.Alert{State: notifier.StateFiring}}},
})
f(newTestAlertingRuleWithKeepFiring("for-pending=>firing=>keepfiring=>keepfiring=>inactive=>pending=>firing", defaultStep, 2*defaultStep), [][]datasource.Metric{
f(newTestAlertingRuleWithCustomFields("for-pending=>firing=>keepfiring=>keepfiring=>inactive=>pending=>firing", defaultStep, 0, 2*defaultStep, nil), [][]datasource.Metric{
{metricWithLabels(t, "name", "foo")},
{metricWithLabels(t, "name", "foo")},
// empty step to keep firing
@ -395,7 +395,7 @@ func TestAlertingRuleExecRange(t *testing.T) {
{State: notifier.StateFiring, ActiveAt: time.Unix(3e3, 0)},
}, nil)
f(newTestAlertingRule("for-pending", time.Second), []datasource.Metric{
f(newTestAlertingRuleWithCustomFields("for-pending", time.Second, 0, 0, map[string]string{"activeAt": "{{ $activeAt.UnixMilli }}"}), []datasource.Metric{
{Values: []float64{1, 1, 1}, Timestamps: []int64{1, 3, 5}},
}, []*notifier.Alert{
{State: notifier.StatePending, ActiveAt: time.Unix(1, 0)},
@ -406,7 +406,7 @@ func TestAlertingRuleExecRange(t *testing.T) {
GroupID: fakeGroup.ID(),
Name: "for-pending",
Labels: map[string]string{"alertname": "for-pending"},
Annotations: map[string]string{},
Annotations: map[string]string{"activeAt": "5000"},
State: notifier.StatePending,
ActiveAt: time.Unix(5, 0),
Value: 1,
@ -414,7 +414,7 @@ func TestAlertingRuleExecRange(t *testing.T) {
},
})
f(newTestAlertingRule("for-firing", 3*time.Second), []datasource.Metric{
f(newTestAlertingRuleWithCustomFields("for-firing", 3*time.Second, 0, 0, map[string]string{"activeAt": "{{ $activeAt.UnixMilli }}"}), []datasource.Metric{
{Values: []float64{1, 1, 1}, Timestamps: []int64{1, 3, 5}},
}, []*notifier.Alert{
{State: notifier.StatePending, ActiveAt: time.Unix(1, 0)},
@ -425,7 +425,7 @@ func TestAlertingRuleExecRange(t *testing.T) {
GroupID: fakeGroup.ID(),
Name: "for-firing",
Labels: map[string]string{"alertname": "for-firing"},
Annotations: map[string]string{},
Annotations: map[string]string{"activeAt": "1000"},
State: notifier.StateFiring,
ActiveAt: time.Unix(1, 0),
Start: time.Unix(5, 0),
@ -434,7 +434,7 @@ func TestAlertingRuleExecRange(t *testing.T) {
},
})
f(newTestAlertingRule("for-hold-pending", time.Second), []datasource.Metric{
f(newTestAlertingRuleWithCustomFields("for-hold-pending", time.Second, 0, 0, map[string]string{"activeAt": "{{ $activeAt.UnixMilli }}"}), []datasource.Metric{
{Values: []float64{1, 1, 1}, Timestamps: []int64{1, 2, 5}},
}, []*notifier.Alert{
{State: notifier.StatePending, ActiveAt: time.Unix(1, 0)},
@ -445,7 +445,7 @@ func TestAlertingRuleExecRange(t *testing.T) {
GroupID: fakeGroup.ID(),
Name: "for-hold-pending",
Labels: map[string]string{"alertname": "for-hold-pending"},
Annotations: map[string]string{},
Annotations: map[string]string{"activeAt": "5000"},
State: notifier.StatePending,
ActiveAt: time.Unix(5, 0),
Value: 1,
@ -453,7 +453,7 @@ func TestAlertingRuleExecRange(t *testing.T) {
},
})
f(newTestAlertingRuleWithEvalInterval("firing=>inactive=>inactive=>firing=>firing", 0, time.Second), []datasource.Metric{
f(newTestAlertingRuleWithCustomFields("firing=>inactive=>inactive=>firing=>firing", 0, time.Second, 0, nil), []datasource.Metric{
{Values: []float64{1, 1, 1, 1}, Timestamps: []int64{1, 4, 5, 6}},
}, []*notifier.Alert{
{State: notifier.StateFiring, ActiveAt: time.Unix(1, 0)},
@ -538,7 +538,6 @@ func TestAlertingRuleExecRange(t *testing.T) {
"source": "vm",
},
},
//
{
State: notifier.StateFiring, ActiveAt: time.Unix(1, 0),
Labels: map[string]string{
@ -1036,15 +1035,13 @@ func newTestAlertingRule(name string, waitFor time.Duration) *AlertingRule {
return &rule
}
func newTestAlertingRuleWithEvalInterval(name string, waitFor, evalInterval time.Duration) *AlertingRule {
rule := newTestAlertingRule(name, waitFor)
rule.EvalInterval = evalInterval
return rule
}
func newTestAlertingRuleWithKeepFiring(name string, waitFor, keepFiringFor time.Duration) *AlertingRule {
func newTestAlertingRuleWithCustomFields(name string, waitFor, evalInterval, keepFiringFor time.Duration, annotation map[string]string) *AlertingRule {
rule := newTestAlertingRule(name, waitFor)
if evalInterval != 0 {
rule.EvalInterval = evalInterval
}
rule.KeepFiringFor = keepFiringFor
rule.Annotations = annotation
return rule
}

View file

@ -50,12 +50,12 @@ func TestRequestToCurl(t *testing.T) {
f(req, "curl -k -X POST 'https://user:xxxxx@foo.com?query=up&step=10'")
req = newReq("https://user:pass@foo.com")
req.Header.Set("Authorisation", "Bearer 123456")
f(req, "curl -k -X POST -H 'Authorisation: <secret>' 'https://user:xxxxx@foo.com'")
req.Header.Set("Authorization", "Bearer 123456")
f(req, "curl -k -X POST -H 'Authorization: <secret>' 'https://user:xxxxx@foo.com'")
req = newReq("https://user:pass@foo.com")
req.Header.Set("Authorisation", "Basic 123456")
f(req, "curl -k -X POST -H 'Authorisation: <secret>' 'https://user:xxxxx@foo.com'")
req.Header.Set("Authorization", "Basic 123456")
f(req, "curl -k -X POST -H 'Authorization: <secret>' 'https://user:xxxxx@foo.com'")
req = newReq("https://foo.com")
req.Header.Set("My-Password", "mypassword")
@ -66,7 +66,7 @@ func TestRequestToCurl(t *testing.T) {
f(req, "curl -k -X POST -H 'Key-For: <secret>' 'https://foo.com'")
req = newReq("https://foo.com")
req.Header.Set("My-Secret-Org", "secret-organisation")
req.Header.Set("My-Secret-Org", "secret-organization")
f(req, "curl -k -X POST -H 'My-Secret-Org: <secret>' 'https://foo.com'")
req = newReq("https://foo.com")

View file

@ -8,5 +8,5 @@ FROM $root_image
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
EXPOSE 8427
ENTRYPOINT ["/vmauth-prod"]
ARG TARGETARCH=non-existing
ARG TARGETARCH
COPY vmauth-linux-${TARGETARCH}-prod ./vmauth-prod

View file

@ -7,5 +7,5 @@ RUN apk update && apk upgrade && apk --update --no-cache add ca-certificates
FROM $root_image
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
ENTRYPOINT ["/vmbackup-prod"]
ARG TARGETARCH=non-existing
ARG TARGETARCH
COPY vmbackup-linux-${TARGETARCH}-prod ./vmbackup-prod

View file

@ -7,5 +7,5 @@ RUN apk update && apk upgrade && apk --update --no-cache add ca-certificates
FROM $root_image
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
ENTRYPOINT ["/vmctl-prod"]
ARG TARGETARCH=non-existing
ARG TARGETARCH
COPY vmctl-linux-${TARGETARCH}-prod ./vmctl-prod

View file

@ -36,40 +36,37 @@ func TestGetTime_Success(t *testing.T) {
}
// only year
f("2019", time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC))
f("2019Z", time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC))
// year and month
f("2019-01", time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC))
f("2019-01Z", time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC))
// year and not first month
f("2019-02", time.Date(2019, 2, 1, 0, 0, 0, 0, time.UTC))
f("2019-02Z", time.Date(2019, 2, 1, 0, 0, 0, 0, time.UTC))
// year, month and day
f("2019-02-01", time.Date(2019, 2, 1, 0, 0, 0, 0, time.UTC))
f("2019-02-01Z", time.Date(2019, 2, 1, 0, 0, 0, 0, time.UTC))
// year, month and not first day
f("2019-02-10", time.Date(2019, 2, 10, 0, 0, 0, 0, time.UTC))
f("2019-02-10Z", time.Date(2019, 2, 10, 0, 0, 0, 0, time.UTC))
// year, month, day and time
f("2019-02-02T00", time.Date(2019, 2, 2, 0, 0, 0, 0, time.UTC))
f("2019-02-02T00Z", time.Date(2019, 2, 2, 0, 0, 0, 0, time.UTC))
// year, month, day and one hour time
f("2019-02-02T01", time.Date(2019, 2, 2, 1, 0, 0, 0, time.UTC))
f("2019-02-02T01Z", time.Date(2019, 2, 2, 1, 0, 0, 0, time.UTC))
// time with zero minutes
f("2019-02-02T01:00", time.Date(2019, 2, 2, 1, 0, 0, 0, time.UTC))
f("2019-02-02T01:00Z", time.Date(2019, 2, 2, 1, 0, 0, 0, time.UTC))
// time with one minute
f("2019-02-02T01:01", time.Date(2019, 2, 2, 1, 1, 0, 0, time.UTC))
f("2019-02-02T01:01Z", time.Date(2019, 2, 2, 1, 1, 0, 0, time.UTC))
// time with zero seconds
f("2019-02-02T01:01:00", time.Date(2019, 2, 2, 1, 1, 0, 0, time.UTC))
f("2019-02-02T01:01:00Z", time.Date(2019, 2, 2, 1, 1, 0, 0, time.UTC))
// timezone with one second
f("2019-02-02T01:01:01", time.Date(2019, 2, 2, 1, 1, 1, 0, time.UTC))
// time with two second and timezone
f("2019-07-07T20:01:02Z", time.Date(2019, 7, 7, 20, 1, 02, 0, time.UTC))
f("2019-02-02T01:01:01Z", time.Date(2019, 2, 2, 1, 1, 1, 0, time.UTC))
// time with seconds and timezone
f("2019-07-07T20:47:40+03:00", func() time.Time {

View file

@ -7,5 +7,5 @@ RUN apk update && apk upgrade && apk --update --no-cache add ca-certificates
FROM $root_image
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
ENTRYPOINT ["/vmrestore-prod"]
ARG TARGETARCH=non-existing
ARG TARGETARCH
COPY vmrestore-linux-${TARGETARCH}-prod ./vmrestore-prod

View file

@ -2,7 +2,6 @@ package vmselect
import (
"embed"
"errors"
"flag"
"fmt"
"net/http"
@ -187,7 +186,7 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
httpserver.EnableCORS(w, r)
if err := prometheus.LabelValuesHandler(qt, startTime, labelName, w, r); err != nil {
labelValuesErrors.Inc()
sendPrometheusError(w, r, err)
httpserver.SendPrometheusError(w, r, err)
return true
}
return true
@ -210,7 +209,7 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
httpserver.EnableCORS(w, r)
if err := prometheus.QueryHandler(qt, startTime, w, r); err != nil {
queryErrors.Inc()
sendPrometheusError(w, r, err)
httpserver.SendPrometheusError(w, r, err)
return true
}
return true
@ -219,7 +218,7 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
httpserver.EnableCORS(w, r)
if err := prometheus.QueryRangeHandler(qt, startTime, w, r); err != nil {
queryRangeErrors.Inc()
sendPrometheusError(w, r, err)
httpserver.SendPrometheusError(w, r, err)
return true
}
return true
@ -228,7 +227,7 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
httpserver.EnableCORS(w, r)
if err := prometheus.SeriesHandler(qt, startTime, w, r); err != nil {
seriesErrors.Inc()
sendPrometheusError(w, r, err)
httpserver.SendPrometheusError(w, r, err)
return true
}
return true
@ -237,7 +236,7 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
httpserver.EnableCORS(w, r)
if err := prometheus.SeriesCountHandler(startTime, w, r); err != nil {
seriesCountErrors.Inc()
sendPrometheusError(w, r, err)
httpserver.SendPrometheusError(w, r, err)
return true
}
return true
@ -246,7 +245,7 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
httpserver.EnableCORS(w, r)
if err := prometheus.LabelsHandler(qt, startTime, w, r); err != nil {
labelsErrors.Inc()
sendPrometheusError(w, r, err)
httpserver.SendPrometheusError(w, r, err)
return true
}
return true
@ -255,7 +254,7 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
httpserver.EnableCORS(w, r)
if err := prometheus.TSDBStatusHandler(qt, startTime, w, r); err != nil {
statusTSDBErrors.Inc()
sendPrometheusError(w, r, err)
httpserver.SendPrometheusError(w, r, err)
return true
}
return true
@ -498,7 +497,7 @@ func handleStaticAndSimpleRequests(w http.ResponseWriter, r *http.Request, path
httpserver.EnableCORS(w, r)
if err := prometheus.QueryStatsHandler(w, r); err != nil {
topQueriesErrors.Inc()
sendPrometheusError(w, r, fmt.Errorf("cannot query status endpoint: %w", err))
httpserver.SendPrometheusError(w, r, fmt.Errorf("cannot query status endpoint: %w", err))
return true
}
return true
@ -575,24 +574,6 @@ func isGraphiteTagsPath(path string) bool {
}
}
func sendPrometheusError(w http.ResponseWriter, r *http.Request, err error) {
logger.WarnfSkipframes(1, "error in %q: %s", httpserver.GetRequestURI(r), err)
w.Header().Set("Content-Type", "application/json")
statusCode := http.StatusUnprocessableEntity
var esc *httpserver.ErrorWithStatusCode
if errors.As(err, &esc) {
statusCode = esc.StatusCode
}
w.WriteHeader(statusCode)
var ure *promql.UserReadableError
if errors.As(err, &ure) {
err = ure
}
prometheus.WriteErrorResponse(w, statusCode, err)
}
var (
requestDuration = metrics.NewHistogram(`vmselect_request_duration_seconds`)

View file

@ -1,61 +0,0 @@
// Code generated by qtc from "error_response.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.
// ErrorResponse generates error response for /api/v1/query.See https://prometheus.io/docs/prometheus/latest/querying/api/#format-overview
//line app/vmselect/prometheus/error_response.qtpl:4
package prometheus
//line app/vmselect/prometheus/error_response.qtpl:4
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line app/vmselect/prometheus/error_response.qtpl:4
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line app/vmselect/prometheus/error_response.qtpl:4
func StreamErrorResponse(qw422016 *qt422016.Writer, statusCode int, err error) {
//line app/vmselect/prometheus/error_response.qtpl:4
qw422016.N().S(`{"status":"error","errorType":"`)
//line app/vmselect/prometheus/error_response.qtpl:7
qw422016.N().D(statusCode)
//line app/vmselect/prometheus/error_response.qtpl:7
qw422016.N().S(`","error":`)
//line app/vmselect/prometheus/error_response.qtpl:8
qw422016.N().Q(err.Error())
//line app/vmselect/prometheus/error_response.qtpl:8
qw422016.N().S(`}`)
//line app/vmselect/prometheus/error_response.qtpl:10
}
//line app/vmselect/prometheus/error_response.qtpl:10
func WriteErrorResponse(qq422016 qtio422016.Writer, statusCode int, err error) {
//line app/vmselect/prometheus/error_response.qtpl:10
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmselect/prometheus/error_response.qtpl:10
StreamErrorResponse(qw422016, statusCode, err)
//line app/vmselect/prometheus/error_response.qtpl:10
qt422016.ReleaseWriter(qw422016)
//line app/vmselect/prometheus/error_response.qtpl:10
}
//line app/vmselect/prometheus/error_response.qtpl:10
func ErrorResponse(statusCode int, err error) string {
//line app/vmselect/prometheus/error_response.qtpl:10
qb422016 := qt422016.AcquireByteBuffer()
//line app/vmselect/prometheus/error_response.qtpl:10
WriteErrorResponse(qb422016, statusCode, err)
//line app/vmselect/prometheus/error_response.qtpl:10
qs422016 := string(qb422016.B)
//line app/vmselect/prometheus/error_response.qtpl:10
qt422016.ReleaseByteBuffer(qb422016)
//line app/vmselect/prometheus/error_response.qtpl:10
return qs422016
//line app/vmselect/prometheus/error_response.qtpl:10
}

View file

@ -19,6 +19,7 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/decimal"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/memory"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/querytracer"
@ -354,7 +355,7 @@ func evalExprInternal(qt *querytracer.Tracer, ec *EvalConfig, e metricsql.Expr)
func evalTransformFunc(qt *querytracer.Tracer, ec *EvalConfig, fe *metricsql.FuncExpr) ([]*timeseries, error) {
tf := getTransformFunc(fe.Name)
if tf == nil {
return nil, &UserReadableError{
return nil, &httpserver.UserReadableError{
Err: fmt.Errorf(`unknown func %q`, fe.Name),
}
}
@ -376,7 +377,7 @@ func evalTransformFunc(qt *querytracer.Tracer, ec *EvalConfig, fe *metricsql.Fun
}
rv, err := tf(tfa)
if err != nil {
return nil, &UserReadableError{
return nil, &httpserver.UserReadableError{
Err: fmt.Errorf(`cannot evaluate %q: %w`, fe.AppendString(nil), err),
}
}
@ -407,7 +408,7 @@ func evalAggrFunc(qt *querytracer.Tracer, ec *EvalConfig, ae *metricsql.AggrFunc
}
af := getAggrFunc(ae.Name)
if af == nil {
return nil, &UserReadableError{
return nil, &httpserver.UserReadableError{
Err: fmt.Errorf(`unknown func %q`, ae.Name),
}
}
@ -802,12 +803,12 @@ func evalRollupFunc(qt *querytracer.Tracer, ec *EvalConfig, funcName string, rf
}
tssAt, err := evalExpr(qt, ec, re.At)
if err != nil {
return nil, &UserReadableError{
return nil, &httpserver.UserReadableError{
Err: fmt.Errorf("cannot evaluate `@` modifier: %w", err),
}
}
if len(tssAt) != 1 {
return nil, &UserReadableError{
return nil, &httpserver.UserReadableError{
Err: fmt.Errorf("`@` modifier must return a single series; it returns %d series instead", len(tssAt)),
}
}
@ -869,7 +870,7 @@ func evalRollupFuncWithoutAt(qt *querytracer.Tracer, ec *EvalConfig, funcName st
rvs, err = evalRollupFuncWithSubquery(qt, ecNew, funcName, rf, expr, re)
}
if err != nil {
return nil, &UserReadableError{
return nil, &httpserver.UserReadableError{
Err: err,
}
}
@ -1601,7 +1602,7 @@ func evalRollupFuncWithMetricExpr(qt *querytracer.Tracer, ec *EvalConfig, funcNa
if ec.Start == ec.End {
rvs, err := evalInstantRollup(qt, ec, funcName, rf, expr, me, iafc, window)
if err != nil {
err = &UserReadableError{
err = &httpserver.UserReadableError{
Err: err,
}
return nil, err
@ -1612,7 +1613,7 @@ func evalRollupFuncWithMetricExpr(qt *querytracer.Tracer, ec *EvalConfig, funcNa
evalWithConfig := func(ec *EvalConfig) ([]*timeseries, error) {
tss, err := evalRollupFuncNoCache(qt, ec, funcName, rf, expr, me, iafc, window, pointsPerSeries)
if err != nil {
err = &UserReadableError{
err = &httpserver.UserReadableError{
Err: err,
}
return nil, err

View file

@ -35,24 +35,6 @@ var (
"Such conversion can be disabled using -search.disableImplicitConversion.")
)
// UserReadableError is a type of error which supposed to be returned to the user without additional context.
type UserReadableError struct {
// Err is the error which needs to be returned to the user.
Err error
}
// Unwrap returns ure.Err.
//
// This is used by standard errors package. See https://golang.org/pkg/errors
func (ure *UserReadableError) Unwrap() error {
return ure.Err
}
// Error satisfies Error interface
func (ure *UserReadableError) Error() string {
return ure.Err.Error()
}
// Exec executes q for the given ec.
func Exec(qt *querytracer.Tracer, ec *EvalConfig, q string, isFirstPointOnly bool) ([]netstorage.Result, error) {
if querystats.Enabled() {

View file

@ -3027,6 +3027,12 @@ func TestExecSuccess(t *testing.T) {
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`1 and (0 > 1)`, func(t *testing.T) {
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6637
t.Parallel()
q := `1 and (0 > 1)`
f(q, nil)
})
t.Run(`time() and 2`, func(t *testing.T) {
t.Parallel()
q := `time() and 2`
@ -7245,6 +7251,17 @@ func TestExecSuccess(t *testing.T) {
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`range_trim_outliers(time() > 1200)`, func(t *testing.T) {
t.Parallel()
q := `range_trim_outliers(0.5, time() > 1200)`
r := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{nan, nan, nan, 1600, 1800, nan},
Timestamps: timestampsExpected,
}
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`range_trim_spikes()`, func(t *testing.T) {
t.Parallel()
q := `range_trim_spikes(0.2, time())`
@ -7256,6 +7273,17 @@ func TestExecSuccess(t *testing.T) {
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`range_trim_spikes(time() > 1200 <= 1800)`, func(t *testing.T) {
t.Parallel()
q := `range_trim_spikes(0.2, time() > 1200 <= 1800)`
r := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{nan, nan, nan, 1600, nan, nan},
Timestamps: timestampsExpected,
}
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`range_trim_zscore()`, func(t *testing.T) {
t.Parallel()
q := `range_trim_zscore(0.9, time())`
@ -7267,6 +7295,17 @@ func TestExecSuccess(t *testing.T) {
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`range_trim_zscore(time() > 1200 <= 1800)`, func(t *testing.T) {
t.Parallel()
q := `range_trim_zscore(0.9, time() > 1200 <= 1800)`
r := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{nan, nan, nan, 1600, nan, nan},
Timestamps: timestampsExpected,
}
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`range_zscore()`, func(t *testing.T) {
t.Parallel()
q := `round(range_zscore(time()), 0.1)`
@ -7278,6 +7317,17 @@ func TestExecSuccess(t *testing.T) {
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`range_zscore(time() > 1200 < 1800)`, func(t *testing.T) {
t.Parallel()
q := `round(range_zscore(time() > 1200 < 1800), 0.1)`
r := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{nan, nan, -1, 1, nan, nan},
Timestamps: timestampsExpected,
}
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`range_quantile(0.5)`, func(t *testing.T) {
t.Parallel()
q := `range_quantile(0.5, time())`
@ -7289,6 +7339,17 @@ func TestExecSuccess(t *testing.T) {
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`range_quantile(0.5, time() > 1200 < 2000)`, func(t *testing.T) {
t.Parallel()
q := `range_quantile(0.5, time() > 1200 < 2000)`
r := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{1600, 1600, 1600, 1600, 1600, 1600},
Timestamps: timestampsExpected,
}
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`range_stddev()`, func(t *testing.T) {
t.Parallel()
q := `round(range_stddev(time()),0.01)`
@ -7300,6 +7361,17 @@ func TestExecSuccess(t *testing.T) {
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`range_stddev(time() > 1200 < 1800)`, func(t *testing.T) {
t.Parallel()
q := `round(range_stddev(time() > 1200 < 1800),0.01)`
r := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{100, 100, 100, 100, 100, 100},
Timestamps: timestampsExpected,
}
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`range_stdvar()`, func(t *testing.T) {
t.Parallel()
q := `round(range_stdvar(time()),0.01)`
@ -7311,6 +7383,17 @@ func TestExecSuccess(t *testing.T) {
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`range_stdvar(time() > 1200 < 1800)`, func(t *testing.T) {
t.Parallel()
q := `round(range_stdvar(time() > 1200 < 1800),0.01)`
r := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{10000, 10000, 10000, 10000, 10000, 10000},
Timestamps: timestampsExpected,
}
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`range_median()`, func(t *testing.T) {
t.Parallel()
q := `range_median(time())`
@ -7671,6 +7754,17 @@ func TestExecSuccess(t *testing.T) {
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`running_min(abs(1500-time()) < 400 > 100)`, func(t *testing.T) {
t.Parallel()
q := `running_min(abs(1500-time()) < 400 > 100)`
r := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{nan, 300, 300, 300, 300, 300},
Timestamps: timestampsExpected,
}
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`running_max(abs(1300-time()))`, func(t *testing.T) {
t.Parallel()
q := `running_max(abs(1300-time()))`
@ -7682,6 +7776,17 @@ func TestExecSuccess(t *testing.T) {
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`running_max(abs(1300-time()) > 300 < 700)`, func(t *testing.T) {
t.Parallel()
q := `running_max(abs(1300-time()) > 300 < 700)`
r := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{nan, nan, nan, nan, 500, 500},
Timestamps: timestampsExpected,
}
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`running_sum(1)`, func(t *testing.T) {
t.Parallel()
q := `running_sum(1)`
@ -7704,6 +7809,17 @@ func TestExecSuccess(t *testing.T) {
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`running_sum(time() > 1.2 < 1.8)`, func(t *testing.T) {
t.Parallel()
q := `running_sum(time()/1e3 > 1.2 < 1.8)`
r := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{nan, nan, 1.4, 3, 3, 3},
Timestamps: timestampsExpected,
}
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`running_avg(time())`, func(t *testing.T) {
t.Parallel()
q := `running_avg(time())`
@ -7715,6 +7831,17 @@ func TestExecSuccess(t *testing.T) {
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`running_avg(time() > 1200 < 1800)`, func(t *testing.T) {
t.Parallel()
q := `running_avg(time() > 1200 < 1800)`
r := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{nan, nan, 1400, 1500, 1500, 1500},
Timestamps: timestampsExpected,
}
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`smooth_exponential(time(), 1)`, func(t *testing.T) {
t.Parallel()
q := `smooth_exponential(time(), 1)`
@ -7795,6 +7922,17 @@ func TestExecSuccess(t *testing.T) {
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`range_min(time() > 1200 < 1800)`, func(t *testing.T) {
t.Parallel()
q := `range_min(time() > 1200 < 1800)`
r := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{1400, 1400, 1400, 1400, 1400, 1400},
Timestamps: timestampsExpected,
}
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`range_normalize(time(),alias(-time(),"negative"))`, func(t *testing.T) {
t.Parallel()
q := `range_normalize(time(),alias(-time(), "negative"))`
@ -7812,6 +7950,23 @@ func TestExecSuccess(t *testing.T) {
resultExpected := []netstorage.Result{r1, r2}
f(q, resultExpected)
})
t.Run(`range_normalize(time() > 1200 < 1800,alias(-(time() > 1400 < 2000),"negative"))`, func(t *testing.T) {
t.Parallel()
q := `range_normalize(time() > 1200 < 1800,alias(-(time() > 1200 < 2000), "negative"))`
r1 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{nan, nan, 0, 1, nan, nan},
Timestamps: timestampsExpected,
}
r2 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{nan, nan, 1, 0.5, 0, nan},
Timestamps: timestampsExpected,
}
r2.MetricName.MetricGroup = []byte("negative")
resultExpected := []netstorage.Result{r1, r2}
f(q, resultExpected)
})
t.Run(`range_first(time())`, func(t *testing.T) {
t.Parallel()
q := `range_first(time())`
@ -7823,6 +7978,17 @@ func TestExecSuccess(t *testing.T) {
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`range_first(time() > 1200 < 1800)`, func(t *testing.T) {
t.Parallel()
q := `range_first(time() > 1200 < 1800)`
r := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{1400, 1400, 1400, 1400, 1400, 1400},
Timestamps: timestampsExpected,
}
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`range_mad(time())`, func(t *testing.T) {
t.Parallel()
q := `range_mad(time())`
@ -7834,6 +8000,17 @@ func TestExecSuccess(t *testing.T) {
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`range_mad(time() > 1200 < 1800)`, func(t *testing.T) {
t.Parallel()
q := `range_mad(time() > 1200 < 1800)`
r := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{100, 100, 100, 100, 100, 100},
Timestamps: timestampsExpected,
}
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`range_max(time())`, func(t *testing.T) {
t.Parallel()
q := `range_max(time())`
@ -7845,6 +8022,39 @@ func TestExecSuccess(t *testing.T) {
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`range_max(time() > 1200 < 1800)`, func(t *testing.T) {
t.Parallel()
q := `range_max(time() > 1200 < 1800)`
r := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{1600, 1600, 1600, 1600, 1600, 1600},
Timestamps: timestampsExpected,
}
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`range_sum(time())`, func(t *testing.T) {
t.Parallel()
q := `range_sum(time())`
r := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{9000, 9000, 9000, 9000, 9000, 9000},
Timestamps: timestampsExpected,
}
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`range_sum(time() > 1200 < 1800)`, func(t *testing.T) {
t.Parallel()
q := `range_sum(time() > 1200 < 1800)`
r := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{3000, 3000, 3000, 3000, 3000, 3000},
Timestamps: timestampsExpected,
}
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`range_last(time())`, func(t *testing.T) {
t.Parallel()
q := `range_last(time())`
@ -7856,6 +8066,17 @@ func TestExecSuccess(t *testing.T) {
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`range_last(time() > 1200 < 1800)`, func(t *testing.T) {
t.Parallel()
q := `range_last(time() > 1200 < 1800)`
r := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{1600, 1600, 1600, 1600, 1600, 1600},
Timestamps: timestampsExpected,
}
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`range_linear_regression(time())`, func(t *testing.T) {
t.Parallel()
q := `range_linear_regression(time())`
@ -7878,6 +8099,17 @@ func TestExecSuccess(t *testing.T) {
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`range_linear_regression(time() > 1200 < 1800)`, func(t *testing.T) {
t.Parallel()
q := `range_linear_regression(time() > 1200 < 1800)`
r := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{1000, 1200, 1400, 1600, 1800, 2000},
Timestamps: timestampsExpected,
}
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`range_linear_regression(100/time())`, func(t *testing.T) {
t.Parallel()
q := `sort_desc(round((

View file

@ -1544,10 +1544,8 @@ func transformRangeFirst(tfa *transformFuncArg) ([]*timeseries, error) {
continue
}
vFirst := values[0]
for i, v := range values {
if math.IsNaN(v) {
continue
}
values = ts.Values
for i := range values {
values[i] = vFirst
}
}
@ -1571,10 +1569,8 @@ func setLastValues(tss []*timeseries) {
continue
}
vLast := values[len(values)-1]
for i, v := range values {
if math.IsNaN(v) {
continue
}
values = ts.Values
for i := range values {
values[i] = vLast
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View file

@ -1,13 +1,13 @@
{
"files": {
"main.css": "./static/css/main.81c6ec3a.css",
"main.js": "./static/js/main.9ed27532.js",
"static/js/685.bebe1265.chunk.js": "./static/js/685.bebe1265.chunk.js",
"static/media/MetricsQL.md": "./static/media/MetricsQL.8c2e588d62b87f90dbf0.md",
"main.js": "./static/js/main.0e69b7a9.js",
"static/js/685.f772060c.chunk.js": "./static/js/685.f772060c.chunk.js",
"static/media/MetricsQL.md": "./static/media/MetricsQL.a00044c91d9781cf8557.md",
"index.html": "./index.html"
},
"entrypoints": [
"static/css/main.81c6ec3a.css",
"static/js/main.9ed27532.js"
"static/js/main.0e69b7a9.js"
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1 @@
<svg width="48" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M24.5475 0C10.3246.0265251 1.11379 3.06365 4.40623 6.10077c0 0 12.32997 11.23333 16.58217 14.84083.8131.6896 2.1728 1.1936 3.5191 1.2201h.1199c1.3463-.0265 2.706-.5305 3.5191-1.2201 4.2522-3.5942 16.5422-14.84083 16.5422-14.84083C48.0478 3.06365 38.8636.0265251 24.6674 0" fill="#020202"/><path d="M28.1579 27.0159c-.8131.6896-2.1728 1.1936-3.5191 1.2201h-.12c-1.3463-.0265-2.7059-.5305-3.519-1.2201-2.9725-2.5067-13.35639-11.87-17.26201-15.3979v5.4112c0 .5968.22661 1.3793.6265 1.7506C7.00358 21.1936 17.2675 30.5437 20.9731 33.6737c.8132.6896 2.1728 1.1936 3.5191 1.2201h.12c1.3463-.0265 2.7059-.5305 3.519-1.2201 3.679-3.13 13.9429-12.4536 16.6089-14.8939.4132-.3713.6265-1.1538.6265-1.7506V11.618c-3.9323 3.5411-14.3162 12.931-17.2354 15.3979h.0267Z" fill="#020202"/><path d="M28.1579 39.748c-.8131.6897-2.1728 1.1937-3.5191 1.2202h-.12c-1.3463-.0265-2.7059-.5305-3.519-1.2202-2.9725-2.4933-13.35639-11.8567-17.26201-15.3978v5.4111c0 .5969.22661 1.3793.6265 1.7507C7.00358 33.9258 17.2675 43.2759 20.9731 46.4058c.8132.6897 2.1728 1.1937 3.5191 1.2202h.12c1.3463-.0265 2.7059-.5305 3.519-1.2202 3.679-3.1299 13.9429-12.4535 16.6089-14.8938.4132-.3714.6265-1.1538.6265-1.7507v-5.4111c-3.9323 3.5411-14.3162 12.931-17.2354 15.3978h.0267Z" fill="#020202"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

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=5"/><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.9ed27532.js"></script><link href="./static/css/main.81c6ec3a.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.svg"/><link rel="apple-touch-icon" href="./favicon.svg"/><link rel="mask-icon" href="./favicon.svg" color="#000000"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=5"/><meta name="theme-color" content="#000000"/><meta name="description" content="Explore and troubleshoot your VictoriaMetrics data"/><link rel="manifest" href="./manifest.json"/><title>vmui</title><script src="./dashboards/index.js" type="module"></script><meta name="twitter:card" content="summary"><meta name="twitter:title" content="UI for VictoriaMetrics"><meta name="twitter:site" content="@https://victoriametrics.com/"><meta name="twitter:description" content="Explore and troubleshoot your VictoriaMetrics data"><meta name="twitter:image" content="./preview.jpg"><meta property="og:type" content="website"><meta property="og:title" content="UI for VictoriaMetrics"><meta property="og:url" content="https://victoriametrics.com/"><meta property="og:description" content="Explore and troubleshoot your VictoriaMetrics data"><script defer="defer" src="./static/js/main.0e69b7a9.js"></script><link href="./static/css/main.81c6ec3a.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

View file

@ -1,16 +1,11 @@
{
"short_name": "Victoria Metrics UI",
"name": "Victoria Metrics UI is a metric explorer for Victoria Metrics",
"short_name": "vmui",
"name": "vmui",
"icons": [
{
"src": "favicon-32x32.png",
"sizes": "32x32",
"type": "image/png"
},
{
"src": "apple-touch-icon.png",
"type": "image/png",
"sizes": "192x192"
"src": "favicon.svg",
"sizes": "any",
"type": "image/svg+xml"
}
],
"start_url": ".",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

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

@ -5,7 +5,7 @@
*/
/**
* @remix-run/router v1.17.0
* @remix-run/router v1.19.2
*
* Copyright (c) Remix Software Inc.
*
@ -16,7 +16,7 @@
*/
/**
* React Router DOM v6.24.0
* React Router DOM v6.26.2
*
* Copyright (c) Remix Software Inc.
*
@ -27,7 +27,7 @@
*/
/**
* React Router v6.24.0
* React Router v6.26.2
*
* Copyright (c) Remix Software Inc.
*

File diff suppressed because one or more lines are too long

View file

@ -103,7 +103,7 @@ The list of MetricsQL features on top of PromQL:
* Trailing commas on all the lists are allowed - label filters, function args and with expressions.
For instance, the following queries are valid: `m{foo="bar",}`, `f(a, b,)`, `WITH (x=y,) x`.
This simplifies maintenance of multi-line queries.
* Metric names and label names may contain any unicode letter. For example `температура{город="Київ"}` is a valid MetricsQL expression.
* Metric names and label names may contain any unicode letter. For example `ტემპერატურა{πόλη="Київ"}` is a valid MetricsQL expression.
* Metric names and labels names may contain escaped chars. For example, `foo\-bar{baz\=aa="b"}` is valid expression.
It returns time series with name `foo-bar` containing label `baz=aa` with value `b`.
Additionally, the following escape sequences are supported:

View file

@ -1,55 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<link rel="icon" href="%PUBLIC_URL%/favicon.ico"/>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5"/>
<meta name="theme-color" content="#000000"/>
<meta
name="description"
content="UI for VictoriaMetrics"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/apple-touch-icon.png"/>
<link rel="icon" type="image/png" sizes="32x32" href="%PUBLIC_URL%/favicon-32x32.png">
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json"/>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>VM UI</title>
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="%PUBLIC_URL%/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="%PUBLIC_URL%/preview.jpg">
<meta property="og:type" content="website">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View file

@ -521,12 +521,21 @@ func writeStorageMetrics(w io.Writer, strg *storage.Storage) {
metrics.WriteGaugeUint64(w, `vm_data_size_bytes{type="indexdb/inmemory"}`, idbm.InmemorySizeBytes)
metrics.WriteGaugeUint64(w, `vm_data_size_bytes{type="indexdb/file"}`, idbm.FileSizeBytes)
metrics.WriteCounterUint64(w, `vm_rows_received_by_storage_total`, m.RowsReceivedTotal)
metrics.WriteCounterUint64(w, `vm_rows_added_to_storage_total`, m.RowsAddedTotal)
metrics.WriteCounterUint64(w, `vm_deduplicated_samples_total{type="merge"}`, m.DedupsDuringMerge)
metrics.WriteGaugeUint64(w, `vm_snapshots`, m.SnapshotsCount)
metrics.WriteCounterUint64(w, `vm_rows_ignored_total{reason="nan_value"}`, m.NaNValueRows)
metrics.WriteCounterUint64(w, `vm_rows_ignored_total{reason="big_timestamp"}`, m.TooBigTimestampRows)
metrics.WriteCounterUint64(w, `vm_rows_ignored_total{reason="small_timestamp"}`, m.TooSmallTimestampRows)
metrics.WriteCounterUint64(w, `vm_rows_ignored_total{reason="invalid_raw_metric_name"}`, m.InvalidRawMetricNames)
if *maxHourlySeries > 0 {
metrics.WriteCounterUint64(w, `vm_rows_ignored_total{reason="hourly_limit_exceeded"}`, m.HourlySeriesLimitRowsDropped)
}
if *maxDailySeries > 0 {
metrics.WriteCounterUint64(w, `vm_rows_ignored_total{reason="daily_limit_exceeded"}`, m.DailySeriesLimitRowsDropped)
}
metrics.WriteCounterUint64(w, `vm_timeseries_repopulated_total`, m.TimeseriesRepopulated)
metrics.WriteCounterUint64(w, `vm_timeseries_precreated_total`, m.TimeseriesPreCreated)

View file

@ -1,4 +1,4 @@
FROM golang:1.23.0 AS build-web-stage
FROM golang:1.23.1 AS build-web-stage
COPY build /build
WORKDIR /build
@ -6,7 +6,7 @@ COPY web/ /build/
RUN GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o web-amd64 github.com/VictoriMetrics/vmui/ && \
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -o web-windows github.com/VictoriMetrics/vmui/
FROM alpine:3.20.2
FROM alpine:3.20.3
USER root
COPY --from=build-web-stage /build/web-amd64 /app/web

View file

@ -10,6 +10,7 @@
# production
/build
/public/index.html
# misc
.DS_Store

View file

@ -8,11 +8,11 @@ const path = require('path');
const pathIndexHTML = (() => {
switch (process.env.REACT_APP_TYPE) {
case 'logs':
return 'public/victorialogs.html';
return 'src/html/victorialogs.html';
case 'anomaly':
return 'public/vmanomaly.html';
return 'src/html/vmanomaly.html';
default:
return 'public/victoriametrics.html';
return 'src/html/victoriametrics.html';
}
})();
const fileContent = fs.readFileSync(path.resolve(__dirname, pathIndexHTML), 'utf8');

View file

@ -8,45 +8,45 @@
"name": "vmui",
"version": "0.1.0",
"dependencies": {
"@types/lodash.debounce": "^4.0.6",
"@types/lodash.get": "^4.4.6",
"@types/lodash.throttle": "^4.1.6",
"@types/node": "^20.4.0",
"@types/qs": "^6.9.7",
"@types/react-input-mask": "^3.0.2",
"@types/lodash.debounce": "^4.0.9",
"@types/lodash.get": "^4.4.9",
"@types/lodash.throttle": "^4.1.9",
"@types/node": "^22.5.4",
"@types/qs": "^6.9.15",
"@types/react-input-mask": "^3.0.5",
"@types/react-router-dom": "^5.3.3",
"@types/webpack-env": "^1.16.3",
"classnames": "^2.3.2",
"dayjs": "^1.11.0",
"@types/webpack-env": "^1.18.5",
"classnames": "^2.5.1",
"dayjs": "^1.11.13",
"lodash.debounce": "^4.0.8",
"lodash.get": "^4.4.2",
"lodash.throttle": "^4.1.1",
"marked": "^12.0.2",
"marked-emoji": "^1.4.0",
"preact": "^10.7.1",
"qs": "^6.10.3",
"marked": "^14.1.2",
"marked-emoji": "^1.4.2",
"preact": "^10.23.2",
"qs": "^6.13.0",
"react-input-mask": "^2.0.4",
"react-router-dom": "^6.10.0",
"sass": "^1.56.0",
"react-router-dom": "^6.26.2",
"sass": "^1.78.0",
"source-map-explorer": "^2.5.3",
"typescript": "~4.6.2",
"uplot": "^1.6.30",
"web-vitals": "^3.3.2"
"web-vitals": "^4.2.3"
},
"devDependencies": {
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7",
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@types/react-dom": "^18.2.6",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^5.15.0",
"@typescript-eslint/parser": "^5.15.0",
"cross-env": "^7.0.3",
"customize-cra": "^1.0.0",
"eslint": "^8.44.0",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-react": "^7.29.4",
"http-proxy-middleware": "^3.0.0",
"eslint-plugin-react": "^7.36.1",
"http-proxy-middleware": "^3.0.2",
"react-app-rewired": "^2.2.1",
"webpack": "^5.88.1"
"webpack": "^5.94.0"
}
},
"node_modules/@alloc/quick-lru": {
@ -3651,9 +3651,9 @@
}
},
"node_modules/@remix-run/router": {
"version": "1.17.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.17.0.tgz",
"integrity": "sha512-2D6XaHEVvkCn682XBnipbJjgZUU7xjLtA4dGJRBVUKpEaDYOZMENZoZjAOSb7qirxt5RupjzZxz4fK2FO+EFPw==",
"version": "1.19.2",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.2.tgz",
"integrity": "sha512-baiMx18+IMuD1yyvOGaHM9QrVUPGGG0jC+z+IPHnRJWUAUvaKuWKyE8gjDj2rzv3sz9zOGoRSPgeBVHRhZnBlA==",
"engines": {
"node": ">=14.0.0"
}
@ -4135,21 +4135,12 @@
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz",
"integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==",
"dev": true,
"peer": true,
"dependencies": {
"@types/estree": "*",
"@types/json-schema": "*"
}
},
"node_modules/@types/eslint-scope": {
"version": "3.7.7",
"resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz",
"integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==",
"dev": true,
"dependencies": {
"@types/eslint": "*",
"@types/estree": "*"
}
},
"node_modules/@types/estree": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
@ -4212,9 +4203,9 @@
"peer": true
},
"node_modules/@types/http-proxy": {
"version": "1.17.14",
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz",
"integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==",
"version": "1.17.15",
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.15.tgz",
"integrity": "sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==",
"dev": true,
"dependencies": {
"@types/node": "*"
@ -4296,11 +4287,11 @@
"peer": true
},
"node_modules/@types/node": {
"version": "20.14.8",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.8.tgz",
"integrity": "sha512-DO+2/jZinXfROG7j7WKFn/3C6nFwxy2lLpgLjEXJz+0XKphZlTLJ14mo8Vfg8X5BWN6XjyESXq+LcYdT7tR3bA==",
"version": "22.5.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz",
"integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
"dependencies": {
"undici-types": "~5.26.4"
"undici-types": "~6.19.2"
}
},
"node_modules/@types/node-forge": {
@ -5357,18 +5348,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/array.prototype.toreversed": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz",
"integrity": "sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA==",
"dev": true,
"dependencies": {
"call-bind": "^1.0.2",
"define-properties": "^1.2.0",
"es-abstract": "^1.22.1",
"es-shim-unscopables": "^1.0.0"
}
},
"node_modules/array.prototype.tosorted": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz",
@ -7143,17 +7122,17 @@
}
},
"node_modules/dayjs": {
"version": "1.11.11",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz",
"integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg=="
"version": "1.11.13",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="
},
"node_modules/debug": {
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
"integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"dev": true,
"dependencies": {
"ms": "2.1.2"
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
@ -7626,9 +7605,9 @@
}
},
"node_modules/enhanced-resolve": {
"version": "5.17.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz",
"integrity": "sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==",
"version": "5.17.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz",
"integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.2.4",
@ -8165,35 +8144,35 @@
}
},
"node_modules/eslint-plugin-react": {
"version": "7.34.3",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.3.tgz",
"integrity": "sha512-aoW4MV891jkUulwDApQbPYTVZmeuSyFrudpbTAQuj5Fv8VL+o6df2xIGpw8B0hPjAaih1/Fb0om9grCdyFYemA==",
"version": "7.36.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.36.1.tgz",
"integrity": "sha512-/qwbqNXZoq+VP30s1d4Nc1C5GTxjJQjk4Jzs4Wq2qzxFM7dSmuG2UkIjg2USMLh3A/aVcUNrK7v0J5U1XEGGwA==",
"dev": true,
"dependencies": {
"array-includes": "^3.1.8",
"array.prototype.findlast": "^1.2.5",
"array.prototype.flatmap": "^1.3.2",
"array.prototype.toreversed": "^1.1.2",
"array.prototype.tosorted": "^1.1.4",
"doctrine": "^2.1.0",
"es-iterator-helpers": "^1.0.19",
"estraverse": "^5.3.0",
"hasown": "^2.0.2",
"jsx-ast-utils": "^2.4.1 || ^3.0.0",
"minimatch": "^3.1.2",
"object.entries": "^1.1.8",
"object.fromentries": "^2.0.8",
"object.hasown": "^1.1.4",
"object.values": "^1.2.0",
"prop-types": "^15.8.1",
"resolve": "^2.0.0-next.5",
"semver": "^6.3.1",
"string.prototype.matchall": "^4.0.11"
"string.prototype.matchall": "^4.0.11",
"string.prototype.repeat": "^1.0.0"
},
"engines": {
"node": ">=4"
},
"peerDependencies": {
"eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8"
"eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7"
}
},
"node_modules/eslint-plugin-react-hooks": {
@ -9931,17 +9910,17 @@
}
},
"node_modules/http-proxy-middleware": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.0.tgz",
"integrity": "sha512-36AV1fIaI2cWRzHo+rbcxhe3M3jUDCNzc4D5zRl57sEWRAxdXYtw7FSQKYY6PDKssiAKjLYypbssHk+xs/kMXw==",
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.2.tgz",
"integrity": "sha512-fBLFpmvDzlxdckwZRjM0wWtwDZ4KBtQ8NFqhrFKoEtK4myzuiumBuNTxD+F4cVbXfOZljIbrynmvByofDzT7Ag==",
"dev": true,
"dependencies": {
"@types/http-proxy": "^1.17.10",
"debug": "^4.3.4",
"@types/http-proxy": "^1.17.15",
"debug": "^4.3.6",
"http-proxy": "^1.18.1",
"is-glob": "^4.0.1",
"is-plain-obj": "^3.0.0",
"micromatch": "^4.0.5"
"is-glob": "^4.0.3",
"is-plain-object": "^5.0.0",
"micromatch": "^4.0.8"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
@ -10446,6 +10425,7 @@
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz",
"integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==",
"dev": true,
"peer": true,
"engines": {
"node": ">=10"
},
@ -10453,6 +10433,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-plain-object": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-potential-custom-element-name": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
@ -13541,9 +13530,9 @@
}
},
"node_modules/marked": {
"version": "12.0.2",
"resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz",
"integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==",
"version": "14.1.2",
"resolved": "https://registry.npmjs.org/marked/-/marked-14.1.2.tgz",
"integrity": "sha512-f3r0yqpz31VXiDB/wj9GaOB0a2PRLQl6vJmXiFrniNwjkKdvakqJRULhjFKJpxOchlCRiG5fcacoUZY5Xa6PEQ==",
"bin": {
"marked": "bin/marked.js"
},
@ -13552,11 +13541,11 @@
}
},
"node_modules/marked-emoji": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/marked-emoji/-/marked-emoji-1.4.1.tgz",
"integrity": "sha512-3xHWQn8XD1LyhMpHxWpHTDWBZ9bpXLlW8JIqvyXTO6he7okKIB/W9fD/3fTg0DQuZlSQvPZ6Ub5hN6Rnmn7j9g==",
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/marked-emoji/-/marked-emoji-1.4.2.tgz",
"integrity": "sha512-2sP+bp2z76dwbILzQ7ijy2PyjjAJR3iAZCzaNGThD2UijFUBeidkn6MoCdX/j47tPIcWt9nwnjqRQPd01ZrfdA==",
"peerDependencies": {
"marked": ">=4 <14"
"marked": ">=4 <15"
}
},
"node_modules/mdn-data": {
@ -13622,9 +13611,9 @@
}
},
"node_modules/micromatch": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
"integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
"dependencies": {
"braces": "^3.0.3",
@ -13747,9 +13736,9 @@
}
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true
},
"node_modules/multicast-dns": {
@ -14064,23 +14053,6 @@
"node": ">= 0.4"
}
},
"node_modules/object.hasown": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.4.tgz",
"integrity": "sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg==",
"dev": true,
"dependencies": {
"define-properties": "^1.2.1",
"es-abstract": "^1.23.2",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/object.values": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz",
@ -15962,9 +15934,9 @@
"peer": true
},
"node_modules/preact": {
"version": "10.22.0",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.22.0.tgz",
"integrity": "sha512-RRurnSjJPj4rp5K6XoP45Ui33ncb7e4H7WiOHVpjbkvqvA3U+N8Z6Qbo0AE6leGYBV66n8EhEaFixvIu3SkxFw==",
"version": "10.23.2",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.23.2.tgz",
"integrity": "sha512-kKYfePf9rzKnxOAKDpsWhg/ysrHPqT+yQ7UW4JjdnqjFIeNUnNcEJvhuA8fDenxAGWzUqtd51DfVg7xp/8T9NA==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
@ -16133,9 +16105,9 @@
}
},
"node_modules/qs": {
"version": "6.12.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz",
"integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==",
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"dependencies": {
"side-channel": "^1.0.6"
},
@ -16487,11 +16459,11 @@
}
},
"node_modules/react-router": {
"version": "6.24.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.24.0.tgz",
"integrity": "sha512-sQrgJ5bXk7vbcC4BxQxeNa5UmboFm35we1AFK0VvQaz9g0LzxEIuLOhHIoZ8rnu9BO21ishGeL9no1WB76W/eg==",
"version": "6.26.2",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.2.tgz",
"integrity": "sha512-tvN1iuT03kHgOFnLPfLJ8V95eijteveqdOSk+srqfePtQvqCExB8eHOYnlilbOcyJyKnYkr1vJvf7YqotAJu1A==",
"dependencies": {
"@remix-run/router": "1.17.0"
"@remix-run/router": "1.19.2"
},
"engines": {
"node": ">=14.0.0"
@ -16501,12 +16473,12 @@
}
},
"node_modules/react-router-dom": {
"version": "6.24.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.24.0.tgz",
"integrity": "sha512-960sKuau6/yEwS8e+NVEidYQb1hNjAYM327gjEyXlc6r3Skf2vtwuJ2l7lssdegD2YjoKG5l8MsVyeTDlVeY8g==",
"version": "6.26.2",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.26.2.tgz",
"integrity": "sha512-z7YkaEW0Dy35T3/QKPYB1LjMK2R1fxnHO8kWpUMTBdfVzZrWOiY9a7CtN8HqdWtDUWd5FY6Dl8HFsqVwH4uOtQ==",
"dependencies": {
"@remix-run/router": "1.17.0",
"react-router": "6.24.0"
"@remix-run/router": "1.19.2",
"react-router": "6.26.2"
},
"engines": {
"node": ">=14.0.0"
@ -17157,9 +17129,9 @@
"peer": true
},
"node_modules/sass": {
"version": "1.77.6",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.77.6.tgz",
"integrity": "sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==",
"version": "1.78.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.78.0.tgz",
"integrity": "sha512-AaIqGSrjo5lA2Yg7RvFZrlXDBCp3nV4XP73GrLGvdRWWwk+8H3l0SDvq/5bA4eF+0RFPLuWUk3E+P1U/YqnpsQ==",
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
@ -17369,13 +17341,6 @@
"dev": true,
"peer": true
},
"node_modules/send/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"peer": true
},
"node_modules/serialize-javascript": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
@ -18105,6 +18070,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/string.prototype.repeat": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz",
"integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==",
"dev": true,
"dependencies": {
"define-properties": "^1.1.3",
"es-abstract": "^1.17.5"
}
},
"node_modules/string.prototype.trim": {
"version": "1.2.9",
"resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz",
@ -19118,9 +19093,9 @@
"peer": true
},
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
"version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="
},
"node_modules/unicode-canonical-property-names-ecmascript": {
"version": "2.0.0",
@ -19416,9 +19391,9 @@
}
},
"node_modules/web-vitals": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-3.5.2.tgz",
"integrity": "sha512-c0rhqNcHXRkY/ogGDJQxZ9Im9D19hDihbzSQJrsioex+KnFgmMzBiy57Z1EjkhX/+OjyBpclDCzz2ITtjokFmg=="
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.3.tgz",
"integrity": "sha512-/CFAm1mNxSmOj6i0Co+iGFJ58OS4NRGVP+AWS/l509uIK5a1bSoIVaHz/ZumpHTfHSZBpgrJ+wjfpAOrTHok5Q=="
},
"node_modules/webidl-conversions": {
"version": "6.1.0",
@ -19431,12 +19406,11 @@
}
},
"node_modules/webpack": {
"version": "5.92.1",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.92.1.tgz",
"integrity": "sha512-JECQ7IwJb+7fgUFBlrJzbyu3GEuNBcdqr1LD7IbSzwkSmIevTm8PF+wej3Oxuz/JFBUZ6O1o43zsPkwm1C4TmA==",
"version": "5.94.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz",
"integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==",
"dev": true,
"dependencies": {
"@types/eslint-scope": "^3.7.3",
"@types/estree": "^1.0.5",
"@webassemblyjs/ast": "^1.12.1",
"@webassemblyjs/wasm-edit": "^1.12.1",
@ -19445,7 +19419,7 @@
"acorn-import-attributes": "^1.9.5",
"browserslist": "^4.21.10",
"chrome-trace-event": "^1.0.2",
"enhanced-resolve": "^5.17.0",
"enhanced-resolve": "^5.17.1",
"es-module-lexer": "^1.2.1",
"eslint-scope": "5.1.1",
"events": "^3.2.0",

View file

@ -4,30 +4,30 @@
"private": true,
"homepage": "./",
"dependencies": {
"@types/lodash.debounce": "^4.0.6",
"@types/lodash.get": "^4.4.6",
"@types/lodash.throttle": "^4.1.6",
"@types/node": "^20.4.0",
"@types/qs": "^6.9.7",
"@types/react-input-mask": "^3.0.2",
"@types/lodash.debounce": "^4.0.9",
"@types/lodash.get": "^4.4.9",
"@types/lodash.throttle": "^4.1.9",
"@types/node": "^22.5.4",
"@types/qs": "^6.9.15",
"@types/react-input-mask": "^3.0.5",
"@types/react-router-dom": "^5.3.3",
"@types/webpack-env": "^1.16.3",
"classnames": "^2.3.2",
"dayjs": "^1.11.0",
"@types/webpack-env": "^1.18.5",
"classnames": "^2.5.1",
"dayjs": "^1.11.13",
"lodash.debounce": "^4.0.8",
"lodash.get": "^4.4.2",
"lodash.throttle": "^4.1.1",
"marked": "^12.0.2",
"marked-emoji": "^1.4.0",
"preact": "^10.7.1",
"qs": "^6.10.3",
"marked": "^14.1.2",
"marked-emoji": "^1.4.2",
"preact": "^10.23.2",
"qs": "^6.13.0",
"react-input-mask": "^2.0.4",
"react-router-dom": "^6.10.0",
"sass": "^1.56.0",
"react-router-dom": "^6.26.2",
"sass": "^1.78.0",
"source-map-explorer": "^2.5.3",
"typescript": "~4.6.2",
"uplot": "^1.6.30",
"web-vitals": "^3.3.2"
"web-vitals": "^4.2.3"
},
"scripts": {
"prestart": "npm run copy-metricsql-docs",
@ -63,17 +63,17 @@
"devDependencies": {
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7",
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@types/react-dom": "^18.2.6",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^5.15.0",
"@typescript-eslint/parser": "^5.15.0",
"cross-env": "^7.0.3",
"customize-cra": "^1.0.0",
"eslint": "^8.44.0",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-react": "^7.29.4",
"http-proxy-middleware": "^3.0.0",
"eslint-plugin-react": "^7.36.1",
"http-proxy-middleware": "^3.0.2",
"react-app-rewired": "^2.2.1",
"webpack": "^5.88.1"
"webpack": "^5.94.0"
},
"overrides": {
"react-app-rewired": {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1 @@
<svg width="48" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M24.5475 0C10.3246.0265251 1.11379 3.06365 4.40623 6.10077c0 0 12.32997 11.23333 16.58217 14.84083.8131.6896 2.1728 1.1936 3.5191 1.2201h.1199c1.3463-.0265 2.706-.5305 3.5191-1.2201 4.2522-3.5942 16.5422-14.84083 16.5422-14.84083C48.0478 3.06365 38.8636.0265251 24.6674 0" fill="#020202"/><path d="M28.1579 27.0159c-.8131.6896-2.1728 1.1936-3.5191 1.2201h-.12c-1.3463-.0265-2.7059-.5305-3.519-1.2201-2.9725-2.5067-13.35639-11.87-17.26201-15.3979v5.4112c0 .5968.22661 1.3793.6265 1.7506C7.00358 21.1936 17.2675 30.5437 20.9731 33.6737c.8132.6896 2.1728 1.1936 3.5191 1.2201h.12c1.3463-.0265 2.7059-.5305 3.519-1.2201 3.679-3.13 13.9429-12.4536 16.6089-14.8939.4132-.3713.6265-1.1538.6265-1.7506V11.618c-3.9323 3.5411-14.3162 12.931-17.2354 15.3979h.0267Z" fill="#020202"/><path d="M28.1579 39.748c-.8131.6897-2.1728 1.1937-3.5191 1.2202h-.12c-1.3463-.0265-2.7059-.5305-3.519-1.2202-2.9725-2.4933-13.35639-11.8567-17.26201-15.3978v5.4111c0 .5969.22661 1.3793.6265 1.7507C7.00358 33.9258 17.2675 43.2759 20.9731 46.4058c.8132.6897 2.1728 1.1937 3.5191 1.2202h.12c1.3463-.0265 2.7059-.5305 3.519-1.2202 3.679-3.1299 13.9429-12.4535 16.6089-14.8938.4132-.3714.6265-1.1538.6265-1.7507v-5.4111c-3.9323 3.5411-14.3162 12.931-17.2354 15.3978h.0267Z" fill="#020202"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -1,16 +1,11 @@
{
"short_name": "Victoria Metrics UI",
"name": "Victoria Metrics UI is a metric explorer for Victoria Metrics",
"short_name": "vmui",
"name": "vmui",
"icons": [
{
"src": "favicon-32x32.png",
"sizes": "32x32",
"type": "image/png"
},
{
"src": "apple-touch-icon.png",
"type": "image/png",
"sizes": "192x192"
"src": "favicon.svg",
"sizes": "any",
"type": "image/svg+xml"
}
],
"start_url": ".",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

View file

@ -1,55 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<link rel="icon" href="%PUBLIC_URL%/favicon.ico"/>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5"/>
<meta name="theme-color" content="#000000"/>
<meta
name="description"
content="UI for VictoriaMetrics"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/apple-touch-icon.png"/>
<link rel="icon" type="image/png" sizes="32x32" href="%PUBLIC_URL%/favicon-32x32.png">
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json"/>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>VM UI</title>
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="%PUBLIC_URL%/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="%PUBLIC_URL%/preview.jpg">
<meta property="og:type" content="website">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View file

@ -1,56 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<link rel="icon" href="%PUBLIC_URL%/favicon.ico"/>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5"/>
<meta name="theme-color" content="#000000"/>
<meta
name="description"
content="UI for VictoriaMetrics"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/apple-touch-icon.png"/>
<link rel="icon" type="image/png" sizes="32x32" href="%PUBLIC_URL%/favicon-32x32.png">
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json"/>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>VM UI</title>
<script src="%PUBLIC_URL%/dashboards/index.js" type="module"></script>
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="%PUBLIC_URL%/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="%PUBLIC_URL%/preview.jpg">
<meta property="og:type" content="website">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View file

@ -1,56 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<link rel="icon" href="%PUBLIC_URL%/favicon.ico"/>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5"/>
<meta name="theme-color" content="#000000"/>
<meta
name="description"
content="UI for VictoriaMetrics"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/apple-touch-icon.png"/>
<link rel="icon" type="image/png" sizes="32x32" href="%PUBLIC_URL%/favicon-32x32.png">
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json"/>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>VM UI</title>
<script src="%PUBLIC_URL%/dashboards/index.js" type="module"></script>
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="%PUBLIC_URL%/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="%PUBLIC_URL%/preview.jpg">
<meta property="og:type" content="website">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View file

@ -103,7 +103,7 @@ The list of MetricsQL features on top of PromQL:
* Trailing commas on all the lists are allowed - label filters, function args and with expressions.
For instance, the following queries are valid: `m{foo="bar",}`, `f(a, b,)`, `WITH (x=y,) x`.
This simplifies maintenance of multi-line queries.
* Metric names and label names may contain any unicode letter. For example `температура{город="Київ"}` is a valid MetricsQL expression.
* Metric names and label names may contain any unicode letter. For example `ტემპერატურა{πόλη="Київ"}` is a valid MetricsQL expression.
* Metric names and labels names may contain escaped chars. For example, `foo\-bar{baz\=aa="b"}` is valid expression.
It returns time series with name `foo-bar` containing label `baz=aa` with value `b`.
Additionally, the following escape sequences are supported:

View file

@ -6,6 +6,7 @@ import classNames from "classnames";
import { MouseEvent } from "react";
import { isMacOs } from "../../../../utils/detect-device";
import Tooltip from "../../../Main/Tooltip/Tooltip";
import { getStreamPairs } from "../../../../utils/logs";
interface Props {
uPlotInst: uPlot;
@ -14,20 +15,26 @@ interface Props {
const BarHitsLegend: FC<Props> = ({ uPlotInst, onApplyFilter }) => {
const [series, setSeries] = useState<Series[]>([]);
const [pairs, setPairs] = useState<string[][]>([]);
const updateSeries = useCallback(() => {
const series = uPlotInst.series.filter(s => s.scale !== "x");
setSeries(series);
setPairs(series.map(s => getStreamPairs(s.label || "")));
}, [uPlotInst]);
const handleClick = (target: Series) => (e: MouseEvent<HTMLDivElement>) => {
const handleClickByValue = (value: string) => (e: MouseEvent<HTMLDivElement>) => {
const metaKey = e.metaKey || e.ctrlKey;
if (!metaKey) {
target.show = !target.show;
} else {
onApplyFilter(target.label || "");
}
if (!metaKey) return;
onApplyFilter(`{${value}}` || "");
updateSeries();
uPlotInst.redraw();
};
const handleClickByStream = (target: Series) => (e: MouseEvent<HTMLDivElement>) => {
const metaKey = e.metaKey || e.ctrlKey;
if (metaKey) return;
target.show = !target.show;
updateSeries();
uPlotInst.redraw();
};
@ -36,7 +43,7 @@ const BarHitsLegend: FC<Props> = ({ uPlotInst, onApplyFilter }) => {
return (
<div className="vm-bar-hits-legend">
{series.map(s => (
{series.map((s, i) => (
<Tooltip
key={s.label}
title={(
@ -51,13 +58,23 @@ const BarHitsLegend: FC<Props> = ({ uPlotInst, onApplyFilter }) => {
"vm-bar-hits-legend-item": true,
"vm-bar-hits-legend-item_hide": !s.show,
})}
onClick={handleClick(s)}
onClick={handleClickByStream(s)}
>
<div
className="vm-bar-hits-legend-item__marker"
style={{ backgroundColor: `${(s?.stroke as () => string)?.()}` }}
/>
<div>{s.label}</div>
<div className="vm-bar-hits-legend-item-pairs">
{pairs[i].map(value => (
<span
className="vm-bar-hits-legend-item-pairs__value"
key={value}
onClick={handleClickByValue(value)}
>
{value}
</span>
))}
</div>
</div>
</Tooltip>
))}

View file

@ -3,16 +3,16 @@
.vm-bar-hits-legend {
display: flex;
flex-wrap: wrap;
gap: 0;
gap: $padding-small;
padding: 0 $padding-small $padding-small;
&-item {
display: grid;
grid-template-columns: auto 1fr;
align-items: center;
gap: 4px;
gap: $padding-small;
font-size: 12px;
padding: $padding-small;
padding: 0 $padding-small;
border-radius: $border-radius-small;
cursor: pointer;
transition: 0.2s;
@ -31,5 +31,30 @@
height: 14px;
border: $color-background-block;
}
&-pairs {
display: flex;
gap: $padding-small;
&__value {
padding: $padding-small 0;
&:hover {
text-decoration: underline;
}
&:after {
content: ",";
}
&:last-child:after {
content: "";
}
}
}
}
&-info {
list-style-position: inside;
}
}

View file

@ -12,12 +12,16 @@ interface Props {
focusDataIdx: number;
}
const timeFormat = (ts: number) => dayjs(ts * 1000).tz().format(DATE_TIME_FORMAT);
const BarHitsTooltip: FC<Props> = ({ data, focusDataIdx, uPlotInst }) => {
const tooltipRef = useRef<HTMLDivElement>(null);
const tooltipData = useMemo(() => {
const series = uPlotInst?.series || [];
const [time, ...values] = data.map((d) => d[focusDataIdx] || 0);
const step = (data[0][1] - data[0][0]);
const timeNext = time + step;
const tooltipItems = values.map((value, i) => {
const targetSeries = series[i + 1];
@ -41,7 +45,7 @@ const BarHitsTooltip: FC<Props> = ({ data, focusDataIdx, uPlotInst }) => {
point,
values: tooltipItems,
total: tooltipItems.reduce((acc, item) => acc + item.value, 0),
timestamp: dayjs(time * 1000).tz().format(DATE_TIME_FORMAT),
timestamp: `${timeFormat(time)} - ${timeFormat(timeNext)}`,
};
}, [focusDataIdx, uPlotInst, data]);

View file

@ -19,8 +19,8 @@ const seriesColors = [
];
const strokeWidth = {
[GRAPH_STYLES.BAR]: 0.8,
[GRAPH_STYLES.LINE_STEPPED]: 1.2,
[GRAPH_STYLES.BAR]: 1,
[GRAPH_STYLES.LINE_STEPPED]: 2,
[GRAPH_STYLES.LINE]: 1.2,
[GRAPH_STYLES.POINTS]: 0,
};
@ -82,7 +82,7 @@ const useBarHitsOptions = ({
cursor: {
points: {
width: (u, seriesIdx, size) => size / 4,
size: (u, seriesIdx) => (u.series?.[seriesIdx]?.points?.size || 1) * 2.5,
size: (u, seriesIdx) => (u.series?.[seriesIdx]?.points?.size || 1) * 1.5,
stroke: (u, seriesIdx) => `${series?.[seriesIdx]?.stroke || "#ffffff"}`,
fill: () => "#ffffff",
},

View file

@ -29,6 +29,7 @@ $chart-tooltip-y: -1 * ($padding-global + $chart-tooltip-half-icon);
white-space: pre-wrap;
word-break: break-all;
width: auto;
max-width: calc(100vw/3);
}
&_sticky {

View file

@ -1,4 +1,4 @@
import React, { FC, Ref, useCallback, useEffect, useMemo, useRef, useState, JSX } from "preact/compat";
import React, { FC, useCallback, useEffect, useMemo, useRef, useState, JSX } from "preact/compat";
import classNames from "classnames";
import Popper from "../Popper/Popper";
import "./style.scss";
@ -17,7 +17,7 @@ export interface AutocompleteOptions {
interface AutocompleteProps {
value: string
options: AutocompleteOptions[]
anchor: Ref<HTMLElement>
anchor: React.RefObject<HTMLElement>
disabled?: boolean
minLength?: number
fullWidth?: boolean
@ -31,7 +31,7 @@ interface AutocompleteProps {
onSelect: (val: string, item: AutocompleteOptions) => void
onOpenAutocomplete?: (val: boolean) => void
onFoundOptions?: (val: AutocompleteOptions[]) => void
onChangeWrapperRef?: (elementRef: Ref<HTMLDivElement>) => void
onChangeWrapperRef?: (elementRef: React.RefObject<HTMLElement>) => void
}
enum FocusType {

View file

@ -1,4 +1,4 @@
import React, { Ref, useMemo, forwardRef } from "preact/compat";
import React, { useMemo, forwardRef } from "preact/compat";
import Calendar from "../../Main/DatePicker/Calendar/Calendar";
import dayjs, { Dayjs } from "dayjs";
import Popper from "../../Main/Popper/Popper";
@ -9,7 +9,7 @@ import useEventListener from "../../../hooks/useEventListener";
interface DatePickerProps {
date: string | Date | Dayjs,
targetRef: Ref<HTMLElement>
targetRef: React.RefObject<HTMLElement>;
format?: string
label?: string
onChange: (val: string) => void

View file

@ -1,4 +1,4 @@
import React, { FC, Ref, useEffect, useMemo, useRef, useState, } from "preact/compat";
import React, { FC, useEffect, useMemo, useRef, useState, } from "preact/compat";
import classNames from "classnames";
import { ArrowDropDownIcon, CloseIcon } from "../Icons";
import { FormEvent, MouseEvent } from "react";
@ -40,7 +40,7 @@ const Select: FC<SelectProps> = ({
const [search, setSearch] = useState("");
const autocompleteAnchorEl = useRef<HTMLDivElement>(null);
const [wrapperRef, setWrapperRef] = useState<Ref<HTMLDivElement> | null>(null);
const [wrapperRef, setWrapperRef] = useState<React.RefObject<HTMLElement> | null>(null);
const [openList, setOpenList] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);

View file

@ -149,7 +149,7 @@
max-width: 15px;
top: 0;
left: $padding-small;
height: 40px;
height: 36px;
position: absolute;
color: $color-text-secondary;
}

View file

@ -1,23 +1,23 @@
import React, { FC, useEffect, useRef, useMemo } from "preact/compat";
import Button from "../../Main/Button/Button";
import { SearchIcon, SettingsIcon } from "../../Main/Icons";
import Popper from "../../Main/Popper/Popper";
import "./style.scss";
import Checkbox from "../../Main/Checkbox/Checkbox";
import Tooltip from "../../Main/Tooltip/Tooltip";
import Switch from "../../Main/Switch/Switch";
import { arrayEquals } from "../../../utils/array";
import classNames from "classnames";
import useDeviceDetect from "../../../hooks/useDeviceDetect";
import useBoolean from "../../../hooks/useBoolean";
import TextField from "../../Main/TextField/TextField";
import { KeyboardEvent, useState } from "react";
import Modal from "../../Main/Modal/Modal";
import { getFromStorage, removeFromStorage, saveToStorage } from "../../../utils/storage";
const title = "Table settings";
interface TableSettingsProps {
columns: string[];
defaultColumns?: string[];
selectedColumns?: string[];
tableCompact: boolean;
toggleTableCompact: () => void;
onChangeColumns: (arr: string[]) => void
@ -25,13 +25,11 @@ interface TableSettingsProps {
const TableSettings: FC<TableSettingsProps> = ({
columns,
defaultColumns = [],
selectedColumns = [],
tableCompact,
onChangeColumns,
toggleTableCompact
}) => {
const { isMobile } = useDeviceDetect();
const buttonRef = useRef<HTMLDivElement>(null);
const {
@ -41,31 +39,34 @@ const TableSettings: FC<TableSettingsProps> = ({
} = useBoolean(false);
const {
value: showSearch,
toggle: toggleShowSearch,
} = useBoolean(false);
value: saveColumns,
toggle: toggleSaveColumns,
} = useBoolean(Boolean(getFromStorage("TABLE_COLUMNS")));
const [searchColumn, setSearchColumn] = useState("");
const [indexFocusItem, setIndexFocusItem] = useState(-1);
const customColumns = useMemo(() => {
return selectedColumns.filter(col => !columns.includes(col));
}, [columns, selectedColumns]);
const filteredColumns = useMemo(() => {
if (!searchColumn) return columns;
return columns.filter(col => col.includes(searchColumn));
}, [columns, searchColumn]);
const allColumns = customColumns.concat(columns);
if (!searchColumn) return allColumns;
return allColumns.filter(col => col.includes(searchColumn));
}, [columns, customColumns, searchColumn]);
const isAllChecked = useMemo(() => {
return filteredColumns.every(col => defaultColumns.includes(col));
}, [defaultColumns, filteredColumns]);
const disabledButton = useMemo(() => !columns.length, [columns]);
return filteredColumns.every(col => selectedColumns.includes(col));
}, [selectedColumns, filteredColumns]);
const handleChange = (key: string) => {
onChangeColumns(defaultColumns.includes(key) ? defaultColumns.filter(col => col !== key) : [...defaultColumns, key]);
onChangeColumns(selectedColumns.includes(key) ? selectedColumns.filter(col => col !== key) : [...selectedColumns, key]);
};
const toggleAllColumns = () => {
if (isAllChecked) {
onChangeColumns(defaultColumns.filter(col => !filteredColumns.includes(col)));
onChangeColumns(selectedColumns.filter(col => !filteredColumns.includes(col)));
} else {
onChangeColumns(filteredColumns);
}
@ -94,10 +95,24 @@ const TableSettings: FC<TableSettingsProps> = ({
};
useEffect(() => {
if (arrayEquals(columns, defaultColumns)) return;
if (arrayEquals(columns, selectedColumns) || saveColumns) return;
onChangeColumns(columns);
}, [columns]);
useEffect(() => {
if (!saveColumns) {
removeFromStorage(["TABLE_COLUMNS"]);
} else if (selectedColumns.length) {
saveToStorage("TABLE_COLUMNS", selectedColumns.join(","));
}
}, [saveColumns, selectedColumns]);
useEffect(() => {
const saveColumns = getFromStorage("TABLE_COLUMNS") as string;
if (!saveColumns) return;
onChangeColumns(saveColumns.split(","));
}, []);
return (
<div className="vm-table-settings">
<Tooltip title={title}>
@ -106,48 +121,24 @@ const TableSettings: FC<TableSettingsProps> = ({
variant="text"
startIcon={<SettingsIcon/>}
onClick={toggleOpenSettings}
disabled={disabledButton}
ariaLabel={title}
/>
</div>
</Tooltip>
<Popper
open={openSettings}
onClose={handleClose}
placement="bottom-right"
buttonRef={buttonRef}
title={title}
>
<div
className={classNames({
"vm-table-settings-popper": true,
"vm-table-settings-popper_mobile": isMobile
})}
{openSettings && (
<Modal
title={title}
className="vm-table-settings-modal"
onClose={handleClose}
>
<div className="vm-table-settings-popper-list vm-table-settings-popper-list_first">
<Switch
label={"Compact view"}
value={tableCompact}
onChange={toggleTableCompact}
/>
</div>
<div className="vm-table-settings-popper-list">
<div>
<div className="vm-table-settings-popper-list-header">
<h3 className="vm-table-settings-popper-list-header__title">Display columns</h3>
<Tooltip title="search column">
<Button
color="primary"
variant="text"
onClick={toggleShowSearch}
startIcon={<SearchIcon/>}
ariaLabel="reset columns"
/>
</Tooltip>
</div>
{showSearch && (
<div className="vm-table-settings-modal-section">
<div className="vm-table-settings-modal-section__title">
Customize columns
</div>
<div className="vm-table-settings-modal-columns">
<div className="vm-table-settings-modal-columns__search">
<TextField
placeholder={"search column"}
placeholder={"Search columns"}
startIcon={<SearchIcon/>}
value={searchColumn}
onChange={setSearchColumn}
@ -155,13 +146,10 @@ const TableSettings: FC<TableSettingsProps> = ({
onKeyDown={handleKeyDown}
type="search"
/>
)}
{!filteredColumns.length && (
<p className="vm-table-settings-popper-list__no-found">No columns found</p>
)}
<div className="vm-table-settings-popper-list-header">
</div>
<div className="vm-table-settings-modal-columns-list">
{!!filteredColumns.length && (
<div className="vm-table-settings-popper-list__item vm-table-settings-popper-list__item_check_all">
<div className="vm-table-settings-modal-columns-list__item vm-table-settings-modal-columns-list__item_all">
<Checkbox
checked={isAllChecked}
onChange={toggleAllColumns}
@ -170,18 +158,24 @@ const TableSettings: FC<TableSettingsProps> = ({
/>
</div>
)}
</div>
<div className="vm-table-settings-popper-list-columns">
{!filteredColumns.length && (
<div className="vm-table-settings-modal-columns-no-found">
<p className="vm-table-settings-modal-columns-no-found__info">
No columns found.
</p>
</div>
)}
{filteredColumns.map((col, i) => (
<div
className={classNames({
"vm-table-settings-popper-list__item": true,
"vm-table-settings-popper-list__item_focus": i === indexFocusItem,
"vm-table-settings-modal-columns-list__item": true,
"vm-table-settings-modal-columns-list__item_focus": i === indexFocusItem,
"vm-table-settings-modal-columns-list__item_custom": customColumns.includes(col),
})}
key={col}
>
<Checkbox
checked={defaultColumns.includes(col)}
checked={selectedColumns.includes(col)}
onChange={createHandlerChange(col)}
label={col}
disabled={tableCompact}
@ -189,10 +183,34 @@ const TableSettings: FC<TableSettingsProps> = ({
</div>
))}
</div>
<div className="vm-table-settings-modal-preserve">
<Checkbox
checked={saveColumns}
onChange={toggleSaveColumns}
label={"Preserve column settings"}
disabled={tableCompact}
color={"primary"}
/>
<p className="vm-table-settings-modal-preserve__info">
This label indicates that when the checkbox is activated,
the current column configurations will not be reset.
</p>
</div>
</div>
</div>
</div>
</Popper>
<div className="vm-table-settings-modal-section">
<div className="vm-table-settings-modal-section__title">
Table view
</div>
<div className="vm-table-settings-modal-columns-list__item">
<Switch
label={"Compact view"}
value={tableCompact}
onChange={toggleTableCompact}
/>
</div>
</div>
</Modal>)}
</div>
);
};

View file

@ -1,66 +1,98 @@
@use "src/styles/variables" as *;
.vm-table-settings-popper {
display: grid;
min-width: 250px;
&_mobile &-list {
gap: $padding-global;
&:first-child {
padding-top: 0;
}
}
&-list {
display: grid;
gap: 12px;
padding: $padding-global;
border-bottom: $border-divider;
max-width: 250px;
&_first {
padding-top: 0;
.vm-table-settings {
&-modal {
.vm-modal-content-body {
padding: 0;
}
&-header {
display: grid;
align-items: center;
justify-content: space-between;
grid-template-columns: 1fr auto;
gap: $padding-small;
min-height: 25px;
&-section {
padding-block: $padding-global;
border-top: $border-divider;
&:first-child {
padding-top: 0;
border-top: none;
}
&__title {
padding-inline: $padding-global;
font-size: $font-size;
font-weight: bold;
margin-bottom: $padding-global;
}
}
&-columns {
max-height: 350px;
overflow: auto;
}
&__item {
padding: calc($padding-global/2) $padding-global;
font-size: $font-size;
&:hover,
&_focus {
background-color: $color-hover-black;
&__search {
padding-inline: $padding-global;
}
&_check_all {
padding: calc($padding-global/2) $padding-global;
margin: 0 (-$padding-global);
&-list {
display: flex;
flex-direction: column;
max-height: 250px;
min-height: 250px;
overflow: auto;
margin-bottom: $padding-global;
&__item {
width: 100%;
font-size: $font-size;
border-radius: $border-radius-small;
&>div {
padding: $padding-small $padding-global;
}
&_all {
font-weight: bold;
}
&:hover,
&_focus {
background-color: $color-hover-black;
}
&_custom {
.vm-checkbox__label:after {
width: 100%;
content: "(custom column, will be removed if unchecked)";
padding: 0 $padding-small;
text-align: right;
font-style: italic;
color: $color-text-secondary;
}
}
}
}
&-no-found {
display: flex;
flex-direction: column;
min-width: 100%;
min-height: 250px;
align-items: center;
justify-content: center;
gap: $padding-global;
&__info {
text-align: center;
font-style: italic;
color: $color-text-secondary;
}
}
}
&__no-found {
text-align: center;
font-style: italic;
color: $color-text-secondary;
margin-bottom: $padding-small;
&-preserve {
padding: $padding-global;
&__info {
padding-top: $padding-small;
font-size: $font-size-small;
color: $color-text-secondary;
line-height: 130%;
}
}
}
}

View file

@ -1,2 +1,2 @@
export const LOGS_ENTRIES_LIMIT = 50;
export const LOGS_BARS_VIEW = 20;
export const LOGS_BARS_VIEW = 100;

View file

@ -1,10 +1,16 @@
import router, { routerOptions } from "../router";
export enum NavigationItemType {
internalLink,
externalLink,
}
export interface NavigationItem {
label?: string,
value?: string,
hide?: boolean
submenu?: NavigationItem[],
type?: NavigationItemType,
}
const explore = {

View file

@ -0,0 +1,44 @@
import { useAppDispatch, useAppState } from "../state/common/StateContext";
import { useEffect, useState } from "preact/compat";
import { ErrorTypes } from "../types";
import { getUrlWithoutTenant } from "../utils/tenants";
const useFetchFlags = () => {
const { serverUrl } = useAppState();
const dispatch = useAppDispatch();
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<ErrorTypes | string>("");
useEffect(() => {
const fetchFlags = async () => {
if (!serverUrl || process.env.REACT_APP_TYPE) return;
setError("");
setIsLoading(true);
try {
const url = getUrlWithoutTenant(serverUrl);
const response = await fetch(`${url}/flags`);
const data = await response.text();
const flags = data.split("\n").filter(flag => flag.trim() !== "")
.reduce((acc, flag) => {
const [keyRaw, valueRaw] = flag.split("=");
const key = keyRaw.trim().replace(/^-/, "");
acc[key.trim()] = valueRaw ? valueRaw.trim().replace(/^"(.*)"$/, "$1") : null;
return acc;
}, {} as Record<string, string|null>);
dispatch({ type: "SET_FLAGS", payload: flags });
} catch (e) {
setIsLoading(false);
if (e instanceof Error) setError(`${e.name}: ${e.message}`);
}
};
fetchFlags();
}, [serverUrl]);
return { isLoading, error };
};
export default useFetchFlags;

View file

@ -110,7 +110,12 @@ export const useFetchQuery = ({
continue;
}
const response = await fetch(url, { signal: controller.signal });
const urlObj = new URL(url);
const response = await fetch(`${urlObj.origin}${urlObj.pathname}`, {
signal: controller.signal,
method: "POST",
body: urlObj.searchParams
});
const resp = await response.json();
if (response.ok) {

Some files were not shown because too many files have changed in this diff Show more