Repository: VictoriaMetrics/VictoriaMetrics
Pull request #5503 (2023-12-20 12:56:20)

docs: clarify information about values
Described information in the Key Concepts about the label value where the when the value converts to the `float64`

Related issue: https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5485

Signed-off-by: dmitryk-dk [d.kozlov@victoriametrics.com](mailto:d.kozlov@victoriametrics.com)


Patch:
diff --git a/README.md b/README.md
index e81401aee..ca62eff47 100644
--- a/README.md
+++ b/README.md
@@ -516,10 +516,8 @@ See also [vmagent](https://docs.victoriametrics.com/vmagent.html), which can be
 
 ## How to send data from DataDog agent
 
-VictoriaMetrics accepts data from [DataDog agent](https://docs.datadoghq.com/agent/)
-or [DogStatsD](https://docs.datadoghq.com/developers/dogstatsd/)
-via ["submit metrics" API](https://docs.datadoghq.com/api/latest/metrics/#submit-metrics)
-at `/datadog/api/v1/series` path.
+VictoriaMetrics accepts data from [DataDog agent](https://docs.datadoghq.com/agent/) or [DogStatsD](https://docs.datadoghq.com/developers/dogstatsd/)
+via ["submit metrics" API](https://docs.datadoghq.com/api/latest/metrics/#submit-metrics) at `/datadog/api/v2/series` path.
 
 ### Sending metrics to VictoriaMetrics
 
@@ -531,12 +529,11 @@ or via [configuration file](https://docs.datadoghq.com/agent/guide/agent-configu
 </p>
 
 To configure DataDog agent via ENV variable add the following prefix:
-<div class="with-copy" markdown="1">
 
+<div class="with-copy" markdown="1">
 ```
 DD_DD_URL=http://victoriametrics:8428/datadog
 ```
-
 </div>
 
 _Choose correct URL for VictoriaMetrics [here](https://docs.victoriametrics.com/url-examples.html#datadog)._
@@ -545,14 +542,12 @@ To configure DataDog agent via [configuration file](https://github.com/DataDog/d
 add the following line:
 
 <div class="with-copy" markdown="1">
-
 ```
 dd_url: http://victoriametrics:8428/datadog
 ```
-
 </div>
 
-vmagent also can accept Datadog metrics format. Depending on where vmagent will forward data, 
+[vmagent](https://docs.victoriametrics.com/vmagent.html) also can accept Datadog metrics format. Depending on where vmagent will forward data,
 pick [single-node or cluster URL](https://docs.victoriametrics.com/url-examples.html#datadog) formats.
 
 ### Sending metrics to Datadog and VictoriaMetrics
@@ -567,12 +562,10 @@ sending via ENV variable `DD_ADDITIONAL_ENDPOINTS` or via configuration file `ad
 Run DataDog using the following ENV variable with VictoriaMetrics as additional metrics receiver:
 
 <div class="with-copy" markdown="1">
-
 ```
 DD_ADDITIONAL_ENDPOINTS='{\"http://victoriametrics:8428/datadog\": [\"apikey\"]}'
 
 ```
-
 </div>
 
 _Choose correct URL for VictoriaMetrics [here](https://docs.victoriametrics.com/url-examples.html#datadog)._
@@ -582,19 +575,16 @@ To configure DataDog Dual Shipping via [configuration file](https://docs.datadog
 add the following line:
 
 <div class="with-copy" markdown="1">
-
 ```
 additional_endpoints:
   "http://victoriametrics:8428/datadog":
   - apikey
 ```
-
 </div>
 
 ### Send via cURL
 
-See how to send data to VictoriaMetrics via 
-[DataDog "submit metrics"](https://docs.victoriametrics.com/url-examples.html#datadogapiv1series) from command line.
+See how to send data to VictoriaMetrics via DataDog "submit metrics" API [here](https://docs.victoriametrics.com/url-examples.html#datadogapiv2series).
 
 The imported data can be read via [export API](https://docs.victoriametrics.com/url-examples.html#apiv1export).
 
@@ -605,7 +595,7 @@ according to [DataDog metric naming recommendations](https://docs.datadoghq.com/
 If you need accepting metric names as is without sanitizing, then pass `-datadog.sanitizeMetricName=false` command-line flag to VictoriaMetrics.
 
 Extra labels may be added to all the written time series by passing `extra_label=name=value` query args.
-For example, `/datadog/api/v1/series?extra_label=foo=bar` would add `{foo="bar"}` label to all the ingested metrics.
+For example, `/datadog/api/v2/series?extra_label=foo=bar` would add `{foo="bar"}` label to all the ingested metrics.
 
 DataDog agent sends the [configured tags](https://docs.datadoghq.com/getting_started/tagging/) to
 undocumented endpoint - `/datadog/intake`. This endpoint isn't supported by VictoriaMetrics yet.
@@ -2580,7 +2570,7 @@ Pass `-help` to VictoriaMetrics in order to see the list of supported command-li
   -csvTrimTimestamp duration
      Trim timestamps when importing csv data to this duration. Minimum practical duration is 1ms. Higher duration (i.e. 1s) may be used for reducing disk space usage for timestamp data (default 1ms)
   -datadog.maxInsertRequestSize size
-     The maximum size in bytes of a single DataDog POST request to /api/v1/series
+     The maximum size in bytes of a single DataDog POST request to /datadog/api/v2/series
      Supports the following optional suffixes for size values: KB, MB, GB, TB, KiB, MiB, GiB, TiB (default 67108864)
   -datadog.sanitizeMetricName
      Sanitize metric names for the ingested DataDog data to comply with DataDog behaviour described at https://docs.datadoghq.com/metrics/custom_metrics/#naming-custom-metrics (default true)
diff --git a/app/vmagent/datadog/request_handler.go b/app/vmagent/datadogv1/request_handler.go
similarity index 85%
rename from app/vmagent/datadog/request_handler.go
rename to app/vmagent/datadogv1/request_handler.go
index 4cdfe1093..722cdffb3 100644
--- a/app/vmagent/datadog/request_handler.go
+++ b/app/vmagent/datadogv1/request_handler.go
@@ -1,4 +1,4 @@
-package datadog
+package datadogv1
 
 import (
 	"net/http"
@@ -8,33 +8,32 @@ import (
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/auth"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
 	parserCommon "github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/common"
-	"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/datadog"
-	"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/datadog/stream"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/datadogutils"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/datadogv1"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/datadogv1/stream"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/tenantmetrics"
 	"github.com/VictoriaMetrics/metrics"
 )
 
 var (
-	rowsInserted       = metrics.NewCounter(`vmagent_rows_inserted_total{type="datadog"}`)
-	rowsTenantInserted = tenantmetrics.NewCounterMap(`vmagent_tenant_inserted_rows_total{type="datadog"}`)
-	rowsPerInsert      = metrics.NewHistogram(`vmagent_rows_per_insert{type="datadog"}`)
+	rowsInserted       = metrics.NewCounter(`vmagent_rows_inserted_total{type="datadogv1"}`)
+	rowsTenantInserted = tenantmetrics.NewCounterMap(`vmagent_tenant_inserted_rows_total{type="datadogv1"}`)
+	rowsPerInsert      = metrics.NewHistogram(`vmagent_rows_per_insert{type="datadogv1"}`)
 )
 
 // InsertHandlerForHTTP processes remote write for DataDog POST /api/v1/series request.
-//
-// See https://docs.datadoghq.com/api/latest/metrics/#submit-metrics
 func InsertHandlerForHTTP(at *auth.Token, req *http.Request) error {
 	extraLabels, err := parserCommon.GetExtraLabels(req)
 	if err != nil {
 		return err
 	}
 	ce := req.Header.Get("Content-Encoding")
-	return stream.Parse(req.Body, ce, func(series []datadog.Series) error {
+	return stream.Parse(req.Body, ce, func(series []datadogv1.Series) error {
 		return insertRows(at, series, extraLabels)
 	})
 }
 
-func insertRows(at *auth.Token, series []datadog.Series, extraLabels []prompbmarshal.Label) error {
+func insertRows(at *auth.Token, series []datadogv1.Series, extraLabels []prompbmarshal.Label) error {
 	ctx := common.GetPushCtx()
 	defer common.PutPushCtx(ctx)
 
@@ -63,7 +62,7 @@ func insertRows(at *auth.Token, series []datadog.Series, extraLabels []prompbmar
 			})
 		}
 		for _, tag := range ss.Tags {
-			name, value := datadog.SplitTag(tag)
+			name, value := datadogutils.SplitTag(tag)
 			if name == "host" {
 				name = "exported_host"
 			}
diff --git a/app/vmagent/datadogv2/request_handler.go b/app/vmagent/datadogv2/request_handler.go
new file mode 100644
index 000000000..917df7efc
--- /dev/null
+++ b/app/vmagent/datadogv2/request_handler.go
@@ -0,0 +1,102 @@
+package datadogv2
+
+import (
+	"net/http"
+
+	"github.com/VictoriaMetrics/VictoriaMetrics/app/vmagent/common"
+	"github.com/VictoriaMetrics/VictoriaMetrics/app/vmagent/remotewrite"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/auth"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
+	parserCommon "github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/common"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/datadogutils"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/datadogv2"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/datadogv2/stream"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/tenantmetrics"
+	"github.com/VictoriaMetrics/metrics"
+)
+
+var (
+	rowsInserted       = metrics.NewCounter(`vmagent_rows_inserted_total{type="datadogv2"}`)
+	rowsTenantInserted = tenantmetrics.NewCounterMap(`vmagent_tenant_inserted_rows_total{type="datadogv2"}`)
+	rowsPerInsert      = metrics.NewHistogram(`vmagent_rows_per_insert{type="datadogv2"}`)
+)
+
+// InsertHandlerForHTTP processes remote write for DataDog POST /api/v2/series request.
+//
+// See https://docs.datadoghq.com/api/latest/metrics/#submit-metrics
+func InsertHandlerForHTTP(at *auth.Token, req *http.Request) error {
+	extraLabels, err := parserCommon.GetExtraLabels(req)
+	if err != nil {
+		return err
+	}
+	ct := req.Header.Get("Content-Type")
+	ce := req.Header.Get("Content-Encoding")
+	return stream.Parse(req.Body, ce, ct, func(series []datadogv2.Series) error {
+		return insertRows(at, series, extraLabels)
+	})
+}
+
+func insertRows(at *auth.Token, series []datadogv2.Series, extraLabels []prompbmarshal.Label) error {
+	ctx := common.GetPushCtx()
+	defer common.PutPushCtx(ctx)
+
+	rowsTotal := 0
+	tssDst := ctx.WriteRequest.Timeseries[:0]
+	labels := ctx.Labels[:0]
+	samples := ctx.Samples[:0]
+	for i := range series {
+		ss := &series[i]
+		rowsTotal += len(ss.Points)
+		labelsLen := len(labels)
+		labels = append(labels, prompbmarshal.Label{
+			Name:  "__name__",
+			Value: ss.Metric,
+		})
+		for _, rs := range ss.Resources {
+			labels = append(labels, prompbmarshal.Label{
+				Name:  rs.Type,
+				Value: rs.Name,
+			})
+		}
+		if ss.SourceTypeName != "" {
+			labels = append(labels, prompbmarshal.Label{
+				Name:  "source_type_name",
+				Value: ss.SourceTypeName,
+			})
+		}
+		for _, tag := range ss.Tags {
+			name, value := datadogutils.SplitTag(tag)
+			if name == "host" {
+				name = "exported_host"
+			}
+			labels = append(labels, prompbmarshal.Label{
+				Name:  name,
+				Value: value,
+			})
+		}
+		labels = append(labels, extraLabels...)
+		samplesLen := len(samples)
+		for _, pt := range ss.Points {
+			samples = append(samples, prompbmarshal.Sample{
+				Timestamp: pt.Timestamp * 1000,
+				Value:     pt.Value,
+			})
+		}
+		tssDst = append(tssDst, prompbmarshal.TimeSeries{
+			Labels:  labels[labelsLen:],
+			Samples: samples[samplesLen:],
+		})
+	}
+	ctx.WriteRequest.Timeseries = tssDst
+	ctx.Labels = labels
+	ctx.Samples = samples
+	if !remotewrite.TryPush(at, &ctx.WriteRequest) {
+		return remotewrite.ErrQueueFullHTTPRetry
+	}
+	rowsInserted.Add(rowsTotal)
+	if at != nil {
+		rowsTenantInserted.Get(at).Add(rowsTotal)
+	}
+	rowsPerInsert.Update(float64(rowsTotal))
+	return nil
+}
diff --git a/app/vmagent/main.go b/app/vmagent/main.go
index 7aa82de5a..9f66d54e8 100644
--- a/app/vmagent/main.go
+++ b/app/vmagent/main.go
@@ -12,7 +12,8 @@ import (
 	"time"
 
 	"github.com/VictoriaMetrics/VictoriaMetrics/app/vmagent/csvimport"
-	"github.com/VictoriaMetrics/VictoriaMetrics/app/vmagent/datadog"
+	"github.com/VictoriaMetrics/VictoriaMetrics/app/vmagent/datadogv1"
+	"github.com/VictoriaMetrics/VictoriaMetrics/app/vmagent/datadogv2"
 	"github.com/VictoriaMetrics/VictoriaMetrics/app/vmagent/graphite"
 	"github.com/VictoriaMetrics/VictoriaMetrics/app/vmagent/influx"
 	"github.com/VictoriaMetrics/VictoriaMetrics/app/vmagent/native"
@@ -343,9 +344,20 @@ func requestHandler(w http.ResponseWriter, r *http.Request) bool {
 		fmt.Fprintf(w, `{"status":"ok"}`)
 		return true
 	case "/datadog/api/v1/series":
-		datadogWriteRequests.Inc()
-		if err := datadog.InsertHandlerForHTTP(nil, r); err != nil {
-			datadogWriteErrors.Inc()
+		datadogv1WriteRequests.Inc()
+		if err := datadogv1.InsertHandlerForHTTP(nil, r); err != nil {
+			datadogv1WriteErrors.Inc()
+			httpserver.Errorf(w, r, "%s", err)
+			return true
+		}
+		w.Header().Set("Content-Type", "application/json")
+		w.WriteHeader(202)
+		fmt.Fprintf(w, `{"status":"ok"}`)
+		return true
+	case "/datadog/api/v2/series":
+		datadogv2WriteRequests.Inc()
+		if err := datadogv2.InsertHandlerForHTTP(nil, r); err != nil {
+			datadogv2WriteErrors.Inc()
 			httpserver.Errorf(w, r, "%s", err)
 			return true
 		}
@@ -566,9 +578,19 @@ func processMultitenantRequest(w http.ResponseWriter, r *http.Request, path stri
 		fmt.Fprintf(w, `{"status":"ok"}`)
 		return true
 	case "datadog/api/v1/series":
-		datadogWriteRequests.Inc()
-		if err := datadog.InsertHandlerForHTTP(at, r); err != nil {
-			datadogWriteErrors.Inc()
+		datadogv1WriteRequests.Inc()
+		if err := datadogv1.InsertHandlerForHTTP(at, r); err != nil {
+			datadogv1WriteErrors.Inc()
+			httpserver.Errorf(w, r, "%s", err)
+			return true
+		}
+		w.WriteHeader(202)
+		fmt.Fprintf(w, `{"status":"ok"}`)
+		return true
+	case "datadog/api/v2/series":
+		datadogv2WriteRequests.Inc()
+		if err := datadogv2.InsertHandlerForHTTP(at, r); err != nil {
+			datadogv2WriteErrors.Inc()
 			httpserver.Errorf(w, r, "%s", err)
 			return true
 		}
@@ -626,8 +648,11 @@ var (
 
 	influxQueryRequests = metrics.NewCounter(`vmagent_http_requests_total{path="/influx/query", protocol="influx"}`)
 
-	datadogWriteRequests = metrics.NewCounter(`vmagent_http_requests_total{path="/datadog/api/v1/series", protocol="datadog"}`)
-	datadogWriteErrors   = metrics.NewCounter(`vmagent_http_request_errors_total{path="/datadog/api/v1/series", protocol="datadog"}`)
+	datadogv1WriteRequests = metrics.NewCounter(`vmagent_http_requests_total{path="/datadog/api/v1/series", protocol="datadog"}`)
+	datadogv1WriteErrors   = metrics.NewCounter(`vmagent_http_request_errors_total{path="/datadog/api/v1/series", protocol="datadog"}`)
+
+	datadogv2WriteRequests = metrics.NewCounter(`vmagent_http_requests_total{path="/datadog/api/v2/series", protocol="datadog"}`)
+	datadogv2WriteErrors   = metrics.NewCounter(`vmagent_http_request_errors_total{path="/datadog/api/v2/series", protocol="datadog"}`)
 
 	datadogValidateRequests = metrics.NewCounter(`vmagent_http_requests_total{path="/datadog/api/v1/validate", protocol="datadog"}`)
 	datadogCheckRunRequests = metrics.NewCounter(`vmagent_http_requests_total{path="/datadog/api/v1/check_run", protocol="datadog"}`)
diff --git a/app/vmagent/remotewrite/client.go b/app/vmagent/remotewrite/client.go
index 94e6ad3ff..370fa853a 100644
--- a/app/vmagent/remotewrite/client.go
+++ b/app/vmagent/remotewrite/client.go
@@ -58,8 +58,10 @@ var (
 	oauth2ClientID         = flagutil.NewArrayString("remoteWrite.oauth2.clientID", "Optional OAuth2 clientID to use for the corresponding -remoteWrite.url")
 	oauth2ClientSecret     = flagutil.NewArrayString("remoteWrite.oauth2.clientSecret", "Optional OAuth2 clientSecret to use for the corresponding -remoteWrite.url")
 	oauth2ClientSecretFile = flagutil.NewArrayString("remoteWrite.oauth2.clientSecretFile", "Optional OAuth2 clientSecretFile to use for the corresponding -remoteWrite.url")
-	oauth2TokenURL         = flagutil.NewArrayString("remoteWrite.oauth2.tokenUrl", "Optional OAuth2 tokenURL to use for the corresponding -remoteWrite.url")
-	oauth2Scopes           = flagutil.NewArrayString("remoteWrite.oauth2.scopes", "Optional OAuth2 scopes to use for the corresponding -remoteWrite.url. Scopes must be delimited by ';'")
+	oauth2EndpointParams   = flagutil.NewArrayString("remoteWrite.oauth2.endpointParams", "Optional OAuth2 endpoint parameters to use for the corresponding -remoteWrite.url . "+
+		`The endpoint parameters must be set in JSON format: {"param1":"value1",...,"paramN":"valueN"}`)
+	oauth2TokenURL = flagutil.NewArrayString("remoteWrite.oauth2.tokenUrl", "Optional OAuth2 tokenURL to use for the corresponding -remoteWrite.url")
+	oauth2Scopes   = flagutil.NewArrayString("remoteWrite.oauth2.scopes", "Optional OAuth2 scopes to use for the corresponding -remoteWrite.url. Scopes must be delimited by ';'")
 
 	awsUseSigv4 = flagutil.NewArrayBool("remoteWrite.aws.useSigv4", "Enables SigV4 request signing for the corresponding -remoteWrite.url. "+
 		"It is expected that other -remoteWrite.aws.* command-line flags are set if sigv4 request signing is enabled")
@@ -234,10 +236,16 @@ func getAuthConfig(argIdx int) (*promauth.Config, error) {
 	clientSecret := oauth2ClientSecret.GetOptionalArg(argIdx)
 	clientSecretFile := oauth2ClientSecretFile.GetOptionalArg(argIdx)
 	if clientSecretFile != "" || clientSecret != "" {
+		endpointParamsJSON := oauth2EndpointParams.GetOptionalArg(argIdx)
+		endpointParams, err := flagutil.ParseJSONMap(endpointParamsJSON)
+		if err != nil {
+			return nil, fmt.Errorf("cannot parse JSON for -remoteWrite.oauth2.endpointParams=%s: %w", endpointParamsJSON, err)
+		}
 		oauth2Cfg = &promauth.OAuth2Config{
 			ClientID:         oauth2ClientID.GetOptionalArg(argIdx),
 			ClientSecret:     promauth.NewSecret(clientSecret),
 			ClientSecretFile: clientSecretFile,
+			EndpointParams:   endpointParams,
 			TokenURL:         oauth2TokenURL.GetOptionalArg(argIdx),
 			Scopes:           strings.Split(oauth2Scopes.GetOptionalArg(argIdx), ";"),
 		}
diff --git a/app/vmalert/datasource/init.go b/app/vmalert/datasource/init.go
index faa015013..9738999b3 100644
--- a/app/vmalert/datasource/init.go
+++ b/app/vmalert/datasource/init.go
@@ -37,11 +37,13 @@ var (
 	tlsCAFile             = flag.String("datasource.tlsCAFile", "", `Optional path to TLS CA file to use for verifying connections to -datasource.url. By default, system CA is used`)
 	tlsServerName         = flag.String("datasource.tlsServerName", "", `Optional TLS server name to use for connections to -datasource.url. By default, the server name from -datasource.url is used`)
 
-	oauth2ClientID         = flag.String("datasource.oauth2.clientID", "", "Optional OAuth2 clientID to use for -datasource.url. ")
-	oauth2ClientSecret     = flag.String("datasource.oauth2.clientSecret", "", "Optional OAuth2 clientSecret to use for -datasource.url.")
-	oauth2ClientSecretFile = flag.String("datasource.oauth2.clientSecretFile", "", "Optional OAuth2 clientSecretFile to use for -datasource.url. ")
-	oauth2TokenURL         = flag.String("datasource.oauth2.tokenUrl", "", "Optional OAuth2 tokenURL to use for -datasource.url.")
-	oauth2Scopes           = flag.String("datasource.oauth2.scopes", "", "Optional OAuth2 scopes to use for -datasource.url. Scopes must be delimited by ';'")
+	oauth2ClientID         = flag.String("datasource.oauth2.clientID", "", "Optional OAuth2 clientID to use for -datasource.url")
+	oauth2ClientSecret     = flag.String("datasource.oauth2.clientSecret", "", "Optional OAuth2 clientSecret to use for -datasource.url")
+	oauth2ClientSecretFile = flag.String("datasource.oauth2.clientSecretFile", "", "Optional OAuth2 clientSecretFile to use for -datasource.url")
+	oauth2EndpointParams   = flag.String("datasource.oauth2.endpointParams", "", "Optional OAuth2 endpoint parameters to use for -datasource.url . "+
+		`The endpoint parameters must be set in JSON format: {"param1":"value1",...,"paramN":"valueN"}`)
+	oauth2TokenURL = flag.String("datasource.oauth2.tokenUrl", "", "Optional OAuth2 tokenURL to use for -datasource.url")
+	oauth2Scopes   = flag.String("datasource.oauth2.scopes", "", "Optional OAuth2 scopes to use for -datasource.url. Scopes must be delimited by ';'")
 
 	lookBack = flag.Duration("datasource.lookback", 0, `Will be deprecated soon, please adjust "-search.latencyOffset"  at datasource side `+
 		`or specify "latency_offset" in rule group's params. Lookback defines how far into the past to look when evaluating queries. `+
@@ -108,10 +110,14 @@ func Init(extraParams url.Values) (QuerierBuilder, error) {
 		extraParams.Set("round_digits", fmt.Sprintf("%d", *roundDigits))
 	}
 
+	endpointParams, err := flagutil.ParseJSONMap(*oauth2EndpointParams)
+	if err != nil {
+		return nil, fmt.Errorf("cannot parse JSON for -datasource.oauth2.endpointParams=%s: %w", *oauth2EndpointParams, err)
+	}
 	authCfg, err := utils.AuthConfig(
 		utils.WithBasicAuth(*basicAuthUsername, *basicAuthPassword, *basicAuthPasswordFile),
 		utils.WithBearer(*bearerToken, *bearerTokenFile),
-		utils.WithOAuth(*oauth2ClientID, *oauth2ClientSecret, *oauth2ClientSecretFile, *oauth2TokenURL, *oauth2Scopes),
+		utils.WithOAuth(*oauth2ClientID, *oauth2ClientSecret, *oauth2ClientSecretFile, *oauth2TokenURL, *oauth2Scopes, endpointParams),
 		utils.WithHeaders(*headers))
 	if err != nil {
 		return nil, fmt.Errorf("failed to configure auth: %w", err)
diff --git a/app/vmalert/notifier/alertmanager.go b/app/vmalert/notifier/alertmanager.go
index 3352bda25..35764be3c 100644
--- a/app/vmalert/notifier/alertmanager.go
+++ b/app/vmalert/notifier/alertmanager.go
@@ -144,7 +144,7 @@ func NewAlertManager(alertManagerURL string, fn AlertURLGenerator, authCfg proma
 	aCfg, err := utils.AuthConfig(
 		utils.WithBasicAuth(ba.Username, ba.Password.String(), ba.PasswordFile),
 		utils.WithBearer(authCfg.BearerToken.String(), authCfg.BearerTokenFile),
-		utils.WithOAuth(oauth.ClientID, oauth.ClientSecretFile, oauth.ClientSecretFile, oauth.TokenURL, strings.Join(oauth.Scopes, ";")))
+		utils.WithOAuth(oauth.ClientID, oauth.ClientSecretFile, oauth.ClientSecretFile, oauth.TokenURL, strings.Join(oauth.Scopes, ";"), oauth.EndpointParams))
 	if err != nil {
 		return nil, fmt.Errorf("failed to configure auth: %w", err)
 	}
diff --git a/app/vmalert/notifier/init.go b/app/vmalert/notifier/init.go
index 6972effb9..6d29ef238 100644
--- a/app/vmalert/notifier/init.go
+++ b/app/vmalert/notifier/init.go
@@ -46,6 +46,8 @@ var (
 		"If multiple args are set, then they are applied independently for the corresponding -notifier.url")
 	oauth2ClientSecretFile = flagutil.NewArrayString("notifier.oauth2.clientSecretFile", "Optional OAuth2 clientSecretFile to use for -notifier.url. "+
 		"If multiple args are set, then they are applied independently for the corresponding -notifier.url")
+	oauth2EndpointParams = flagutil.NewArrayString("notifier.oauth2.endpointParams", "Optional OAuth2 endpoint parameters to use for the corresponding -notifier.url . "+
+		`The endpoint parameters must be set in JSON format: {"param1":"value1",...,"paramN":"valueN"}`)
 	oauth2TokenURL = flagutil.NewArrayString("notifier.oauth2.tokenUrl", "Optional OAuth2 tokenURL to use for -notifier.url. "+
 		"If multiple args are set, then they are applied independently for the corresponding -notifier.url")
 	oauth2Scopes = flagutil.NewArrayString("notifier.oauth2.scopes", "Optional OAuth2 scopes to use for -notifier.url. Scopes must be delimited by ';'. "+
@@ -141,6 +143,11 @@ func InitSecretFlags() {
 func notifiersFromFlags(gen AlertURLGenerator) ([]Notifier, error) {
 	var notifiers []Notifier
 	for i, addr := range *addrs {
+		endpointParamsJSON := oauth2EndpointParams.GetOptionalArg(i)
+		endpointParams, err := flagutil.ParseJSONMap(endpointParamsJSON)
+		if err != nil {
+			return nil, fmt.Errorf("cannot parse JSON for -notifier.oauth2.endpointParams=%s: %w", endpointParamsJSON, err)
+		}
 		authCfg := promauth.HTTPClientConfig{
 			TLSConfig: &promauth.TLSConfig{
 				CAFile:             tlsCAFile.GetOptionalArg(i),
@@ -160,6 +167,7 @@ func notifiersFromFlags(gen AlertURLGenerator) ([]Notifier, error) {
 				ClientID:         oauth2ClientID.GetOptionalArg(i),
 				ClientSecret:     promauth.NewSecret(oauth2ClientSecret.GetOptionalArg(i)),
 				ClientSecretFile: oauth2ClientSecretFile.GetOptionalArg(i),
+				EndpointParams:   endpointParams,
 				Scopes:           strings.Split(oauth2Scopes.GetOptionalArg(i), ";"),
 				TokenURL:         oauth2TokenURL.GetOptionalArg(i),
 			},
diff --git a/app/vmalert/remoteread/init.go b/app/vmalert/remoteread/init.go
index 417462b7c..10f0bcd1c 100644
--- a/app/vmalert/remoteread/init.go
+++ b/app/vmalert/remoteread/init.go
@@ -41,8 +41,10 @@ var (
 	oauth2ClientID         = flag.String("remoteRead.oauth2.clientID", "", "Optional OAuth2 clientID to use for -remoteRead.url.")
 	oauth2ClientSecret     = flag.String("remoteRead.oauth2.clientSecret", "", "Optional OAuth2 clientSecret to use for -remoteRead.url.")
 	oauth2ClientSecretFile = flag.String("remoteRead.oauth2.clientSecretFile", "", "Optional OAuth2 clientSecretFile to use for -remoteRead.url.")
-	oauth2TokenURL         = flag.String("remoteRead.oauth2.tokenUrl", "", "Optional OAuth2 tokenURL to use for -remoteRead.url. ")
-	oauth2Scopes           = flag.String("remoteRead.oauth2.scopes", "", "Optional OAuth2 scopes to use for -remoteRead.url. Scopes must be delimited by ';'.")
+	oauth2EndpointParams   = flag.String("remoteRead.oauth2.endpointParams", "", "Optional OAuth2 endpoint parameters to use for -remoteRead.url . "+
+		`The endpoint parameters must be set in JSON format: {"param1":"value1",...,"paramN":"valueN"}`)
+	oauth2TokenURL = flag.String("remoteRead.oauth2.tokenUrl", "", "Optional OAuth2 tokenURL to use for -remoteRead.url. ")
+	oauth2Scopes   = flag.String("remoteRead.oauth2.scopes", "", "Optional OAuth2 scopes to use for -remoteRead.url. Scopes must be delimited by ';'.")
 )
 
 // InitSecretFlags must be called after flag.Parse and before any logging
@@ -63,10 +65,14 @@ func Init() (datasource.QuerierBuilder, error) {
 		return nil, fmt.Errorf("failed to create transport: %w", err)
 	}
 
+	endpointParams, err := flagutil.ParseJSONMap(*oauth2EndpointParams)
+	if err != nil {
+		return nil, fmt.Errorf("cannot parse JSON for -remoteRead.oauth2.endpointParams=%s: %w", *oauth2EndpointParams, err)
+	}
 	authCfg, err := utils.AuthConfig(
 		utils.WithBasicAuth(*basicAuthUsername, *basicAuthPassword, *basicAuthPasswordFile),
 		utils.WithBearer(*bearerToken, *bearerTokenFile),
-		utils.WithOAuth(*oauth2ClientID, *oauth2ClientSecret, *oauth2ClientSecretFile, *oauth2TokenURL, *oauth2Scopes),
+		utils.WithOAuth(*oauth2ClientID, *oauth2ClientSecret, *oauth2ClientSecretFile, *oauth2TokenURL, *oauth2Scopes, endpointParams),
 		utils.WithHeaders(*headers))
 	if err != nil {
 		return nil, fmt.Errorf("failed to configure auth: %w", err)
diff --git a/app/vmalert/remotewrite/init.go b/app/vmalert/remotewrite/init.go
index 7bc1f90d6..998d076e8 100644
--- a/app/vmalert/remotewrite/init.go
+++ b/app/vmalert/remotewrite/init.go
@@ -41,11 +41,13 @@ var (
 	tlsServerName = flag.String("remoteWrite.tlsServerName", "", "Optional TLS server name to use for connections to -remoteWrite.url. "+
 		"By default, the server name from -remoteWrite.url is used")
 
-	oauth2ClientID         = flag.String("remoteWrite.oauth2.clientID", "", "Optional OAuth2 clientID to use for -remoteWrite.url.")
-	oauth2ClientSecret     = flag.String("remoteWrite.oauth2.clientSecret", "", "Optional OAuth2 clientSecret to use for -remoteWrite.url.")
-	oauth2ClientSecretFile = flag.String("remoteWrite.oauth2.clientSecretFile", "", "Optional OAuth2 clientSecretFile to use for -remoteWrite.url.")
-	oauth2TokenURL         = flag.String("remoteWrite.oauth2.tokenUrl", "", "Optional OAuth2 tokenURL to use for -notifier.url.")
-	oauth2Scopes           = flag.String("remoteWrite.oauth2.scopes", "", "Optional OAuth2 scopes to use for -notifier.url. Scopes must be delimited by ';'.")
+	oauth2ClientID         = flag.String("remoteWrite.oauth2.clientID", "", "Optional OAuth2 clientID to use for -remoteWrite.url")
+	oauth2ClientSecret     = flag.String("remoteWrite.oauth2.clientSecret", "", "Optional OAuth2 clientSecret to use for -remoteWrite.url")
+	oauth2ClientSecretFile = flag.String("remoteWrite.oauth2.clientSecretFile", "", "Optional OAuth2 clientSecretFile to use for -remoteWrite.url")
+	oauth2EndpointParams   = flag.String("remoteWrite.oauth2.endpointParams", "", "Optional OAuth2 endpoint parameters to use for -remoteWrite.url . "+
+		`The endpoint parameters must be set in JSON format: {"param1":"value1",...,"paramN":"valueN"}`)
+	oauth2TokenURL = flag.String("remoteWrite.oauth2.tokenUrl", "", "Optional OAuth2 tokenURL to use for -notifier.url.")
+	oauth2Scopes   = flag.String("remoteWrite.oauth2.scopes", "", "Optional OAuth2 scopes to use for -notifier.url. Scopes must be delimited by ';'.")
 )
 
 // InitSecretFlags must be called after flag.Parse and before any logging
@@ -67,10 +69,14 @@ func Init(ctx context.Context) (*Client, error) {
 		return nil, fmt.Errorf("failed to create transport: %w", err)
 	}
 
+	endpointParams, err := flagutil.ParseJSONMap(*oauth2EndpointParams)
+	if err != nil {
+		return nil, fmt.Errorf("cannot parse JSON for -remoteWrite.oauth2.endpointParams=%s: %w", *oauth2EndpointParams, err)
+	}
 	authCfg, err := utils.AuthConfig(
 		utils.WithBasicAuth(*basicAuthUsername, *basicAuthPassword, *basicAuthPasswordFile),
 		utils.WithBearer(*bearerToken, *bearerTokenFile),
-		utils.WithOAuth(*oauth2ClientID, *oauth2ClientSecret, *oauth2ClientSecretFile, *oauth2TokenURL, *oauth2Scopes),
+		utils.WithOAuth(*oauth2ClientID, *oauth2ClientSecret, *oauth2ClientSecretFile, *oauth2TokenURL, *oauth2Scopes, endpointParams),
 		utils.WithHeaders(*headers))
 	if err != nil {
 		return nil, fmt.Errorf("failed to configure auth: %w", err)
diff --git a/app/vmalert/utils/auth.go b/app/vmalert/utils/auth.go
index 35db61fb3..ae30031e3 100644
--- a/app/vmalert/utils/auth.go
+++ b/app/vmalert/utils/auth.go
@@ -45,13 +45,14 @@ func WithBearer(token, tokenFile string) AuthConfigOptions {
 }
 
 // WithOAuth returns AuthConfigOptions and set OAuth params based on given params
-func WithOAuth(clientID, clientSecret, clientSecretFile, tokenURL, scopes string) AuthConfigOptions {
+func WithOAuth(clientID, clientSecret, clientSecretFile, tokenURL, scopes string, endpointParams map[string]string) AuthConfigOptions {
 	return func(config *promauth.HTTPClientConfig) {
 		if clientSecretFile != "" || clientSecret != "" {
 			config.OAuth2 = &promauth.OAuth2Config{
 				ClientID:         clientID,
 				ClientSecret:     promauth.NewSecret(clientSecret),
 				ClientSecretFile: clientSecretFile,
+				EndpointParams:   endpointParams,
 				TokenURL:         tokenURL,
 				Scopes:           strings.Split(scopes, ";"),
 			}
diff --git a/app/vminsert/datadog/request_handler.go b/app/vminsert/datadogv1/request_handler.go
similarity index 83%
rename from app/vminsert/datadog/request_handler.go
rename to app/vminsert/datadogv1/request_handler.go
index 9717b7ef5..792a73879 100644
--- a/app/vminsert/datadog/request_handler.go
+++ b/app/vminsert/datadogv1/request_handler.go
@@ -1,4 +1,4 @@
-package datadog
+package datadogv1
 
 import (
 	"net/http"
@@ -7,31 +7,30 @@ import (
 	"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/relabel"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
 	parserCommon "github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/common"
-	parser "github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/datadog"
-	"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/datadog/stream"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/datadogutils"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/datadogv1"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/datadogv1/stream"
 	"github.com/VictoriaMetrics/metrics"
 )
 
 var (
-	rowsInserted  = metrics.NewCounter(`vm_rows_inserted_total{type="datadog"}`)
-	rowsPerInsert = metrics.NewHistogram(`vm_rows_per_insert{type="datadog"}`)
+	rowsInserted  = metrics.NewCounter(`vm_rows_inserted_total{type="datadogv1"}`)
+	rowsPerInsert = metrics.NewHistogram(`vm_rows_per_insert{type="datadogv1"}`)
 )
 
 // InsertHandlerForHTTP processes remote write for DataDog POST /api/v1/series request.
-//
-// See https://docs.datadoghq.com/api/latest/metrics/#submit-metrics
 func InsertHandlerForHTTP(req *http.Request) error {
 	extraLabels, err := parserCommon.GetExtraLabels(req)
 	if err != nil {
 		return err
 	}
 	ce := req.Header.Get("Content-Encoding")
-	return stream.Parse(req.Body, ce, func(series []parser.Series) error {
+	return stream.Parse(req.Body, ce, func(series []datadogv1.Series) error {
 		return insertRows(series, extraLabels)
 	})
 }
 
-func insertRows(series []parser.Series, extraLabels []prompbmarshal.Label) error {
+func insertRows(series []datadogv1.Series, extraLabels []prompbmarshal.Label) error {
 	ctx := common.GetInsertCtx()
 	defer common.PutInsertCtx(ctx)
 
@@ -54,7 +53,7 @@ func insertRows(series []parser.Series, extraLabels []prompbmarshal.Label) error
 			ctx.AddLabel("device", ss.Device)
 		}
 		for _, tag := range ss.Tags {
-			name, value := parser.SplitTag(tag)
+			name, value := datadogutils.SplitTag(tag)
 			if name == "host" {
 				name = "exported_host"
 			}
diff --git a/app/vminsert/datadogv2/request_handler.go b/app/vminsert/datadogv2/request_handler.go
new file mode 100644
index 000000000..4523d463a
--- /dev/null
+++ b/app/vminsert/datadogv2/request_handler.go
@@ -0,0 +1,91 @@
+package datadogv2
+
+import (
+	"net/http"
+
+	"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/common"
+	"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/relabel"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
+	parserCommon "github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/common"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/datadogutils"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/datadogv2"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/datadogv2/stream"
+	"github.com/VictoriaMetrics/metrics"
+)
+
+var (
+	rowsInserted  = metrics.NewCounter(`vm_rows_inserted_total{type="datadogv2"}`)
+	rowsPerInsert = metrics.NewHistogram(`vm_rows_per_insert{type="datadogv2"}`)
+)
+
+// InsertHandlerForHTTP processes remote write for DataDog POST /api/v2/series request.
+//
+// See https://docs.datadoghq.com/api/latest/metrics/#submit-metrics
+func InsertHandlerForHTTP(req *http.Request) error {
+	extraLabels, err := parserCommon.GetExtraLabels(req)
+	if err != nil {
+		return err
+	}
+	ct := req.Header.Get("Content-Type")
+	ce := req.Header.Get("Content-Encoding")
+	return stream.Parse(req.Body, ce, ct, func(series []datadogv2.Series) error {
+		return insertRows(series, extraLabels)
+	})
+}
+
+func insertRows(series []datadogv2.Series, extraLabels []prompbmarshal.Label) error {
+	ctx := common.GetInsertCtx()
+	defer common.PutInsertCtx(ctx)
+
+	rowsLen := 0
+	for i := range series {
+		rowsLen += len(series[i].Points)
+	}
+	ctx.Reset(rowsLen)
+	rowsTotal := 0
+	hasRelabeling := relabel.HasRelabeling()
+	for i := range series {
+		ss := &series[i]
+		rowsTotal += len(ss.Points)
+		ctx.Labels = ctx.Labels[:0]
+		ctx.AddLabel("", ss.Metric)
+		for _, rs := range ss.Resources {
+			ctx.AddLabel(rs.Type, rs.Name)
+		}
+		for _, tag := range ss.Tags {
+			name, value := datadogutils.SplitTag(tag)
+			if name == "host" {
+				name = "exported_host"
+			}
+			ctx.AddLabel(name, value)
+		}
+		if ss.SourceTypeName != "" {
+			ctx.AddLabel("source_type_name", ss.SourceTypeName)
+		}
+		for j := range extraLabels {
+			label := &extraLabels[j]
+			ctx.AddLabel(label.Name, label.Value)
+		}
+		if hasRelabeling {
+			ctx.ApplyRelabeling()
+		}
+		if len(ctx.Labels) == 0 {
+			// Skip metric without labels.
+			continue
+		}
+		ctx.SortLabelsIfNeeded()
+		var metricNameRaw []byte
+		var err error
+		for _, pt := range ss.Points {
+			timestamp := pt.Timestamp * 1000
+			value := pt.Value
+			metricNameRaw, err = ctx.WriteDataPointExt(metricNameRaw, ctx.Labels, timestamp, value)
+			if err != nil {
+				return err
+			}
+		}
+	}
+	rowsInserted.Add(rowsTotal)
+	rowsPerInsert.Update(float64(rowsTotal))
+	return ctx.FlushBufs()
+}
diff --git a/app/vminsert/main.go b/app/vminsert/main.go
index a53f8216d..c32ea402d 100644
--- a/app/vminsert/main.go
+++ b/app/vminsert/main.go
@@ -13,7 +13,8 @@ import (
 
 	vminsertCommon "github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/common"
 	"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/csvimport"
-	"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/datadog"
+	"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/datadogv1"
+	"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/datadogv2"
 	"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/graphite"
 	"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/influx"
 	"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/native"
@@ -246,9 +247,20 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
 		fmt.Fprintf(w, `{"status":"ok"}`)
 		return true
 	case "/datadog/api/v1/series":
-		datadogWriteRequests.Inc()
-		if err := datadog.InsertHandlerForHTTP(r); err != nil {
-			datadogWriteErrors.Inc()
+		datadogv1WriteRequests.Inc()
+		if err := datadogv1.InsertHandlerForHTTP(r); err != nil {
+			datadogv1WriteErrors.Inc()
+			httpserver.Errorf(w, r, "%s", err)
+			return true
+		}
+		w.Header().Set("Content-Type", "application/json")
+		w.WriteHeader(202)
+		fmt.Fprintf(w, `{"status":"ok"}`)
+		return true
+	case "/datadog/api/v2/series":
+		datadogv2WriteRequests.Inc()
+		if err := datadogv2.InsertHandlerForHTTP(r); err != nil {
+			datadogv2WriteErrors.Inc()
 			httpserver.Errorf(w, r, "%s", err)
 			return true
 		}
@@ -371,8 +383,11 @@ var (
 
 	influxQueryRequests = metrics.NewCounter(`vm_http_requests_total{path="/influx/query", protocol="influx"}`)
 
-	datadogWriteRequests = metrics.NewCounter(`vm_http_requests_total{path="/datadog/api/v1/series", protocol="datadog"}`)
-	datadogWriteErrors   = metrics.NewCounter(`vm_http_request_errors_total{path="/datadog/api/v1/series", protocol="datadog"}`)
+	datadogv1WriteRequests = metrics.NewCounter(`vm_http_requests_total{path="/datadog/api/v1/series", protocol="datadog"}`)
+	datadogv1WriteErrors   = metrics.NewCounter(`vm_http_request_errors_total{path="/datadog/api/v1/series", protocol="datadog"}`)
+
+	datadogv2WriteRequests = metrics.NewCounter(`vm_http_requests_total{path="/datadog/api/v2/series", protocol="datadog"}`)
+	datadogv2WriteErrors   = metrics.NewCounter(`vm_http_request_errors_total{path="/datadog/api/v2/series", protocol="datadog"}`)
 
 	datadogValidateRequests = metrics.NewCounter(`vm_http_requests_total{path="/datadog/api/v1/validate", protocol="datadog"}`)
 	datadogCheckRunRequests = metrics.NewCounter(`vm_http_requests_total{path="/datadog/api/v1/check_run", protocol="datadog"}`)
diff --git a/app/vmselect/promql/rollup.go b/app/vmselect/promql/rollup.go
index d700cd4cd..cd5f2bbff 100644
--- a/app/vmselect/promql/rollup.go
+++ b/app/vmselect/promql/rollup.go
@@ -2182,6 +2182,8 @@ func rollupFirst(rfa *rollupFuncArg) float64 {
 	return values[0]
 }
 
+var rollupLast = rollupDefault
+
 func rollupDefault(rfa *rollupFuncArg) float64 {
 	values := rfa.values
 	if len(values) == 0 {
@@ -2195,17 +2197,6 @@ func rollupDefault(rfa *rollupFuncArg) float64 {
 	return values[len(values)-1]
 }
 
-func rollupLast(rfa *rollupFuncArg) float64 {
-	values := rfa.values
-	if len(values) == 0 {
-		// Do not take into account rfa.prevValue, since it may lead
-		// to inconsistent results comparing to Prometheus on broken time series
-		// with irregular data points.
-		return nan
-	}
-	return values[len(values)-1]
-}
-
 func rollupDistinct(rfa *rollupFuncArg) float64 {
 	// There is no need in handling NaNs here, since they must be cleaned up
 	// before calling rollup funcs.
diff --git a/docs/Articles.md b/docs/Articles.md
index dbd871e97..0c47b764b 100644
--- a/docs/Articles.md
+++ b/docs/Articles.md
@@ -84,6 +84,8 @@ See also [case studies](https://docs.victoriametrics.com/CaseStudies.html).
 * [VictoriaMetrics: a comprehensive guide](https://medium.com/@seifeddinerajhi/victoriametrics-a-comprehensive-guide-comparing-it-to-prometheus-and-implementing-kubernetes-03eb8feb0cc2)
 * [Unleashing VM histograms for Ruby: Migrating from Prometheus to VictoriaMetrics with vm-client](https://hackernoon.com/unleashing-vm-histograms-for-ruby-migrating-from-prometheus-to-victoriametrics-with-vm-client)
 * [Observe and record performance of Spark jobs with Victoria Metrics](https://medium.com/constructor-engineering/observe-and-record-performance-of-databricks-jobs-11ffe236555e)
+* [Supercharge your Monitoring: Migrate from Prometheus to VictoriaMetrics for Scalability and Speed - Part 1](https://zetablogs.medium.com/supercharge-your-monitoring-migrate-from-prometheus-to-victoriametrics-for-scalability-and-speed-e1e9df786145)
+* [Supercharge your Monitoring: Migrate from Prometheus to VictoriaMetrics for optimised CPU and Memory usage - Part 2](https://zetablogs.medium.com/part-2-supercharge-your-monitoring-migrate-from-prometheus-to-victoriametrics-for-optimised-cpu-9a90c015ccba)
 
 ## Our articles
 
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 16ad0013f..1e21d45c6 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -28,16 +28,25 @@ The sandbox cluster installation is running under the constant load generated by
 
 ## tip
 
+* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): add support for [DataDog v2 data ingestion protocol](https://docs.datadoghq.com/api/latest/metrics/#submit-metrics). JSON protocol is supported right now. Protobuf protocol will be supported later. See [these docs](https://docs.victoriametrics.com/#how-to-send-data-from-datadog-agent) and [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4451).
+* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): expose ability to set OAuth2 endpoint parameters per each `-remoteWrite.url` via the command-line flag `-remoteWrite.oauth2.endpointParams`. See [these docs](https://docs.victoriametrics.com/vmagent.html#advanced-usage). Thanks to @mhill-holoplot for the [pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/5427).
+* FEATURE: [vmalert](https://docs.victoriametrics.com/vmagent.html): expose ability to set OAuth2 endpoint parameters via the following command-line flags:
+  - `-datasource.oauth2.endpointParams` for `-datasource.url`
+  - `-notifier.oauth2.endpointParams` for `-notifier.url`
+  - `-remoteRead.oauth2.endpointParams` for `-remoteRead.url`
+  - `-remoteWrite.oauth2.endpointParams` for `-remoteWrite.url`
 * FEATURE: [vmauth](https://docs.victoriametrics.com/vmauth.html): add ability to proxy incoming requests to different backends based on the requested host via `src_hosts` option at `url_map`. See [these docs](https://docs.victoriametrics.com/vmauth.html#generic-http-proxy-for-different-backends).
 * FEATURE: [vmctl](https://docs.victoriametrics.com/vmctl.html): rename cmd-line flag `vm-native-disable-retries` to `vm-native-disable-per-metric-migration` to better reflect its meaning.
 * FEATURE: all VictoriaMetrics components: add ability to specify arbitrary HTTP headers to send with every request to `-pushmetrics.url`. See [`push metrics` docs](https://docs.victoriametrics.com/#push-metrics).
 * FEATURE: all VictoriaMetrics components: add `-metrics.exposeMetadata` command-line flag, which allows displaying `TYPE` and `HELP` metadata at `/metrics` page exposed at `-httpListenAddr`. This may be needed when the `/metrics` page is scraped by collector, which requires the `TYPE` and `HELP` metadata such as [Google Cloud Managed Prometheus](https://cloud.google.com/stackdriver/docs/managed-prometheus/troubleshooting#missing-metric-type).
 
+* BUGFIX: [VictoriaMetrics cluster](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html): properly return full results when `-search.skipSlowReplicas` command-line flag is passed to `vmselect` and when [vmstorage groups](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#vmstorage-groups-at-vmselect) are in use. Previously partial results could be returned in this case.
 * BUGFIX: `vminsert`: properly accept samples via [OpenTelemetry data ingestion protocol](https://docs.victoriametrics.com/#sending-data-via-opentelemetry) when these samples have no [resource attributes](https://opentelemetry.io/docs/instrumentation/go/resources/). Previously such samples were silently skipped.
 * BUGFIX: `vmstorage`: added missing `-inmemoryDataFlushInterval` command-line flag, which was missing in [VictoriaMetrics cluster](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html) after implementing [this feature](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3337) in [v1.85.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.85.0).
 * BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert.html): check `-external.url` schema when starting vmalert, must be `http` or `https`. Before, alertmanager could reject alert notifications if `-external.url` contained no or wrong schema.
 * BUGFIX: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): properly handle queries, which wrap [rollup functions](https://docs.victoriametrics.com/MetricsQL.html#rollup-functions) with multiple arguments without explicitly specified lookbehind window in square brackets into [aggregate functions](https://docs.victoriametrics.com/MetricsQL.html#aggregate-functions). For example, `sum(quantile_over_time(0.5, process_resident_memory_bytes))` was resulting to `expecting at least 2 args to ...; got 1 args` error. Thanks to @atykhyy for [the pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/5414).
 * BUGFIX: [vmctl](https://docs.victoriametrics.com/vmctl.html): retry on import errors in `vm-native` mode. Before, retries happened only on writes into a network connection between source and destination. But errors returned by server after all the data was transmitted were logged, but not retried.
+* BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): properly assume role with [AWS IRSA authorization](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html). Previously role chaining was not supported. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3822) for details.
 
 ## [v1.96.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.96.0)
 
diff --git a/docs/Cluster-VictoriaMetrics.md b/docs/Cluster-VictoriaMetrics.md
index 83fd78292..bc991638a 100644
--- a/docs/Cluster-VictoriaMetrics.md
+++ b/docs/Cluster-VictoriaMetrics.md
@@ -363,7 +363,8 @@ Check practical examples of VictoriaMetrics API [here](https://docs.victoriametr
     - `prometheus/api/v1/import/csv` - for importing arbitrary CSV data. See [these docs](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#how-to-import-csv-data) for details.
     - `prometheus/api/v1/import/prometheus` - for importing data in [Prometheus text exposition format](https://github.com/prometheus/docs/blob/master/content/docs/instrumenting/exposition_formats.md#text-based-format) and in [OpenMetrics format](https://github.com/OpenObservability/OpenMetrics/blob/master/specification/OpenMetrics.md). This endpoint also supports [Pushgateway protocol](https://github.com/prometheus/pushgateway#url). See [these docs](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#how-to-import-data-in-prometheus-exposition-format) for details.
     - `opentelemetry/api/v1/push` - for ingesting data via [OpenTelemetry protocol for metrics](https://github.com/open-telemetry/opentelemetry-specification/blob/ffddc289462dfe0c2041e3ca42a7b1df805706de/specification/metrics/data-model.md). See [these docs](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#sending-data-via-opentelemetry).
-    - `datadog/api/v1/series` - for ingesting data with [DataDog submit metrics API](https://docs.datadoghq.com/api/latest/metrics/#submit-metrics). See [these docs](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#how-to-send-data-from-datadog-agent) for details.
+    - `datadog/api/v1/series` - for ingesting data with DataDog submit metrics API v1. See [these docs](https://docs.victoriametrics.com/url-examples.html#datadogapiv1series) for details.
+    - `datadog/api/v2/series` - for ingesting data with [DataDog submit metrics API](https://docs.datadoghq.com/api/latest/metrics/#submit-metrics). See [these docs](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#how-to-send-data-from-datadog-agent) for details.
     - `influx/write` and `influx/api/v2/write` - for ingesting data with [InfluxDB line protocol](https://docs.influxdata.com/influxdb/v1.7/write_protocols/line_protocol_tutorial/). See [these docs](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#how-to-send-data-from-influxdb-compatible-agents-such-as-telegraf) for details.
     - `newrelic/infra/v2/metrics/events/bulk` - for accepting data from [NewRelic infrastructure agent](https://docs.newrelic.com/docs/infrastructure/install-infrastructure-agent). See [these docs](https://docs.victoriametrics.com/#how-to-send-data-from-newrelic-agent) for details.
     - `opentsdb/api/put` - for accepting [OpenTSDB HTTP /api/put requests](http://opentsdb.net/docs/build/html/api_http/put.html). This handler is disabled by default. It is exposed on a distinct TCP address set via `-opentsdbHTTPListenAddr` command-line flag. See [these docs](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#sending-opentsdb-data-via-http-apiput-requests) for details.
@@ -960,7 +961,7 @@ Below is the output for `/path/to/vminsert -help`:
   -csvTrimTimestamp duration
      Trim timestamps when importing csv data to this duration. Minimum practical duration is 1ms. Higher duration (i.e. 1s) may be used for reducing disk space usage for timestamp data (default 1ms)
   -datadog.maxInsertRequestSize size
-     The maximum size in bytes of a single DataDog POST request to /api/v1/series
+     The maximum size in bytes of a single DataDog POST request to /datadog/api/v1/series
      Supports the following optional suffixes for size values: KB, MB, GB, TB, KiB, MiB, GiB, TiB (default 67108864)
   -datadog.sanitizeMetricName
      Sanitize metric names for the ingested DataDog data to comply with DataDog behaviour described at https://docs.datadoghq.com/metrics/custom_metrics/#naming-custom-metrics (default true)
diff --git a/docs/README.md b/docs/README.md
index 0a48307a5..758ae2274 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -519,10 +519,8 @@ See also [vmagent](https://docs.victoriametrics.com/vmagent.html), which can be
 
 ## How to send data from DataDog agent
 
-VictoriaMetrics accepts data from [DataDog agent](https://docs.datadoghq.com/agent/)
-or [DogStatsD](https://docs.datadoghq.com/developers/dogstatsd/)
-via ["submit metrics" API](https://docs.datadoghq.com/api/latest/metrics/#submit-metrics)
-at `/datadog/api/v1/series` path.
+VictoriaMetrics accepts data from [DataDog agent](https://docs.datadoghq.com/agent/) or [DogStatsD](https://docs.datadoghq.com/developers/dogstatsd/)
+via ["submit metrics" API](https://docs.datadoghq.com/api/latest/metrics/#submit-metrics) at `/datadog/api/v2/series` path.
 
 ### Sending metrics to VictoriaMetrics
 
@@ -534,12 +532,11 @@ or via [configuration file](https://docs.datadoghq.com/agent/guide/agent-configu
 </p>
 
 To configure DataDog agent via ENV variable add the following prefix:
-<div class="with-copy" markdown="1">
 
+<div class="with-copy" markdown="1">
 ```
 DD_DD_URL=http://victoriametrics:8428/datadog
 ```
-
 </div>
 
 _Choose correct URL for VictoriaMetrics [here](https://docs.victoriametrics.com/url-examples.html#datadog)._
@@ -548,14 +545,12 @@ To configure DataDog agent via [configuration file](https://github.com/DataDog/d
 add the following line:
 
 <div class="with-copy" markdown="1">
-
 ```
 dd_url: http://victoriametrics:8428/datadog
 ```
-
 </div>
 
-vmagent also can accept Datadog metrics format. Depending on where vmagent will forward data, 
+[vmagent](https://docs.victoriametrics.com/vmagent.html) also can accept Datadog metrics format. Depending on where vmagent will forward data,
 pick [single-node or cluster URL](https://docs.victoriametrics.com/url-examples.html#datadog) formats.
 
 ### Sending metrics to Datadog and VictoriaMetrics
@@ -570,12 +565,10 @@ sending via ENV variable `DD_ADDITIONAL_ENDPOINTS` or via configuration file `ad
 Run DataDog using the following ENV variable with VictoriaMetrics as additional metrics receiver:
 
 <div class="with-copy" markdown="1">
-
 ```
 DD_ADDITIONAL_ENDPOINTS='{\"http://victoriametrics:8428/datadog\": [\"apikey\"]}'
 
 ```
-
 </div>
 
 _Choose correct URL for VictoriaMetrics [here](https://docs.victoriametrics.com/url-examples.html#datadog)._
@@ -585,19 +578,16 @@ To configure DataDog Dual Shipping via [configuration file](https://docs.datadog
 add the following line:
 
 <div class="with-copy" markdown="1">
-
 ```
 additional_endpoints:
   "http://victoriametrics:8428/datadog":
   - apikey
 ```
-
 </div>
 
 ### Send via cURL
 
-See how to send data to VictoriaMetrics via 
-[DataDog "submit metrics"](https://docs.victoriametrics.com/url-examples.html#datadogapiv1series) from command line.
+See how to send data to VictoriaMetrics via DataDog "submit metrics" API [here](https://docs.victoriametrics.com/url-examples.html#datadogapiv2series).
 
 The imported data can be read via [export API](https://docs.victoriametrics.com/url-examples.html#apiv1export).
 
@@ -608,7 +598,7 @@ according to [DataDog metric naming recommendations](https://docs.datadoghq.com/
 If you need accepting metric names as is without sanitizing, then pass `-datadog.sanitizeMetricName=false` command-line flag to VictoriaMetrics.
 
 Extra labels may be added to all the written time series by passing `extra_label=name=value` query args.
-For example, `/datadog/api/v1/series?extra_label=foo=bar` would add `{foo="bar"}` label to all the ingested metrics.
+For example, `/datadog/api/v2/series?extra_label=foo=bar` would add `{foo="bar"}` label to all the ingested metrics.
 
 DataDog agent sends the [configured tags](https://docs.datadoghq.com/getting_started/tagging/) to
 undocumented endpoint - `/datadog/intake`. This endpoint isn't supported by VictoriaMetrics yet.
@@ -2583,7 +2573,7 @@ Pass `-help` to VictoriaMetrics in order to see the list of supported command-li
   -csvTrimTimestamp duration
      Trim timestamps when importing csv data to this duration. Minimum practical duration is 1ms. Higher duration (i.e. 1s) may be used for reducing disk space usage for timestamp data (default 1ms)
   -datadog.maxInsertRequestSize size
-     The maximum size in bytes of a single DataDog POST request to /api/v1/series
+     The maximum size in bytes of a single DataDog POST request to /datadog/api/v2/series
      Supports the following optional suffixes for size values: KB, MB, GB, TB, KiB, MiB, GiB, TiB (default 67108864)
   -datadog.sanitizeMetricName
      Sanitize metric names for the ingested DataDog data to comply with DataDog behaviour described at https://docs.datadoghq.com/metrics/custom_metrics/#naming-custom-metrics (default true)
diff --git a/docs/Single-server-VictoriaMetrics.md b/docs/Single-server-VictoriaMetrics.md
index fed6433f4..5e7bbcd0d 100644
--- a/docs/Single-server-VictoriaMetrics.md
+++ b/docs/Single-server-VictoriaMetrics.md
@@ -527,10 +527,8 @@ See also [vmagent](https://docs.victoriametrics.com/vmagent.html), which can be
 
 ## How to send data from DataDog agent
 
-VictoriaMetrics accepts data from [DataDog agent](https://docs.datadoghq.com/agent/)
-or [DogStatsD](https://docs.datadoghq.com/developers/dogstatsd/)
-via ["submit metrics" API](https://docs.datadoghq.com/api/latest/metrics/#submit-metrics)
-at `/datadog/api/v1/series` path.
+VictoriaMetrics accepts data from [DataDog agent](https://docs.datadoghq.com/agent/) or [DogStatsD](https://docs.datadoghq.com/developers/dogstatsd/)
+via ["submit metrics" API](https://docs.datadoghq.com/api/latest/metrics/#submit-metrics) at `/datadog/api/v2/series` path.
 
 ### Sending metrics to VictoriaMetrics
 
@@ -542,12 +540,11 @@ or via [configuration file](https://docs.datadoghq.com/agent/guide/agent-configu
 </p>
 
 To configure DataDog agent via ENV variable add the following prefix:
-<div class="with-copy" markdown="1">
 
+<div class="with-copy" markdown="1">
 ```
 DD_DD_URL=http://victoriametrics:8428/datadog
 ```
-
 </div>
 
 _Choose correct URL for VictoriaMetrics [here](https://docs.victoriametrics.com/url-examples.html#datadog)._
@@ -556,14 +553,12 @@ To configure DataDog agent via [configuration file](https://github.com/DataDog/d
 add the following line:
 
 <div class="with-copy" markdown="1">
-
 ```
 dd_url: http://victoriametrics:8428/datadog
 ```
-
 </div>
 
-vmagent also can accept Datadog metrics format. Depending on where vmagent will forward data, 
+[vmagent](https://docs.victoriametrics.com/vmagent.html) also can accept Datadog metrics format. Depending on where vmagent will forward data,
 pick [single-node or cluster URL](https://docs.victoriametrics.com/url-examples.html#datadog) formats.
 
 ### Sending metrics to Datadog and VictoriaMetrics
@@ -578,12 +573,10 @@ sending via ENV variable `DD_ADDITIONAL_ENDPOINTS` or via configuration file `ad
 Run DataDog using the following ENV variable with VictoriaMetrics as additional metrics receiver:
 
 <div class="with-copy" markdown="1">
-
 ```
 DD_ADDITIONAL_ENDPOINTS='{\"http://victoriametrics:8428/datadog\": [\"apikey\"]}'
 
 ```
-
 </div>
 
 _Choose correct URL for VictoriaMetrics [here](https://docs.victoriametrics.com/url-examples.html#datadog)._
@@ -593,19 +586,16 @@ To configure DataDog Dual Shipping via [configuration file](https://docs.datadog
 add the following line:
 
 <div class="with-copy" markdown="1">
-
 ```
 additional_endpoints:
   "http://victoriametrics:8428/datadog":
   - apikey
 ```
-
 </div>
 
 ### Send via cURL
 
-See how to send data to VictoriaMetrics via 
-[DataDog "submit metrics"](https://docs.victoriametrics.com/url-examples.html#datadogapiv1series) from command line.
+See how to send data to VictoriaMetrics via DataDog "submit metrics" API [here](https://docs.victoriametrics.com/url-examples.html#datadogapiv2series).
 
 The imported data can be read via [export API](https://docs.victoriametrics.com/url-examples.html#apiv1export).
 
@@ -616,7 +606,7 @@ according to [DataDog metric naming recommendations](https://docs.datadoghq.com/
 If you need accepting metric names as is without sanitizing, then pass `-datadog.sanitizeMetricName=false` command-line flag to VictoriaMetrics.
 
 Extra labels may be added to all the written time series by passing `extra_label=name=value` query args.
-For example, `/datadog/api/v1/series?extra_label=foo=bar` would add `{foo="bar"}` label to all the ingested metrics.
+For example, `/datadog/api/v2/series?extra_label=foo=bar` would add `{foo="bar"}` label to all the ingested metrics.
 
 DataDog agent sends the [configured tags](https://docs.datadoghq.com/getting_started/tagging/) to
 undocumented endpoint - `/datadog/intake`. This endpoint isn't supported by VictoriaMetrics yet.
@@ -2591,7 +2581,7 @@ Pass `-help` to VictoriaMetrics in order to see the list of supported command-li
   -csvTrimTimestamp duration
      Trim timestamps when importing csv data to this duration. Minimum practical duration is 1ms. Higher duration (i.e. 1s) may be used for reducing disk space usage for timestamp data (default 1ms)
   -datadog.maxInsertRequestSize size
-     The maximum size in bytes of a single DataDog POST request to /api/v1/series
+     The maximum size in bytes of a single DataDog POST request to /datadog/api/v2/series
      Supports the following optional suffixes for size values: KB, MB, GB, TB, KiB, MiB, GiB, TiB (default 67108864)
   -datadog.sanitizeMetricName
      Sanitize metric names for the ingested DataDog data to comply with DataDog behaviour described at https://docs.datadoghq.com/metrics/custom_metrics/#naming-custom-metrics (default true)
diff --git a/docs/keyConcepts.md b/docs/keyConcepts.md
index 520eb0c37..94bb2edbc 100644
--- a/docs/keyConcepts.md
+++ b/docs/keyConcepts.md
@@ -80,7 +80,9 @@ See [these docs](https://docs.victoriametrics.com/FAQ.html#what-is-high-cardinal
 #### Raw samples
 
 Every unique time series may consist of an arbitrary number of `(value, timestamp)` data points (aka `raw samples`) sorted by `timestamp`.
-The `value` is a [double-precision floating-point number](https://en.wikipedia.org/wiki/Double-precision_floating-point_format).
+VictoriaMetrics stores all the `values` as [float64](https://en.wikipedia.org/wiki/Double-precision_floating-point_format) values 
+with [extra compression](https://faun.pub/victoriametrics-achieving-better-compression-for-time-series-data-than-gorilla-317bc1f95932) applied.
+This guarantees precision correctness for values with up to 12 significant decimal digits ([-2^54 ... 2^54-1]).
 The `timestamp` is a [Unix timestamp](https://en.wikipedia.org/wiki/Unix_time) with millisecond precision.
 
 Below is an example of a single raw sample
diff --git a/docs/operator/resources/vmauth.md b/docs/operator/resources/vmauth.md
index 87ff2c83b..a55377a77 100644
--- a/docs/operator/resources/vmauth.md
+++ b/docs/operator/resources/vmauth.md
@@ -101,8 +101,8 @@ metadata:
   name: vmauth-unauthorized-example
 spec:
   unauthorizedAccessConfig:
-    - paths: ["/metrics"]
-      urls:
+    - src_paths: ["/metrics"]
+      url_prefix:
         - http://vmsingle-example.default.svc:8428
 ```
 
@@ -204,8 +204,8 @@ spec:
       - 5.6.7.8
   # allow read vmsingle metrics without authorization for users from internal network
   unauthorizedAccessConfig:
-    - paths: ["/metrics"]
-      urls: ["http://vmsingle-example.default.svc:8428"]
+    - src_paths: ["/metrics"]
+      url_prefix: ["http://vmsingle-example.default.svc:8428"]
       ip_filters:
         allow_list:
           - 192.168.0.0/16
diff --git a/docs/url-examples.md b/docs/url-examples.md
index bc1a9983e..35d3f55fe 100644
--- a/docs/url-examples.md
+++ b/docs/url-examples.md
@@ -473,7 +473,7 @@ http://vminsert:8480/insert/0/datadog
 
 ### /datadog/api/v1/series
 
-**Imports data in DataDog format into VictoriaMetrics**
+**Imports data in DataDog v1 format into VictoriaMetrics**
 
 Single-node VictoriaMetrics:
 <div class="with-copy" markdown="1">
@@ -531,7 +531,79 @@ echo '
 
 Additional information:
 
-* [How to send data from datadog agent](https://docs.victoriametrics.com/#how-to-send-data-from-datadog-agent)
+* [How to send data from DataDog agent](https://docs.victoriametrics.com/#how-to-send-data-from-datadog-agent)
+* [URL format for VictoriaMetrics cluster](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#url-format)
+
+
+### /datadog/api/v2/series
+
+**Imports data in [DataDog v2](https://docs.datadoghq.com/api/latest/metrics/#submit-metrics) format into VictoriaMetrics**
+
+Single-node VictoriaMetrics:
+<div class="with-copy" markdown="1">
+
+```console
+echo '
+{
+  "series": [
+    {
+      "metric": "system.load.1",
+      "type": 0,
+      "points": [
+        {
+          "timestamp": 0,
+          "value": 0.7
+        }
+      ],
+      "resources": [
+        {
+          "name": "dummyhost",
+          "type": "host"
+        }
+      ],
+      "tags": ["environment:test"]
+    }
+  ]
+}
+' | curl -X POST -H 'Content-Type: application/json' --data-binary @- http://localhost:8428/datadog/api/v2/series
+```
+
+</div>
+
+Cluster version of VictoriaMetrics:
+<div class="with-copy" markdown="1">
+
+```console
+echo '
+{
+  "series": [
+    {
+      "metric": "system.load.1",
+      "type": 0,
+      "points": [
+        {
+          "timestamp": 0,
+          "value": 0.7
+        }
+      ],
+      "resources": [
+        {
+          "name": "dummyhost",
+          "type": "host"
+        }
+      ],
+      "tags": ["environment:test"]
+    }
+  ]
+}
+' | curl -X POST -H 'Content-Type: application/json' --data-binary @- 'http://<vminsert>:8480/insert/0/datadog/api/v2/series'
+```
+
+</div>
+
+Additional information:
+
+* [How to send data from DataDog agent](https://docs.victoriametrics.com/#how-to-send-data-from-datadog-agent)
 * [URL format for VictoriaMetrics cluster](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#url-format)
 
 ### /federate
diff --git a/docs/vmagent.md b/docs/vmagent.md
index c28ba2b0b..d54e49099 100644
--- a/docs/vmagent.md
+++ b/docs/vmagent.md
@@ -1921,6 +1921,9 @@ See the docs at https://docs.victoriametrics.com/vmagent.html .
   -remoteWrite.oauth2.clientSecretFile array
      Optional OAuth2 clientSecretFile to use for the corresponding -remoteWrite.url
      Supports an array of values separated by comma or specified via multiple flags.
+  -remoteWrite.oauth2.endpointParams array
+     Optional OAuth2 endpoint parameters to use for the corresponding -remoteWrite.url . The endpoint parameters must be set in JSON format: {"param1":"value1",...,"paramN":"valueN"}
+     Supports an array of values separated by comma or specified via multiple flags.
   -remoteWrite.oauth2.scopes array
      Optional OAuth2 scopes to use for the corresponding -remoteWrite.url. Scopes must be delimited by ';'
      Supports an array of values separated by comma or specified via multiple flags.
diff --git a/docs/vmalert.md b/docs/vmalert.md
index 608921884..a427e6a0f 100644
--- a/docs/vmalert.md
+++ b/docs/vmalert.md
@@ -1003,11 +1003,13 @@ The shortlist of configuration flags is the following:
   -datasource.maxIdleConnections int
      Defines the number of idle (keep-alive connections) to each configured datasource. Consider setting this value equal to the value: groups_total * group.concurrency. Too low a value may result in a high number of sockets in TIME_WAIT state. (default 100)
   -datasource.oauth2.clientID string
-     Optional OAuth2 clientID to use for -datasource.url. 
+     Optional OAuth2 clientID to use for -datasource.url
   -datasource.oauth2.clientSecret string
-     Optional OAuth2 clientSecret to use for -datasource.url.
+     Optional OAuth2 clientSecret to use for -datasource.url
   -datasource.oauth2.clientSecretFile string
-     Optional OAuth2 clientSecretFile to use for -datasource.url. 
+     Optional OAuth2 clientSecretFile to use for -datasource.url
+  -datasource.oauth2.endpointParams string
+     Optional OAuth2 endpoint parameters to use for -datasource.url . The endpoint parameters must be set in JSON format: {"param1":"value1",...,"paramN":"valueN"}
   -datasource.oauth2.scopes string
      Optional OAuth2 scopes to use for -datasource.url. Scopes must be delimited by ';'
   -datasource.oauth2.tokenUrl string
@@ -1156,6 +1158,9 @@ The shortlist of configuration flags is the following:
   -notifier.oauth2.clientSecretFile array
      Optional OAuth2 clientSecretFile to use for -notifier.url. If multiple args are set, then they are applied independently for the corresponding -notifier.url
      Supports an array of values separated by comma or specified via multiple flags.
+  -notifier.oauth2.endpointParams array
+     Optional OAuth2 endpoint parameters to use for the corresponding -notifier.url . The endpoint parameters must be set in JSON format: {"param1":"value1",...,"paramN":"valueN"}
+     Supports an array of values separated by comma or specified via multiple flags.
   -notifier.oauth2.scopes array
      Optional OAuth2 scopes to use for -notifier.url. Scopes must be delimited by ';'. If multiple args are set, then they are applied independently for the corresponding -notifier.url
      Supports an array of values separated by comma or specified via multiple flags.
@@ -1233,6 +1238,8 @@ The shortlist of configuration flags is the following:
      Optional OAuth2 clientSecret to use for -remoteRead.url.
   -remoteRead.oauth2.clientSecretFile string
      Optional OAuth2 clientSecretFile to use for -remoteRead.url.
+  -remoteRead.oauth2.endpointParams string
+     Optional OAuth2 endpoint parameters to use for -remoteRead.url . The endpoint parameters must be set in JSON format: {"param1":"value1",...,"paramN":"valueN"}
   -remoteRead.oauth2.scopes string
      Optional OAuth2 scopes to use for -remoteRead.url. Scopes must be delimited by ';'.
   -remoteRead.oauth2.tokenUrl string
@@ -1274,11 +1281,13 @@ The shortlist of configuration flags is the following:
   -remoteWrite.maxQueueSize int
      Defines the max number of pending datapoints to remote write endpoint (default 100000)
   -remoteWrite.oauth2.clientID string
-     Optional OAuth2 clientID to use for -remoteWrite.url.
+     Optional OAuth2 clientID to use for -remoteWrite.url
   -remoteWrite.oauth2.clientSecret string
-     Optional OAuth2 clientSecret to use for -remoteWrite.url.
+     Optional OAuth2 clientSecret to use for -remoteWrite.url
   -remoteWrite.oauth2.clientSecretFile string
-     Optional OAuth2 clientSecretFile to use for -remoteWrite.url.
+     Optional OAuth2 clientSecretFile to use for -remoteWrite.url
+  -remoteWrite.oauth2.endpointParams string
+     Optional OAuth2 endpoint parameters to use for -remoteWrite.url . The endpoint parameters must be set in JSON format: {"param1":"value1",...,"paramN":"valueN"}
   -remoteWrite.oauth2.scopes string
      Optional OAuth2 scopes to use for -notifier.url. Scopes must be delimited by ';'.
   -remoteWrite.oauth2.tokenUrl string
diff --git a/go.mod b/go.mod
index ccd3e7655..17b334b6f 100644
--- a/go.mod
+++ b/go.mod
@@ -11,7 +11,7 @@ require (
 	// Do not use the original github.com/valyala/fasthttp because of issues
 	// like https://github.com/valyala/fasthttp/commit/996610f021ff45fdc98c2ce7884d5fa4e7f9199b
 	github.com/VictoriaMetrics/fasthttp v1.2.0
-	github.com/VictoriaMetrics/metrics v1.29.0
+	github.com/VictoriaMetrics/metrics v1.29.1
 	github.com/VictoriaMetrics/metricsql v0.70.0
 	github.com/aws/aws-sdk-go-v2 v1.24.0
 	github.com/aws/aws-sdk-go-v2/config v1.26.1
diff --git a/lib/awsapi/config.go b/lib/awsapi/config.go
index 4f3d53514..862aabba9 100644
--- a/lib/awsapi/config.go
+++ b/lib/awsapi/config.go
@@ -17,9 +17,15 @@ import (
 
 // Config represent aws access configuration.
 type Config struct {
-	client       *http.Client
-	region       string
-	roleARN      string
+	client  *http.Client
+	region  string
+	roleARN string
+
+	// IRSA may use a different role for assume API call.
+	// It can only be set via AWS_ROLE_ARN env variable.
+	// See https://docs.aws.amazon.com/eks/latest/userguide/pod-configuration.html
+	irsaRoleARN string
+
 	webTokenPath string
 
 	ec2Endpoint string
@@ -49,6 +55,7 @@ func NewConfig(ec2Endpoint, stsEndpoint, region, roleARN, accessKey, secretKey,
 		client:           http.DefaultClient,
 		region:           region,
 		roleARN:          roleARN,
+		irsaRoleARN:      os.Getenv("AWS_ROLE_ARN"),
 		service:          service,
 		defaultAccessKey: os.Getenv("AWS_ACCESS_KEY_ID"),
 		defaultSecretKey: os.Getenv("AWS_SECRET_ACCESS_KEY"),
@@ -69,7 +76,7 @@ func NewConfig(ec2Endpoint, stsEndpoint, region, roleARN, accessKey, secretKey,
 		cfg.roleARN = os.Getenv("AWS_ROLE_ARN")
 	}
 	cfg.webTokenPath = os.Getenv("AWS_WEB_IDENTITY_TOKEN_FILE")
-	if cfg.webTokenPath != "" && cfg.roleARN == "" {
+	if cfg.webTokenPath != "" && cfg.irsaRoleARN == "" {
 		return nil, fmt.Errorf("roleARN is missing for AWS_WEB_IDENTITY_TOKEN_FILE=%q; set it via env var AWS_ROLE_ARN", cfg.webTokenPath)
 	}
 	// explicitly set credentials has priority over env variables
@@ -83,6 +90,7 @@ func NewConfig(ec2Endpoint, stsEndpoint, region, roleARN, accessKey, secretKey,
 		AccessKeyID:     cfg.defaultAccessKey,
 		SecretAccessKey: cfg.defaultSecretKey,
 	}
+
 	return cfg, nil
 }
 
@@ -201,7 +209,7 @@ func (cfg *Config) getAPICredentials() (*credentials, error) {
 		if err != nil {
 			return nil, fmt.Errorf("cannot read webToken from path: %q, err: %w", cfg.webTokenPath, err)
 		}
-		return cfg.getRoleWebIdentityCredentials(string(token))
+		return cfg.getRoleWebIdentityCredentials(string(token), cfg.irsaRoleARN)
 	}
 	if ecsMetaURI := os.Getenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"); len(ecsMetaURI) > 0 {
 		path := "http://169.254.170.2" + ecsMetaURI
@@ -223,7 +231,7 @@ func (cfg *Config) getAPICredentials() (*credentials, error) {
 
 	// read credentials from sts api, if role_arn is defined
 	if len(cfg.roleARN) > 0 {
-		ac, err := cfg.getRoleARNCredentials(acNew)
+		ac, err := cfg.getRoleARNCredentials(acNew, cfg.roleARN)
 		if err != nil {
 			return nil, fmt.Errorf("cannot get credentials for role_arn %q: %w", cfg.roleARN, err)
 		}
@@ -330,28 +338,44 @@ func getMetadataByPath(client *http.Client, apiPath string) ([]byte, error) {
 }
 
 // getRoleWebIdentityCredentials obtains credentials for the given roleARN with webToken.
+//
 // https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html
 // aws IRSA for kubernetes.
 // https://aws.amazon.com/blogs/opensource/introducing-fine-grained-iam-roles-service-accounts/
-func (cfg *Config) getRoleWebIdentityCredentials(token string) (*credentials, error) {
-	data, err := cfg.getSTSAPIResponse("AssumeRoleWithWebIdentity", func(apiURL string) (*http.Request, error) {
+func (cfg *Config) getRoleWebIdentityCredentials(token, roleARN string) (*credentials, error) {
+	data, err := cfg.getSTSAPIResponse("AssumeRoleWithWebIdentity", roleARN, func(apiURL string) (*http.Request, error) {
 		apiURL += fmt.Sprintf("&WebIdentityToken=%s", url.QueryEscape(token))
 		return http.NewRequest(http.MethodGet, apiURL, nil)
 	})
 	if err != nil {
 		return nil, err
 	}
-	return parseARNCredentials(data, "AssumeRoleWithWebIdentity")
+	creds, err := parseARNCredentials(data, "AssumeRoleWithWebIdentity")
+	if err != nil {
+		return nil, err
+	}
+	if roleARN != cfg.roleARN {
+		// need to assume a different role
+		assumeCreds, err := cfg.getRoleARNCredentials(creds, cfg.roleARN)
+		if err != nil {
+			return nil, fmt.Errorf("cannot assume chained role=%q for roleARN=%q: %w", cfg.roleARN, roleARN, err)
+		}
+		if assumeCreds.Expiration.After(creds.Expiration) {
+			assumeCreds.Expiration = creds.Expiration
+		}
+		return assumeCreds, nil
+	}
+	return creds, nil
 }
 
 // getSTSAPIResponse makes request to aws sts api with the given cfg and returns temporary credentials with expiration time.
 //
 // See https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html
-func (cfg *Config) getSTSAPIResponse(action string, reqBuilder func(apiURL string) (*http.Request, error)) ([]byte, error) {
+func (cfg *Config) getSTSAPIResponse(action string, roleARN string, reqBuilder func(apiURL string) (*http.Request, error)) ([]byte, error) {
 	// See https://docs.aws.amazon.com/AWSEC2/latest/APIReference/Query-Requests.html
 	apiURL := fmt.Sprintf("%s?Action=%s", cfg.stsEndpoint, action)
 	apiURL += "&Version=2011-06-15"
-	apiURL += fmt.Sprintf("&RoleArn=%s", cfg.roleARN)
+	apiURL += fmt.Sprintf("&RoleArn=%s", roleARN)
 	// we have to provide unique session name for cloudtrail audit
 	apiURL += "&RoleSessionName=vmagent-ec2-discovery"
 	req, err := reqBuilder(apiURL)
@@ -366,8 +390,8 @@ func (cfg *Config) getSTSAPIResponse(action string, reqBuilder func(apiURL strin
 }
 
 // getRoleARNCredentials obtains credentials for the given roleARN.
-func (cfg *Config) getRoleARNCredentials(creds *credentials) (*credentials, error) {
-	data, err := cfg.getSTSAPIResponse("AssumeRole", func(apiURL string) (*http.Request, error) {
+func (cfg *Config) getRoleARNCredentials(creds *credentials, roleARN string) (*credentials, error) {
+	data, err := cfg.getSTSAPIResponse("AssumeRole", roleARN, func(apiURL string) (*http.Request, error) {
 		return newSignedGetRequest(apiURL, "sts", cfg.region, creds)
 	})
 	if err != nil {
diff --git a/lib/flagutil/dict.go b/lib/flagutil/dict.go
index eceade66d..a7ae57393 100644
--- a/lib/flagutil/dict.go
+++ b/lib/flagutil/dict.go
@@ -1,6 +1,7 @@
 package flagutil
 
 import (
+	"encoding/json"
 	"flag"
 	"fmt"
 	"strconv"
@@ -98,3 +99,16 @@ func (di *DictInt) Get(key string) int {
 	}
 	return di.defaultValue
 }
+
+// ParseJSONMap parses s, which must contain JSON map of {"k1":"v1",...,"kN":"vN"}
+func ParseJSONMap(s string) (map[string]string, error) {
+	if s == "" {
+		// Special case
+		return nil, nil
+	}
+	var m map[string]string
+	if err := json.Unmarshal([]byte(s), &m); err != nil {
+		return nil, err
+	}
+	return m, nil
+}
diff --git a/lib/promauth/config.go b/lib/promauth/config.go
index fb4e6f13d..c47d3f768 100644
--- a/lib/promauth/config.go
+++ b/lib/promauth/config.go
@@ -582,18 +582,18 @@ func (opts *Options) NewConfig() (*Config, error) {
 			return nil, fmt.Errorf("cannot simultaneously use `authorization`, `basic_auth, `bearer_token` and `ouath2`")
 		}
 		if err := actx.initFromOAuth2Config(baseDir, opts.OAuth2); err != nil {
-			return nil, err
+			return nil, fmt.Errorf("cannot initialize oauth2: %w", err)
 		}
 	}
 	var tctx tlsContext
 	if opts.TLSConfig != nil {
 		if err := tctx.initFromTLSConfig(baseDir, opts.TLSConfig); err != nil {
-			return nil, err
+			return nil, fmt.Errorf("cannot initialize tls: %w", err)
 		}
 	}
 	headers, err := parseHeaders(opts.Headers)
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("cannot parse headers: %w", err)
 	}
 	hd := xxhash.New()
 	for _, kv := range headers {
diff --git a/lib/protoparser/datadogutils/datadogutils.go b/lib/protoparser/datadogutils/datadogutils.go
new file mode 100644
index 000000000..51cc79c07
--- /dev/null
+++ b/lib/protoparser/datadogutils/datadogutils.go
@@ -0,0 +1,57 @@
+package datadogutils
+
+import (
+	"flag"
+	"regexp"
+	"strings"
+
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
+)
+
+var (
+	// MaxInsertRequestSize is the maximum request size is defined at https://docs.datadoghq.com/api/latest/metrics/#submit-metrics
+	MaxInsertRequestSize = flagutil.NewBytes("datadog.maxInsertRequestSize", 64*1024*1024, "The maximum size in bytes of a single DataDog POST request to /datadog/api/v2/series")
+
+	// SanitizeMetricName controls sanitizing metric names ingested via DataDog protocols.
+	//
+	// If all metrics in Datadog have the same naming schema as custom metrics, then the following rules apply:
+	// https://docs.datadoghq.com/metrics/custom_metrics/#naming-custom-metrics
+	// But there's some hidden behaviour. In addition to what it states in the docs, the following is also done:
+	// - Consecutive underscores are replaced with just one underscore
+	// - Underscore immediately before or after a dot are removed
+	SanitizeMetricName = flag.Bool("datadog.sanitizeMetricName", true, "Sanitize metric names for the ingested DataDog data to comply with DataDog behaviour described at "+
+		"https://docs.datadoghq.com/metrics/custom_metrics/#naming-custom-metrics")
+)
+
+// SplitTag splits DataDog tag into tag name and value.
+//
+// See https://docs.datadoghq.com/getting_started/tagging/#define-tags
+func SplitTag(tag string) (string, string) {
+	n := strings.IndexByte(tag, ':')
+	if n < 0 {
+		// No tag value.
+		return tag, "no_label_value"
+	}
+	return tag[:n], tag[n+1:]
+}
+
+// SanitizeName performs DataDog-compatible sanitizing for metric names
+//
+// See https://docs.datadoghq.com/metrics/custom_metrics/#naming-custom-metrics
+func SanitizeName(name string) string {
+	return namesSanitizer.Transform(name)
+}
+
+var namesSanitizer = bytesutil.NewFastStringTransformer(func(s string) string {
+	s = unsupportedDatadogChars.ReplaceAllString(s, "_")
+	s = multiUnderscores.ReplaceAllString(s, "_")
+	s = underscoresWithDots.ReplaceAllString(s, ".")
+	return s
+})
+
+var (
+	unsupportedDatadogChars = regexp.MustCompile(`[^0-9a-zA-Z_\.]+`)
+	multiUnderscores        = regexp.MustCompile(`_+`)
+	underscoresWithDots     = regexp.MustCompile(`_?\._?`)
+)
diff --git a/lib/protoparser/datadog/parser.go b/lib/protoparser/datadogv1/parser.go
similarity index 82%
rename from lib/protoparser/datadog/parser.go
rename to lib/protoparser/datadogv1/parser.go
index c32a9c99d..ba0d69071 100644
--- a/lib/protoparser/datadog/parser.go
+++ b/lib/protoparser/datadogv1/parser.go
@@ -1,28 +1,13 @@
-package datadog
+package datadogv1
 
 import (
 	"encoding/json"
 	"fmt"
-	"strings"
 
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
 )
 
-// SplitTag splits DataDog tag into tag name and value.
-//
-// See https://docs.datadoghq.com/getting_started/tagging/#define-tags
-func SplitTag(tag string) (string, string) {
-	n := strings.IndexByte(tag, ':')
-	if n < 0 {
-		// No tag value.
-		return tag, "no_label_value"
-	}
-	return tag[:n], tag[n+1:]
-}
-
 // Request represents DataDog POST request to /api/v1/series
-//
-// See https://docs.datadoghq.com/api/latest/metrics/#submit-metrics
 type Request struct {
 	Series []Series `json:"series"`
 }
@@ -41,8 +26,6 @@ func (req *Request) reset() {
 
 // Unmarshal unmarshals DataDog /api/v1/series request body from b to req.
 //
-// See https://docs.datadoghq.com/api/latest/metrics/#submit-metrics
-//
 // b shouldn't be modified when req is in use.
 func (req *Request) Unmarshal(b []byte) error {
 	req.reset()
@@ -64,8 +47,6 @@ func (req *Request) Unmarshal(b []byte) error {
 }
 
 // Series represents a series item from DataDog POST request to /api/v1/series
-//
-// See https://docs.datadoghq.com/api/latest/metrics/#submit-metrics
 type Series struct {
 	Metric string `json:"metric"`
 	Host   string `json:"host"`
diff --git a/lib/protoparser/datadog/stream/streamparser.go b/lib/protoparser/datadogv1/stream/streamparser.go
similarity index 58%
rename from lib/protoparser/datadog/stream/streamparser.go
rename to lib/protoparser/datadogv1/stream/streamparser.go
index d79f0b84a..d1ce9f7d4 100644
--- a/lib/protoparser/datadog/stream/streamparser.go
+++ b/lib/protoparser/datadogv1/stream/streamparser.go
@@ -2,39 +2,24 @@ package stream
 
 import (
 	"bufio"
-	"flag"
 	"fmt"
 	"io"
-	"regexp"
 	"sync"
 
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/cgroup"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
-	"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/common"
-	"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/datadog"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/datadogutils"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/datadogv1"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/writeconcurrencylimiter"
 	"github.com/VictoriaMetrics/metrics"
 )
 
-var (
-	// The maximum request size is defined at https://docs.datadoghq.com/api/latest/metrics/#submit-metrics
-	maxInsertRequestSize = flagutil.NewBytes("datadog.maxInsertRequestSize", 64*1024*1024, "The maximum size in bytes of a single DataDog POST request to /api/v1/series")
-
-	// If all metrics in Datadog have the same naming schema as custom metrics, then the following rules apply:
-	// https://docs.datadoghq.com/metrics/custom_metrics/#naming-custom-metrics
-	// But there's some hidden behaviour. In addition to what it states in the docs, the following is also done:
-	// - Consecutive underscores are replaced with just one underscore
-	// - Underscore immediately before or after a dot are removed
-	sanitizeMetricName = flag.Bool("datadog.sanitizeMetricName", true, "Sanitize metric names for the ingested DataDog data to comply with DataDog behaviour described at "+
-		"https://docs.datadoghq.com/metrics/custom_metrics/#naming-custom-metrics")
-)
-
 // Parse parses DataDog POST request for /api/v1/series from reader and calls callback for the parsed request.
 //
 // callback shouldn't hold series after returning.
-func Parse(r io.Reader, contentEncoding string, callback func(series []datadog.Series) error) error {
+func Parse(r io.Reader, contentEncoding string, callback func(series []datadogv1.Series) error) error {
 	wcr := writeconcurrencylimiter.GetReader(r)
 	defer writeconcurrencylimiter.PutReader(wcr)
 	r = wcr
@@ -70,8 +55,8 @@ func Parse(r io.Reader, contentEncoding string, callback func(series []datadog.S
 	series := req.Series
 	for i := range series {
 		rows += len(series[i].Points)
-		if *sanitizeMetricName {
-			series[i].Metric = sanitizeName(series[i].Metric)
+		if *datadogutils.SanitizeMetricName {
+			series[i].Metric = datadogutils.SanitizeName(series[i].Metric)
 		}
 	}
 	rowsRead.Add(rows)
@@ -94,25 +79,25 @@ func (ctx *pushCtx) reset() {
 
 func (ctx *pushCtx) Read() error {
 	readCalls.Inc()
-	lr := io.LimitReader(ctx.br, int64(maxInsertRequestSize.N)+1)
+	lr := io.LimitReader(ctx.br, int64(datadogutils.MaxInsertRequestSize.N)+1)
 	startTime := fasttime.UnixTimestamp()
 	reqLen, err := ctx.reqBuf.ReadFrom(lr)
 	if err != nil {
 		readErrors.Inc()
 		return fmt.Errorf("cannot read request in %d seconds: %w", fasttime.UnixTimestamp()-startTime, err)
 	}
-	if reqLen > int64(maxInsertRequestSize.N) {
+	if reqLen > int64(datadogutils.MaxInsertRequestSize.N) {
 		readErrors.Inc()
-		return fmt.Errorf("too big request; mustn't exceed -datadog.maxInsertRequestSize=%d bytes", maxInsertRequestSize.N)
+		return fmt.Errorf("too big request; mustn't exceed -datadog.maxInsertRequestSize=%d bytes", datadogutils.MaxInsertRequestSize.N)
 	}
 	return nil
 }
 
 var (
-	readCalls       = metrics.NewCounter(`vm_protoparser_read_calls_total{type="datadog"}`)
-	readErrors      = metrics.NewCounter(`vm_protoparser_read_errors_total{type="datadog"}`)
-	rowsRead        = metrics.NewCounter(`vm_protoparser_rows_read_total{type="datadog"}`)
-	unmarshalErrors = metrics.NewCounter(`vm_protoparser_unmarshal_errors_total{type="datadog"}`)
+	readCalls       = metrics.NewCounter(`vm_protoparser_read_calls_total{type="datadogv1"}`)
+	readErrors      = metrics.NewCounter(`vm_protoparser_read_errors_total{type="datadogv1"}`)
+	rowsRead        = metrics.NewCounter(`vm_protoparser_rows_read_total{type="datadogv1"}`)
+	unmarshalErrors = metrics.NewCounter(`vm_protoparser_unmarshal_errors_total{type="datadogv1"}`)
 )
 
 func getPushCtx(r io.Reader) *pushCtx {
@@ -144,36 +129,16 @@ func putPushCtx(ctx *pushCtx) {
 var pushCtxPool sync.Pool
 var pushCtxPoolCh = make(chan *pushCtx, cgroup.AvailableCPUs())
 
-func getRequest() *datadog.Request {
+func getRequest() *datadogv1.Request {
 	v := requestPool.Get()
 	if v == nil {
-		return &datadog.Request{}
+		return &datadogv1.Request{}
 	}
-	return v.(*datadog.Request)
+	return v.(*datadogv1.Request)
 }
 
-func putRequest(req *datadog.Request) {
+func putRequest(req *datadogv1.Request) {
 	requestPool.Put(req)
 }
 
 var requestPool sync.Pool
-
-// sanitizeName performs DataDog-compatible sanitizing for metric names
-//
-// See https://docs.datadoghq.com/metrics/custom_metrics/#naming-custom-metrics
-func sanitizeName(name string) string {
-	return namesSanitizer.Transform(name)
-}
-
-var namesSanitizer = bytesutil.NewFastStringTransformer(func(s string) string {
-	s = unsupportedDatadogChars.ReplaceAllString(s, "_")
-	s = multiUnderscores.ReplaceAllString(s, "_")
-	s = underscoresWithDots.ReplaceAllString(s, ".")
-	return s
-})
-
-var (
-	unsupportedDatadogChars = regexp.MustCompile(`[^0-9a-zA-Z_\.]+`)
-	multiUnderscores        = regexp.MustCompile(`_+`)
-	underscoresWithDots     = regexp.MustCompile(`_?\._?`)
-)
diff --git a/lib/protoparser/datadogv2/parser.go b/lib/protoparser/datadogv2/parser.go
new file mode 100644
index 000000000..55b57b007
--- /dev/null
+++ b/lib/protoparser/datadogv2/parser.go
@@ -0,0 +1,144 @@
+package datadogv2
+
+import (
+	"encoding/json"
+	"fmt"
+
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
+)
+
+// Request represents DataDog POST request to /api/v2/series
+//
+// See https://docs.datadoghq.com/api/latest/metrics/#submit-metrics
+type Request struct {
+	Series []Series `json:"series"`
+}
+
+func (req *Request) reset() {
+	// recursively reset all the fields in req in order to avoid field value
+	// re-use in json.Unmarshal() when the corresponding field is missing
+	// in the unmarshaled JSON.
+	// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3432
+	series := req.Series
+	for i := range series {
+		series[i].reset()
+	}
+	req.Series = series[:0]
+}
+
+// UnmarshalJSON unmarshals JSON DataDog /api/v2/series request body from b to req.
+//
+// See https://docs.datadoghq.com/api/latest/metrics/#submit-metrics
+//
+// b shouldn't be modified when req is in use.
+func UnmarshalJSON(req *Request, b []byte) error {
+	req.reset()
+	if err := json.Unmarshal(b, req); err != nil {
+		return fmt.Errorf("cannot unmarshal %q: %w", b, err)
+	}
+	// Set missing timestamps to the current time.
+	currentTimestamp := int64(fasttime.UnixTimestamp())
+	series := req.Series
+	for i := range series {
+		points := series[i].Points
+		for j := range points {
+			pt := &points[j]
+			if pt.Timestamp <= 0 {
+				pt.Timestamp = currentTimestamp
+			}
+		}
+	}
+	return nil
+}
+
+// UnmarshalProtobuf unmarshals protobuf DataDog /api/v2/series request body from b to req.
+//
+// See https://docs.datadoghq.com/api/latest/metrics/#submit-metrics
+//
+// b shouldn't be modified when req is in use.
+func UnmarshalProtobuf(req *Request, b []byte) error {
+	req.reset()
+	_ = b
+	return fmt.Errorf("unimplemented")
+}
+
+// Series represents a series item from DataDog POST request to /api/v2/series
+//
+// See https://docs.datadoghq.com/api/latest/metrics/#submit-metrics
+type Series struct {
+	// Do not decode Interval, since it isn't used by VictoriaMetrics
+	// Interval int64 `json:"interval"`
+
+	// Do not decode Metadata, since it isn't used by VictoriaMetrics
+	// Metadata Metadata `json:"metadata"`
+
+	// Metric is the name of the metric
+	Metric string `json:"metric"`
+
+	// Points points for the given metric
+	Points []Point `json:"points"`
+
+	Resources      []Resource `json:"resources"`
+	SourceTypeName string     `json:"source_type_name"`
+
+	Tags []string
+
+	// Do not decode Type, since it isn't used by VictoriaMetrics
+	// Type int `json:"type"`
+
+	// Do not decode Unit, since it isn't used by VictoriaMetrics
+	// Unit string
+}
+
+func (s *Series) reset() {
+	s.Metric = ""
+
+	points := s.Points
+	for i := range points {
+		points[i].reset()
+	}
+	s.Points = points[:0]
+
+	resources := s.Resources
+	for i := range resources {
+		resources[i].reset()
+	}
+	s.Resources = resources[:0]
+
+	s.SourceTypeName = ""
+
+	tags := s.Tags
+	for i := range tags {
+		tags[i] = ""
+	}
+	s.Tags = tags[:0]
+}
+
+// Point represents a point from DataDog POST request to /api/v2/series
+//
+// See https://docs.datadoghq.com/api/latest/metrics/#submit-metrics
+type Point struct {
+	// Timestamp is point timestamp in seconds
+	Timestamp int64 `json:"timestamp"`
+
+	// Value is point value
+	Value float64 `json:"value"`
+}
+
+func (pt *Point) reset() {
+	pt.Timestamp = 0
+	pt.Value = 0
+}
+
+// Resource is series resource from DataDog POST request to /api/v2/series
+//
+// See https://docs.datadoghq.com/api/latest/metrics/#submit-metrics
+type Resource struct {
+	Name string `json:"name"`
+	Type string `json:"type"`
+}
+
+func (r *Resource) reset() {
+	r.Name = ""
+	r.Type = ""
+}
diff --git a/lib/protoparser/datadogv2/stream/streamparser.go b/lib/protoparser/datadogv2/stream/streamparser.go
new file mode 100644
index 000000000..0fe06269d
--- /dev/null
+++ b/lib/protoparser/datadogv2/stream/streamparser.go
@@ -0,0 +1,154 @@
+package stream
+
+import (
+	"bufio"
+	"fmt"
+	"io"
+	"sync"
+
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/cgroup"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/common"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/datadogutils"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/datadogv2"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/writeconcurrencylimiter"
+	"github.com/VictoriaMetrics/metrics"
+)
+
+// Parse parses DataDog POST request for /api/v2/series from reader and calls callback for the parsed request.
+//
+// callback shouldn't hold series after returning.
+func Parse(r io.Reader, contentEncoding, contentType string, callback func(series []datadogv2.Series) error) error {
+	wcr := writeconcurrencylimiter.GetReader(r)
+	defer writeconcurrencylimiter.PutReader(wcr)
+	r = wcr
+
+	switch contentEncoding {
+	case "gzip":
+		zr, err := common.GetGzipReader(r)
+		if err != nil {
+			return fmt.Errorf("cannot read gzipped DataDog data: %w", err)
+		}
+		defer common.PutGzipReader(zr)
+		r = zr
+	case "deflate":
+		zlr, err := common.GetZlibReader(r)
+		if err != nil {
+			return fmt.Errorf("cannot read deflated DataDog data: %w", err)
+		}
+		defer common.PutZlibReader(zlr)
+		r = zlr
+	}
+
+	ctx := getPushCtx(r)
+	defer putPushCtx(ctx)
+	if err := ctx.Read(); err != nil {
+		return err
+	}
+	req := getRequest()
+	defer putRequest(req)
+
+	var err error
+	switch contentType {
+	case "application/x-protobuf":
+		err = datadogv2.UnmarshalProtobuf(req, ctx.reqBuf.B)
+	default:
+		err = datadogv2.UnmarshalJSON(req, ctx.reqBuf.B)
+	}
+	if err != nil {
+		unmarshalErrors.Inc()
+		return fmt.Errorf("cannot unmarshal DataDog %s request with size %d bytes: %w", contentType, len(ctx.reqBuf.B), err)
+	}
+
+	rows := 0
+	series := req.Series
+	for i := range series {
+		rows += len(series[i].Points)
+		if *datadogutils.SanitizeMetricName {
+			series[i].Metric = datadogutils.SanitizeName(series[i].Metric)
+		}
+	}
+	rowsRead.Add(rows)
+
+	if err := callback(series); err != nil {
+		return fmt.Errorf("error when processing imported data: %w", err)
+	}
+	return nil
+}
+
+type pushCtx struct {
+	br     *bufio.Reader
+	reqBuf bytesutil.ByteBuffer
+}
+
+func (ctx *pushCtx) reset() {
+	ctx.br.Reset(nil)
+	ctx.reqBuf.Reset()
+}
+
+func (ctx *pushCtx) Read() error {
+	readCalls.Inc()
+	lr := io.LimitReader(ctx.br, int64(datadogutils.MaxInsertRequestSize.N)+1)
+	startTime := fasttime.UnixTimestamp()
+	reqLen, err := ctx.reqBuf.ReadFrom(lr)
+	if err != nil {
+		readErrors.Inc()
+		return fmt.Errorf("cannot read request in %d seconds: %w", fasttime.UnixTimestamp()-startTime, err)
+	}
+	if reqLen > int64(datadogutils.MaxInsertRequestSize.N) {
+		readErrors.Inc()
+		return fmt.Errorf("too big request; mustn't exceed -datadog.maxInsertRequestSize=%d bytes", datadogutils.MaxInsertRequestSize.N)
+	}
+	return nil
+}
+
+var (
+	readCalls       = metrics.NewCounter(`vm_protoparser_read_calls_total{type="datadogv2"}`)
+	readErrors      = metrics.NewCounter(`vm_protoparser_read_errors_total{type="datadogv2"}`)
+	rowsRead        = metrics.NewCounter(`vm_protoparser_rows_read_total{type="datadogv2"}`)
+	unmarshalErrors = metrics.NewCounter(`vm_protoparser_unmarshal_errors_total{type="datadogv2"}`)
+)
+
+func getPushCtx(r io.Reader) *pushCtx {
+	select {
+	case ctx := <-pushCtxPoolCh:
+		ctx.br.Reset(r)
+		return ctx
+	default:
+		if v := pushCtxPool.Get(); v != nil {
+			ctx := v.(*pushCtx)
+			ctx.br.Reset(r)
+			return ctx
+		}
+		return &pushCtx{
+			br: bufio.NewReaderSize(r, 64*1024),
+		}
+	}
+}
+
+func putPushCtx(ctx *pushCtx) {
+	ctx.reset()
+	select {
+	case pushCtxPoolCh <- ctx:
+	default:
+		pushCtxPool.Put(ctx)
+	}
+}
+
+var pushCtxPool sync.Pool
+var pushCtxPoolCh = make(chan *pushCtx, cgroup.AvailableCPUs())
+
+func getRequest() *datadogv2.Request {
+	v := requestPool.Get()
+	if v == nil {
+		return &datadogv2.Request{}
+	}
+	return v.(*datadogv2.Request)
+}
+
+func putRequest(req *datadogv2.Request) {
+	requestPool.Put(req)
+}
+
+var requestPool sync.Pool
diff --git a/vendor/github.com/VictoriaMetrics/metrics/gauge.go b/vendor/github.com/VictoriaMetrics/metrics/gauge.go
index 9f676f40e..9bbbce21b 100644
--- a/vendor/github.com/VictoriaMetrics/metrics/gauge.go
+++ b/vendor/github.com/VictoriaMetrics/metrics/gauge.go
@@ -28,11 +28,11 @@ func NewGauge(name string, f func() float64) *Gauge {
 
 // Gauge is a float64 gauge.
 type Gauge struct {
-	// f is a callback, which is called for returning the gauge value.
-	f func() float64
-
 	// valueBits contains uint64 representation of float64 passed to Gauge.Set.
 	valueBits uint64
+
+	// f is a callback, which is called for returning the gauge value.
+	f func() float64
 }
 
 // Get returns the current value for g.
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 52ac9993b..39ac4c546 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -97,7 +97,7 @@ github.com/VictoriaMetrics/fastcache
 github.com/VictoriaMetrics/fasthttp
 github.com/VictoriaMetrics/fasthttp/fasthttputil
 github.com/VictoriaMetrics/fasthttp/stackless
-# github.com/VictoriaMetrics/metrics v1.29.0
+# github.com/VictoriaMetrics/metrics v1.29.1
 ## explicit; go 1.17
 github.com/VictoriaMetrics/metrics
 # github.com/VictoriaMetrics/metricsql v0.70.0


Test patch:
diff --git a/lib/flagutil/dict_test.go b/lib/flagutil/dict_test.go
index 31e326ab4..ef66e5ae1 100644
--- a/lib/flagutil/dict_test.go
+++ b/lib/flagutil/dict_test.go
@@ -1,9 +1,56 @@
 package flagutil
 
 import (
+	"encoding/json"
 	"testing"
 )
 
+func TestParseJSONMapSuccess(t *testing.T) {
+	f := func(s string) {
+		t.Helper()
+		m, err := ParseJSONMap(s)
+		if err != nil {
+			t.Fatalf("unexpected error: %s", err)
+		}
+		if s == "" && m == nil {
+			return
+		}
+		data, err := json.Marshal(m)
+		if err != nil {
+			t.Fatalf("cannot marshal m: %s", err)
+		}
+		if s != string(data) {
+			t.Fatalf("unexpected result; got %s; want %s", data, s)
+		}
+	}
+
+	f("")
+	f("{}")
+	f(`{"foo":"bar"}`)
+	f(`{"a":"b","c":"d"}`)
+}
+
+func TestParseJSONMapFailure(t *testing.T) {
+	f := func(s string) {
+		t.Helper()
+		m, err := ParseJSONMap(s)
+		if err == nil {
+			t.Fatalf("expecting non-nil error")
+		}
+		if m != nil {
+			t.Fatalf("expecting nil m")
+		}
+	}
+
+	f("foo")
+	f("123")
+	f("{")
+	f(`{foo:bar}`)
+	f(`{"foo":1}`)
+	f(`[]`)
+	f(`{"foo":"bar","a":[123]}`)
+}
+
 func TestDictIntSetSuccess(t *testing.T) {
 	f := func(s string) {
 		t.Helper()
diff --git a/lib/protoparser/datadog/stream/streamparser_test.go b/lib/protoparser/datadogutils/datadogutils_test.go
similarity index 61%
rename from lib/protoparser/datadog/stream/streamparser_test.go
rename to lib/protoparser/datadogutils/datadogutils_test.go
index 19a52edf4..05575530a 100644
--- a/lib/protoparser/datadog/stream/streamparser_test.go
+++ b/lib/protoparser/datadogutils/datadogutils_test.go
@@ -1,13 +1,30 @@
-package stream
+package datadogutils
 
 import (
 	"testing"
 )
 
+func TestSplitTag(t *testing.T) {
+	f := func(s, nameExpected, valueExpected string) {
+		t.Helper()
+		name, value := SplitTag(s)
+		if name != nameExpected {
+			t.Fatalf("unexpected name obtained from %q; got %q; want %q", s, name, nameExpected)
+		}
+		if value != valueExpected {
+			t.Fatalf("unexpected value obtained from %q; got %q; want %q", s, value, valueExpected)
+		}
+	}
+	f("", "", "no_label_value")
+	f("foo", "foo", "no_label_value")
+	f("foo:bar", "foo", "bar")
+	f(":bar", "", "bar")
+}
+
 func TestSanitizeName(t *testing.T) {
 	f := func(s, resultExpected string) {
 		t.Helper()
-		result := sanitizeName(s)
+		result := SanitizeName(s)
 		if result != resultExpected {
 			t.Fatalf("unexpected result for sanitizeName(%q); got\n%q\nwant\n%q", s, result, resultExpected)
 		}
diff --git a/lib/protoparser/datadog/parser_test.go b/lib/protoparser/datadogv1/parser_test.go
similarity index 79%
rename from lib/protoparser/datadog/parser_test.go
rename to lib/protoparser/datadogv1/parser_test.go
index 5ea720331..d359f616a 100644
--- a/lib/protoparser/datadog/parser_test.go
+++ b/lib/protoparser/datadogv1/parser_test.go
@@ -1,27 +1,10 @@
-package datadog
+package datadogv1
 
 import (
 	"reflect"
 	"testing"
 )
 
-func TestSplitTag(t *testing.T) {
-	f := func(s, nameExpected, valueExpected string) {
-		t.Helper()
-		name, value := SplitTag(s)
-		if name != nameExpected {
-			t.Fatalf("unexpected name obtained from %q; got %q; want %q", s, name, nameExpected)
-		}
-		if value != valueExpected {
-			t.Fatalf("unexpected value obtained from %q; got %q; want %q", s, value, valueExpected)
-		}
-	}
-	f("", "", "no_label_value")
-	f("foo", "foo", "no_label_value")
-	f("foo:bar", "foo", "bar")
-	f(":bar", "", "bar")
-}
-
 func TestRequestUnmarshalMissingHost(t *testing.T) {
 	// This tests https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3432
 	req := Request{
diff --git a/lib/protoparser/datadog/parser_timing_test.go b/lib/protoparser/datadogv1/parser_timing_test.go
similarity index 93%
rename from lib/protoparser/datadog/parser_timing_test.go
rename to lib/protoparser/datadogv1/parser_timing_test.go
index f3c2b568a..6964305dc 100644
--- a/lib/protoparser/datadog/parser_timing_test.go
+++ b/lib/protoparser/datadogv1/parser_timing_test.go
@@ -1,4 +1,4 @@
-package datadog
+package datadogv1
 
 import (
 	"fmt"
@@ -12,10 +12,10 @@ func BenchmarkRequestUnmarshal(b *testing.B) {
       "host": "test.example.com",
       "interval": 20,
       "metric": "system.load.1",
-      "points": [
+      "points": [[
         1575317847,
         0.5
-      ],
+      ]],
       "tags": [
         "environment:test"
       ],
diff --git a/lib/protoparser/datadogv2/parser_test.go b/lib/protoparser/datadogv2/parser_test.go
new file mode 100644
index 000000000..d8ddca466
--- /dev/null
+++ b/lib/protoparser/datadogv2/parser_test.go
@@ -0,0 +1,79 @@
+package datadogv2
+
+import (
+	"reflect"
+	"testing"
+)
+
+func TestRequestUnmarshalJSONFailure(t *testing.T) {
+	f := func(s string) {
+		t.Helper()
+		var req Request
+		if err := UnmarshalJSON(&req, []byte(s)); err == nil {
+			t.Fatalf("expecting non-nil error for Unmarshal(%q)", s)
+		}
+	}
+	f("")
+	f("foobar")
+	f(`{"series":123`)
+	f(`1234`)
+	f(`[]`)
+}
+
+func TestRequestUnmarshalJSONSuccess(t *testing.T) {
+	f := func(s string, reqExpected *Request) {
+		t.Helper()
+		var req Request
+		if err := UnmarshalJSON(&req, []byte(s)); err != nil {
+			t.Fatalf("unexpected error in Unmarshal(%q): %s", s, err)
+		}
+		if !reflect.DeepEqual(&req, reqExpected) {
+			t.Fatalf("unexpected row;\ngot\n%+v\nwant\n%+v", &req, reqExpected)
+		}
+	}
+	f("{}", &Request{})
+	f(`
+{
+  "series": [
+    {
+      "metric": "system.load.1",
+      "type": 0,
+      "points": [
+        {
+          "timestamp": 1636629071,
+          "value": 0.7
+        }
+      ],
+      "resources": [
+        {
+          "name": "dummyhost",
+          "type": "host"
+        }
+      ],
+      "source_type_name": "kubernetes",
+      "tags": ["environment:test"]
+    }
+  ]
+}
+`, &Request{
+		Series: []Series{{
+			Metric: "system.load.1",
+			Points: []Point{
+				{
+					Timestamp: 1636629071,
+					Value:     0.7,
+				},
+			},
+			Resources: []Resource{
+				{
+					Name: "dummyhost",
+					Type: "host",
+				},
+			},
+			SourceTypeName: "kubernetes",
+			Tags: []string{
+				"environment:test",
+			},
+		}},
+	})
+}
diff --git a/lib/protoparser/datadogv2/parser_timing_test.go b/lib/protoparser/datadogv2/parser_timing_test.go
new file mode 100644
index 000000000..83b2c02c6
--- /dev/null
+++ b/lib/protoparser/datadogv2/parser_timing_test.go
@@ -0,0 +1,43 @@
+package datadogv2
+
+import (
+	"fmt"
+	"testing"
+)
+
+func BenchmarkRequestUnmarshalJSON(b *testing.B) {
+	reqBody := []byte(`{
+  "series": [
+    {
+      "metric": "system.load.1",
+      "type": 0,
+      "points": [
+        {
+          "timestamp": 1636629071,
+          "value": 0.7
+        }
+      ],
+      "resources": [
+        {
+          "name": "dummyhost",
+          "type": "host"
+        }
+      ],
+      "tags": ["environment:test"]
+    }
+  ]
+}`)
+	b.SetBytes(int64(len(reqBody)))
+	b.ReportAllocs()
+	b.RunParallel(func(pb *testing.PB) {
+		var req Request
+		for pb.Next() {
+			if err := UnmarshalJSON(&req, reqBody); err != nil {
+				panic(fmt.Errorf("unexpected error: %w", err))
+			}
+			if len(req.Series) != 1 {
+				panic(fmt.Errorf("unexpected number of series unmarshaled: got %d; want 4", len(req.Series)))
+			}
+		}
+	})
+}