Repository: VictoriaMetrics/VictoriaMetrics
Pull request #775 (2020-09-17 14:16:51)

Add improvements to ec2_sd_discovery
 role_arn support with aws sts
 instance iam_role support
 refreshing temporary tokens

https://github.com/VictoriaMetrics/VictoriaMetrics/issues/771
#771 



Patch:
diff --git a/app/vmagent/README.md b/app/vmagent/README.md
index abb2e1118..d596cc59f 100644
--- a/app/vmagent/README.md
+++ b/app/vmagent/README.md
@@ -135,7 +135,7 @@ The following scrape types in [scrape_config](https://prometheus.io/docs/prometh
   See [kubernetes_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#kubernetes_sd_config) for details.
 * `ec2_sd_configs` - for scraping targets in Amazon EC2.
   See [ec2_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#ec2_sd_config) for details.
-  `vmagent` doesn't support `role_arn` config param yet.
+  `vmagent` doesn't support `profile` config param and aws credentials file yet.
 * `gce_sd_configs` - for scraping targets in Google Compute Engine (GCE).
   See [gce_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#gce_sd_config) for details.
   `vmagent` provides the following additional functionality for `gce_sd_config`:
diff --git a/app/vmagent/remotewrite/remotewrite.go b/app/vmagent/remotewrite/remotewrite.go
index f018c34b6..8431eb3bc 100644
--- a/app/vmagent/remotewrite/remotewrite.go
+++ b/app/vmagent/remotewrite/remotewrite.go
@@ -23,7 +23,7 @@ var (
 		"It is recommended using VictoriaMetrics as remote storage. Example url: http://<victoriametrics-host>:8428/api/v1/write . "+
 		"Pass multiple -remoteWrite.url flags in order to write data concurrently to multiple remote storage systems")
 	tmpDataPath = flag.String("remoteWrite.tmpDataPath", "vmagent-remotewrite-data", "Path to directory where temporary data for remote write component is stored")
-	queues      = flag.Int("remoteWrite.queues", 1, "The number of concurrent queues to each -remoteWrite.url. Set more queues if a single queue "+
+	queues      = flag.Int("remoteWrite.queues", 4, "The number of concurrent queues to each -remoteWrite.url. Set more queues if default number of queues "+
 		"isn't enough for sending high volume of collected data to remote storage")
 	showRemoteWriteURL = flag.Bool("remoteWrite.showURL", false, "Whether to show -remoteWrite.url in the exported metrics. "+
 		"It is hidden by default, since it can contain sensitive info such as auth key")
diff --git a/app/vmalert/README.md b/app/vmalert/README.md
index 846245fa5..3b1df8103 100644
--- a/app/vmalert/README.md
+++ b/app/vmalert/README.md
@@ -166,6 +166,8 @@ The shortlist of configuration flags is the following:
     	Optional basic auth password for -datasource.url
   -datasource.basicAuth.username string
     	Optional basic auth username for -datasource.url
+ -datasource.lookback duration
+        Lookback defines how far to look into past when evaluating queries. For example, if datasource.lookback=5m then param "time" with value now()-5m will be added to every query.
   -datasource.tlsCAFile string
     	Optional path to TLS CA file to use for verifying connections to -datasource.url. By default system CA is used
   -datasource.tlsCertFile string
diff --git a/app/vmalert/datasource/init.go b/app/vmalert/datasource/init.go
index 244d3c9c3..89814dde5 100644
--- a/app/vmalert/datasource/init.go
+++ b/app/vmalert/datasource/init.go
@@ -21,6 +21,9 @@ var (
 		"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")
+
+	lookBack = flag.Duration("datasource.lookback", 0, "Lookback defines how far to look into past when evaluating queries. "+
+		"For example, if datasource.lookback=5m then param \"time\" with value now()-5m will be added to every query.")
 )
 
 // Init creates a Querier from provided flag values.
@@ -34,5 +37,5 @@ func Init() (Querier, error) {
 		return nil, fmt.Errorf("failed to create transport: %w", err)
 	}
 	c := &http.Client{Transport: tr}
-	return NewVMStorage(*addr, *basicAuthUsername, *basicAuthPassword, c), nil
+	return NewVMStorage(*addr, *basicAuthUsername, *basicAuthPassword, *lookBack, c), nil
 }
diff --git a/app/vmalert/datasource/vm.go b/app/vmalert/datasource/vm.go
index 65c3db9e0..88458b827 100644
--- a/app/vmalert/datasource/vm.go
+++ b/app/vmalert/datasource/vm.go
@@ -9,6 +9,7 @@ import (
 	"net/url"
 	"strconv"
 	"strings"
+	"time"
 )
 
 type response struct {
@@ -45,23 +46,25 @@ func (r response) metrics() ([]Metric, error) {
 	return ms, nil
 }
 
-const queryPath = "/api/v1/query?query="
-
 // VMStorage represents vmstorage entity with ability to read and write metrics
 type VMStorage struct {
 	c             *http.Client
 	queryURL      string
 	basicAuthUser string
 	basicAuthPass string
+	lookBack      time.Duration
 }
 
+const queryPath = "/api/v1/query?query="
+
 // NewVMStorage is a constructor for VMStorage
-func NewVMStorage(baseURL, basicAuthUser, basicAuthPass string, c *http.Client) *VMStorage {
+func NewVMStorage(baseURL, basicAuthUser, basicAuthPass string, lookBack time.Duration, c *http.Client) *VMStorage {
 	return &VMStorage{
 		c:             c,
 		basicAuthUser: basicAuthUser,
 		basicAuthPass: basicAuthPass,
 		queryURL:      strings.TrimSuffix(baseURL, "/") + queryPath,
+		lookBack:      lookBack,
 	}
 }
 
@@ -70,7 +73,12 @@ func (s *VMStorage) Query(ctx context.Context, query string) ([]Metric, error) {
 	const (
 		statusSuccess, statusError, rtVector = "success", "error", "vector"
 	)
-	req, err := http.NewRequest("POST", s.queryURL+url.QueryEscape(query), nil)
+	q := s.queryURL + url.QueryEscape(query)
+	if s.lookBack > 0 {
+		lookBack := time.Now().Add(-s.lookBack)
+		q += fmt.Sprintf("&time=%d", lookBack.Unix())
+	}
+	req, err := http.NewRequest("POST", q, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -85,7 +93,7 @@ func (s *VMStorage) Query(ctx context.Context, query string) ([]Metric, error) {
 	defer func() { _ = resp.Body.Close() }()
 	if resp.StatusCode != http.StatusOK {
 		body, _ := ioutil.ReadAll(resp.Body)
-		return nil, fmt.Errorf("datasource returns unexpected response code %d for %s with err %w. Reponse body %s", resp.StatusCode, req.URL, err, body)
+		return nil, fmt.Errorf("datasource returns unexpected response code %d for %s. Reponse body %s", resp.StatusCode, req.URL, body)
 	}
 	r := &response{}
 	if err := json.NewDecoder(resp.Body).Decode(r); err != nil {
diff --git a/app/vmalert/remoteread/init.go b/app/vmalert/remoteread/init.go
index 69067493d..dfbcba8f6 100644
--- a/app/vmalert/remoteread/init.go
+++ b/app/vmalert/remoteread/init.go
@@ -35,5 +35,5 @@ func Init() (datasource.Querier, error) {
 		return nil, fmt.Errorf("failed to create transport: %w", err)
 	}
 	c := &http.Client{Transport: tr}
-	return datasource.NewVMStorage(*addr, *basicAuthUsername, *basicAuthPassword, c), nil
+	return datasource.NewVMStorage(*addr, *basicAuthUsername, *basicAuthPassword, 0, c), nil
 }
diff --git a/app/vmselect/graphite/graphite.go b/app/vmselect/graphite/graphite.go
index 25c63f6eb..73d180854 100644
--- a/app/vmselect/graphite/graphite.go
+++ b/app/vmselect/graphite/graphite.go
@@ -74,10 +74,11 @@ func MetricsFindHandler(startTime time.Time, w http.ResponseWriter, r *http.Requ
 		MinTimestamp: from,
 		MaxTimestamp: until,
 	}
-	paths, err := metricsFind(tr, label, query, delimiter[0], deadline)
+	paths, err := metricsFind(tr, label, query, delimiter[0], false, deadline)
 	if err != nil {
 		return err
 	}
+	paths = deduplicatePaths(paths)
 	if leavesOnly {
 		paths = filterLeaves(paths, delimiter)
 	}
@@ -92,6 +93,18 @@ func MetricsFindHandler(startTime time.Time, w http.ResponseWriter, r *http.Requ
 	return nil
 }
 
+func deduplicatePaths(paths []string) []string {
+	m := make(map[string]struct{}, len(paths))
+	for _, path := range paths {
+		m[path] = struct{}{}
+	}
+	dst := make([]string, 0, len(m))
+	for path := range m {
+		dst = append(dst, path)
+	}
+	return dst
+}
+
 // MetricsExpandHandler implements /metrics/expand handler.
 //
 // See https://graphite-api.readthedocs.io/en/latest/api.html#metrics-expand
@@ -133,7 +146,7 @@ func MetricsExpandHandler(startTime time.Time, w http.ResponseWriter, r *http.Re
 	}
 	m := make(map[string][]string, len(queries))
 	for _, query := range queries {
-		paths, err := metricsFind(tr, label, query, delimiter[0], deadline)
+		paths, err := metricsFind(tr, label, query, delimiter[0], true, deadline)
 		if err != nil {
 			return err
 		}
@@ -197,29 +210,33 @@ func MetricsIndexHandler(startTime time.Time, w http.ResponseWriter, r *http.Req
 }
 
 // metricsFind searches for label values that match the given query.
-func metricsFind(tr storage.TimeRange, label, query string, delimiter byte, deadline searchutils.Deadline) ([]string, error) {
-	expandTail := strings.HasSuffix(query, "*")
-	for strings.HasSuffix(query, "*") {
-		query = query[:len(query)-1]
-	}
-	var results []string
+func metricsFind(tr storage.TimeRange, label, query string, delimiter byte, isExpand bool, deadline searchutils.Deadline) ([]string, error) {
 	n := strings.IndexAny(query, "*{[")
-	if n < 0 {
+	if n < 0 || n == len(query)-1 && strings.HasSuffix(query, "*") {
+		expandTail := n >= 0
+		if expandTail {
+			query = query[:len(query)-1]
+		}
 		suffixes, err := netstorage.GetTagValueSuffixes(tr, label, query, delimiter, deadline)
 		if err != nil {
 			return nil, err
 		}
-		if expandTail {
-			for _, suffix := range suffixes {
+		if len(suffixes) == 0 {
+			return nil, nil
+		}
+		if !expandTail && len(query) > 0 && query[len(query)-1] == delimiter {
+			return []string{query}, nil
+		}
+		results := make([]string, 0, len(suffixes))
+		for _, suffix := range suffixes {
+			if expandTail || len(suffix) == 0 || len(suffix) == 1 && suffix[0] == delimiter {
 				results = append(results, query+suffix)
 			}
-		} else if isFullMatch(query, suffixes, delimiter) {
-			results = append(results, query)
 		}
 		return results, nil
 	}
 	subquery := query[:n] + "*"
-	paths, err := metricsFind(tr, label, subquery, delimiter, deadline)
+	paths, err := metricsFind(tr, label, subquery, delimiter, isExpand, deadline)
 	if err != nil {
 		return nil, err
 	}
@@ -229,24 +246,32 @@ func metricsFind(tr storage.TimeRange, label, query string, delimiter byte, dead
 		tail = suffix[m+1:]
 		suffix = suffix[:m+1]
 	}
-	q := query[:n] + suffix
-	re, err := getRegexpForQuery(q, delimiter)
+	qPrefix := query[:n] + suffix
+	rePrefix, err := getRegexpForQuery(qPrefix, delimiter)
 	if err != nil {
-		return nil, fmt.Errorf("cannot convert query %q to regexp: %w", q, err)
-	}
-	if expandTail {
-		tail += "*"
+		return nil, fmt.Errorf("cannot convert query %q to regexp: %w", qPrefix, err)
 	}
+	results := make([]string, 0, len(paths))
 	for _, path := range paths {
-		if !re.MatchString(path) {
+		if !rePrefix.MatchString(path) {
+			continue
+		}
+		if tail == "" {
+			results = append(results, path)
 			continue
 		}
 		subquery := path + tail
-		tmp, err := metricsFind(tr, label, subquery, delimiter, deadline)
+		fullPaths, err := metricsFind(tr, label, subquery, delimiter, isExpand, deadline)
 		if err != nil {
 			return nil, err
 		}
-		results = append(results, tmp...)
+		if isExpand {
+			results = append(results, fullPaths...)
+		} else {
+			for _, fullPath := range fullPaths {
+				results = append(results, qPrefix+fullPath[len(path):])
+			}
+		}
 	}
 	return results, nil
 }
@@ -257,21 +282,6 @@ var (
 	metricsIndexDuration  = metrics.NewSummary(`vm_request_duration_seconds{path="/metrics/index.json"}`)
 )
 
-func isFullMatch(tagValuePrefix string, suffixes []string, delimiter byte) bool {
-	if len(suffixes) == 0 {
-		return false
-	}
-	if strings.LastIndexByte(tagValuePrefix, delimiter) == len(tagValuePrefix)-1 {
-		return true
-	}
-	for _, suffix := range suffixes {
-		if suffix == "" {
-			return true
-		}
-	}
-	return false
-}
-
 func addAutomaticVariants(query, delimiter string) string {
 	// See https://github.com/graphite-project/graphite-web/blob/bb9feb0e6815faa73f538af6ed35adea0fb273fd/webapp/graphite/metrics/views.py#L152
 	parts := strings.Split(query, delimiter)
@@ -317,7 +327,8 @@ func getRegexpForQuery(query string, delimiter byte) (*regexp.Regexp, error) {
 		return re.re, re.err
 	}
 	a := make([]string, 0, len(query))
-	tillNextDelimiter := "[^" + regexp.QuoteMeta(string([]byte{delimiter})) + "]*"
+	quotedDelimiter := regexp.QuoteMeta(string([]byte{delimiter}))
+	tillNextDelimiter := "[^" + quotedDelimiter + "]*"
 	for i := 0; i < len(query); i++ {
 		switch query[i] {
 		case '*':
@@ -351,6 +362,10 @@ func getRegexpForQuery(query string, delimiter byte) (*regexp.Regexp, error) {
 		}
 	}
 	s := strings.Join(a, "")
+	if !strings.HasSuffix(s, quotedDelimiter) {
+		s += quotedDelimiter + "?"
+	}
+	s = "^(?:" + s + ")$"
 	re, err := regexp.Compile(s)
 	regexpCache[k] = &regexpCacheEntry{
 		re:  re,
diff --git a/go.mod b/go.mod
index 0e7f4a50e..2b997d958 100644
--- a/go.mod
+++ b/go.mod
@@ -7,7 +7,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.0.5
+	github.com/VictoriaMetrics/fasthttp v1.0.7
 	github.com/VictoriaMetrics/metrics v1.12.3
 	github.com/VictoriaMetrics/metricsql v0.6.0
 	github.com/aws/aws-sdk-go v1.34.25
@@ -19,7 +19,7 @@ require (
 	github.com/valyala/fasttemplate v1.2.1
 	github.com/valyala/gozstd v1.8.3
 	github.com/valyala/histogram v1.1.2
-	github.com/valyala/quicktemplate v1.6.2
+	github.com/valyala/quicktemplate v1.6.3
 	golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43
 	golang.org/x/sys v0.0.0-20200916084744-dbad9cb7cb7a
 	golang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3 // indirect
diff --git a/go.sum b/go.sum
index 87a66f751..e98c2033a 100644
--- a/go.sum
+++ b/go.sum
@@ -54,8 +54,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/VictoriaMetrics/fastcache v1.5.7 h1:4y6y0G8PRzszQUYIQHHssv/jgPHAb5qQuuDNdCbyAgw=
 github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8=
-github.com/VictoriaMetrics/fasthttp v1.0.5 h1:DvNn5bStvgb7cZbWUkvd7psaPY9ul+7b2gA6uoiCmHc=
-github.com/VictoriaMetrics/fasthttp v1.0.5/go.mod h1:88NVQ+fmidWtXEFT153t7lAEZNu6Q3DgK1g0l1ZyQtw=
+github.com/VictoriaMetrics/fasthttp v1.0.7 h1:9RntF8jE9z+ooyXy0tb3Pl76jan4DLaCQoCQFp/cIUE=
+github.com/VictoriaMetrics/fasthttp v1.0.7/go.mod h1:eaGv8oDDOWE8JQa6GgBgpCwjJhRxavmprtf2oWw8b3o=
 github.com/VictoriaMetrics/metrics v1.12.2 h1:SG8iAmqavDNuh7GIdHPoGHUhDL23KeKfvSZSozucNeA=
 github.com/VictoriaMetrics/metrics v1.12.2/go.mod h1:Z1tSfPfngDn12bTfZSCqArT3OPY3u88J12hSoOhuiRE=
 github.com/VictoriaMetrics/metrics v1.12.3 h1:Fe6JHC6MSEKa+BtLhPN8WIvS+HKPzMc2evEpNeCGy7I=
@@ -152,10 +152,6 @@ github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfE
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
-github.com/klauspost/compress v1.10.10 h1:a/y8CglcM7gLGYmlbP/stPE5sR3hbhFRUjCBfd/0B3I=
-github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
-github.com/klauspost/compress v1.10.11 h1:K9z59aO18Aywg2b/WSgBaUX99mHy2BES18Cr5lBKZHk=
-github.com/klauspost/compress v1.10.11/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
 github.com/klauspost/compress v1.11.0 h1:wJbzvpYMVGG9iTI9VxpnNZfd4DzMPoCWze3GgSqz8yg=
 github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
@@ -175,7 +171,7 @@ github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
-github.com/valyala/fasthttp v1.15.1/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA=
+github.com/valyala/fasthttp v1.16.0/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA=
 github.com/valyala/fastjson v1.6.1 h1:qJs/Kz/HebWzk8LmhOrSm7kdOyJBr1XB+zSkYtEEfQE=
 github.com/valyala/fastjson v1.6.1/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
 github.com/valyala/fastrand v1.0.0 h1:LUKT9aKer2dVQNUi3waewTbKV+7H17kvWFNKs2ObdkI=
@@ -186,8 +182,8 @@ github.com/valyala/gozstd v1.8.3 h1:nHlS+sCFoNLsZpRPKDviXkhHybaRSUjH2w0P/myYo0I=
 github.com/valyala/gozstd v1.8.3/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ=
 github.com/valyala/histogram v1.1.2 h1:vOk5VrGjMBIoPR5k6wA8vBaC8toeJ8XO0yfRjFEc1h8=
 github.com/valyala/histogram v1.1.2/go.mod h1:CZAr6gK9dbD7hYx2s8WSPh0p5x5wETjC+2b3PJVtEdg=
-github.com/valyala/quicktemplate v1.6.2 h1:k0vgK7zlmFzqAoIBIOrhrfmZ6JoTGJlLRPLbkPGr2/M=
-github.com/valyala/quicktemplate v1.6.2/go.mod h1:mtEJpQtUiBV0SHhMX6RtiJtqxncgrfmjcUy5T68X8TM=
+github.com/valyala/quicktemplate v1.6.3 h1:O7EuMwuH7Q94U2CXD6sOX8AYHqQqWtmIk690IhmpkKA=
+github.com/valyala/quicktemplate v1.6.3/go.mod h1:fwPzK2fHuYEODzJ9pkw0ipCPNHZ2tD5KW4lOuSdPKzY=
 github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
 github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
diff --git a/lib/decimal/decimal.go b/lib/decimal/decimal.go
index d474f7382..4c65261ef 100644
--- a/lib/decimal/decimal.go
+++ b/lib/decimal/decimal.go
@@ -76,11 +76,20 @@ func ExtendInt64sCapacity(dst []int64, additionalItems int) []int64 {
 	return dst[:dstLen]
 }
 
+func extendInt16sCapacity(dst []int16, additionalItems int) []int16 {
+	dstLen := len(dst)
+	if n := dstLen + additionalItems - cap(dst); n > 0 {
+		dst = append(dst[:cap(dst)], make([]int16, n)...)
+	}
+	return dst[:dstLen]
+}
+
 // AppendDecimalToFloat converts each item in va to f=v*10^e, appends it
 // to dst and returns the resulting dst.
 func AppendDecimalToFloat(dst []float64, va []int64, e int16) []float64 {
 	// Extend dst capacity in order to eliminate memory allocations below.
 	dst = ExtendFloat64sCapacity(dst, len(va))
+	a := dst[len(dst) : len(dst)+len(va)]
 
 	if fastnum.IsInt64Zeros(va) {
 		return fastnum.AppendFloat64Zeros(dst, len(va))
@@ -89,35 +98,53 @@ func AppendDecimalToFloat(dst []float64, va []int64, e int16) []float64 {
 		if fastnum.IsInt64Ones(va) {
 			return fastnum.AppendFloat64Ones(dst, len(va))
 		}
-		for _, v := range va {
+		_ = a[len(va)-1]
+		for i, v := range va {
 			f := float64(v)
-			dst = append(dst, f)
+			if v == vInfPos {
+				f = infPos
+			} else if v == vInfNeg {
+				f = infNeg
+			}
+			a[i] = f
 		}
-		return dst
+		return dst[:len(dst)+len(va)]
 	}
 
 	// increase conversion precision for negative exponents by dividing by e10
 	if e < 0 {
 		e10 := math.Pow10(int(-e))
-		for _, v := range va {
+		_ = a[len(va)-1]
+		for i, v := range va {
 			f := float64(v) / e10
-			dst = append(dst, f)
+			if v == vInfPos {
+				f = infPos
+			} else if v == vInfNeg {
+				f = infNeg
+			}
+			a[i] = f
 		}
-		return dst
+		return dst[:len(dst)+len(va)]
 	}
 	e10 := math.Pow10(int(e))
-	for _, v := range va {
+	_ = a[len(va)-1]
+	for i, v := range va {
 		f := float64(v) * e10
-		dst = append(dst, f)
+		if v == vInfPos {
+			f = infPos
+		} else if v == vInfNeg {
+			f = infNeg
+		}
+		a[i] = f
 	}
-	return dst
+	return dst[:len(dst)+len(va)]
 }
 
 // AppendFloatToDecimal converts each item in src to v*10^e and appends
 // each v to dst returning it as va.
 //
 // It tries minimizing each item in dst.
-func AppendFloatToDecimal(dst []int64, src []float64) (va []int64, e int16) {
+func AppendFloatToDecimal(dst []int64, src []float64) ([]int64, int16) {
 	if len(src) == 0 {
 		return dst, 0
 	}
@@ -130,9 +157,6 @@ func AppendFloatToDecimal(dst []int64, src []float64) (va []int64, e int16) {
 		return dst, 0
 	}
 
-	// Extend dst capacity in order to eliminate memory allocations below.
-	dst = ExtendInt64sCapacity(dst, len(src))
-
 	vaev := vaeBufPool.Get()
 	if vaev == nil {
 		vaev = &vaeBuf{
@@ -141,19 +165,20 @@ func AppendFloatToDecimal(dst []int64, src []float64) (va []int64, e int16) {
 		}
 	}
 	vae := vaev.(*vaeBuf)
-	vae.va = vae.va[:0]
-	vae.ea = vae.ea[:0]
+	va := vae.va[:0]
+	ea := vae.ea[:0]
+	va = ExtendInt64sCapacity(va, len(src))
+	va = va[:len(src)]
+	ea = extendInt16sCapacity(ea, len(src))
+	ea = ea[:len(src)]
 
 	// Determine the minimum exponent across all src items.
-	v, exp := FromFloat(src[0])
-	vae.va = append(vae.va, v)
-	vae.ea = append(vae.ea, exp)
-	minExp := exp
-	for _, f := range src[1:] {
+	minExp := int16(1<<15 - 1)
+	for i, f := range src {
 		v, exp := FromFloat(f)
-		vae.va = append(vae.va, v)
-		vae.ea = append(vae.ea, exp)
-		if exp < minExp {
+		va[i] = v
+		ea[i] = exp
+		if exp < minExp && v != vInfPos && v != vInfNeg {
 			minExp = exp
 		}
 	}
@@ -161,8 +186,12 @@ func AppendFloatToDecimal(dst []int64, src []float64) (va []int64, e int16) {
 	// Determine whether all the src items may be upscaled to minExp.
 	// If not, adjust minExp accordingly.
 	downExp := int16(0)
-	for i, v := range vae.va {
-		exp := vae.ea[i]
+	_ = ea[len(va)-1]
+	for i, v := range va {
+		if v == vInfPos || v == vInfNeg {
+			continue
+		}
+		exp := ea[i]
 		upExp := exp - minExp
 		maxUpExp := maxUpExponent(v)
 		if upExp-maxUpExp > downExp {
@@ -171,9 +200,19 @@ func AppendFloatToDecimal(dst []int64, src []float64) (va []int64, e int16) {
 	}
 	minExp += downExp
 
+	// Extend dst capacity in order to eliminate memory allocations below.
+	dst = ExtendInt64sCapacity(dst, len(src))
+	a := dst[len(dst) : len(dst)+len(src)]
+
 	// Scale each item in src to minExp and append it to dst.
-	for i, v := range vae.va {
-		exp := vae.ea[i]
+	_ = a[len(va)-1]
+	_ = ea[len(va)-1]
+	for i, v := range va {
+		if v == vInfPos || v == vInfNeg {
+			a[i] = v
+			continue
+		}
+		exp := ea[i]
 		adjExp := exp - minExp
 		for adjExp > 0 {
 			v *= 10
@@ -183,12 +222,14 @@ func AppendFloatToDecimal(dst []int64, src []float64) (va []int64, e int16) {
 			v /= 10
 			adjExp++
 		}
-		dst = append(dst, v)
+		a[i] = v
 	}
 
+	vae.va = va
+	vae.ea = ea
 	vaeBufPool.Put(vae)
 
-	return dst, minExp
+	return dst[:len(dst)+len(va)], minExp
 }
 
 type vaeBuf struct {
@@ -198,6 +239,8 @@ type vaeBuf struct {
 
 var vaeBufPool sync.Pool
 
+const int64Max = int64(1<<63 - 1)
+
 func maxUpExponent(v int64) int16 {
 	if v == 0 {
 		// Any exponent allowed.
@@ -210,46 +253,42 @@ func maxUpExponent(v int64) int16 {
 		// Handle corner case for v=-1<<63
 		return 0
 	}
-
-	maxMultiplier := ((1 << 63) - 1) / uint64(v)
 	switch {
-	case maxMultiplier >= 1e19:
-		return 19
-	case maxMultiplier >= 1e18:
+	case v <= int64Max/1e18:
 		return 18
-	case maxMultiplier >= 1e17:
+	case v <= int64Max/1e17:
 		return 17
-	case maxMultiplier >= 1e16:
+	case v <= int64Max/1e16:
 		return 16
-	case maxMultiplier >= 1e15:
+	case v <= int64Max/1e15:
 		return 15
-	case maxMultiplier >= 1e14:
+	case v <= int64Max/1e14:
 		return 14
-	case maxMultiplier >= 1e13:
+	case v <= int64Max/1e13:
 		return 13
-	case maxMultiplier >= 1e12:
+	case v <= int64Max/1e12:
 		return 12
-	case maxMultiplier >= 1e11:
+	case v <= int64Max/1e11:
 		return 11
-	case maxMultiplier >= 1e10:
+	case v <= int64Max/1e10:
 		return 10
-	case maxMultiplier >= 1e9:
+	case v <= int64Max/1e9:
 		return 9
-	case maxMultiplier >= 1e8:
+	case v <= int64Max/1e8:
 		return 8
-	case maxMultiplier >= 1e7:
+	case v <= int64Max/1e7:
 		return 7
-	case maxMultiplier >= 1e6:
+	case v <= int64Max/1e6:
 		return 6
-	case maxMultiplier >= 1e5:
+	case v <= int64Max/1e5:
 		return 5
-	case maxMultiplier >= 1e4:
+	case v <= int64Max/1e4:
 		return 4
-	case maxMultiplier >= 1e3:
+	case v <= int64Max/1e3:
 		return 3
-	case maxMultiplier >= 1e2:
+	case v <= int64Max/1e2:
 		return 2
-	case maxMultiplier >= 1e1:
+	case v <= int64Max/1e1:
 		return 1
 	default:
 		return 0
@@ -290,6 +329,12 @@ func Round(f float64, digits int) float64 {
 
 // ToFloat returns f=v*10^e.
 func ToFloat(v int64, e int16) float64 {
+	if v == vInfPos {
+		return infPos
+	}
+	if v == vInfNeg {
+		return infNeg
+	}
 	f := float64(v)
 	// increase conversion precision for negative exponents by dividing by e10
 	if e < 0 {
@@ -298,6 +343,11 @@ func ToFloat(v int64, e int16) float64 {
 	return f * math.Pow10(int(e))
 }
 
+var (
+	infPos = math.Inf(1)
+	infNeg = math.Inf(-1)
+)
+
 const (
 	vInfPos = 1<<63 - 1
 	vInfNeg = -1 << 63
diff --git a/lib/filestream/filestream.go b/lib/filestream/filestream.go
index f4c0d790f..9fbe35872 100644
--- a/lib/filestream/filestream.go
+++ b/lib/filestream/filestream.go
@@ -254,11 +254,17 @@ func (w *Writer) Write(p []byte) (int, error) {
 }
 
 // MustFlush flushes all the buffered data to file.
-func (w *Writer) MustFlush() {
+//
+// if isSync is true, then the flushed data is fsynced to the underlying storage.
+func (w *Writer) MustFlush(isSync bool) {
 	if err := w.bw.Flush(); err != nil {
 		logger.Panicf("FATAL: cannot flush buffered data to file %q: %s", w.f.Name(), err)
 	}
-	// Do not call w.f.Sync() for performance reasons.
+	if isSync {
+		if err := w.f.Sync(); err != nil {
+			logger.Panicf("FATAL: cannot fsync data to the underlying storage for file %q: %s", w.f.Name(), err)
+		}
+	}
 }
 
 type statWriter struct {
diff --git a/lib/persistentqueue/fastqueue.go b/lib/persistentqueue/fastqueue.go
index 18aba7fbc..5a3dd33e2 100644
--- a/lib/persistentqueue/fastqueue.go
+++ b/lib/persistentqueue/fastqueue.go
@@ -47,7 +47,8 @@ func MustOpenFastQueue(path, name string, maxInmemoryBlocks, maxPendingBytes int
 	}
 	fq.cond.L = &fq.mu
 	fq.lastInmemoryBlockReadTime = fasttime.UnixTimestamp()
-	logger.Infof("opened fast persistent queue at %q with maxInmemoryBlocks=%d", path, maxInmemoryBlocks)
+	pendingBytes := fq.GetPendingBytes()
+	logger.Infof("opened fast persistent queue at %q with maxInmemoryBlocks=%d, it contains %d pending bytes", path, maxInmemoryBlocks, pendingBytes)
 	return fq
 }
 
diff --git a/lib/persistentqueue/persistentqueue.go b/lib/persistentqueue/persistentqueue.go
index 159f21c33..c003a3507 100644
--- a/lib/persistentqueue/persistentqueue.go
+++ b/lib/persistentqueue/persistentqueue.go
@@ -411,7 +411,8 @@ func (q *Queue) writeBlockLocked(block []byte) error {
 	if q.writerLocalOffset+q.maxBlockSize+8 > q.chunkFileSize {
 		// Finalize the current chunk and start new one.
 		q.writer.MustClose()
-		fs.MustSyncPath(q.writerPath)
+		// There is no need to do fs.MustSyncPath(q.writerPath) here,
+		// since MustClose already does this.
 		if n := q.writerOffset % q.chunkFileSize; n > 0 {
 			q.writerOffset += (q.chunkFileSize - n)
 		}
@@ -445,7 +446,7 @@ func (q *Queue) writeBlockLocked(block []byte) error {
 	}
 	q.blocksWritten.Inc()
 	q.bytesWritten.Add(len(block))
-	return q.flushMetainfoIfNeededLocked()
+	return q.flushMetainfoIfNeededLocked(true)
 }
 
 // MustReadBlock appends the next block from q to dst and returns the result.
@@ -522,7 +523,7 @@ func (q *Queue) readBlockLocked(dst []byte) ([]byte, error) {
 	}
 	q.blocksRead.Inc()
 	q.bytesRead.Add(int(blockLen))
-	if err := q.flushMetainfoIfNeededLocked(); err != nil {
+	if err := q.flushMetainfoIfNeededLocked(false); err != nil {
 		return dst, err
 	}
 	return dst, nil
@@ -545,7 +546,7 @@ func (q *Queue) write(buf []byte) error {
 func (q *Queue) readFull(buf []byte) error {
 	bufLen := uint64(len(buf))
 	if q.readerOffset+bufLen > q.writerFlushedOffset {
-		q.writer.MustFlush()
+		q.writer.MustFlush(false)
 		q.writerFlushedOffset = q.writerOffset
 	}
 	n, err := io.ReadFull(q.reader, buf)
@@ -560,11 +561,14 @@ func (q *Queue) readFull(buf []byte) error {
 	return nil
 }
 
-func (q *Queue) flushMetainfoIfNeededLocked() error {
+func (q *Queue) flushMetainfoIfNeededLocked(flushData bool) error {
 	t := fasttime.UnixTimestamp()
 	if t == q.lastMetainfoFlushTime {
 		return nil
 	}
+	if flushData {
+		q.writer.MustFlush(true)
+	}
 	if err := q.flushMetainfoLocked(); err != nil {
 		return fmt.Errorf("cannot flush metainfo: %w", err)
 	}
diff --git a/lib/promscrape/client.go b/lib/promscrape/client.go
index 17894f2bb..e7f4d0f5e 100644
--- a/lib/promscrape/client.go
+++ b/lib/promscrape/client.go
@@ -97,7 +97,16 @@ func (c *client) ReadData(dst []byte) ([]byte, error) {
 		req.Header.Set("Authorization", c.authHeader)
 	}
 	resp := fasthttp.AcquireResponse()
+	swapResponseBodies := len(dst) == 0
+	if swapResponseBodies {
+		// An optimization: write response directly to dst.
+		// This should reduce memory uage when scraping big targets.
+		dst = resp.SwapBody(dst)
+	}
 	err := doRequestWithPossibleRetry(c.hc, req, resp, deadline)
+	if swapResponseBodies {
+		dst = resp.SwapBody(dst)
+	}
 	statusCode := resp.StatusCode()
 	if err == nil && (statusCode == fasthttp.StatusMovedPermanently || statusCode == fasthttp.StatusFound) {
 		// Allow a single redirect.
@@ -125,14 +134,21 @@ func (c *client) ReadData(dst []byte) ([]byte, error) {
 	dstLen := len(dst)
 	if ce := resp.Header.Peek("Content-Encoding"); string(ce) == "gzip" {
 		var err error
-		dst, err = fasthttp.AppendGunzipBytes(dst, resp.Body())
+		var src []byte
+		if swapResponseBodies {
+			src = append(src, dst...)
+			dst = dst[:0]
+		} else {
+			src = resp.Body()
+		}
+		dst, err = fasthttp.AppendGunzipBytes(dst, src)
 		if err != nil {
 			fasthttp.ReleaseResponse(resp)
 			scrapesGunzipFailed.Inc()
 			return dst, fmt.Errorf("cannot ungzip response from %q: %w", c.scrapeURL, err)
 		}
 		scrapesGunzipped.Inc()
-	} else {
+	} else if !swapResponseBodies {
 		dst = append(dst, resp.Body()...)
 	}
 	if statusCode != fasthttp.StatusOK {
diff --git a/lib/promscrape/discovery/ec2/api.go b/lib/promscrape/discovery/ec2/api.go
index 38e0d6bc0..cfbdc7f27 100644
--- a/lib/promscrape/discovery/ec2/api.go
+++ b/lib/promscrape/discovery/ec2/api.go
@@ -2,29 +2,64 @@ package ec2
 
 import (
 	"encoding/json"
+	"encoding/xml"
 	"fmt"
 	"io/ioutil"
 	"net/http"
 	"net/url"
 	"os"
 	"strings"
+	"sync/atomic"
+	"time"
 
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
 )
 
+const (
+	awsAccessKeyEnv = "AWS_ACCESS_KEY_ID"
+	awsSecretKeyEnv = "AWS_SECRET_ACCESS_KEY"
+)
+
 type apiConfig struct {
-	endpoint  string
-	region    string
-	accessKey string
-	secretKey string
-	filters   string
-	port      int
+	endpoint string
+	region   string
+	roleARN  string
+	filters  string
+	port     int
+	// this keys  needed,
+	// when we are using temporary credentials
+	defaultAccessKey string
+	defaultSecretKey string
+	apiCredentials   atomic.Value
+}
+
+// apiCredentials represents aws api credentials
+type apiCredentials struct {
+	AccessKeyID     string    `json:"AccessKeyId"`
+	SecretAccessKey string    `json:"SecretAccessKey"`
+	Token           string    `json:"Token"`
+	Expiration      time.Time `json:"Expiration"`
+}
+
+// stsCredentialsResponse - represents aws sts AssumeRole response
+// https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html
+type stsCredentialsResponse struct {
+	AssumeRoleResult struct {
+		Credentials struct {
+			AccessKeyID     string    `xml:"AccessKeyId"`
+			SecretAccessKey string    `xml:"SecretAccessKey"`
+			SessionToken    string    `xml:"SessionToken"`
+			Expiration      time.Time `xml:"Expiration"`
+		}
+	}
 }
 
 var configMap = discoveryutils.NewConfigMap()
 
 func getAPIConfig(sdc *SDConfig) (*apiConfig, error) {
-	v, err := configMap.Get(sdc, func() (interface{}, error) { return newAPIConfig(sdc) })
+	v, err := configMap.Get(sdc, func() (interface{}, error) {
+		return newAPIConfig(sdc)
+	})
 	if err != nil {
 		return nil, err
 	}
@@ -40,33 +75,42 @@ func newAPIConfig(sdc *SDConfig) (*apiConfig, error) {
 		}
 		region = r
 	}
-	accessKey := sdc.AccessKey
-	if len(accessKey) == 0 {
-		accessKey = os.Getenv("AWS_ACCESS_KEY_ID")
-		if len(accessKey) == 0 {
-			return nil, fmt.Errorf("missing `access_key` in AWS_ACCESS_KEY_ID env var; probably, `access_key` must be set in `ec2_sd_config`?")
-		}
-	}
-	secretKey := sdc.SecretKey
-	if len(secretKey) == 0 {
-		secretKey = os.Getenv("AWS_SECRET_ACCESS_KEY")
-		if len(secretKey) == 0 {
-			return nil, fmt.Errorf("miising `secret_key` in AWS_SECRET_ACCESS_KEY env var; probably, `secret_key` must be set in `ec2_sd_config`?")
-		}
-	}
 	filters := getFiltersQueryString(sdc.Filters)
 	port := 80
 	if sdc.Port != nil {
 		port = *sdc.Port
 	}
-	return &apiConfig{
-		endpoint:  sdc.Endpoint,
-		region:    region,
-		accessKey: accessKey,
-		secretKey: secretKey,
-		filters:   filters,
-		port:      port,
-	}, nil
+	cfg := &apiConfig{
+		endpoint: sdc.Endpoint,
+		region:   region,
+		roleARN:  sdc.RoleARN,
+		filters:  filters,
+		port:     port,
+	}
+	// explicitly set credentials has priority over env variables
+	cfg.defaultAccessKey = os.Getenv(awsAccessKeyEnv)
+	cfg.defaultSecretKey = os.Getenv(awsSecretKeyEnv)
+	if len(sdc.AccessKey) > 0 {
+		cfg.defaultAccessKey = sdc.AccessKey
+	}
+	if len(sdc.SecretKey) > 0 {
+		cfg.defaultSecretKey = sdc.SecretKey
+	}
+
+	// fast return if credentials are set and there is no roleARN
+	if len(cfg.defaultAccessKey) > 0 && len(cfg.defaultSecretKey) > 0 && len(sdc.RoleARN) == 0 {
+		cfg.apiCredentials.Store(&apiCredentials{
+			AccessKeyID:     cfg.defaultAccessKey,
+			SecretAccessKey: cfg.defaultSecretKey,
+		})
+		return cfg, nil
+	}
+
+	if err := cfg.refreshAPICredentials(); err != nil {
+		return nil, err
+	}
+
+	return cfg, nil
 }
 
 func getFiltersQueryString(filters []Filter) string {
@@ -93,6 +137,89 @@ func getDefaultRegion() (string, error) {
 	return id.Region, nil
 }
 
+// refreshAPICredentials updates expired tokens.
+func (cfg *apiConfig) refreshAPICredentials() error {
+
+	newAPICredentials := &apiCredentials{
+		AccessKeyID:     cfg.defaultAccessKey,
+		SecretAccessKey: cfg.defaultSecretKey,
+	}
+
+	// we need instance credentials
+	// if dont have key and secret
+	if len(newAPICredentials.AccessKeyID) == 0 && len(newAPICredentials.SecretAccessKey) == 0 {
+
+		ac, err := getInstanceRoleCredentials()
+		if err != nil {
+			return err
+		}
+		newAPICredentials.Token = ac.Token
+		newAPICredentials.SecretAccessKey = ac.SecretAccessKey
+		newAPICredentials.AccessKeyID = ac.AccessKeyID
+		newAPICredentials.Expiration = ac.Expiration
+
+	}
+
+	// read credentials from sts api, if role_arn is defined
+	if cfg.roleARN != "" {
+
+		ac, err := getRoleARNCredentials(cfg.region, cfg.endpoint, cfg.roleARN, newAPICredentials)
+		if err != nil {
+			return fmt.Errorf("cannot get credentials for role_arn: %s: %w", cfg.roleARN, err)
+		}
+		if newAPICredentials.Expiration.IsZero() || ac.Expiration.Before(newAPICredentials.Expiration) {
+			newAPICredentials.Expiration = ac.Expiration
+		}
+		newAPICredentials.AccessKeyID = ac.AccessKeyID
+		newAPICredentials.SecretAccessKey = ac.SecretAccessKey
+		newAPICredentials.Token = ac.Token
+	}
+
+	if len(newAPICredentials.AccessKeyID) == 0 {
+		return fmt.Errorf("missing `access_key`, you can set it with %s env var, "+
+			"directly at `ec2_sd_config` as `access_key` or use instance iam role", awsAccessKeyEnv)
+	}
+	if len(newAPICredentials.SecretAccessKey) == 0 {
+		return fmt.Errorf("missing `secret_key`, you can set it with %s env var,"+
+			"directly at `ec2_sd_config` as `secret_key` or use instance iam role", awsSecretKeyEnv)
+	}
+
+	cfg.apiCredentials.Store(newAPICredentials)
+
+	return nil
+}
+
+// credentialsExpired - checks if tokens refresh is needed
+func (cfg *apiConfig) credentialsExpired() bool {
+	ac := cfg.credentials()
+	return !ac.Expiration.IsZero() && time.Since(ac.Expiration) > -5*time.Second
+}
+
+// credentials - returns current api credentials
+func (cfg *apiConfig) credentials() *apiCredentials {
+	return cfg.apiCredentials.Load().(*apiCredentials)
+}
+
+// getInstanceRoleCredentials makes request to local ec2 instance metadata service
+// and tries to retrieve credentials from assigned iam role.
+// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html
+func getInstanceRoleCredentials() (*apiCredentials, error) {
+	instanceRoleName, err := getMetadataByPath("meta-data/iam/security-credentials/")
+	if err != nil {
+		return nil, fmt.Errorf("cannot get instanceRoleName: %w", err)
+	}
+
+	credentials, err := getMetadataByPath("meta-data/iam/security-credentials/" + string(instanceRoleName))
+	if err != nil {
+		return nil, fmt.Errorf("cannot get instanceCredentials with role %s, error: %w", instanceRoleName, err)
+	}
+	ac := &apiCredentials{}
+	if err := json.Unmarshal(credentials, ac); err != nil {
+		return nil, fmt.Errorf("cannot parse instance metadata role response : %w", err)
+	}
+	return ac, nil
+}
+
 // IdentityDocument is identity document returned from AWS metadata server.
 //
 // See https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html
@@ -100,11 +227,10 @@ type IdentityDocument struct {
 	Region string
 }
 
+// getMetadataByPath returns instance metadata by url path
 func getMetadataByPath(apiPath string) ([]byte, error) {
 	// See https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html
 
-	client := discoveryutils.GetHTTPClient()
-
 	// Obtain session token
 	sessionTokenURL := "http://169.254.169.254/latest/api/token"
 	req, err := http.NewRequest("PUT", sessionTokenURL, nil)
@@ -112,7 +238,7 @@ func getMetadataByPath(apiPath string) ([]byte, error) {
 		return nil, fmt.Errorf("cannot create request for IMDSv2 session token at url %q: %w", sessionTokenURL, err)
 	}
 	req.Header.Set("X-aws-ec2-metadata-token-ttl-seconds", "60")
-	resp, err := client.Do(req)
+	resp, err := discoveryutils.GetHTTPClient().Do(req)
 	if err != nil {
 		return nil, fmt.Errorf("cannot obtain IMDSv2 session token from %q; probably, `region` is missing in `ec2_sd_config`; error: %w", sessionTokenURL, err)
 	}
@@ -128,26 +254,83 @@ func getMetadataByPath(apiPath string) ([]byte, error) {
 		return nil, fmt.Errorf("cannot create request to %q: %w", apiURL, err)
 	}
 	req.Header.Set("X-aws-ec2-metadata-token", string(token))
-	resp, err = client.Do(req)
+	resp, err = discoveryutils.GetHTTPClient().Do(req)
 	if err != nil {
 		return nil, fmt.Errorf("cannot obtain response for %q: %w", apiURL, err)
 	}
 	return readResponseBody(resp, apiURL)
 }
 
-func getAPIResponse(cfg *apiConfig, action, nextPageToken string) ([]byte, error) {
+// getRoleARNCredentials - retrieves credentials from aws sts with role_arn
+func getRoleARNCredentials(region, endpoint, roleARN string, credentials *apiCredentials) (*apiCredentials, error) {
+
+	data, err := getSTSAPIResponse(region, endpoint, roleARN, credentials)
+	if err != nil {
+		return nil, err
+	}
+	scr := &stsCredentialsResponse{}
+	if err := xml.Unmarshal(data, scr); err != nil {
+		return nil, fmt.Errorf("cannot parse sts api response: %w", err)
+	}
+	return &apiCredentials{
+		AccessKeyID:     scr.AssumeRoleResult.Credentials.AccessKeyID,
+		SecretAccessKey: scr.AssumeRoleResult.Credentials.SecretAccessKey,
+		Token:           scr.AssumeRoleResult.Credentials.SessionToken,
+		Expiration:      scr.AssumeRoleResult.Credentials.Expiration,
+	}, nil
+
+}
+
+// buildAPIEndpoint - creates endpoint for aws api access
+func buildAPIEndpoint(region, cfgEndpoint, service string) string {
 	// See https://docs.aws.amazon.com/AWSEC2/latest/APIReference/Query-Requests.html
-	endpoint := fmt.Sprintf("https://ec2.%s.amazonaws.com/", cfg.region)
-	if len(cfg.endpoint) > 0 {
-		endpoint = cfg.endpoint
+	apiEndpoint := fmt.Sprintf("https://%s.%s.amazonaws.com/", service, region)
+	if len(cfgEndpoint) > 0 {
+		apiEndpoint = cfgEndpoint
 		// endpoint may contain only hostname. Convert it to proper url then.
-		if !strings.Contains(endpoint, "://") {
-			endpoint = "https://" + endpoint
+		if !strings.Contains(apiEndpoint, "://") {
+			apiEndpoint = "https://" + apiEndpoint
 		}
-		if !strings.HasSuffix(endpoint, "/") {
-			endpoint += "/"
+		if !strings.HasSuffix(apiEndpoint, "/") {
+			apiEndpoint += "/"
+		}
+	}
+	return apiEndpoint
+}
+
+// getSTSAPIResponse makes request to aws sts api with role_arn
+// and returns temporary credentials with expiration time
+// https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html
+func getSTSAPIResponse(region, endpoint, roleARN string, credentials *apiCredentials) ([]byte, error) {
+	endpoint = buildAPIEndpoint(region, endpoint, "sts")
+	// See https://docs.aws.amazon.com/AWSEC2/latest/APIReference/Query-Requests.html
+	apiURL := fmt.Sprintf("%s?Action=%s", endpoint, "AssumeRole")
+	apiURL += "&Version=2011-06-15"
+	apiURL += fmt.Sprintf("&RoleArn=%s", roleARN)
+	// we have to provide unique session name for cloudtrail audit
+	apiURL += "&RoleSessionName=vmagent-ec2-discovery"
+	req, err := newSignedRequest(apiURL, "sts", region, credentials)
+	if err != nil {
+		return nil, fmt.Errorf("cannot create signed request: %w", err)
+	}
+	resp, err := discoveryutils.GetHTTPClient().Do(req)
+	if err != nil {
+		return nil, fmt.Errorf("cannot perform http request to %q: %w", apiURL, err)
+	}
+	return readResponseBody(resp, apiURL)
+
+}
+
+// getEC2APIResponse lists ec2 instances with given action
+func getEC2APIResponse(cfg *apiConfig, action, nextPageToken string) ([]byte, error) {
+	endpoint := buildAPIEndpoint(cfg.region, cfg.endpoint, "ec2")
+	// refresh api credentials if needed
+	if cfg.credentialsExpired() {
+		if err := cfg.refreshAPICredentials(); err != nil {
+			return nil, fmt.Errorf("failed to update expired aws credentials: %w", err)
 		}
 	}
+
 	apiURL := fmt.Sprintf("%s?Action=%s", endpoint, url.QueryEscape(action))
 	if len(cfg.filters) > 0 {
 		apiURL += "&" + cfg.filters
@@ -156,7 +339,7 @@ func getAPIResponse(cfg *apiConfig, action, nextPageToken string) ([]byte, error
 		apiURL += fmt.Sprintf("&NextToken=%s", url.QueryEscape(nextPageToken))
 	}
 	apiURL += "&Version=2013-10-15"
-	req, err := newSignedRequest(apiURL, "ec2", cfg.region, cfg.accessKey, cfg.secretKey)
+	req, err := newSignedRequest(apiURL, "ec2", cfg.region, cfg.credentials())
 	if err != nil {
 		return nil, fmt.Errorf("cannot create signed request: %w", err)
 	}
diff --git a/lib/promscrape/discovery/ec2/ec2.go b/lib/promscrape/discovery/ec2/ec2.go
index 86dbf0f6a..c81d790b0 100644
--- a/lib/promscrape/discovery/ec2/ec2.go
+++ b/lib/promscrape/discovery/ec2/ec2.go
@@ -12,9 +12,9 @@ type SDConfig struct {
 	Endpoint  string `yaml:"endpoint"`
 	AccessKey string `yaml:"access_key"`
 	SecretKey string `yaml:"secret_key"`
-	Profile   string `yaml:"profile"`
-	// TODO: add support for RoleARN
-	// RoleARN   string `yaml:"role_arn"`
+	// TODO add support for Profile, not working atm
+	Profile string `yaml:"profile"`
+	RoleARN string `yaml:"role_arn"`
 	// RefreshInterval time.Duration `yaml:"refresh_interval"`
 	// refresh_interval is obtained from `-promscrape.ec2SDCheckInterval` command-line option.
 	Port    *int     `yaml:"port"`
diff --git a/lib/promscrape/discovery/ec2/instance.go b/lib/promscrape/discovery/ec2/instance.go
index 4e3b62a56..7dbf6203a 100644
--- a/lib/promscrape/discovery/ec2/instance.go
+++ b/lib/promscrape/discovery/ec2/instance.go
@@ -29,7 +29,7 @@ func getReservations(cfg *apiConfig) ([]Reservation, error) {
 	var rs []Reservation
 	pageToken := ""
 	for {
-		data, err := getAPIResponse(cfg, action, pageToken)
+		data, err := getEC2APIResponse(cfg, action, pageToken)
 		if err != nil {
 			return nil, fmt.Errorf("cannot obtain instances: %w", err)
 		}
diff --git a/lib/promscrape/discovery/ec2/sign.go b/lib/promscrape/discovery/ec2/sign.go
index 30e16d0b5..2b3890125 100644
--- a/lib/promscrape/discovery/ec2/sign.go
+++ b/lib/promscrape/discovery/ec2/sign.go
@@ -16,12 +16,12 @@ import (
 // newSignedRequest signed request for apiURL according to aws signature algorithm.
 //
 // See the algorithm at https://docs.aws.amazon.com/general/latest/gr/sigv4-signed-request-examples.html
-func newSignedRequest(apiURL, service, region, accessKey, secretKey string) (*http.Request, error) {
+func newSignedRequest(apiURL, service, region string, credentials *apiCredentials) (*http.Request, error) {
 	t := time.Now().UTC()
-	return newSignedRequestWithTime(apiURL, service, region, accessKey, secretKey, t)
+	return newSignedRequestWithTime(apiURL, service, region, credentials, t)
 }
 
-func newSignedRequestWithTime(apiURL, service, region, accessKey, secretKey string, t time.Time) (*http.Request, error) {
+func newSignedRequestWithTime(apiURL, service, region string, credentials *apiCredentials, t time.Time) (*http.Request, error) {
 	uri, err := url.Parse(apiURL)
 	if err != nil {
 		return nil, fmt.Errorf("cannot parse %q: %w", apiURL, err)
@@ -57,11 +57,11 @@ func newSignedRequestWithTime(apiURL, service, region, accessKey, secretKey stri
 	stringToSign := strings.Join(tmp, "\n")
 
 	// Calculate the signature
-	signingKey := getSignatureKey(secretKey, datestamp, region, service)
+	signingKey := getSignatureKey(credentials.SecretAccessKey, datestamp, region, service)
 	signature := hmacHex(signingKey, stringToSign)
 
 	// Calculate autheader
-	authHeader := fmt.Sprintf("%s Credential=%s/%s, SignedHeaders=%s, Signature=%s", algorithm, accessKey, credentialScope, signedHeaders, signature)
+	authHeader := fmt.Sprintf("%s Credential=%s/%s, SignedHeaders=%s, Signature=%s", algorithm, credentials.AccessKeyID, credentialScope, signedHeaders, signature)
 
 	req, err := http.NewRequest("GET", apiURL, nil)
 	if err != nil {
@@ -69,6 +69,10 @@ func newSignedRequestWithTime(apiURL, service, region, accessKey, secretKey stri
 	}
 	req.Header.Set("x-amz-date", amzdate)
 	req.Header.Set("Authorization", authHeader)
+	if credentials.Token != "" {
+		req.Header.Set("X-Amz-Security-Token", credentials.Token)
+	}
+
 	return req, nil
 }
 
diff --git a/lib/storage/partition.go b/lib/storage/partition.go
index 2ec85077d..9bc347a8b 100644
--- a/lib/storage/partition.go
+++ b/lib/storage/partition.go
@@ -924,8 +924,8 @@ func (pt *partition) smallPartsMerger() {
 }
 
 const (
-	minMergeSleepTime = time.Millisecond
-	maxMergeSleepTime = time.Second
+	minMergeSleepTime = 10 * time.Millisecond
+	maxMergeSleepTime = 10 * time.Second
 )
 
 func (pt *partition) partsMerger(mergerFunc func(isFinal bool) error) error {
diff --git a/lib/storage/storage.go b/lib/storage/storage.go
index 292a32215..93504e693 100644
--- a/lib/storage/storage.go
+++ b/lib/storage/storage.go
@@ -1127,11 +1127,6 @@ func (s *Storage) add(rows []rawRow, mrs []MetricRow, precisionBits uint8) ([]ra
 			// doesn't know how to work with them.
 			continue
 		}
-		if math.IsInf(mr.Value, 0) {
-			// Skip Inf values, since they may break precision for already stored data.
-			// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/752
-			continue
-		}
 		if mr.Timestamp < minTimestamp {
 			// Skip rows with too small timestamps outside the retention.
 			if firstWarn == nil {
diff --git a/vendor/github.com/VictoriaMetrics/fasthttp/client.go b/vendor/github.com/VictoriaMetrics/fasthttp/client.go
index 3070c5744..7ff9f9cbf 100644
--- a/vendor/github.com/VictoriaMetrics/fasthttp/client.go
+++ b/vendor/github.com/VictoriaMetrics/fasthttp/client.go
@@ -885,9 +885,11 @@ func clientDoDeadline(req *Request, resp *Response, deadline time.Time, c client
 	// Make req and resp copies, since on timeout they no longer
 	// may be accessed.
 	reqCopy := AcquireRequest()
-	req.copyToSkipBody(reqCopy)
-	swapRequestBody(req, reqCopy)
+	req.CopyTo(reqCopy)
 	respCopy := AcquireResponse()
+	if resp != nil {
+		swapResponseBody(resp, respCopy)
+	}
 
 	// Note that the request continues execution on ErrTimeout until
 	// client-specific ReadTimeout exceeds. This helps limiting load
@@ -908,7 +910,6 @@ func clientDoDeadline(req *Request, resp *Response, deadline time.Time, c client
 			respCopy.copyToSkipBody(resp)
 			swapResponseBody(resp, respCopy)
 		}
-		swapRequestBody(reqCopy, req)
 		ReleaseResponse(respCopy)
 		ReleaseRequest(reqCopy)
 		errorChPool.Put(chv)
diff --git a/vendor/github.com/VictoriaMetrics/fasthttp/go.mod b/vendor/github.com/VictoriaMetrics/fasthttp/go.mod
index ecd7a311c..46d90f7aa 100644
--- a/vendor/github.com/VictoriaMetrics/fasthttp/go.mod
+++ b/vendor/github.com/VictoriaMetrics/fasthttp/go.mod
@@ -3,7 +3,7 @@ module github.com/VictoriaMetrics/fasthttp
 go 1.13
 
 require (
-	github.com/klauspost/compress v1.10.11
+	github.com/klauspost/compress v1.11.0
 	github.com/valyala/bytebufferpool v1.0.0
 	github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a
 )
diff --git a/vendor/github.com/VictoriaMetrics/fasthttp/go.sum b/vendor/github.com/VictoriaMetrics/fasthttp/go.sum
index 68536bf51..dfeca7270 100644
--- a/vendor/github.com/VictoriaMetrics/fasthttp/go.sum
+++ b/vendor/github.com/VictoriaMetrics/fasthttp/go.sum
@@ -1,5 +1,5 @@
-github.com/klauspost/compress v1.10.11 h1:K9z59aO18Aywg2b/WSgBaUX99mHy2BES18Cr5lBKZHk=
-github.com/klauspost/compress v1.10.11/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
+github.com/klauspost/compress v1.11.0 h1:wJbzvpYMVGG9iTI9VxpnNZfd4DzMPoCWze3GgSqz8yg=
+github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
 github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
 github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a h1:0R4NLDRDZX6JcmhJgXi5E4b8Wg84ihbmUKp/GvSPEzc=
diff --git a/vendor/github.com/valyala/quicktemplate/go.mod b/vendor/github.com/valyala/quicktemplate/go.mod
index ef2a6c4d6..247953354 100644
--- a/vendor/github.com/valyala/quicktemplate/go.mod
+++ b/vendor/github.com/valyala/quicktemplate/go.mod
@@ -3,7 +3,7 @@ module github.com/valyala/quicktemplate
 go 1.11
 
 require (
-	github.com/klauspost/compress v1.10.10 // indirect
+	github.com/klauspost/compress v1.11.0 // indirect
 	github.com/valyala/bytebufferpool v1.0.0
-	github.com/valyala/fasthttp v1.15.1
+	github.com/valyala/fasthttp v1.16.0
 )
diff --git a/vendor/github.com/valyala/quicktemplate/go.sum b/vendor/github.com/valyala/quicktemplate/go.sum
index 73293b1e5..c7ebac1a1 100644
--- a/vendor/github.com/valyala/quicktemplate/go.sum
+++ b/vendor/github.com/valyala/quicktemplate/go.sum
@@ -1,12 +1,12 @@
 github.com/andybalholm/brotli v1.0.0 h1:7UCwP93aiSfvWpapti8g88vVVGp2qqtGyePsSuDafo4=
 github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
 github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
-github.com/klauspost/compress v1.10.10 h1:a/y8CglcM7gLGYmlbP/stPE5sR3hbhFRUjCBfd/0B3I=
-github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
+github.com/klauspost/compress v1.11.0 h1:wJbzvpYMVGG9iTI9VxpnNZfd4DzMPoCWze3GgSqz8yg=
+github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
 github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
-github.com/valyala/fasthttp v1.15.1 h1:eRb5jzWhbCn/cGu3gNJMcOfPUfXgXCcQIOHjh9ajAS8=
-github.com/valyala/fasthttp v1.15.1/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA=
+github.com/valyala/fasthttp v1.16.0 h1:9zAqOYLl8Tuy3E5R6ckzGDJ1g8+pw15oQp2iL9Jl6gQ=
+github.com/valyala/fasthttp v1.16.0/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA=
 github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 94e2c7780..6001d65c2 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -10,7 +10,7 @@ cloud.google.com/go/internal/version
 cloud.google.com/go/storage
 # github.com/VictoriaMetrics/fastcache v1.5.7
 github.com/VictoriaMetrics/fastcache
-# github.com/VictoriaMetrics/fasthttp v1.0.5
+# github.com/VictoriaMetrics/fasthttp v1.0.7
 github.com/VictoriaMetrics/fasthttp
 github.com/VictoriaMetrics/fasthttp/fasthttputil
 github.com/VictoriaMetrics/fasthttp/stackless
@@ -109,7 +109,7 @@ github.com/valyala/fasttemplate
 github.com/valyala/gozstd
 # github.com/valyala/histogram v1.1.2
 github.com/valyala/histogram
-# github.com/valyala/quicktemplate v1.6.2
+# github.com/valyala/quicktemplate v1.6.3
 github.com/valyala/quicktemplate
 # go.opencensus.io v0.22.4
 go.opencensus.io


Test patch:
diff --git a/app/vmalert/datasource/vm_test.go b/app/vmalert/datasource/vm_test.go
index 708f76a04..d7998fb4e 100644
--- a/app/vmalert/datasource/vm_test.go
+++ b/app/vmalert/datasource/vm_test.go
@@ -4,7 +4,9 @@ import (
 	"context"
 	"net/http"
 	"net/http/httptest"
+	"strconv"
 	"testing"
+	"time"
 )
 
 var (
@@ -31,6 +33,13 @@ func TestVMSelectQuery(t *testing.T) {
 		if r.URL.Query().Get("query") != query {
 			t.Errorf("expected %s in query param, got %s", query, r.URL.Query().Get("query"))
 		}
+		timeParam := r.URL.Query().Get("time")
+		if timeParam == "" {
+			t.Errorf("expected 'time' in query param, got nil instead")
+		}
+		if _, err := strconv.ParseInt(timeParam, 10, 64); err != nil {
+			t.Errorf("failed to parse 'time' query param: %s", err)
+		}
 		switch c {
 		case 0:
 			conn, _, _ := w.(http.Hijacker).Hijack()
@@ -52,7 +61,7 @@ func TestVMSelectQuery(t *testing.T) {
 
 	srv := httptest.NewServer(mux)
 	defer srv.Close()
-	am := NewVMStorage(srv.URL, basicAuthName, basicAuthPass, srv.Client())
+	am := NewVMStorage(srv.URL, basicAuthName, basicAuthPass, time.Minute, srv.Client())
 	if _, err := am.Query(ctx, query); err == nil {
 		t.Fatalf("expected connection error got nil")
 	}
@@ -89,5 +98,4 @@ func TestVMSelectQuery(t *testing.T) {
 		m[0].Labels[0].Name != expected.Labels[0].Name {
 		t.Fatalf("unexpected metric %+v want %+v", m[0], expected)
 	}
-
 }
diff --git a/app/vmselect/graphite/graphite_test.go b/app/vmselect/graphite/graphite_test.go
index 89d9b0948..91727f1d4 100644
--- a/app/vmselect/graphite/graphite_test.go
+++ b/app/vmselect/graphite/graphite_test.go
@@ -17,13 +17,17 @@ func TestGetRegexpForQuery(t *testing.T) {
 			t.Fatalf("unexpected regexp for query=%q, delimiter=%c; got %s; want %s", query, delimiter, reStr, reExpected)
 		}
 	}
-	f("", '.', "")
-	f("foobar", '.', "foobar")
-	f("*", '.', `[^\.]*`)
-	f("*", '_', `[^_]*`)
-	f("foo.*.bar", '.', `foo\.[^\.]*\.bar`)
-	f("fo*b{ar,aaa}[a-z]xx*.d", '.', `fo[^\.]*b(?:ar|aaa)[a-z]xx[^\.]*\.d`)
-	f("fo*b{ar,aaa}[a-z]xx*_d", '_', `fo[^_]*b(?:ar|aaa)[a-z]xx[^_]*_d`)
+	f("", '.', `^(?:\.?)$`)
+	f("foobar", '.', `^(?:foobar\.?)$`)
+	f("*", '.', `^(?:[^\.]*\.?)$`)
+	f("*", '_', `^(?:[^_]*_?)$`)
+	f("foo.*.bar", '.', `^(?:foo\.[^\.]*\.bar\.?)$`)
+	f("fo*b{ar,aaa}[a-z]xx*.d", '.', `^(?:fo[^\.]*b(?:ar|aaa)[a-z]xx[^\.]*\.d\.?)$`)
+	f("fo*b{ar,aaa}[a-z]xx*_d", '_', `^(?:fo[^_]*b(?:ar|aaa)[a-z]xx[^_]*_d_?)$`)
+	f("foo.[ab]*z", '.', `^(?:foo\.[ab][^\.]*z\.?)$`)
+	f("foo_[ab]*", '_', `^(?:foo_[ab][^_]*_?)$`)
+	f("foo_[ab]_", '_', `^(?:foo_[ab]_)$`)
+	f("foo.[ab].", '.', `^(?:foo\.[ab]\.)$`)
 }
 
 func TestSortPaths(t *testing.T) {
diff --git a/lib/decimal/decimal_test.go b/lib/decimal/decimal_test.go
index 5feabc871..cc93720ed 100644
--- a/lib/decimal/decimal_test.go
+++ b/lib/decimal/decimal_test.go
@@ -69,6 +69,8 @@ func TestPositiveFloatToDecimal(t *testing.T) {
 
 	f(0.000874957, 874957, -9)
 	f(0.001130435, 1130435, -9)
+	f(vInfPos, 9223372036854775, 3)
+	f(vMax, 9223372036854775, 3)
 }
 
 func TestAppendDecimalToFloat(t *testing.T) {
@@ -87,7 +89,9 @@ func TestAppendDecimalToFloat(t *testing.T) {
 	testAppendDecimalToFloat(t, []int64{874957, 1130435}, -11, []float64{8.74957e-6, 1.130435e-5})
 	testAppendDecimalToFloat(t, []int64{874957, 1130435}, -12, []float64{8.74957e-7, 1.130435e-6})
 	testAppendDecimalToFloat(t, []int64{874957, 1130435}, -13, []float64{8.74957e-8, 1.130435e-7})
-	testAppendDecimalToFloat(t, []int64{vInfPos, vInfNeg, 1, 2}, 0, []float64{9.223372036854776e+18, -9.223372036854776e+18, 1, 2})
+	testAppendDecimalToFloat(t, []int64{vInfPos, vInfNeg, 1, 2}, 0, []float64{infPos, infNeg, 1, 2})
+	testAppendDecimalToFloat(t, []int64{vInfPos, vInfNeg, 1, 2}, 4, []float64{infPos, infNeg, 1e4, 2e4})
+	testAppendDecimalToFloat(t, []int64{vInfPos, vInfNeg, 1, 2}, -4, []float64{infPos, infNeg, 1e-4, 2e-4})
 }
 
 func testAppendDecimalToFloat(t *testing.T, va []int64, e int16, fExpected []float64) {
@@ -245,8 +249,7 @@ func TestAppendFloatToDecimal(t *testing.T) {
 	testAppendFloatToDecimal(t, []float64{}, nil, 0)
 	testAppendFloatToDecimal(t, []float64{0}, []int64{0}, 0)
 	testAppendFloatToDecimal(t, []float64{infPos, infNeg, 123}, []int64{vInfPos, vInfNeg, 123}, 0)
-	testAppendFloatToDecimal(t, []float64{infPos, infNeg, 123, 1e-4, 1e32}, []int64{92233, -92233, 0, 0, 1000000000000000000}, 14)
-	testAppendFloatToDecimal(t, []float64{float64(vInfPos), float64(vInfNeg), 123}, []int64{9223372036854775000, -9223372036854775000, 123}, 0)
+	testAppendFloatToDecimal(t, []float64{infPos, infNeg, 123, 1e-4, 1e32}, []int64{vInfPos, vInfNeg, 0, 0, 1000000000000000000}, 14)
 	testAppendFloatToDecimal(t, []float64{0, -0, 1, -1, 12345678, -123456789}, []int64{0, 0, 1, -1, 12345678, -123456789}, 0)
 
 	// upExp
@@ -327,6 +330,10 @@ func TestFloatToDecimal(t *testing.T) {
 
 	f(math.Inf(1), vInfPos, 0)
 	f(math.Inf(-1), vInfNeg, 0)
+	f(vInfPos, 9223372036854775, 3)
+	f(vInfNeg, -9223372036854775, 3)
+	f(vMax, 9223372036854775, 3)
+	f(vMin, -9223372036854775, 3)
 	f(1<<63-1, 9223372036854775, 3)
 	f(-1<<63, -9223372036854775, 3)
 
@@ -338,16 +345,15 @@ func TestFloatToDecimal(t *testing.T) {
 func TestFloatToDecimalRoundtrip(t *testing.T) {
 	f := func(f float64) {
 		t.Helper()
-
 		v, e := FromFloat(f)
 		fNew := ToFloat(v, e)
-		if !equalFloat(fNew, f) {
+		if !equalFloat(f, fNew) {
 			t.Fatalf("unexpected fNew for v=%d, e=%d; got %g; expecting %g", v, e, fNew, f)
 		}
 
 		v, e = FromFloat(-f)
 		fNew = ToFloat(v, e)
-		if !equalFloat(fNew, -f) {
+		if !equalFloat(-f, fNew) {
 			t.Fatalf("unexepcted fNew for v=%d, e=%d; got %g; expecting %g", v, e, fNew, -f)
 		}
 	}
@@ -362,7 +368,7 @@ func TestFloatToDecimalRoundtrip(t *testing.T) {
 	f(321e260)
 	f(1234567890123)
 	f(12.34567890125)
-	f(-1234567.8901256789)
+	f(1234567.8901256789)
 	f(15e18)
 	f(0.000874957)
 	f(0.001130435)
@@ -373,6 +379,8 @@ func TestFloatToDecimalRoundtrip(t *testing.T) {
 	f(float64(vInfNeg))
 	f(infPos)
 	f(infNeg)
+	f(vMax)
+	f(vMin)
 
 	for i := 0; i < 1e4; i++ {
 		v := rand.NormFloat64()
@@ -396,26 +404,12 @@ func roundFloat(f float64, exp int) float64 {
 }
 
 func equalFloat(f1, f2 float64) bool {
-	f1 = adjustInf(f1)
-	f2 = adjustInf(f2)
-	if math.IsInf(f1, 0) {
-		return math.IsInf(f1, 1) == math.IsInf(f2, 1) || math.IsInf(f1, -1) == math.IsInf(f2, -1)
+	if math.IsInf(f1, 1) {
+		return math.IsInf(f2, 1)
+	}
+	if math.IsInf(f2, -1) {
+		return math.IsInf(f2, -1)
 	}
 	eps := math.Abs(f1 - f2)
 	return eps == 0 || eps*conversionPrecision < math.Abs(f1)+math.Abs(f2)
 }
-
-func adjustInf(f float64) float64 {
-	if f == float64(vInfPos) {
-		return infPos
-	}
-	if f == float64(vInfNeg) {
-		return infNeg
-	}
-	return f
-}
-
-var (
-	infPos = math.Inf(1)
-	infNeg = math.Inf(-1)
-)
diff --git a/lib/promscrape/discovery/ec2/sign_test.go b/lib/promscrape/discovery/ec2/sign_test.go
index 6a1152012..7cea154b8 100644
--- a/lib/promscrape/discovery/ec2/sign_test.go
+++ b/lib/promscrape/discovery/ec2/sign_test.go
@@ -10,10 +10,12 @@ func TestNewSignedRequest(t *testing.T) {
 		t.Helper()
 		service := "ec2"
 		region := "us-east-1"
-		accessKey := "fake-access-key"
-		secretKey := "foobar"
+		ac := &apiCredentials{
+			AccessKeyID:     "fake-access-key",
+			SecretAccessKey: "foobar",
+		}
 		ct := time.Unix(0, 0).UTC()
-		req, err := newSignedRequestWithTime(apiURL, service, region, accessKey, secretKey, ct)
+		req, err := newSignedRequestWithTime(apiURL, service, region, ac, ct)
 		if err != nil {
 			t.Fatalf("error in newSignedRequest: %s", err)
 		}