Compare commits

..

1 Commits

Author SHA1 Message Date
Haley Wang
ec4e6522c4 vmselect: add -search.maxDeleteDuration to limit the duration of the /api/v1/admin/tsdb/delete_series call
Previously, it's reusing `-search.maxQueryDuration`.
2025-01-15 14:43:03 +08:00
1049 changed files with 398900 additions and 13298 deletions

View File

@@ -204,19 +204,19 @@ check-all: fmt vet golangci-lint govulncheck
clean-checkers: remove-golangci-lint remove-govulncheck
test:
go test ./lib/... ./app/...
DISABLE_FSYNC_FOR_TESTING=1 go test ./lib/... ./app/...
test-race:
go test -race ./lib/... ./app/...
DISABLE_FSYNC_FOR_TESTING=1 go test -race ./lib/... ./app/...
test-pure:
CGO_ENABLED=0 go test ./lib/... ./app/...
DISABLE_FSYNC_FOR_TESTING=1 CGO_ENABLED=0 go test ./lib/... ./app/...
test-full:
go test -coverprofile=coverage.txt -covermode=atomic ./lib/... ./app/...
DISABLE_FSYNC_FOR_TESTING=1 go test -coverprofile=coverage.txt -covermode=atomic ./lib/... ./app/...
test-full-386:
GOARCH=386 go test -coverprofile=coverage.txt -covermode=atomic ./lib/... ./app/...
DISABLE_FSYNC_FOR_TESTING=1 GOARCH=386 go test -coverprofile=coverage.txt -covermode=atomic ./lib/... ./app/...
integration-test: all
go test ./apptest/... -skip="^TestSingle.*"

View File

