Repository: VictoriaMetrics/VictoriaMetrics
Pull request #2527 (2022-05-02 07:52:00)

added details about deduplication


Patch:
diff --git a/app/vmalert/README.md b/app/vmalert/README.md
index 556bdd847..50ffe39b3 100644
--- a/app/vmalert/README.md
+++ b/app/vmalert/README.md
@@ -481,6 +481,9 @@ per rule before giving up.
 (rules which depend on each other) rules. It is expected, that remote storage will be able to persist
 previously accepted data during the delay, so data will be available for the subsequent queries.
 Keep it equal or bigger than `-remoteWrite.flushInterval`.
+* `replay.disableProgressBar` - whether to disable progress bar which shows progress work.
+Progress bar may generate a lot of log records, which is not formatted as standard VictoriaMetrics logger.
+It could break logs parsing by external system and generate additional load on it.
 
 See full description for these flags in `./vmalert --help`.
 
diff --git a/app/vmalert/notifier/alert.go b/app/vmalert/notifier/alert.go
index 233540e90..96f5e5d1a 100644
--- a/app/vmalert/notifier/alert.go
+++ b/app/vmalert/notifier/alert.go
@@ -92,12 +92,12 @@ var tplHeaders = []string{
 // requires a queryFunction as an argument.
 func (a *Alert) ExecTemplate(q QueryFn, labels, annotations map[string]string) (map[string]string, error) {
 	tplData := AlertTplData{Value: a.Value, Labels: labels, Expr: a.Expr}
-	return templateAnnotations(annotations, tplData, funcsWithQuery(q))
+	return templateAnnotations(annotations, tplData, funcsWithQuery(q), true)
 }
 
 // ExecTemplate executes the given template for given annotations map.
 func ExecTemplate(q QueryFn, annotations map[string]string, tpl AlertTplData) (map[string]string, error) {
-	return templateAnnotations(annotations, tpl, funcsWithQuery(q))
+	return templateAnnotations(annotations, tpl, funcsWithQuery(q), true)
 }
 
 // ValidateTemplates validate annotations for possible template error, uses empty data for template population
@@ -105,11 +105,11 @@ func ValidateTemplates(annotations map[string]string) error {
 	_, err := templateAnnotations(annotations, AlertTplData{
 		Labels: map[string]string{},
 		Value:  0,
-	}, tmplFunc)
+	}, tmplFunc, false)
 	return err
 }
 
