Repository: VictoriaMetrics/VictoriaMetrics
Pull request #922 (2020-11-23 13:20:06)

Spelling mistakes
fix spelling mistake


Patch:
diff --git a/README.md b/README.md
index 85675eb54..ff7fb3fc2 100644
--- a/README.md
+++ b/README.md
@@ -566,6 +566,7 @@ VictoriaMetrics supports the following handlers from [Graphite Tags API](https:/
 * [/tags/findSeries](https://graphite.readthedocs.io/en/stable/tags.html#exploring-tags)
 * [/tags/autoComplete/tags](https://graphite.readthedocs.io/en/stable/tags.html#auto-complete-support)
 * [/tags/autoComplete/values](https://graphite.readthedocs.io/en/stable/tags.html#auto-complete-support)
+* [/tags/delSeries](https://graphite.readthedocs.io/en/stable/tags.html#removing-series-from-the-tagdb)
 
 
 ## How to build from sources
diff --git a/app/vmbackup/main.go b/app/vmbackup/main.go
index bbaa2a77a..550e3fde8 100644
--- a/app/vmbackup/main.go
+++ b/app/vmbackup/main.go
@@ -43,7 +43,7 @@ func main() {
 	cgroup.UpdateGOMAXPROCSToCPUQuota()
 
 	if len(*snapshotCreateURL) > 0 {
-		logger.Infof("%s", "Snapshots enabled")
+		logger.Infof("Snapshots enabled")
 		logger.Infof("Snapshot create url %s", *snapshotCreateURL)
 		if len(*snapshotDeleteURL) <= 0 {
 			err := flag.Set("snapshot.deleteURL", strings.Replace(*snapshotCreateURL, "/create", "/delete", 1))
@@ -55,17 +55,17 @@ func main() {
 
 		name, err := snapshot.Create(*snapshotCreateURL)
 		if err != nil {
-			logger.Fatalf("%s", err)
+			logger.Fatalf("cannot create snapshot: %s", err)
 		}
 		err = flag.Set("snapshotName", name)
 		if err != nil {
-			logger.Fatalf("Failed to set snapshotName flag: %v", err)
+			logger.Fatalf("cannot set snapshotName flag: %v", err)
 		}
 
 		defer func() {
 			err := snapshot.Delete(*snapshotDeleteURL, name)
 			if err != nil {
-				logger.Fatalf("%s", err)
+				logger.Fatalf("cannot delete snapshot: %s", err)
 			}
 		}()
 	}
diff --git a/app/vmbackup/snapshot/snapshot.go b/app/vmbackup/snapshot/snapshot.go
index 852c0b3b4..774db2c6f 100644
--- a/app/vmbackup/snapshot/snapshot.go
+++ b/app/vmbackup/snapshot/snapshot.go
@@ -20,7 +20,7 @@ type snapshot struct {
 // Create creates a snapshot and the provided api endpoint and returns
 // the snapshot name
 func Create(createSnapshotURL string) (string, error) {
-	logger.Infof("%s", "Creating snapshot")
+	logger.Infof("Creating snapshot")
 	u, err := url.Parse(createSnapshotURL)
 	if err != nil {
 		return "", err
diff --git a/app/vminsert/graphite/tags.go b/app/vminsert/graphite/tags.go
deleted file mode 100644
index fe49fcebf..000000000
--- a/app/vminsert/graphite/tags.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package graphite
-
-import (
-	"fmt"
-	"net/http"
-	"sort"
-	"time"
-
-	"github.com/VictoriaMetrics/VictoriaMetrics/app/vmstorage"
-	"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
-	graphiteparser "github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/graphite"
-	"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
-	"github.com/VictoriaMetrics/metrics"
-)
-
-// TagsTagSeriesHandler implements /tags/tagSeries handler.
-//
-// See https://graphite.readthedocs.io/en/stable/tags.html#adding-series-to-the-tagdb
-func TagsTagSeriesHandler(w http.ResponseWriter, r *http.Request) error {
-	return registerMetrics(w, r, false)
-}
-
-// TagsTagMultiSeriesHandler implements /tags/tagMultiSeries handler.
-//
-// See https://graphite.readthedocs.io/en/stable/tags.html#adding-series-to-the-tagdb
-func TagsTagMultiSeriesHandler(w http.ResponseWriter, r *http.Request) error {
-	return registerMetrics(w, r, true)
-}
-
-func registerMetrics(w http.ResponseWriter, r *http.Request, isJSONResponse bool) error {
-	startTime := time.Now()
-	if err := r.ParseForm(); err != nil {
-		return fmt.Errorf("cannot parse form values: %w", err)
-	}
-	paths := r.Form["path"]
-	var row graphiteparser.Row
-	var labels []prompb.Label
-	var b []byte
-	var tagsPool []graphiteparser.Tag
-	mrs := make([]storage.MetricRow, len(paths))
-	ct := time.Now().UnixNano() / 1e6
-	canonicalPaths := make([]string, len(paths))
-	for i, path := range paths {
-		var err error
-		tagsPool, err = row.UnmarshalMetricAndTags(path, tagsPool[:0])
-		if err != nil {
-			return fmt.Errorf("cannot parse path=%q: %w", path, err)
-		}
-
-		// Construct canonical path according to https://graphite.readthedocs.io/en/stable/tags.html#adding-series-to-the-tagdb
-		sort.Slice(row.Tags, func(i, j int) bool {
-			return row.Tags[i].Key < row.Tags[j].Key
-		})
-		b = append(b[:0], row.Metric...)
-		for _, tag := range row.Tags {
-			b = append(b, ';')
-			b = append(b, tag.Key...)
-			b = append(b, '=')
-			b = append(b, tag.Value...)
-		}
-		canonicalPaths[i] = string(b)
-
-		// Convert parsed metric and tags to labels.
-		labels = append(labels[:0], prompb.Label{
-			Name:  []byte("__name__"),
-			Value: []byte(row.Metric),
-		})
-		for _, tag := range row.Tags {
-			labels = append(labels, prompb.Label{
-				Name:  []byte(tag.Key),
-				Value: []byte(tag.Value),
-			})
-		}
-
-		// Put labels with the current timestamp to MetricRow
-		mr := &mrs[i]
-		mr.MetricNameRaw = storage.MarshalMetricNameRaw(mr.MetricNameRaw[:0], labels)
-		mr.Timestamp = ct
-	}
-	if err := vmstorage.RegisterMetricNames(mrs); err != nil {
-		return fmt.Errorf("cannot register paths: %w", err)
-	}
-
-	// Return response
-	contentType := "text/plain; charset=utf-8"
-	if isJSONResponse {
-		contentType = "application/json; charset=utf-8"
-	}
-	w.Header().Set("Content-Type", contentType)
-	WriteTagsTagMultiSeriesResponse(w, canonicalPaths, isJSONResponse)
-	if isJSONResponse {
-		tagsTagMultiSeriesDuration.UpdateDuration(startTime)
-	} else {
-		tagsTagSeriesDuration.UpdateDuration(startTime)
-	}
-	return nil
-}
-
-var (
-	tagsTagSeriesDuration      = metrics.NewSummary(`vm_request_duration_seconds{path="/tags/tagSeries"}`)
-	tagsTagMultiSeriesDuration = metrics.NewSummary(`vm_request_duration_seconds{path="/tags/tagMultiSeries"}`)
-)
diff --git a/app/vminsert/graphite/tags_tag_multi_series_response.qtpl.go b/app/vminsert/graphite/tags_tag_multi_series_response.qtpl.go
deleted file mode 100644
index 773a6ce4e..000000000
--- a/app/vminsert/graphite/tags_tag_multi_series_response.qtpl.go
+++ /dev/null
@@ -1,75 +0,0 @@
-// Code generated by qtc from "tags_tag_multi_series_response.qtpl". DO NOT EDIT.
-// See https://github.com/valyala/quicktemplate for details.
-
-// TagsTagMultiSeriesResponse generates response for /tags/tagMultiSeries .See https://graphite.readthedocs.io/en/stable/tags.html#adding-series-to-the-tagdb
-
-//line app/vminsert/graphite/tags_tag_multi_series_response.qtpl:5
-package graphite
-
-//line app/vminsert/graphite/tags_tag_multi_series_response.qtpl:5
-import (
-	qtio422016 "io"
-
-	qt422016 "github.com/valyala/quicktemplate"
-)
-
-//line app/vminsert/graphite/tags_tag_multi_series_response.qtpl:5
-var (
-	_ = qtio422016.Copy
-	_ = qt422016.AcquireByteBuffer
-)
-
-//line app/vminsert/graphite/tags_tag_multi_series_response.qtpl:5
-func StreamTagsTagMultiSeriesResponse(qw422016 *qt422016.Writer, canonicalPaths []string, isJSONResponse bool) {
-//line app/vminsert/graphite/tags_tag_multi_series_response.qtpl:6
-	if isJSONResponse {
-//line app/vminsert/graphite/tags_tag_multi_series_response.qtpl:6
-		qw422016.N().S(`[`)
-//line app/vminsert/graphite/tags_tag_multi_series_response.qtpl:6
-	}
-//line app/vminsert/graphite/tags_tag_multi_series_response.qtpl:7
-	for i, path := range canonicalPaths {
-//line app/vminsert/graphite/tags_tag_multi_series_response.qtpl:8
-		qw422016.N().Q(path)
-//line app/vminsert/graphite/tags_tag_multi_series_response.qtpl:9
-		if i+1 < len(canonicalPaths) {
-//line app/vminsert/graphite/tags_tag_multi_series_response.qtpl:9
-			qw422016.N().S(`,`)
-//line app/vminsert/graphite/tags_tag_multi_series_response.qtpl:9
-		}
-//line app/vminsert/graphite/tags_tag_multi_series_response.qtpl:10
-	}
-//line app/vminsert/graphite/tags_tag_multi_series_response.qtpl:11
-	if isJSONResponse {
-//line app/vminsert/graphite/tags_tag_multi_series_response.qtpl:11
-		qw422016.N().S(`]`)
-//line app/vminsert/graphite/tags_tag_multi_series_response.qtpl:11
-	}
-//line app/vminsert/graphite/tags_tag_multi_series_response.qtpl:12
-}
-
-//line app/vminsert/graphite/tags_tag_multi_series_response.qtpl:12
-func WriteTagsTagMultiSeriesResponse(qq422016 qtio422016.Writer, canonicalPaths []string, isJSONResponse bool) {
-//line app/vminsert/graphite/tags_tag_multi_series_response.qtpl:12
-	qw422016 := qt422016.AcquireWriter(qq422016)
-//line app/vminsert/graphite/tags_tag_multi_series_response.qtpl:12
-	StreamTagsTagMultiSeriesResponse(qw422016, canonicalPaths, isJSONResponse)
-//line app/vminsert/graphite/tags_tag_multi_series_response.qtpl:12
-	qt422016.ReleaseWriter(qw422016)
-//line app/vminsert/graphite/tags_tag_multi_series_response.qtpl:12
-}
-
-//line app/vminsert/graphite/tags_tag_multi_series_response.qtpl:12
-func TagsTagMultiSeriesResponse(canonicalPaths []string, isJSONResponse bool) string {
-//line app/vminsert/graphite/tags_tag_multi_series_response.qtpl:12
-	qb422016 := qt422016.AcquireByteBuffer()
-//line app/vminsert/graphite/tags_tag_multi_series_response.qtpl:12
-	WriteTagsTagMultiSeriesResponse(qb422016, canonicalPaths, isJSONResponse)
-//line app/vminsert/graphite/tags_tag_multi_series_response.qtpl:12
-	qs422016 := string(qb422016.B)
-//line app/vminsert/graphite/tags_tag_multi_series_response.qtpl:12
-	qt422016.ReleaseByteBuffer(qb422016)
-//line app/vminsert/graphite/tags_tag_multi_series_response.qtpl:12
-	return qs422016
-//line app/vminsert/graphite/tags_tag_multi_series_response.qtpl:12
-}
diff --git a/app/vminsert/main.go b/app/vminsert/main.go
index 1716b1c4e..3bfd7f2c3 100644
--- a/app/vminsert/main.go
+++ b/app/vminsert/main.go
@@ -40,7 +40,7 @@ var (
 		"Telnet put messages and HTTP /api/put messages are simultaneously served on TCP port. "+
 		"Usually :4242 must be set. Doesn't work if empty")
 	opentsdbHTTPListenAddr = flag.String("opentsdbHTTPListenAddr", "", "TCP address to listen for OpentTSDB HTTP put requests. Usually :4242 must be set. Doesn't work if empty")
-	maxLabelsPerTimeseries = flag.Int("maxLabelsPerTimeseries", 30, "The maximum number of labels accepted per time series. Superflouos labels are dropped")
+	maxLabelsPerTimeseries = flag.Int("maxLabelsPerTimeseries", 30, "The maximum number of labels accepted per time series. Superfluous labels are dropped")
 )
 
 var (
@@ -153,22 +153,6 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
 		influxQueryRequests.Inc()
 		fmt.Fprintf(w, `{"results":[{"series":[{"values":[]}]}]}`)
 		return true
-	case "/tags/tagSeries":
-		graphiteTagsTagSeriesRequests.Inc()
-		if err := graphite.TagsTagSeriesHandler(w, r); err != nil {
-			graphiteTagsTagSeriesErrors.Inc()
-			httpserver.Errorf(w, r, "error in %q: %s", r.URL.Path, err)
-			return true
-		}
-		return true
-	case "/tags/tagMultiSeries":
-		graphiteTagsTagMultiSeriesRequests.Inc()
-		if err := graphite.TagsTagMultiSeriesHandler(w, r); err != nil {
-			graphiteTagsTagMultiSeriesErrors.Inc()
-			httpserver.Errorf(w, r, "error in %q: %s", r.URL.Path, err)
-			return true
-		}
-		return true
 	case "/targets":
 		promscrapeTargetsRequests.Inc()
 		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
@@ -223,12 +207,6 @@ var (
 
 	influxQueryRequests = metrics.NewCounter(`vm_http_requests_total{path="/query", protocol="influx"}`)
 
-	graphiteTagsTagSeriesRequests = metrics.NewCounter(`vm_http_requests_total{path="/tags/tagSeries", protocol="graphite"}`)
-	graphiteTagsTagSeriesErrors   = metrics.NewCounter(`vm_http_request_errors_total{path="/tags/tagSeries", protocol="graphite"}`)
-
-	graphiteTagsTagMultiSeriesRequests = metrics.NewCounter(`vm_http_requests_total{path="/tags/tagMultiSeries", protocol="graphite"}`)
-	graphiteTagsTagMultiSeriesErrors   = metrics.NewCounter(`vm_http_request_errors_total{path="/tags/tagMultiSeries", protocol="graphite"}`)
-
 	promscrapeTargetsRequests      = metrics.NewCounter(`vm_http_requests_total{path="/targets"}`)
 	promscrapeAPIV1TargetsRequests = metrics.NewCounter(`vm_http_requests_total{path="/api/v1/targets"}`)
 
diff --git a/app/vmselect/graphite/tags_api.go b/app/vmselect/graphite/tags_api.go
index df0d613e7..ba46aeb2d 100644
--- a/app/vmselect/graphite/tags_api.go
+++ b/app/vmselect/graphite/tags_api.go
@@ -12,10 +12,146 @@ import (
 	"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/bufferedwriter"
 	"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/netstorage"
 	"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/searchutils"
+	"github.com/VictoriaMetrics/VictoriaMetrics/app/vmstorage"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
+	graphiteparser "github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/graphite"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
 	"github.com/VictoriaMetrics/metrics"
 )
 
+// TagsDelSeriesHandler implements /tags/delSeries handler.
+//
+// See https://graphite.readthedocs.io/en/stable/tags.html#removing-series-from-the-tagdb
+func TagsDelSeriesHandler(startTime time.Time, w http.ResponseWriter, r *http.Request) error {
+	if err := r.ParseForm(); err != nil {
+		return fmt.Errorf("cannot parse form values: %w", err)
+	}
+	paths := r.Form["path"]
+	totalDeleted := 0
+	var row graphiteparser.Row
+	var tagsPool []graphiteparser.Tag
+	ct := time.Now().UnixNano() / 1e6
+	for _, path := range paths {
+		var err error
+		tagsPool, err = row.UnmarshalMetricAndTags(path, tagsPool[:0])
+		if err != nil {
+			return fmt.Errorf("cannot parse path=%q: %w", path, err)
+		}
+		tfs := make([]storage.TagFilter, 0, 1+len(row.Tags))
+		tfs = append(tfs, storage.TagFilter{
+			Key:   nil,
+			Value: []byte(row.Metric),
+		})
+		for _, tag := range row.Tags {
+			tfs = append(tfs, storage.TagFilter{
+				Key:   []byte(tag.Key),
+				Value: []byte(tag.Value),
+			})
+		}
+		sq := storage.NewSearchQuery(0, ct, [][]storage.TagFilter{tfs})
+		n, err := netstorage.DeleteSeries(sq)
+		if err != nil {
+			return fmt.Errorf("cannot delete series for %q: %w", sq, err)
+		}
+		totalDeleted += n
+	}
+
+	w.Header().Set("Content-Type", "application/json; charset=utf-8")
+	if totalDeleted > 0 {
+		fmt.Fprintf(w, "true")
+	} else {
+		fmt.Fprintf(w, "false")
+	}
+	return nil
+}
+
+// TagsTagSeriesHandler implements /tags/tagSeries handler.
+//
+// See https://graphite.readthedocs.io/en/stable/tags.html#adding-series-to-the-tagdb
+func TagsTagSeriesHandler(startTime time.Time, w http.ResponseWriter, r *http.Request) error {
+	return registerMetrics(startTime, w, r, false)
+}
+
+// TagsTagMultiSeriesHandler implements /tags/tagMultiSeries handler.
+//
+// See https://graphite.readthedocs.io/en/stable/tags.html#adding-series-to-the-tagdb
+func TagsTagMultiSeriesHandler(startTime time.Time, w http.ResponseWriter, r *http.Request) error {
+	return registerMetrics(startTime, w, r, true)
+}
+
+func registerMetrics(startTime time.Time, w http.ResponseWriter, r *http.Request, isJSONResponse bool) error {
+	if err := r.ParseForm(); err != nil {
+		return fmt.Errorf("cannot parse form values: %w", err)
+	}
+	paths := r.Form["path"]
+	var row graphiteparser.Row
+	var labels []prompb.Label
+	var b []byte
+	var tagsPool []graphiteparser.Tag
+	mrs := make([]storage.MetricRow, len(paths))
+	ct := time.Now().UnixNano() / 1e6
+	canonicalPaths := make([]string, len(paths))
+	for i, path := range paths {
+		var err error
+		tagsPool, err = row.UnmarshalMetricAndTags(path, tagsPool[:0])
+		if err != nil {
+			return fmt.Errorf("cannot parse path=%q: %w", path, err)
+		}
+
+		// Construct canonical path according to https://graphite.readthedocs.io/en/stable/tags.html#adding-series-to-the-tagdb
+		sort.Slice(row.Tags, func(i, j int) bool {
+			return row.Tags[i].Key < row.Tags[j].Key
+		})
+		b = append(b[:0], row.Metric...)
+		for _, tag := range row.Tags {
+			b = append(b, ';')
+			b = append(b, tag.Key...)
+			b = append(b, '=')
+			b = append(b, tag.Value...)
+		}
+		canonicalPaths[i] = string(b)
+
+		// Convert parsed metric and tags to labels.
+		labels = append(labels[:0], prompb.Label{
+			Name:  []byte("__name__"),
+			Value: []byte(row.Metric),
+		})
+		for _, tag := range row.Tags {
+			labels = append(labels, prompb.Label{
+				Name:  []byte(tag.Key),
+				Value: []byte(tag.Value),
+			})
+		}
+
+		// Put labels with the current timestamp to MetricRow
+		mr := &mrs[i]
+		mr.MetricNameRaw = storage.MarshalMetricNameRaw(mr.MetricNameRaw[:0], labels)
+		mr.Timestamp = ct
+	}
+	if err := vmstorage.RegisterMetricNames(mrs); err != nil {
+		return fmt.Errorf("cannot register paths: %w", err)
+	}
+
+	// Return response
+	contentType := "text/plain; charset=utf-8"
+	if isJSONResponse {
+		contentType = "application/json; charset=utf-8"
+	}
+	w.Header().Set("Content-Type", contentType)
+	WriteTagsTagMultiSeriesResponse(w, canonicalPaths, isJSONResponse)
+	if isJSONResponse {
+		tagsTagMultiSeriesDuration.UpdateDuration(startTime)
+	} else {
+		tagsTagSeriesDuration.UpdateDuration(startTime)
+	}
+	return nil
+}
+
+var (
+	tagsTagSeriesDuration      = metrics.NewSummary(`vm_request_duration_seconds{path="/tags/tagSeries"}`)
+	tagsTagMultiSeriesDuration = metrics.NewSummary(`vm_request_duration_seconds{path="/tags/tagMultiSeries"}`)
+)
+
 // TagsAutoCompleteValuesHandler implements /tags/autoComplete/values endpoint from Graphite Tags API.
 //
 // See https://graphite.readthedocs.io/en/stable/tags.html#auto-complete-support
diff --git a/app/vminsert/graphite/tags_tag_multi_series_response.qtpl b/app/vmselect/graphite/tags_tag_multi_series_response.qtpl
similarity index 100%
rename from app/vminsert/graphite/tags_tag_multi_series_response.qtpl
rename to app/vmselect/graphite/tags_tag_multi_series_response.qtpl
diff --git a/app/vmselect/graphite/tags_tag_multi_series_response.qtpl.go b/app/vmselect/graphite/tags_tag_multi_series_response.qtpl.go
new file mode 100644
index 000000000..d996a925d
--- /dev/null
+++ b/app/vmselect/graphite/tags_tag_multi_series_response.qtpl.go
@@ -0,0 +1,75 @@
+// Code generated by qtc from "tags_tag_multi_series_response.qtpl". DO NOT EDIT.
+// See https://github.com/valyala/quicktemplate for details.
+
+// TagsTagMultiSeriesResponse generates response for /tags/tagMultiSeries .See https://graphite.readthedocs.io/en/stable/tags.html#adding-series-to-the-tagdb
+
+//line app/vmselect/graphite/tags_tag_multi_series_response.qtpl:5
+package graphite
+
+//line app/vmselect/graphite/tags_tag_multi_series_response.qtpl:5
+import (
+	qtio422016 "io"
+
+	qt422016 "github.com/valyala/quicktemplate"
+)
+
+//line app/vmselect/graphite/tags_tag_multi_series_response.qtpl:5
+var (
+	_ = qtio422016.Copy
+	_ = qt422016.AcquireByteBuffer
+)
+
+//line app/vmselect/graphite/tags_tag_multi_series_response.qtpl:5
+func StreamTagsTagMultiSeriesResponse(qw422016 *qt422016.Writer, canonicalPaths []string, isJSONResponse bool) {
+//line app/vmselect/graphite/tags_tag_multi_series_response.qtpl:6
+	if isJSONResponse {
+//line app/vmselect/graphite/tags_tag_multi_series_response.qtpl:6
+		qw422016.N().S(`[`)
+//line app/vmselect/graphite/tags_tag_multi_series_response.qtpl:6
+	}
+//line app/vmselect/graphite/tags_tag_multi_series_response.qtpl:7
+	for i, path := range canonicalPaths {
+//line app/vmselect/graphite/tags_tag_multi_series_response.qtpl:8
+		qw422016.N().Q(path)
+//line app/vmselect/graphite/tags_tag_multi_series_response.qtpl:9
+		if i+1 < len(canonicalPaths) {
+//line app/vmselect/graphite/tags_tag_multi_series_response.qtpl:9
+			qw422016.N().S(`,`)
+//line app/vmselect/graphite/tags_tag_multi_series_response.qtpl:9
+		}
+//line app/vmselect/graphite/tags_tag_multi_series_response.qtpl:10
+	}
+//line app/vmselect/graphite/tags_tag_multi_series_response.qtpl:11
+	if isJSONResponse {
+//line app/vmselect/graphite/tags_tag_multi_series_response.qtpl:11
+		qw422016.N().S(`]`)
+//line app/vmselect/graphite/tags_tag_multi_series_response.qtpl:11
+	}
+//line app/vmselect/graphite/tags_tag_multi_series_response.qtpl:12
+}
+
+//line app/vmselect/graphite/tags_tag_multi_series_response.qtpl:12
+func WriteTagsTagMultiSeriesResponse(qq422016 qtio422016.Writer, canonicalPaths []string, isJSONResponse bool) {
+//line app/vmselect/graphite/tags_tag_multi_series_response.qtpl:12
+	qw422016 := qt422016.AcquireWriter(qq422016)
+//line app/vmselect/graphite/tags_tag_multi_series_response.qtpl:12
+	StreamTagsTagMultiSeriesResponse(qw422016, canonicalPaths, isJSONResponse)
+//line app/vmselect/graphite/tags_tag_multi_series_response.qtpl:12
+	qt422016.ReleaseWriter(qw422016)
+//line app/vmselect/graphite/tags_tag_multi_series_response.qtpl:12
+}
+
+//line app/vmselect/graphite/tags_tag_multi_series_response.qtpl:12
+func TagsTagMultiSeriesResponse(canonicalPaths []string, isJSONResponse bool) string {
+//line app/vmselect/graphite/tags_tag_multi_series_response.qtpl:12
+	qb422016 := qt422016.AcquireByteBuffer()
+//line app/vmselect/graphite/tags_tag_multi_series_response.qtpl:12
+	WriteTagsTagMultiSeriesResponse(qb422016, canonicalPaths, isJSONResponse)
+//line app/vmselect/graphite/tags_tag_multi_series_response.qtpl:12
+	qs422016 := string(qb422016.B)
+//line app/vmselect/graphite/tags_tag_multi_series_response.qtpl:12
+	qt422016.ReleaseByteBuffer(qb422016)
+//line app/vmselect/graphite/tags_tag_multi_series_response.qtpl:12
+	return qs422016
+//line app/vmselect/graphite/tags_tag_multi_series_response.qtpl:12
+}
diff --git a/app/vmselect/main.go b/app/vmselect/main.go
index cc8aab099..3ec78f12e 100644
--- a/app/vmselect/main.go
+++ b/app/vmselect/main.go
@@ -23,7 +23,7 @@ import (
 )
 
 var (
-	deleteAuthKey         = flag.String("deleteAuthKey", "", "authKey for metrics' deletion via /api/v1/admin/tsdb/delete_series")
+	deleteAuthKey         = flag.String("deleteAuthKey", "", "authKey for metrics' deletion via /api/v1/admin/tsdb/delete_series and /tags/delSeries")
 	maxConcurrentRequests = flag.Int("search.maxConcurrentRequests", getDefaultMaxConcurrentRequests(), "The maximum number of concurrent search requests. "+
 		"It shouldn't be high, since a single request can saturate all the CPU cores. See also -search.maxQueueDuration")
 	maxQueueDuration = flag.Duration("search.maxQueueDuration", 10*time.Second, "The maximum time the request waits for execution when -search.maxConcurrentRequests "+
@@ -269,6 +269,22 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
 			return true
 		}
 		return true
+	case "/tags/tagSeries":
+		graphiteTagsTagSeriesRequests.Inc()
+		if err := graphite.TagsTagSeriesHandler(startTime, w, r); err != nil {
+			graphiteTagsTagSeriesErrors.Inc()
+			httpserver.Errorf(w, r, "error in %q: %s", r.URL.Path, err)
+			return true
+		}
+		return true
+	case "/tags/tagMultiSeries":
+		graphiteTagsTagMultiSeriesRequests.Inc()
+		if err := graphite.TagsTagMultiSeriesHandler(startTime, w, r); err != nil {
+			graphiteTagsTagMultiSeriesErrors.Inc()
+			httpserver.Errorf(w, r, "error in %q: %s", r.URL.Path, err)
+			return true
+		}
+		return true
 	case "/tags":
 		graphiteTagsRequests.Inc()
 		if err := graphite.TagsHandler(startTime, w, r); err != nil {
@@ -303,6 +319,19 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
 			return true
 		}
 		return true
+	case "/tags/delSeries":
+		graphiteTagsDelSeriesRequests.Inc()
+		authKey := r.FormValue("authKey")
+		if authKey != *deleteAuthKey {
+			httpserver.Errorf(w, r, "invalid authKey %q. It must match the value from -deleteAuthKey command line flag", authKey)
+			return true
+		}
+		if err := graphite.TagsDelSeriesHandler(startTime, w, r); err != nil {
+			graphiteTagsDelSeriesErrors.Inc()
+			httpserver.Errorf(w, r, "error in %q: %s", r.URL.Path, err)
+			return true
+		}
+		return true
 	case "/api/v1/rules":
 		// Return dumb placeholder
 		rulesRequests.Inc()
@@ -416,6 +445,12 @@ var (
 	graphiteMetricsIndexRequests = metrics.NewCounter(`vm_http_requests_total{path="/metrics/index.json"}`)
 	graphiteMetricsIndexErrors   = metrics.NewCounter(`vm_http_request_errors_total{path="/metrics/index.json"}`)
 
+	graphiteTagsTagSeriesRequests = metrics.NewCounter(`vm_http_requests_total{path="/tags/tagSeries"}`)
+	graphiteTagsTagSeriesErrors   = metrics.NewCounter(`vm_http_request_errors_total{path="/tags/tagSeries"}`)
+
+	graphiteTagsTagMultiSeriesRequests = metrics.NewCounter(`vm_http_requests_total{path="/tags/tagMultiSeries"}`)
+	graphiteTagsTagMultiSeriesErrors   = metrics.NewCounter(`vm_http_request_errors_total{path="/tags/tagMultiSeries"}`)
+
 	graphiteTagsRequests = metrics.NewCounter(`vm_http_requests_total{path="/tags"}`)
 	graphiteTagsErrors   = metrics.NewCounter(`vm_http_request_errors_total{path="/tags"}`)
 
@@ -431,6 +466,9 @@ var (
 	graphiteTagsAutoCompleteValuesRequests = metrics.NewCounter(`vm_http_requests_total{path="/tags/autoComplete/values"}`)
 	graphiteTagsAutoCompleteValuesErrors   = metrics.NewCounter(`vm_http_request_errors_total{path="/tags/autoComplete/values"}`)
 
+	graphiteTagsDelSeriesRequests = metrics.NewCounter(`vm_http_requests_total{path="/tags/delSeries"}`)
+	graphiteTagsDelSeriesErrors   = metrics.NewCounter(`vm_http_request_errors_total{path="/tags/delSeries"}`)
+
 	rulesRequests    = metrics.NewCounter(`vm_http_requests_total{path="/api/v1/rules"}`)
 	alertsRequests   = metrics.NewCounter(`vm_http_requests_total{path="/api/v1/alerts"}`)
 	metadataRequests = metrics.NewCounter(`vm_http_requests_total{path="/api/v1/metadata"}`)
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 44de82a62..66d91bd55 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -11,6 +11,8 @@
 * FEATURE: vminsert: export `vm_rpc_vmstorage_is_reachable` metric, which can be used for monitoring reachability of vmstorage nodes from vminsert nodes.
 * FEATURE: vmagent: add Netflix Eureka service discovery (aka [eureka_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#eureka_sd_config)).
 See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/851
+* FEATURE: add `filters` option to `dockerswarm_sd_config` like Prometheus did in v2.23.0 - see https://github.com/prometheus/prometheus/pull/8074
+* FEATURE: expose `__meta_ec2_ipv6_addresses` label for `ec2_sd_config` like Prometheus will do in the next release.
 * FEATURE: add `-loggerWarnsPerSecondLimit` command-line flag for rate limiting of WARN messages in logs. See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/905
 * FEATURE: apply `loggerErrorsPerSecondLimit` and `-loggerWarnsPerSecondLimit` rate limit per caller. I.e. log messages are suppressed if the same caller logs the same message
   at the rate exceeding the given limit. See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/905#issuecomment-729395855
diff --git a/docs/Cluster-VictoriaMetrics.md b/docs/Cluster-VictoriaMetrics.md
index fed3173d3..090601648 100644
--- a/docs/Cluster-VictoriaMetrics.md
+++ b/docs/Cluster-VictoriaMetrics.md
@@ -205,11 +205,14 @@ or [an alternative dashboard for VictoriaMetrics cluster](https://grafana.com/gr
       - `metrics/find` - searches Graphite metrics. See [these docs](https://graphite-api.readthedocs.io/en/latest/api.html#metrics-find).
       - `metrics/expand` - expands Graphite metrics. See [these docs](https://graphite-api.readthedocs.io/en/latest/api.html#metrics-expand).
       - `metrics/index.json` - returns all the metric names. See [these docs](https://graphite-api.readthedocs.io/en/latest/api.html#metrics-index-json).
+      - `tags/tagSeries` - registers time series. See [these docs](https://graphite.readthedocs.io/en/stable/tags.html#adding-series-to-the-tagdb).
+      - `tags/tagMultiSeries` - register multiple time series. See [these docs](https://graphite.readthedocs.io/en/stable/tags.html#adding-series-to-the-tagdb).
       - `tags` - returns tag names. See [these docs](https://graphite.readthedocs.io/en/stable/tags.html#exploring-tags).
       - `tags/<tag_name>` - returns tag values for the given `<tag_name>`. See [these docs](https://graphite.readthedocs.io/en/stable/tags.html#exploring-tags).
       - `tags/findSeries` - returns series matching the given `expr`. See [these docs](https://graphite.readthedocs.io/en/stable/tags.html#exploring-tags).
       - `tags/autoComplete/tags` - returns tags matching the given `tagPrefix` and/or `expr`. See [these docs](https://graphite.readthedocs.io/en/stable/tags.html#auto-complete-support).
       - `tags/autoComplete/values` - returns tag values matching the given `valuePrefix` and/or `expr`. See [these docs](https://graphite.readthedocs.io/en/stable/tags.html#auto-complete-support).
+      - `tags/delSeries` - deletes series matching the given `path`. See [these docs](https://graphite.readthedocs.io/en/stable/tags.html#removing-series-from-the-tagdb).
 
 * URL for time series deletion: `http://<vmselect>:8481/delete/<accountID>/prometheus/api/v1/admin/tsdb/delete_series?match[]=<timeseries_selector_for_delete>`.
   Note that the `delete_series` handler should be used only in exceptional cases such as deletion of accidentally ingested incorrect time series. It shouldn't
diff --git a/docs/Single-server-VictoriaMetrics.md b/docs/Single-server-VictoriaMetrics.md
index 85675eb54..ff7fb3fc2 100644
--- a/docs/Single-server-VictoriaMetrics.md
+++ b/docs/Single-server-VictoriaMetrics.md
@@ -566,6 +566,7 @@ VictoriaMetrics supports the following handlers from [Graphite Tags API](https:/
 * [/tags/findSeries](https://graphite.readthedocs.io/en/stable/tags.html#exploring-tags)
 * [/tags/autoComplete/tags](https://graphite.readthedocs.io/en/stable/tags.html#auto-complete-support)
 * [/tags/autoComplete/values](https://graphite.readthedocs.io/en/stable/tags.html#auto-complete-support)
+* [/tags/delSeries](https://graphite.readthedocs.io/en/stable/tags.html#removing-series-from-the-tagdb)
 
 
 ## How to build from sources
diff --git a/lib/promscrape/discovery/dockerswarm/api.go b/lib/promscrape/discovery/dockerswarm/api.go
index 25dffe207..3853600a4 100644
--- a/lib/promscrape/discovery/dockerswarm/api.go
+++ b/lib/promscrape/discovery/dockerswarm/api.go
@@ -1,8 +1,12 @@
 package dockerswarm
 
 import (
+	"encoding/json"
 	"fmt"
+	"net/url"
+	"strings"
 
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
 )
@@ -12,6 +16,9 @@ var configMap = discoveryutils.NewConfigMap()
 type apiConfig struct {
 	client *discoveryutils.Client
 	port   int
+
+	// filtersQueryArg contains escaped `filters` query arg to add to each request to Docker Swarm API.
+	filtersQueryArg string
 }
 
 func getAPIConfig(sdc *SDConfig, baseDir string) (*apiConfig, error) {
@@ -24,7 +31,8 @@ func getAPIConfig(sdc *SDConfig, baseDir string) (*apiConfig, error) {
 
 func newAPIConfig(sdc *SDConfig, baseDir string) (*apiConfig, error) {
 	cfg := &apiConfig{
-		port: sdc.Port,
+		port:            sdc.Port,
+		filtersQueryArg: getFiltersQueryArg(sdc.Filters),
 	}
 	ac, err := promauth.NewConfig(baseDir, sdc.BasicAuth, sdc.BearerToken, sdc.BearerTokenFile, sdc.TLSConfig)
 	if err != nil {
@@ -37,3 +45,36 @@ func newAPIConfig(sdc *SDConfig, baseDir string) (*apiConfig, error) {
 	cfg.client = client
 	return cfg, nil
 }
+
+func (cfg *apiConfig) getAPIResponse(path string) ([]byte, error) {
+	if len(cfg.filtersQueryArg) > 0 {
+		separator := "?"
+		if strings.Contains(path, "?") {
+			separator = "&"
+		}
+		path += separator + "filters=" + cfg.filtersQueryArg
+	}
+	return cfg.client.GetAPIResponse(path)
+}
+
+func getFiltersQueryArg(filters []Filter) string {
+	if len(filters) == 0 {
+		return ""
+	}
+	m := make(map[string]map[string]bool)
+	for _, f := range filters {
+		x := m[f.Name]
+		if x == nil {
+			x = make(map[string]bool)
+			m[f.Name] = x
+		}
+		for _, value := range f.Values {
+			x[value] = true
+		}
+	}
+	buf, err := json.Marshal(m)
+	if err != nil {
+		logger.Panicf("BUG: unexpected error in json.Marshal: %s", err)
+	}
+	return url.QueryEscape(string(buf))
+}
diff --git a/lib/promscrape/discovery/dockerswarm/dockerswarm.go b/lib/promscrape/discovery/dockerswarm/dockerswarm.go
index 3d49365a3..61b4b02f6 100644
--- a/lib/promscrape/discovery/dockerswarm/dockerswarm.go
+++ b/lib/promscrape/discovery/dockerswarm/dockerswarm.go
@@ -10,17 +10,25 @@ import (
 //
 // See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#dockerswarm_sd_config
 type SDConfig struct {
-	Host string `yaml:"host"`
+	Host    string   `yaml:"host"`
+	Role    string   `yaml:"role"`
+	Port    int      `yaml:"port,omitempty"`
+	Filters []Filter `yaml:"filters,omitempty"`
+
 	// TODO: add support for proxy_url
 	TLSConfig *promauth.TLSConfig `yaml:"tls_config,omitempty"`
-	Role      string              `yaml:"role"`
-	Port      int                 `yaml:"port,omitempty"`
 	// refresh_interval is obtained from `-promscrape.dockerswarmSDCheckInterval` command-line option
 	BasicAuth       *promauth.BasicAuthConfig `yaml:"basic_auth,omitempty"`
 	BearerToken     string                    `yaml:"bearer_token,omitempty"`
 	BearerTokenFile string                    `yaml:"bearer_token_file,omitempty"`
 }
 
+// Filter is a filter, which can be passed to SDConfig.
+type Filter struct {
+	Name   string   `yaml:"name"`
+	Values []string `yaml:"values"`
+}
+
 // GetLabels returns dockerswarm labels according to sdc.
 func GetLabels(sdc *SDConfig, baseDir string) ([]map[string]string, error) {
 	cfg, err := getAPIConfig(sdc, baseDir)
diff --git a/lib/promscrape/discovery/dockerswarm/network.go b/lib/promscrape/discovery/dockerswarm/network.go
index bcf21dd0a..27bb748cb 100644
--- a/lib/promscrape/discovery/dockerswarm/network.go
+++ b/lib/promscrape/discovery/dockerswarm/network.go
@@ -27,7 +27,7 @@ func getNetworksLabelsByNetworkID(cfg *apiConfig) (map[string]map[string]string,
 }
 
 func getNetworks(cfg *apiConfig) ([]network, error) {
-	resp, err := cfg.client.GetAPIResponse("/networks")
+	resp, err := cfg.getAPIResponse("/networks")
 	if err != nil {
 		return nil, fmt.Errorf("cannot query dockerswarm api for networks: %w", err)
 	}
diff --git a/lib/promscrape/discovery/dockerswarm/nodes.go b/lib/promscrape/discovery/dockerswarm/nodes.go
index d5eec44cc..c6db715f2 100644
--- a/lib/promscrape/discovery/dockerswarm/nodes.go
+++ b/lib/promscrape/discovery/dockerswarm/nodes.go
@@ -46,7 +46,7 @@ func getNodesLabels(cfg *apiConfig) ([]map[string]string, error) {
 }
 
 func getNodes(cfg *apiConfig) ([]node, error) {
-	resp, err := cfg.client.GetAPIResponse("/nodes")
+	resp, err := cfg.getAPIResponse("/nodes")
 	if err != nil {
 		return nil, fmt.Errorf("cannot query dockerswarm api for nodes: %w", err)
 	}
diff --git a/lib/promscrape/discovery/dockerswarm/services.go b/lib/promscrape/discovery/dockerswarm/services.go
index 147c610cc..29e4f0dcb 100644
--- a/lib/promscrape/discovery/dockerswarm/services.go
+++ b/lib/promscrape/discovery/dockerswarm/services.go
@@ -59,7 +59,7 @@ func getServicesLabels(cfg *apiConfig) ([]map[string]string, error) {
 }
 
 func getServices(cfg *apiConfig) ([]service, error) {
-	data, err := cfg.client.GetAPIResponse("/services")
+	data, err := cfg.getAPIResponse("/services")
 	if err != nil {
 		return nil, fmt.Errorf("cannot query dockerswarm api for services: %w", err)
 	}
diff --git a/lib/promscrape/discovery/dockerswarm/tasks.go b/lib/promscrape/discovery/dockerswarm/tasks.go
index e8724c2cb..ecf69e4a5 100644
--- a/lib/promscrape/discovery/dockerswarm/tasks.go
+++ b/lib/promscrape/discovery/dockerswarm/tasks.go
@@ -58,7 +58,7 @@ func getTasksLabels(cfg *apiConfig) ([]map[string]string, error) {
 }
 
 func getTasks(cfg *apiConfig) ([]task, error) {
-	resp, err := cfg.client.GetAPIResponse("/tasks")
+	resp, err := cfg.getAPIResponse("/tasks")
 	if err != nil {
 		return nil, fmt.Errorf("cannot query dockerswarm api for tasks: %w", err)
 	}
diff --git a/lib/promscrape/discovery/ec2/instance.go b/lib/promscrape/discovery/ec2/instance.go
index 7dbf6203a..45ccfb0cf 100644
--- a/lib/promscrape/discovery/ec2/instance.go
+++ b/lib/promscrape/discovery/ec2/instance.go
@@ -104,7 +104,13 @@ type NetworkInterfaceSet struct {
 
 // NetworkInterface represents NetworkInterface from https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_InstanceNetworkInterface.html
 type NetworkInterface struct {
-	SubnetID string `xml:"subnetId"`
+	SubnetID         string           `xml:"subnetId"`
+	IPv6AddressesSet Ipv6AddressesSet `xml:"ipv6AddressesSet"`
+}
+
+// Ipv6AddressesSet represents ipv6AddressesSet from https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_InstanceNetworkInterface.html
+type Ipv6AddressesSet struct {
+	Items []string `xml:"item"`
 }
 
 // TagSet represents TagSet from https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_Instance.html
@@ -151,21 +157,27 @@ func (inst *Instance) appendTargetLabels(ms []map[string]string, ownerID string,
 		"__meta_ec2_vpc_id":             inst.VPCID,
 	}
 	if len(inst.VPCID) > 0 {
-		// Deduplicate VPC Subnet IDs maintaining the order of the network interfaces returned by EC2.
 		subnets := make([]string, 0, len(inst.NetworkInterfaceSet.Items))
 		seenSubnets := make(map[string]bool, len(inst.NetworkInterfaceSet.Items))
+		var ipv6Addrs []string
 		for _, ni := range inst.NetworkInterfaceSet.Items {
 			if len(ni.SubnetID) == 0 {
 				continue
 			}
+			// Deduplicate VPC Subnet IDs maintaining the order of the network interfaces returned by EC2.
 			if !seenSubnets[ni.SubnetID] {
 				seenSubnets[ni.SubnetID] = true
 				subnets = append(subnets, ni.SubnetID)
 			}
+			// Collect ipv6 addresses
+			ipv6Addrs = append(ipv6Addrs, ni.IPv6AddressesSet.Items...)
 		}
 		// We surround the separated list with the separator as well. This way regular expressions
 		// in relabeling rules don't have to consider tag positions.
 		m["__meta_ec2_subnet_id"] = "," + strings.Join(subnets, ",") + ","
+		if len(ipv6Addrs) > 0 {
+			m["__meta_ec2_ipv6_addresses"] = "," + strings.Join(ipv6Addrs, ",") + ","
+		}
 	}
 	for _, t := range inst.TagSet.Items {
 		if len(t.Key) == 0 || len(t.Value) == 0 {


Test patch:
diff --git a/lib/promscrape/discovery/dockerswarm/api_test.go b/lib/promscrape/discovery/dockerswarm/api_test.go
new file mode 100644
index 000000000..fc8888ee5
--- /dev/null
+++ b/lib/promscrape/discovery/dockerswarm/api_test.go
@@ -0,0 +1,26 @@
+package dockerswarm
+
+import (
+	"testing"
+)
+
+func TestGetFiltersQueryArg(t *testing.T) {
+	f := func(filters []Filter, queryArgExpected string) {
+		t.Helper()
+		queryArg := getFiltersQueryArg(filters)
+		if queryArg != queryArgExpected {
+			t.Fatalf("unexpected query arg; got %s; want %s", queryArg, queryArgExpected)
+		}
+	}
+	f(nil, "")
+	f([]Filter{
+		{
+			Name:   "name",
+			Values: []string{"foo", "bar"},
+		},
+		{
+			Name:   "xxx",
+			Values: []string{"aa"},
+		},
+	}, "%7B%22name%22%3A%7B%22bar%22%3Atrue%2C%22foo%22%3Atrue%7D%2C%22xxx%22%3A%7B%22aa%22%3Atrue%7D%7D")
+}