@@ -688,13 +688,13 @@ func ProcessStatsQueryRangeRequest(ctx context.Context, w http.ResponseWriter, r
m := make(map[string]*statsSeries)
var mLock 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 {
timestamp := q.GetTimestamp()
labels := make([]logstorage.Field, 0, len(byFields))
for j, c := range columns {
if c.Name == "_time" {

View File

@@ -28,7 +28,7 @@ func TestParseExtraFilters_Success(t *testing.T) {
// LogsQL filter
f(`foobar`, `foobar`)
f(`foo:bar`, `foo:bar`)
f(`foo:(bar or baz) error _time:5m {"foo"=bar,baz="z"}`, `{foo="bar",baz="z"} (foo:bar or foo:baz) error _time:5m`)
f(`foo:(bar or baz) error _time:5m {"foo"=bar,baz="z"}`, `(foo:bar or foo:baz) error _time:5m {foo="bar",baz="z"}`)
}
func TestParseExtraFilters_Failure(t *testing.T) {
@@ -77,7 +77,7 @@ func TestParseExtraStreamFilters_Success(t *testing.T) {
// LogsQL filter
f(`foobar`, `foobar`)
f(`foo:bar`, `foo:bar`)
f(`foo:(bar or baz) error _time:5m {"foo"=bar,baz="z"}`, `{foo="bar",baz="z"} (foo:bar or foo:baz) error _time:5m`)
f(`foo:(bar or baz) error _time:5m {"foo"=bar,baz="z"}`, `(foo:bar or foo:baz) error _time:5m {foo="bar",baz="z"}`)
}
func TestParseExtraStreamFilters_Failure(t *testing.T) {

View File

@@ -1,12 +1,12 @@
{
"files": {
"main.css": "./static/css/main.02a1c6cb.css",
"main.js": "./static/js/main.55c8060b.js",
"main.css": "./static/css/main.4aacd559.css",
"main.js": "./static/js/main.5ce54a05.js",
"static/js/685.f772060c.chunk.js": "./static/js/685.f772060c.chunk.js",
"index.html": "./index.html"
},
"entrypoints": [
"static/css/main.02a1c6cb.css",
"static/js/main.55c8060b.js"
"static/css/main.4aacd559.css",
"static/js/main.5ce54a05.js"
]
}

View File

@@ -1 +1 @@
<!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.55c8060b.js"></script><link href="./static/css/main.02a1c6cb.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.5ce54a05.js"></script><link href="./static/css/main.4aacd559.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -216,7 +216,8 @@ func processRequest(w http.ResponseWriter, r *http.Request, ui *UserInfo) {
isDefault = true
}
rtb := newReadTrackingBody(r.Body, maxRequestBodySizeToRetry.IntN())
rtb := getReadTrackingBody(r.Body, maxRequestBodySizeToRetry.IntN())
defer putReadTrackingBody(rtb)
r.Body = rtb
maxAttempts := up.getBackendsCount()
@@ -552,11 +553,22 @@ type readTrackingBody struct {
bufComplete bool
}
func newReadTrackingBody(r io.ReadCloser, maxBodySize int) *readTrackingBody {
// do not use sync.Pool there
// since http.RoundTrip may still use request body after return
// See this issue for details https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8051
rtb := &readTrackingBody{}
func (rtb *readTrackingBody) reset() {
rtb.maxBodySize = 0
rtb.r = nil
rtb.buf = rtb.buf[:0]
rtb.readBuf = nil
rtb.cannotRetry = false
rtb.bufComplete = false
}
func getReadTrackingBody(r io.ReadCloser, maxBodySize int) *readTrackingBody {
v := readTrackingBodyPool.Get()
if v == nil {
v = &readTrackingBody{}
}
rtb := v.(*readTrackingBody)
if maxBodySize < 0 {
maxBodySize = 0
}
@@ -579,6 +591,13 @@ func (r *zeroReader) Close() error {
return nil
}
func putReadTrackingBody(rtb *readTrackingBody) {
rtb.reset()
readTrackingBodyPool.Put(rtb)
}
var readTrackingBodyPool sync.Pool
// Read implements io.Reader interface.
func (rtb *readTrackingBody) Read(p []byte) (int, error) {
if len(rtb.readBuf) > 0 {

View File

@@ -181,7 +181,7 @@ unauthorized_user:
}
responseExpected = `
statusCode=401
Expected to receive non-empty authKey when -reloadAuthKey is set`
The provided authKey doesn't match -reloadAuthKey`
f(cfgStr, requestURL, backendHandler, responseExpected)
if err := reloadAuthKey.Set(origAuthKey); err != nil {
t.Fatalf("unexpected error: %s", err)
@@ -531,7 +531,8 @@ func TestReadTrackingBody_RetrySuccess(t *testing.T) {
f := func(s string, maxBodySize int) {
t.Helper()
rtb := newReadTrackingBody(io.NopCloser(bytes.NewBufferString(s)), maxBodySize)
rtb := getReadTrackingBody(io.NopCloser(bytes.NewBufferString(s)), maxBodySize)
defer putReadTrackingBody(rtb)
if !rtb.canRetry() {
t.Fatalf("canRetry() must return true before reading anything")
@@ -566,7 +567,8 @@ func TestReadTrackingBody_RetrySuccessPartialRead(t *testing.T) {
t.Helper()
// Check the case with partial read
rtb := newReadTrackingBody(io.NopCloser(bytes.NewBufferString(s)), maxBodySize)
rtb := getReadTrackingBody(io.NopCloser(bytes.NewBufferString(s)), maxBodySize)
defer putReadTrackingBody(rtb)
for i := 0; i < len(s); i++ {
buf := make([]byte, i)
@@ -615,7 +617,8 @@ func TestReadTrackingBody_RetryFailureTooBigBody(t *testing.T) {
f := func(s string, maxBodySize int) {
t.Helper()
rtb := newReadTrackingBody(io.NopCloser(bytes.NewBufferString(s)), maxBodySize)
rtb := getReadTrackingBody(io.NopCloser(bytes.NewBufferString(s)), maxBodySize)
defer putReadTrackingBody(rtb)
if !rtb.canRetry() {
t.Fatalf("canRetry() must return true before reading anything")
@@ -664,7 +667,8 @@ func TestReadTrackingBody_RetryFailureZeroOrNegativeMaxBodySize(t *testing.T) {
f := func(s string, maxBodySize int) {
t.Helper()
rtb := newReadTrackingBody(io.NopCloser(bytes.NewBufferString(s)), maxBodySize)
rtb := getReadTrackingBody(io.NopCloser(bytes.NewBufferString(s)), maxBodySize)
defer putReadTrackingBody(rtb)
if !rtb.canRetry() {
t.Fatalf("canRetry() must return true before reading anything")

View File

@@ -114,7 +114,7 @@ again:
rowsProcessed, err := rerouteRowsToReadyStorageNodes(snb, sn, buf)
rows -= rowsProcessed
if err != nil {
return fmt.Errorf("%d rows dropped because the current vsmtorage is unavailable and %w", rows, err)
return fmt.Errorf("%d rows dropped because the current vmstorage is unavailable and %w", rows, err)
}
return nil
}

View File

@@ -52,8 +52,8 @@ var (
"limit is reached; see also -search.maxQueryDuration")
minScrapeInterval = flag.Duration("dedup.minScrapeInterval", 0, "Leave only the last sample in every time series per each discrete interval "+
"equal to -dedup.minScrapeInterval > 0. See https://docs.victoriametrics.com/#deduplication for details")
deleteAuthKey = flagutil.NewPassword("deleteAuthKey", "authKey for metrics' deletion via /prometheus/api/v1/admin/tsdb/delete_series and /graphite/tags/delSeries. It could be passed via authKey query arg. It overrides -httpAuth.*")
resetCacheAuthKey = flagutil.NewPassword("search.resetCacheAuthKey", "Optional authKey for resetting rollup cache via /internal/resetRollupResultCache call. It could be passed via authKey query arg. It overrides -httpAuth.*")
deleteAuthKey = flagutil.NewPassword("deleteAuthKey", "authKey for metrics' deletion via /prometheus/api/v1/admin/tsdb/delete_series and /graphite/tags/delSeries")
resetCacheAuthKey = flagutil.NewPassword("search.resetCacheAuthKey", "Optional authKey for resetting rollup cache via /internal/resetRollupResultCache call")
logSlowQueryDuration = flag.Duration("search.logSlowQueryDuration", 5*time.Second, "Log queries with execution time exceeding this value. Zero disables slow query logging. "+
"See also -search.logQueryMemoryUsage")
vmalertProxyURL = flag.String("vmalert.proxyURL", "", "Optional URL for proxying requests to vmalert. For example, if -vmalert.proxyURL=http://vmalert:8880 , then alerting API requests such as /api/v1/rules from Grafana will be proxied to http://vmalert:8880/api/v1/rules")
@@ -63,10 +63,6 @@ var (
clusternativeListenAddr = flag.String("clusternativeListenAddr", "", "TCP address to listen for requests from other vmselect nodes in multi-level cluster setup. "+
"See https://docs.victoriametrics.com/cluster-victoriametrics/#multi-level-cluster-setup . Usually :8401 should be set to match default vmstorage port for vmselect. Disabled work if empty")
maxMemoryPerQuery = flagutil.NewBytes("search.maxMemoryPerQuery", 0, "The maximum amounts of memory a single query may consume. "+
"Queries requiring more memory are rejected. The total memory limit for concurrently executed queries can be estimated "+
"as -search.maxMemoryPerQuery multiplied by -search.maxConcurrentRequests . "+
"See also -search.logQueryMemoryUsage")
)
var slowQueries = metrics.NewCounter(`vm_slow_queries_total`)
@@ -120,10 +116,6 @@ func main() {
netstorage.InitTmpBlocksDir("")
promql.InitRollupResultCache("")
}
promql.SetMaxMemoryPerQuery(maxMemoryPerQuery.N)
netstorage.SetMaxMemoryUsagePerQuery(maxMemoryPerQuery.N)
concurrencyLimitCh = make(chan struct{}, *maxConcurrentRequests)
initVMAlertProxy()
var vmselectapiServer *vmselectapi.Server

View File

@@ -1,73 +0,0 @@
package netstorage
import (
"fmt"
"sync"
)
var (
maxMemoryUsagePerQuery int64
)
// getMaxMemoryUsagePerQuery returns the maximum memory usage per query.
func getMaxMemoryUsagePerQuery() int64 {
return maxMemoryUsagePerQuery
}
// SetMaxMemoryUsagePerQuery sets the maximum memory usage per query.
func SetMaxMemoryUsagePerQuery(v int64) {
maxMemoryUsagePerQuery = v
}
// memoryLimiter tracks and limits memory usage for operations
type memoryLimiter struct {
maxSize uint64
mu sync.Mutex
usage uint64
}
func newMemoryLimiter() *memoryLimiter {
maxSize := uint64(getMaxMemoryUsagePerQuery())
return &memoryLimiter{
maxSize: maxSize,
}
}
func (ml *memoryLimiter) AddBytes(n uint64) error {
if ml.maxSize <= 0 {
return nil
}
ml.mu.Lock()
ok := n <= ml.maxSize && ml.maxSize-n >= ml.usage
if ok {
ml.usage += n
}
ml.mu.Unlock()
if !ok {
return &limitExceededErr{
err: fmt.Errorf("cannot allocate %d bytes; max allowed memory usage is %d bytes", n, ml.maxSize),
}
}
return nil
}
// AddStrings accounts for and limits memory usage of string slices
func (ml *memoryLimiter) AddStrings(v []string) error {
if len(v) == 0 {
return nil
}
// Account for:
// - Slice header (24 bytes)
// - String headers (16 bytes each)
// - String data
overhead := uint64(24 + (16 * len(v)))
dataSize := uint64(0)
for _, s := range v {
dataSize += uint64(len(s))
}
return ml.AddBytes(overhead + dataSize)
}

View File

@@ -906,14 +906,10 @@ func LabelNames(qt *querytracer.Tracer, denyPartialResponse bool, sq *storage.Se
return nil, false, err
}
sns := getStorageNodes()
ml := newMemoryLimiter()
snr := startStorageNodesRequest(qt, sns, denyPartialResponse, func(qt *querytracer.Tracer, _ uint, sn *storageNode) any {
return execSearchQuery(qt, sq, func(qt *querytracer.Tracer, requestData []byte, _ storage.TenantToken) any {
sn.labelNamesRequests.Inc()
labelNames, err := sn.getLabelNames(qt, requestData, maxLabelNames, deadline)
if err == nil {
err = ml.AddStrings(labelNames)
}
if err != nil {
sn.labelNamesErrors.Inc()
err = fmt.Errorf("cannot get labels from vmstorage %s: %w", sn.connPool.Addr(), err)
@@ -1027,14 +1023,14 @@ func LabelValues(qt *querytracer.Tracer, denyPartialResponse bool, labelName str
case "vm_project_id":
idx = 1
default:
logger.Fatalf("BUG: unexpected labelName=%q", labelName)
logger.Fatalf("BUG: unexpected labeName=%q", labelName)
}
labelValues := make([]string, 0, len(tenants))
for _, t := range tenants {
s := strings.Split(t, ":")
if len(s) != 2 {
logger.Panicf("BUG: unexpected tenant received from storage: %q", t)
logger.Fatalf("BUG: unexpected tenant received from storage: %q", t)
}
labelValues = append(labelValues, s[idx])
@@ -1053,15 +1049,11 @@ func LabelValues(qt *querytracer.Tracer, denyPartialResponse bool, labelName str
if err != nil {
return nil, false, err
}
ml := newMemoryLimiter()
sns := getStorageNodes()
snr := startStorageNodesRequest(qt, sns, denyPartialResponse, func(qt *querytracer.Tracer, _ uint, sn *storageNode) any {
return execSearchQuery(qt, sq, func(qt *querytracer.Tracer, requestData []byte, _ storage.TenantToken) any {
sn.labelValuesRequests.Inc()
labelValues, err := sn.getLabelValues(qt, labelName, requestData, maxLabelValues, deadline)
if err == nil {
err = ml.AddStrings(labelValues)
}
if err != nil {
sn.labelValuesErrors.Inc()
err = fmt.Errorf("cannot get label values from vmstorage %s: %w", sn.connPool.Addr(), err)
@@ -1202,14 +1194,10 @@ func TagValueSuffixes(qt *querytracer.Tracer, accountID, projectID uint32, denyP
suffixes []string
err error
}
ml := newMemoryLimiter()
sns := getStorageNodes()
snr := startStorageNodesRequest(qt, sns, denyPartialResponse, func(qt *querytracer.Tracer, _ uint, sn *storageNode) any {
sn.tagValueSuffixesRequests.Inc()
suffixes, err := sn.getTagValueSuffixes(qt, accountID, projectID, tr, tagKey, tagValuePrefix, delimiter, maxSuffixes, deadline)
if err == nil {
err = ml.AddStrings(suffixes)
}
if err != nil {
sn.tagValueSuffixesErrors.Inc()
err = fmt.Errorf("cannot get tag value suffixes for timeRange=%s, tagKey=%q, tagValuePrefix=%q, delimiter=%c from vmstorage %s: %w",
@@ -1707,15 +1695,11 @@ func SearchMetricNames(qt *querytracer.Tracer, denyPartialResponse bool, sq *sto
if err != nil {
return nil, false, err
}
ml := newMemoryLimiter()
sns := getStorageNodes()
snr := startStorageNodesRequest(qt, sns, denyPartialResponse, func(qt *querytracer.Tracer, _ uint, sn *storageNode) any {
return execSearchQuery(qt, sq, func(qt *querytracer.Tracer, requestData []byte, t storage.TenantToken) any {
sn.searchMetricNamesRequests.Inc()
metricNames, err := sn.processSearchMetricNames(qt, requestData, deadline)
if err == nil {
err = ml.AddStrings(metricNames)
}
if sq.IsMultiTenant {
// TODO: (@f41gh7) this function could produce duplicate labels
// if original metricName already have tenant labels
@@ -1967,10 +1951,8 @@ func populateSqTenantTokensIfNeeded(sq *storage.SearchQuery) error {
type storageNodesRequest struct {
denyPartialResponse bool
resultsCh chan rpcResult
qt *querytracer.Tracer
// query tracers to storageAddresses mapping
qts map[*querytracer.Tracer]string
sns []*storageNode
qts map[*querytracer.Tracer]struct{}
sns []*storageNode
}
type rpcResult struct {
@@ -1983,21 +1965,15 @@ func startStorageNodesRequest(qt *querytracer.Tracer, sns []*storageNode, denyPa
f func(qt *querytracer.Tracer, workerID uint, sn *storageNode) any,
) *storageNodesRequest {
resultsCh := make(chan rpcResult, len(sns))
qts := make(map[*querytracer.Tracer]string, len(sns))
qts := make(map[*querytracer.Tracer]struct{}, len(sns))
for idx, sn := range sns {
// Do not use qt.NewChild.
// StorageNodesRequest may be finished before goroutine returns.
// Caller must register tracker manually with finishQueryTracer after goroutine returns result.
// It ensures that tracker is no longer referenced by any concurrent goroutines.
//
// See this issue: https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8114
qtOrphan := querytracer.NewOrphan(qt, "rpc at vmstorage %s", sn.connPool.Addr())
qts[qtOrphan] = sn.connPool.Addr()
qtChild := qt.NewChild("rpc at vmstorage %s", sn.connPool.Addr())
qts[qtChild] = struct{}{}
go func(workerID uint, sn *storageNode) {
data := f(qtOrphan, workerID, sn)
data := f(qtChild, workerID, sn)
resultsCh <- rpcResult{
data: data,
qt: qtOrphan,
qt: qtChild,
group: sn.group,
}
}(uint(idx), sn)
@@ -2005,21 +1981,14 @@ func startStorageNodesRequest(qt *querytracer.Tracer, sns []*storageNode, denyPa
return &storageNodesRequest{
denyPartialResponse: denyPartialResponse,
resultsCh: resultsCh,
qt: qt,
qts: qts,
sns: sns,
}
}
func (snr *storageNodesRequest) finishQueryTracers(msg string) {
for qt, storageAddr := range snr.qts {
// since qt cannot be used concurrently,
// replace child still referenced by concurrent storageNode goroutine
// with local child that belongs to current goroutine.
// Add reason msg why it was done.
cancelledQt := snr.qt.NewChild("rpc at vmstorage: %s: %s", storageAddr, msg)
cancelledQt.Done()
delete(snr.qts, qt)
for qt := range snr.qts {
snr.finishQueryTracer(qt, msg)
}
}
@@ -2030,7 +1999,6 @@ func (snr *storageNodesRequest) finishQueryTracer(qt *querytracer.Tracer, msg st
qt.Donef("%s", msg)
}
delete(snr.qts, qt)
snr.qt.AddChild(qt)
}
func (snr *storageNodesRequest) collectAllResults(f func(result any) error) error {

View File

@@ -508,14 +508,11 @@ func DeleteHandler(startTime time.Time, at *auth.Token, r *http.Request) error {
return err
}
cp.deadline = searchutils.GetDeadlineForDelete(r, startTime)
if !cp.IsDefaultTimeRange() {
return fmt.Errorf("start=%d and end=%d args aren't supported. Remove these args from the query in order to delete all the matching metrics", cp.start, cp.end)
}
sq, err := getSearchQuery(nil, at, cp, *maxDeleteSeries)
if err != nil {
return err
}
sq := storage.NewSearchQuery(at.AccountID, at.ProjectID, cp.start, cp.end, cp.filterss, *maxDeleteSeries)
deletedCount, err := netstorage.DeleteSeries(nil, sq, cp.deadline)
if err != nil {
return fmt.Errorf("cannot delete time series: %w", err)
@@ -758,9 +755,7 @@ var labelsDuration = metrics.NewSummary(`vm_request_duration_seconds{path="/api/
// SeriesCountHandler processes /api/v1/series/count request.
func SeriesCountHandler(startTime time.Time, at *auth.Token, w http.ResponseWriter, r *http.Request) error {
defer seriesCountDuration.UpdateDuration(startTime)
if at == nil {
return fmt.Errorf("multi-tenant request to /api/v1/series/count is not supported")
}
deadline := searchutils.GetDeadlineForStatusRequest(r, startTime)
denyPartialResponse := httputils.GetDenyPartialResponse(r)
n, isPartial, err := netstorage.SeriesCount(nil, at.AccountID, at.ProjectID, denyPartialResponse, deadline)

View File

@@ -36,7 +36,10 @@ var (
"See https://docs.victoriametrics.com/#backfilling . See also -search.resetRollupResultCacheOnStartup")
maxPointsSubqueryPerTimeseries = flag.Int("search.maxPointsSubqueryPerTimeseries", 100e3, "The maximum number of points per series, which can be generated by subquery. "+
"See https://valyala.medium.com/prometheus-subqueries-in-victoriametrics-9b1492b720b3")
maxMemoryPerQuery = flagutil.NewBytes("search.maxMemoryPerQuery", 0, "The maximum amounts of memory a single query may consume. "+
"Queries requiring more memory are rejected. The total memory limit for concurrently executed queries can be estimated "+
"as -search.maxMemoryPerQuery multiplied by -search.maxConcurrentRequests . "+
"See also -search.logQueryMemoryUsage")
logQueryMemoryUsage = flagutil.NewBytes("search.logQueryMemoryUsage", 0, "Log query and increment vm_memory_intensive_queries_total metric each time "+
"the query requires more memory than specified by this flag. "+
"This may help detecting and optimizing heavy queries. Query logging is disabled by default. "+
@@ -49,20 +52,6 @@ var (
"A larger value makes the pushed-down filter more complex but fewer time series will be returned. This flag is useful when selective label contains numerous values, for example `instance`, and storage resources are abundant.")
)
var (
maxMemoryPerQuery int64
)
// SetMaxMemoryPerQuery sets the maximum memory allowed per query.
func SetMaxMemoryPerQuery(v int64) {
maxMemoryPerQuery = v
}
// getMaxMemoryPerQuery returns the maximum memory allowed per query.
func getMaxMemoryPerQuery() int64 {
return maxMemoryPerQuery
}
// The minimum number of points per timeseries for enabling time rounding.
// This improves cache hit ratio for frequently requested queries over
// big time ranges.
@@ -1804,7 +1793,7 @@ func evalRollupFuncNoCache(qt *querytracer.Tracer, ec *EvalConfig, funcName stri
"the query selects %d time series and generates %d points across all the time series; try reducing the number of selected time series",
ec.QuotedRemoteAddr, requestURI, expr.AppendString(nil), rollupMemorySize, maxMemory, timeseriesLen*len(rcs), rollupPoints)
}
if maxMemory := int64(getMaxMemoryPerQuery()); maxMemory > 0 && rollupMemorySize > maxMemory {
if maxMemory := int64(maxMemoryPerQuery.N); maxMemory > 0 && rollupMemorySize > maxMemory {
rss.Cancel()
err := fmt.Errorf("not enough memory for processing %s, which returns %d data points across %d time series with %d points in each time series "+
"according to -search.maxMemoryPerQuery=%d; requested memory: %d bytes; "+

View File

@@ -374,8 +374,8 @@ func getRollupConfigs(funcName string, rf rollupFunc, expr metricsql.Expr, start
preFunc := func(_ []float64, _ []int64) {}
funcName = strings.ToLower(funcName)
if rollupFuncsRemoveCounterResets[funcName] {
preFunc = func(values []float64, timestamps []int64) {
removeCounterResets(values, timestamps, lookbackDelta)
preFunc = func(values []float64, _ []int64) {
removeCounterResets(values)
}
}
samplesScannedPerCall := rollupFuncsSamplesScannedPerCall[funcName]
@@ -487,8 +487,8 @@ func getRollupConfigs(funcName string, rf rollupFunc, expr metricsql.Expr, start
for _, aggrFuncName := range aggrFuncNames {
if rollupFuncsRemoveCounterResets[aggrFuncName] {
// There is no need to save the previous preFunc, since it is either empty or the same.
preFunc = func(values []float64, timestamps []int64) {
removeCounterResets(values, timestamps, lookbackDelta)
preFunc = func(values []float64, _ []int64) {
removeCounterResets(values)
}
}
rf := rollupAggrFuncs[aggrFuncName]
@@ -522,7 +522,7 @@ type rollupFuncArg struct {
timestamps []int64
// Real value preceding values.
// Is populated if preceding value is within the -search.maxStalenessInterval (rc.LookbackDelta).
// Is populated if preceding value is within the staleness interval.
realPrevValue float64
// Real value which goes after values.
@@ -773,13 +773,7 @@ func (rc *rollupConfig) doInternal(dstValues []float64, tsm *timeseriesMap, valu
rfa.realPrevValue = nan
if i > 0 {
prevValue, prevTimestamp := values[i-1], timestamps[i-1]
// set realPrevValue if rc.LookbackDelta == 0
// or if distance between datapoint in prev interval and beginning of this interval
// doesn't exceed LookbackDelta.
// https://github.com/VictoriaMetrics/VictoriaMetrics/pull/1381
// https://github.com/VictoriaMetrics/VictoriaMetrics/issues/894
// https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8045
if rc.LookbackDelta == 0 || (tStart-prevTimestamp) < rc.LookbackDelta {
if (tEnd - prevTimestamp) < maxPrevInterval {
rfa.realPrevValue = prevValue
}
}
@@ -905,7 +899,7 @@ func getMaxPrevInterval(scrapeInterval int64) int64 {
return scrapeInterval + scrapeInterval/8
}
func removeCounterResets(values []float64, timestamps []int64, maxStalenessInterval int64) {
func removeCounterResets(values []float64) {
// There is no need in handling NaNs here, since they are impossible
// on values from vmstorage.
if len(values) == 0 {
@@ -924,16 +918,6 @@ func removeCounterResets(values []float64, timestamps []int64, maxStalenessInter
correction += prevValue
}
}
if i > 0 && maxStalenessInterval > 0 {
gap := timestamps[i] - timestamps[i-1]
if gap > maxStalenessInterval {
// reset correction if gap between samples exceeds staleness interval
// see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8072
correction = 0
prevValue = v
continue
}
}
prevValue = v
values[i] = v + correction
// Check again, there could be precision error in float operations,

View File

@@ -117,49 +117,31 @@ func TestRollupIderivDuplicateTimestamps(t *testing.T) {
}
func TestRemoveCounterResets(t *testing.T) {
removeCounterResets(nil, nil, 0)
removeCounterResets(nil)
values := append([]float64{}, testValues...)
timestamps := append([]int64{}, testTimestamps...)
removeCounterResets(values, timestamps, 0)
removeCounterResets(values)
valuesExpected := []float64{123, 157, 167, 188, 221, 255, 320, 332, 364, 396, 398, 398}
testRowsEqual(t, values, testTimestamps, valuesExpected, testTimestamps)
// removeCounterResets doesn't expect negative values, so it doesn't work properly with them.
values = []float64{-100, -200, -300, -400}
timestampsExpected := []int64{0, 1, 2, 3}
removeCounterResets(values, timestampsExpected, 0)
removeCounterResets(values)
valuesExpected = []float64{-100, -100, -100, -100}
timestampsExpected := []int64{0, 1, 2, 3}
testRowsEqual(t, values, timestampsExpected, valuesExpected, timestampsExpected)
// verify how partial counter reset is handled.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2787
values = []float64{100, 95, 120, 119, 139, 50}
timestampsExpected = []int64{0, 1, 2, 3, 4, 5}
removeCounterResets(values, timestampsExpected, 0)
removeCounterResets(values)
valuesExpected = []float64{100, 100, 125, 125, 145, 195}
timestampsExpected = []int64{0, 1, 2, 3, 4, 5}
testRowsEqual(t, values, timestampsExpected, valuesExpected, timestampsExpected)
// verify that staleness interval is respected during resets
// see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8072
values = []float64{10, 12, 14, 4, 6, 8, 6, 8, 4, 6}
timestamps = []int64{10, 20, 30, 60, 70, 80, 90, 100, 120, 130}
valuesExpected = []float64{10, 12, 14, 4, 6, 8, 14, 16, 4, 6}
removeCounterResets(values, timestamps, 10)
testRowsEqual(t, values, timestamps, valuesExpected, timestamps)
// verify that staleness is respected if there was no counter reset
// but correction was made previously
values = []float64{10, 12, 2, 4}
timestamps = []int64{10, 20, 30, 60}
valuesExpected = []float64{10, 12, 14, 4}
removeCounterResets(values, timestamps, 10)
testRowsEqual(t, values, timestamps, valuesExpected, timestamps)
// verify results always increase monotonically with possible float operations precision error
values = []float64{34.094223, 2.7518, 2.140669, 0.044878, 1.887095, 2.546569, 2.490149, 0.045, 0.035684, 0.062454, 0.058296}
timestampsExpected = []int64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
removeCounterResets(values, timestampsExpected, 0)
removeCounterResets(values)
var prev float64
for i, v := range values {
if v < prev {
@@ -184,7 +166,7 @@ func TestDeltaValues(t *testing.T) {
// remove counter resets
values = append([]float64{}, testValues...)
removeCounterResets(values, testTimestamps, 0)
removeCounterResets(values)
deltaValues(values)
valuesExpected = []float64{34, 10, 21, 33, 34, 65, 12, 32, 32, 2, 0, 0}
testRowsEqual(t, values, testTimestamps, valuesExpected, testTimestamps)
@@ -206,7 +188,7 @@ func TestDerivValues(t *testing.T) {
// remove counter resets
values = append([]float64{}, testValues...)
removeCounterResets(values, testTimestamps, 0)
removeCounterResets(values)
derivValues(values, testTimestamps)
valuesExpected = []float64{3400, 1111.111111111111, 1750, 2538.4615384615386, 3090.909090909091, 3611.1111111111113,
6000, 1882.3529411764705, 1777.7777777777778, 400, 0, 0}
@@ -237,7 +219,7 @@ func testRollupFunc(t *testing.T, funcName string, args []any, vExpected float64
rfa.timestamps = append(rfa.timestamps, testTimestamps...)
rfa.window = rfa.timestamps[len(rfa.timestamps)-1] - rfa.timestamps[0]
if rollupFuncsRemoveCounterResets[funcName] {
removeCounterResets(rfa.values, rfa.timestamps, 0)
removeCounterResets(rfa.values)
}
for i := 0; i < 5; i++ {
v := rf(&rfa)
@@ -1608,60 +1590,17 @@ func TestRollupDelta(t *testing.T) {
f(100, nan, nan, nil, 0)
}
func TestRollupDeltaWithStaleness(t *testing.T) {
func TestRollupIncreaseWithStaleness(t *testing.T) {
// there is a gap between samples in the dataset below
timestamps := []int64{0, 15000, 30000, 70000}
values := []float64{1, 1, 1, 1}
// if step > gap, then delta will always respect value before gap
t.Run("step>gap", func(t *testing.T) {
t.Run("step > gap", func(t *testing.T) {
rc := rollupConfig{
Func: rollupDelta,
Start: 0,
End: 70000,
Step: 45000,
Window: 0,
MaxPointsPerSeries: 1e4,
}
rc.Timestamps = rc.getTimestamps()
gotValues, samplesScanned := rc.Do(nil, values, timestamps)
if samplesScanned != 7 {
t.Fatalf("expecting 8 samplesScanned from rollupConfig.Do; got %d", samplesScanned)
}
valuesExpected := []float64{1, 0}
timestampsExpected := []int64{0, 45e3}
testRowsEqual(t, gotValues, rc.Timestamps, valuesExpected, timestampsExpected)
})
// even if LookbackDelta < gap
t.Run("step>gap;LookbackDelta<gap", func(t *testing.T) {
rc := rollupConfig{
Func: rollupDelta,
Start: 0,
End: 70000,
Step: 45000,
LookbackDelta: 10e3,
Window: 0,
MaxPointsPerSeries: 1e4,
}
rc.Timestamps = rc.getTimestamps()
gotValues, samplesScanned := rc.Do(nil, values, timestamps)
if samplesScanned != 7 {
t.Fatalf("expecting 8 samplesScanned from rollupConfig.Do; got %d", samplesScanned)
}
valuesExpected := []float64{1, 0}
timestampsExpected := []int64{0, 45e3}
testRowsEqual(t, gotValues, rc.Timestamps, valuesExpected, timestampsExpected)
})
// if step < gap and LookbackDelta==0 then delta will always respect value before gap
// as LookbackDelta=0 ignores staleness
t.Run("step<gap;LookbackDelta=0", func(t *testing.T) {
rc := rollupConfig{
Func: rollupDelta,
Start: 0,
End: 70000,
Step: 10000,
LookbackDelta: 0,
Step: 35000,
Window: 0,
MaxPointsPerSeries: 1e4,
}
@@ -1670,14 +1609,12 @@ func TestRollupDeltaWithStaleness(t *testing.T) {
if samplesScanned != 8 {
t.Fatalf("expecting 8 samplesScanned from rollupConfig.Do; got %d", samplesScanned)
}
valuesExpected := []float64{1, 0, 0, 0, 0, 0, 0, 0}
timestampsExpected := []int64{0, 10e3, 20e3, 30e3, 40e3, 50e3, 60e3, 70e3}
valuesExpected := []float64{1, 0, 0}
timestampsExpected := []int64{0, 35e3, 70e3}
testRowsEqual(t, gotValues, rc.Timestamps, valuesExpected, timestampsExpected)
})
// if step < gap and LookbackDelta>0 then delta will respect value before gap
// only if it is not stale according to LookbackDelta
t.Run("step<gap;LookbackDelta>0", func(t *testing.T) {
t.Run("step < gap", func(t *testing.T) {
rc := rollupConfig{
Func: rollupDelta,
Start: 0,
@@ -1685,7 +1622,6 @@ func TestRollupDeltaWithStaleness(t *testing.T) {
Step: 10000,
Window: 0,
MaxPointsPerSeries: 1e4,
LookbackDelta: 30e3,
}
rc.Timestamps = rc.getTimestamps()
gotValues, samplesScanned := rc.Do(nil, values, timestamps)
@@ -1720,116 +1656,3 @@ func TestRollupDeltaWithStaleness(t *testing.T) {
testRowsEqual(t, gotValues, rc.Timestamps, valuesExpected, timestampsExpected)
})
}
func TestRollupIncreasePureWithStaleness(t *testing.T) {
// there is a gap between samples in the dataset below
timestamps := []int64{0, 15000, 30000, 70000}
values := []float64{1, 1, 1, 1}
// if step > gap, then delta will always respect value before gap
t.Run("step>gap", func(t *testing.T) {
rc := rollupConfig{
Func: rollupIncreasePure,
Start: 0,
End: 70000,
Step: 45000,
Window: 0,
MaxPointsPerSeries: 1e4,
}
rc.Timestamps = rc.getTimestamps()
gotValues, samplesScanned := rc.Do(nil, values, timestamps)
if samplesScanned != 7 {
t.Fatalf("expecting 8 samplesScanned from rollupConfig.Do; got %d", samplesScanned)
}
valuesExpected := []float64{1, 0}
timestampsExpected := []int64{0, 45e3}
testRowsEqual(t, gotValues, rc.Timestamps, valuesExpected, timestampsExpected)
})
// even if LookbackDelta < gap
t.Run("step>gap;LookbackDelta<gap", func(t *testing.T) {
rc := rollupConfig{
Func: rollupIncreasePure,
Start: 0,
End: 70000,
Step: 45000,
LookbackDelta: 10e3,
Window: 0,
MaxPointsPerSeries: 1e4,
}
rc.Timestamps = rc.getTimestamps()
gotValues, samplesScanned := rc.Do(nil, values, timestamps)
if samplesScanned != 7 {
t.Fatalf("expecting 8 samplesScanned from rollupConfig.Do; got %d", samplesScanned)
}
valuesExpected := []float64{1, 0}
timestampsExpected := []int64{0, 45e3}
testRowsEqual(t, gotValues, rc.Timestamps, valuesExpected, timestampsExpected)
})
// if step < gap and LookbackDelta==0 then delta will always respect value before gap
// as LookbackDelta=0 ignores staleness
t.Run("step<gap;LookbackDelta=0", func(t *testing.T) {
rc := rollupConfig{
Func: rollupIncreasePure,
Start: 0,
End: 70000,
Step: 10000,
LookbackDelta: 0,
Window: 0,
MaxPointsPerSeries: 1e4,
}
rc.Timestamps = rc.getTimestamps()
gotValues, samplesScanned := rc.Do(nil, values, timestamps)
if samplesScanned != 8 {
t.Fatalf("expecting 8 samplesScanned from rollupConfig.Do; got %d", samplesScanned)
}
valuesExpected := []float64{1, 0, 0, 0, 0, 0, 0, 0}
timestampsExpected := []int64{0, 10e3, 20e3, 30e3, 40e3, 50e3, 60e3, 70e3}
testRowsEqual(t, gotValues, rc.Timestamps, valuesExpected, timestampsExpected)
})
// if step < gap and LookbackDelta>0 then delta will respect value before gap
// only if it is not stale according to LookbackDelta
t.Run("step<gap;LookbackDelta>0", func(t *testing.T) {
rc := rollupConfig{
Func: rollupIncreasePure,
Start: 0,
End: 70000,
Step: 10000,
Window: 0,
MaxPointsPerSeries: 1e4,
LookbackDelta: 30e3,
}
rc.Timestamps = rc.getTimestamps()
gotValues, samplesScanned := rc.Do(nil, values, timestamps)
if samplesScanned != 8 {
t.Fatalf("expecting 8 samplesScanned from rollupConfig.Do; got %d", samplesScanned)
}
valuesExpected := []float64{1, 0, 0, 0, 0, 0, 0, 1}
timestampsExpected := []int64{0, 10e3, 20e3, 30e3, 40e3, 50e3, 60e3, 70e3}
testRowsEqual(t, gotValues, rc.Timestamps, valuesExpected, timestampsExpected)
})
// there is a staleness marker between samples in the dataset below
timestamps = []int64{0, 10000, 20000, 30000, 40000}
values = []float64{1, 1, 1, decimal.StaleNaN, 1}
t.Run("staleness marker", func(t *testing.T) {
rc := rollupConfig{
Func: rollupIncreasePure,
Start: 0,
End: 40000,
Step: 10000,
Window: 0,
MaxPointsPerSeries: 1e4,
}
rc.Timestamps = rc.getTimestamps()
gotValues, samplesScanned := rc.Do(nil, values, timestamps)
if samplesScanned != 10 {
t.Fatalf("expecting 10 samplesScanned from rollupConfig.Do; got %d", samplesScanned)
}
valuesExpected := []float64{1, 0, 0, nan, 1}
timestampsExpected := []int64{0, 10e3, 20e3, 30e3, 40e3}
testRowsEqual(t, gotValues, rc.Timestamps, valuesExpected, timestampsExpected)
})
}

View File

@@ -15,7 +15,7 @@ import (
var (
maxExportDuration = flag.Duration("search.maxExportDuration", time.Hour*24*30, "The maximum duration for /api/v1/export call")
maxDeleteDuration = flag.Duration("search.maxDeleteDuration", time.Minute*5, "The maximum duration for /api/v1/admin/tsdb/delete_series call")
maxDeleteDuration = flag.Duration("search.maxDeleteDuration", time.Minute*5, "The maximum duration for /api/v1/delete call")
maxQueryDuration = flag.Duration("search.maxQueryDuration", time.Second*30, "The maximum duration for query execution. It can be overridden to a smaller value on a per-query basis via 'timeout' query arg")
maxStatusRequestDuration = flag.Duration("search.maxStatusRequestDuration", time.Minute*5, "The maximum duration for /api/v1/status/* requests")
maxLabelsAPIDuration = flag.Duration("search.maxLabelsAPIDuration", time.Second*5, "The maximum duration for /api/v1/labels, /api/v1/label/.../values and /api/v1/series requests. "+
@@ -53,18 +53,18 @@ func GetDeadlineForExport(r *http.Request, startTime time.Time) Deadline {
return getDeadlineWithMaxDuration(r, startTime, dMax, "-search.maxExportDuration")
}
// GetDeadlineForDelete returns deadline for the given request to /api/v1/delete.
func GetDeadlineForDelete(r *http.Request, startTime time.Time) Deadline {
dMax := maxDeleteDuration.Milliseconds()
return getDeadlineWithMaxDuration(r, startTime, dMax, "-search.maxDeleteDuration")
}
// GetDeadlineForLabelsAPI returns deadline for the given request to /api/v1/labels, /api/v1/label/.../values or /api/v1/series
func GetDeadlineForLabelsAPI(r *http.Request, startTime time.Time) Deadline {
dMax := maxLabelsAPIDuration.Milliseconds()
return getDeadlineWithMaxDuration(r, startTime, dMax, "-search.maxLabelsAPIDuration")
}
// GetDeadlineForDelete returns deadline for the given request to /api/v1/admin/tsdb/delete_series.
func GetDeadlineForDelete(r *http.Request, startTime time.Time) Deadline {
dMax := maxDeleteDuration.Milliseconds()
return getDeadlineWithMaxDuration(r, startTime, dMax, "-search.maxDeleteDuration")
}
func getDeadlineWithMaxDuration(r *http.Request, startTime time.Time, dMax int64, flagHint string) Deadline {
d, err := httputils.GetDuration(r, "timeout", 0)
if err != nil {

View File

@@ -1,13 +1,13 @@
{
"files": {
"main.css": "./static/css/main.af583aad.css",
"main.js": "./static/js/main.1413b18d.js",
"main.css": "./static/css/main.63479b72.css",
"main.js": "./static/js/main.256ee243.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.af583aad.css",
"static/js/main.1413b18d.js"
"static/css/main.63479b72.css",
"static/js/main.256ee243.js"
]
}

View File

@@ -1 +1 @@
<!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.1413b18d.js"></script><link href="./static/css/main.af583aad.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.256ee243.js"></script><link href="./static/css/main.63479b72.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,4 @@
FROM golang:1.23.5 AS build-web-stage
FROM golang:1.23.4 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.21.2
FROM alpine:3.21.0
USER root
COPY --from=build-web-stage /build/web-amd64 /app/web

View File

@@ -1,6 +1,3 @@
import uPlot from "uplot";
import { ReactNode } from "react";
export interface MetricBase {
group: number;
metric: {
@@ -9,13 +6,13 @@ export interface MetricBase {
}
export interface MetricResult extends MetricBase {
values: [number, string][];
values: [number, string][]
}
export interface InstantMetricResult extends MetricBase {
value?: [number, string];
values?: [number, string][];
value?: [number, string]
values?: [number, string][]
}
export interface ExportMetricResult extends MetricBase {
@@ -47,23 +44,9 @@ export interface LogHits {
timestamps: string[];
values: number[];
total?: number;
fields: { [key: string]: string; };
_isOther: boolean;
}
export interface LegendLogHits {
label: string;
total: number;
totalHits: number;
isOther: boolean;
fields: { [key: string]: string; };
stroke?: uPlot.Series.Stroke;
}
export interface LegendLogHitsMenu {
title: string;
icon?: ReactNode;
handler?: () => void;
fields: {
[key: string]: string;
};
}
export interface ReportMetaData {

View File

@@ -1,23 +1,22 @@
import React, { FC, useCallback, useMemo, useRef, useState } from "preact/compat";
import React, { FC, useMemo, useRef, useState } from "preact/compat";
import "./style.scss";
import "uplot/dist/uPlot.min.css";
import useElementSize from "../../../hooks/useElementSize";
import uPlot, { AlignedData } from "uplot";
import { useEffect } from "react";
import useBarHitsOptions, { getLabelFromLogHit } from "./hooks/useBarHitsOptions";
import useBarHitsOptions from "./hooks/useBarHitsOptions";
import BarHitsTooltip from "./BarHitsTooltip/BarHitsTooltip";
import { TimeParams } from "../../../types";
import usePlotScale from "../../../hooks/uplot/usePlotScale";
import useReadyChart from "../../../hooks/uplot/useReadyChart";
import useZoomChart from "../../../hooks/uplot/useZoomChart";
import classNames from "classnames";
import { LegendLogHits, LogHits } from "../../../api/types";
import { LogHits } from "../../../api/types";
import { addSeries, delSeries, setBand } from "../../../utils/uplot";
import { GraphOptions, GRAPH_STYLES } from "./types";
import BarHitsOptions from "./BarHitsOptions/BarHitsOptions";
import stack from "../../../utils/uplot/stack";
import BarHitsLegend from "./BarHitsLegend/BarHitsLegend";
import { calculateTotalHits, sortLogHits } from "../../../utils/logs";
interface Props {
logHits: LogHits[];
@@ -58,29 +57,6 @@ const BarHitsChart: FC<Props> = ({ logHits, data: _data, period, setPeriod, onAp
graphOptions
});
const prepareLegend = useCallback((hits: LogHits[], totalHits: number): LegendLogHits[] => {
return hits.map((hit) => {
const label = getLabelFromLogHit(hit);
const legendItem: LegendLogHits = {
label,
isOther: hit._isOther,
fields: hit.fields,
total: hit.total || 0,
totalHits,
stroke: series.find((s) => s.label === label)?.stroke,
};
return legendItem;
}).sort(sortLogHits("total"));
}, [series]);
const legendDetails: LegendLogHits[] = useMemo(() => {
const totalHits = calculateTotalHits(logHits);
return prepareLegend(logHits, totalHits);
}, [logHits, prepareLegend]);
useEffect(() => {
if (!uPlotInst) return;
delSeries(uPlotInst);
@@ -145,7 +121,6 @@ const BarHitsChart: FC<Props> = ({ logHits, data: _data, period, setPeriod, onAp
<BarHitsLegend
uPlotInst={uPlotInst}
onApplyFilter={onApplyFilter}
legendDetails={legendDetails}
/>
)}
</div>

View File

@@ -1,53 +1,83 @@
import React, { FC, useEffect, useState } from "preact/compat";
import React, { FC, useCallback, useEffect, useState } from "preact/compat";
import uPlot, { Series } from "uplot";
import "./style.scss";
import "../../Line/Legend/style.scss";
import BarHitsLegendItem from "./BarHitsLegendItem";
import { LegendLogHits } from "../../../../api/types";
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;
legendDetails: LegendLogHits[];
onApplyFilter: (value: string) => void;
}
const BarHitsLegend: FC<Props> = ({ uPlotInst, legendDetails, onApplyFilter }) => {
const BarHitsLegend: FC<Props> = ({ uPlotInst, onApplyFilter }) => {
const [series, setSeries] = useState<Series[]>([]);
const totalHits = legendDetails[0]?.totalHits || 0;
const [pairs, setPairs] = useState<string[][]>([]);
const getSeries = () => {
return uPlotInst.series.filter(s => s.scale !== "x");
};
const handleRedrawGraph = () => {
uPlotInst.redraw();
setSeries(getSeries());
};
useEffect(() => {
setSeries(getSeries());
const updateSeries = useCallback(() => {
const series = uPlotInst.series.filter(s => s.scale !== "x");
setSeries(series);
setPairs(series.map(s => getStreamPairs(s.label || "")));
}, [uPlotInst]);
const handleClickByValue = (value: string) => (e: MouseEvent<HTMLDivElement>) => {
const metaKey = e.metaKey || e.ctrlKey;
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();
};
useEffect(updateSeries, [uPlotInst]);
return (
<div className="vm-bar-hits-legend">
{legendDetails.map((legend) => (
<BarHitsLegendItem
key={legend.label}
legend={legend}
series={series}
onRedrawGraph={handleRedrawGraph}
onApplyFilter={onApplyFilter}
/>
{series.map((s, i) => (
<Tooltip
key={s.label}
title={(
<ul className="vm-bar-hits-legend-info">
<li>Click to {s.show ? "hide" : "show"} the _stream.</li>
<li>{isMacOs() ? "Cmd" : "Ctrl"} + Click to filter by the _stream.</li>
</ul>
)}
>
<div
className={classNames({
"vm-bar-hits-legend-item": true,
"vm-bar-hits-legend-item_hide": !s.show,
})}
onClick={handleClickByStream(s)}
>
<div
className="vm-bar-hits-legend-item__marker"
style={{ backgroundColor: `${(s?.stroke as () => string)?.()}` }}
/>
<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>
))}
<div className="vm-bar-hits-legend-info">
<div>
Total hits: <b>{totalHits.toLocaleString("en-US")}</b>
</div>
<div>
<code>L-Click</code> toggles visibility.&nbsp;
<code>R-Click</code> opens menu.
</div>
</div>
</div>
);
};

View File

@@ -1,92 +0,0 @@
import React, { FC, useMemo, useRef, useState } from "preact/compat";
import classNames from "classnames";
import { Series } from "uplot";
import { MouseEvent } from "react";
import { LegendLogHits } from "../../../../api/types";
import { getStreamPairs } from "../../../../utils/logs";
import { formatNumberShort } from "../../../../utils/math";
import Popper from "../../../Main/Popper/Popper";
import useBoolean from "../../../../hooks/useBoolean";
import LegendHitsMenu from "../LegendHitsMenu/LegendHitsMenu";
interface Props {
legend: LegendLogHits;
series: Series[];
onRedrawGraph: () => void;
onApplyFilter: (value: string) => void;
}
const BarHitsLegendItem: FC<Props> = ({ legend, series, onRedrawGraph, onApplyFilter }) => {
const {
value: openContextMenu,
setTrue: handleOpenContextMenu,
setFalse: handleCloseContextMenu,
} = useBoolean(false);
const legendRef = useRef<HTMLDivElement>(null);
const [clickPosition, setClickPosition] = useState<{ top: number; left: number } | null>(null);
const targetSeries = useMemo(() => series.find(s => s.label === legend.label), [series]);
const fields = useMemo(() => getStreamPairs(legend.label), [legend.label]);
const label = fields.join(", ");
const totalShortFormatted = formatNumberShort(legend.total);
const handleClickByStream = (e: MouseEvent<HTMLDivElement>) => {
if (!targetSeries) return;
if (e.metaKey || e.ctrlKey) {
targetSeries.show = !targetSeries.show;
} else {
const isOnlyTargetVisible = series.every(s => s === targetSeries || !s.show);
series.forEach(s => {
s.show = isOnlyTargetVisible || (s === targetSeries);
});
}
onRedrawGraph();
};
const handleContextMenu = (e: MouseEvent<HTMLDivElement>) => {
e.preventDefault();
setClickPosition({ top: e.clientY, left: e.clientX });
handleOpenContextMenu();
};
return (
<div
ref={legendRef}
className={classNames({
"vm-bar-hits-legend-item": true,
"vm-bar-hits-legend-item_other": legend.isOther,
"vm-bar-hits-legend-item_hide": !targetSeries?.show,
})}
onClick={handleClickByStream}
onContextMenu={handleContextMenu}
>
<div
className="vm-bar-hits-legend-item__marker"
style={{ backgroundColor: `${legend.stroke}` }}
/>
<div className="vm-bar-hits-legend-item__label">{label}</div>
<span className="vm-bar-hits-legend-item__total">({totalShortFormatted})</span>
<Popper
placement="fixed"
open={openContextMenu}
buttonRef={legendRef}
placementPosition={clickPosition}
onClose={handleCloseContextMenu}
>
<LegendHitsMenu
legend={legend}
fields={fields}
onApplyFilter={onApplyFilter}
onClose={handleCloseContextMenu}
/>
</Popper>
</div>
);
};
export default BarHitsLegendItem;

View File

@@ -3,16 +3,16 @@
.vm-bar-hits-legend {
display: flex;
flex-wrap: wrap;
gap: $padding-small;
padding: 0 $padding-small $padding-small;
color: $color-text;
&-item {
max-width: 50%;
display: flex;
display: grid;
grid-template-columns: auto 1fr;
align-items: center;
gap: $padding-small;
font-size: $font-size-small;
padding: $padding-small $padding-global;
font-size: 12px;
padding: 0 $padding-small;
border-radius: $border-radius-small;
cursor: pointer;
transition: 0.2s;
@@ -27,44 +27,34 @@
}
&__marker {
min-width: 14px;
max-width: 14px;
width: 14px;
height: 14px;
border: $color-background-block;
}
&__label {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
&-pairs {
display: flex;
gap: $padding-small;
&__total {
color: $color-text-secondary;
font-style: italic;
grid-column: 2;
&__value {
padding: $padding-small 0;
&:hover {
text-decoration: underline;
}
&:after {
content: ",";
}
&:last-child:after {
content: "";
}
}
}
}
&-info {
flex-grow: 1;
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
padding-top: $padding-small;
color: $color-text-secondary;
font-size: $font-size-small;
code {
display: inline-block;
padding: calc($padding-small / 2) $padding-small;
font-size: $font-size-small;
text-align: center;
background-color: $color-background-body;
background-repeat: repeat-x;
border: $border-divider;
border-radius: 4px;
}
list-style-position: inside;
}
}

View File

@@ -5,6 +5,7 @@ import "./style.scss";
import useStateSearchParams from "../../../../hooks/useStateSearchParams";
import { useSearchParams } from "react-router-dom";
import Button from "../../../Main/Button/Button";
import classNames from "classnames";
import { SettingsIcon, VisibilityIcon, VisibilityOffIcon } from "../../../Main/Icons";
import Tooltip from "../../../Main/Tooltip/Tooltip";
import Popper from "../../../Main/Popper/Popper";
@@ -23,20 +24,27 @@ const BarHitsOptions: FC<Props> = ({ onChange }) => {
setFalse: handleCloseOptions,
} = useBoolean(false);
const [graphStyle, setGraphStyle] = useStateSearchParams(GRAPH_STYLES.LINE_STEPPED, "graph");
const [stacked, setStacked] = useStateSearchParams(false, "stacked");
const [fill, setFill] = useStateSearchParams("true", "fill");
const [fill, setFill] = useStateSearchParams(false, "fill");
const [hideChart, setHideChart] = useStateSearchParams(false, "hide_chart");
const options: GraphOptions = useMemo(() => ({
graphStyle: GRAPH_STYLES.BAR,
graphStyle,
stacked,
fill: fill === "true",
fill,
hideChart,
}), [stacked, fill, hideChart]);
}), [graphStyle, stacked, fill, hideChart]);
const handleChangeGraphStyle = (val: string) => () => {
setGraphStyle(val as GRAPH_STYLES);
searchParams.set("graph", val);
setSearchParams(searchParams);
};
const handleChangeFill = (val: boolean) => {
setFill(`${val}`);
searchParams.set("fill", `${val}`);
setFill(val);
val ? searchParams.set("fill", "true") : searchParams.delete("fill");
setSearchParams(searchParams);
};
@@ -89,6 +97,21 @@ const BarHitsOptions: FC<Props> = ({ onChange }) => {
title={"Graph settings"}
>
<div className="vm-bar-hits-options-settings">
<div className="vm-bar-hits-options-settings-item vm-bar-hits-options-settings-item_list">
<p className="vm-bar-hits-options-settings-item__title">Graph style:</p>
{Object.values(GRAPH_STYLES).map(style => (
<div
key={style}
className={classNames({
"vm-list-item": true,
"vm-list-item_active": graphStyle === style,
})}
onClick={handleChangeGraphStyle(style)}
>
{style}
</div>
))}
</div>
<div className="vm-bar-hits-options-settings-item">
<Switch
label={"Stacked"}
@@ -99,7 +122,7 @@ const BarHitsOptions: FC<Props> = ({ onChange }) => {
<div className="vm-bar-hits-options-settings-item">
<Switch
label={"Fill"}
value={fill === "true"}
value={fill}
onChange={handleChangeFill}
/>
</div>

View File

@@ -11,12 +11,12 @@
&-settings {
display: grid;
align-items: flex-start;
min-width: 200px;
gap: $padding-global;
padding-bottom: $padding-global;
min-width: 200px;
&-item {
padding: 0 $padding-global;
border-bottom: $border-divider;
padding: 0 $padding-global $padding-global;
&_list {
padding: 0;

View File

@@ -5,7 +5,6 @@ import { DATE_TIME_FORMAT } from "../../../../constants/date";
import classNames from "classnames";
import "./style.scss";
import "../../ChartTooltip/style.scss";
import { sortLogHits } from "../../../../utils/logs";
interface Props {
data: AlignedData;
@@ -27,7 +26,7 @@ const BarHitsTooltip: FC<Props> = ({ data, focusDataIdx, uPlotInst }) => {
const tooltipItems = values.map((value, i) => {
const targetSeries = series[i + 1];
const stroke = (targetSeries?.stroke as () => string)?.();
const label = targetSeries?.label;
const label = targetSeries?.label || "other";
const show = targetSeries?.show;
return {
label,
@@ -35,7 +34,7 @@ const BarHitsTooltip: FC<Props> = ({ data, focusDataIdx, uPlotInst }) => {
value,
show
};
}).filter(item => item.value > 0 && item.show).sort(sortLogHits("value"));
}).filter(item => item.value > 0 && item.show).sort((a, b) => b.value - a.value);
const point = {
top: tooltipItems[0] ? uPlotInst?.valToPos?.(tooltipItems[0].value, "y") || 0 : 0,
@@ -105,19 +104,16 @@ const BarHitsTooltip: FC<Props> = ({ data, focusDataIdx, uPlotInst }) => {
className="vm-chart-tooltip-data__marker"
style={{ background: item.stroke }}
/>
<p className="vm-bar-hits-tooltip-item">
<span className="vm-bar-hits-tooltip-item__label">{item.label}</span>
<span>{item.value.toLocaleString("en-US")}</span>
<p>
{item.label}: <b>{item.value}</b>
</p>
</div>
))}
</div>
{tooltipData.values.length > 1 && (
<div className="vm-chart-tooltip-data">
<span/>
<p className="vm-bar-hits-tooltip-item">
<span className="vm-bar-hits-tooltip-item__label">Total</span>
<span>{tooltipData.total.toLocaleString("en-US")}</span>
<p>
Total records: <b>{tooltipData.total}</b>
</p>
</div>
)}

View File

@@ -9,19 +9,4 @@
opacity: 1;
pointer-events: auto;
}
&-item {
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
gap: $padding-global;
max-width: 100%;
&__label {
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}

View File

@@ -1,50 +0,0 @@
import React, { FC } from "preact/compat";
import "./style.scss";
import { LegendLogHits } from "../../../../api/types";
import LegendHitsMenuStats from "./LegendHitsMenuStats";
import LegendHitsMenuBase from "./LegendHitsMenuBase";
import LegendHitsMenuRow from "./LegendHitsMenuRow";
import LegendHitsMenuFields from "./LegendHitsMenuFields";
import { LOGS_LIMIT_HITS } from "../../../../constants/logs";
const otherDescription = `aggregated results for fields not in the top ${LOGS_LIMIT_HITS}`;
interface Props {
legend: LegendLogHits;
fields: string[];
onApplyFilter: (value: string) => void;
onClose: () => void;
}
const LegendHitsMenu: FC<Props> = ({ legend, fields, onApplyFilter, onClose }) => {
return (
<div className="vm-legend-hits-menu">
<div className="vm-legend-hits-menu-section">
<LegendHitsMenuRow
className="vm-legend-hits-menu-row_info"
title={legend.isOther ? otherDescription : legend.label}
/>
</div>
{!legend.isOther && (
<LegendHitsMenuBase
legend={legend}
onApplyFilter={onApplyFilter}
onClose={onClose}
/>
)}
{!legend.isOther && (
<LegendHitsMenuFields
fields={fields}
onApplyFilter={onApplyFilter}
onClose={onClose}
/>
)}
<LegendHitsMenuStats legend={legend}/>
</div>
);
};
export default LegendHitsMenu;

View File

@@ -1,64 +0,0 @@
import React, { FC } from "preact/compat";
import LegendHitsMenuRow from "./LegendHitsMenuRow";
import useCopyToClipboard from "../../../../hooks/useCopyToClipboard";
import { CopyIcon, FilterIcon, FilterOffIcon } from "../../../Main/Icons";
import { LegendLogHits, LegendLogHitsMenu } from "../../../../api/types";
import { LOGS_GROUP_BY } from "../../../../constants/logs";
interface Props {
legend: LegendLogHits;
onApplyFilter: (value: string) => void;
onClose: () => void;
}
const LegendHitsMenuBase: FC<Props> = ({ legend, onApplyFilter, onClose }) => {
const copyToClipboard = useCopyToClipboard();
const handleAddStreamToFilter = () => {
onApplyFilter(`${LOGS_GROUP_BY}: ${legend.label}`);
onClose();
};
const handleExcludeStreamToFilter = () => {
onApplyFilter(`(NOT ${LOGS_GROUP_BY}: ${legend.label})`);
onClose();
};
const handlerCopyLabel = async () => {
await copyToClipboard(legend.label, `${legend.label} has been copied`);
onClose();
};
const options: LegendLogHitsMenu[] = [
{
title: `Copy ${LOGS_GROUP_BY} name`,
icon: <CopyIcon/>,
handler: handlerCopyLabel,
},
{
title: `Add ${LOGS_GROUP_BY} to filter`,
icon: <FilterIcon/>,
handler: handleAddStreamToFilter,
},
{
title: `Exclude ${LOGS_GROUP_BY} to filter`,
icon: <FilterOffIcon/>,
handler: handleExcludeStreamToFilter,
}
];
return (
<div className="vm-legend-hits-menu-section">
{options.map(({ icon, title, handler }) => (
<LegendHitsMenuRow
key={title}
iconStart={icon}
title={title}
handler={handler}
/>
))}
</div>
);
};
export default LegendHitsMenuBase;

View File

@@ -1,74 +0,0 @@
import React, { FC, useMemo } from "preact/compat";
import LegendHitsMenuRow from "./LegendHitsMenuRow";
import { CopyIcon, FilterIcon, FilterOffIcon } from "../../../Main/Icons";
import { convertToFieldFilter } from "../../../../utils/logs";
import { LegendLogHitsMenu } from "../../../../api/types";
import useCopyToClipboard from "../../../../hooks/useCopyToClipboard";
interface Props {
fields: string[];
onApplyFilter: (value: string) => void;
onClose: () => void;
}
const LegendHitsMenuFields: FC<Props> = ({ fields, onApplyFilter, onClose }) => {
const copyToClipboard = useCopyToClipboard();
const handleCopy = (field: string) => async () => {
await copyToClipboard(field, `${field} has been copied`);
onClose();
};
const handleAddToFilter = (field: string) => () => {
onApplyFilter(field);
onClose();
};
const handleExcludeToFilter = (field: string) => () => {
onApplyFilter(`-${field}`);
onClose();
};
const generateFieldMenu = (field: string): LegendLogHitsMenu[] => {
return [
{
title: "Copy",
icon: <CopyIcon/>,
handler: handleCopy(field),
},
{
title: "Add to filter",
icon: <FilterIcon/>,
handler: handleAddToFilter(field),
},
{
title: "Exclude to filter",
icon: <FilterOffIcon/>,
handler: handleExcludeToFilter(field),
}
];
};
const fieldsWithMenu: LegendLogHitsMenu[] = useMemo(() => {
return fields.map(field => {
const title = convertToFieldFilter(field);
return {
title,
submenu: generateFieldMenu(title),
};
});
}, [fields]);
return (
<div className="vm-legend-hits-menu-section">
{fieldsWithMenu?.map((field) => (
<LegendHitsMenuRow
key={field.title}
{...field}
/>
))}
</div>
);
};
export default LegendHitsMenuFields;

View File

@@ -1,116 +0,0 @@
import React, { FC, useRef, useState } from "preact/compat";
import classNames from "classnames";
import { ReactNode, useEffect } from "react";
import Tooltip from "../../../Main/Tooltip/Tooltip";
import { LegendLogHitsMenu } from "../../../../api/types";
import { ArrowDropDownIcon } from "../../../Main/Icons";
import useClickOutside from "../../../../hooks/useClickOutside";
interface Props {
title: string | ReactNode;
handler?: () => void;
iconStart?: ReactNode;
iconEnd?: ReactNode;
className?: string;
submenu?: LegendLogHitsMenu[];
}
const LegendHitsMenuRow: FC<Props> = ({ title, handler, iconStart, iconEnd, className, submenu }) => {
const containerRef = useRef<HTMLDivElement>(null);
const titleRef = useRef<HTMLDivElement>(null);
const submenuRef = useRef<HTMLDivElement>(null);
const [isOverflownTitle, setIsOverflownTitle] = useState(false);
const [openSubmenu, setOpenSubmenu] = useState(false);
const [posSubmenuLeft, setPosSubmenuLeft] = useState(false);
const hasSubmenu = !!submenu?.length;
const handleToggleContextMenu = () => {
setOpenSubmenu(prev => !prev);
};
const handleCloseContextMenu = () => {
setOpenSubmenu(false);
};
const handleClick = () => {
handler && handler();
hasSubmenu && handleToggleContextMenu();
};
useEffect(() => {
if (!titleRef.current) return;
setIsOverflownTitle(titleRef.current.scrollWidth > titleRef.current.clientWidth);
}, [title, titleRef]);
useEffect(() => {
requestAnimationFrame(() => {
if (!openSubmenu || !submenuRef.current) {
setPosSubmenuLeft(false);
return;
}
const { left, width } = submenuRef.current.getBoundingClientRect();
setPosSubmenuLeft(left + width > window.innerWidth);
});
}, [submenuRef, openSubmenu]);
useClickOutside(containerRef, handleCloseContextMenu);
const titleContent = (
<div
ref={titleRef}
className="vm-legend-hits-menu-row__title"
>
{title}
</div>
);
return (
<div
ref={containerRef}
className={classNames({
"vm-legend-hits-menu-row": true,
"vm-legend-hits-menu-row_interactive": !!handler || hasSubmenu,
[`${className}`]: className
})}
onClick={handleClick}
>
{iconStart && <div className="vm-legend-hits-menu-row__icon">{iconStart}</div>}
{isOverflownTitle ? (<Tooltip title={title}>{titleContent}</Tooltip>) : titleContent}
{iconEnd && !hasSubmenu && <div className="vm-legend-hits-menu-row__icon">{iconEnd}</div>}
{hasSubmenu && (
<div className="vm-legend-hits-menu-row__icon vm-legend-hits-menu-row__icon_drop">
<ArrowDropDownIcon/>
</div>
)}
{openSubmenu && submenu && (
<div
ref={submenuRef}
className={classNames({
"vm-legend-hits-menu": true,
"vm-legend-hits-menu_submenu": true,
"vm-legend-hits-menu_submenu_left": posSubmenuLeft
})}
>
<div className="vm-legend-hits-menu-section">
{submenu.map(({ icon, title, handler }) => (
<LegendHitsMenuRow
key={title}
iconStart={icon}
title={title}
handler={handler}
/>
))}
</div>
</div>
)}
</div>
);
};
export default LegendHitsMenuRow;

View File

@@ -1,23 +0,0 @@
import React, { FC } from "preact/compat";
import { LegendLogHits } from "../../../../api/types";
interface Props {
legend: LegendLogHits;
}
const LegendHitsMenuStats: FC<Props> = ({ legend }) => {
const totalFormatted = legend.total.toLocaleString("en-US");
const percentage = Math.round((legend.total / legend.totalHits) * 100);
return (
<div className="vm-legend-hits-menu-section">
<div className="vm-legend-hits-menu-row">
<div className="vm-legend-hits-menu-row__title">
Total: {totalFormatted} ({percentage}%)
</div>
</div>
</div>
);
};
export default LegendHitsMenuStats;

View File

@@ -1,178 +0,0 @@
@use "src/styles/variables" as *;
.vm-legend-hits-menu {
min-width: 160px;
z-index: 1;
&_submenu {
position: absolute;
top: calc(-1 * $padding-small);
background-color: $color-background-block;
left: calc(100% + ($padding-small / 2));
box-shadow: $box-shadow-popper;
border-radius: $border-radius-small;
animation: vm-submenu-show 150ms cubic-bezier(0.280, 0.840, 0.2, 1);
transform-origin: top left;
&_left {
left: auto;
right: calc(100% + ($padding-small / 2));
transform-origin: top right;
}
}
&-section {
border-bottom: $border-divider;
&:last-child {
border-bottom: none;
}
}
&-row {
position: relative;
display: flex;
gap: $padding-small;
align-items: center;
justify-content: flex-start;
padding: 0 $padding-global;
transition: background-color 0.3s;
color: $color-text;
&_interactive {
cursor: pointer;
&:hover {
background-color: rgba(0, 0, 0, 0.05);
}
}
&_info {
font-size: $font-size-small;
font-weight: 500;
padding-block: $padding-small;
}
&_info &__icon {
color: $color-info;
}
&__icon {
display: flex;
align-items: center;
justify-content: center;
width: 14px;
height: 14px;
&_drop {
transform: rotate(-90deg);
}
}
&__title {
flex-grow: 1;
padding: $padding-global 0;
position: relative;
max-width: 400px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
&-other-list {
width: 80vw;
height: 80vh;
overflow: auto;
&__search {
position: sticky;
top: 0;
padding: $padding-small 0;
background-color: $color-background-block;
border-bottom: $border-divider;
z-index: 2;
}
&-row {
border-bottom: $border-divider;
&_header {
border-bottom: none;
position: sticky;
top: 65px;
background-color: $color-background-block;
z-index: 1;
width: 100%;
&:after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 1px;
border-bottom: $border-divider;
}
}
}
&-cell {
padding: calc($padding-small / 2) 0;
text-align: left;
&_header {
padding: $padding-small;
font-weight: 500;
}
&_number {
padding: $padding-small;
text-align: right;
font-variant-numeric: tabular-nums;
}
&_fields {
width: 100%;
}
}
&-fields {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: flex-start;
&__field {
padding: calc($padding-small / 2) $padding-small;
border-radius: $border-radius-small;
transition: background-color 0.3s;
&:hover {
background-color: $color-hover-black;
}
&:not(:last-child) {
&:after {
content: ',';
}
}
}
}
&-actions {
display: flex;
align-items: center;
justify-content: center;
}
}
}
@keyframes vm-submenu-show {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}

View File

@@ -36,14 +36,6 @@ interface UseGetBarHitsOptionsArgs {
graphOptions: GraphOptions;
}
export const OTHER_HITS_LABEL = "other";
export const getLabelFromLogHit = (logHit: LogHits) => {
if (logHit?._isOther) return OTHER_HITS_LABEL;
const fields = Object.values(logHit?.fields || {});
return fields.map((value) => value || "\"\"").join(", ");
};
const useBarHitsOptions = ({
data,
logHits,
@@ -67,12 +59,12 @@ const useBarHitsOptions = ({
let colorN = 0;
return data.map((_d, i) => {
if (i === 0) return {}; // 0 index is xAxis(timestamps)
const target = logHits?.[i - 1];
const label = getLabelFromLogHit(target);
const color = getCssVariable(target?._isOther ? "color-log-hits-bar-0" : seriesColors[colorN]);
if (!target?._isOther) colorN++;
const fields = Object.values(logHits?.[i - 1]?.fields || {});
const label = fields.map((value) => value || "\"\"").join(", ");
const color = getCssVariable(label ? seriesColors[colorN] : "color-log-hits-bar-0");
if (label) colorN++;
return {
label,
label: label || "other",
width: strokeWidth[graphOptions.graphStyle],
spanGaps: true,
stroke: color,

View File

@@ -32,11 +32,6 @@ $chart-tooltip-y: -1 * ($padding-global + $chart-tooltip-half-icon);
max-width: calc(100vw/3);
}
&_hits &-data {
display: grid;
grid-template-columns: $font-size 1fr;
}
&_sticky {
pointer-events: auto;
z-index: 99;
@@ -95,8 +90,6 @@ $chart-tooltip-y: -1 * ($padding-global + $chart-tooltip-half-icon);
}
&__marker {
min-width: $font-size;
max-width: $font-size;
width: $font-size;
height: $font-size;
border: 1px solid rgba($color-white, 0.5);

View File

@@ -124,7 +124,7 @@ const QueryEditor: FC<QueryEditorProps> = ({
};
useEffect(() => {
setOpenAutocomplete(!!AutocompleteEl && autocompleteQuick);
setOpenAutocomplete(!!AutocompleteEl);
}, [autocompleteQuick]);
useEffect(() => {

View File

@@ -1,246 +0,0 @@
import React, { FC, useMemo, useState } from "preact/compat";
import useBoolean from "../../../hooks/useBoolean";
import { RestartIcon, SettingsIcon } from "../../Main/Icons";
import Button from "../../Main/Button/Button";
import Modal from "../../Main/Modal/Modal";
import Tooltip from "../../Main/Tooltip/Tooltip";
import { Logs } from "../../../api/types";
import Select from "../../Main/Select/Select";
import { useSearchParams } from "react-router-dom";
import "./style.scss";
import Switch from "../../Main/Switch/Switch";
import TextField from "../../Main/TextField/TextField";
import dayjs from "dayjs";
import Hyperlink from "../../Main/Hyperlink/Hyperlink";
import {
LOGS_DISPLAY_FIELDS,
LOGS_GROUP_BY,
LOGS_DATE_FORMAT,
LOGS_URL_PARAMS,
WITHOUT_GROUPING
} from "../../../constants/logs";
const {
GROUP_BY,
NO_WRAP_LINES,
COMPACT_GROUP_HEADER,
DISPLAY_FIELDS,
DATE_FORMAT
} = LOGS_URL_PARAMS;
const title = "Group view settings";
interface Props {
logs: Logs[];
}
const GroupLogsConfigurators: FC<Props> = ({ logs }) => {
const [searchParams, setSearchParams] = useSearchParams();
const groupBy = searchParams.get(GROUP_BY) || LOGS_GROUP_BY;
const noWrapLines = searchParams.get(NO_WRAP_LINES) === "true";
const compactGroupHeader = searchParams.get(COMPACT_GROUP_HEADER) === "true";
const displayFieldsString = searchParams.get(DISPLAY_FIELDS) || "";
const displayFields = displayFieldsString ? displayFieldsString.split(",") : [];
const [dateFormat, setDateFormat] = useState(searchParams.get(DATE_FORMAT) || LOGS_DATE_FORMAT);
const [errorFormat, setErrorFormat] = useState("");
const isGroupChanged = groupBy !== LOGS_GROUP_BY;
const isDisplayFieldsChanged = displayFields.length > 0;
const isTimeChanged = searchParams.get(DATE_FORMAT) !== LOGS_DATE_FORMAT;
const hasChanges = [
isGroupChanged,
isDisplayFieldsChanged,
noWrapLines,
compactGroupHeader,
isTimeChanged
].some(Boolean);
const logsKeys = useMemo(() => {
const excludeKeys = ["_msg", "_time"];
const uniqKeys = Array.from(new Set(logs.map(l => Object.keys(l)).flat()));
return uniqKeys.filter(k => !excludeKeys.includes(k));
}, [logs]);
const {
value: openModal,
toggle: toggleOpen,
setFalse: handleClose,
} = useBoolean(false);
const handleSelectGroupBy = (key: string) => {
searchParams.set(GROUP_BY, key);
setSearchParams(searchParams);
};
const handleSelectDisplayField = (value: string) => {
const prev = displayFields;
const newDisplayFields = prev.includes(value) ? prev.filter(v => v !== value) : [...prev, value];
searchParams.set(DISPLAY_FIELDS, newDisplayFields.join(","));
setSearchParams(searchParams);
};
const handleResetDisplayFields = () => {
searchParams.delete(DISPLAY_FIELDS);
setSearchParams(searchParams);
};
const toggleWrapLines = () => {
searchParams.set(NO_WRAP_LINES, String(!noWrapLines));
setSearchParams(searchParams);
};
const toggleCompactGroupHeader = () => {
searchParams.set(COMPACT_GROUP_HEADER, String(!compactGroupHeader));
setSearchParams(searchParams);
};
const handleChangeDateFormat = (format: string) => {
const date = new Date();
if (!dayjs(date, format, true).isValid()) {
setErrorFormat("Invalid date format");
}
setDateFormat(format);
};
const handleSaveAndClose = () => {
searchParams.set(DATE_FORMAT, dateFormat);
setSearchParams(searchParams);
handleClose();
};
const tooltipContent = () => {
if (!hasChanges) return title;
return (
<div className="vm-group-logs-configurator__tooltip">
<p>{title}</p>
<hr/>
<ul>
{isGroupChanged && <li>Group by <code>{`"${groupBy}"`}</code></li>}
{isDisplayFieldsChanged && <li>Display fields: {displayFields.length || 1}</li>}
{noWrapLines && <li>Single-line text is enabled</li>}
{compactGroupHeader && <li>Compact group header is enabled</li>}
{isTimeChanged && <li>Date format: <code>{dateFormat}</code></li>}
</ul>
</div>
);
};
return (
<>
<div className="vm-group-logs-configurator-button">
<Tooltip title={tooltipContent()}>
<Button
variant="text"
startIcon={<SettingsIcon/>}
onClick={toggleOpen}
ariaLabel={title}
/>
</Tooltip>
{hasChanges && <span className="vm-group-logs-configurator-button__marker"/>}
</div>
{openModal && (
<Modal
title={title}
onClose={handleSaveAndClose}
>
<div className="vm-group-logs-configurator">
<div className="vm-group-logs-configurator-item">
<Select
value={groupBy}
list={[WITHOUT_GROUPING, ...logsKeys]}
label="Group by field"
placeholder="Group by field"
onChange={handleSelectGroupBy}
searchable
/>
<Tooltip title={"Reset grouping"}>
<Button
variant="text"
color="primary"
startIcon={<RestartIcon/>}
onClick={() => handleSelectGroupBy(LOGS_GROUP_BY)}
/>
</Tooltip>
<span className="vm-group-logs-configurator-item__info">
Select a field to group logs by (default: <code>{LOGS_GROUP_BY}</code>).
</span>
</div>
<div className="vm-group-logs-configurator-item">
<Select
value={displayFields}
list={logsKeys}
label="Display fields"
placeholder="Display fields"
onChange={handleSelectDisplayField}
searchable
/>
<Tooltip title={"Clear fields"}>
<Button
variant="text"
color="primary"
startIcon={<RestartIcon/>}
onClick={handleResetDisplayFields}
/>
</Tooltip>
<span className="vm-group-logs-configurator-item__info">
Select fields to display instead of the message (default: <code>{LOGS_DISPLAY_FIELDS}</code>).
</span>
</div>
<div className="vm-group-logs-configurator-item">
<TextField
autofocus
label="Date format"
value={dateFormat}
onChange={handleChangeDateFormat}
error={errorFormat}
/>
<Tooltip title={"Reset format"}>
<Button
variant="text"
color="primary"
startIcon={<RestartIcon/>}
onClick={() => setDateFormat(LOGS_DATE_FORMAT)}
/>
</Tooltip>
<span className="vm-group-logs-configurator-item__info vm-group-logs-configurator-item__info_input">
Set the date format (e.g., <code>YYYY-MM-DD HH:mm:ss</code>).
Learn more in <Hyperlink
href="https://day.js.org/docs/en/display/format"
>this documentation</Hyperlink>. <br/>
Your current date format: <code>{dayjs().format(dateFormat || LOGS_DATE_FORMAT)}</code>
</span>
</div>
<div className="vm-group-logs-configurator-item">
<Switch
value={noWrapLines}
onChange={toggleWrapLines}
label="Single-line message"
/>
<span className="vm-group-logs-configurator-item__info">
Displays message in a single line and truncates it with an ellipsis if it exceeds the available space
</span>
</div>
<div className="vm-group-logs-configurator-item">
<Switch
value={compactGroupHeader}
onChange={toggleCompactGroupHeader}
label="Compact group header"
/>
<span className="vm-group-logs-configurator-item__info">
Shows group headers in one line with a &quot;+N more&quot; badge for extra fields.
</span>
</div>
</div>
</Modal>
)}
</>
);
};
export default GroupLogsConfigurators;

View File

@@ -1,48 +0,0 @@
@use "src/styles/variables" as *;
.vm-group-logs-configurator {
display: grid;
gap: calc($padding-large * 2);
padding: $padding-global 0;
width: 600px;
&-item {
display: grid;
grid-template-columns: 1fr 31px;
align-items: center;
justify-content: stretch;
gap: 0 $padding-small;
&__info {
margin-top: $padding-small;
grid-column: 1/span 2;
font-size: $font-size-small;
color: $color-text-secondary;
line-height: 130%;
&_input {
margin-top: 0;
}
}
}
&-button {
position: relative;
&__marker {
position: absolute;
top: 6px;
left: 6px;
width: 5px;
height: 5px;
border-radius: 50%;
background-color: $color-secondary;
}
}
&__tooltip {
ul {
list-style-position: inside;
}
}
}

View File

@@ -30,10 +30,6 @@ const Accordion: FC<AccordionProps> = ({
onChange && onChange(isOpen);
}, [isOpen]);
useEffect(() => {
setIsOpen(defaultExpanded);
}, [defaultExpanded]);
return (
<>
<header

View File

@@ -581,45 +581,3 @@ export const CommentIcon = () => (
></path>
</svg>
);
export const FilterIcon = () => (
<svg
viewBox="0 0 24 24"
fill="currentColor"
>
<path
d="M4.25 5.61C6.27 8.2 10 13 10 13v6c0 .55.45 1 1 1h2c.55 0 1-.45 1-1v-6s3.72-4.8 5.74-7.39c.51-.66.04-1.61-.79-1.61H5.04c-.83 0-1.3.95-.79 1.61"
></path>
</svg>
);
export const FilterOffIcon = () => (
<svg
viewBox="0 0 24 24"
fill="currentColor"
>
<path
d="M19.79 5.61C20.3 4.95 19.83 4 19 4H6.83l7.97 7.97zM2.81 2.81 1.39 4.22 10 13v6c0 .55.45 1 1 1h2c.55 0 1-.45 1-1v-2.17l5.78 5.78 1.41-1.41z"
></path>
</svg>
);
export const OpenNewIcon = () => (
<svg
viewBox="0 0 24 24"
fill="currentColor"
>
<path
d="M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3z"
></path>
</svg>
);
export const ModalIcon = () => (
<svg
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.89-2-2-2m0 14H5V8h14z"></path>
</svg>
);

View File

@@ -67,11 +67,11 @@ const Modal: FC<ModalProps> = ({
})}
onMouseDown={onClose}
>
<div
className="vm-modal-content"
onMouseDown={handleMouseDown}
>
<div className="vm-modal-content-header">
<div className="vm-modal-content">
<div
className="vm-modal-content-header"
onMouseDown={handleMouseDown}
>
{title && (
<div className="vm-modal-content-header__title">
{title}
@@ -91,6 +91,7 @@ const Modal: FC<ModalProps> = ({
{/* tabIndex to fix Ctrl-A */}
<div
className="vm-modal-content-body"
onMouseDown={handleMouseDown}
tabIndex={0}
>
{children}

View File

@@ -15,10 +15,9 @@ interface PopperProps {
open: boolean
onClose: () => void
buttonRef: React.RefObject<HTMLElement>
placement?: "bottom-right" | "bottom-left" | "top-left" | "top-right" | "fixed"
placementPosition?: { top: number, left: number } | null
placement?: "bottom-right" | "bottom-left" | "top-left" | "top-right"
animation?: string
offset?: { top: number, left: number }
offset?: {top: number, left: number}
clickOutside?: boolean,
fullWidth?: boolean
title?: string
@@ -30,7 +29,6 @@ const Popper: FC<PopperProps> = ({
children,
buttonRef,
placement = "bottom-left",
placementPosition,
open = false,
onClose,
offset = { top: 6, left: 0 },
@@ -94,18 +92,13 @@ const Popper: FC<PopperProps> = ({
if (needAlignRight) position.left = buttonPos.right - popperSize.width;
if (needAlignTop) position.top = buttonPos.top - popperSize.height - offsetTop;
if (placement === "fixed" && placementPosition) {
position.top = Math.max(placementPosition.top + offset.top, 0);
position.left = Math.max(placementPosition.left + offset.left, 0);
return position;
}
const { innerWidth, innerHeight } = window;
const margin = 20;
const isOverflowBottom = (position.top + popperSize.height) > innerHeight;
const isOverflowTop = (position.top) < 0;
const isOverflowRight = (position.left + popperSize.width) > innerWidth;
const isOverflowLeft = (position.left) < 0;
const isOverflowBottom = (position.top + popperSize.height + margin) > innerHeight;
const isOverflowTop = (position.top - margin) < 0;
const isOverflowRight = (position.left + popperSize.width + margin) > innerWidth;
const isOverflowLeft = (position.left - margin) < 0;
if (isOverflowBottom) position.top = buttonPos.top - popperSize.height - offsetTop;
if (isOverflowTop) position.top = buttonPos.height + buttonPos.top + offsetTop;
@@ -113,11 +106,11 @@ const Popper: FC<PopperProps> = ({
if (isOverflowLeft) position.left = buttonPos.left + offsetLeft;
if (fullWidth) position.width = `${buttonPos.width}px`;
if (position.top < 0) position.top = 0;
if (position.left < 0) position.left = 0;
if (position.top < 0) position.top = 20;
if (position.left < 0) position.left = 20;
return position;
}, [buttonRef, placement, isOpen, children, fullWidth]);
},[buttonRef, placement, isOpen, children, fullWidth]);
const handleClickClose = (e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => {
e.stopPropagation();
@@ -138,10 +131,10 @@ const Popper: FC<PopperProps> = ({
if (!popperRef.current || !isOpen || (isMobile && !disabledFullScreen)) return;
const { right, width } = popperRef.current.getBoundingClientRect();
if (right > window.innerWidth) {
const left = window.innerWidth - width;
popperRef.current.style.left = `${left}px`;
const left = window.innerWidth - 20 - width;
popperRef.current.style.left = left < window.innerWidth ? "0" : `${left}px`;
}
}, [isOpen, popperRef, placementPosition]);
}, [isOpen, popperRef]);
const handlePopstate = useCallback(() => {
if (isOpen && isMobile && !disabledFullScreen) {

View File

@@ -11,7 +11,7 @@
border-radius: $border-radius-small;
&_open {
z-index: 100;
z-index: 101;
opacity: 1;
transform-origin: top center;
animation: vm-slider 150ms cubic-bezier(0.280, 0.840, 0.420, 1.1);

View File

@@ -33,9 +33,9 @@
align-items: center;
justify-content: center;
background-color: $color-hover-black;
padding: 2px 2px 2px $padding-small;
padding: 2px 2px 2px 6px;
border-radius: $border-radius-small;
font-size: $font-size-small;
font-size: $font-size;
line-height: $font-size;
max-width: 100%;

View File

@@ -11,7 +11,7 @@ import useBoolean from "../../../hooks/useBoolean";
import TextField from "../../Main/TextField/TextField";
import { KeyboardEvent, useState } from "react";
import Modal from "../../Main/Modal/Modal";
import { useSearchParams } from "react-router-dom";
import { getFromStorage, removeFromStorage, saveToStorage } from "../../../utils/storage";
const title = "Table settings";
@@ -30,8 +30,6 @@ const TableSettings: FC<TableSettingsProps> = ({
onChangeColumns,
toggleTableCompact
}) => {
const [searchParams, setSearchParams] = useSearchParams();
const buttonRef = useRef<HTMLDivElement>(null);
const {
@@ -40,6 +38,11 @@ const TableSettings: FC<TableSettingsProps> = ({
setFalse: handleClose,
} = useBoolean(false);
const {
value: saveColumns,
toggle: toggleSaveColumns,
} = useBoolean(Boolean(getFromStorage("TABLE_COLUMNS")));
const [searchColumn, setSearchColumn] = useState("");
const [indexFocusItem, setIndexFocusItem] = useState(-1);
@@ -57,34 +60,15 @@ const TableSettings: FC<TableSettingsProps> = ({
return filteredColumns.every(col => selectedColumns.includes(col));
}, [selectedColumns, filteredColumns]);
const handleChangeDisplayColumns = (displayColumns: string[]) => {
onChangeColumns(displayColumns);
const updatedParams = new URLSearchParams(searchParams.toString());
const isAllCheck = displayColumns.length === columns.length;
if (isAllCheck) {
updatedParams.delete("columns");
} else {
updatedParams.set("columns", displayColumns.map(encodeURIComponent).join(","));
}
setSearchParams(updatedParams);
};
const handleChange = (key: string) => {
const displayColumns = selectedColumns.includes(key)
? selectedColumns.filter(col => col !== key)
: [...selectedColumns, key];
handleChangeDisplayColumns(displayColumns);
onChangeColumns(selectedColumns.includes(key) ? selectedColumns.filter(col => col !== key) : [...selectedColumns, key]);
};
const toggleAllColumns = () => {
if (isAllChecked) {
handleChangeDisplayColumns(selectedColumns.filter(col => !filteredColumns.includes(col)));
onChangeColumns(selectedColumns.filter(col => !filteredColumns.includes(col)));
} else {
handleChangeDisplayColumns(filteredColumns);
onChangeColumns(filteredColumns);
}
};
@@ -111,16 +95,22 @@ const TableSettings: FC<TableSettingsProps> = ({
};
useEffect(() => {
if (arrayEquals(columns, selectedColumns) || searchParams.has("columns")) return;
if (arrayEquals(columns, selectedColumns) || saveColumns) return;
onChangeColumns(columns);
}, [columns]);
useEffect(() => {
const hasColumns = searchParams.has("columns");
if (!hasColumns) return;
const columnsParam = searchParams.get("columns") || "";
const columnsArray = columnsParam.split(",").map(decodeURIComponent).filter(Boolean);
onChangeColumns(columnsArray);
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 (
@@ -193,6 +183,19 @@ 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 className="vm-table-settings-modal-section">

View File

@@ -3,7 +3,6 @@
.vm-table-settings {
&-modal {
.vm-modal-content-body {
min-width: clamp(300px, 600px, 90vw);
padding: 0;
}
@@ -84,5 +83,16 @@
}
}
}
&-preserve {
padding: $padding-global;
&__info {
padding-top: $padding-small;
font-size: $font-size-small;
color: $color-text-secondary;
line-height: 130%;
}
}
}
}

View File

@@ -1,22 +1,2 @@
import { DATE_TIME_FORMAT } from "./date";
export const LOGS_ENTRIES_LIMIT = 50;
export const LOGS_BARS_VIEW = 100;
export const LOGS_LIMIT_HITS = 5;
// "Ungrouped" is a string that is used as a value for the "groupBy" parameter.
export const WITHOUT_GROUPING = "Ungrouped";
// Default values for the logs configurators.
export const LOGS_GROUP_BY = "_stream";
export const LOGS_DISPLAY_FIELDS = "_msg";
export const LOGS_DATE_FORMAT = `${DATE_TIME_FORMAT}.SSS`;
// URL parameters for the logs page.
export const LOGS_URL_PARAMS = {
GROUP_BY: "groupBy",
DISPLAY_FIELDS: "displayFields",
NO_WRAP_LINES: "noWrapLines",
COMPACT_GROUP_HEADER: "compactGroupHeader",
DATE_FORMAT: "dateFormat",
};

View File

@@ -20,7 +20,7 @@ const useClickOutside = <T extends HTMLElement = HTMLElement>(
handler(event); // Call the handler only if the click is outside of the element passed.
}, [ref, handler]);
useEventListener("mouseup", listener);
useEventListener("mousedown", listener);
useEventListener("touchstart", listener);
};

View File

@@ -69,7 +69,7 @@ const ExploreLogs: FC = () => {
};
const handleApplyFilter = (val: string) => {
setQuery(prev => `${val} AND (${prev})`);
setQuery(prev => `_stream: ${val === "other" ? "{}" : val} AND (${prev})`);
setIsUpdatingQuery(true);
};

View File

@@ -1,19 +1,24 @@
import React, { FC, useCallback, useEffect, useMemo } from "preact/compat";
import { useState } from "react";
import React, { FC, useCallback, useEffect, useMemo, useRef } from "preact/compat";
import { MouseEvent, useState } from "react";
import "./style.scss";
import { Logs } from "../../../api/types";
import Accordion from "../../../components/Main/Accordion/Accordion";
import { groupByMultipleKeys } from "../../../utils/array";
import Tooltip from "../../../components/Main/Tooltip/Tooltip";
import useCopyToClipboard from "../../../hooks/useCopyToClipboard";
import GroupLogsItem from "./GroupLogsItem";
import { useAppState } from "../../../state/common/StateContext";
import classNames from "classnames";
import Button from "../../../components/Main/Button/Button";
import { CollapseIcon, ExpandIcon } from "../../../components/Main/Icons";
import { CollapseIcon, ExpandIcon, StorageIcon } from "../../../components/Main/Icons";
import Popper from "../../../components/Main/Popper/Popper";
import TextField from "../../../components/Main/TextField/TextField";
import useBoolean from "../../../hooks/useBoolean";
import useStateSearchParams from "../../../hooks/useStateSearchParams";
import { useSearchParams } from "react-router-dom";
import { getStreamPairs } from "../../../utils/logs";
import GroupLogsConfigurators
from "../../../components/LogsConfigurators/GroupLogsConfigurators/GroupLogsConfigurators";
import GroupLogsHeader from "./GroupLogsHeader";
import { LOGS_DISPLAY_FIELDS, LOGS_GROUP_BY, LOGS_URL_PARAMS, WITHOUT_GROUPING } from "../../../constants/logs";
const WITHOUT_GROUPING = "No Grouping";
interface Props {
logs: Logs[];
@@ -21,31 +26,73 @@ interface Props {
}
const GroupLogs: FC<Props> = ({ logs, settingsRef }) => {
const [searchParams] = useSearchParams();
const { isDarkTheme } = useAppState();
const copyToClipboard = useCopyToClipboard();
const [searchParams, setSearchParams] = useSearchParams();
const [expandGroups, setExpandGroups] = useState<boolean[]>([]);
const [groupBy, setGroupBy] = useStateSearchParams("_stream", "groupBy");
const [copied, setCopied] = useState<string | null>(null);
const [searchKey, setSearchKey] = useState("");
const optionsButtonRef = useRef<HTMLDivElement>(null);
const groupBy = searchParams.get(LOGS_URL_PARAMS.GROUP_BY) || LOGS_GROUP_BY;
const displayFieldsString = searchParams.get(LOGS_URL_PARAMS.DISPLAY_FIELDS) || LOGS_DISPLAY_FIELDS;
const displayFields = displayFieldsString.split(",");
const {
value: openOptions,
toggle: toggleOpenOptions,
setFalse: handleCloseOptions,
} = useBoolean(false);
const expandAll = useMemo(() => expandGroups.every(Boolean), [expandGroups]);
const logsKeys = useMemo(() => {
const excludeKeys = ["_msg", "_time"];
const uniqKeys = Array.from(new Set(logs.map(l => Object.keys(l)).flat()));
return [WITHOUT_GROUPING, ...uniqKeys.filter(k => !excludeKeys.includes(k))];
}, [logs]);
const filteredLogsKeys = useMemo(() => {
if (!searchKey) return logsKeys;
try {
const regexp = new RegExp(searchKey, "i");
return logsKeys.filter(item => regexp.test(item))
.sort((a, b) => (a.match(regexp)?.index || 0) - (b.match(regexp)?.index || 0));
} catch (e) {
return [];
}
}, [logsKeys, searchKey]);
const groupData = useMemo(() => {
return groupByMultipleKeys(logs, [groupBy]).map((item) => {
const streamValue = item.values[0]?.[groupBy] || "";
const pairs = getStreamPairs(streamValue);
// values sorting by time
const values = item.values.sort((a, b) => new Date(b._time).getTime() - new Date(a._time).getTime());
const values = item.values.sort((a,b) => new Date(b._time).getTime() - new Date(a._time).getTime());
return {
keys: item.keys,
keysString: item.keys.join(""),
values,
pairs,
};
}).sort((a, b) => b.values.length - a.values.length); // groups sorting
}).sort((a, b) => a.keysString.localeCompare(b.keysString)); // groups sorting
}, [logs, groupBy]);
const handleClickByPair = (value: string) => async (e: MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
const isKeyValue = /(.+)?=(".+")/.test(value);
const copyValue = isKeyValue ? `${value.replace(/=/, ": ")}` : `${groupBy}: "${value}"`;
const isCopied = await copyToClipboard(copyValue);
if (isCopied) {
setCopied(value);
}
};
const handleSelectGroupBy = (key: string) => () => {
setGroupBy(key);
searchParams.set("groupBy", key);
setSearchParams(searchParams);
handleCloseOptions();
};
const handleToggleExpandAll = useCallback(() => {
setExpandGroups(new Array(groupData.length).fill(!expandAll));
}, [expandAll, groupData.length]);
@@ -58,6 +105,11 @@ const GroupLogs: FC<Props> = ({ logs, settingsRef }) => {
});
}, []);
useEffect(() => {
if (copied === null) return;
const timeout = setTimeout(() => setCopied(null), 2000);
return () => clearTimeout(timeout);
}, [copied]);
useEffect(() => {
setExpandGroups(new Array(groupData.length).fill(true));
@@ -72,16 +124,38 @@ const GroupLogs: FC<Props> = ({ logs, settingsRef }) => {
key={item.keysString}
>
<Accordion
key={String(expandGroups[i])}
defaultExpanded={expandGroups[i]}
onChange={handleChangeExpand(i)}
title={groupBy !== WITHOUT_GROUPING && <GroupLogsHeader group={item}/>}
title={groupBy !== WITHOUT_GROUPING && (
<div className="vm-group-logs-section-keys">
<span className="vm-group-logs-section-keys__title">Group by <code>{groupBy}</code>:</span>
{item.pairs.map((pair) => (
<Tooltip
title={copied === pair ? "Copied" : "Copy to clipboard"}
key={`${item.keysString}_${pair}`}
placement={"top-center"}
>
<div
className={classNames({
"vm-group-logs-section-keys__pair": true,
"vm-group-logs-section-keys__pair_dark": isDarkTheme
})}
onClick={handleClickByPair(pair)}
>
{pair}
</div>
</Tooltip>
))}
<span className="vm-group-logs-section-keys__count">{item.values.length} entries</span>
</div>
)}
>
<div className="vm-group-logs-section-rows">
{item.values.map((value) => (
<GroupLogsItem
key={`${value._msg}${value._time}`}
log={value}
displayFields={displayFields}
/>
))}
</div>
@@ -101,7 +175,47 @@ const GroupLogs: FC<Props> = ({ logs, settingsRef }) => {
ariaLabel={expandAll ? "Collapse All" : "Expand All"}
/>
</Tooltip>
<GroupLogsConfigurators logs={logs}/>
<Tooltip title={"Group by"}>
<div ref={optionsButtonRef}>
<Button
variant="text"
startIcon={<StorageIcon/>}
onClick={toggleOpenOptions}
ariaLabel={"Group by"}
/>
</div>
</Tooltip>
{
<Popper
open={openOptions}
placement="bottom-right"
onClose={handleCloseOptions}
buttonRef={optionsButtonRef}
>
<div className="vm-list vm-group-logs-header-keys">
<div className="vm-group-logs-header-keys__search">
<TextField
label="Search key"
value={searchKey}
onChange={setSearchKey}
type="search"
/>
</div>
{filteredLogsKeys.map(id => (
<div
className={classNames({
"vm-list-item": true,
"vm-list-item_active": id === groupBy
})}
key={id}
onClick={handleSelectGroupBy(id)}
>
{id}
</div>
))}
</div>
</Popper>
}
</div>
), settingsRef.current)}
</>

View File

@@ -1,10 +1,8 @@
import React, { FC, memo, useCallback, useEffect, useState } from "preact/compat";
import Tooltip from "../../../components/Main/Tooltip/Tooltip";
import Button from "../../../components/Main/Button/Button";
import { CopyIcon, StorageIcon, VisibilityIcon } from "../../../components/Main/Icons";
import { CopyIcon } from "../../../components/Main/Icons";
import useCopyToClipboard from "../../../hooks/useCopyToClipboard";
import { useSearchParams } from "react-router-dom";
import { LOGS_GROUP_BY, LOGS_URL_PARAMS } from "../../../constants/logs";
interface Props {
field: string;
@@ -13,17 +11,8 @@ interface Props {
const GroupLogsFieldRow: FC<Props> = ({ field, value }) => {
const copyToClipboard = useCopyToClipboard();
const [searchParams, setSearchParams] = useSearchParams();
const [copied, setCopied] = useState<boolean>(false);
const groupBy = searchParams.get(LOGS_URL_PARAMS.GROUP_BY) || LOGS_GROUP_BY;
const displayFieldsString = searchParams.get(LOGS_URL_PARAMS.DISPLAY_FIELDS) || "";
const displayFields = displayFieldsString ? displayFieldsString.split(",") : [];
const isSelectedField = displayFields.includes(field);
const isGroupByField = groupBy === field;
const handleCopy = useCallback(async () => {
if (copied) return;
try {
@@ -34,18 +23,6 @@ const GroupLogsFieldRow: FC<Props> = ({ field, value }) => {
}
}, [copied, copyToClipboard]);
const handleSelectDisplayField = () => {
const prev = displayFields;
const newDisplayFields = prev.includes(field) ? prev.filter(v => v !== field) : [...prev, field];
searchParams.set(LOGS_URL_PARAMS.DISPLAY_FIELDS, newDisplayFields.join(","));
setSearchParams(searchParams);
};
const handleSelectGroupBy = () => {
isGroupByField ? searchParams.delete(LOGS_URL_PARAMS.GROUP_BY) : searchParams.set(LOGS_URL_PARAMS.GROUP_BY, field);
setSearchParams(searchParams);
};
useEffect(() => {
if (copied === null) return;
const timeout = setTimeout(() => setCopied(false), 2000);
@@ -58,7 +35,6 @@ const GroupLogsFieldRow: FC<Props> = ({ field, value }) => {
<div className="vm-group-logs-row-fields-item-controls__wrapper">
<Tooltip title={copied ? "Copied" : "Copy to clipboard"}>
<Button
className="vm-group-logs-row-fields-item-controls__button"
variant="text"
color="gray"
size="small"
@@ -67,34 +43,6 @@ const GroupLogsFieldRow: FC<Props> = ({ field, value }) => {
ariaLabel="copy to clipboard"
/>
</Tooltip>
<Tooltip
key={`${field}_${isSelectedField}_${isGroupByField}`}
title={isSelectedField ? "Hide this field" : "Show this field instead of the message"}
>
<Button
className="vm-group-logs-row-fields-item-controls__button"
variant="text"
color={isSelectedField ? "secondary" : "gray"}
size="small"
startIcon={isSelectedField ? <VisibilityIcon/> : <VisibilityIcon/>}
onClick={handleSelectDisplayField}
ariaLabel="copy to clipboard"
/>
</Tooltip>
<Tooltip
key={`${field}_${isSelectedField}_${isGroupByField}`}
title={isGroupByField ? "Ungroup this field" : "Group by this field"}
>
<Button
className="vm-group-logs-row-fields-item-controls__button"
variant="text"
color={isGroupByField ? "secondary" : "gray"}
size="small"
startIcon={<StorageIcon/>}
onClick={handleSelectGroupBy}
ariaLabel="copy to clipboard"
/>
</Tooltip>
</div>
</td>
<td className="vm-group-logs-row-fields-item__key">{field}</td>

View File

@@ -1,127 +0,0 @@
import React, { FC, useCallback, useEffect, useRef } from "preact/compat";
import classNames from "classnames";
import { useSearchParams } from "react-router-dom";
import { MouseEvent, useState } from "react";
import { useAppState } from "../../../state/common/StateContext";
import { Logs } from "../../../api/types";
import useEventListener from "../../../hooks/useEventListener";
import Popper from "../../../components/Main/Popper/Popper";
import useBoolean from "../../../hooks/useBoolean";
import GroupLogsHeaderItem from "./GroupLogsHeaderItem";
import { LOGS_GROUP_BY, LOGS_URL_PARAMS } from "../../../constants/logs";
interface Props {
group: {
keys: string[]
keysString: string
values: Logs[]
pairs: string[]
};
}
const GroupLogsHeader: FC<Props> = ({ group }) => {
const { isDarkTheme } = useAppState();
const [searchParams] = useSearchParams();
const containerRef = useRef<HTMLDivElement>(null);
const moreRef = useRef<HTMLDivElement>(null);
const {
value: openMore,
toggle: handleToggleMore,
setFalse: handleCloseMore,
} = useBoolean(false);
const [hideParisCount, setHideParisCount] = useState<number>(0);
const groupBy = searchParams.get(LOGS_URL_PARAMS.GROUP_BY) || LOGS_GROUP_BY;
const compactGroupHeader = searchParams.get(LOGS_URL_PARAMS.COMPACT_GROUP_HEADER) === "true";
const pairs = group.pairs;
const hideAboveIndex = pairs.length - hideParisCount - 1;
const handleClickMore = (e: MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
handleToggleMore();
};
const calcVisiblePairsCount = useCallback(() => {
if (!compactGroupHeader || !containerRef.current) {
setHideParisCount(0);
return;
}
const container = containerRef.current;
const containerSize = container.getBoundingClientRect();
const selector = ".vm-group-logs-section-keys__pair:not(.vm-group-logs-section-keys__pair_more)";
const children = Array.from(container.querySelectorAll(selector));
let count = 0;
for (const child of children) {
const { right } = (child as HTMLElement).getBoundingClientRect();
if ((right + 220) > containerSize.width) {
count++;
}
}
setHideParisCount(count);
}, [compactGroupHeader, containerRef]);
useEffect(calcVisiblePairsCount, [group.pairs, compactGroupHeader, containerRef]);
useEventListener("resize", calcVisiblePairsCount);
return (
<div
className={classNames({
"vm-group-logs-section-keys": true,
"vm-group-logs-section-keys_compact": compactGroupHeader,
})}
ref={containerRef}
>
<span className="vm-group-logs-section-keys__title">Group by <code>{groupBy}</code>:</span>
{pairs.map((pair, i) => (
<GroupLogsHeaderItem
key={`${group.keysString}_${pair}`}
pair={pair}
isHide={hideParisCount ? i > hideAboveIndex : false}
/>
))}
{hideParisCount > 0 && (
<>
<div
className={classNames({
"vm-group-logs-section-keys__pair": true,
"vm-group-logs-section-keys__pair_more": true,
"vm-group-logs-section-keys__pair_dark": isDarkTheme
})}
ref={moreRef}
onClick={handleClickMore}
>
+{hideParisCount} more
</div>
<Popper
open={openMore}
buttonRef={moreRef}
placement="bottom-left"
onClose={handleCloseMore}
>
<div className="vm-group-logs-section-keys vm-group-logs-section-keys_popper">
{pairs.slice(hideAboveIndex + 1).map((pair) => (
<GroupLogsHeaderItem
key={`${group.keysString}_${pair}`}
pair={pair}
/>
))}
</div>
</Popper>
</>
)}
<span className="vm-group-logs-section-keys__count">{group.values.length} entries</span>
</div>
)
;
};
export default GroupLogsHeader;

View File

@@ -1,59 +0,0 @@
import React, { FC, useEffect } from "preact/compat";
import { useAppState } from "../../../state/common/StateContext";
import Tooltip from "../../../components/Main/Tooltip/Tooltip";
import classNames from "classnames";
import { MouseEvent, useState } from "react";
import useCopyToClipboard from "../../../hooks/useCopyToClipboard";
import { useSearchParams } from "react-router-dom";
import { LOGS_GROUP_BY, LOGS_URL_PARAMS } from "../../../constants/logs";
import { convertToFieldFilter } from "../../../utils/logs";
interface Props {
pair: string;
isHide?: boolean;
}
const GroupLogsHeaderItem: FC<Props> = ({ pair, isHide }) => {
const { isDarkTheme } = useAppState();
const copyToClipboard = useCopyToClipboard();
const [searchParams] = useSearchParams();
const [copied, setCopied] = useState<string | null>(null);
const groupBy = searchParams.get(LOGS_URL_PARAMS.GROUP_BY) || LOGS_GROUP_BY;
const handleClickByPair = (value: string) => async (e: MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
const copyValue = convertToFieldFilter(value, groupBy);
const isCopied = await copyToClipboard(copyValue);
if (isCopied) {
setCopied(value);
}
};
useEffect(() => {
if (copied === null) return;
const timeout = setTimeout(() => setCopied(null), 2000);
return () => clearTimeout(timeout);
}, [copied]);
return (
<Tooltip
title={copied === pair ? "Copied" : "Copy to clipboard"}
placement={"top-center"}
>
<div
className={classNames({
"vm-group-logs-section-keys__pair": true,
"vm-group-logs-section-keys__pair_hide": isHide,
"vm-group-logs-section-keys__pair_dark": isDarkTheme
})}
onClick={handleClickByPair(pair)}
>
{pair}
</div>
</Tooltip>
);
};
export default GroupLogsHeaderItem;

View File

@@ -6,34 +6,28 @@ import { ArrowDownIcon } from "../../../components/Main/Icons";
import classNames from "classnames";
import { useLogsState } from "../../../state/logsPanel/LogsStateContext";
import dayjs from "dayjs";
import { DATE_TIME_FORMAT } from "../../../constants/date";
import { useTimeState } from "../../../state/time/TimeStateContext";
import GroupLogsFieldRow from "./GroupLogsFieldRow";
import { marked } from "marked";
import { useSearchParams } from "react-router-dom";
import { LOGS_DATE_FORMAT, LOGS_URL_PARAMS } from "../../../constants/logs";
interface Props {
log: Logs;
displayFields?: string[];
}
const GroupLogsItem: FC<Props> = ({ log, displayFields = ["_msg"] }) => {
const GroupLogsItem: FC<Props> = ({ log }) => {
const {
value: isOpenFields,
toggle: toggleOpenFields,
} = useBoolean(false);
const [searchParams] = useSearchParams();
const { markdownParsing } = useLogsState();
const { timezone } = useTimeState();
const noWrapLines = searchParams.get(LOGS_URL_PARAMS.NO_WRAP_LINES) === "true";
const dateFormat = searchParams.get(LOGS_URL_PARAMS.DATE_FORMAT) || LOGS_DATE_FORMAT;
const formattedTime = useMemo(() => {
if (!log._time) return "";
return dayjs(log._time).tz().format(dateFormat);
}, [log._time, timezone, dateFormat]);
return dayjs(log._time).tz().format(`${DATE_TIME_FORMAT}.SSS`);
}, [log._time, timezone]);
const formattedMarkdown = useMemo(() => {
if (!markdownParsing || !log._msg) return "";
@@ -44,14 +38,6 @@ const GroupLogsItem: FC<Props> = ({ log, displayFields = ["_msg"] }) => {
const hasFields = fields.length > 0;
const displayMessage = useMemo(() => {
if (displayFields.length) {
return displayFields.filter(field => log[field]).map((field, i) => (
<span
className="vm-group-logs-row-content__sub-msg"
key={field + i}
>{log[field]}</span>
));
}
if (log._msg) return log._msg;
if (!hasFields) return;
const dataObject = fields.reduce<{ [key: string]: string }>((obj, [key, value]) => {
@@ -59,7 +45,7 @@ const GroupLogsItem: FC<Props> = ({ log, displayFields = ["_msg"] }) => {
return obj;
}, {});
return JSON.stringify(dataObject);
}, [log, fields, hasFields, displayFields]);
}, [log, fields, hasFields]);
return (
<div className="vm-group-logs-row">
@@ -90,8 +76,7 @@ const GroupLogsItem: FC<Props> = ({ log, displayFields = ["_msg"] }) => {
className={classNames({
"vm-group-logs-row-content__msg": true,
"vm-group-logs-row-content__msg_empty-msg": !log._msg,
"vm-group-logs-row-content__msg_missing": !displayMessage,
"vm-group-logs-row-content__msg_single-line": noWrapLines,
"vm-group-logs-row-content__msg_missing": !displayMessage
})}
dangerouslySetInnerHTML={(markdownParsing && formattedMarkdown) ? { __html: formattedMarkdown } : undefined}
>

View File

@@ -1,7 +1,5 @@
@use "src/styles/variables" as *;
$font-size-logs: var(--font-size-logs, $font-size-small);
.vm-group-logs {
margin-top: calc(-1 * $padding-medium);
@@ -21,44 +19,22 @@ $font-size-logs: var(--font-size-logs, $font-size-small);
}
&-section {
border-bottom: $border-divider;
&-keys {
position: relative;
display: flex;
align-items: center;
flex-wrap: wrap;
gap: $padding-small;
padding: $padding-small 120px $padding-small 0;
font-size: $font-size-logs;
&_compact {
flex-wrap: nowrap;
overflow: hidden;
}
&_popper {
display: flex;
flex-wrap: nowrap;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
padding: $padding-global;
max-height: 400px;
overflow: auto;
}
border-bottom: $border-divider;
padding: $padding-small 0;
&__title {
font-weight: bold;
white-space: nowrap;
code {
font-family: monospace;
&:before {
content: "\"";
}
&:after {
content: "\"";
}
@@ -66,35 +42,19 @@ $font-size-logs: var(--font-size-logs, $font-size-small);
}
&__count {
position: absolute;
top: auto;
right: 0;
flex-grow: 1;
text-align: right;
font-size: $font-size-logs;
font-size: $font-size-small;
color: $color-text-secondary;
padding-right: calc($padding-large * 3);
}
&__pair {
order: 0;
padding: calc($padding-global / 2) $padding-global;
background-color: lighten($color-tropical-blue, 6%);
color: darken($color-dodger-blue, 20%);
border-radius: $border-radius-medium;
transition: background-color 0.3s ease-in, transform 0.1s ease-in, opacity 0.3s ease-in;
white-space: nowrap;
&_hide {
order: 2;
visibility: hidden;
opacity: 0;
pointer-events: none;
}
&_more {
order: 1;
}
&:hover {
background-color: $color-tropical-blue;
@@ -124,19 +84,13 @@ $font-size-logs: var(--font-size-logs, $font-size-small);
&-row {
position: relative;
&:last-child {
margin-bottom: $padding-small;
}
border-bottom: $border-divider;
&-content {
position: relative;
display: grid;
grid-template-columns: auto max-content 1fr;
padding: calc($padding-small / 4) 0;
font-size: $font-size-logs;
font-variant-numeric: tabular-nums;
line-height: 1.3;
grid-template-columns: auto minmax(180px, max-content) 1fr;
padding: $padding-global 0;
cursor: pointer;
transition: background-color 0.2s ease-in;
@@ -162,7 +116,8 @@ $font-size-logs: var(--font-size-logs, $font-size-small);
display: flex;
align-items: flex-start;
justify-content: flex-end;
padding: 0 $padding-global 0 $padding-small;
margin-right: $padding-small;
line-height: 1;
white-space: nowrap;
&_missing {
@@ -175,12 +130,7 @@ $font-size-logs: var(--font-size-logs, $font-size-small);
&__msg {
font-family: $font-family-monospace;
overflow-wrap: anywhere;
&_single-line {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
line-height: 1.1;
&_empty-msg {
overflow: hidden;
@@ -208,7 +158,7 @@ $font-size-logs: var(--font-size-logs, $font-size-small);
border-radius: $border-radius-small;
tab-size: 4;
font-variant-ligatures: none;
margin: calc($padding-small / 4) 0;
margin: calc($padding-small/4) 0;
}
p {
@@ -221,7 +171,7 @@ $font-size-logs: var(--font-size-logs, $font-size-small);
}
code {
font-size: $font-size-logs;
font-size: $font-size-small;
padding: calc($padding-small / 4) calc($padding-small / 2);
}
@@ -244,35 +194,25 @@ $font-size-logs: var(--font-size-logs, $font-size-small);
blockquote {
border-left: 4px solid $color-hover-black;
margin: calc($padding-small / 2) $padding-small;
padding: calc($padding-small / 2) $padding-small;
margin: calc($padding-small/2) $padding-small;
padding: calc($padding-small/2) $padding-small;
}
ul, ol {
list-style-position: inside;
}
/* end styles for markdown */
}
&__sub-msg {
padding-right: $padding-global;
}
}
&-fields {
position: relative;
grid-row: 2;
padding: $padding-small 0;
margin: $padding-small 0 $padding-small calc($padding-global * 2);
margin-bottom: $padding-small;
border: $border-divider;
border-radius: $border-radius-small;
overflow: auto;
max-height: 300px;
resize: vertical;
font-family: $font-family-monospace;
font-size: $font-size-logs;
font-variant-numeric: tabular-nums;
&-item {
border-radius: $border-radius-small;
@@ -283,26 +223,19 @@ $font-size-logs: var(--font-size-logs, $font-size-small);
}
&-controls {
padding: 0 calc($padding-small / 2);
padding: 0;
&__wrapper {
display: flex;
align-items: center;
justify-content: center;
}
&__button.vm-button_small {
width: 22px;
height: 22px;
min-height: 22px;
}
}
&__key,
&__value {
vertical-align: top;
line-height: $font-size;
padding: calc($padding-small / 2);
padding: calc($padding-small / 2) $padding-global;
}
&__key {

View File

@@ -4,8 +4,6 @@ import { ErrorTypes, TimeParams } from "../../../types";
import { LogHits } from "../../../api/types";
import { useSearchParams } from "react-router-dom";
import { getHitsTimeParams } from "../../../utils/logs";
import { LOGS_GROUP_BY, LOGS_LIMIT_HITS } from "../../../constants/logs";
import { isEmptyObject } from "../../../utils/object";
export const useFetchLogHits = (server: string, query: string) => {
const [searchParams] = useSearchParams();
@@ -32,12 +30,46 @@ export const useFetchLogHits = (server: string, query: string) => {
step: `${step}ms`,
start: start.toISOString(),
end: end.toISOString(),
fields_limit: `${LOGS_LIMIT_HITS}`,
field: LOGS_GROUP_BY,
field: "_stream" // In the future, this field can be made configurable
})
};
};
const accumulateHits = (resultHit: LogHits, hit: LogHits) => {
resultHit.total = (resultHit.total || 0) + (hit.total || 0);
hit.timestamps.forEach((timestamp, i) => {
const index = resultHit.timestamps.findIndex(t => t === timestamp);
if (index === -1) {
resultHit.timestamps.push(timestamp);
resultHit.values.push(hit.values[i]);
} else {
resultHit.values[index] += hit.values[i];
}
});
return resultHit;
};
const getHitsWithTop = (hits: LogHits[]) => {
const topN = 5;
const defaultHit = { fields: {}, timestamps: [], values: [], total: 0 };
const hitsByTotal = hits.sort((a, b) => (b.total || 0) - (a.total || 0));
const result = [];
const otherHits: LogHits = hitsByTotal.slice(topN).reduce(accumulateHits, defaultHit);
if (otherHits.total) {
result.push(otherHits);
}
const topHits: LogHits[] = hitsByTotal.slice(0, topN);
if (topHits.length) {
result.push(...topHits);
}
return result;
};
const fetchLogHits = useCallback(async (period: TimeParams) => {
abortControllerRef.current.abort();
abortControllerRef.current = new AbortController();
@@ -66,7 +98,7 @@ export const useFetchLogHits = (server: string, query: string) => {
setError(error);
}
setLogHits(hits.map(hit => ({ ...hit, _isOther: isEmptyObject(hit.fields) })));
setLogHits(!hits ? [] : getHitsWithTop(hits));
} catch (e) {
if (e instanceof Error && e.name !== "AbortError") {
setError(String(e));

View File

@@ -1,8 +1,6 @@
import { TimeParams } from "../types";
import dayjs from "dayjs";
import { LOGS_BARS_VIEW, LOGS_GROUP_BY } from "../constants/logs";
import { LogHits } from "../api/types";
import { OTHER_HITS_LABEL } from "../components/Chart/BarHitsChart/hooks/useBarHitsOptions";
import { LOGS_BARS_VIEW } from "../constants/logs";
export const getStreamPairs = (value: string): string[] => {
const pairs = /^{.+}$/.test(value) ? value.slice(1, -1).split(",") : [value];
@@ -16,27 +14,3 @@ export const getHitsTimeParams = (period: TimeParams) => {
const step = Math.ceil(totalSeconds / LOGS_BARS_VIEW) || 1;
return { start, end, step };
};
export const convertToFieldFilter = (value: string, field = LOGS_GROUP_BY) => {
const isKeyValue = /(.+)?=(".+")/.test(value);
if (isKeyValue) {
return value.replace(/=/, ": ");
}
return `${field}: "${value}"`;
};
export const calculateTotalHits = (hits: LogHits[]): number => {
return hits.reduce((acc, item) => acc + (item.total || 0), 0);
};
export const sortLogHits = <T extends { label?: string }>(key: keyof T) => (a: T, b: T): number => {
if (a.label === OTHER_HITS_LABEL) return 1;
if (b.label === OTHER_HITS_LABEL) return -1;
const aValue = a[key] as unknown as number;
const bValue = b[key] as unknown as number;
return bValue - aValue;
};

View File

@@ -57,15 +57,3 @@ export const getLastFromArray = (a: number[]) => {
}
}
};
export const formatNumberShort = (value: number) => {
if (value >= 1_000_000_000) {
return (value / 1_000_000_000).toFixed(1).replace(/\.0$/, "") + "B"; // Миллиарды
} else if (value >= 1_000_000) {
return (value / 1_000_000).toFixed(1).replace(/\.0$/, "") + "M"; // Миллионы
} else if (value >= 1_000) {
return (value / 1_000).toFixed(1).replace(/\.0$/, "") + "K"; // Тысячи
} else {
return value.toString(); // Для чисел меньше 1000
}
};

View File

@@ -14,7 +14,3 @@ export function filterObject<T extends object>(
export function compactObject<T extends object>(obj: T) {
return filterObject(obj, (entry) => !!entry[1] || typeof entry[1] === "number");
}
export function isEmptyObject(obj: object) {
return Object.keys(obj).length === 0;
}

View File

@@ -3,6 +3,7 @@ export type StorageKeys = "AUTOCOMPLETE"
| "QUERY_TRACING"
| "SERIES_LIMITS"
| "TABLE_COMPACT"
| "TABLE_COLUMNS"
| "TIMEZONE"
| "DISABLED_DEFAULT_TIMEZONE"
| "THEME"

View File

@@ -10,7 +10,7 @@ import (
// specific files
// static content
//
//go:embed favicon.svg robots.txt index.html manifest.json asset-manifest.json
//go:embed favicon-32x32.png robots.txt index.html manifest.json asset-manifest.json
//go:embed static
var files embed.FS

View File

@@ -8,7 +8,7 @@ as apposed to a unit test that verifies the behavior of a building block of an
application.
To achieve that an integration test starts an application in a separate process
and then issues HTTP request to it and verifies the responses, examines the
and then issues HTTP requets to it and verifies the responses, examines the
metrics the app exposes and/or files it creates, etc.
Note that an object of testing may be not just a single app, but several apps

View File

@@ -159,11 +159,3 @@ func (app *ServesMetrics) GetMetricsByPrefix(t *testing.T, prefix string) []floa
}
return values
}
func getExpectedResponse(opts, def int) int {
if opts == 0 {
return def
}
return opts
}

View File

@@ -20,8 +20,6 @@ type PrometheusQuerier interface {
PrometheusAPIV1Query(t *testing.T, query string, opts QueryOpts) *PrometheusAPIV1QueryResponse
PrometheusAPIV1QueryRange(t *testing.T, query string, opts QueryOpts) *PrometheusAPIV1QueryResponse
PrometheusAPIV1Series(t *testing.T, matchQuery string, opts QueryOpts) *PrometheusAPIV1SeriesResponse
PrometheusAPIV1Labels(t *testing.T, opts QueryOpts) *PrometheusAPIV1LabelsResponse
PrometheusAPIV1LabelValues(t *testing.T, labelName string, opts QueryOpts) *PrometheusAPIV1LabelValuesResponse
}
// PrometheusWriter contains methods available to Prometheus-like HTTP API for Writing new data
@@ -55,8 +53,6 @@ type QueryOpts struct {
ExtraFilters []string
ExtraLabels []string
Trace string
ExpectedResponseCode int
}
func (qos *QueryOpts) asURLValues() url.Values {
@@ -258,39 +254,3 @@ func (t *Trace) Contains(s string) int {
}
return times
}
// PrometheusAPIV1LabelsResponse represents the response from /api/v1/labels endpoint
type PrometheusAPIV1LabelsResponse struct {
Status string `json:"status"`
Data []string `json:"data"`
ErrorType string `json:"errorType,omitempty"`
Error string `json:"error,omitempty"`
}
// NewPrometheusAPIV1LabelsResponse creates a new PrometheusAPIV1LabelsResponse from JSON
func NewPrometheusAPIV1LabelsResponse(t *testing.T, s string) *PrometheusAPIV1LabelsResponse {
t.Helper()
res := &PrometheusAPIV1LabelsResponse{}
if err := json.Unmarshal([]byte(s), res); err != nil {
t.Fatalf("could not unmarshal labels response data:\n%s\n err: %v", s, err)
}
return res
}
// PrometheusAPIV1LabelValuesResponse represents the response from /api/v1/label/{label}/values endpoint
type PrometheusAPIV1LabelValuesResponse struct {
Status string `json:"status"`
Data []string `json:"data"`
ErrorType string `json:"errorType,omitempty"`
Error string `json:"error,omitempty"`
}
// NewPrometheusAPIV1LabelValuesResponse creates a new PrometheusAPIV1LabelValuesResponse from JSON
func NewPrometheusAPIV1LabelValuesResponse(t *testing.T, s string) *PrometheusAPIV1LabelValuesResponse {
t.Helper()
res := &PrometheusAPIV1LabelValuesResponse{}
if err := json.Unmarshal([]byte(s), res); err != nil {
t.Fatalf("could not unmarshal label values response data:\n%s\n err: %v", s, err)
}
return res
}

View File

@@ -1,56 +0,0 @@
package tests
import (
"net/http"
"testing"
"github.com/VictoriaMetrics/VictoriaMetrics/apptest"
)
// Data used in tests
var testDataMemoryLimits = []string{
`foo_bar{job="some_value"} 1.00`,
`foo_bar{job="other_value"} 2.00`,
}
func TestMaxMemoryUsageExceeded(t *testing.T) {
tc := apptest.NewTestCase(t)
defer tc.Stop()
querier := tc.MustStartCluster(&apptest.ClusterOptions{
VminsertInstance: "vminsert",
VmselectInstance: "vmselect",
Vmstorage1Instance: "vmstorage1",
Vmstorage2Instance: "vmstorage2",
VmselectFlags: []string{"-search.maxMemoryPerQuery=1"},
VminsertFlags: []string{"-clusternativeListenAddr=127.0.0.1:0"},
})
querier.PrometheusAPIV1ImportPrometheus(t, testDataMemoryLimits, apptest.QueryOpts{})
querier.ForceFlush(t)
// Test labels endpoint
labelsResp := querier.PrometheusAPIV1Labels(t, apptest.QueryOpts{
ExpectedResponseCode: http.StatusUnprocessableEntity,
})
if labelsResp.Status != "error" {
t.Fatalf("unexpected status for labels; got %q, want %q", labelsResp.Status, "error")
}
// Test label values endpoint
labelValuesResp := querier.PrometheusAPIV1LabelValues(t, "job", apptest.QueryOpts{
ExpectedResponseCode: http.StatusUnprocessableEntity,
})
if labelValuesResp.Status != "error" {
t.Fatalf("unexpected status for label values; got %q, want %q", labelValuesResp.Status, "error")
}
// Test series endpoint
seriesResp := querier.PrometheusAPIV1Series(t, "foo_bar", apptest.QueryOpts{
ExpectedResponseCode: http.StatusUnprocessableEntity,
})
if seriesResp.Status != "error" {
t.Fatalf("unexpected status for series; got %q, want %q", seriesResp.Status, "error")
}
}

View File

@@ -171,49 +171,4 @@ func TestClusterMultiTenantSelect(t *testing.T) {
t.Errorf("unexpected response (-want, +got):\n%s", diff)
}
// Delete series from specific tenant
vmselect.DeleteSeries(t, "foo_bar", apptest.QueryOpts{
Tenant: "5:15",
})
wantSR = apptest.NewPrometheusAPIV1SeriesResponse(t,
`{"data": [
{"__name__":"foo_bar", "vm_account_id":"0", "vm_project_id":"10"},
{"__name__":"foo_bar", "vm_account_id":"1", "vm_project_id":"1"},
{"__name__":"foo_bar", "vm_account_id":"1", "vm_project_id":"15"},
{"__name__":"foo_bar", "vm_account_id":"5", "vm_project_id":"0"}
]
}`)
wantSR.Sort()
gotSR = vmselect.PrometheusAPIV1Series(t, "foo_bar", apptest.QueryOpts{
Tenant: "multitenant",
Start: "2022-05-10T08:03:00.000Z",
})
gotSR.Sort()
if diff := cmp.Diff(wantSR, gotSR, cmpSROpt); diff != "" {
t.Errorf("unexpected response (-want, +got):\n%s", diff)
}
// Delete series for multitenant with tenant filter
vmselect.DeleteSeries(t, `foo_bar{vm_account_id="1"}`, apptest.QueryOpts{
Tenant: "multitenant",
})
wantSR = apptest.NewPrometheusAPIV1SeriesResponse(t,
`{"data": [
{"__name__":"foo_bar", "vm_account_id":"0", "vm_project_id":"10"},
{"__name__":"foo_bar", "vm_account_id":"5", "vm_project_id":"0"}
]
}`)
wantSR.Sort()
gotSR = vmselect.PrometheusAPIV1Series(t, `foo_bar`, apptest.QueryOpts{
Tenant: "multitenant",
Start: "2022-05-10T08:03:00.000Z",
})
gotSR.Sort()
if diff := cmp.Diff(wantSR, gotSR, cmpSROpt); diff != "" {
t.Errorf("unexpected response (-want, +got):\n%s", diff)
}
}

View File

@@ -66,7 +66,7 @@ func (app *Vmselect) PrometheusAPIV1Export(t *testing.T, query string, opts Quer
values := opts.asURLValues()
values.Add("match[]", query)
values.Add("format", "promapi")
res := app.cli.PostForm(t, exportURL, values, getExpectedResponse(opts.ExpectedResponseCode, http.StatusOK))
res := app.cli.PostForm(t, exportURL, values, http.StatusOK)
return NewPrometheusAPIV1QueryResponse(t, res)
}
@@ -82,7 +82,7 @@ func (app *Vmselect) PrometheusAPIV1Query(t *testing.T, query string, opts Query
values := opts.asURLValues()
values.Add("query", query)
res := app.cli.PostForm(t, queryURL, values, getExpectedResponse(opts.ExpectedResponseCode, http.StatusOK))
res := app.cli.PostForm(t, queryURL, values, http.StatusOK)
return NewPrometheusAPIV1QueryResponse(t, res)
}
@@ -98,7 +98,7 @@ func (app *Vmselect) PrometheusAPIV1QueryRange(t *testing.T, query string, opts
values := opts.asURLValues()
values.Add("query", query)
res := app.cli.PostForm(t, queryURL, values, getExpectedResponse(opts.ExpectedResponseCode, http.StatusOK))
res := app.cli.PostForm(t, queryURL, values, http.StatusOK)
return NewPrometheusAPIV1QueryResponse(t, res)
}
@@ -113,52 +113,10 @@ func (app *Vmselect) PrometheusAPIV1Series(t *testing.T, matchQuery string, opts
values := opts.asURLValues()
values.Add("match[]", matchQuery)
res := app.cli.PostForm(t, seriesURL, values, getExpectedResponse(opts.ExpectedResponseCode, http.StatusOK))
res := app.cli.PostForm(t, seriesURL, values, http.StatusOK)
return NewPrometheusAPIV1SeriesResponse(t, res)
}
// PrometheusAPIV1Labels sends a query to a /prometheus/api/v1/labels endpoint
// and returns the list of label names.
//
// See https://docs.victoriametrics.com/url-examples/#apiv1labels
func (app *Vmselect) PrometheusAPIV1Labels(t *testing.T, opts QueryOpts) *PrometheusAPIV1LabelsResponse {
t.Helper()
labelsURL := fmt.Sprintf("http://%s/select/%s/prometheus/api/v1/labels", app.httpListenAddr, opts.getTenant())
values := opts.asURLValues()
res := app.cli.PostForm(t, labelsURL, values, getExpectedResponse(opts.ExpectedResponseCode, http.StatusOK))
return NewPrometheusAPIV1LabelsResponse(t, res)
}
// PrometheusAPIV1LabelValues sends a query to a /prometheus/api/v1/label/{label}/values endpoint
// and returns the list of label values for the specified label name.
//
// See https://docs.victoriametrics.com/url-examples/#apiv1labelvalues
func (app *Vmselect) PrometheusAPIV1LabelValues(t *testing.T, labelName string, opts QueryOpts) *PrometheusAPIV1LabelValuesResponse {
t.Helper()
valuesURL := fmt.Sprintf("http://%s/select/%s/prometheus/api/v1/label/%s/values", app.httpListenAddr, opts.getTenant(), labelName)
values := opts.asURLValues()
res := app.cli.PostForm(t, valuesURL, values, getExpectedResponse(opts.ExpectedResponseCode, http.StatusOK))
return NewPrometheusAPIV1LabelValuesResponse(t, res)
}
// DeleteSeries sends a query to a /prometheus/api/v1/admin/tsdb/delete_series
//
// See https://docs.victoriametrics.com/url-examples/#apiv1admintsdbdelete_series
func (app *Vmselect) DeleteSeries(t *testing.T, matchQuery string, opts QueryOpts) {
t.Helper()
seriesURL := fmt.Sprintf("http://%s/delete/%s/prometheus/api/v1/admin/tsdb/delete_series", app.httpListenAddr, opts.getTenant())
values := opts.asURLValues()
values.Add("match[]", matchQuery)
res := app.cli.PostForm(t, seriesURL, values, http.StatusNoContent)
if res != "" {
t.Fatalf("unexpected non-empty DeleteSeries response=%q", res)
}
}
// String returns the string representation of the vmselect app state.
func (app *Vmselect) String() string {
return fmt.Sprintf("{app: %s httpListenAddr: %q}", app.app, app.httpListenAddr)

View File

@@ -9,9 +9,8 @@ import (
"testing"
"time"
"github.com/golang/snappy"
pb "github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
"github.com/golang/snappy"
)
// Vmsingle holds the state of a vmsingle app and provides vmsingle-specific
@@ -32,12 +31,10 @@ type Vmsingle struct {
prometheusAPIV1WriteURL string
// vmselect URLs.
prometheusAPIV1ExportURL string
prometheusAPIV1QueryURL string
prometheusAPIV1QueryRangeURL string
prometheusAPIV1SeriesURL string
prometheusAPIV1LabelsURL string
prometheusAPIV1LabelValuesURL string
prometheusAPIV1ExportURL string
prometheusAPIV1QueryURL string
prometheusAPIV1QueryRangeURL string
prometheusAPIV1SeriesURL string
}
// StartVmsingle starts an instance of vmsingle with the given flags. It also
@@ -75,8 +72,6 @@ func StartVmsingle(instance string, flags []string, cli *Client) (*Vmsingle, err
prometheusAPIV1QueryURL: fmt.Sprintf("http://%s/prometheus/api/v1/query", stderrExtracts[1]),
prometheusAPIV1QueryRangeURL: fmt.Sprintf("http://%s/prometheus/api/v1/query_range", stderrExtracts[1]),
prometheusAPIV1SeriesURL: fmt.Sprintf("http://%s/prometheus/api/v1/series", stderrExtracts[1]),
prometheusAPIV1LabelsURL: fmt.Sprintf("http://%s/prometheus/api/v1/labels", stderrExtracts[1]),
prometheusAPIV1LabelValuesURL: fmt.Sprintf("http://%s/prometheus/api/v1/label/{}/values", stderrExtracts[1]),
}, nil
}
@@ -103,12 +98,12 @@ func (app *Vmsingle) InfluxWrite(t *testing.T, records []string, _ QueryOpts) {
// PrometheusAPIV1Write is a test helper function that inserts a
// collection of records in Prometheus remote-write format by sending a HTTP
// POST request to /prometheus/api/v1/write vmsingle endpoint.
func (app *Vmsingle) PrometheusAPIV1Write(t *testing.T, records []pb.TimeSeries, opts QueryOpts) {
func (app *Vmsingle) PrometheusAPIV1Write(t *testing.T, records []pb.TimeSeries, _ QueryOpts) {
t.Helper()
wr := pb.WriteRequest{Timeseries: records}
data := snappy.Encode(nil, wr.MarshalProtobuf(nil))
app.cli.Post(t, app.prometheusAPIV1WriteURL, "application/x-protobuf", data, getExpectedResponse(opts.ExpectedResponseCode, http.StatusNoContent))
app.cli.Post(t, app.prometheusAPIV1WriteURL, "application/x-protobuf", data, http.StatusNoContent)
}
// PrometheusAPIV1ImportPrometheus is a test helper function that inserts a
@@ -116,11 +111,11 @@ func (app *Vmsingle) PrometheusAPIV1Write(t *testing.T, records []pb.TimeSeries,
// POST request to /prometheus/api/v1/import/prometheus vmsingle endpoint.
//
// See https://docs.victoriametrics.com/url-examples/#apiv1importprometheus
func (app *Vmsingle) PrometheusAPIV1ImportPrometheus(t *testing.T, records []string, opts QueryOpts) {
func (app *Vmsingle) PrometheusAPIV1ImportPrometheus(t *testing.T, records []string, _ QueryOpts) {
t.Helper()
data := []byte(strings.Join(records, "\n"))
app.cli.Post(t, app.prometheusAPIV1ImportPrometheusURL, "text/plain", data, getExpectedResponse(opts.ExpectedResponseCode, http.StatusNoContent))
app.cli.Post(t, app.prometheusAPIV1ImportPrometheusURL, "text/plain", data, http.StatusNoContent)
}
// PrometheusAPIV1Export is a test helper function that performs the export of
@@ -134,7 +129,7 @@ func (app *Vmsingle) PrometheusAPIV1Export(t *testing.T, query string, opts Quer
values.Add("match[]", query)
values.Add("format", "promapi")
res := app.cli.PostForm(t, app.prometheusAPIV1ExportURL, values, getExpectedResponse(opts.ExpectedResponseCode, http.StatusOK))
res := app.cli.PostForm(t, app.prometheusAPIV1ExportURL, values, http.StatusOK)
return NewPrometheusAPIV1QueryResponse(t, res)
}
@@ -148,7 +143,7 @@ func (app *Vmsingle) PrometheusAPIV1Query(t *testing.T, query string, opts Query
values := opts.asURLValues()
values.Add("query", query)
res := app.cli.PostForm(t, app.prometheusAPIV1QueryURL, values, getExpectedResponse(opts.ExpectedResponseCode, http.StatusOK))
res := app.cli.PostForm(t, app.prometheusAPIV1QueryURL, values, http.StatusOK)
return NewPrometheusAPIV1QueryResponse(t, res)
}
@@ -163,35 +158,10 @@ func (app *Vmsingle) PrometheusAPIV1QueryRange(t *testing.T, query string, opts
values := opts.asURLValues()
values.Add("query", query)
res := app.cli.PostForm(t, app.prometheusAPIV1QueryRangeURL, values, getExpectedResponse(opts.ExpectedResponseCode, http.StatusOK))
res := app.cli.PostForm(t, app.prometheusAPIV1QueryRangeURL, values, http.StatusOK)
return NewPrometheusAPIV1QueryResponse(t, res)
}
// PrometheusAPIV1Labels sends a query to a /prometheus/api/v1/labels endpoint
// and returns the list of label names.
//
// See https://docs.victoriametrics.com/url-examples/#apiv1labels
func (app *Vmsingle) PrometheusAPIV1Labels(t *testing.T, opts QueryOpts) *PrometheusAPIV1LabelsResponse {
t.Helper()
values := opts.asURLValues()
res := app.cli.PostForm(t, app.prometheusAPIV1LabelsURL, values, getExpectedResponse(opts.ExpectedResponseCode, http.StatusOK))
return NewPrometheusAPIV1LabelsResponse(t, res)
}
// PrometheusAPIV1LabelValues sends a query to a /prometheus/api/v1/label/{label}/values endpoint
// and returns the list of label values for the specified label name.
//
// See https://docs.victoriametrics.com/url-examples/#apiv1labellabelvalues
func (app *Vmsingle) PrometheusAPIV1LabelValues(t *testing.T, labelName string, opts QueryOpts) *PrometheusAPIV1LabelValuesResponse {
t.Helper()
values := opts.asURLValues()
url := strings.Replace(app.prometheusAPIV1LabelValuesURL, "{}", labelName, 1)
res := app.cli.PostForm(t, url, values, getExpectedResponse(opts.ExpectedResponseCode, http.StatusOK))
return NewPrometheusAPIV1LabelValuesResponse(t, res)
}
// PrometheusAPIV1Series sends a query to a /prometheus/api/v1/series endpoint
// and returns the list of time series that match the query.
//
@@ -202,7 +172,7 @@ func (app *Vmsingle) PrometheusAPIV1Series(t *testing.T, matchQuery string, opts
values := opts.asURLValues()
values.Add("match[]", matchQuery)
res := app.cli.PostForm(t, app.prometheusAPIV1SeriesURL, values, getExpectedResponse(opts.ExpectedResponseCode, http.StatusOK))
res := app.cli.PostForm(t, app.prometheusAPIV1SeriesURL, values, http.StatusOK)
return NewPrometheusAPIV1SeriesResponse(t, res)
}

View File

@@ -1,7 +1,7 @@
dashboard-copy:
echo "" > dashboards/vm/${SRC}
cat dashboards/${SRC} >> dashboards/vm/${SRC}
sed -i='.tmp' 's/prometheus/victoriametrics-metrics-datasource/g' dashboards/vm/${SRC}
sed -i='.tmp' 's/prometheus/victoriametrics-datasource/g' dashboards/vm/${SRC}
sed -i='.tmp' 's/Prometheus/VictoriaMetrics/g' dashboards/vm/${SRC}
sed -i='.tmp' 's/${D_UID}/${D_UID}_vm/g' dashboards/vm/${SRC}
sed -i='.tmp' 's/"title": "${TITLE}"/"title": "${TITLE} (VM)"/g' dashboards/vm/${SRC}

View File

@@ -17,7 +17,7 @@
},
{
"type": "datasource",
"id": "victoriametrics-metrics-datasource",
"id": "victoriametrics-datasource",
"name": "VictoriaMetrics",
"version": "1.0.0"
},
@@ -85,7 +85,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -133,7 +133,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -150,7 +150,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Status of last backup operation.",
@@ -210,7 +210,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -225,7 +225,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Number of backups stored in remote storage.",
@@ -275,7 +275,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -290,7 +290,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Space used in remote storage.",
@@ -341,7 +341,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -356,7 +356,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "",
@@ -420,7 +420,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -435,7 +435,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Status of last retention run.\n\nRetention is a process of removing old backups from remote storage.",
@@ -495,7 +495,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -510,7 +510,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -573,7 +573,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -588,7 +588,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -674,7 +674,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -715,7 +715,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -801,7 +801,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -852,7 +852,7 @@
"panels": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Max duration of backup run. Lower better.\n\nEach backup starts with data upload during `latest` backup. Subsequent backups (`hourly`, `daily`, `weekly`, `monthly`) are copying date by using server-side copy. ",
@@ -931,7 +931,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -946,7 +946,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -1020,7 +1020,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1035,7 +1035,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -1110,7 +1110,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1125,7 +1125,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -1184,7 +1184,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1227,7 +1227,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -1298,7 +1298,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1313,7 +1313,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -1387,7 +1387,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1416,7 +1416,7 @@
"panels": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Max duration of retention run. Lower better.\n\nRetention is a process of removing old backups from remote storage.",
@@ -1485,7 +1485,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1500,7 +1500,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -1574,7 +1574,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1603,7 +1603,7 @@
"panels": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Duration of backup run. Lower better.\n\nEach backup starts with data upload during `latest` backup. Subsequent backups (`hourly`, `daily`, `weekly`, `monthly`) are copying date by using server-side copy.\n",
@@ -1671,7 +1671,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1705,7 +1705,7 @@
"multi": false,
"name": "ds",
"options": [],
"query": "victoriametrics-metrics-datasource",
"query": "victoriametrics-datasource",
"queryValue": "",
"refresh": 1,
"regex": "",
@@ -1764,7 +1764,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"filters": [],

View File

@@ -17,7 +17,7 @@
},
{
"type": "datasource",
"id": "victoriametrics-metrics-datasource",
"id": "victoriametrics-datasource",
"name": "VictoriaMetrics",
"version": "1.0.0"
},
@@ -80,7 +80,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "How many datapoints are inserted into storage per second by accountID and projectID",
@@ -168,7 +168,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -184,7 +184,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Request rate accepted by vmselect nodes per tenant",
@@ -272,7 +272,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -290,7 +290,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the number of active time series with new data points inserted during the last hour. High value may result in ingestion slowdown. \n\nSee following link for details:",
@@ -385,7 +385,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -403,7 +403,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Time spent on query execution per tenant per second",
@@ -491,7 +491,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -509,7 +509,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the amount of on-disk space occupied by data points only. The disk space is used for storing by datapoint and indexdb. There is no option to expose per tenant statistic for indexdb. Usually, indexed takes much less space compared to datapoints. But with a high churn rate, the size of the indexdb could grow significantly.",
@@ -596,7 +596,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -614,7 +614,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Number of new series created over last 24h.",
@@ -702,7 +702,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -731,7 +731,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -785,7 +785,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -800,7 +800,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -853,7 +853,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -868,7 +868,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -921,7 +921,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -936,7 +936,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -991,7 +991,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1006,7 +1006,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -1059,7 +1059,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1074,7 +1074,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -1127,7 +1127,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1142,7 +1142,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -1242,7 +1242,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1260,7 +1260,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -1360,7 +1360,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1378,7 +1378,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -1478,7 +1478,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1496,7 +1496,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -1596,7 +1596,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1614,7 +1614,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -1714,7 +1714,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1732,7 +1732,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -1832,7 +1832,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1868,7 +1868,7 @@
"multi": false,
"name": "ds",
"options": [],
"query": "victoriametrics-metrics-datasource",
"query": "victoriametrics-datasource",
"queryValue": "",
"refresh": 1,
"regex": "",
@@ -1879,7 +1879,7 @@
"allValue": ".*",
"current": {},
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"definition": "label_values(vm_tenant_active_timeseries, accountID)",
@@ -1905,7 +1905,7 @@
"allValue": ".*",
"current": {},
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"definition": "label_values(vm_tenant_active_timeseries{accountID=~\"$accountID\"},projectID)",
@@ -1929,7 +1929,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "PE8D8DB4BEE4E4B22"
},
"filters": [],

View File

@@ -33,7 +33,7 @@
{
"collapsed": false,
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"gridPos": {
@@ -47,7 +47,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"refId": "A"
@@ -58,7 +58,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"gridPos": {
@@ -81,7 +81,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"refId": "A"
@@ -92,7 +92,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Number of objects at kubernetes cluster per each controller",
@@ -147,7 +147,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -162,7 +162,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -215,7 +215,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -234,7 +234,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": " Shows per namespace watchers for VictoriaMetrics Operator objects (ServiceMonitors, PodMonitors, etc) ",
@@ -288,11 +288,11 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
"expr": "sum(operator_victoriametrics-metrics-datasource_converter_active_watchers)",
"expr": "sum(operator_victoriametrics-datasource_converter_active_watchers)",
"instant": false,
"legendFormat": "__auto",
"range": true,
@@ -304,7 +304,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": " Number of operator instances with obtained leader status. \n Value above 1 indicates that instances with the same job may behave incorrectly.\n It's recommend to check Operator logs. ",
@@ -358,7 +358,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -374,7 +374,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": " Shows number of active reconcile workers",
@@ -428,7 +428,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -444,7 +444,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": " Shows amount of VictoriaMetrics Operator objects processed by Operator.",
@@ -527,11 +527,11 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
"expr": "sum(rate(operator_victoriametrics-metrics-datasource_converter_watch_events_total{job=~\"$job\"}[$__interval])) by (event_type,object_type_name)",
"expr": "sum(rate(operator_victoriametrics-datasource_converter_watch_events_total{job=~\"$job\"}[$__interval])) by (event_type,object_type_name)",
"instant": false,
"legendFormat": "{{object_type_name}} {{event_type}}",
"range": true,
@@ -543,7 +543,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -627,7 +627,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -642,7 +642,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the rate of logging the messages by their level. Unexpected spike in rate is a good reason to check logs.",
@@ -727,7 +727,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -743,7 +743,7 @@
{
"collapsed": false,
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"gridPos": {
@@ -757,7 +757,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"refId": "A"
@@ -768,7 +768,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Non zero metrics indicates about error with CR object definition (typos or incorrect values) or errors with kubernetes API connection.",
@@ -852,7 +852,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -865,7 +865,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -881,7 +881,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Operator limits number of reconcile configuration events to 5 events per 2 seconds by default.",
@@ -965,7 +965,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -980,7 +980,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Number of objects waiting in the queue for reconciliation. Non-zero values indicate that operator cannot process CR objects changes with the given resources.",
@@ -1064,7 +1064,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1079,7 +1079,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": " For controllers with StatefulSet it's ok to see latency greater then 3 seconds. It could be vmalertmanager,vmcluster or vmagent in statefulMode.\n\n For other controllers, latency greater then 2 second may indicate issues with kubernetes cluster or operator's performance.\n ",
@@ -1165,7 +1165,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1180,7 +1180,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Number of HTTP requests to the Kubernetes API server break down by code and method",
@@ -1262,7 +1262,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1278,7 +1278,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows how many ongoing reconcile events are taking place, where:\n* `max` - equal to the value of flag`-controller.maxConcurrentReconciles`;\n* `current` - current number of reconcile workers processing CRD objects.\n\nWhen `current` hits `max` constantly, it means operator cannot process events in time. It should be either increased value for flag `-controller.maxConcurrentReconciles` or allocated additional CPU resources to the operator.",
@@ -1377,7 +1377,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1389,7 +1389,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1406,7 +1406,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the time goroutines have spent in runnable state before actually running. The lower is better.\n\nHigh values or values exceeding the threshold is usually a sign of insufficient CPU resources or CPU throttling. \n\nVerify that service has enough CPU resources. Otherwise, the service could work unreliably with delays in processing.",
@@ -1486,7 +1486,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1502,7 +1502,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": " Requests latency to the Kubernetes API server.",
@@ -1585,7 +1585,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1602,7 +1602,7 @@
{
"collapsed": true,
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"gridPos": {
@@ -1615,7 +1615,7 @@
"panels": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -1699,7 +1699,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1710,7 +1710,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1722,7 +1722,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1734,7 +1734,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1750,7 +1750,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -1830,7 +1830,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1845,7 +1845,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -1925,7 +1925,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1940,7 +1940,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -2023,7 +2023,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -2040,7 +2040,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"refId": "A"
@@ -2065,7 +2065,7 @@
"multi": false,
"name": "ds",
"options": [],
"query": "victoriametrics-metrics-datasource",
"query": "victoriametrics-datasource",
"queryValue": "te",
"refresh": 1,
"regex": "",
@@ -2075,7 +2075,7 @@
{
"current": {},
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"definition": "label_values(operator_log_messages_total,job)",
@@ -2097,7 +2097,7 @@
{
"current": {},
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"definition": "label_values(operator_log_messages_total{job=~\"$job\"},instance)",
@@ -2119,7 +2119,7 @@
{
"current": {},
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"definition": "label_values(vm_app_version{job=\"$job\", instance=\"$instance\"}, version)",

View File

@@ -11,7 +11,7 @@
},
{
"type": "datasource",
"id": "victoriametrics-metrics-datasource",
"id": "victoriametrics-datasource",
"name": "VictoriaMetrics",
"version": "1.0.0"
},
@@ -50,7 +50,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"enable": true,
@@ -63,7 +63,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"enable": true,
@@ -97,7 +97,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "How many log entries are in storage",
@@ -149,7 +149,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -168,7 +168,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the logs ingestion rate.",
@@ -220,7 +220,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -239,7 +239,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "The ratio of original data size and compressed data stored on disk",
@@ -291,7 +291,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -310,7 +310,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Total number of available CPUs for VM process",
@@ -366,7 +366,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -385,7 +385,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -439,7 +439,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -456,7 +456,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Total amount of used disk space",
@@ -508,7 +508,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -527,7 +527,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the rate of HTTP read requests.",
@@ -579,7 +579,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -598,7 +598,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Total size of available memory for VM process",
@@ -680,7 +680,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "How many logs are inserted into storage per second",
@@ -769,7 +769,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -787,7 +787,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "* `*` - unsupported query path\n* `/insert` - insert into VM\n* `/metrics` - query VL system metrics\n* `/query` - read the data",
@@ -877,7 +877,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -895,7 +895,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the amount of on-disk space occupied by data before and after compressiom",
@@ -985,7 +985,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1003,7 +1003,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "The number of the new log streams created over the last 24h",
@@ -1093,7 +1093,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1111,7 +1111,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the number of restarts per job. The chart can be useful to identify periodic process restarts and correlate them with potential issues or anomalies. Normally, processes shouldn't restart unless restart was inited by user. The reason of restarts should be figured out by checking the logs of each specific service. ",
@@ -1200,7 +1200,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1226,7 +1226,7 @@
"panels": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Percentage of used memory (resident).\nThe application's performance will significantly degrade when memory usage is close to 100%.",
@@ -1315,7 +1315,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1332,7 +1332,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "",
@@ -1421,7 +1421,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"expr": "sum(go_memstats_sys_bytes{job=~\"$job\", instance=~\"$instance\"}) + sum(vm_cache_size_bytes{job=~\"$job\", instance=~\"$instance\"})",
@@ -1433,7 +1433,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"expr": "sum(go_memstats_heap_inuse_bytes{job=~\"$job\", instance=~\"$instance\"}) + sum(vm_cache_size_bytes{job=~\"$job\", instance=~\"$instance\"})",
@@ -1445,7 +1445,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"expr": "sum(go_memstats_stack_inuse_bytes{job=~\"$job\", instance=~\"$instance\"})",
@@ -1457,7 +1457,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"expr": "sum(process_resident_memory_bytes{job=~\"$job\", instance=~\"$instance\"})",
@@ -1470,7 +1470,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"exemplar": false,
@@ -1488,7 +1488,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Share for memory allocated by the process itself. When memory usage reaches 100% it will be likely OOM-killed.\nSafe memory usage % considered to be below 80%",
@@ -1577,7 +1577,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1594,7 +1594,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -1682,7 +1682,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1701,7 +1701,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the percentage of open file descriptors compared to the limit set in the OS.\nReaching the limit of open files can cause various issues and must be prevented.\n\nSee how to change limits here https://medium.com/@muhammadtriwibowo/set-permanently-ulimit-n-open-files-in-ubuntu-4d61064429a",
@@ -1807,7 +1807,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1826,7 +1826,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "",
@@ -1931,7 +1931,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1945,7 +1945,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"exemplar": false,
@@ -1963,7 +1963,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -2052,7 +2052,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -2069,7 +2069,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the number of bytes read/write from the storage layer.",
@@ -2170,7 +2170,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"expr": "sum(rate(process_io_storage_read_bytes_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval]))",
@@ -2183,7 +2183,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"expr": "sum(rate(process_io_storage_written_bytes_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval]))",
@@ -2200,7 +2200,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -2289,7 +2289,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -2306,7 +2306,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the number of read/write syscalls such as read, pread, write, pwrite.",
@@ -2407,7 +2407,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -2422,7 +2422,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -2441,7 +2441,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "",
@@ -2530,7 +2530,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -2548,7 +2548,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "",
@@ -2637,7 +2637,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -2655,7 +2655,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the time goroutines have spent in runnable state before actually running. The lower is better.\n\nHigh values or values exceeding the threshold is usually a sign of insufficient CPU resources or CPU throttling. \n\nVerify that service has enough CPU resources. Otherwise, the service could work unreliably with delays in processing.",
@@ -2743,7 +2743,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -2780,7 +2780,7 @@
"multi": false,
"name": "ds",
"options": [],
"query": "victoriametrics-metrics-datasource",
"query": "victoriametrics-datasource",
"queryValue": "",
"refresh": 1,
"regex": "",
@@ -2790,7 +2790,7 @@
{
"current": {},
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"definition": "label_values(vm_app_version{version=~\"victoria-logs-.*\"}, job)",
@@ -2812,7 +2812,7 @@
{
"current": {},
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"definition": "label_values(vm_app_version{job=~\"$job\"}, instance)",
@@ -2833,7 +2833,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"filters": [],

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,7 @@
},
{
"type": "datasource",
"id": "victoriametrics-metrics-datasource",
"id": "victoriametrics-datasource",
"name": "VictoriaMetrics",
"version": "1.0.0"
},
@@ -56,7 +56,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"enable": true,
@@ -69,7 +69,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"enable": true,
@@ -140,7 +140,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows if the last configuration update was successful. \"Not Ok\" means there was an unsuccessful attempt to update the configuration due to some error. Check the log for details.",
@@ -210,7 +210,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"exemplar": false,
@@ -225,7 +225,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the total number of loaded alerting rules across selected instances and groups.",
@@ -273,7 +273,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"exemplar": false,
@@ -288,7 +288,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the total number of loaded recording rules across selected instances and groups.",
@@ -336,7 +336,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"exemplar": false,
@@ -351,7 +351,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the total number of errors generated by recording/alerting rules for selected instances and groups.",
@@ -403,7 +403,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"exemplar": false,
@@ -418,7 +418,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows number of Recording Rules which produce no data.\n\n Usually it means that such rules are misconfigured, since they give no output during the evaluation.\nPlease check if rule's expression is correct and it is working as expected.",
@@ -470,7 +470,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -487,7 +487,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -568,7 +568,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -584,7 +584,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -672,7 +672,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -716,7 +716,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the number of fired alerts by job.",
@@ -804,7 +804,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -821,7 +821,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Top $topk groups by evaluation duration. Shows groups that take the most of time during the evaluation across all instances.\n\nThe panel uses MetricsQL functions and may not work with VictoriaMetrics.",
@@ -909,7 +909,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -926,7 +926,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows how many requests (executions) per second vmalert sends to the configured datasource.",
@@ -1011,7 +1011,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1028,7 +1028,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the error rate while executing configured rules. Non-zero value means there are some issues with existing rules. Check the logs to get more details.",
@@ -1113,7 +1113,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1144,7 +1144,7 @@
"panels": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "The precentage of used RSS memory\n\nIf you think that usage is abnormal or unexpected, please file an issue and attach memory profile if possible.",
@@ -1241,7 +1241,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1258,7 +1258,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Amount of used RSS memory\n\nIf you think that usage is abnormal or unexpected, please file an issue and attach memory profile if possible.",
@@ -1354,7 +1354,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1371,7 +1371,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the CPU usage percentage per vmalert instance. \nIf you think that usage is abnormal or unexpected pls file an issue and attach CPU profile if possible.",
@@ -1468,7 +1468,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1487,7 +1487,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the max number of CPU cores used by a `job` and the corresponding limit.",
@@ -1584,7 +1584,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1599,7 +1599,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1619,7 +1619,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Panel shows the percentage of open file descriptors in the OS.\nReaching the limit of open files can cause various issues and must be prevented.\n\nSee how to change limits here https://medium.com/@muhammadtriwibowo/set-permanently-ulimit-n-open-files-in-ubuntu-4d61064429a",
@@ -1709,7 +1709,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1728,7 +1728,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -1817,7 +1817,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1835,7 +1835,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the percent of CPU spent on garbage collection.\n\nIf % is high, then CPU usage can be decreased by changing GOGC to higher values. Increasing GOGC value will increase memory usage, and decrease CPU usage.\n\nTry searching for keyword `GOGC` at https://docs.victoriametrics.com/troubleshooting/ ",
@@ -1925,7 +1925,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1943,7 +1943,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the time goroutines have spent in runnable state before actually running. The lower is better.\n\nHigh values or values exceeding the threshold is usually a sign of insufficient CPU resources or CPU throttling. \n\nVerify that service has enough CPU resources. Otherwise, the service could work unreliably with delays in processing.",
@@ -2031,7 +2031,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -2072,7 +2072,7 @@
"panels": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -2157,7 +2157,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -2202,7 +2202,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Missed evaluation means that group evaluation time takes longer than the configured evaluation interval. \nThis may result in missed alerting notifications or recording rules samples. Try increasing evaluation interval or concurrency for such groups. See https://docs.victoriametrics.com/vmalert/#groups\n\nIf rule expressions are taking longer than expected, please see https://docs.victoriametrics.com/troubleshooting/#slow-queries.\"",
@@ -2287,7 +2287,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -2304,7 +2304,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the number of restarts per job. The chart can be useful to identify periodic process restarts and correlate them with potential issues or anomalies. Normally, processes shouldn't restart unless restart was inited by user. The reason of restarts should be figured out by checking the logs of each specific service. ",
@@ -2392,7 +2392,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -2426,7 +2426,7 @@
"panels": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows top $topk current active (firing) alerting rules.\n\nThe panel uses MetricsQL functions and may not work with VictoriaMetrics.",
@@ -2511,7 +2511,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -2528,7 +2528,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the events when rule execution resulted into an error. Check the logs for more details.",
@@ -2613,7 +2613,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -2630,7 +2630,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the current pending alerting rules per group.\nBy pending means the rule which remains active less than configured `for` parameter.",
@@ -2715,7 +2715,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -2732,7 +2732,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the error rate for the attempts to send alerts to Alertmanager. If not zero it means there issues on attempt to send notification to Alertmanager and some alerts may be not delivered properly. Check the logs for more details.",
@@ -2816,7 +2816,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"exemplar": false,
@@ -2831,7 +2831,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows how many alerts are sent to Alertmanager per second. Only active alerts are sent.",
@@ -2915,7 +2915,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -2959,7 +2959,7 @@
"panels": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the top $topk recording rules which generate the most of [samples](https://docs.victoriametrics.com/keyconcepts/#raw-samples). Each generated sample is basically a time series which then ingested into configured remote storage. Rules with high numbers may cause the most pressure on the remote database and become a source of too high cardinality.\n\nThe panel uses MetricsQL functions and may not work with VictoriaMetrics.",
@@ -3044,7 +3044,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -3061,7 +3061,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the rules which do not produce any [samples](https://docs.victoriametrics.com/keyconcepts/#raw-samples) during the evaluation. Usually it means that such rules are misconfigured, since they give no output during the evaluation.\nPlease check if rule's expression is correct and it is working as expected.",
@@ -3146,7 +3146,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -3163,7 +3163,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -3245,7 +3245,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -3285,7 +3285,7 @@
"panels": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -3361,7 +3361,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -3376,7 +3376,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the number of datapoints dropped by vmalert while sending to the configured remote write URL. vmalert performs up to 5 retries before dropping the data. Check vmalert's error logs for the specific error message.",
@@ -3453,7 +3453,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -3468,7 +3468,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows current number of established connections to remote write endpoints.\n\n",
@@ -3554,7 +3554,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -3571,7 +3571,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the global rate for number of written bytes via remote write connections.",
@@ -3657,7 +3657,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -3696,7 +3696,7 @@
"multi": false,
"name": "ds",
"options": [],
"query": "victoriametrics-metrics-datasource",
"query": "victoriametrics-datasource",
"queryValue": "",
"refresh": 1,
"regex": "",
@@ -3706,7 +3706,7 @@
{
"current": {},
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"definition": "label_values(vm_app_version{version=~\"^vmalert.*\"}, job)",
@@ -3729,7 +3729,7 @@
"allValue": ".*",
"current": {},
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"definition": "label_values(vm_app_version{job=~\"$job\"}, instance)",
@@ -3752,7 +3752,7 @@
"allValue": ".*",
"current": {},
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"definition": "label_values(vmalert_iteration_total{job=~\"$job\", instance=~\"$instance\"},file)",
@@ -3775,7 +3775,7 @@
"allValue": ".*",
"current": {},
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"definition": "label_values(vmalert_iteration_total{job=~\"$job\", instance=~\"$instance\"}, group)",
@@ -3843,7 +3843,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"filters": [],

View File

@@ -11,7 +11,7 @@
},
{
"type": "datasource",
"id": "victoriametrics-metrics-datasource",
"id": "victoriametrics-datasource",
"name": "VictoriaMetrics",
"version": "1.0.0"
},
@@ -116,7 +116,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -204,7 +204,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -222,7 +222,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows if the last configuration update was successful. \"Not Ok\" means there was an unsuccessful attempt to update the configuration due to some error. Check the log for details.",
@@ -292,7 +292,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"exemplar": false,
@@ -307,7 +307,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the rate of requests.",
@@ -356,7 +356,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -372,7 +372,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the total number of users defined at configuration file.",
@@ -420,7 +420,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -437,7 +437,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the rate of request errors.",
@@ -486,7 +486,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -502,7 +502,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -581,7 +581,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -611,7 +611,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -690,7 +690,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -702,7 +702,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -718,7 +718,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows percent utilization of per concurrent requests capacity.",
@@ -805,7 +805,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -822,7 +822,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the rate of rejected requests by a reason.",
@@ -902,7 +902,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -918,7 +918,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": " The number of concurrent connections processed by vmauth reached one of limits. Possible solutions:\n- increase global limit with flag -maxConcurrentRequests\n- increase limit with flag: -maxConcurrentPerUserRequests for all users or with config option `max_concurrent_requests` per user.\n- deploy additional vmauth replicas\n- check requests latency at backend service and allocate resources to it if needed",
@@ -998,7 +998,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1010,7 +1010,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1022,7 +1022,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1038,7 +1038,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows duration in seconds of user requests by quantile.",
@@ -1124,7 +1124,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1136,7 +1136,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1162,7 +1162,7 @@
"panels": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Percentage of used memory (resident).\nThe application's performance will significantly degrade when memory usage is close to 100%.",
@@ -1251,7 +1251,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1268,7 +1268,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -1356,7 +1356,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1375,7 +1375,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "",
@@ -1464,7 +1464,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"expr": "sum(go_memstats_sys_bytes{job=~\"$job\", instance=~\"$instance\"}) + sum(vm_cache_size_bytes{job=~\"$job\", instance=~\"$instance\"})",
@@ -1476,7 +1476,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"expr": "sum(go_memstats_heap_inuse_bytes{job=~\"$job\", instance=~\"$instance\"}) + sum(vm_cache_size_bytes{job=~\"$job\", instance=~\"$instance\"})",
@@ -1488,7 +1488,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"expr": "sum(go_memstats_stack_inuse_bytes{job=~\"$job\", instance=~\"$instance\"})",
@@ -1500,7 +1500,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"expr": "sum(process_resident_memory_bytes{job=~\"$job\", instance=~\"$instance\"})",
@@ -1513,7 +1513,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"exemplar": false,
@@ -1531,7 +1531,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "",
@@ -1636,7 +1636,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"expr": "rate(process_cpu_seconds_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])",
@@ -1648,7 +1648,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"exemplar": false,
@@ -1666,7 +1666,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "",
@@ -1755,7 +1755,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"expr": "sum(vm_tcplistener_conns{job=~\"$job\", instance=~\"$instance\"})",
@@ -1771,7 +1771,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "",
@@ -1860,7 +1860,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -1878,7 +1878,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the percentage of open file descriptors compared to the limit set in the OS.\nReaching the limit of open files can cause various issues and must be prevented.\n\nSee how to change limits here https://medium.com/@muhammadtriwibowo/set-permanently-ulimit-n-open-files-in-ubuntu-4d61064429a",
@@ -1984,7 +1984,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -2003,7 +2003,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -2092,7 +2092,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"expr": "sum(go_goroutines{job=~\"$job\", instance=~\"$instance\"})",
@@ -2107,7 +2107,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -2196,7 +2196,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"expr": "sum(process_num_threads{job=~\"$job\", instance=~\"$instance\"})",
@@ -2225,7 +2225,7 @@
"panels": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
@@ -2311,7 +2311,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -2352,7 +2352,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows number of generated error and warning messages in logs. Non-zero value may be a sign of connectivity or missconfiguration errors.",
@@ -2433,7 +2433,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -2448,7 +2448,7 @@
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"description": "Shows the number of restarts per job. The chart can be useful to identify periodic process restarts and correlate them with potential issues or anomalies. Normally, processes shouldn't restart unless restart was inited by user. The reason of restarts should be figured out by checking the logs of each specific service. ",
@@ -2537,7 +2537,7 @@
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
@@ -2573,7 +2573,7 @@
"multi": false,
"name": "ds",
"options": [],
"query": "victoriametrics-metrics-datasource",
"query": "victoriametrics-datasource",
"queryValue": "",
"refresh": 1,
"regex": "",
@@ -2583,7 +2583,7 @@
{
"current": {},
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"definition": "label_values(vm_app_version{version=~\"^vmauth.*\"}, job)",
@@ -2606,7 +2606,7 @@
"allValue": ".*",
"current": {},
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"definition": "label_values(vm_app_version{job=~\"$job\"}, instance)",
@@ -2628,7 +2628,7 @@
{
"current": {},
"datasource": {
"type": "victoriametrics-metrics-datasource",
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"definition": "label_values(vmauth_user_requests_total{job=~\"$job\", instance=~\"$instance\"}, username)",

View File

@@ -2,11 +2,11 @@
DOCKER_NAMESPACE ?= victoriametrics
ROOT_IMAGE ?= alpine:3.21.2
ROOT_IMAGE ?= alpine:3.21.0
ROOT_IMAGE_SCRATCH ?= scratch
CERTS_IMAGE := alpine:3.21.2
CERTS_IMAGE := alpine:3.21.0
GO_BUILDER_IMAGE := golang:1.23.5-alpine
GO_BUILDER_IMAGE := golang:1.23.4-alpine
BUILDER_IMAGE := local/builder:2.0.0-$(shell echo $(GO_BUILDER_IMAGE) | tr :/ __)-1
BASE_IMAGE := local/base:1.1.4-$(shell echo $(ROOT_IMAGE) | tr :/ __)-$(shell echo $(CERTS_IMAGE) | tr :/ __)
DOCKER ?= docker

View File

@@ -4,7 +4,7 @@ services:
# And forward them to --remoteWrite.url
vmagent:
container_name: vmagent
image: victoriametrics/vmagent:v1.110.0
image: victoriametrics/vmagent:v1.108.1
depends_on:
- "vminsert"
ports:
@@ -39,7 +39,7 @@ services:
# where N is number of vmstorages (2 in this case).
vmstorage-1:
container_name: vmstorage-1
image: victoriametrics/vmstorage:v1.110.0-cluster
image: victoriametrics/vmstorage:v1.108.1-cluster
ports:
- 8482
- 8400
@@ -51,7 +51,7 @@ services:
restart: always
vmstorage-2:
container_name: vmstorage-2
image: victoriametrics/vmstorage:v1.110.0-cluster
image: victoriametrics/vmstorage:v1.108.1-cluster
ports:
- 8482
- 8400
@@ -66,7 +66,7 @@ services:
# pre-process them and distributes across configured vmstorage shards.
vminsert:
container_name: vminsert
image: victoriametrics/vminsert:v1.110.0-cluster
image: victoriametrics/vminsert:v1.108.1-cluster
depends_on:
- "vmstorage-1"
- "vmstorage-2"
@@ -81,7 +81,7 @@ services:
# vmselect collects results from configured `--storageNode` shards.
vmselect-1:
container_name: vmselect-1
image: victoriametrics/vmselect:v1.110.0-cluster
image: victoriametrics/vmselect:v1.108.1-cluster
depends_on:
- "vmstorage-1"
- "vmstorage-2"
@@ -94,7 +94,7 @@ services:
restart: always
vmselect-2:
container_name: vmselect-2
image: victoriametrics/vmselect:v1.110.0-cluster
image: victoriametrics/vmselect:v1.108.1-cluster
depends_on:
- "vmstorage-1"
- "vmstorage-2"
@@ -112,7 +112,7 @@ services:
# It can be used as an authentication proxy.
vmauth:
container_name: vmauth
image: victoriametrics/vmauth:v1.110.0
image: victoriametrics/vmauth:v1.108.1
depends_on:
- "vmselect-1"
- "vmselect-2"
@@ -127,7 +127,7 @@ services:
# vmalert executes alerting and recording rules
vmalert:
container_name: vmalert
image: victoriametrics/vmalert:v1.110.0
image: victoriametrics/vmalert:v1.108.1
depends_on:
- "vmauth"
ports:

View File

@@ -16,7 +16,7 @@ services:
- ./../../dashboards/victoriametrics.json:/var/lib/grafana/dashboards/vm.json
- ./../../dashboards/victorialogs.json:/var/lib/grafana/dashboards/vl.json
environment:
- "GF_INSTALL_PLUGINS=https://github.com/VictoriaMetrics/victorialogs-datasource/releases/download/v0.13.4/victoriametrics-logs-datasource-v0.13.4.zip;victoriametrics-logs-datasource"
- "GF_INSTALL_PLUGINS=https://github.com/VictoriaMetrics/victorialogs-datasource/releases/download/v0.13.0/victoriametrics-logs-datasource-v0.13.0.zip;victoriametrics-logs-datasource"
- "GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS=victoriametrics-logs-datasource"
networks:
- vm_net
@@ -45,7 +45,7 @@ services:
# storing logs and serving read queries.
victorialogs:
container_name: victorialogs
image: victoriametrics/victoria-logs:v1.8.0-victorialogs
image: victoriametrics/victoria-logs:v1.5.0-victorialogs
command:
- "--storageDataPath=/vlogs"
- "--httpListenAddr=:9428"
@@ -60,7 +60,7 @@ services:
# scraping, storing metrics and serve read requests.
victoriametrics:
container_name: victoriametrics
image: victoriametrics/victoria-metrics:v1.110.0
image: victoriametrics/victoria-metrics:v1.108.1
ports:
- 8428:8428
volumes:
@@ -79,7 +79,7 @@ services:
# depending on the requested path.
vmauth:
container_name: vmauth
image: victoriametrics/vmauth:v1.110.0
image: victoriametrics/vmauth:v1.108.1
depends_on:
- "victoriametrics"
- "victorialogs"
@@ -96,7 +96,7 @@ services:
# vmalert executes alerting and recording rules according to given rule type.
vmalert:
container_name: vmalert
image: victoriametrics/vmalert:v1.110.0
image: victoriametrics/vmalert:v1.108.1
depends_on:
- "vmauth"
- "alertmanager"

View File

@@ -4,7 +4,7 @@ services:
# And forward them to --remoteWrite.url
vmagent:
container_name: vmagent
image: victoriametrics/vmagent:v1.110.0
image: victoriametrics/vmagent:v1.108.1
depends_on:
- "victoriametrics"
ports:
@@ -22,7 +22,7 @@ services:
# storing metrics and serve read requests.
victoriametrics:
container_name: victoriametrics
image: victoriametrics/victoria-metrics:v1.110.0
image: victoriametrics/victoria-metrics:v1.108.1
ports:
- 8428:8428
- 8089:8089
@@ -65,7 +65,7 @@ services:
# vmalert executes alerting and recording rules
vmalert:
container_name: vmalert
image: victoriametrics/vmalert:v1.110.0
image: victoriametrics/vmalert:v1.108.1
depends_on:
- "victoriametrics"
- "alertmanager"

View File

@@ -2,7 +2,7 @@ apiVersion: 1
datasources:
- name: VictoriaMetrics - cluster
type: victoriametrics-metrics-datasource
type: victoriametrics-datasource
access: proxy
url: http://vmauth:8427/select/0/prometheus
isDefault: true

View File

@@ -2,7 +2,7 @@ apiVersion: 1
datasources:
- name: VictoriaMetrics
type: victoriametrics-metrics-datasource
type: victoriametrics-datasource
access: proxy
url: http://victoriametrics:8428
isDefault: true

View File

@@ -1,7 +1,7 @@
services:
# meta service will be ignored by compose
.victorialogs:
image: docker.io/victoriametrics/victoria-logs:v1.8.0-victorialogs
image: docker.io/victoriametrics/victoria-logs:v1.5.0-victorialogs
command:
- -storageDataPath=/vlogs
- -loggerFormat=json
@@ -19,7 +19,7 @@ services:
retries: 10
dd-proxy:
image: docker.io/victoriametrics/vmauth:v1.110.0
image: docker.io/victoriametrics/vmauth:v1.108.1
restart: on-failure
volumes:
- ./:/etc/vmauth
@@ -45,7 +45,7 @@ services:
replicas: 0
victoriametrics:
image: victoriametrics/victoria-metrics:v1.110.0
image: victoriametrics/victoria-metrics:latest
ports:
- '8428:8428'
command:

View File

@@ -16,6 +16,6 @@ services:
- ./../../dashboards/vm/vmalert.json:/var/lib/grafana/dashboards/vmalert.json
- ./../../dashboards/vm/vmauth.json:/var/lib/grafana/dashboards/vmauth.json
environment:
- "GF_INSTALL_PLUGINS=https://github.com/VictoriaMetrics/victoriametrics-datasource/releases/download/v0.12.1/victoriametrics-metrics-datasource-v0.12.1.zip;victoriametrics-metrics-datasource"
- "GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS=victoriametrics-metrics-datasource"
- "GF_INSTALL_PLUGINS=https://github.com/VictoriaMetrics/victoriametrics-datasource/releases/download/v0.10.3/victoriametrics-datasource-v0.10.3.zip;victoriametrics-datasource"
- "GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS=victoriametrics-datasource"
restart: always

View File

@@ -15,8 +15,8 @@ services:
- ./../../dashboards/vm/vmagent.json:/var/lib/grafana/dashboards/vmagent.json
- ./../../dashboards/vm/vmalert.json:/var/lib/grafana/dashboards/vmalert.json
environment:
- "GF_INSTALL_PLUGINS=https://github.com/VictoriaMetrics/victoriametrics-datasource/releases/download/v0.12.1/victoriametrics-metrics-datasource-v0.12.1.zip;victoriametrics-metrics-datasource"
- "GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS=victoriametrics-metrics-datasource"
- "GF_INSTALL_PLUGINS=https://github.com/VictoriaMetrics/victoriametrics-datasource/releases/download/v0.10.3/victoriametrics-datasource-v0.10.3.zip;victoriametrics-datasource"
- "GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS=victoriametrics-datasource"
networks:
- vm_net
restart: always

View File

@@ -1,7 +1,7 @@
services:
vmagent:
container_name: vmagent
image: victoriametrics/vmagent:v1.110.0
image: victoriametrics/vmagent:v1.108.1
depends_on:
- "victoriametrics"
ports:
@@ -18,7 +18,7 @@ services:
victoriametrics:
container_name: victoriametrics
image: victoriametrics/victoria-metrics:v1.110.0
image: victoriametrics/victoria-metrics:v1.108.1
ports:
- 8428:8428
volumes:
@@ -50,7 +50,7 @@ services:
vmalert:
container_name: vmalert
image: victoriametrics/vmalert:v1.110.0
image: victoriametrics/vmalert:v1.108.1
depends_on:
- "victoriametrics"
ports:
@@ -72,7 +72,7 @@ services:
restart: always
vmanomaly:
container_name: vmanomaly
image: victoriametrics/vmanomaly:v1.19.1
image: victoriametrics/vmanomaly:v1.18.8
depends_on:
- "victoriametrics"
ports:

View File

@@ -1,23 +1,21 @@
schedulers:
periodic:
# class: "scheduler.periodic.PeriodicScheduler"
infer_every: "1m"
fit_every: "1h"
fit_window: "2d" # 2d-14d based on the presense of weekly seasonality in your data
fit_every: "2m"
fit_window: "3h"
models:
prophet:
class: "prophet"
class: "model.prophet.ProphetModel"
args:
interval_width: 0.98
weekly_seasonality: False # comment it if your data has weekly seasonality
yearly_seasonality: False
reader:
datasource_url: "http://victoriametrics:8428/"
sampling_period: "60s"
sampling_period: "60s"
queries:
node_cpu_rate:
expr: "sum(rate(node_cpu_seconds_total[5m])) by (mode, instance, job)"
node_cpu_rate: "sum(rate(node_cpu_seconds_total[5m])) by (mode, instance, job)"
writer:
datasource_url: "http://victoriametrics:8428/"
@@ -26,4 +24,4 @@ writer:
monitoring:
pull: # Enable /metrics endpoint.
addr: "0.0.0.0"
port: 8490
port: 8490

View File

@@ -18,7 +18,7 @@ services:
- vlogs
generator:
image: golang:1.23.5-alpine
image: golang:1.23.4-alpine
restart: always
working_dir: /go/src/app
volumes:

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