-func templateAnnotations(annotations map[string]string, data AlertTplData, funcs template.FuncMap) (map[string]string, error) {
+func templateAnnotations(annotations map[string]string, data AlertTplData, funcs template.FuncMap, execute bool) (map[string]string, error) {
 	var builder strings.Builder
 	var buf bytes.Buffer
 	eg := new(utils.ErrGroup)
@@ -122,7 +122,7 @@ func templateAnnotations(annotations map[string]string, data AlertTplData, funcs
 		builder.Grow(len(header) + len(text))
 		builder.WriteString(header)
 		builder.WriteString(text)
-		if err := templateAnnotation(&buf, builder.String(), tData, funcs); err != nil {
+		if err := templateAnnotation(&buf, builder.String(), tData, funcs, execute); err != nil {
 			r[key] = text
 			eg.Add(fmt.Errorf("key %q, template %q: %w", key, text, err))
 			continue
@@ -138,12 +138,15 @@ type tplData struct {
 	ExternalURL    string
 }
 
-func templateAnnotation(dst io.Writer, text string, data tplData, funcs template.FuncMap) error {
+func templateAnnotation(dst io.Writer, text string, data tplData, funcs template.FuncMap, execute bool) error {
 	t := template.New("").Funcs(funcs).Option("missingkey=zero")
 	tpl, err := t.Parse(text)
 	if err != nil {
 		return fmt.Errorf("error parsing annotation: %w", err)
 	}
+	if !execute {
+		return nil
+	}
 	if err = tpl.Execute(dst, data); err != nil {
 		return fmt.Errorf("error evaluating annotation template: %w", err)
 	}
diff --git a/app/vmalert/notifier/alertmanager_request.qtpl.go b/app/vmalert/notifier/alertmanager_request.qtpl.go
index f3f7b193a..8a6ce45e2 100644
--- a/app/vmalert/notifier/alertmanager_request.qtpl.go
+++ b/app/vmalert/notifier/alertmanager_request.qtpl.go
@@ -1,135 +1,135 @@
 // Code generated by qtc from "alertmanager_request.qtpl". DO NOT EDIT.
 // See https://github.com/valyala/quicktemplate for details.
 
-//line notifier/alertmanager_request.qtpl:1
+//line app/vmalert/notifier/alertmanager_request.qtpl:1
 package notifier
 
-//line notifier/alertmanager_request.qtpl:1
+//line app/vmalert/notifier/alertmanager_request.qtpl:1
 import (
 	"time"
 
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
 )
 
-//line notifier/alertmanager_request.qtpl:8
+//line app/vmalert/notifier/alertmanager_request.qtpl:8
 import (
 	qtio422016 "io"
 
 	qt422016 "github.com/valyala/quicktemplate"
 )
 
-//line notifier/alertmanager_request.qtpl:8
+//line app/vmalert/notifier/alertmanager_request.qtpl:8
 var (
 	_ = qtio422016.Copy
 	_ = qt422016.AcquireByteBuffer
 )
 
-//line notifier/alertmanager_request.qtpl:8
+//line app/vmalert/notifier/alertmanager_request.qtpl:8
 func streamamRequest(qw422016 *qt422016.Writer, alerts []Alert, generatorURL func(Alert) string, relabelCfg *promrelabel.ParsedConfigs) {
-//line notifier/alertmanager_request.qtpl:8
+//line app/vmalert/notifier/alertmanager_request.qtpl:8
 	qw422016.N().S(`[`)
-//line notifier/alertmanager_request.qtpl:10
+//line app/vmalert/notifier/alertmanager_request.qtpl:10
 	for i, alert := range alerts {
-//line notifier/alertmanager_request.qtpl:10
+//line app/vmalert/notifier/alertmanager_request.qtpl:10
 		qw422016.N().S(`{"startsAt":`)
-//line notifier/alertmanager_request.qtpl:12
+//line app/vmalert/notifier/alertmanager_request.qtpl:12
 		qw422016.N().Q(alert.Start.Format(time.RFC3339Nano))
-//line notifier/alertmanager_request.qtpl:12
+//line app/vmalert/notifier/alertmanager_request.qtpl:12
 		qw422016.N().S(`,"generatorURL":`)
-//line notifier/alertmanager_request.qtpl:13
+//line app/vmalert/notifier/alertmanager_request.qtpl:13
 		qw422016.N().Q(generatorURL(alert))
-//line notifier/alertmanager_request.qtpl:13
+//line app/vmalert/notifier/alertmanager_request.qtpl:13
 		qw422016.N().S(`,`)
-//line notifier/alertmanager_request.qtpl:14
+//line app/vmalert/notifier/alertmanager_request.qtpl:14
 		if !alert.End.IsZero() {
-//line notifier/alertmanager_request.qtpl:14
+//line app/vmalert/notifier/alertmanager_request.qtpl:14
 			qw422016.N().S(`"endsAt":`)
-//line notifier/alertmanager_request.qtpl:15
+//line app/vmalert/notifier/alertmanager_request.qtpl:15
 			qw422016.N().Q(alert.End.Format(time.RFC3339Nano))
-//line notifier/alertmanager_request.qtpl:15
+//line app/vmalert/notifier/alertmanager_request.qtpl:15
 			qw422016.N().S(`,`)
-//line notifier/alertmanager_request.qtpl:16
+//line app/vmalert/notifier/alertmanager_request.qtpl:16
 		}
-//line notifier/alertmanager_request.qtpl:16
+//line app/vmalert/notifier/alertmanager_request.qtpl:16
 		qw422016.N().S(`"labels": {"alertname":`)
-//line notifier/alertmanager_request.qtpl:18
+//line app/vmalert/notifier/alertmanager_request.qtpl:18
 		qw422016.N().Q(alert.Name)
-//line notifier/alertmanager_request.qtpl:19
+//line app/vmalert/notifier/alertmanager_request.qtpl:19
 		lbls := alert.toPromLabels(relabelCfg)
 
-//line notifier/alertmanager_request.qtpl:20
+//line app/vmalert/notifier/alertmanager_request.qtpl:20
 		for _, l := range lbls {
-//line notifier/alertmanager_request.qtpl:20
+//line app/vmalert/notifier/alertmanager_request.qtpl:20
 			qw422016.N().S(`,`)
-//line notifier/alertmanager_request.qtpl:21
+//line app/vmalert/notifier/alertmanager_request.qtpl:21
 			qw422016.N().Q(l.Name)
-//line notifier/alertmanager_request.qtpl:21
+//line app/vmalert/notifier/alertmanager_request.qtpl:21
 			qw422016.N().S(`:`)
-//line notifier/alertmanager_request.qtpl:21
+//line app/vmalert/notifier/alertmanager_request.qtpl:21
 			qw422016.N().Q(l.Value)
-//line notifier/alertmanager_request.qtpl:22
+//line app/vmalert/notifier/alertmanager_request.qtpl:22
 		}
-//line notifier/alertmanager_request.qtpl:22
+//line app/vmalert/notifier/alertmanager_request.qtpl:22
 		qw422016.N().S(`},"annotations": {`)
-//line notifier/alertmanager_request.qtpl:25
+//line app/vmalert/notifier/alertmanager_request.qtpl:25
 		c := len(alert.Annotations)
 
-//line notifier/alertmanager_request.qtpl:26
+//line app/vmalert/notifier/alertmanager_request.qtpl:26
 		for k, v := range alert.Annotations {
-//line notifier/alertmanager_request.qtpl:27
+//line app/vmalert/notifier/alertmanager_request.qtpl:27
 			c = c - 1
 
-//line notifier/alertmanager_request.qtpl:28
+//line app/vmalert/notifier/alertmanager_request.qtpl:28
 			qw422016.N().Q(k)
-//line notifier/alertmanager_request.qtpl:28
+//line app/vmalert/notifier/alertmanager_request.qtpl:28
 			qw422016.N().S(`:`)
-//line notifier/alertmanager_request.qtpl:28
+//line app/vmalert/notifier/alertmanager_request.qtpl:28
 			qw422016.N().Q(v)
-//line notifier/alertmanager_request.qtpl:28
+//line app/vmalert/notifier/alertmanager_request.qtpl:28
 			if c > 0 {
-//line notifier/alertmanager_request.qtpl:28
+//line app/vmalert/notifier/alertmanager_request.qtpl:28
 				qw422016.N().S(`,`)
-//line notifier/alertmanager_request.qtpl:28
+//line app/vmalert/notifier/alertmanager_request.qtpl:28
 			}
-//line notifier/alertmanager_request.qtpl:29
+//line app/vmalert/notifier/alertmanager_request.qtpl:29
 		}
-//line notifier/alertmanager_request.qtpl:29
+//line app/vmalert/notifier/alertmanager_request.qtpl:29
 		qw422016.N().S(`}}`)
-//line notifier/alertmanager_request.qtpl:32
+//line app/vmalert/notifier/alertmanager_request.qtpl:32
 		if i != len(alerts)-1 {
-//line notifier/alertmanager_request.qtpl:32
+//line app/vmalert/notifier/alertmanager_request.qtpl:32
 			qw422016.N().S(`,`)
-//line notifier/alertmanager_request.qtpl:32
+//line app/vmalert/notifier/alertmanager_request.qtpl:32
 		}
-//line notifier/alertmanager_request.qtpl:33
+//line app/vmalert/notifier/alertmanager_request.qtpl:33
 	}
-//line notifier/alertmanager_request.qtpl:33
+//line app/vmalert/notifier/alertmanager_request.qtpl:33
 	qw422016.N().S(`]`)
-//line notifier/alertmanager_request.qtpl:35
+//line app/vmalert/notifier/alertmanager_request.qtpl:35
 }
 
-//line notifier/alertmanager_request.qtpl:35
+//line app/vmalert/notifier/alertmanager_request.qtpl:35
 func writeamRequest(qq422016 qtio422016.Writer, alerts []Alert, generatorURL func(Alert) string, relabelCfg *promrelabel.ParsedConfigs) {
-//line notifier/alertmanager_request.qtpl:35
+//line app/vmalert/notifier/alertmanager_request.qtpl:35
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line notifier/alertmanager_request.qtpl:35
+//line app/vmalert/notifier/alertmanager_request.qtpl:35
 	streamamRequest(qw422016, alerts, generatorURL, relabelCfg)
-//line notifier/alertmanager_request.qtpl:35
+//line app/vmalert/notifier/alertmanager_request.qtpl:35
 	qt422016.ReleaseWriter(qw422016)
-//line notifier/alertmanager_request.qtpl:35
+//line app/vmalert/notifier/alertmanager_request.qtpl:35
 }
 
-//line notifier/alertmanager_request.qtpl:35
+//line app/vmalert/notifier/alertmanager_request.qtpl:35
 func amRequest(alerts []Alert, generatorURL func(Alert) string, relabelCfg *promrelabel.ParsedConfigs) string {
-//line notifier/alertmanager_request.qtpl:35
+//line app/vmalert/notifier/alertmanager_request.qtpl:35
 	qb422016 := qt422016.AcquireByteBuffer()
-//line notifier/alertmanager_request.qtpl:35
+//line app/vmalert/notifier/alertmanager_request.qtpl:35
 	writeamRequest(qb422016, alerts, generatorURL, relabelCfg)
-//line notifier/alertmanager_request.qtpl:35
+//line app/vmalert/notifier/alertmanager_request.qtpl:35
 	qs422016 := string(qb422016.B)
-//line notifier/alertmanager_request.qtpl:35
+//line app/vmalert/notifier/alertmanager_request.qtpl:35
 	qt422016.ReleaseByteBuffer(qb422016)
-//line notifier/alertmanager_request.qtpl:35
+//line app/vmalert/notifier/alertmanager_request.qtpl:35
 	return qs422016
-//line notifier/alertmanager_request.qtpl:35
+//line app/vmalert/notifier/alertmanager_request.qtpl:35
 }
diff --git a/app/vmalert/replay.go b/app/vmalert/replay.go
index f702f11d3..c02112241 100644
--- a/app/vmalert/replay.go
+++ b/app/vmalert/replay.go
@@ -29,6 +29,8 @@ var (
 		"Max number of data points expected in one request. The higher the value, the less requests will be made during replay.")
 	replayRuleRetryAttempts = flag.Int("replay.ruleRetryAttempts", 5,
 		"Defines how many retries to make before giving up on rule if request for it returns an error.")
+	disableProgressBar = flag.Bool("replay.disableProgressBar", false, "Whether to disable rendering progress bars during the replay. "+
+		"Progress bar rendering might be verbose or break the logs parsing, so it is recommended to be disabled when not used in interactive mode.")
 )
 
 func replay(groupsCfg []config.Group, qb datasource.QuerierBuilder, rw *remotewrite.Client) error {
@@ -88,7 +90,10 @@ func (g *Group) replay(start, end time.Time, rw *remotewrite.Client) int {
 		g.Name, g.Interval, iterations, step)
 	for _, rule := range g.Rules {
 		fmt.Printf("> Rule %q (ID: %d)\n", rule, rule.ID())
-		bar := pb.StartNew(iterations)
+		var bar *pb.ProgressBar
+		if !*disableProgressBar {
+			bar = pb.StartNew(iterations)
+		}
 		ri.reset()
 		for ri.next() {
 			n, err := replayRule(rule, ri.s, ri.e, rw)
@@ -96,9 +101,13 @@ func (g *Group) replay(start, end time.Time, rw *remotewrite.Client) int {
 				logger.Fatalf("rule %q: %s", rule, err)
 			}
 			total += n
-			bar.Increment()
+			if bar != nil {
+				bar.Increment()
+			}
+		}
+		if bar != nil {
+			bar.Finish()
 		}
-		bar.Finish()
 		// sleep to let remote storage to flush data on-disk
 		// so chained rules could be calculated correctly
 		time.Sleep(*replayRulesDelay)
diff --git a/app/vmalert/tpl/footer.qtpl.go b/app/vmalert/tpl/footer.qtpl.go
index 7100c36bc..fd5a0a1ce 100644
--- a/app/vmalert/tpl/footer.qtpl.go
+++ b/app/vmalert/tpl/footer.qtpl.go
@@ -1,25 +1,25 @@
 // Code generated by qtc from "footer.qtpl". DO NOT EDIT.
 // See https://github.com/valyala/quicktemplate for details.
 
-//line tpl/footer.qtpl:1
+//line app/vmalert/tpl/footer.qtpl:1
 package tpl
 
-//line tpl/footer.qtpl:1
+//line app/vmalert/tpl/footer.qtpl:1
 import (
 	qtio422016 "io"
 
 	qt422016 "github.com/valyala/quicktemplate"
 )
 
-//line tpl/footer.qtpl:1
+//line app/vmalert/tpl/footer.qtpl:1
 var (
 	_ = qtio422016.Copy
 	_ = qt422016.AcquireByteBuffer
 )
 
-//line tpl/footer.qtpl:1
+//line app/vmalert/tpl/footer.qtpl:1
 func StreamFooter(qw422016 *qt422016.Writer) {
-//line tpl/footer.qtpl:1
+//line app/vmalert/tpl/footer.qtpl:1
 	qw422016.N().S(`
         </main>
         <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
@@ -56,31 +56,31 @@ func StreamFooter(qw422016 *qt422016.Writer) {
     </body>
 </html>
 `)
-//line tpl/footer.qtpl:36
+//line app/vmalert/tpl/footer.qtpl:36
 }
 
-//line tpl/footer.qtpl:36
+//line app/vmalert/tpl/footer.qtpl:36
 func WriteFooter(qq422016 qtio422016.Writer) {
-//line tpl/footer.qtpl:36
+//line app/vmalert/tpl/footer.qtpl:36
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line tpl/footer.qtpl:36
+//line app/vmalert/tpl/footer.qtpl:36
 	StreamFooter(qw422016)
-//line tpl/footer.qtpl:36
+//line app/vmalert/tpl/footer.qtpl:36
 	qt422016.ReleaseWriter(qw422016)
-//line tpl/footer.qtpl:36
+//line app/vmalert/tpl/footer.qtpl:36
 }
 
-//line tpl/footer.qtpl:36
+//line app/vmalert/tpl/footer.qtpl:36
 func Footer() string {
-//line tpl/footer.qtpl:36
+//line app/vmalert/tpl/footer.qtpl:36
 	qb422016 := qt422016.AcquireByteBuffer()
-//line tpl/footer.qtpl:36
+//line app/vmalert/tpl/footer.qtpl:36
 	WriteFooter(qb422016)
-//line tpl/footer.qtpl:36
+//line app/vmalert/tpl/footer.qtpl:36
 	qs422016 := string(qb422016.B)
-//line tpl/footer.qtpl:36
+//line app/vmalert/tpl/footer.qtpl:36
 	qt422016.ReleaseByteBuffer(qb422016)
-//line tpl/footer.qtpl:36
+//line app/vmalert/tpl/footer.qtpl:36
 	return qs422016
-//line tpl/footer.qtpl:36
+//line app/vmalert/tpl/footer.qtpl:36
 }
diff --git a/app/vmalert/tpl/header.qtpl.go b/app/vmalert/tpl/header.qtpl.go
index a4f75c3e7..a91b9cb5c 100644
--- a/app/vmalert/tpl/header.qtpl.go
+++ b/app/vmalert/tpl/header.qtpl.go
@@ -1,39 +1,39 @@
 // Code generated by qtc from "header.qtpl". DO NOT EDIT.
 // See https://github.com/valyala/quicktemplate for details.
 
-//line tpl/header.qtpl:1
+//line app/vmalert/tpl/header.qtpl:1
 package tpl
 
-//line tpl/header.qtpl:1
+//line app/vmalert/tpl/header.qtpl:1
 import (
 	qtio422016 "io"
 
 	qt422016 "github.com/valyala/quicktemplate"
 )
 
-//line tpl/header.qtpl:1
+//line app/vmalert/tpl/header.qtpl:1
 var (
 	_ = qtio422016.Copy
 	_ = qt422016.AcquireByteBuffer
 )
 
-//line tpl/header.qtpl:1
+//line app/vmalert/tpl/header.qtpl:1
 func StreamHeader(qw422016 *qt422016.Writer, title string, pages []NavItem) {
-//line tpl/header.qtpl:1
+//line app/vmalert/tpl/header.qtpl:1
 	qw422016.N().S(`
 <!DOCTYPE html>
 <html lang="en">
 <head>
     <title>vmalert`)
-//line tpl/header.qtpl:5
+//line app/vmalert/tpl/header.qtpl:5
 	if title != "" {
-//line tpl/header.qtpl:5
+//line app/vmalert/tpl/header.qtpl:5
 		qw422016.N().S(` - `)
-//line tpl/header.qtpl:5
+//line app/vmalert/tpl/header.qtpl:5
 		qw422016.E().S(title)
-//line tpl/header.qtpl:5
+//line app/vmalert/tpl/header.qtpl:5
 	}
-//line tpl/header.qtpl:5
+//line app/vmalert/tpl/header.qtpl:5
 	qw422016.N().S(`</title>
     <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
     <style>
@@ -66,37 +66,37 @@ func StreamHeader(qw422016 *qt422016.Writer, title string, pages []NavItem) {
 </head>
 <body>
     `)
-//line tpl/header.qtpl:36
+//line app/vmalert/tpl/header.qtpl:36
 	StreamPrintNavItems(qw422016, title, pages)
-//line tpl/header.qtpl:36
+//line app/vmalert/tpl/header.qtpl:36
 	qw422016.N().S(`
     <main class="px-2">
 `)
-//line tpl/header.qtpl:38
+//line app/vmalert/tpl/header.qtpl:38
 }
 
-//line tpl/header.qtpl:38
+//line app/vmalert/tpl/header.qtpl:38
 func WriteHeader(qq422016 qtio422016.Writer, title string, pages []NavItem) {
-//line tpl/header.qtpl:38
+//line app/vmalert/tpl/header.qtpl:38
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line tpl/header.qtpl:38
+//line app/vmalert/tpl/header.qtpl:38
 	StreamHeader(qw422016, title, pages)
-//line tpl/header.qtpl:38
+//line app/vmalert/tpl/header.qtpl:38
 	qt422016.ReleaseWriter(qw422016)
-//line tpl/header.qtpl:38
+//line app/vmalert/tpl/header.qtpl:38
 }
 
-//line tpl/header.qtpl:38
+//line app/vmalert/tpl/header.qtpl:38
 func Header(title string, pages []NavItem) string {
-//line tpl/header.qtpl:38
+//line app/vmalert/tpl/header.qtpl:38
 	qb422016 := qt422016.AcquireByteBuffer()
-//line tpl/header.qtpl:38
+//line app/vmalert/tpl/header.qtpl:38
 	WriteHeader(qb422016, title, pages)
-//line tpl/header.qtpl:38
+//line app/vmalert/tpl/header.qtpl:38
 	qs422016 := string(qb422016.B)
-//line tpl/header.qtpl:38
+//line app/vmalert/tpl/header.qtpl:38
 	qt422016.ReleaseByteBuffer(qb422016)
-//line tpl/header.qtpl:38
+//line app/vmalert/tpl/header.qtpl:38
 	return qs422016
-//line tpl/header.qtpl:38
+//line app/vmalert/tpl/header.qtpl:38
 }
diff --git a/app/vmalert/tpl/nav.qtpl.go b/app/vmalert/tpl/nav.qtpl.go
index d4e7f56a0..85b56266f 100644
--- a/app/vmalert/tpl/nav.qtpl.go
+++ b/app/vmalert/tpl/nav.qtpl.go
@@ -1,96 +1,96 @@
 // Code generated by qtc from "nav.qtpl". DO NOT EDIT.
 // See https://github.com/valyala/quicktemplate for details.
 
-//line tpl/nav.qtpl:1
+//line app/vmalert/tpl/nav.qtpl:1
 package tpl
 
-//line tpl/nav.qtpl:1
+//line app/vmalert/tpl/nav.qtpl:1
 import (
 	qtio422016 "io"
 
 	qt422016 "github.com/valyala/quicktemplate"
 )
 
-//line tpl/nav.qtpl:1
+//line app/vmalert/tpl/nav.qtpl:1
 var (
 	_ = qtio422016.Copy
 	_ = qt422016.AcquireByteBuffer
 )
 
-//line tpl/nav.qtpl:2
+//line app/vmalert/tpl/nav.qtpl:2
 type NavItem struct {
 	Name string
 	Url  string
 }
 
-//line tpl/nav.qtpl:8
+//line app/vmalert/tpl/nav.qtpl:8
 func StreamPrintNavItems(qw422016 *qt422016.Writer, current string, items []NavItem) {
-//line tpl/nav.qtpl:8
+//line app/vmalert/tpl/nav.qtpl:8
 	qw422016.N().S(`
 <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
   <div class="container-fluid">
     <div class="collapse navbar-collapse" id="navbarCollapse">
         <ul class="navbar-nav me-auto mb-2 mb-md-0">
             `)
-//line tpl/nav.qtpl:13
+//line app/vmalert/tpl/nav.qtpl:13
 	for _, item := range items {
-//line tpl/nav.qtpl:13
+//line app/vmalert/tpl/nav.qtpl:13
 		qw422016.N().S(`
                 <li class="nav-item">
                     <a class="nav-link`)
-//line tpl/nav.qtpl:15
+//line app/vmalert/tpl/nav.qtpl:15
 		if current == item.Name {
-//line tpl/nav.qtpl:15
+//line app/vmalert/tpl/nav.qtpl:15
 			qw422016.N().S(` active`)
-//line tpl/nav.qtpl:15
+//line app/vmalert/tpl/nav.qtpl:15
 		}
-//line tpl/nav.qtpl:15
+//line app/vmalert/tpl/nav.qtpl:15
 		qw422016.N().S(`" href="`)
-//line tpl/nav.qtpl:15
+//line app/vmalert/tpl/nav.qtpl:15
 		qw422016.E().S(item.Url)
-//line tpl/nav.qtpl:15
+//line app/vmalert/tpl/nav.qtpl:15
 		qw422016.N().S(`">
                         `)
-//line tpl/nav.qtpl:16
+//line app/vmalert/tpl/nav.qtpl:16
 		qw422016.E().S(item.Name)
-//line tpl/nav.qtpl:16
+//line app/vmalert/tpl/nav.qtpl:16
 		qw422016.N().S(`
                     </a>
                 </li>
             `)
-//line tpl/nav.qtpl:19
+//line app/vmalert/tpl/nav.qtpl:19
 	}
-//line tpl/nav.qtpl:19
+//line app/vmalert/tpl/nav.qtpl:19
 	qw422016.N().S(`
         </ul>
   </div>
 </nav>
 `)
-//line tpl/nav.qtpl:23
+//line app/vmalert/tpl/nav.qtpl:23
 }
 
-//line tpl/nav.qtpl:23
+//line app/vmalert/tpl/nav.qtpl:23
 func WritePrintNavItems(qq422016 qtio422016.Writer, current string, items []NavItem) {
-//line tpl/nav.qtpl:23
+//line app/vmalert/tpl/nav.qtpl:23
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line tpl/nav.qtpl:23
+//line app/vmalert/tpl/nav.qtpl:23
 	StreamPrintNavItems(qw422016, current, items)
-//line tpl/nav.qtpl:23
+//line app/vmalert/tpl/nav.qtpl:23
 	qt422016.ReleaseWriter(qw422016)
-//line tpl/nav.qtpl:23
+//line app/vmalert/tpl/nav.qtpl:23
 }
 
-//line tpl/nav.qtpl:23
+//line app/vmalert/tpl/nav.qtpl:23
 func PrintNavItems(current string, items []NavItem) string {
-//line tpl/nav.qtpl:23
+//line app/vmalert/tpl/nav.qtpl:23
 	qb422016 := qt422016.AcquireByteBuffer()
-//line tpl/nav.qtpl:23
+//line app/vmalert/tpl/nav.qtpl:23
 	WritePrintNavItems(qb422016, current, items)
-//line tpl/nav.qtpl:23
+//line app/vmalert/tpl/nav.qtpl:23
 	qs422016 := string(qb422016.B)
-//line tpl/nav.qtpl:23
+//line app/vmalert/tpl/nav.qtpl:23
 	qt422016.ReleaseByteBuffer(qb422016)
-//line tpl/nav.qtpl:23
+//line app/vmalert/tpl/nav.qtpl:23
 	return qs422016
-//line tpl/nav.qtpl:23
+//line app/vmalert/tpl/nav.qtpl:23
 }
diff --git a/app/vmalert/web.qtpl.go b/app/vmalert/web.qtpl.go
index e7f30c709..fde6567ea 100644
--- a/app/vmalert/web.qtpl.go
+++ b/app/vmalert/web.qtpl.go
@@ -1,10 +1,10 @@
 // Code generated by qtc from "web.qtpl". DO NOT EDIT.
 // See https://github.com/valyala/quicktemplate for details.
 
-//line web.qtpl:1
+//line app/vmalert/web.qtpl:1
 package main
 
-//line web.qtpl:3
+//line app/vmalert/web.qtpl:3
 import (
 	"path"
 	"sort"
@@ -14,111 +14,111 @@ import (
 	"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/tpl"
 )
 
-//line web.qtpl:13
+//line app/vmalert/web.qtpl:13
 import (
 	qtio422016 "io"
 
 	qt422016 "github.com/valyala/quicktemplate"
 )
 
-//line web.qtpl:13
+//line app/vmalert/web.qtpl:13
 var (
 	_ = qtio422016.Copy
 	_ = qt422016.AcquireByteBuffer
 )
 
-//line web.qtpl:13
+//line app/vmalert/web.qtpl:13
 func StreamWelcome(qw422016 *qt422016.Writer) {
-//line web.qtpl:13
+//line app/vmalert/web.qtpl:13
 	qw422016.N().S(`
     `)
-//line web.qtpl:14
+//line app/vmalert/web.qtpl:14
 	tpl.StreamHeader(qw422016, "vmalert", navItems)
-//line web.qtpl:14
+//line app/vmalert/web.qtpl:14
 	qw422016.N().S(`
     <p>
         API:<br>
         `)
-//line web.qtpl:17
+//line app/vmalert/web.qtpl:17
 	for _, p := range apiLinks {
-//line web.qtpl:17
+//line app/vmalert/web.qtpl:17
 		qw422016.N().S(`
             `)
-//line web.qtpl:19
+//line app/vmalert/web.qtpl:19
 		p, doc := p[0], p[1]
 
-//line web.qtpl:20
+//line app/vmalert/web.qtpl:20
 		qw422016.N().S(`
         	<a href="`)
-//line web.qtpl:21
+//line app/vmalert/web.qtpl:21
 		qw422016.E().S(p)
-//line web.qtpl:21
+//line app/vmalert/web.qtpl:21
 		qw422016.N().S(`">`)
-//line web.qtpl:21
+//line app/vmalert/web.qtpl:21
 		qw422016.E().S(p)
-//line web.qtpl:21
+//line app/vmalert/web.qtpl:21
 		qw422016.N().S(`</a> - `)
-//line web.qtpl:21
+//line app/vmalert/web.qtpl:21
 		qw422016.E().S(doc)
-//line web.qtpl:21
+//line app/vmalert/web.qtpl:21
 		qw422016.N().S(`<br/>
         `)
-//line web.qtpl:22
+//line app/vmalert/web.qtpl:22
 	}
-//line web.qtpl:22
+//line app/vmalert/web.qtpl:22
 	qw422016.N().S(`
     </p>
     `)
-//line web.qtpl:24
+//line app/vmalert/web.qtpl:24
 	tpl.StreamFooter(qw422016)
-//line web.qtpl:24
+//line app/vmalert/web.qtpl:24
 	qw422016.N().S(`
 `)
-//line web.qtpl:25
+//line app/vmalert/web.qtpl:25
 }
 
-//line web.qtpl:25
+//line app/vmalert/web.qtpl:25
 func WriteWelcome(qq422016 qtio422016.Writer) {
-//line web.qtpl:25
+//line app/vmalert/web.qtpl:25
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line web.qtpl:25
+//line app/vmalert/web.qtpl:25
 	StreamWelcome(qw422016)
-//line web.qtpl:25
+//line app/vmalert/web.qtpl:25
 	qt422016.ReleaseWriter(qw422016)
-//line web.qtpl:25
+//line app/vmalert/web.qtpl:25
 }
 
-//line web.qtpl:25
+//line app/vmalert/web.qtpl:25
 func Welcome() string {
-//line web.qtpl:25
+//line app/vmalert/web.qtpl:25
 	qb422016 := qt422016.AcquireByteBuffer()
-//line web.qtpl:25
+//line app/vmalert/web.qtpl:25
 	WriteWelcome(qb422016)
-//line web.qtpl:25
+//line app/vmalert/web.qtpl:25
 	qs422016 := string(qb422016.B)
-//line web.qtpl:25
+//line app/vmalert/web.qtpl:25
 	qt422016.ReleaseByteBuffer(qb422016)
-//line web.qtpl:25
+//line app/vmalert/web.qtpl:25
 	return qs422016
-//line web.qtpl:25
+//line app/vmalert/web.qtpl:25
 }
 
-//line web.qtpl:27
+//line app/vmalert/web.qtpl:27
 func StreamListGroups(qw422016 *qt422016.Writer, groups []APIGroup) {
-//line web.qtpl:27
+//line app/vmalert/web.qtpl:27
 	qw422016.N().S(`
     `)
-//line web.qtpl:28
+//line app/vmalert/web.qtpl:28
 	tpl.StreamHeader(qw422016, "Groups", navItems)
-//line web.qtpl:28
+//line app/vmalert/web.qtpl:28
 	qw422016.N().S(`
     `)
-//line web.qtpl:29
+//line app/vmalert/web.qtpl:29
 	if len(groups) > 0 {
-//line web.qtpl:29
+//line app/vmalert/web.qtpl:29
 		qw422016.N().S(`
         `)
-//line web.qtpl:31
+//line app/vmalert/web.qtpl:31
 		rOk := make(map[string]int)
 		rNotOk := make(map[string]int)
 		for _, g := range groups {
@@ -131,111 +131,111 @@ func StreamListGroups(qw422016 *qt422016.Writer, groups []APIGroup) {
 			}
 		}
 
-//line web.qtpl:42
+//line app/vmalert/web.qtpl:42
 		qw422016.N().S(`
          <a class="btn btn-primary" role="button" onclick="collapseAll()">Collapse All</a>
          <a class="btn btn-primary" role="button" onclick="expandAll()">Expand All</a>
         `)
-//line web.qtpl:45
+//line app/vmalert/web.qtpl:45
 		for _, g := range groups {
-//line web.qtpl:45
+//line app/vmalert/web.qtpl:45
 			qw422016.N().S(`
               <div class="group-heading`)
-//line web.qtpl:46
+//line app/vmalert/web.qtpl:46
 			if rNotOk[g.Name] > 0 {
-//line web.qtpl:46
+//line app/vmalert/web.qtpl:46
 				qw422016.N().S(` alert-danger`)
-//line web.qtpl:46
+//line app/vmalert/web.qtpl:46
 			}
-//line web.qtpl:46
+//line app/vmalert/web.qtpl:46
 			qw422016.N().S(`"  data-bs-target="rules-`)
-//line web.qtpl:46
+//line app/vmalert/web.qtpl:46
 			qw422016.E().S(g.ID)
-//line web.qtpl:46
+//line app/vmalert/web.qtpl:46
 			qw422016.N().S(`">
                 <span class="anchor" id="group-`)
-//line web.qtpl:47
+//line app/vmalert/web.qtpl:47
 			qw422016.E().S(g.ID)
-//line web.qtpl:47
+//line app/vmalert/web.qtpl:47
 			qw422016.N().S(`"></span>
                 <a href="#group-`)
-//line web.qtpl:48
+//line app/vmalert/web.qtpl:48
 			qw422016.E().S(g.ID)
-//line web.qtpl:48
+//line app/vmalert/web.qtpl:48
 			qw422016.N().S(`">`)
-//line web.qtpl:48
+//line app/vmalert/web.qtpl:48
 			qw422016.E().S(g.Name)
-//line web.qtpl:48
+//line app/vmalert/web.qtpl:48
 			if g.Type != "prometheus" {
-//line web.qtpl:48
+//line app/vmalert/web.qtpl:48
 				qw422016.N().S(` (`)
-//line web.qtpl:48
+//line app/vmalert/web.qtpl:48
 				qw422016.E().S(g.Type)
-//line web.qtpl:48
+//line app/vmalert/web.qtpl:48
 				qw422016.N().S(`)`)
-//line web.qtpl:48
+//line app/vmalert/web.qtpl:48
 			}
-//line web.qtpl:48
+//line app/vmalert/web.qtpl:48
 			qw422016.N().S(` (every `)
-//line web.qtpl:48
+//line app/vmalert/web.qtpl:48
 			qw422016.N().FPrec(g.Interval, 0)
-//line web.qtpl:48
+//line app/vmalert/web.qtpl:48
 			qw422016.N().S(`s)</a>
                  `)
-//line web.qtpl:49
+//line app/vmalert/web.qtpl:49
 			if rNotOk[g.Name] > 0 {
-//line web.qtpl:49
+//line app/vmalert/web.qtpl:49
 				qw422016.N().S(`<span class="badge bg-danger" title="Number of rules with status Error">`)
-//line web.qtpl:49
+//line app/vmalert/web.qtpl:49
 				qw422016.N().D(rNotOk[g.Name])
-//line web.qtpl:49
+//line app/vmalert/web.qtpl:49
 				qw422016.N().S(`</span> `)
-//line web.qtpl:49
+//line app/vmalert/web.qtpl:49
 			}
-//line web.qtpl:49
+//line app/vmalert/web.qtpl:49
 			qw422016.N().S(`
                 <span class="badge bg-success" title="Number of rules withs status Ok">`)
-//line web.qtpl:50
+//line app/vmalert/web.qtpl:50
 			qw422016.N().D(rOk[g.Name])
-//line web.qtpl:50
+//line app/vmalert/web.qtpl:50
 			qw422016.N().S(`</span>
                 <p class="fs-6 fw-lighter">`)
-//line web.qtpl:51
+//line app/vmalert/web.qtpl:51
 			qw422016.E().S(g.File)
-//line web.qtpl:51
+//line app/vmalert/web.qtpl:51
 			qw422016.N().S(`</p>
                 `)
-//line web.qtpl:52
+//line app/vmalert/web.qtpl:52
 			if len(g.Params) > 0 {
-//line web.qtpl:52
+//line app/vmalert/web.qtpl:52
 				qw422016.N().S(`
                     <div class="fs-6 fw-lighter">Extra params
                     `)
-//line web.qtpl:54
+//line app/vmalert/web.qtpl:54
 				for _, param := range g.Params {
-//line web.qtpl:54
+//line app/vmalert/web.qtpl:54
 					qw422016.N().S(`
                             <span class="float-left badge bg-primary">`)
-//line web.qtpl:55
+//line app/vmalert/web.qtpl:55
 					qw422016.E().S(param)
-//line web.qtpl:55
+//line app/vmalert/web.qtpl:55
 					qw422016.N().S(`</span>
                     `)
-//line web.qtpl:56
+//line app/vmalert/web.qtpl:56
 				}
-//line web.qtpl:56
+//line app/vmalert/web.qtpl:56
 				qw422016.N().S(`
                     </div>
                 `)
-//line web.qtpl:58
+//line app/vmalert/web.qtpl:58
 			}
-//line web.qtpl:58
+//line app/vmalert/web.qtpl:58
 			qw422016.N().S(`
             </div>
             <div class="collapse" id="rules-`)
-//line web.qtpl:60
+//line app/vmalert/web.qtpl:60
 			qw422016.E().S(g.ID)
-//line web.qtpl:60
+//line app/vmalert/web.qtpl:60
 			qw422016.N().S(`">
                 <table class="table table-striped table-hover table-sm">
                     <thead>
@@ -248,230 +248,230 @@ func StreamListGroups(qw422016 *qt422016.Writer, groups []APIGroup) {
                     </thead>
                     <tbody>
                     `)
-//line web.qtpl:71
+//line app/vmalert/web.qtpl:71
 			for _, r := range g.Rules {
-//line web.qtpl:71
+//line app/vmalert/web.qtpl:71
 				qw422016.N().S(`
                         <tr`)
-//line web.qtpl:72
+//line app/vmalert/web.qtpl:72
 				if r.LastError != "" {
-//line web.qtpl:72
+//line app/vmalert/web.qtpl:72
 					qw422016.N().S(` class="alert-danger"`)
-//line web.qtpl:72
+//line app/vmalert/web.qtpl:72
 				}
-//line web.qtpl:72
+//line app/vmalert/web.qtpl:72
 				qw422016.N().S(`>
                             <td>
                                 `)
-//line web.qtpl:74
+//line app/vmalert/web.qtpl:74
 				if r.Type == "alerting" {
-//line web.qtpl:74
+//line app/vmalert/web.qtpl:74
 					qw422016.N().S(`
                                 <b>alert:</b> (for: `)
-//line web.qtpl:75
+//line app/vmalert/web.qtpl:75
 					qw422016.E().V(r.Duration)
-//line web.qtpl:75
+//line app/vmalert/web.qtpl:75
 					qw422016.N().S(`)
                                 `)
-//line web.qtpl:76
+//line app/vmalert/web.qtpl:76
 				} else {
-//line web.qtpl:76
+//line app/vmalert/web.qtpl:76
 					qw422016.N().S(`
                                 <b>record:</b> `)
-//line web.qtpl:77
+//line app/vmalert/web.qtpl:77
 					qw422016.E().S(r.Name)
-//line web.qtpl:77
+//line app/vmalert/web.qtpl:77
 					qw422016.N().S(`
                                 `)
-//line web.qtpl:78
+//line app/vmalert/web.qtpl:78
 				}
-//line web.qtpl:78
+//line app/vmalert/web.qtpl:78
 				qw422016.N().S(`
                                 <br>
                                 <code><pre class="text-wrap">`)
-//line web.qtpl:80
+//line app/vmalert/web.qtpl:80
 				qw422016.E().S(r.Query)
-//line web.qtpl:80
+//line app/vmalert/web.qtpl:80
 				qw422016.N().S(`</pre></code><br>
                                 `)
-//line web.qtpl:81
+//line app/vmalert/web.qtpl:81
 				if len(r.Labels) > 0 {
-//line web.qtpl:81
+//line app/vmalert/web.qtpl:81
 					qw422016.N().S(` <b>Labels:</b>`)
-//line web.qtpl:81
+//line app/vmalert/web.qtpl:81
 				}
-//line web.qtpl:81
+//line app/vmalert/web.qtpl:81
 				qw422016.N().S(`
                                 `)
-//line web.qtpl:82
+//line app/vmalert/web.qtpl:82
 				for k, v := range r.Labels {
-//line web.qtpl:82
+//line app/vmalert/web.qtpl:82
 					qw422016.N().S(`
                                         <span class="ms-1 badge bg-primary">`)
-//line web.qtpl:83
+//line app/vmalert/web.qtpl:83
 					qw422016.E().S(k)
-//line web.qtpl:83
+//line app/vmalert/web.qtpl:83
 					qw422016.N().S(`=`)
-//line web.qtpl:83
+//line app/vmalert/web.qtpl:83
 					qw422016.E().S(v)
-//line web.qtpl:83
+//line app/vmalert/web.qtpl:83
 					qw422016.N().S(`</span>
                                 `)
-//line web.qtpl:84
+//line app/vmalert/web.qtpl:84
 				}
-//line web.qtpl:84
+//line app/vmalert/web.qtpl:84
 				qw422016.N().S(`
                             </td>
                             <td><div class="error-cell">`)
-//line web.qtpl:86
+//line app/vmalert/web.qtpl:86
 				qw422016.E().S(r.LastError)
-//line web.qtpl:86
+//line app/vmalert/web.qtpl:86
 				qw422016.N().S(`</div></td>
                             <td>`)
-//line web.qtpl:87
+//line app/vmalert/web.qtpl:87
 				qw422016.N().D(r.LastSamples)
-//line web.qtpl:87
+//line app/vmalert/web.qtpl:87
 				qw422016.N().S(`</td>
                             <td>`)
-//line web.qtpl:88
+//line app/vmalert/web.qtpl:88
 				qw422016.N().FPrec(time.Since(r.LastEvaluation).Seconds(), 3)
-//line web.qtpl:88
+//line app/vmalert/web.qtpl:88
 				qw422016.N().S(`s ago</td>
                         </tr>
                     `)
-//line web.qtpl:90
+//line app/vmalert/web.qtpl:90
 			}
-//line web.qtpl:90
+//line app/vmalert/web.qtpl:90
 			qw422016.N().S(`
                  </tbody>
                 </table>
             </div>
         `)
-//line web.qtpl:94
+//line app/vmalert/web.qtpl:94
 		}
-//line web.qtpl:94
+//line app/vmalert/web.qtpl:94
 		qw422016.N().S(`
 
     `)
-//line web.qtpl:96
+//line app/vmalert/web.qtpl:96
 	} else {
-//line web.qtpl:96
+//line app/vmalert/web.qtpl:96
 		qw422016.N().S(`
         <div>
             <p>No items...</p>
         </div>
     `)
-//line web.qtpl:100
+//line app/vmalert/web.qtpl:100
 	}
-//line web.qtpl:100
+//line app/vmalert/web.qtpl:100
 	qw422016.N().S(`
 
     `)
-//line web.qtpl:102
+//line app/vmalert/web.qtpl:102
 	tpl.StreamFooter(qw422016)
-//line web.qtpl:102
+//line app/vmalert/web.qtpl:102
 	qw422016.N().S(`
 
 `)
-//line web.qtpl:104
+//line app/vmalert/web.qtpl:104
 }
 
-//line web.qtpl:104
+//line app/vmalert/web.qtpl:104
 func WriteListGroups(qq422016 qtio422016.Writer, groups []APIGroup) {
-//line web.qtpl:104
+//line app/vmalert/web.qtpl:104
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line web.qtpl:104
+//line app/vmalert/web.qtpl:104
 	StreamListGroups(qw422016, groups)
-//line web.qtpl:104
+//line app/vmalert/web.qtpl:104
 	qt422016.ReleaseWriter(qw422016)
-//line web.qtpl:104
+//line app/vmalert/web.qtpl:104
 }
 
-//line web.qtpl:104
+//line app/vmalert/web.qtpl:104
 func ListGroups(groups []APIGroup) string {
-//line web.qtpl:104
+//line app/vmalert/web.qtpl:104
 	qb422016 := qt422016.AcquireByteBuffer()
-//line web.qtpl:104
+//line app/vmalert/web.qtpl:104
 	WriteListGroups(qb422016, groups)
-//line web.qtpl:104
+//line app/vmalert/web.qtpl:104
 	qs422016 := string(qb422016.B)
-//line web.qtpl:104
+//line app/vmalert/web.qtpl:104
 	qt422016.ReleaseByteBuffer(qb422016)
-//line web.qtpl:104
+//line app/vmalert/web.qtpl:104
 	return qs422016
-//line web.qtpl:104
+//line app/vmalert/web.qtpl:104
 }
 
-//line web.qtpl:107
+//line app/vmalert/web.qtpl:107
 func StreamListAlerts(qw422016 *qt422016.Writer, pathPrefix string, groupAlerts []GroupAlerts) {
-//line web.qtpl:107
+//line app/vmalert/web.qtpl:107
 	qw422016.N().S(`
     `)
-//line web.qtpl:108
+//line app/vmalert/web.qtpl:108
 	tpl.StreamHeader(qw422016, "Alerts", navItems)
-//line web.qtpl:108
+//line app/vmalert/web.qtpl:108
 	qw422016.N().S(`
     `)
-//line web.qtpl:109
+//line app/vmalert/web.qtpl:109
 	if len(groupAlerts) > 0 {
-//line web.qtpl:109
+//line app/vmalert/web.qtpl:109
 		qw422016.N().S(`
          <a class="btn btn-primary" role="button" onclick="collapseAll()">Collapse All</a>
          <a class="btn btn-primary" role="button" onclick="expandAll()">Expand All</a>
          `)
-//line web.qtpl:112
+//line app/vmalert/web.qtpl:112
 		for _, ga := range groupAlerts {
-//line web.qtpl:112
+//line app/vmalert/web.qtpl:112
 			qw422016.N().S(`
             `)
-//line web.qtpl:113
+//line app/vmalert/web.qtpl:113
 			g := ga.Group
 
-//line web.qtpl:113
+//line app/vmalert/web.qtpl:113
 			qw422016.N().S(`
             <div class="group-heading alert-danger" data-bs-target="rules-`)
-//line web.qtpl:114
+//line app/vmalert/web.qtpl:114
 			qw422016.E().S(g.ID)
-//line web.qtpl:114
+//line app/vmalert/web.qtpl:114
 			qw422016.N().S(`">
                 <span class="anchor" id="group-`)
-//line web.qtpl:115
+//line app/vmalert/web.qtpl:115
 			qw422016.E().S(g.ID)
-//line web.qtpl:115
+//line app/vmalert/web.qtpl:115
 			qw422016.N().S(`"></span>
                 <a href="#group-`)
-//line web.qtpl:116
+//line app/vmalert/web.qtpl:116
 			qw422016.E().S(g.ID)
-//line web.qtpl:116
+//line app/vmalert/web.qtpl:116
 			qw422016.N().S(`">`)
-//line web.qtpl:116
+//line app/vmalert/web.qtpl:116
 			qw422016.E().S(g.Name)
-//line web.qtpl:116
+//line app/vmalert/web.qtpl:116
 			if g.Type != "prometheus" {
-//line web.qtpl:116
+//line app/vmalert/web.qtpl:116
 				qw422016.N().S(` (`)
-//line web.qtpl:116
+//line app/vmalert/web.qtpl:116
 				qw422016.E().S(g.Type)
-//line web.qtpl:116
+//line app/vmalert/web.qtpl:116
 				qw422016.N().S(`)`)
-//line web.qtpl:116
+//line app/vmalert/web.qtpl:116
 			}
-//line web.qtpl:116
+//line app/vmalert/web.qtpl:116
 			qw422016.N().S(`</a>
                 <span class="badge bg-danger" title="Number of active alerts">`)
-//line web.qtpl:117
+//line app/vmalert/web.qtpl:117
 			qw422016.N().D(len(ga.Alerts))
-//line web.qtpl:117
+//line app/vmalert/web.qtpl:117
 			qw422016.N().S(`</span>
                 <br>
                 <p class="fs-6 fw-lighter">`)
-//line web.qtpl:119
+//line app/vmalert/web.qtpl:119
 			qw422016.E().S(g.File)
-//line web.qtpl:119
+//line app/vmalert/web.qtpl:119
 			qw422016.N().S(`</p>
             </div>
             `)
-//line web.qtpl:122
+//line app/vmalert/web.qtpl:122
 			var keys []string
 			alertsByRule := make(map[string][]*APIAlert)
 			for _, alert := range ga.Alerts {
@@ -482,20 +482,20 @@ func StreamListAlerts(qw422016 *qt422016.Writer, pathPrefix string, groupAlerts
 			}
 			sort.Strings(keys)
 
-//line web.qtpl:131
+//line app/vmalert/web.qtpl:131
 			qw422016.N().S(`
             <div class="collapse" id="rules-`)
-//line web.qtpl:132
+//line app/vmalert/web.qtpl:132
 			qw422016.E().S(g.ID)
-//line web.qtpl:132
+//line app/vmalert/web.qtpl:132
 			qw422016.N().S(`">
                 `)
-//line web.qtpl:133
+//line app/vmalert/web.qtpl:133
 			for _, ruleID := range keys {
-//line web.qtpl:133
+//line app/vmalert/web.qtpl:133
 				qw422016.N().S(`
                     `)
-//line web.qtpl:135
+//line app/vmalert/web.qtpl:135
 				defaultAR := alertsByRule[ruleID][0]
 				var labelKeys []string
 				for k := range defaultAR.Labels {
@@ -503,28 +503,28 @@ func StreamListAlerts(qw422016 *qt422016.Writer, pathPrefix string, groupAlerts
 				}
 				sort.Strings(labelKeys)
 
-//line web.qtpl:141
+//line app/vmalert/web.qtpl:141
 				qw422016.N().S(`
                     <br>
                     <b>alert:</b> `)
-//line web.qtpl:143
+//line app/vmalert/web.qtpl:143
 				qw422016.E().S(defaultAR.Name)
-//line web.qtpl:143
+//line app/vmalert/web.qtpl:143
 				qw422016.N().S(` (`)
-//line web.qtpl:143
+//line app/vmalert/web.qtpl:143
 				qw422016.N().D(len(alertsByRule[ruleID]))
-//line web.qtpl:143
+//line app/vmalert/web.qtpl:143
 				qw422016.N().S(`)
                      | <span><a target="_blank" href="`)
-//line web.qtpl:144
+//line app/vmalert/web.qtpl:144
 				qw422016.E().S(defaultAR.SourceLink)
-//line web.qtpl:144
+//line app/vmalert/web.qtpl:144
 				qw422016.N().S(`">Source</a></span>
                     <br>
                     <b>expr:</b><code><pre>`)
-//line web.qtpl:146
+//line app/vmalert/web.qtpl:146
 				qw422016.E().S(defaultAR.Expression)
-//line web.qtpl:146
+//line app/vmalert/web.qtpl:146
 				qw422016.N().S(`</pre></code>
                     <table class="table table-striped table-hover table-sm">
                         <thead>
@@ -538,204 +538,204 @@ func StreamListAlerts(qw422016 *qt422016.Writer, pathPrefix string, groupAlerts
                         </thead>
                         <tbody>
                         `)
-//line web.qtpl:158
+//line app/vmalert/web.qtpl:158
 				for _, ar := range alertsByRule[ruleID] {
-//line web.qtpl:158
+//line app/vmalert/web.qtpl:158
 					qw422016.N().S(`
                             <tr>
                                 <td>
                                     `)
-//line web.qtpl:161
+//line app/vmalert/web.qtpl:161
 					for _, k := range labelKeys {
-//line web.qtpl:161
+//line app/vmalert/web.qtpl:161
 						qw422016.N().S(`
                                         <span class="ms-1 badge bg-primary">`)
-//line web.qtpl:162
+//line app/vmalert/web.qtpl:162
 						qw422016.E().S(k)
-//line web.qtpl:162
+//line app/vmalert/web.qtpl:162
 						qw422016.N().S(`=`)
-//line web.qtpl:162
+//line app/vmalert/web.qtpl:162
 						qw422016.E().S(ar.Labels[k])
-//line web.qtpl:162
+//line app/vmalert/web.qtpl:162
 						qw422016.N().S(`</span>
                                     `)
-//line web.qtpl:163
+//line app/vmalert/web.qtpl:163
 					}
-//line web.qtpl:163
+//line app/vmalert/web.qtpl:163
 					qw422016.N().S(`
                                 </td>
                                 <td>`)
-//line web.qtpl:165
+//line app/vmalert/web.qtpl:165
 					streambadgeState(qw422016, ar.State)
-//line web.qtpl:165
+//line app/vmalert/web.qtpl:165
 					qw422016.N().S(`</td>
                                 <td>
                                     `)
-//line web.qtpl:167
+//line app/vmalert/web.qtpl:167
 					qw422016.E().S(ar.ActiveAt.Format("2006-01-02T15:04:05Z07:00"))
-//line web.qtpl:167
+//line app/vmalert/web.qtpl:167
 					qw422016.N().S(`
                                     `)
-//line web.qtpl:168
+//line app/vmalert/web.qtpl:168
 					if ar.Restored {
-//line web.qtpl:168
+//line app/vmalert/web.qtpl:168
 						streambadgeRestored(qw422016)
-//line web.qtpl:168
+//line app/vmalert/web.qtpl:168
 					}
-//line web.qtpl:168
+//line app/vmalert/web.qtpl:168
 					qw422016.N().S(`
                                 </td>
                                 <td>`)
-//line web.qtpl:170
+//line app/vmalert/web.qtpl:170
 					qw422016.E().S(ar.Value)
-//line web.qtpl:170
+//line app/vmalert/web.qtpl:170
 					qw422016.N().S(`</td>
                                 <td>
                                     <a href="`)
-//line web.qtpl:172
+//line app/vmalert/web.qtpl:172
 					qw422016.E().S(path.Join(pathPrefix, g.ID, ar.ID, "status"))
-//line web.qtpl:172
+//line app/vmalert/web.qtpl:172
 					qw422016.N().S(`">Details</a>
                                 </td>
                             </tr>
                         `)
-//line web.qtpl:175
+//line app/vmalert/web.qtpl:175
 				}
-//line web.qtpl:175
+//line app/vmalert/web.qtpl:175
 				qw422016.N().S(`
                      </tbody>
                     </table>
                 `)
-//line web.qtpl:178
+//line app/vmalert/web.qtpl:178
 			}
-//line web.qtpl:178
+//line app/vmalert/web.qtpl:178
 			qw422016.N().S(`
             </div>
             <br>
         `)
-//line web.qtpl:181
+//line app/vmalert/web.qtpl:181
 		}
-//line web.qtpl:181
+//line app/vmalert/web.qtpl:181
 		qw422016.N().S(`
 
     `)
-//line web.qtpl:183
+//line app/vmalert/web.qtpl:183
 	} else {
-//line web.qtpl:183
+//line app/vmalert/web.qtpl:183
 		qw422016.N().S(`
         <div>
             <p>No items...</p>
         </div>
     `)
-//line web.qtpl:187
+//line app/vmalert/web.qtpl:187
 	}
-//line web.qtpl:187
+//line app/vmalert/web.qtpl:187
 	qw422016.N().S(`
 
     `)
-//line web.qtpl:189
+//line app/vmalert/web.qtpl:189
 	tpl.StreamFooter(qw422016)
-//line web.qtpl:189
+//line app/vmalert/web.qtpl:189
 	qw422016.N().S(`
 
 `)
-//line web.qtpl:191
+//line app/vmalert/web.qtpl:191
 }
 
-//line web.qtpl:191
+//line app/vmalert/web.qtpl:191
 func WriteListAlerts(qq422016 qtio422016.Writer, pathPrefix string, groupAlerts []GroupAlerts) {
-//line web.qtpl:191
+//line app/vmalert/web.qtpl:191
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line web.qtpl:191
+//line app/vmalert/web.qtpl:191
 	StreamListAlerts(qw422016, pathPrefix, groupAlerts)
-//line web.qtpl:191
+//line app/vmalert/web.qtpl:191
 	qt422016.ReleaseWriter(qw422016)
-//line web.qtpl:191
+//line app/vmalert/web.qtpl:191
 }
 
-//line web.qtpl:191
+//line app/vmalert/web.qtpl:191
 func ListAlerts(pathPrefix string, groupAlerts []GroupAlerts) string {
-//line web.qtpl:191
+//line app/vmalert/web.qtpl:191
 	qb422016 := qt422016.AcquireByteBuffer()
-//line web.qtpl:191
+//line app/vmalert/web.qtpl:191
 	WriteListAlerts(qb422016, pathPrefix, groupAlerts)
-//line web.qtpl:191
+//line app/vmalert/web.qtpl:191
 	qs422016 := string(qb422016.B)
-//line web.qtpl:191
+//line app/vmalert/web.qtpl:191
 	qt422016.ReleaseByteBuffer(qb422016)
-//line web.qtpl:191
+//line app/vmalert/web.qtpl:191
 	return qs422016
-//line web.qtpl:191
+//line app/vmalert/web.qtpl:191
 }
 
-//line web.qtpl:193
+//line app/vmalert/web.qtpl:193
 func StreamListTargets(qw422016 *qt422016.Writer, targets map[notifier.TargetType][]notifier.Target) {
-//line web.qtpl:193
+//line app/vmalert/web.qtpl:193
 	qw422016.N().S(`
     `)
-//line web.qtpl:194
+//line app/vmalert/web.qtpl:194
 	tpl.StreamHeader(qw422016, "Notifiers", navItems)
-//line web.qtpl:194
+//line app/vmalert/web.qtpl:194
 	qw422016.N().S(`
     `)
-//line web.qtpl:195
+//line app/vmalert/web.qtpl:195
 	if len(targets) > 0 {
-//line web.qtpl:195
+//line app/vmalert/web.qtpl:195
 		qw422016.N().S(`
          <a class="btn btn-primary" role="button" onclick="collapseAll()">Collapse All</a>
          <a class="btn btn-primary" role="button" onclick="expandAll()">Expand All</a>
 
          `)
-//line web.qtpl:200
+//line app/vmalert/web.qtpl:200
 		var keys []string
 		for key := range targets {
 			keys = append(keys, string(key))
 		}
 		sort.Strings(keys)
 
-//line web.qtpl:205
+//line app/vmalert/web.qtpl:205
 		qw422016.N().S(`
 
          `)
-//line web.qtpl:207
+//line app/vmalert/web.qtpl:207
 		for i := range keys {
-//line web.qtpl:207
+//line app/vmalert/web.qtpl:207
 			qw422016.N().S(`
            `)
-//line web.qtpl:208
+//line app/vmalert/web.qtpl:208
 			typeK, ns := keys[i], targets[notifier.TargetType(keys[i])]
 			count := len(ns)
 
-//line web.qtpl:210
+//line app/vmalert/web.qtpl:210
 			qw422016.N().S(`
            <div class="group-heading data-bs-target="rules-`)
-//line web.qtpl:211
+//line app/vmalert/web.qtpl:211
 			qw422016.E().S(typeK)
-//line web.qtpl:211
+//line app/vmalert/web.qtpl:211
 			qw422016.N().S(`">
              <span class="anchor" id="notifiers-`)
-//line web.qtpl:212
+//line app/vmalert/web.qtpl:212
 			qw422016.E().S(typeK)
-//line web.qtpl:212
+//line app/vmalert/web.qtpl:212
 			qw422016.N().S(`"></span>
              <a href="#notifiers-`)
-//line web.qtpl:213
+//line app/vmalert/web.qtpl:213
 			qw422016.E().S(typeK)
-//line web.qtpl:213
+//line app/vmalert/web.qtpl:213
 			qw422016.N().S(`">`)
-//line web.qtpl:213
+//line app/vmalert/web.qtpl:213
 			qw422016.E().S(typeK)
-//line web.qtpl:213
+//line app/vmalert/web.qtpl:213
 			qw422016.N().S(` (`)
-//line web.qtpl:213
+//line app/vmalert/web.qtpl:213
 			qw422016.N().D(count)
-//line web.qtpl:213
+//line app/vmalert/web.qtpl:213
 			qw422016.N().S(`)</a>
          </div>
          <div class="collapse show" id="notifiers-`)
-//line web.qtpl:215
+//line app/vmalert/web.qtpl:215
 			qw422016.E().S(typeK)
-//line web.qtpl:215
+//line app/vmalert/web.qtpl:215
 			qw422016.N().S(`">
              <table class="table table-striped table-hover table-sm">
                  <thead>
@@ -746,113 +746,113 @@ func StreamListTargets(qw422016 *qt422016.Writer, targets map[notifier.TargetTyp
                  </thead>
                  <tbody>
                  `)
-//line web.qtpl:224
+//line app/vmalert/web.qtpl:224
 			for _, n := range ns {
-//line web.qtpl:224
+//line app/vmalert/web.qtpl:224
 				qw422016.N().S(`
                      <tr>
                          <td>
                               `)
-//line web.qtpl:227
+//line app/vmalert/web.qtpl:227
 				for _, l := range n.Labels {
-//line web.qtpl:227
+//line app/vmalert/web.qtpl:227
 					qw422016.N().S(`
                                       <span class="ms-1 badge bg-primary">`)
-//line web.qtpl:228
+//line app/vmalert/web.qtpl:228
 					qw422016.E().S(l.Name)
-//line web.qtpl:228
+//line app/vmalert/web.qtpl:228
 					qw422016.N().S(`=`)
-//line web.qtpl:228
+//line app/vmalert/web.qtpl:228
 					qw422016.E().S(l.Value)
-//line web.qtpl:228
+//line app/vmalert/web.qtpl:228
 					qw422016.N().S(`</span>
                               `)
-//line web.qtpl:229
+//line app/vmalert/web.qtpl:229
 				}
-//line web.qtpl:229
+//line app/vmalert/web.qtpl:229
 				qw422016.N().S(`
                           </td>
                          <td>`)
-//line web.qtpl:231
+//line app/vmalert/web.qtpl:231
 				qw422016.E().S(n.Notifier.Addr())
-//line web.qtpl:231
+//line app/vmalert/web.qtpl:231
 				qw422016.N().S(`</td>
                      </tr>
                  `)
-//line web.qtpl:233
+//line app/vmalert/web.qtpl:233
 			}
-//line web.qtpl:233
+//line app/vmalert/web.qtpl:233
 			qw422016.N().S(`
               </tbody>
              </table>
          </div>
      `)
-//line web.qtpl:237
+//line app/vmalert/web.qtpl:237
 		}
-//line web.qtpl:237
+//line app/vmalert/web.qtpl:237
 		qw422016.N().S(`
 
     `)
-//line web.qtpl:239
+//line app/vmalert/web.qtpl:239
 	} else {
-//line web.qtpl:239
+//line app/vmalert/web.qtpl:239
 		qw422016.N().S(`
         <div>
             <p>No items...</p>
         </div>
     `)
-//line web.qtpl:243
+//line app/vmalert/web.qtpl:243
 	}
-//line web.qtpl:243
+//line app/vmalert/web.qtpl:243
 	qw422016.N().S(`
 
     `)
-//line web.qtpl:245
+//line app/vmalert/web.qtpl:245
 	tpl.StreamFooter(qw422016)
-//line web.qtpl:245
+//line app/vmalert/web.qtpl:245
 	qw422016.N().S(`
 
 `)
-//line web.qtpl:247
+//line app/vmalert/web.qtpl:247
 }
 
-//line web.qtpl:247
+//line app/vmalert/web.qtpl:247
 func WriteListTargets(qq422016 qtio422016.Writer, targets map[notifier.TargetType][]notifier.Target) {
-//line web.qtpl:247
+//line app/vmalert/web.qtpl:247
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line web.qtpl:247
+//line app/vmalert/web.qtpl:247
 	StreamListTargets(qw422016, targets)
-//line web.qtpl:247
+//line app/vmalert/web.qtpl:247
 	qt422016.ReleaseWriter(qw422016)
-//line web.qtpl:247
+//line app/vmalert/web.qtpl:247
 }
 
-//line web.qtpl:247
+//line app/vmalert/web.qtpl:247
 func ListTargets(targets map[notifier.TargetType][]notifier.Target) string {
-//line web.qtpl:247
+//line app/vmalert/web.qtpl:247
 	qb422016 := qt422016.AcquireByteBuffer()
-//line web.qtpl:247
+//line app/vmalert/web.qtpl:247
 	WriteListTargets(qb422016, targets)
-//line web.qtpl:247
+//line app/vmalert/web.qtpl:247
 	qs422016 := string(qb422016.B)
-//line web.qtpl:247
+//line app/vmalert/web.qtpl:247
 	qt422016.ReleaseByteBuffer(qb422016)
-//line web.qtpl:247
+//line app/vmalert/web.qtpl:247
 	return qs422016
-//line web.qtpl:247
+//line app/vmalert/web.qtpl:247
 }
 
-//line web.qtpl:249
+//line app/vmalert/web.qtpl:249
 func StreamAlert(qw422016 *qt422016.Writer, pathPrefix string, alert *APIAlert) {
-//line web.qtpl:249
+//line app/vmalert/web.qtpl:249
 	qw422016.N().S(`
     `)
-//line web.qtpl:250
+//line app/vmalert/web.qtpl:250
 	tpl.StreamHeader(qw422016, "", navItems)
-//line web.qtpl:250
+//line app/vmalert/web.qtpl:250
 	qw422016.N().S(`
     `)
-//line web.qtpl:252
+//line app/vmalert/web.qtpl:252
 	var labelKeys []string
 	for k := range alert.Labels {
 		labelKeys = append(labelKeys, k)
@@ -865,28 +865,28 @@ func StreamAlert(qw422016 *qt422016.Writer, pathPrefix string, alert *APIAlert)
 	}
 	sort.Strings(annotationKeys)
 
-//line web.qtpl:263
+//line app/vmalert/web.qtpl:263
 	qw422016.N().S(`
     <div class="display-6 pb-3 mb-3">`)
-//line web.qtpl:264
+//line app/vmalert/web.qtpl:264
 	qw422016.E().S(alert.Name)
-//line web.qtpl:264
+//line app/vmalert/web.qtpl:264
 	qw422016.N().S(`<span class="ms-2 badge `)
-//line web.qtpl:264
+//line app/vmalert/web.qtpl:264
 	if alert.State == "firing" {
-//line web.qtpl:264
+//line app/vmalert/web.qtpl:264
 		qw422016.N().S(`bg-danger`)
-//line web.qtpl:264
+//line app/vmalert/web.qtpl:264
 	} else {
-//line web.qtpl:264
+//line app/vmalert/web.qtpl:264
 		qw422016.N().S(` bg-warning text-dark`)
-//line web.qtpl:264
+//line app/vmalert/web.qtpl:264
 	}
-//line web.qtpl:264
+//line app/vmalert/web.qtpl:264
 	qw422016.N().S(`">`)
-//line web.qtpl:264
+//line app/vmalert/web.qtpl:264
 	qw422016.E().S(alert.State)
-//line web.qtpl:264
+//line app/vmalert/web.qtpl:264
 	qw422016.N().S(`</span></div>
     <div class="container border-bottom p-2">
       <div class="row">
@@ -895,9 +895,9 @@ func StreamAlert(qw422016 *qt422016.Writer, pathPrefix string, alert *APIAlert)
         </div>
         <div class="col">
           `)
-//line web.qtpl:271
+//line app/vmalert/web.qtpl:271
 	qw422016.E().S(alert.ActiveAt.Format("2006-01-02T15:04:05Z07:00"))
-//line web.qtpl:271
+//line app/vmalert/web.qtpl:271
 	qw422016.N().S(`
         </div>
       </div>
@@ -909,9 +909,9 @@ func StreamAlert(qw422016 *qt422016.Writer, pathPrefix string, alert *APIAlert)
         </div>
         <div class="col">
           <code><pre>`)
-//line web.qtpl:281
+//line app/vmalert/web.qtpl:281
 	qw422016.E().S(alert.Expression)
-//line web.qtpl:281
+//line app/vmalert/web.qtpl:281
 	qw422016.N().S(`</pre></code>
         </div>
       </div>
@@ -923,23 +923,23 @@ func StreamAlert(qw422016 *qt422016.Writer, pathPrefix string, alert *APIAlert)
         </div>
         <div class="col">
            `)
-//line web.qtpl:291
+//line app/vmalert/web.qtpl:291
 	for _, k := range labelKeys {
-//line web.qtpl:291
+//line app/vmalert/web.qtpl:291
 		qw422016.N().S(`
                 <span class="m-1 badge bg-primary">`)
-//line web.qtpl:292
+//line app/vmalert/web.qtpl:292
 		qw422016.E().S(k)
-//line web.qtpl:292
+//line app/vmalert/web.qtpl:292
 		qw422016.N().S(`=`)
-//line web.qtpl:292
+//line app/vmalert/web.qtpl:292
 		qw422016.E().S(alert.Labels[k])
-//line web.qtpl:292
+//line app/vmalert/web.qtpl:292
 		qw422016.N().S(`</span>
           `)
-//line web.qtpl:293
+//line app/vmalert/web.qtpl:293
 	}
-//line web.qtpl:293
+//line app/vmalert/web.qtpl:293
 	qw422016.N().S(`
         </div>
       </div>
@@ -951,24 +951,24 @@ func StreamAlert(qw422016 *qt422016.Writer, pathPrefix string, alert *APIAlert)
         </div>
         <div class="col">
            `)
-//line web.qtpl:303
+//line app/vmalert/web.qtpl:303
 	for _, k := range annotationKeys {
-//line web.qtpl:303
+//line app/vmalert/web.qtpl:303
 		qw422016.N().S(`
                 <b>`)
-//line web.qtpl:304
+//line app/vmalert/web.qtpl:304
 		qw422016.E().S(k)
-//line web.qtpl:304
+//line app/vmalert/web.qtpl:304
 		qw422016.N().S(`:</b><br>
                 <p>`)
-//line web.qtpl:305
+//line app/vmalert/web.qtpl:305
 		qw422016.E().S(alert.Annotations[k])
-//line web.qtpl:305
+//line app/vmalert/web.qtpl:305
 		qw422016.N().S(`</p>
           `)
-//line web.qtpl:306
+//line app/vmalert/web.qtpl:306
 	}
-//line web.qtpl:306
+//line app/vmalert/web.qtpl:306
 	qw422016.N().S(`
         </div>
       </div>
@@ -980,17 +980,17 @@ func StreamAlert(qw422016 *qt422016.Writer, pathPrefix string, alert *APIAlert)
         </div>
         <div class="col">
            <a target="_blank" href="`)
-//line web.qtpl:316
+//line app/vmalert/web.qtpl:316
 	qw422016.E().S(path.Join(pathPrefix, "groups"))
-//line web.qtpl:316
+//line app/vmalert/web.qtpl:316
 	qw422016.N().S(`#group-`)
-//line web.qtpl:316
+//line app/vmalert/web.qtpl:316
 	qw422016.E().S(alert.GroupID)
-//line web.qtpl:316
+//line app/vmalert/web.qtpl:316
 	qw422016.N().S(`">`)
-//line web.qtpl:316
+//line app/vmalert/web.qtpl:316
 	qw422016.E().S(alert.GroupID)
-//line web.qtpl:316
+//line app/vmalert/web.qtpl:316
 	qw422016.N().S(`</a>
         </div>
       </div>
@@ -1002,132 +1002,132 @@ func StreamAlert(qw422016 *qt422016.Writer, pathPrefix string, alert *APIAlert)
         </div>
         <div class="col">
            <a target="_blank" href="`)
-//line web.qtpl:326
+//line app/vmalert/web.qtpl:326
 	qw422016.E().S(alert.SourceLink)
-//line web.qtpl:326
+//line app/vmalert/web.qtpl:326
 	qw422016.N().S(`">Link</a>
         </div>
       </div>
     </div>
     `)
-//line web.qtpl:330
+//line app/vmalert/web.qtpl:330
 	tpl.StreamFooter(qw422016)
-//line web.qtpl:330
+//line app/vmalert/web.qtpl:330
 	qw422016.N().S(`
 
 `)
-//line web.qtpl:332
+//line app/vmalert/web.qtpl:332
 }
 
-//line web.qtpl:332
+//line app/vmalert/web.qtpl:332
 func WriteAlert(qq422016 qtio422016.Writer, pathPrefix string, alert *APIAlert) {
-//line web.qtpl:332
+//line app/vmalert/web.qtpl:332
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line web.qtpl:332
+//line app/vmalert/web.qtpl:332
 	StreamAlert(qw422016, pathPrefix, alert)
-//line web.qtpl:332
+//line app/vmalert/web.qtpl:332
 	qt422016.ReleaseWriter(qw422016)
-//line web.qtpl:332
+//line app/vmalert/web.qtpl:332
 }
 
-//line web.qtpl:332
+//line app/vmalert/web.qtpl:332
 func Alert(pathPrefix string, alert *APIAlert) string {
-//line web.qtpl:332
+//line app/vmalert/web.qtpl:332
 	qb422016 := qt422016.AcquireByteBuffer()
-//line web.qtpl:332
+//line app/vmalert/web.qtpl:332
 	WriteAlert(qb422016, pathPrefix, alert)
-//line web.qtpl:332
+//line app/vmalert/web.qtpl:332
 	qs422016 := string(qb422016.B)
-//line web.qtpl:332
+//line app/vmalert/web.qtpl:332
 	qt422016.ReleaseByteBuffer(qb422016)
-//line web.qtpl:332
+//line app/vmalert/web.qtpl:332
 	return qs422016
-//line web.qtpl:332
+//line app/vmalert/web.qtpl:332
 }
 
-//line web.qtpl:334
+//line app/vmalert/web.qtpl:334
 func streambadgeState(qw422016 *qt422016.Writer, state string) {
-//line web.qtpl:334
+//line app/vmalert/web.qtpl:334
 	qw422016.N().S(`
 `)
-//line web.qtpl:336
+//line app/vmalert/web.qtpl:336
 	badgeClass := "bg-warning text-dark"
 	if state == "firing" {
 		badgeClass = "bg-danger"
 	}
 
-//line web.qtpl:340
+//line app/vmalert/web.qtpl:340
 	qw422016.N().S(`
 <span class="badge `)
-//line web.qtpl:341
+//line app/vmalert/web.qtpl:341
 	qw422016.E().S(badgeClass)
-//line web.qtpl:341
+//line app/vmalert/web.qtpl:341
 	qw422016.N().S(`">`)
-//line web.qtpl:341
+//line app/vmalert/web.qtpl:341
 	qw422016.E().S(state)
-//line web.qtpl:341
+//line app/vmalert/web.qtpl:341
 	qw422016.N().S(`</span>
 `)
-//line web.qtpl:342
+//line app/vmalert/web.qtpl:342
 }
 
-//line web.qtpl:342
+//line app/vmalert/web.qtpl:342
 func writebadgeState(qq422016 qtio422016.Writer, state string) {
-//line web.qtpl:342
+//line app/vmalert/web.qtpl:342
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line web.qtpl:342
+//line app/vmalert/web.qtpl:342
 	streambadgeState(qw422016, state)
-//line web.qtpl:342
+//line app/vmalert/web.qtpl:342
 	qt422016.ReleaseWriter(qw422016)
-//line web.qtpl:342
+//line app/vmalert/web.qtpl:342
 }
 
-//line web.qtpl:342
+//line app/vmalert/web.qtpl:342
 func badgeState(state string) string {
-//line web.qtpl:342
+//line app/vmalert/web.qtpl:342
 	qb422016 := qt422016.AcquireByteBuffer()
-//line web.qtpl:342
+//line app/vmalert/web.qtpl:342
 	writebadgeState(qb422016, state)
-//line web.qtpl:342
+//line app/vmalert/web.qtpl:342
 	qs422016 := string(qb422016.B)
-//line web.qtpl:342
+//line app/vmalert/web.qtpl:342
 	qt422016.ReleaseByteBuffer(qb422016)
-//line web.qtpl:342
+//line app/vmalert/web.qtpl:342
 	return qs422016
-//line web.qtpl:342
+//line app/vmalert/web.qtpl:342
 }
 
-//line web.qtpl:344
+//line app/vmalert/web.qtpl:344
 func streambadgeRestored(qw422016 *qt422016.Writer) {
-//line web.qtpl:344
+//line app/vmalert/web.qtpl:344
 	qw422016.N().S(`
 <span class="badge bg-warning text-dark" title="Alert state was restored after the service restart from remote storage">restored</span>
 `)
-//line web.qtpl:346
+//line app/vmalert/web.qtpl:346
 }
 
-//line web.qtpl:346
+//line app/vmalert/web.qtpl:346
 func writebadgeRestored(qq422016 qtio422016.Writer) {
-//line web.qtpl:346
+//line app/vmalert/web.qtpl:346
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line web.qtpl:346
+//line app/vmalert/web.qtpl:346
 	streambadgeRestored(qw422016)
-//line web.qtpl:346
+//line app/vmalert/web.qtpl:346
 	qt422016.ReleaseWriter(qw422016)
-//line web.qtpl:346
+//line app/vmalert/web.qtpl:346
 }
 
-//line web.qtpl:346
+//line app/vmalert/web.qtpl:346
 func badgeRestored() string {
-//line web.qtpl:346
+//line app/vmalert/web.qtpl:346
 	qb422016 := qt422016.AcquireByteBuffer()
-//line web.qtpl:346
+//line app/vmalert/web.qtpl:346
 	writebadgeRestored(qb422016)
-//line web.qtpl:346
+//line app/vmalert/web.qtpl:346
 	qs422016 := string(qb422016.B)
-//line web.qtpl:346
+//line app/vmalert/web.qtpl:346
 	qt422016.ReleaseByteBuffer(qb422016)
-//line web.qtpl:346
+//line app/vmalert/web.qtpl:346
 	return qs422016
-//line web.qtpl:346
+//line app/vmalert/web.qtpl:346
 }
diff --git a/app/vmstorage/main.go b/app/vmstorage/main.go
index e722d6010..3c13dd0b0 100644
--- a/app/vmstorage/main.go
+++ b/app/vmstorage/main.go
@@ -22,10 +22,11 @@ import (
 )
 
 var (
-	retentionPeriod   = flagutil.NewDuration("retentionPeriod", 1, "Data with timestamps outside the retentionPeriod is automatically deleted")
+	retentionPeriod   = flagutil.NewDuration("retentionPeriod", "1", "Data with timestamps outside the retentionPeriod is automatically deleted")
 	snapshotAuthKey   = flag.String("snapshotAuthKey", "", "authKey, which must be passed in query string to /snapshot* pages")
 	forceMergeAuthKey = flag.String("forceMergeAuthKey", "", "authKey, which must be passed in query string to /internal/force_merge pages")
 	forceFlushAuthKey = flag.String("forceFlushAuthKey", "", "authKey, which must be passed in query string to /internal/force_flush pages")
+	snapshotsMaxAge   = flag.Duration("snapshotsMaxAge", 0, "Automatically delete snapshots older than -snapshotsMaxAge if it is set to non-zero duration. Make sure that backup process has enough time to finish the backup before the corresponding snapshot is automatically deleted")
 
 	precisionBits = flag.Int("precisionBits", 64, "The number of precision bits to store per each value. Lower precision bits improves data compression at the cost of precision loss")
 
@@ -102,6 +103,7 @@ func InitWithoutMetrics(resetCacheIfNeeded func(mrs []storage.MetricRow)) {
 		logger.Fatalf("cannot open a storage at %s with -retentionPeriod=%s: %s", *DataPath, retentionPeriod, err)
 	}
 	Storage = strg
+	initStaleSnapshotsRemover()
 
 	var m storage.Metrics
 	Storage.UpdateMetrics(&m)
@@ -255,6 +257,7 @@ func Stop() {
 	logger.Infof("gracefully closing the storage at %s", *DataPath)
 	startTime := time.Now()
 	WG.WaitAndBlock()
+	stopStaleSnapshotsRemover()
 	Storage.MustClose()
 	logger.Infof("successfully closed the storage in %.3f seconds", time.Since(startTime).Seconds())
 
@@ -374,6 +377,40 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
 	}
 }
 
+func initStaleSnapshotsRemover() {
+	staleSnapshotsRemoverCh = make(chan struct{})
+	if *snapshotsMaxAge <= 0 {
+		return
+	}
+	staleSnapshotsRemoverWG.Add(1)
+	go func() {
+		defer staleSnapshotsRemoverWG.Done()
+		t := time.NewTicker(11 * time.Second)
+		defer t.Stop()
+		for {
+			select {
+			case <-staleSnapshotsRemoverCh:
+				return
+			case <-t.C:
+			}
+			if err := Storage.DeleteStaleSnapshots(*snapshotsMaxAge); err != nil {
+				// Use logger.Errorf instead of logger.Fatalf in the hope the error is temporary.
+				logger.Errorf("cannot delete stale snapshots: %s", err)
+			}
+		}
+	}()
+}
+
+func stopStaleSnapshotsRemover() {
+	close(staleSnapshotsRemoverCh)
+	staleSnapshotsRemoverWG.Wait()
+}
+
+var (
+	staleSnapshotsRemoverCh chan struct{}
+	staleSnapshotsRemoverWG sync.WaitGroup
+)
+
 var activeForceMerges = metrics.NewCounter("vm_active_force_merges")
 
 func registerStorageMetrics() {
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 8ac8f2beb..338d42bc8 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -22,18 +22,23 @@ The following tip changes can be tested by building VictoriaMetrics components f
 * FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): add support for `tls_config` and `proxy_url` options at `oauth2` section in the same way as Prometheus does. See [oauth2 docs](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#oauth2).
 * FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): add support for `min_version` option at `tls_config` section in the same way as Prometheus does. See [tls_config docs](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#tls_config).
 * FEATURE: [vmalert](https://docs.victoriametrics.com/vmalert.html): add support for DNS-based discovery for notifiers in the same way as Prometheus does. See [these docs](https://docs.victoriametrics.com/vmalert.html#notifier-configuration-file) and [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2460).
+* FEATURE: [vmalert](https://docs.victoriametrics.com/vmalert.html): add `-replay.disableProgressBar` command-line flag, which allows disabling progressbar in [rules' backfilling mode](https://docs.victoriametrics.com/vmalert.html#rules-backfilling). See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1761).
 * FEATURE: allow specifying TLS cipher suites for incoming https requests via `-tlsCipherSuites` command-line flag. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2404).
 * FEATURE: allow specifying TLS cipher suites for mTLS connections between cluster components via `-cluster.tlsCipherSuites` command-line flag. See [these docs](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#mtls-protection).
+* FEATURE: vmstorage: add `-snapshotsMaxAge` command-line flag for automatic removal of snapshots older than the given age.
 * FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): shown an empty graph on the selected time range when there is no data on it. Previously `No data to show` placeholder was shown instead of the graph in this case. This prevented from zooming and scrolling of such a graph.
 * FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): show the selected `last N minutes/hours/days` in the top right corner. Previously the `start - end` duration was shown instead, which could be hard to interpret. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2402).
 * FEATURE: expose `vm_indexdb_items_added_total` and `vm_indexdb_items_added_size_bytes_total` counters at `/metrics` page, which can be used for monitoring the rate for addition of new entries in `indexdb` (aka `inverted index`) alongside the total size in bytes for the added entries. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2471).
+* FEATURE: [vmctl](https://docs.victoriametrics.com/vmctl.html): show data pocessing speed during data migration.
 * FEATURE: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): add `drop_common_labels()` function, which drops common `label="name"` pairs from the passed time series. See [these docs](https://docs.victoriametrics.com/MetricsQL.html#drop_common_labels).
 * FEATURE: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): add `tlast_change_over_time(m[d])` function, which returns the timestamp of the last change of `m` on the given lookbehind window `d`. See [these docs](https://docs.victoriametrics.com/MetricsQL.html#tlast_change_over_time).
 * FEATURE: add a handler for `/api/v1/status/buildinfo` endpoint, which is used by Grafana starting from v8.5.0 . See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/2515).
 
 * BUGFIX: export staleness markers as `null` values from [JSON export API](https://docs.victoriametrics.com/#how-to-export-data-in-json-line-format). Previously they were exported as `NaN` values. This could break the exported JSON parsing, since `NaN` values aren't supported by [JSON specification](https://www.json.org/).
+* BUGFIX: [VictoriaMetrics cluster](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html): close `vmselect->vmstorage` connections if they were idle for more than 30 seconds. Expose `vm_tcpdialer_conns_idle` metric at `http://vmselect:8481/metrics` with the number of idle connections to `vmstorage`. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2508).
 * BUGFIX: [vmctl](https://docs.victoriametrics.com/vmctl.html): return non-zero exit code on error. This allows handling `vmctl` errors in shell scripts. Previously `vmctl` was returning 0 exit code on error. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2322).
 * BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): properly show `scrape_timeout` and `scrape_interval` options at `http://vmagent:8429/config` page. Previously these options weren't displayed even if they were set in `-promscrape.config`.
+* BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert.html): skip template execution during rules' validation. This should prevent from `error evaluating annotation template` errors when some template functions expect non-empty args. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2514).
 * BUGFIX: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): properly handle joins on time series filtered by values. For example, `kube_pod_container_resource_requests{resource="cpu"} * on (namespace,pod) group_left() (kube_pod_status_phase{phase=~"Pending|Running"}==1)`. This query could result in `duplicate time series on the right side` error even if `==1` filter leaves only a single time series per `(namespace,pod)` labels. Now such query is properly executed.
 * BUGFIX: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): properly handle `scalar default vector`, `scalar if vector` and `scalar ifnot vector` queries. Previously such queries could return unexpected results from the `vector` part.
 * BUGFIX: [Official Grafana dashboards for VictoriaMetrics](https://grafana.com/orgs/victoriametrics): take into account `indexdb` when calculating disk space usage. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2368).
diff --git a/docs/README.md b/docs/README.md
index 54a7921e8..a477def14 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1097,6 +1097,12 @@ with the enabled de-duplication. See [this section](#deduplication) for details.
 
 VictoriaMetrics de-duplicates data points if `-dedup.minScrapeInterval` command-line flag is set to positive duration. For example, `-dedup.minScrapeInterval=60s` would de-duplicate data points on the same time series if they fall within the same discrete 60s bucket.  The earliest data point will be kept. In the case of equal timestamps, an arbitrary data point will be kept. See [this comment](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2112#issuecomment-1032587618) for more details on how downsampling works.
 
+The same `-dedup.minScrapeInterval` command-line flag value must be passed to both `vmselect` and `vmstorage` nodes because of the following aspects.
+By default `vminsert` tries to route all the samples for a single time series to a single `vmstorage` node. But samples for a single time series can be spread among multiple `vmstorage` nodes under certain conditions:
+* when adding/removing `vmstorage` nodes. Then new samples for a part of time series will be routed to another `vmstorage` nodes;
+* when `vmstorage` nodes are temporarily unavailable (for instance, during their restart). Then new samples are re-routed to the remaining available `vmstorage` nodes;
+* when `vmstorage` node has no enough capacity for processing incoming data stream. Then `vminsert` re-routes new samples to other `vmstorage` nodes.
+
 The `-dedup.minScrapeInterval=D` is equivalent to `-downsampling.period=0s:D` if [downsampling](#downsampling) is enabled. It is safe to use deduplication and downsampling simultaneously.
 
 The recommended value for `-dedup.minScrapeInterval` must equal to `scrape_interval` config from Prometheus configs. It is recommended to have a single `scrape_interval` across all the scrape targets. See [this article](https://www.robustperception.io/keep-it-simple-scrape_interval-id) for details.
diff --git a/lib/flagutil/duration.go b/lib/flagutil/duration.go
index 8dd920f67..87d602c6e 100644
--- a/lib/flagutil/duration.go
+++ b/lib/flagutil/duration.go
@@ -12,14 +12,14 @@ import (
 // NewDuration returns new `duration` flag with the given name, defaultValue and description.
 //
 // DefaultValue is in months.
-func NewDuration(name string, defaultValue float64, description string) *Duration {
+func NewDuration(name string, defaultValue string, description string) *Duration {
 	description += "\nThe following optional suffixes are supported: h (hour), d (day), w (week), y (year). If suffix isn't set, then the duration is counted in months"
-	d := Duration{
-		Msecs:       int64(defaultValue * msecsPerMonth),
-		valueString: fmt.Sprintf("%g", defaultValue),
+	d := &Duration{}
+	if err := d.Set(defaultValue); err != nil {
+		panic(fmt.Sprintf("BUG: can not parse default value %s for flag %s", defaultValue, name))
 	}
-	flag.Var(&d, name, description)
-	return &d
+	flag.Var(d, name, description)
+	return d
 }
 
 // Duration is a flag for holding duration.
diff --git a/lib/storage/storage.go b/lib/storage/storage.go
index 95d4f88a8..1f48d0278 100644
--- a/lib/storage/storage.go
+++ b/lib/storage/storage.go
@@ -418,6 +418,39 @@ func (s *Storage) DeleteSnapshot(snapshotName string) error {
 	return nil
 }
 
+// DeleteStaleSnapshots deletes snapshot older than given maxAge
+func (s *Storage) DeleteStaleSnapshots(maxAge time.Duration) error {
+	list, err := s.ListSnapshots()
+	if err != nil {
+		return err
+	}
+	expireDeadline := time.Now().UTC().Add(-maxAge)
+	for _, snapshotName := range list {
+		t, err := snapshotTime(snapshotName)
+		if err != nil {
+			return fmt.Errorf("cannot parse snapshot date from %q: %w", snapshotName, err)
+		}
+		if t.Before(expireDeadline) {
+			if err := s.DeleteSnapshot(snapshotName); err != nil {
+				return fmt.Errorf("cannot delete snapshot %q: %w", snapshotName, err)
+			}
+		}
+	}
+	return nil
+}
+
+func snapshotTime(snapshotName string) (time.Time, error) {
+	if !snapshotNameRegexp.MatchString(snapshotName) {
+		return time.Time{}, fmt.Errorf("unexpected snapshotName must be in the format `YYYYMMDDhhmmss-idx`; got %q", snapshotName)
+	}
+	n := strings.IndexByte(snapshotName, '-')
+	if n < 0 {
+		return time.Time{}, fmt.Errorf("cannot find `-` in snapshotName=%q", snapshotName)
+	}
+	s := snapshotName[:n]
+	return time.Parse("20060102150405", s)
+}
+
 var snapshotIdx = uint64(time.Now().UnixNano())
 
 func nextSnapshotIdx() uint64 {


Test patch:
diff --git a/lib/flagutil/duration_test.go b/lib/flagutil/duration_test.go
index 2c35951b3..bd26d47b8 100644
--- a/lib/flagutil/duration_test.go
+++ b/lib/flagutil/duration_test.go
@@ -56,5 +56,6 @@ func TestDurationSetSuccess(t *testing.T) {
 	f("1h", 3600*1000)
 	f("1.5d", 1.5*24*3600*1000)
 	f("2.3W", 2.3*7*24*3600*1000)
+	f("1w", 7*24*3600*1000)
 	f("0.25y", 0.25*365*24*3600*1000)
 }
diff --git a/lib/storage/storage_test.go b/lib/storage/storage_test.go
index 6ed004ae2..fa6ebb5c9 100644
--- a/lib/storage/storage_test.go
+++ b/lib/storage/storage_test.go
@@ -1106,6 +1106,58 @@ func testStorageAddMetrics(s *Storage, workerNum int) error {
 	return nil
 }
 
+func TestStorageDeleteStaleSnapshots(t *testing.T) {
+	path := "TestStorageDeleteStaleSnapshots"
+	s, err := OpenStorage(path, 0, 1e5, 1e5)
+	if err != nil {
+		t.Fatalf("cannot open storage: %s", err)
+	}
+	const rowsPerAdd = 1e3
+	const addsCount = 10
+	for i := 0; i < addsCount; i++ {
+		mrs := testGenerateMetricRows(rowsPerAdd, 0, 1e10)
+		if err := s.AddRows(mrs, defaultPrecisionBits); err != nil {
+			t.Fatalf("unexpected error when adding mrs: %s", err)
+		}
+	}
+	// Try creating a snapshot from the storage.
+	snapshotName, err := s.CreateSnapshot()
+	if err != nil {
+		t.Fatalf("cannot create snapshot from the storage: %s", err)
+	}
+	// Delete snapshots older than 1 month
+	if err := s.DeleteStaleSnapshots(30 * 24 * time.Hour); err != nil {
+		t.Fatalf("error in DeleteStaleSnapshots(1 month): %s", err)
+	}
+	snapshots, err := s.ListSnapshots()
+	if err != nil {
+		t.Fatalf("cannot list snapshots: %s", err)
+	}
+	if len(snapshots) != 1 {
+		t.Fatalf("expecting one snapshot; got %q", snapshots)
+	}
+	if snapshots[0] != snapshotName {
+		t.Fatalf("snapshot %q is missing in %q", snapshotName, snapshots)
+	}
+
+	// Delete the snapshot which is older than 1 nanoseconds
+	time.Sleep(2 * time.Nanosecond)
+	if err := s.DeleteStaleSnapshots(time.Nanosecond); err != nil {
+		t.Fatalf("cannot delete snapshot %q: %s", snapshotName, err)
+	}
+	snapshots, err = s.ListSnapshots()
+	if err != nil {
+		t.Fatalf("cannot list snapshots: %s", err)
+	}
+	if len(snapshots) != 0 {
+		t.Fatalf("expecting zero snapshots; got %q", snapshots)
+	}
+	s.MustClose()
+	if err := os.RemoveAll(path); err != nil {
+		t.Fatalf("cannot remove %q: %s", path, err)
+	}
+}
+
 func containsString(a []string, s string) bool {
 	for i := range a {
 		if a[i] == s {