Repository: VictoriaMetrics/VictoriaMetrics
Pull request #639 (2020-07-19 08:57:06)

app/vmagent: mention grafana dashboard in README


Patch:
diff --git a/app/vmagent/README.md b/app/vmagent/README.md
index c88b6ef15..06ec342b9 100644
--- a/app/vmagent/README.md
+++ b/app/vmagent/README.md
@@ -200,6 +200,8 @@ Read more about relabeling in the following articles:
 
 `vmagent` exports various metrics in Prometheus exposition format at `http://vmagent-host:8429/metrics` page. It is recommended setting up regular scraping of this page
 either via `vmagent` itself or via Prometheus, so the exported metrics could be analyzed later.
+Use official [Grafana dashboard](https://grafana.com/grafana/dashboards/12683) for `vmagent` state overview. If you have suggestions, improvements or found a bug - feel free to open an issue 
+on github or add review to the dashboard. 
 
 `vmagent` also exports target statuses at `http://vmagent-host:8429/targets` page in plaintext format.
 
diff --git a/app/vmagent/main.go b/app/vmagent/main.go
index 6552ca7b0..0e0a1e47f 100644
--- a/app/vmagent/main.go
+++ b/app/vmagent/main.go
@@ -136,7 +136,7 @@ func requestHandler(w http.ResponseWriter, r *http.Request) bool {
 		prometheusWriteRequests.Inc()
 		if err := promremotewrite.InsertHandler(r); err != nil {
 			prometheusWriteErrors.Inc()
-			httpserver.Errorf(w, "error in %q: %s", r.URL.Path, err)
+			httpserver.Errorf(w, r, "error in %q: %s", r.URL.Path, err)
 			return true
 		}
 		w.WriteHeader(http.StatusNoContent)
@@ -145,7 +145,7 @@ func requestHandler(w http.ResponseWriter, r *http.Request) bool {
 		vmimportRequests.Inc()
 		if err := vmimport.InsertHandler(r); err != nil {
 			vmimportErrors.Inc()
-			httpserver.Errorf(w, "error in %q: %s", r.URL.Path, err)
+			httpserver.Errorf(w, r, "error in %q: %s", r.URL.Path, err)
 			return true
 		}
 		w.WriteHeader(http.StatusNoContent)
@@ -154,7 +154,7 @@ func requestHandler(w http.ResponseWriter, r *http.Request) bool {
 		csvimportRequests.Inc()
 		if err := csvimport.InsertHandler(r); err != nil {
 			csvimportErrors.Inc()
-			httpserver.Errorf(w, "error in %q: %s", r.URL.Path, err)
+			httpserver.Errorf(w, r, "error in %q: %s", r.URL.Path, err)
 			return true
 		}
 		w.WriteHeader(http.StatusNoContent)
@@ -163,7 +163,7 @@ func requestHandler(w http.ResponseWriter, r *http.Request) bool {
 		prometheusimportRequests.Inc()
 		if err := prometheusimport.InsertHandler(r); err != nil {
 			prometheusimportErrors.Inc()
-			httpserver.Errorf(w, "error in %q: %s", r.URL.Path, err)
+			httpserver.Errorf(w, r, "error in %q: %s", r.URL.Path, err)
 			return true
 		}
 		w.WriteHeader(http.StatusNoContent)
@@ -172,7 +172,7 @@ func requestHandler(w http.ResponseWriter, r *http.Request) bool {
 		influxWriteRequests.Inc()
 		if err := influx.InsertHandlerForHTTP(r); err != nil {
 			influxWriteErrors.Inc()
-			httpserver.Errorf(w, "error in %q: %s", r.URL.Path, err)
+			httpserver.Errorf(w, r, "error in %q: %s", r.URL.Path, err)
 			return true
 		}
 		w.WriteHeader(http.StatusNoContent)
diff --git a/app/vmagent/remotewrite/relabel.go b/app/vmagent/remotewrite/relabel.go
index f1d48b3dd..7ebb4165b 100644
--- a/app/vmagent/remotewrite/relabel.go
+++ b/app/vmagent/remotewrite/relabel.go
@@ -43,6 +43,10 @@ func loadRelabelConfigs() (*relabelConfigs, error) {
 	}
 	rcs.perURL = make([][]promrelabel.ParsedRelabelConfig, len(*remoteWriteURLs))
 	for i, path := range *relabelConfigPaths {
+		if len(path) == 0 {
+			// Skip empty relabel config.
+			continue
+		}
 		prc, err := promrelabel.LoadRelabelConfigs(path)
 		if err != nil {
 			return nil, fmt.Errorf("cannot load relabel configs from -remoteWrite.urlRelabelConfig=%q: %w", path, err)
diff --git a/app/vmalert/web.go b/app/vmalert/web.go
index 07d46beba..dbd90fa9a 100644
--- a/app/vmalert/web.go
+++ b/app/vmalert/web.go
@@ -27,7 +27,6 @@ var pathList = [][]string{
 }
 
 func (rh *requestHandler) handler(w http.ResponseWriter, r *http.Request) bool {
-	resph := responseHandler{w}
 	switch r.URL.Path {
 	case "/":
 		for _, path := range pathList {
@@ -36,10 +35,22 @@ func (rh *requestHandler) handler(w http.ResponseWriter, r *http.Request) bool {
 		}
 		return true
 	case "/api/v1/groups":
-		resph.handle(rh.listGroups())
+		data, err := rh.listGroups()
+		if err != nil {
+			httpserver.Errorf(w, r, "error in %q: %s", r.URL.Path, err)
+			return true
+		}
+		w.Header().Set("Content-Type", "application/json")
+		w.Write(data)
 		return true
 	case "/api/v1/alerts":
-		resph.handle(rh.listAlerts())
+		data, err := rh.listAlerts()
+		if err != nil {
+			httpserver.Errorf(w, r, "error in %q: %s", r.URL.Path, err)
+			return true
+		}
+		w.Header().Set("Content-Type", "application/json")
+		w.Write(data)
 		return true
 	case "/-/reload":
 		logger.Infof("api config reload was called, sending sighup")
@@ -47,12 +58,18 @@ func (rh *requestHandler) handler(w http.ResponseWriter, r *http.Request) bool {
 		w.WriteHeader(http.StatusOK)
 		return true
 	default:
+		if !strings.HasSuffix(r.URL.Path, "/status") {
+			return false
+		}
 		// /api/v1/<groupName>/<alertID>/status
-		if strings.HasSuffix(r.URL.Path, "/status") {
-			resph.handle(rh.alert(r.URL.Path))
+		data, err := rh.alert(r.URL.Path)
+		if err != nil {
+			httpserver.Errorf(w, r, "error in %q: %s", r.URL.Path, err)
 			return true
 		}
-		return false
+		w.Header().Set("Content-Type", "application/json")
+		w.Write(data)
+		return true
 	}
 }
 
@@ -151,18 +168,6 @@ func (rh *requestHandler) alert(path string) ([]byte, error) {
 	return json.Marshal(resp)
 }
 
-// responseHandler wrapper on http.ResponseWriter with sugar
-type responseHandler struct{ http.ResponseWriter }
-
-func (w responseHandler) handle(b []byte, err error) {
-	if err != nil {
-		httpserver.Errorf(w, "%s", err)
-		return
-	}
-	w.Header().Set("Content-Type", "application/json")
-	w.Write(b)
-}
-
 func uint64FromPath(path string) (uint64, error) {
 	s := strings.TrimRight(path, "/")
 	return strconv.ParseUint(s, 10, 0)
diff --git a/app/vmauth/main.go b/app/vmauth/main.go
index 9b58cc5e3..e7b43ef6a 100644
--- a/app/vmauth/main.go
+++ b/app/vmauth/main.go
@@ -49,20 +49,20 @@ func main() {
 func requestHandler(w http.ResponseWriter, r *http.Request) bool {
 	username, password, ok := r.BasicAuth()
 	if !ok {
-		httpserver.Errorf(w, "Missing `Authorization: Basic *` header")
+		httpserver.Errorf(w, r, "missing `Authorization: Basic *` header")
 		return true
 	}
 	ac := authConfig.Load().(map[string]*UserInfo)
 	info := ac[username]
 	if info == nil || info.Password != password {
-		httpserver.Errorf(w, "Cannot find the provided username %q or password in config", username)
+		httpserver.Errorf(w, r, "cannot find the provided username %q or password in config", username)
 		return true
 	}
 	info.requests.Inc()
 
 	targetURL := createTargetURL(info.URLPrefix, r.URL)
 	if _, err := url.Parse(targetURL); err != nil {
-		httpserver.Errorf(w, "Invalid targetURL=%q: %s", targetURL, err)
+		httpserver.Errorf(w, r, "invalid targetURL=%q: %s", targetURL, err)
 		return true
 	}
 	r.Header.Set("vm-target-url", targetURL)
diff --git a/app/vminsert/main.go b/app/vminsert/main.go
index 61587a97e..b581596c1 100644
--- a/app/vminsert/main.go
+++ b/app/vminsert/main.go
@@ -92,7 +92,7 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
 		prometheusWriteRequests.Inc()
 		if err := promremotewrite.InsertHandler(r); err != nil {
 			prometheusWriteErrors.Inc()
-			httpserver.Errorf(w, "error in %q: %s", r.URL.Path, err)
+			httpserver.Errorf(w, r, "error in %q: %s", r.URL.Path, err)
 			return true
 		}
 		w.WriteHeader(http.StatusNoContent)
@@ -101,7 +101,7 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
 		vmimportRequests.Inc()
 		if err := vmimport.InsertHandler(r); err != nil {
 			vmimportErrors.Inc()
-			httpserver.Errorf(w, "error in %q: %s", r.URL.Path, err)
+			httpserver.Errorf(w, r, "error in %q: %s", r.URL.Path, err)
 			return true
 		}
 		w.WriteHeader(http.StatusNoContent)
@@ -110,7 +110,7 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
 		csvimportRequests.Inc()
 		if err := csvimport.InsertHandler(r); err != nil {
 			csvimportErrors.Inc()
-			httpserver.Errorf(w, "error in %q: %s", r.URL.Path, err)
+			httpserver.Errorf(w, r, "error in %q: %s", r.URL.Path, err)
 			return true
 		}
 		w.WriteHeader(http.StatusNoContent)
@@ -119,7 +119,7 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
 		prometheusimportRequests.Inc()
 		if err := prometheusimport.InsertHandler(r); err != nil {
 			prometheusimportErrors.Inc()
-			httpserver.Errorf(w, "error in %q: %s", r.URL.Path, err)
+			httpserver.Errorf(w, r, "error in %q: %s", r.URL.Path, err)
 			return true
 		}
 		w.WriteHeader(http.StatusNoContent)
@@ -128,7 +128,7 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
 		influxWriteRequests.Inc()
 		if err := influx.InsertHandlerForHTTP(r); err != nil {
 			influxWriteErrors.Inc()
-			httpserver.Errorf(w, "error in %q: %s", r.URL.Path, err)
+			httpserver.Errorf(w, r, "error in %q: %s", r.URL.Path, err)
 			return true
 		}
 		w.WriteHeader(http.StatusNoContent)
diff --git a/app/vmselect/main.go b/app/vmselect/main.go
index 615241bf0..baca764e2 100644
--- a/app/vmselect/main.go
+++ b/app/vmselect/main.go
@@ -90,7 +90,7 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
 					*maxConcurrentRequests, *maxQueueDuration),
 				StatusCode: http.StatusServiceUnavailable,
 			}
-			httpserver.Errorf(w, "%s", err)
+			httpserver.Errorf(w, r, "%s", err)
 			return true
 		}
 	}
@@ -191,7 +191,7 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
 		exportRequests.Inc()
 		if err := prometheus.ExportHandler(startTime, w, r); err != nil {
 			exportErrors.Inc()
-			httpserver.Errorf(w, "error in %q: %s", r.URL.Path, err)
+			httpserver.Errorf(w, r, "error in %q: %s", r.URL.Path, err)
 			return true
 		}
 		return true
@@ -199,7 +199,7 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
 		federateRequests.Inc()
 		if err := prometheus.FederateHandler(startTime, w, r); err != nil {
 			federateErrors.Inc()
-			httpserver.Errorf(w, "error in %q: %s", r.URL.Path, err)
+			httpserver.Errorf(w, r, "error in %q: %s", r.URL.Path, err)
 			return true
 		}
 		return true
@@ -225,12 +225,12 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
 		deleteRequests.Inc()
 		authKey := r.FormValue("authKey")
 		if authKey != *deleteAuthKey {
-			httpserver.Errorf(w, "invalid authKey %q. It must match the value from -deleteAuthKey command line flag", authKey)
+			httpserver.Errorf(w, r, "invalid authKey %q. It must match the value from -deleteAuthKey command line flag", authKey)
 			return true
 		}
 		if err := prometheus.DeleteHandler(startTime, r); err != nil {
 			deleteErrors.Inc()
-			httpserver.Errorf(w, "error in %q: %s", r.URL.Path, err)
+			httpserver.Errorf(w, r, "error in %q: %s", r.URL.Path, err)
 			return true
 		}
 		w.WriteHeader(http.StatusNoContent)
diff --git a/app/vmselect/prometheus/prometheus.go b/app/vmselect/prometheus/prometheus.go
index fecb30b62..04de32a64 100644
--- a/app/vmselect/prometheus/prometheus.go
+++ b/app/vmselect/prometheus/prometheus.go
@@ -797,14 +797,15 @@ func queryRangeHandler(w http.ResponseWriter, query string, start, end, step int
 
 	// Remove NaN values as Prometheus does.
 	// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/153
-	removeNaNValuesInplace(result)
+	result = removeEmptyValuesAndTimeseries(result)
 
 	w.Header().Set("Content-Type", "application/json")
 	WriteQueryRangeResponse(w, result)
 	return nil
 }
 
-func removeNaNValuesInplace(tss []netstorage.Result) {
+func removeEmptyValuesAndTimeseries(tss []netstorage.Result) []netstorage.Result {
+	dst := tss[:0]
 	for i := range tss {
 		ts := &tss[i]
 		hasNaNs := false
@@ -816,6 +817,9 @@ func removeNaNValuesInplace(tss []netstorage.Result) {
 		}
 		if !hasNaNs {
 			// Fast path: nothing to remove.
+			if len(ts.Values) > 0 {
+				dst = append(dst, *ts)
+			}
 			continue
 		}
 
@@ -832,7 +836,11 @@ func removeNaNValuesInplace(tss []netstorage.Result) {
 		}
 		ts.Values = dstValues
 		ts.Timestamps = dstTimestamps
+		if len(ts.Values) > 0 {
+			dst = append(dst, *ts)
+		}
 	}
+	return dst
 }
 
 var queryRangeDuration = metrics.NewSummary(`vm_request_duration_seconds{path="/api/v1/query_range"}`)
diff --git a/app/vmselect/promql/aggr.go b/app/vmselect/promql/aggr.go
index e6caeec06..5a648d9d7 100644
--- a/app/vmselect/promql/aggr.go
+++ b/app/vmselect/promql/aggr.go
@@ -46,6 +46,7 @@ var aggrFuncs = map[string]aggrFunc{
 	"bottomk_median": newAggrFuncRangeTopK(medianValue, true),
 	"any":            aggrFuncAny,
 	"outliersk":      aggrFuncOutliersK,
+	"mode":           newAggrFunc(aggrFuncMode),
 }
 
 type aggrFunc func(afa *aggrFuncArg) ([]*timeseries, error)
@@ -422,6 +423,52 @@ func aggrFuncDistinct(tss []*timeseries) []*timeseries {
 	return tss[:1]
 }
 
+func aggrFuncMode(tss []*timeseries) []*timeseries {
+	dst := tss[0]
+	a := make([]float64, 0, len(tss))
+	for i := range dst.Values {
+		a := a[:0]
+		for _, ts := range tss {
+			v := ts.Values[i]
+			if !math.IsNaN(v) {
+				a = append(a, v)
+			}
+		}
+		dst.Values[i] = modeNoNaNs(nan, a)
+	}
+	return tss[:1]
+}
+
+// modeNoNaNs returns mode for a.
+//
+// It is expected that a doesn't contain NaNs.
+//
+// See https://en.wikipedia.org/wiki/Mode_(statistics)
+func modeNoNaNs(prevValue float64, a []float64) float64 {
+	if len(a) == 0 {
+		return prevValue
+	}
+	sort.Float64s(a)
+	j := -1
+	dMax := 0
+	mode := prevValue
+	for i, v := range a {
+		if prevValue == v {
+			continue
+		}
+		if d := i - j; d > dMax || math.IsNaN(mode) {
+			dMax = d
+			mode = prevValue
+		}
+		j = i
+		prevValue = v
+	}
+	if d := len(a) - j; d > dMax || math.IsNaN(mode) {
+		mode = prevValue
+	}
+	return mode
+}
+
 func aggrFuncCountValues(afa *aggrFuncArg) ([]*timeseries, error) {
 	args := afa.args
 	if err := expectTransformArgsNum(args, 2); err != nil {
diff --git a/app/vmselect/promql/rollup.go b/app/vmselect/promql/rollup.go
index 16b5f2604..dda5d02b8 100644
--- a/app/vmselect/promql/rollup.go
+++ b/app/vmselect/promql/rollup.go
@@ -4,7 +4,6 @@ import (
 	"flag"
 	"fmt"
 	"math"
-	"sort"
 	"strings"
 	"sync"
 
@@ -1543,27 +1542,7 @@ func rollupTimestamp(rfa *rollupFuncArg) float64 {
 func rollupModeOverTime(rfa *rollupFuncArg) float64 {
 	// There is no need in handling NaNs here, since they must be cleaned up
 	// before calling rollup funcs.
-	values := rfa.values
-	prevValue := rfa.prevValue
-	if len(values) == 0 {
-		return prevValue
-	}
-	sort.Float64s(values)
-	j := -1
-	dMax := 0
-	mode := prevValue
-	for i, v := range values {
-		if prevValue == v {
-			continue
-		}
-		if d := i - j; d > dMax {
-			dMax = d
-			mode = prevValue
-		}
-		j = i
-		prevValue = v
-	}
-	return mode
+	return modeNoNaNs(rfa.prevValue, rfa.values)
 }
 
 func rollupAscentOverTime(rfa *rollupFuncArg) float64 {
diff --git a/app/vmstorage/main.go b/app/vmstorage/main.go
index c400257c1..0400f1a58 100644
--- a/app/vmstorage/main.go
+++ b/app/vmstorage/main.go
@@ -181,7 +181,7 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
 	}
 	authKey := r.FormValue("authKey")
 	if authKey != *snapshotAuthKey {
-		httpserver.Errorf(w, "invalid authKey %q. It must match the value from -snapshotAuthKey command line flag", authKey)
+		httpserver.Errorf(w, r, "invalid authKey %q. It must match the value from -snapshotAuthKey command line flag", authKey)
 		return true
 	}
 	path = path[len("/snapshot"):]
diff --git a/docs/MetricsQL.md b/docs/MetricsQL.md
index 381f654c5..62080faf2 100644
--- a/docs/MetricsQL.md
+++ b/docs/MetricsQL.md
@@ -124,3 +124,4 @@ This functionality can be tried at [an editable Grafana dashboard](http://play-g
 - `ascent_over_time(m[d])` - returns the sum of positive deltas between adjancent data points in `m` over `d`. Useful for tracking height gains in GPS track.
 - `descent_over_time(m[d])` - returns the absolute sum of negative deltas between adjancent data points in `m` over `d`. Useful for tracking height loss in GPS track.
 - `mode_over_time(m[d])` - returns [mode](https://en.wikipedia.org/wiki/Mode_(statistics)) for `m` values over `d`. It is expected that `m` values are discrete.
+- `mode(q) by (x)` - returns [mode](https://en.wikipedia.org/wiki/Mode_(statistics)) for each point in `q` grouped by `x`.
diff --git a/go.mod b/go.mod
index d40b1f58e..629910196 100644
--- a/go.mod
+++ b/go.mod
@@ -8,8 +8,8 @@ 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.1
-	github.com/VictoriaMetrics/metrics v1.11.3
-	github.com/VictoriaMetrics/metricsql v0.2.6
+	github.com/VictoriaMetrics/metrics v1.12.0
+	github.com/VictoriaMetrics/metricsql v0.2.7
 	github.com/aws/aws-sdk-go v1.33.5
 	github.com/cespare/xxhash/v2 v2.1.1
 	github.com/golang/snappy v0.0.1
@@ -17,7 +17,7 @@ require (
 	github.com/valyala/fastjson v1.5.3
 	github.com/valyala/fastrand v1.0.0
 	github.com/valyala/gozstd v1.7.0
-	github.com/valyala/histogram v1.0.1
+	github.com/valyala/histogram v1.1.2
 	github.com/valyala/quicktemplate v1.5.1
 	golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
 	golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae
diff --git a/go.sum b/go.sum
index 6b953d678..d1a641c7e 100644
--- a/go.sum
+++ b/go.sum
@@ -51,10 +51,10 @@ github.com/VictoriaMetrics/fasthttp v1.0.1 h1:I7YdbswTIW63WxoFoUOSNxeOEGB46rdKUL
 github.com/VictoriaMetrics/fasthttp v1.0.1/go.mod h1:BqgsieH90PR7x97c89j+eqZDloKkDhAEQTwhLw6jw/4=
 github.com/VictoriaMetrics/metrics v1.11.2 h1:t/ceLP6SvagUqypCKU7cI7+tQn54+TIV/tGoxihHvx8=
 github.com/VictoriaMetrics/metrics v1.11.2/go.mod h1:LU2j9qq7xqZYXz8tF3/RQnB2z2MbZms5TDiIg9/NHiQ=
-github.com/VictoriaMetrics/metrics v1.11.3 h1:eSfXc0CrquKa1VTNUvhP+dhNjLUZHQGTFfp19mYCQWE=
-github.com/VictoriaMetrics/metrics v1.11.3/go.mod h1:LU2j9qq7xqZYXz8tF3/RQnB2z2MbZms5TDiIg9/NHiQ=
-github.com/VictoriaMetrics/metricsql v0.2.6 h1:hJxeGeyP++fWuW41URWcl2PTNTMTTeqm7UcT1BEZOOg=
-github.com/VictoriaMetrics/metricsql v0.2.6/go.mod h1:UIjd9S0W1UnTWlJdM0wLS+2pfuPqjwqKoK8yTos+WyE=
+github.com/VictoriaMetrics/metrics v1.12.0 h1:BudxtRYSA6j8H9mzjhXNEIsCPIEUPCb76QwFEptQzvQ=
+github.com/VictoriaMetrics/metrics v1.12.0/go.mod h1:Z1tSfPfngDn12bTfZSCqArT3OPY3u88J12hSoOhuiRE=
+github.com/VictoriaMetrics/metricsql v0.2.7 h1:4FXyJJjXTbAPVAakEEwaFSD0YOEPKEjdZKNQrWN76Ts=
+github.com/VictoriaMetrics/metricsql v0.2.7/go.mod h1:UIjd9S0W1UnTWlJdM0wLS+2pfuPqjwqKoK8yTos+WyE=
 github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8=
 github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
 github.com/aws/aws-sdk-go v1.33.5 h1:p2fr1ryvNTU6avUWLI+/H7FGv0TBIjzVM5WDgXBBv4U=
@@ -173,6 +173,8 @@ github.com/valyala/gozstd v1.7.0 h1:Ljh5c9zboqLhwTI33al32R72iCZfn0mCbVGcFWbGwRQ=
 github.com/valyala/gozstd v1.7.0/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ=
 github.com/valyala/histogram v1.0.1 h1:FzA7n2Tz/wKRMejgu3PV1vw3htAklTjjuoI6z3d4KDg=
 github.com/valyala/histogram v1.0.1/go.mod h1:lQy0xA4wUz2+IUnf97SivorsJIp8FxsnRd6x25q7Mto=
+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.5.1 h1:JtrOtlRMP+pySF9dkPBfVfhZ3YHTitAJpMLNqT9ZjFk=
 github.com/valyala/quicktemplate v1.5.1/go.mod h1:v7yYWpBEiutDyNfVaph6oC/yKwejzVyTX/2cwwHxyok=
 github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
diff --git a/lib/httpserver/httpserver.go b/lib/httpserver/httpserver.go
index e52ea7f2e..c4fdd37f1 100644
--- a/lib/httpserver/httpserver.go
+++ b/lib/httpserver/httpserver.go
@@ -172,7 +172,7 @@ func handlerWrapper(s *server, w http.ResponseWriter, r *http.Request, rh Reques
 	requestsTotal.Inc()
 	path, err := getCanonicalPath(r.URL.Path)
 	if err != nil {
-		Errorf(w, "cannot get canonical path: %s", err)
+		Errorf(w, r, "cannot get canonical path: %s", err)
 		unsupportedRequestErrors.Inc()
 		return
 	}
@@ -238,7 +238,7 @@ func handlerWrapper(s *server, w http.ResponseWriter, r *http.Request, rh Reques
 			return
 		}
 
-		Errorf(w, "unsupported path requested: %q", r.URL.Path)
+		Errorf(w, r, "unsupported path requested: %q", r.URL.Path)
 		unsupportedRequestErrors.Inc()
 		return
 	}
@@ -482,8 +482,9 @@ var (
 )
 
 // Errorf writes formatted error message to w and to logger.
-func Errorf(w http.ResponseWriter, format string, args ...interface{}) {
+func Errorf(w http.ResponseWriter, r *http.Request, format string, args ...interface{}) {
 	errStr := fmt.Sprintf(format, args...)
+	errStr = fmt.Sprintf("remoteAddr: %s; %s", r.RemoteAddr, errStr)
 	logger.WarnfSkipframes(1, "%s", errStr)
 
 	// Extract statusCode from args
diff --git a/lib/ingestserver/opentsdbhttp/server.go b/lib/ingestserver/opentsdbhttp/server.go
index d3d9384db..591ee4232 100644
--- a/lib/ingestserver/opentsdbhttp/server.go
+++ b/lib/ingestserver/opentsdbhttp/server.go
@@ -28,7 +28,7 @@ type Server struct {
 // MustStart starts HTTP OpenTSDB server on the given addr.
 //
 // MustStop must be called on the returned server when it is no longer needed.
-func MustStart(addr string, insertHandler func(req *http.Request) error) *Server {
+func MustStart(addr string, insertHandler func(r *http.Request) error) *Server {
 	logger.Infof("starting HTTP OpenTSDB server at %q", addr)
 	lnTCP, err := netutil.NewTCPListener("opentsdbhttp", addr)
 	if err != nil {
@@ -40,7 +40,7 @@ func MustStart(addr string, insertHandler func(req *http.Request) error) *Server
 // MustServe serves OpenTSDB HTTP put requests from ln.
 //
 // MustStop must be called on the returned server when it is no longer needed.
-func MustServe(ln net.Listener, insertHandler func(req *http.Request) error) *Server {
+func MustServe(ln net.Listener, insertHandler func(r *http.Request) error) *Server {
 	h := newRequestHandler(insertHandler)
 	hs := &http.Server{
 		Handler:           h,
@@ -84,12 +84,12 @@ func (s *Server) MustStop() {
 	logger.Infof("OpenTSDB HTTP server at %q has been stopped", s.ln.Addr())
 }
 
-func newRequestHandler(insertHandler func(req *http.Request) error) http.Handler {
-	rh := func(w http.ResponseWriter, req *http.Request) {
+func newRequestHandler(insertHandler func(r *http.Request) error) http.Handler {
+	rh := func(w http.ResponseWriter, r *http.Request) {
 		writeRequests.Inc()
-		if err := insertHandler(req); err != nil {
+		if err := insertHandler(r); err != nil {
 			writeErrors.Inc()
-			httpserver.Errorf(w, "error in %q: %s", req.URL.Path, err)
+			httpserver.Errorf(w, r, "error in %q: %s", r.URL.Path, err)
 			return
 		}
 		w.WriteHeader(http.StatusNoContent)
diff --git a/vendor/github.com/VictoriaMetrics/metrics/gauge.go b/vendor/github.com/VictoriaMetrics/metrics/gauge.go
index adc1f21b6..05bf1473f 100644
--- a/vendor/github.com/VictoriaMetrics/metrics/gauge.go
+++ b/vendor/github.com/VictoriaMetrics/metrics/gauge.go
@@ -18,6 +18,8 @@ import (
 // f must be safe for concurrent calls.
 //
 // The returned gauge is safe to use from concurrent goroutines.
+//
+// See also FloatCounter for working with floating-point values.
 func NewGauge(name string, f func() float64) *Gauge {
 	return defaultSet.NewGauge(name, f)
 }
@@ -58,6 +60,8 @@ func (g *Gauge) marshalTo(prefix string, w io.Writer) {
 // The returned gauge is safe to use from concurrent goroutines.
 //
 // Performance tip: prefer NewGauge instead of GetOrCreateGauge.
+//
+// See also FloatCounter for working with floating-point values.
 func GetOrCreateGauge(name string, f func() float64) *Gauge {
 	return defaultSet.GetOrCreateGauge(name, f)
 }
diff --git a/vendor/github.com/VictoriaMetrics/metrics/go.mod b/vendor/github.com/VictoriaMetrics/metrics/go.mod
index 15e3dccdd..a66c19bd3 100644
--- a/vendor/github.com/VictoriaMetrics/metrics/go.mod
+++ b/vendor/github.com/VictoriaMetrics/metrics/go.mod
@@ -1,5 +1,5 @@
 module github.com/VictoriaMetrics/metrics
 
-require github.com/valyala/histogram v1.0.1
+require github.com/valyala/histogram v1.1.2
 
 go 1.12
diff --git a/vendor/github.com/VictoriaMetrics/metrics/go.sum b/vendor/github.com/VictoriaMetrics/metrics/go.sum
index 9dd64f0a0..b1219448d 100644
--- a/vendor/github.com/VictoriaMetrics/metrics/go.sum
+++ b/vendor/github.com/VictoriaMetrics/metrics/go.sum
@@ -1,4 +1,4 @@
 github.com/valyala/fastrand v1.0.0 h1:LUKT9aKer2dVQNUi3waewTbKV+7H17kvWFNKs2ObdkI=
 github.com/valyala/fastrand v1.0.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
-github.com/valyala/histogram v1.0.1 h1:FzA7n2Tz/wKRMejgu3PV1vw3htAklTjjuoI6z3d4KDg=
-github.com/valyala/histogram v1.0.1/go.mod h1:lQy0xA4wUz2+IUnf97SivorsJIp8FxsnRd6x25q7Mto=
+github.com/valyala/histogram v1.1.2 h1:vOk5VrGjMBIoPR5k6wA8vBaC8toeJ8XO0yfRjFEc1h8=
+github.com/valyala/histogram v1.1.2/go.mod h1:CZAr6gK9dbD7hYx2s8WSPh0p5x5wETjC+2b3PJVtEdg=
diff --git a/vendor/github.com/VictoriaMetrics/metricsql/aggr.go b/vendor/github.com/VictoriaMetrics/metricsql/aggr.go
index 8d8e7a6d8..80ae9f8f7 100644
--- a/vendor/github.com/VictoriaMetrics/metricsql/aggr.go
+++ b/vendor/github.com/VictoriaMetrics/metricsql/aggr.go
@@ -36,6 +36,7 @@ var aggrFuncs = map[string]bool{
 	"bottomk_median": true,
 	"any":            true,
 	"outliersk":      true,
+	"mode":           true,
 }
 
 func isAggrFunc(s string) bool {
diff --git a/vendor/github.com/valyala/histogram/go.mod b/vendor/github.com/valyala/histogram/go.mod
index 9710bc72b..984efbe63 100644
--- a/vendor/github.com/valyala/histogram/go.mod
+++ b/vendor/github.com/valyala/histogram/go.mod
@@ -1,3 +1,5 @@
 module github.com/valyala/histogram
 
+go 1.12
+
 require github.com/valyala/fastrand v1.0.0
diff --git a/vendor/github.com/valyala/histogram/histogram.go b/vendor/github.com/valyala/histogram/histogram.go
index 3dce6404f..e90dd7033 100644
--- a/vendor/github.com/valyala/histogram/histogram.go
+++ b/vendor/github.com/valyala/histogram/histogram.go
@@ -41,8 +41,14 @@ func (f *Fast) Reset() {
 	f.max = infNeg
 	f.min = infPos
 	f.count = 0
-	f.a = f.a[:0]
-	f.tmp = f.tmp[:0]
+	if len(f.a) > 0 {
+		f.a = f.a[:0]
+		f.tmp = f.tmp[:0]
+	} else {
+		// Free up memory occupied by unused histogram.
+		f.a = nil
+		f.tmp = nil
+	}
 }
 
 // Update updates the f with v.
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 3ea861ae9..f2b3fa7b1 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -14,9 +14,9 @@ github.com/VictoriaMetrics/fastcache
 github.com/VictoriaMetrics/fasthttp
 github.com/VictoriaMetrics/fasthttp/fasthttputil
 github.com/VictoriaMetrics/fasthttp/stackless
-# github.com/VictoriaMetrics/metrics v1.11.3
+# github.com/VictoriaMetrics/metrics v1.12.0
 github.com/VictoriaMetrics/metrics
-# github.com/VictoriaMetrics/metricsql v0.2.6
+# github.com/VictoriaMetrics/metricsql v0.2.7
 github.com/VictoriaMetrics/metricsql
 github.com/VictoriaMetrics/metricsql/binaryop
 # github.com/aws/aws-sdk-go v1.33.5
@@ -106,7 +106,7 @@ github.com/valyala/fastjson/fastfloat
 github.com/valyala/fastrand
 # github.com/valyala/gozstd v1.7.0
 github.com/valyala/gozstd
-# github.com/valyala/histogram v1.0.1
+# github.com/valyala/histogram v1.1.2
 github.com/valyala/histogram
 # github.com/valyala/quicktemplate v1.5.1
 github.com/valyala/quicktemplate


Test patch:
diff --git a/app/vmselect/prometheus/prometheus_test.go b/app/vmselect/prometheus/prometheus_test.go
index a84028f92..c86734fb0 100644
--- a/app/vmselect/prometheus/prometheus_test.go
+++ b/app/vmselect/prometheus/prometheus_test.go
@@ -11,10 +11,10 @@ import (
 	"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/netstorage"
 )
 
-func TestRemoveNaNValuesInplace(t *testing.T) {
+func TestRemoveEmptyValuesAndTimeseries(t *testing.T) {
 	f := func(tss []netstorage.Result, tssExpected []netstorage.Result) {
 		t.Helper()
-		removeNaNValuesInplace(tss)
+		tss = removeEmptyValuesAndTimeseries(tss)
 		if !reflect.DeepEqual(tss, tssExpected) {
 			t.Fatalf("unexpected result; got %v; want %v", tss, tssExpected)
 		}
@@ -30,6 +30,14 @@ func TestRemoveNaNValuesInplace(t *testing.T) {
 			Timestamps: []int64{100, 200, 300, 400},
 			Values:     []float64{nan, nan, 3, nan},
 		},
+		{
+			Timestamps: []int64{1, 2},
+			Values:     []float64{nan, nan},
+		},
+		{
+			Timestamps: nil,
+			Values:     nil,
+		},
 	}, []netstorage.Result{
 		{
 			Timestamps: []int64{100, 200, 300},
diff --git a/app/vmselect/promql/aggr_test.go b/app/vmselect/promql/aggr_test.go
new file mode 100644
index 000000000..a8c58883f
--- /dev/null
+++ b/app/vmselect/promql/aggr_test.go
@@ -0,0 +1,36 @@
+package promql
+
+import (
+	"math"
+	"testing"
+)
+
+func TestModeNoNaNs(t *testing.T) {
+	f := func(prevValue float64, a []float64, expectedResult float64) {
+		t.Helper()
+		result := modeNoNaNs(prevValue, a)
+		if math.IsNaN(result) {
+			if !math.IsNaN(expectedResult) {
+				t.Fatalf("unexpected result; got %v; want %v", result, expectedResult)
+			}
+			return
+		}
+		if result != expectedResult {
+			t.Fatalf("unexpected result; got %v; want %v", result, expectedResult)
+		}
+	}
+	f(nan, nil, nan)
+	f(nan, []float64{123}, 123)
+	f(nan, []float64{1, 2, 3}, 1)
+	f(nan, []float64{1, 2, 2}, 2)
+	f(nan, []float64{1, 1, 2}, 1)
+	f(nan, []float64{1, 1, 1}, 1)
+	f(nan, []float64{1, 2, 2, 3}, 2)
+	f(nan, []float64{1, 1, 2, 2, 3, 3, 3}, 3)
+	f(1, []float64{2, 3, 4, 5}, 1)
+	f(1, []float64{2, 2}, 2)
+	f(1, []float64{2, 3, 3}, 3)
+	f(1, []float64{2, 4, 3, 4, 3, 4}, 4)
+	f(1, []float64{2, 3, 3, 4, 4}, 3)
+	f(1, []float64{4, 3, 2, 3, 4}, 3)
+}
diff --git a/app/vmselect/promql/exec_test.go b/app/vmselect/promql/exec_test.go
index 5db083a47..217b35605 100644
--- a/app/vmselect/promql/exec_test.go
+++ b/app/vmselect/promql/exec_test.go
@@ -3242,6 +3242,24 @@ func TestExecSuccess(t *testing.T) {
 		resultExpected := []netstorage.Result{r}
 		f(q, resultExpected)
 	})
+	t.Run(`mode()`, func(t *testing.T) {
+		t.Parallel()
+		q := `mode((
+			alias(3, "m1"),
+			alias(2, "m2"),
+			alias(3, "m3"),
+			alias(4, "m4"),
+			alias(3, "m5"),
+			alias(2, "m6"),
+		))`
+		r := netstorage.Result{
+			MetricName: metricNameExpected,
+			Values:     []float64{3, 3, 3, 3, 3, 3},
+			Timestamps: timestampsExpected,
+		}
+		resultExpected := []netstorage.Result{r}
+		f(q, resultExpected)
+	})
 	t.Run(`avg(scalar) without (xx, yy)`, func(t *testing.T) {
 		t.Parallel()
 		q := `avg without (xx, yy) (123)`
@@ -4513,7 +4531,7 @@ func TestExecSuccess(t *testing.T) {
 		q := `mode_over_time(round(time()/500)[100s:1s])`
 		r := netstorage.Result{
 			MetricName: metricNameExpected,
-			Values:     []float64{2, 2, 3, 3, 3, 4},
+			Values:     []float64{2, 2, 3, 3, 4, 4},
 			Timestamps: timestampsExpected,
 		}
 		resultExpected := []netstorage.Result{r}
@@ -5731,6 +5749,7 @@ func TestExecError(t *testing.T) {
 	f(`outliersk()`)
 	f(`outliersk(1)`)
 	f(`mode_over_time()`)
+	f(`mode()`)
 
 	// Invalid argument type
 	f(`median_over_time({}, 2)`)