mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-05-17 16:59:40 +03:00
Compare commits
141 Commits
query-debu
...
v1.20.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
844ce4731e | ||
|
|
683bf2a11f | ||
|
|
eb2283a029 | ||
|
|
e8377011ab | ||
|
|
33ea2120c3 | ||
|
|
cf63669303 | ||
|
|
feacfffe89 | ||
|
|
4bb738ddd9 | ||
|
|
90e72c2a42 | ||
|
|
ccd8b7a003 | ||
|
|
d32845781e | ||
|
|
af2ceaaa0b | ||
|
|
61926bae01 | ||
|
|
ee13256f74 | ||
|
|
3b3b2f1e6e | ||
|
|
c9cbf5351c | ||
|
|
146c6e1f72 | ||
|
|
d261fa2885 | ||
|
|
5b47c00910 | ||
|
|
9e1119dab8 | ||
|
|
47a3228108 | ||
|
|
e88a03323a | ||
|
|
b75630fcf4 | ||
|
|
80db24386e | ||
|
|
296c14317f | ||
|
|
973e4b5b76 | ||
|
|
7aadec8e3c | ||
|
|
45fc8cb72f | ||
|
|
4b2523fb40 | ||
|
|
70ba36fa37 | ||
|
|
a78b3dba7f | ||
|
|
a9cfca6a72 | ||
|
|
710d6c33ea | ||
|
|
a8d4224828 | ||
|
|
341bed4822 | ||
|
|
5982e94c94 | ||
|
|
6d6c9eb1f8 | ||
|
|
86d3d907a5 | ||
|
|
269285848f | ||
|
|
47e1e5eb4b | ||
|
|
d2c801029b | ||
|
|
beb479b8f1 | ||
|
|
611c4401f8 | ||
|
|
a8db528930 | ||
|
|
15613e5338 | ||
|
|
3237d0309c | ||
|
|
26f8d7ea1b | ||
|
|
419197ba08 | ||
|
|
a4b4db9bf6 | ||
|
|
c1276edab5 | ||
|
|
2322c9a45a | ||
|
|
89b928ff24 | ||
|
|
935bfd7a18 | ||
|
|
3dd36b8088 | ||
|
|
afb964670a | ||
|
|
20fc0e0e54 | ||
|
|
4d9f088526 | ||
|
|
82d1707861 | ||
|
|
70d20ce8de | ||
|
|
723bf1af7f | ||
|
|
ac7b186f13 | ||
|
|
cd1bc32158 | ||
|
|
1c33b5937e | ||
|
|
8bb6bc986d | ||
|
|
d2be567482 | ||
|
|
7e7d4d5275 | ||
|
|
bf9782eaf6 | ||
|
|
cbe692f0e2 | ||
|
|
7b6623558f | ||
|
|
a1351bbaee | ||
|
|
b4d707d9bb | ||
|
|
bee7298f81 | ||
|
|
dbd217b8f0 | ||
|
|
4d936b1524 | ||
|
|
7354090aad | ||
|
|
d37924900b | ||
|
|
c0baa977cf | ||
|
|
f4252f87e6 | ||
|
|
0b78d228d2 | ||
|
|
0371c216a7 | ||
|
|
c1f18ee48d | ||
|
|
fbd7044b2b | ||
|
|
2afe511d80 | ||
|
|
f4e63cd070 | ||
|
|
667115a5c7 | ||
|
|
1458450dba | ||
|
|
5a5ba749f2 | ||
|
|
a3e26de45e | ||
|
|
53ea90865d | ||
|
|
17f0a53068 | ||
|
|
b03bdb32ff | ||
|
|
15f59c6df9 | ||
|
|
da45a20491 | ||
|
|
5859bb9556 | ||
|
|
28f6c36ab4 | ||
|
|
4794f894a4 | ||
|
|
c7280ba61a | ||
|
|
fbd8b03f15 | ||
|
|
d17a47e3e0 | ||
|
|
d6862a2d97 | ||
|
|
f2cf5d8e36 | ||
|
|
27f0d098bd | ||
|
|
a51ff2c6cb | ||
|
|
56b952c456 | ||
|
|
61bad1e07e | ||
|
|
be97f764f5 | ||
|
|
a576d1f5d3 | ||
|
|
968d094524 | ||
|
|
e307a4d92c | ||
|
|
0eae39daa7 | ||
|
|
437e0b2300 | ||
|
|
4b3af728ea | ||
|
|
4a12c4c982 | ||
|
|
2e75efb64e | ||
|
|
25900162f6 | ||
|
|
16afcd6aff | ||
|
|
c2a5eef5e3 | ||
|
|
4859ca0cda | ||
|
|
feb6b203a4 | ||
|
|
51ee990902 | ||
|
|
5262aae5da | ||
|
|
54fb8b21f9 | ||
|
|
d6523ffe90 | ||
|
|
024560b161 | ||
|
|
96ac664b27 | ||
|
|
2ffcf7a4a5 | ||
|
|
5cbd4cfca9 | ||
|
|
718ce33714 | ||
|
|
f332c0d54e | ||
|
|
eca566ed22 | ||
|
|
5bbfdff9fe | ||
|
|
6b0ae332f8 | ||
|
|
2eb3602d61 | ||
|
|
6fb9dd09f5 | ||
|
|
19b6643e5c | ||
|
|
08b889ef09 | ||
|
|
d15d0127fe | ||
|
|
674888fdc9 | ||
|
|
fb140eda33 | ||
|
|
398ec4383e | ||
|
|
eff0debe14 |
22
.travis.yml
Normal file
22
.travis.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.12.x
|
||||
|
||||
install: make
|
||||
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
|
||||
before_install:
|
||||
- GO111MODULE=off go get -v golang.org/x/lint/golint
|
||||
- GO111MODULE=off go get -u github.com/kisielk/errcheck
|
||||
|
||||
script:
|
||||
- make check_all
|
||||
- git diff --exit-code
|
||||
- make test_full
|
||||
- make victoria-metrics
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
37
Makefile
37
Makefile
@@ -19,16 +19,20 @@ include deployment/*/Makefile
|
||||
clean:
|
||||
rm -rf bin/*
|
||||
|
||||
publish: publish-victoria-metrics
|
||||
|
||||
package: package-victoria-metrics
|
||||
|
||||
release: victoria-metrics-prod
|
||||
cd bin && tar czf victoria-metrics-$(PKG_TAG).tar.gz victoria-metrics-prod
|
||||
|
||||
fmt:
|
||||
go fmt $(PKG_PREFIX)/lib/...
|
||||
go fmt $(PKG_PREFIX)/app/...
|
||||
GO111MODULE=on gofmt -l -w -s ./lib
|
||||
GO111MODULE=on gofmt -l -w -s ./app
|
||||
|
||||
vet:
|
||||
go vet $(PKG_PREFIX)/lib/...
|
||||
go vet $(PKG_PREFIX)/app/...
|
||||
GO111MODULE=on go vet -mod=vendor ./lib/...
|
||||
GO111MODULE=on go vet -mod=vendor ./app/...
|
||||
|
||||
lint: install-golint
|
||||
golint lib/...
|
||||
@@ -46,19 +50,34 @@ errcheck: install-errcheck
|
||||
install-errcheck:
|
||||
which errcheck || GO111MODULE=off go get -u github.com/kisielk/errcheck
|
||||
|
||||
check_all: fmt vet lint errcheck golangci-lint
|
||||
|
||||
test:
|
||||
go test $(PKG_PREFIX)/lib/...
|
||||
GO111MODULE=on go test -mod=vendor ./lib/...
|
||||
GO111MODULE=on go test -mod=vendor ./app/...
|
||||
|
||||
test_full:
|
||||
GO111MODULE=on go test -mod=vendor -coverprofile=coverage.txt -covermode=atomic ./lib/... ./app/...
|
||||
|
||||
benchmark:
|
||||
go test -bench=. $(PKG_PREFIX)/lib/...
|
||||
GO111MODULE=on go test -mod=vendor -bench=. ./lib/...
|
||||
GO111MODULE=on go test -mod=vendor -bench=. ./app/...
|
||||
|
||||
vendor-update:
|
||||
go get -u
|
||||
go mod tidy
|
||||
go mod vendor
|
||||
GO111MODULE=on go get -u ./lib/...
|
||||
GO111MODULE=on go get -u ./app/...
|
||||
GO111MODULE=on go mod tidy
|
||||
GO111MODULE=on go mod vendor
|
||||
|
||||
quicktemplate-gen: install-qtc
|
||||
qtc
|
||||
|
||||
install-qtc:
|
||||
which qtc || GO111MODULE=off go get -u github.com/valyala/quicktemplate/qtc
|
||||
|
||||
|
||||
golangci-lint: install-golangci-lint
|
||||
golangci-lint run --exclude '(SA4003|SA1019):' -D errcheck
|
||||
|
||||
install-golangci-lint:
|
||||
which golangci-lint || GO111MODULE=off go get -u github.com/golangci/golangci-lint/cmd/golangci-lint
|
||||
|
||||
228
README.md
228
README.md
@@ -1,12 +1,16 @@
|
||||
<img text-align="center" alt="Victoria Metrics" src="logo.png">
|
||||
[](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest)
|
||||
[](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/LICENSE)
|
||||
[](https://goreportcard.com/report/github.com/VictoriaMetrics/VictoriaMetrics)
|
||||
[](https://travis-ci.org/VictoriaMetrics/VictoriaMetrics)
|
||||
[](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics)
|
||||
|
||||
<img text-align="center" alt="Victoria Metrics" src="logo.png">
|
||||
|
||||
## Single-node VictoriaMetrics
|
||||
|
||||
[](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest)
|
||||
|
||||
VictoriaMetrics is a long-term remote storage for Prometheus.
|
||||
VictoriaMetrics is fast, cost-effective and scalable time series database. It can be used as a long-term remote storage for Prometheus.
|
||||
It is available in [binary releases](https://github.com/VictoriaMetrics/VictoriaMetrics/releases),
|
||||
[docker images](https://hub.docker.com/r/valyala/victoria-metrics/) and
|
||||
[docker images](https://hub.docker.com/r/victoriametrics/victoria-metrics/) and
|
||||
in [source code](https://github.com/VictoriaMetrics/VictoriaMetrics).
|
||||
|
||||
Cluster version is available [here](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/cluster).
|
||||
@@ -37,7 +41,7 @@ Cluster version is available [here](https://github.com/VictoriaMetrics/VictoriaM
|
||||
* [Graphite plaintext protocol](https://graphite.readthedocs.io/en/latest/feeding-carbon.html) with [tags](https://graphite.readthedocs.io/en/latest/tags.html#carbon)
|
||||
if `-graphiteListenAddr` is set.
|
||||
* [OpenTSDB put message](http://opentsdb.net/docs/build/html/api_telnet/put.html) if `-opentsdbListenAddr` is set.
|
||||
* Ideally works with big amounts of time series data from IoT sensors, connected car sensors and industrial sensors.
|
||||
* Ideally works with big amounts of time series data from Kubernetes, IoT sensors, connected cars and industrial telemetry.
|
||||
* Has open source [cluster version](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/cluster).
|
||||
|
||||
|
||||
@@ -46,34 +50,47 @@ Cluster version is available [here](https://github.com/VictoriaMetrics/VictoriaM
|
||||
|
||||
### Table of contents
|
||||
|
||||
* [How to build from sources](#how-to-build-from-sources)
|
||||
* [How to start VictoriaMetrics](#how-to-start-victoriametrics)
|
||||
* [Prometheus setup](#prometheus-setup)
|
||||
* [Grafana setup](#grafana-setup)
|
||||
* [How to send data from InfluxDB-compatible agents such as Telegraf](#how-to-send-data-from-influxdb-compatible-agents-such-as-telegraf)
|
||||
* [How to send data from Graphite-compatible agents such as StatsD](#how-to-send-data-from-graphite-compatible-agents-such-as-statsd)
|
||||
* [How to send data from OpenTSDB-compatible agents](#how-to-send-data-from-opentsdb-compatible-agents)
|
||||
* [How to apply new config / ugrade VictoriaMetrics](#how-to-apply-new-config--upgrade-victoriametrics)
|
||||
* [How to work with snapshots](#how-to-work-with-snapshots)
|
||||
* [How to delete time series](#how-to-delete-time-series)
|
||||
* [How to export time series](#how-to-export-time-series)
|
||||
* [Federation](#federation)
|
||||
* [Capacity planning](#capacity-planning)
|
||||
* [High Availability](#high-availability)
|
||||
* [Multiple retentions](#multiple-retentions)
|
||||
* [Scalability and cluster version](#scalability-and-cluster-version)
|
||||
* [Security](#security)
|
||||
* [Tuning](#tuning)
|
||||
* [Monitoring](#monitoring)
|
||||
* [Troubleshooting](#troubleshooting)
|
||||
* [Community and contributions](#community-and-contributions)
|
||||
* [Reporting bugs](#reporting-bugs)
|
||||
- [How to build from sources](#how-to-build-from-sources)
|
||||
- [Development build](#development-build)
|
||||
- [Production build](#production-build)
|
||||
- [Building docker images](#building-docker-images)
|
||||
- [How to start VictoriaMetrics](#how-to-start-victoriametrics)
|
||||
- [Setting up service](#setting-up-service)
|
||||
- [Third-party contributions](#third-party-contributions)
|
||||
- [Prometheus setup](#prometheus-setup)
|
||||
- [Grafana setup](#grafana-setup)
|
||||
- [How to send data from InfluxDB-compatible agents such as Telegraf?](#how-to-send-data-from-influxdb-compatible-agents-such-as-telegraf)
|
||||
- [How to send data from Graphite-compatible agents such as StatsD?](#how-to-send-data-from-graphite-compatible-agents-such-as-statsd)
|
||||
- [How to send data from OpenTSDB-compatible agents?](#how-to-send-data-from-opentsdb-compatible-agents)
|
||||
- [How to apply new config / upgrade VictoriaMetrics?](#how-to-apply-new-config--upgrade-victoriametrics)
|
||||
- [How to work with snapshots?](#how-to-work-with-snapshots)
|
||||
- [How to delete time series?](#how-to-delete-time-series)
|
||||
- [How to export time series?](#how-to-export-time-series)
|
||||
- [Federation](#federation)
|
||||
- [Capacity planning](#capacity-planning)
|
||||
- [High availability](#high-availability)
|
||||
- [Multiple retentions](#multiple-retentions)
|
||||
- [Downsampling](#downsampling)
|
||||
- [Multi-tenancy](#multi-tenancy)
|
||||
- [Scalability and cluster version](#scalability-and-cluster-version)
|
||||
- [Security](#security)
|
||||
- [Tuning](#tuning)
|
||||
- [Monitoring](#monitoring)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Contacts](#contacts)
|
||||
- [Community and contributions](#community-and-contributions)
|
||||
- [Reporting bugs](#reporting-bugs)
|
||||
- [Victoria Metrics Logo](#victoria-metrics-logo)
|
||||
- [Logo Usage Guidelines](#logo-usage-guidelines)
|
||||
- [Font used:](#font-used)
|
||||
- [Color Palette:](#color-palette)
|
||||
- [We kindly ask:](#we-kindly-ask)
|
||||
|
||||
|
||||
### How to build from sources
|
||||
|
||||
We recommend using either [binary releases](https://github.com/VictoriaMetrics/VictoriaMetrics/releases) or
|
||||
[docker images](https://hub.docker.com/r/valyala/victoria-metrics/) instead of building VictoriaMetrics
|
||||
[docker images](https://hub.docker.com/r/victoriametrics/victoria-metrics/) instead of building VictoriaMetrics
|
||||
from sources. Building from sources is reasonable when developing an additional features specific
|
||||
to your needs.
|
||||
|
||||
@@ -81,21 +98,20 @@ to your needs.
|
||||
#### Development build
|
||||
|
||||
1. [Install Go](https://golang.org/doc/install). The minimum supported version is Go 1.12.
|
||||
2. Run `go build ./app/victoria-metrics` from the root folder of the repository.
|
||||
It will build `victoria-metrics` binary in the root folder of the repository.
|
||||
2. Run `make victoria-metrics` from the root folder of the repository.
|
||||
It will build `victoria-metrics` binary and put it into the `bin` folder.
|
||||
|
||||
#### Production build
|
||||
|
||||
1. [Install docker](https://docs.docker.com/install/).
|
||||
2. Run `make victoria-metrics-prod` from the root folder of the respository.
|
||||
2. Run `make victoria-metrics-prod` from the root folder of the repository.
|
||||
It will build `victoria-metrics-prod` binary and put it into the `bin` folder.
|
||||
|
||||
#### Building docker images
|
||||
|
||||
Run `make package-victoria-metrics`. It will build `valyala/victoria-metrics:<PKG_TAG>` docker image locally.
|
||||
Run `make package-victoria-metrics`. It will build `victoriametrics/victoria-metrics:<PKG_TAG>` docker image locally.
|
||||
`<PKG_TAG>` is auto-generated image tag, which depends on source code in the repository.
|
||||
The `<PKG_TAG>` may be manually set via `PKG_TAG=foobar make package`.
|
||||
|
||||
The `<PKG_TAG>` may be manually set via `PKG_TAG=foobar make package-victoria-metrics`.
|
||||
|
||||
|
||||
### How to start VictoriaMetrics
|
||||
@@ -113,6 +129,16 @@ The following command line flags are used the most:
|
||||
Pass `-help` to see all the available flags with description and default values.
|
||||
|
||||
|
||||
### Setting up service
|
||||
|
||||
Read [these instructions](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/43) on how to set up VictoriaMetrics as a service in your OS.
|
||||
|
||||
|
||||
### Third-party contributions
|
||||
|
||||
* [Unofficial yum repository](https://copr.fedorainfracloud.org/coprs/antonpatsev/VictoriaMetrics/) ([source code](https://github.com/patsevanton/victoriametrics-rpm))
|
||||
|
||||
|
||||
### Prometheus setup
|
||||
|
||||
Add the following lines to Prometheus config file (it is usually located at `/etc/prometheus/prometheus.yml`):
|
||||
@@ -122,6 +148,7 @@ remote_write:
|
||||
- url: http://<victoriametrics-addr>:8428/api/v1/write
|
||||
queue_config:
|
||||
max_samples_per_send: 10000
|
||||
max_shards: 100
|
||||
```
|
||||
|
||||
Substitute `<victoriametrics-addr>` with the hostname or IP address of VictoriaMetrics.
|
||||
@@ -176,10 +203,43 @@ For instance, put the following lines into `Telegraf` config, so it sends data t
|
||||
Do not forget substituting `<victoriametrics-addr>` with the real address where VictoriaMetrics runs.
|
||||
|
||||
VictoriaMetrics maps Influx data using the following rules:
|
||||
* [`db` query arg](https://docs.influxdata.com/influxdb/v1.7/tools/api/#write-http-endpoint) is mapped into `db` label value
|
||||
* Field names are mapped to time series names prefixed by `{measurement}.` value
|
||||
* Field values are mapped to time series values
|
||||
* Tags are mapped to Prometheus labels as-is
|
||||
* [`db` query arg](https://docs.influxdata.com/influxdb/v1.7/tools/api/#write-http-endpoint) is mapped into `db` label value.
|
||||
* Field names are mapped to time series names prefixed with `{measurement}{separator}` value. `{separator}` equals to `.` by default, but can be changed with `-influxMeasurementFieldSeparator` command-line flag.
|
||||
* Field values are mapped to time series values.
|
||||
* Tags are mapped to Prometheus labels as-is.
|
||||
|
||||
For example, the following Influx line:
|
||||
|
||||
```
|
||||
foo,tag1=value1,tag2=value2 field1=12,field2=40
|
||||
```
|
||||
|
||||
is converted into the following Prometheus data points:
|
||||
|
||||
```
|
||||
foo.field1{tag1="value1", tag2="value2"} 12
|
||||
foo.field2{tag1="value1", tag2="value2"} 40
|
||||
```
|
||||
|
||||
Example for writing data with Influx line protocol to local VictoriaMetrics using `curl`:
|
||||
|
||||
```
|
||||
curl -d 'measurement,tag1=value1,tag2=value2 field1=123,field2=1.23' -X POST 'http://localhost:8428/write'
|
||||
```
|
||||
|
||||
Arbitrary number of lines delimited by '\n' may be sent in a single request.
|
||||
After that the data may be read via [/api/v1/export](#how-to-export-time-series) endpoint:
|
||||
|
||||
```
|
||||
curl -G 'http://localhost:8428/api/v1/export' --data-urlencode 'match={__name__!=""}'
|
||||
```
|
||||
|
||||
The `/api/v1/export` endpoint should return the following response:
|
||||
|
||||
```
|
||||
{"metric":{"__name__":"measurement.field1","tag1":"value1","tag2":"value2"},"values":[123],"timestamps":[1560272508147]}
|
||||
{"metric":{"__name__":"measurement.field2","tag1":"value1","tag2":"value2"},"values":[1.23],"timestamps":[1560272508147]}
|
||||
```
|
||||
|
||||
|
||||
### How to send data from Graphite-compatible agents such as [StatsD](https://github.com/etsy/statsd)?
|
||||
@@ -188,25 +248,66 @@ VictoriaMetrics maps Influx data using the following rules:
|
||||
the following command will enable Graphite receiver in VictoriaMetrics on TCP and UDP port `2003`:
|
||||
|
||||
```
|
||||
/path/to/victoria-metrics-prod ... -graphiteListenAddr=:2003
|
||||
/path/to/victoria-metrics-prod -graphiteListenAddr=:2003
|
||||
```
|
||||
|
||||
2) Use the configured address in Graphite-compatible agents. For instance, set `graphiteHost`
|
||||
to the VictoriaMetrics host in `StatsD` configs.
|
||||
|
||||
|
||||
Example for writing data with Graphite plaintext protocol to local VictoriaMetrics using `nc`:
|
||||
|
||||
```
|
||||
echo "foo.bar.baz;tag1=value1;tag2=value2 123 `date +%s`" | nc -N localhost 2003
|
||||
```
|
||||
|
||||
VictoriaMetrics sets the current time if timestamp is omitted.
|
||||
Arbitrary number of lines delimited by `\n` may be sent in one go.
|
||||
After that the data may be read via [/api/v1/export](#how-to-export-time-series) endpoint:
|
||||
|
||||
```
|
||||
curl -G 'http://localhost:8428/api/v1/export' --data-urlencode 'match={__name__!=""}'
|
||||
```
|
||||
|
||||
The `/api/v1/export` endpoint should return the following response:
|
||||
|
||||
```
|
||||
{"metric":{"__name__":"foo.bar.baz","tag1":"value1","tag2":"value2"},"values":[123],"timestamps":[1560277406000]}
|
||||
```
|
||||
|
||||
|
||||
### How to send data from OpenTSDB-compatible agents?
|
||||
|
||||
1) Enable OpenTSDB receiver in VictoriaMetrics by setting `-opentsdbListenAddr` command line flag. For instance,
|
||||
the following command will enable OpenTSDB receiver in VictoriaMetrics on TCP and UDP port `4242`:
|
||||
|
||||
```
|
||||
/path/to/victoria-metrics-prod ... -opentsdbListenAddr=:4242
|
||||
/path/to/victoria-metrics-prod -opentsdbListenAddr=:4242
|
||||
```
|
||||
|
||||
2) Send data to the given address from OpenTSDB-compatible agents.
|
||||
|
||||
|
||||
Example for writing data with OpenTSDB protocol to local VictoriaMetrics using `nc`:
|
||||
|
||||
```
|
||||
echo "put foo.bar.baz `date +%s` 123 tag1=value1 tag2=value2" | nc -N localhost 4242
|
||||
```
|
||||
|
||||
Arbitrary number of lines delimited by `\n` may be sent in one go.
|
||||
After that the data may be read via [/api/v1/export](#how-to-export-time-series) endpoint:
|
||||
|
||||
```
|
||||
curl -G 'http://localhost:8428/api/v1/export' --data-urlencode 'match={__name__!=""}'
|
||||
```
|
||||
|
||||
The `/api/v1/export` endpoint should return the following response:
|
||||
|
||||
```
|
||||
{"metric":{"__name__":"foo.bar.baz","tag1":"value1","tag2":"value2"},"values":[123],"timestamps":[1560277292000]}
|
||||
```
|
||||
|
||||
|
||||
### How to apply new config / upgrade VictoriaMetrics?
|
||||
|
||||
VictoriaMetrics must be restarted in order to upgrade or apply new config:
|
||||
@@ -218,6 +319,8 @@ VictoriaMetrics must be restarted in order to upgrade or apply new config:
|
||||
|
||||
### How to work with snapshots?
|
||||
|
||||
VictoriaMetrics is able to create [instant snapshots](https://medium.com/@valyala/how-victoriametrics-makes-instant-snapshots-for-multi-terabyte-time-series-data-e1f3fb0e0282)
|
||||
for all the data stored under `-storageDataPath` directory.
|
||||
Navigate to `http://<victoriametrics-addr>:8428/snapshot/create` in order to create an instant snapshot.
|
||||
The page will return the following JSON response:
|
||||
|
||||
@@ -226,7 +329,7 @@ The page will return the following JSON response:
|
||||
```
|
||||
|
||||
Snapshots are created under `<-storageDataPath>/snapshots` directory, where `<-storageDataPath>`
|
||||
is the command-line flag value. Snapshots can be archived to backup storage via `rsync -L`, `scp -r`
|
||||
is the command-line flag value. Snapshots can be archived to backup storage via `cp -L`, `rsync -L`, `scp -r`
|
||||
or any similar tool that follows symlinks during copying.
|
||||
|
||||
The `http://<victoriametrics-addr>:8428/snapshot/list` page contains the list of available snapshots.
|
||||
@@ -236,6 +339,12 @@ to delete `<snapshot-name>` snapshot.
|
||||
|
||||
Navigate to `http://<victoriametrics-addr>:8428/snapshot/delete_all` in order to delete all the snapshots.
|
||||
|
||||
Steps for restoring from a snapshot:
|
||||
1. Stop VictoriaMetrics with `kill -INT`.
|
||||
2. Remove the entire contents of the directory pointed by `-storageDataPath` command-line flag.
|
||||
3. Copy snapshot contents to the directory pointed by `-storageDataPath`.
|
||||
4. Start VictoriaMetrics.
|
||||
|
||||
|
||||
### How to delete time series?
|
||||
|
||||
@@ -268,7 +377,7 @@ at `http://<victoriametrics-addr>:8428/federate?match[]=<timeseries_selector_for
|
||||
|
||||
Optional `start` and `end` args may be added to the request in order to scrape the last point for each selected time series on the `[start ... end]` interval.
|
||||
`start` and `end` may contain either unix timestamp in seconds or [RFC3339](https://www.ietf.org/rfc/rfc3339.txt) values. By default the last point
|
||||
on the interval `[now - max_lookback ... now]` is scraped for each time series. Default value for `max_lookback` is `5m` (5 minutes), but can be overriden.
|
||||
on the interval `[now - max_lookback ... now]` is scraped for each time series. Default value for `max_lookback` is `5m` (5 minutes), but can be overridden.
|
||||
For instance, `/federate?match[]=up&max_lookback=1h` would return last points on the `[now - 1h ... now]` interval. This may be useful for time series federation
|
||||
with scrape intervals exceeding `5m`.
|
||||
|
||||
@@ -325,6 +434,23 @@ Just start multiple VictoriaMetrics instances with distinct values for the follo
|
||||
* `-httpListenAddr`, so clients may reach VictoriaMetrics instance with proper retention
|
||||
|
||||
|
||||
### Downsampling
|
||||
|
||||
There is no downsampling support at the moment, but:
|
||||
- VictoriaMetrics is optimized for querying big amounts of raw data. See benchmark results for heavy queries
|
||||
in [this article](https://medium.com/@valyala/measuring-vertical-scalability-for-time-series-databases-in-google-cloud-92550d78d8ae).
|
||||
- VictoriaMetrics has good compression for on-disk data. See [this article](https://medium.com/@valyala/victoriametrics-achieving-better-compression-for-time-series-data-than-gorilla-317bc1f95932)
|
||||
for details.
|
||||
|
||||
These properties reduce the need in downsampling. We plan implementing downsampling in the future.
|
||||
See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/36) for details.
|
||||
|
||||
|
||||
### Multi-tenancy
|
||||
|
||||
Single-node VictoriaMetrics doesn't support multi-tenancy. Use [cluster version](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/cluster) instead.
|
||||
|
||||
|
||||
### Scalability and cluster version
|
||||
|
||||
Though single-node VictoriaMetrics cannot scale to multiple nodes, it is optimized for resource usage - storage size / bandwidth / IOPS, RAM, CPU.
|
||||
@@ -376,10 +502,24 @@ There is [an official Grafana dashboard for single-node VictoriaMetrics](https:/
|
||||
Another option is to increase `-memory.allowedPercent` command-line flag value. Be careful with this
|
||||
option, since too big value for `-memory.allowedPercent` may result in high I/O usage.
|
||||
|
||||
* If VictoriaMetrics doesn't work because of certain parts are corrupted due to disk errors,
|
||||
then just remove directoreis with broken parts. This will recover VictoriaMetrics at the cost
|
||||
of data loss stored in the broken parts. In the future `vmrecover` tool will be created
|
||||
for automatic recovering from such errors.
|
||||
|
||||
|
||||
## Contacts
|
||||
|
||||
Contact us with any questions regarding VictoriaMetrics at [info@victoriametrics.com](mailto:info@victoriametrics.com).
|
||||
|
||||
|
||||
## Community and contributions
|
||||
|
||||
Feel free asking any questions regarding VictoriaMetrics [here](https://groups.google.com/forum/#!forum/victorametrics-users).
|
||||
Feel free asking any questions regarding VictoriaMetrics:
|
||||
|
||||
- [telergam-en](https://t.me/VictoriaMetrics_en)
|
||||
- [telergam-ru](https://t.me/VictoriaMetrics_ru1)
|
||||
- [google groups](https://groups.google.com/forum/#!forum/victorametrics-users)
|
||||
|
||||
We are open to third-party pull requests provided they follow [KISS design principle](https://en.wikipedia.org/wiki/KISS_principle):
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
# All these commands must run from repository root.
|
||||
|
||||
victoria-metrics:
|
||||
GO111MODULE=on go build -mod=vendor -ldflags "$(GO_BUILDINFO)" -o bin/victoria-metrics ./app/victoria-metrics
|
||||
|
||||
victoria-metrics-prod:
|
||||
APP_NAME=victoria-metrics $(MAKE) app-via-docker
|
||||
|
||||
@@ -12,10 +15,13 @@ publish-victoria-metrics:
|
||||
|
||||
run-victoria-metrics:
|
||||
mkdir -p victoria-metrics-data
|
||||
DOCKER_OPTS='-v $(shell pwd)/victoria-metrics-data:/victoria-metrics-data -p 8428:8428 -p 2003:2003 -p 2003:2003/udp' \
|
||||
DOCKER_OPTS='-v $(shell pwd)/victoria-metrics-data:/victoria-metrics-data' \
|
||||
APP_NAME=victoria-metrics \
|
||||
ARGS='-graphiteListenAddr=:2003 -opentsdbListenAddr=:4242 -retentionPeriod=12 -search.maxUniqueTimeseries=1000000 -search.maxQueryDuration=10m' \
|
||||
$(MAKE) run-via-docker
|
||||
|
||||
victoria-metrics-arm:
|
||||
CC=arm-linux-gnueabi-gcc CGO_ENABLED=1 GOARCH=arm GO111MODULE=on go build -mod=vendor -ldflags "$(GO_BUILDINFO)" -o bin/victoria-metrics-arm ./app/victoria-metrics
|
||||
|
||||
victoria-metrics-arm64:
|
||||
CC=aarch64-linux-gnu-gcc CGO_ENABLED=1 GOARCH=arm64 GO111MODULE=on go build -mod=vendor -ldflags "$(GO_BUILDINFO)" -o bin/victoria-metrics-arm64 ./app/victoria-metrics
|
||||
|
||||
@@ -30,11 +30,10 @@ func (ctx *InsertCtx) Reset(rowsLen int) {
|
||||
mr.MetricNameRaw = nil
|
||||
}
|
||||
ctx.mrs = ctx.mrs[:0]
|
||||
|
||||
if n := rowsLen - cap(ctx.mrs); n > 0 {
|
||||
ctx.mrs = append(ctx.mrs[:cap(ctx.mrs)], make([]storage.MetricRow, n)...)
|
||||
}
|
||||
ctx.mrs = ctx.mrs[:rowsLen]
|
||||
ctx.mrs = ctx.mrs[:0]
|
||||
ctx.metricNamesBuf = ctx.metricNamesBuf[:0]
|
||||
}
|
||||
|
||||
|
||||
67
app/vminsert/common/lines_reader.go
Normal file
67
app/vminsert/common/lines_reader.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
)
|
||||
|
||||
// The maximum size of a single line returned by ReadLinesBlock.
|
||||
const maxLineSize = 256 * 1024
|
||||
|
||||
// Default size in bytes of a single block returned by ReadLinesBlock.
|
||||
const defaultBlockSize = 64 * 1024
|
||||
|
||||
// ReadLinesBlock reads a block of lines delimited by '\n' from tailBuf and r into dstBuf.
|
||||
//
|
||||
// Trailing chars after the last newline are put into tailBuf.
|
||||
//
|
||||
// Returns (dstBuf, tailBuf).
|
||||
func ReadLinesBlock(r io.Reader, dstBuf, tailBuf []byte) ([]byte, []byte, error) {
|
||||
if cap(dstBuf) < defaultBlockSize {
|
||||
dstBuf = bytesutil.Resize(dstBuf, defaultBlockSize)
|
||||
}
|
||||
dstBuf = append(dstBuf[:0], tailBuf...)
|
||||
again:
|
||||
n, err := r.Read(dstBuf[len(dstBuf):cap(dstBuf)])
|
||||
// Check for error only if zero bytes read from r, i.e. no forward progress made.
|
||||
// Otherwise process the read data.
|
||||
if n == 0 {
|
||||
if err == nil {
|
||||
return dstBuf, tailBuf, fmt.Errorf("no forward progress made")
|
||||
}
|
||||
if err == io.EOF && len(dstBuf) > 0 {
|
||||
// Missing newline in the end of stream. This is OK,
|
||||
/// so suppress io.EOF for now. It will be returned during the next
|
||||
// call to ReadLinesBlock.
|
||||
// This fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/60 .
|
||||
return dstBuf, tailBuf, nil
|
||||
}
|
||||
return dstBuf, tailBuf, err
|
||||
}
|
||||
dstBuf = dstBuf[:len(dstBuf)+n]
|
||||
|
||||
// Search for the last newline in dstBuf and put the rest into tailBuf.
|
||||
nn := bytes.LastIndexByte(dstBuf[len(dstBuf)-n:], '\n')
|
||||
if nn < 0 {
|
||||
// Didn't found at least a single line.
|
||||
if len(dstBuf) > maxLineSize {
|
||||
return dstBuf, tailBuf, fmt.Errorf("too long line: more than %d bytes", maxLineSize)
|
||||
}
|
||||
if cap(dstBuf) < 2*len(dstBuf) {
|
||||
// Increase dsbBuf capacity, so more data could be read into it.
|
||||
dstBufLen := len(dstBuf)
|
||||
dstBuf = bytesutil.Resize(dstBuf, 2*cap(dstBuf))
|
||||
dstBuf = dstBuf[:dstBufLen]
|
||||
}
|
||||
goto again
|
||||
}
|
||||
|
||||
// Found at least a single line. Return it.
|
||||
nn += len(dstBuf) - n
|
||||
tailBuf = append(tailBuf[:0], dstBuf[nn+1:]...)
|
||||
dstBuf = dstBuf[:nn]
|
||||
return dstBuf, tailBuf, nil
|
||||
}
|
||||
146
app/vminsert/common/lines_reader_test.go
Normal file
146
app/vminsert/common/lines_reader_test.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReadLinesBlockFailure(t *testing.T) {
|
||||
f := func(s string) {
|
||||
t.Helper()
|
||||
r := bytes.NewBufferString(s)
|
||||
if _, _, err := ReadLinesBlock(r, nil, nil); err == nil {
|
||||
t.Fatalf("expecting non-nil error")
|
||||
}
|
||||
sbr := &singleByteReader{
|
||||
b: []byte(s),
|
||||
}
|
||||
if _, _, err := ReadLinesBlock(sbr, nil, nil); err == nil {
|
||||
t.Fatalf("expecting non-nil error")
|
||||
}
|
||||
fr := &failureReader{}
|
||||
if _, _, err := ReadLinesBlock(fr, nil, nil); err == nil {
|
||||
t.Fatalf("expecting non-nil error")
|
||||
}
|
||||
}
|
||||
|
||||
// empty string
|
||||
f("")
|
||||
|
||||
// too long string
|
||||
b := make([]byte, maxLineSize+1)
|
||||
f(string(b))
|
||||
}
|
||||
|
||||
type failureReader struct{}
|
||||
|
||||
func (fr *failureReader) Read(p []byte) (int, error) {
|
||||
return 0, fmt.Errorf("some error")
|
||||
}
|
||||
|
||||
func TestReadLineBlockSuccessSingleByteReader(t *testing.T) {
|
||||
f := func(s, dstBufExpected, tailBufExpected string) {
|
||||
t.Helper()
|
||||
|
||||
r := &singleByteReader{
|
||||
b: []byte(s),
|
||||
}
|
||||
dstBuf, tailBuf, err := ReadLinesBlock(r, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
if string(dstBuf) != dstBufExpected {
|
||||
t.Fatalf("unexpected dstBuf; got %q; want %q; tailBuf=%q", dstBuf, dstBufExpected, tailBuf)
|
||||
}
|
||||
if string(tailBuf) != tailBufExpected {
|
||||
t.Fatalf("unexpected tailBuf; got %q; want %q; dstBuf=%q", tailBuf, tailBufExpected, dstBuf)
|
||||
}
|
||||
|
||||
// Verify the same with non-empty dstBuf and tailBuf
|
||||
r = &singleByteReader{
|
||||
b: []byte(s),
|
||||
}
|
||||
dstBuf, tailBuf, err = ReadLinesBlock(r, dstBuf, tailBuf[:0])
|
||||
if err != nil {
|
||||
t.Fatalf("non-empty bufs: unexpected error: %s", err)
|
||||
}
|
||||
if string(dstBuf) != dstBufExpected {
|
||||
t.Fatalf("non-empty bufs: unexpected dstBuf; got %q; want %q; tailBuf=%q", dstBuf, dstBufExpected, tailBuf)
|
||||
}
|
||||
if string(tailBuf) != tailBufExpected {
|
||||
t.Fatalf("non-empty bufs: unexpected tailBuf; got %q; want %q; dstBuf=%q", tailBuf, tailBufExpected, dstBuf)
|
||||
}
|
||||
}
|
||||
|
||||
f("\n", "", "")
|
||||
f("foo\n", "foo", "")
|
||||
f("\nfoo", "", "")
|
||||
f("foo\nbar", "foo", "")
|
||||
f("foo\nbar\nbaz", "foo", "")
|
||||
f("foo", "foo", "")
|
||||
|
||||
// The maximum line size
|
||||
b := make([]byte, maxLineSize+10)
|
||||
b[maxLineSize] = '\n'
|
||||
f(string(b), string(b[:maxLineSize]), "")
|
||||
}
|
||||
|
||||
func TestReadLineBlockSuccessBytesBuffer(t *testing.T) {
|
||||
f := func(s, dstBufExpected, tailBufExpected string) {
|
||||
t.Helper()
|
||||
|
||||
r := bytes.NewBufferString(s)
|
||||
dstBuf, tailBuf, err := ReadLinesBlock(r, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
if string(dstBuf) != dstBufExpected {
|
||||
t.Fatalf("unexpected dstBuf; got %q; want %q; tailBuf=%q", dstBuf, dstBufExpected, tailBuf)
|
||||
}
|
||||
if string(tailBuf) != tailBufExpected {
|
||||
t.Fatalf("unexpected tailBuf; got %q; want %q; dstBuf=%q", tailBuf, tailBufExpected, dstBuf)
|
||||
}
|
||||
|
||||
// Verify the same with non-empty dstBuf and tailBuf
|
||||
r = bytes.NewBufferString(s)
|
||||
dstBuf, tailBuf, err = ReadLinesBlock(r, dstBuf, tailBuf[:0])
|
||||
if err != nil {
|
||||
t.Fatalf("non-empty bufs: unexpected error: %s", err)
|
||||
}
|
||||
if string(dstBuf) != dstBufExpected {
|
||||
t.Fatalf("non-empty bufs: unexpected dstBuf; got %q; want %q; tailBuf=%q", dstBuf, dstBufExpected, tailBuf)
|
||||
}
|
||||
if string(tailBuf) != tailBufExpected {
|
||||
t.Fatalf("non-empty bufs: unexpected tailBuf; got %q; want %q; dstBuf=%q", tailBuf, tailBufExpected, dstBuf)
|
||||
}
|
||||
}
|
||||
|
||||
f("\n", "", "")
|
||||
f("foo\n", "foo", "")
|
||||
f("\nfoo", "", "foo")
|
||||
f("foo\nbar", "foo", "bar")
|
||||
f("foo\nbar\nbaz", "foo\nbar", "baz")
|
||||
|
||||
// The maximum line size
|
||||
b := make([]byte, maxLineSize+10)
|
||||
b[maxLineSize] = '\n'
|
||||
f(string(b), string(b[:maxLineSize]), string(b[maxLineSize+1:]))
|
||||
}
|
||||
|
||||
type singleByteReader struct {
|
||||
b []byte
|
||||
}
|
||||
|
||||
func (sbr *singleByteReader) Read(p []byte) (int, error) {
|
||||
if len(sbr.b) == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n := copy(p, sbr.b[:1])
|
||||
sbr.b = sbr.b[n:]
|
||||
if len(sbr.b) == 0 {
|
||||
return n, io.EOF
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
@@ -1,34 +1,49 @@
|
||||
package concurrencylimiter
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timerpool"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
)
|
||||
|
||||
var maxConcurrentInserts = flag.Int("maxConcurrentInserts", runtime.GOMAXPROCS(-1)*4, "The maximum number of concurrent inserts")
|
||||
|
||||
var (
|
||||
// ch is the channel for limiting concurrent inserts.
|
||||
// Put an item into it before performing an insert and remove
|
||||
// the item after the insert is complete.
|
||||
ch = make(chan struct{}, runtime.GOMAXPROCS(-1)*2)
|
||||
// ch is the channel for limiting concurrent calls to Do.
|
||||
ch chan struct{}
|
||||
|
||||
// waitDuration is the amount of time to wait until at least a single
|
||||
// concurrent insert out of cap(Ch) inserts is complete.
|
||||
// concurrent Do call out of cap(ch) inserts is complete.
|
||||
waitDuration = time.Second * 30
|
||||
)
|
||||
|
||||
// Init initializes concurrencylimiter.
|
||||
//
|
||||
// Init must be called after flag.Parse call.
|
||||
func Init() {
|
||||
ch = make(chan struct{}, *maxConcurrentInserts)
|
||||
}
|
||||
|
||||
// Do calls f with the limited concurrency.
|
||||
func Do(f func() error) error {
|
||||
// Limit the number of conurrent inserts in order to prevent from excess
|
||||
// Limit the number of conurrent f calls in order to prevent from excess
|
||||
// memory usage and CPU trashing.
|
||||
t := time.NewTimer(waitDuration)
|
||||
t := timerpool.Get(waitDuration)
|
||||
select {
|
||||
case ch <- struct{}{}:
|
||||
t.Stop()
|
||||
timerpool.Put(t)
|
||||
err := f()
|
||||
<-ch
|
||||
return err
|
||||
case <-t.C:
|
||||
return fmt.Errorf("the server is overloaded with %d concurrent inserts; either increase the number of CPUs or reduce the load", cap(ch))
|
||||
timerpool.Put(t)
|
||||
concurrencyLimitErrors.Inc()
|
||||
return fmt.Errorf("the server is overloaded with %d concurrent inserts; either increase -maxConcurrentInserts or reduce the load", cap(ch))
|
||||
}
|
||||
}
|
||||
|
||||
var concurrencyLimitErrors = metrics.NewCounter(`vm_concurrency_limit_errors_total`)
|
||||
|
||||
@@ -86,7 +86,9 @@ func (r *Row) unmarshal(s string, tagsPool []Tag) ([]Tag, error) {
|
||||
|
||||
n = strings.IndexByte(tail, ' ')
|
||||
if n < 0 {
|
||||
return tagsPool, fmt.Errorf("cannot find whitespace between value and timestamp in %q", s)
|
||||
// There is no timestamp. Use default timestamp instead.
|
||||
r.Value = fastfloat.ParseBestEffort(tail)
|
||||
return tagsPool, nil
|
||||
}
|
||||
r.Value = fastfloat.ParseBestEffort(tail[:n])
|
||||
r.Timestamp = fastfloat.ParseInt64BestEffort(tail[n+1:])
|
||||
|
||||
@@ -22,9 +22,6 @@ func TestRowsUnmarshalFailure(t *testing.T) {
|
||||
// Missing value
|
||||
f("aaa")
|
||||
|
||||
// Missing timestamp
|
||||
f("aaa 1123")
|
||||
|
||||
// Invalid multiline
|
||||
f("aaa\nbbb 123 34")
|
||||
|
||||
@@ -81,6 +78,14 @@ func TestRowsUnmarshalSuccess(t *testing.T) {
|
||||
}},
|
||||
})
|
||||
|
||||
// Missing timestamp
|
||||
f("aaa 1123", &Rows{
|
||||
Rows: []Row{{
|
||||
Metric: "aaa",
|
||||
Value: 1123,
|
||||
}},
|
||||
})
|
||||
|
||||
// Tags
|
||||
f("foo;bar=baz 1 2", &Rows{
|
||||
Rows: []Row{{
|
||||
@@ -116,13 +121,17 @@ func TestRowsUnmarshalSuccess(t *testing.T) {
|
||||
})
|
||||
|
||||
// Multi lines
|
||||
f("foo 0.3 2\nbar.baz 0.34 43\n", &Rows{
|
||||
f("foo 0.3 2\naaa 3\nbar.baz 0.34 43\n", &Rows{
|
||||
Rows: []Row{
|
||||
{
|
||||
Metric: "foo",
|
||||
Value: 0.3,
|
||||
Timestamp: 2,
|
||||
},
|
||||
{
|
||||
Metric: "aaa",
|
||||
Value: 3,
|
||||
},
|
||||
{
|
||||
Metric: "bar.baz",
|
||||
Value: 0.34,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package graphite
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
@@ -55,8 +54,6 @@ func (ctx *pushCtx) InsertRows() error {
|
||||
return ic.FlushBufs()
|
||||
}
|
||||
|
||||
const maxReadPacketSize = 4 * 1024 * 1024
|
||||
|
||||
const flushTimeout = 3 * time.Second
|
||||
|
||||
func (ctx *pushCtx) Read(r io.Reader) bool {
|
||||
@@ -71,40 +68,40 @@ func (ctx *pushCtx) Read(r io.Reader) bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
lr := io.LimitReader(r, maxReadPacketSize)
|
||||
ctx.reqBuf.Reset()
|
||||
ctx.reqBuf.B = append(ctx.reqBuf.B[:0], ctx.tailBuf...)
|
||||
n, err := io.CopyBuffer(&ctx.reqBuf, lr, ctx.copyBuf[:])
|
||||
if err != nil {
|
||||
if ne, ok := err.(net.Error); ok && ne.Timeout() {
|
||||
ctx.reqBuf, ctx.tailBuf, ctx.err = common.ReadLinesBlock(r, ctx.reqBuf, ctx.tailBuf)
|
||||
if ctx.err != nil {
|
||||
if ne, ok := ctx.err.(net.Error); ok && ne.Timeout() {
|
||||
// Flush the read data on timeout and try reading again.
|
||||
ctx.err = nil
|
||||
} else {
|
||||
graphiteReadErrors.Inc()
|
||||
ctx.err = fmt.Errorf("cannot read graphite plaintext protocol data: %s", err)
|
||||
if ctx.err != io.EOF {
|
||||
graphiteReadErrors.Inc()
|
||||
ctx.err = fmt.Errorf("cannot read graphite plaintext protocol data: %s", ctx.err)
|
||||
}
|
||||
return false
|
||||
}
|
||||
} else if n < maxReadPacketSize {
|
||||
// Mark the end of stream.
|
||||
ctx.err = io.EOF
|
||||
}
|
||||
|
||||
// Parse all the rows until the last newline in ctx.reqBuf.B
|
||||
nn := bytes.LastIndexByte(ctx.reqBuf.B, '\n')
|
||||
ctx.tailBuf = ctx.tailBuf[:0]
|
||||
if nn >= 0 {
|
||||
ctx.tailBuf = append(ctx.tailBuf[:0], ctx.reqBuf.B[nn+1:]...)
|
||||
ctx.reqBuf.B = ctx.reqBuf.B[:nn]
|
||||
}
|
||||
if err = ctx.Rows.Unmarshal(bytesutil.ToUnsafeString(ctx.reqBuf.B)); err != nil {
|
||||
if err := ctx.Rows.Unmarshal(bytesutil.ToUnsafeString(ctx.reqBuf)); err != nil {
|
||||
graphiteUnmarshalErrors.Inc()
|
||||
ctx.err = fmt.Errorf("cannot unmarshal graphite plaintext protocol data with size %d: %s", len(ctx.reqBuf.B), err)
|
||||
ctx.err = fmt.Errorf("cannot unmarshal graphite plaintext protocol data with size %d: %s", len(ctx.reqBuf), err)
|
||||
return false
|
||||
}
|
||||
|
||||
// Convert timestamps from seconds to milliseconds
|
||||
for i := range ctx.Rows.Rows {
|
||||
ctx.Rows.Rows[i].Timestamp *= 1e3
|
||||
// Fill missing timestamps with the current timestamp rounded to seconds.
|
||||
currentTimestamp := time.Now().Unix()
|
||||
rows := ctx.Rows.Rows
|
||||
for i := range rows {
|
||||
r := &rows[i]
|
||||
if r.Timestamp == 0 {
|
||||
r.Timestamp = currentTimestamp
|
||||
}
|
||||
}
|
||||
|
||||
// Convert timestamps from seconds to milliseconds.
|
||||
for i := range rows {
|
||||
rows[i].Timestamp *= 1e3
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -112,9 +109,8 @@ type pushCtx struct {
|
||||
Rows Rows
|
||||
Common common.InsertCtx
|
||||
|
||||
reqBuf bytesutil.ByteBuffer
|
||||
reqBuf []byte
|
||||
tailBuf []byte
|
||||
copyBuf [16 * 1024]byte
|
||||
|
||||
err error
|
||||
}
|
||||
@@ -129,7 +125,7 @@ func (ctx *pushCtx) Error() error {
|
||||
func (ctx *pushCtx) reset() {
|
||||
ctx.Rows.Reset()
|
||||
ctx.Common.Reset(0)
|
||||
ctx.reqBuf.Reset()
|
||||
ctx.reqBuf = ctx.reqBuf[:0]
|
||||
ctx.tailBuf = ctx.tailBuf[:0]
|
||||
|
||||
ctx.err = nil
|
||||
|
||||
@@ -293,8 +293,10 @@ func parseFieldValue(s string, hasQuotedFields bool) (float64, error) {
|
||||
if len(s) < 2 || s[len(s)-1] != '"' {
|
||||
return 0, fmt.Errorf("missing closing quote for quoted field value %s", s)
|
||||
}
|
||||
// Quoted string is translated to empty value.
|
||||
return 0, nil
|
||||
// Try converting quoted string to number, since sometimes Influx agents
|
||||
// send numbers as strings.
|
||||
s = s[1 : len(s)-1]
|
||||
return fastfloat.ParseBestEffort(s), nil
|
||||
}
|
||||
ch := s[len(s)-1]
|
||||
if ch == 'i' {
|
||||
|
||||
@@ -241,17 +241,27 @@ func TestRowsUnmarshalSuccess(t *testing.T) {
|
||||
})
|
||||
|
||||
// Line with multiple tags, multiple fields and timestamp
|
||||
f(`system,host=ip-172-16-10-144 uptime_format="3 days, 21:01" 1557761040000000000`, &Rows{
|
||||
f(`system,host=ip-172-16-10-144 uptime_format="3 days, 21:01",quoted_float="-1.23",quoted_int="123" 1557761040000000000`, &Rows{
|
||||
Rows: []Row{{
|
||||
Measurement: "system",
|
||||
Tags: []Tag{{
|
||||
Key: "host",
|
||||
Value: "ip-172-16-10-144",
|
||||
}},
|
||||
Fields: []Field{{
|
||||
Key: "uptime_format",
|
||||
Value: 0,
|
||||
}},
|
||||
Fields: []Field{
|
||||
{
|
||||
Key: "uptime_format",
|
||||
Value: 0,
|
||||
},
|
||||
{
|
||||
Key: "quoted_float",
|
||||
Value: -1.23,
|
||||
},
|
||||
{
|
||||
Key: "quoted_int",
|
||||
Value: 123,
|
||||
},
|
||||
},
|
||||
Timestamp: 1557761040000000000,
|
||||
}},
|
||||
})
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package influx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -17,6 +17,11 @@ import (
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
)
|
||||
|
||||
var (
|
||||
measurementFieldSeparator = flag.String("influxMeasurementFieldSeparator", ".", "Separator for `{measurement}{separator}{field_name}` metric name when inserted via Influx line protocol")
|
||||
skipSingleField = flag.Bool("influxSkipSingleField", false, "Uses `{measurement}` instead of `{measurement}{separator}{field_name}` for metic name if Influx line contains only a single field")
|
||||
)
|
||||
|
||||
var rowsInserted = metrics.NewCounter(`vm_rows_inserted_total{type="influx"}`)
|
||||
|
||||
// InsertHandler processes remote write for influx line protocol.
|
||||
@@ -89,11 +94,16 @@ func (ctx *pushCtx) InsertRows(db string) error {
|
||||
}
|
||||
ctx.metricNameBuf = storage.MarshalMetricNameRaw(ctx.metricNameBuf[:0], ic.Labels)
|
||||
ctx.metricGroupBuf = append(ctx.metricGroupBuf[:0], r.Measurement...)
|
||||
ctx.metricGroupBuf = append(ctx.metricGroupBuf, '.')
|
||||
skipFieldKey := len(r.Fields) == 1 && *skipSingleField
|
||||
if !skipFieldKey {
|
||||
ctx.metricGroupBuf = append(ctx.metricGroupBuf, *measurementFieldSeparator...)
|
||||
}
|
||||
metricGroupPrefixLen := len(ctx.metricGroupBuf)
|
||||
for j := range r.Fields {
|
||||
f := &r.Fields[j]
|
||||
ctx.metricGroupBuf = append(ctx.metricGroupBuf[:metricGroupPrefixLen], f.Key...)
|
||||
if !skipFieldKey {
|
||||
ctx.metricGroupBuf = append(ctx.metricGroupBuf[:metricGroupPrefixLen], f.Key...)
|
||||
}
|
||||
metricGroup := bytesutil.ToUnsafeString(ctx.metricGroupBuf)
|
||||
ic.Labels = ic.Labels[:0]
|
||||
ic.AddLabel("", metricGroup)
|
||||
@@ -123,36 +133,21 @@ func putGzipReader(zr *gzip.Reader) {
|
||||
|
||||
var gzipReaderPool sync.Pool
|
||||
|
||||
const maxReadPacketSize = 4 * 1024 * 1024
|
||||
|
||||
func (ctx *pushCtx) Read(r io.Reader, tsMultiplier int64) bool {
|
||||
if ctx.err != nil {
|
||||
return false
|
||||
}
|
||||
lr := io.LimitReader(r, maxReadPacketSize)
|
||||
ctx.reqBuf.Reset()
|
||||
ctx.reqBuf.B = append(ctx.reqBuf.B[:0], ctx.tailBuf...)
|
||||
n, err := io.CopyBuffer(&ctx.reqBuf, lr, ctx.copyBuf[:])
|
||||
if err != nil {
|
||||
influxReadErrors.Inc()
|
||||
ctx.err = fmt.Errorf("cannot read influx line protocol data: %s", err)
|
||||
ctx.reqBuf, ctx.tailBuf, ctx.err = common.ReadLinesBlock(r, ctx.reqBuf, ctx.tailBuf)
|
||||
if ctx.err != nil {
|
||||
if ctx.err != io.EOF {
|
||||
influxReadErrors.Inc()
|
||||
ctx.err = fmt.Errorf("cannot read influx line protocol data: %s", ctx.err)
|
||||
}
|
||||
return false
|
||||
}
|
||||
if n < maxReadPacketSize {
|
||||
// Mark the end of stream.
|
||||
ctx.err = io.EOF
|
||||
}
|
||||
|
||||
// Parse all the rows until the last newline in ctx.reqBuf.B
|
||||
nn := bytes.LastIndexByte(ctx.reqBuf.B, '\n')
|
||||
ctx.tailBuf = ctx.tailBuf[:0]
|
||||
if nn >= 0 {
|
||||
ctx.tailBuf = append(ctx.tailBuf[:0], ctx.reqBuf.B[nn+1:]...)
|
||||
ctx.reqBuf.B = ctx.reqBuf.B[:nn]
|
||||
}
|
||||
if err = ctx.Rows.Unmarshal(bytesutil.ToUnsafeString(ctx.reqBuf.B)); err != nil {
|
||||
if err := ctx.Rows.Unmarshal(bytesutil.ToUnsafeString(ctx.reqBuf)); err != nil {
|
||||
influxUnmarshalErrors.Inc()
|
||||
ctx.err = fmt.Errorf("cannot unmarshal influx line protocol data with size %d: %s", len(ctx.reqBuf.B), err)
|
||||
ctx.err = fmt.Errorf("cannot unmarshal influx line protocol data with size %d: %s", len(ctx.reqBuf), err)
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -191,9 +186,8 @@ type pushCtx struct {
|
||||
Rows Rows
|
||||
Common common.InsertCtx
|
||||
|
||||
reqBuf bytesutil.ByteBuffer
|
||||
reqBuf []byte
|
||||
tailBuf []byte
|
||||
copyBuf [16 * 1024]byte
|
||||
metricNameBuf []byte
|
||||
metricGroupBuf []byte
|
||||
|
||||
@@ -211,7 +205,7 @@ func (ctx *pushCtx) reset() {
|
||||
ctx.Rows.Reset()
|
||||
ctx.Common.Reset(0)
|
||||
|
||||
ctx.reqBuf.Reset()
|
||||
ctx.reqBuf = ctx.reqBuf[:0]
|
||||
ctx.tailBuf = ctx.tailBuf[:0]
|
||||
ctx.metricNameBuf = ctx.metricNameBuf[:0]
|
||||
ctx.metricGroupBuf = ctx.metricGroupBuf[:0]
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/concurrencylimiter"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/graphite"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/influx"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/opentsdb"
|
||||
@@ -22,6 +23,7 @@ var (
|
||||
|
||||
// Init initializes vminsert.
|
||||
func Init() {
|
||||
concurrencylimiter.Init()
|
||||
if len(*graphiteListenAddr) > 0 {
|
||||
go graphite.Serve(*graphiteListenAddr)
|
||||
}
|
||||
@@ -63,7 +65,8 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return true
|
||||
case "/query":
|
||||
// Emulate fake response for influx query
|
||||
// Emulate fake response for influx query.
|
||||
// This is required for TSBS benchmark.
|
||||
influxQueryRequests.Inc()
|
||||
fmt.Fprintf(w, `{"results":[{"series":[{"values":[]}]}]}`)
|
||||
return true
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package opentsdb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
@@ -55,8 +54,6 @@ func (ctx *pushCtx) InsertRows() error {
|
||||
return ic.FlushBufs()
|
||||
}
|
||||
|
||||
const maxReadPacketSize = 4 * 1024 * 1024
|
||||
|
||||
const flushTimeout = 3 * time.Second
|
||||
|
||||
func (ctx *pushCtx) Read(r io.Reader) bool {
|
||||
@@ -71,33 +68,22 @@ func (ctx *pushCtx) Read(r io.Reader) bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
lr := io.LimitReader(r, maxReadPacketSize)
|
||||
ctx.reqBuf.Reset()
|
||||
ctx.reqBuf.B = append(ctx.reqBuf.B[:0], ctx.tailBuf...)
|
||||
n, err := io.CopyBuffer(&ctx.reqBuf, lr, ctx.copyBuf[:])
|
||||
if err != nil {
|
||||
if ne, ok := err.(net.Error); ok && ne.Timeout() {
|
||||
ctx.reqBuf, ctx.tailBuf, ctx.err = common.ReadLinesBlock(r, ctx.reqBuf, ctx.tailBuf)
|
||||
if ctx.err != nil {
|
||||
if ne, ok := ctx.err.(net.Error); ok && ne.Timeout() {
|
||||
// Flush the read data on timeout and try reading again.
|
||||
ctx.err = nil
|
||||
} else {
|
||||
opentsdbReadErrors.Inc()
|
||||
ctx.err = fmt.Errorf("cannot read OpenTSDB put protocol data: %s", err)
|
||||
if ctx.err != io.EOF {
|
||||
opentsdbReadErrors.Inc()
|
||||
ctx.err = fmt.Errorf("cannot read OpenTSDB put protocol data: %s", ctx.err)
|
||||
}
|
||||
return false
|
||||
}
|
||||
} else if n < maxReadPacketSize {
|
||||
// Mark the end of stream.
|
||||
ctx.err = io.EOF
|
||||
}
|
||||
|
||||
// Parse all the rows until the last newline in ctx.reqBuf.B
|
||||
nn := bytes.LastIndexByte(ctx.reqBuf.B, '\n')
|
||||
ctx.tailBuf = ctx.tailBuf[:0]
|
||||
if nn >= 0 {
|
||||
ctx.tailBuf = append(ctx.tailBuf[:0], ctx.reqBuf.B[nn+1:]...)
|
||||
ctx.reqBuf.B = ctx.reqBuf.B[:nn]
|
||||
}
|
||||
if err = ctx.Rows.Unmarshal(bytesutil.ToUnsafeString(ctx.reqBuf.B)); err != nil {
|
||||
if err := ctx.Rows.Unmarshal(bytesutil.ToUnsafeString(ctx.reqBuf)); err != nil {
|
||||
opentsdbUnmarshalErrors.Inc()
|
||||
ctx.err = fmt.Errorf("cannot unmarshal OpenTSDB put protocol data with size %d: %s", len(ctx.reqBuf.B), err)
|
||||
ctx.err = fmt.Errorf("cannot unmarshal OpenTSDB put protocol data with size %d: %s", len(ctx.reqBuf), err)
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -112,9 +98,8 @@ type pushCtx struct {
|
||||
Rows Rows
|
||||
Common common.InsertCtx
|
||||
|
||||
reqBuf bytesutil.ByteBuffer
|
||||
reqBuf []byte
|
||||
tailBuf []byte
|
||||
copyBuf [16 * 1024]byte
|
||||
|
||||
err error
|
||||
}
|
||||
@@ -129,7 +114,7 @@ func (ctx *pushCtx) Error() error {
|
||||
func (ctx *pushCtx) reset() {
|
||||
ctx.Rows.Reset()
|
||||
ctx.Common.Reset(0)
|
||||
ctx.reqBuf.Reset()
|
||||
ctx.reqBuf = ctx.reqBuf[:0]
|
||||
ctx.tailBuf = ctx.tailBuf[:0]
|
||||
|
||||
ctx.err = nil
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timerpool"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
)
|
||||
|
||||
@@ -42,13 +43,14 @@ func Stop() {
|
||||
// RequestHandler handles remote read API requests for Prometheus
|
||||
func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
|
||||
// Limit the number of concurrent queries.
|
||||
// Sleep for a second until giving up. This should resolve short bursts in requests.
|
||||
t := time.NewTimer(*maxQueueDuration)
|
||||
// Sleep for a while until giving up. This should resolve short bursts in requests.
|
||||
t := timerpool.Get(*maxQueueDuration)
|
||||
select {
|
||||
case concurrencyCh <- struct{}{}:
|
||||
t.Stop()
|
||||
timerpool.Put(t)
|
||||
defer func() { <-concurrencyCh }()
|
||||
case <-t.C:
|
||||
timerpool.Put(t)
|
||||
httpserver.Errorf(w, "cannot handle more than %d concurrent requests", cap(concurrencyCh))
|
||||
return true
|
||||
}
|
||||
@@ -115,6 +117,15 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
|
||||
return true
|
||||
}
|
||||
return true
|
||||
case "/api/v1/labels/count":
|
||||
labelsCountRequests.Inc()
|
||||
httpserver.EnableCORS(w, r)
|
||||
if err := prometheus.LabelsCountHandler(w, r); err != nil {
|
||||
labelsCountErrors.Inc()
|
||||
sendPrometheusError(w, r, err)
|
||||
return true
|
||||
}
|
||||
return true
|
||||
case "/api/v1/export":
|
||||
exportRequests.Inc()
|
||||
if err := prometheus.ExportHandler(w, r); err != nil {
|
||||
@@ -178,6 +189,9 @@ var (
|
||||
labelsRequests = metrics.NewCounter(`vm_http_requests_total{path="/api/v1/labels"}`)
|
||||
labelsErrors = metrics.NewCounter(`vm_http_request_errors_total{path="/api/v1/labels"}`)
|
||||
|
||||
labelsCountRequests = metrics.NewCounter(`vm_http_requests_total{path="/api/v1/labels/count"}`)
|
||||
labelsCountErrors = metrics.NewCounter(`vm_http_request_errors_total{path="/api/v1/labels/count"}`)
|
||||
|
||||
deleteRequests = metrics.NewCounter(`vm_http_requests_total{path="/api/v1/admin/tsdb/delete_series"}`)
|
||||
deleteErrors = metrics.NewCounter(`vm_http_request_errors_total{path="/api/v1/admin/tsdb/delete_series"}`)
|
||||
|
||||
|
||||
@@ -400,6 +400,36 @@ func GetLabelValues(labelName string, deadline Deadline) ([]string, error) {
|
||||
return labelValues, nil
|
||||
}
|
||||
|
||||
// GetLabelEntries returns all the label entries until the given deadline.
|
||||
func GetLabelEntries(deadline Deadline) ([]storage.TagEntry, error) {
|
||||
labelEntries, err := vmstorage.SearchTagEntries(*maxTagKeysPerSearch, *maxTagValuesPerSearch)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error during label entries request: %s", err)
|
||||
}
|
||||
|
||||
// Substitute "" with "__name__"
|
||||
for i := range labelEntries {
|
||||
e := &labelEntries[i]
|
||||
if e.Key == "" {
|
||||
e.Key = "__name__"
|
||||
}
|
||||
}
|
||||
|
||||
// Sort labelEntries by the number of label values in each entry.
|
||||
sort.Slice(labelEntries, func(i, j int) bool {
|
||||
a, b := labelEntries[i].Values, labelEntries[j].Values
|
||||
if len(a) < len(b) {
|
||||
return true
|
||||
}
|
||||
if len(a) > len(b) {
|
||||
return false
|
||||
}
|
||||
return labelEntries[i].Key < labelEntries[j].Key
|
||||
})
|
||||
|
||||
return labelEntries, nil
|
||||
}
|
||||
|
||||
// GetSeriesCount returns the number of unique series.
|
||||
func GetSeriesCount(deadline Deadline) (uint64, error) {
|
||||
n, err := vmstorage.GetSeriesCount()
|
||||
|
||||
@@ -22,9 +22,7 @@ func InitTmpBlocksDir(tmpDirPath string) {
|
||||
tmpDirPath = os.TempDir()
|
||||
}
|
||||
tmpBlocksDir = tmpDirPath + "/searchResults"
|
||||
if err := os.RemoveAll(tmpBlocksDir); err != nil {
|
||||
logger.Panicf("FATAL: cannot remove %q: %s", tmpBlocksDir, err)
|
||||
}
|
||||
fs.MustRemoveAll(tmpBlocksDir)
|
||||
if err := fs.MkdirAllIfNotExist(tmpBlocksDir); err != nil {
|
||||
logger.Panicf("FATAL: cannot create %q: %s", tmpBlocksDir, err)
|
||||
}
|
||||
|
||||
17
app/vmselect/prometheus/labels_count_response.qtpl
Normal file
17
app/vmselect/prometheus/labels_count_response.qtpl
Normal file
@@ -0,0 +1,17 @@
|
||||
{% import "github.com/VictoriaMetrics/VictoriaMetrics/lib/storage" %}
|
||||
|
||||
{% stripspace %}
|
||||
LabelsCountResponse generates response for /api/v1/labels/count .
|
||||
{% func LabelsCountResponse(labelEntries []storage.TagEntry) %}
|
||||
{
|
||||
"status":"success",
|
||||
"data":{
|
||||
{% for i, e := range labelEntries %}
|
||||
{%q= e.Key %}:{%d= len(e.Values) %}
|
||||
{% if i+1 < len(labelEntries) %},{% endif %}
|
||||
{% endfor %}
|
||||
}
|
||||
}
|
||||
{% endfunc %}
|
||||
|
||||
{% endstripspace %}
|
||||
74
app/vmselect/prometheus/labels_count_response.qtpl.go
Normal file
74
app/vmselect/prometheus/labels_count_response.qtpl.go
Normal file
@@ -0,0 +1,74 @@
|
||||
// Code generated by qtc from "labels_count_response.qtpl". DO NOT EDIT.
|
||||
// See https://github.com/valyala/quicktemplate for details.
|
||||
|
||||
//line app/vmselect/prometheus/labels_count_response.qtpl:1
|
||||
package prometheus
|
||||
|
||||
//line app/vmselect/prometheus/labels_count_response.qtpl:1
|
||||
import "github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
|
||||
|
||||
// LabelsCountResponse generates response for /api/v1/labels/count .
|
||||
|
||||
//line app/vmselect/prometheus/labels_count_response.qtpl:5
|
||||
import (
|
||||
qtio422016 "io"
|
||||
|
||||
qt422016 "github.com/valyala/quicktemplate"
|
||||
)
|
||||
|
||||
//line app/vmselect/prometheus/labels_count_response.qtpl:5
|
||||
var (
|
||||
_ = qtio422016.Copy
|
||||
_ = qt422016.AcquireByteBuffer
|
||||
)
|
||||
|
||||
//line app/vmselect/prometheus/labels_count_response.qtpl:5
|
||||
func StreamLabelsCountResponse(qw422016 *qt422016.Writer, labelEntries []storage.TagEntry) {
|
||||
//line app/vmselect/prometheus/labels_count_response.qtpl:5
|
||||
qw422016.N().S(`{"status":"success","data":{`)
|
||||
//line app/vmselect/prometheus/labels_count_response.qtpl:9
|
||||
for i, e := range labelEntries {
|
||||
//line app/vmselect/prometheus/labels_count_response.qtpl:10
|
||||
qw422016.N().Q(e.Key)
|
||||
//line app/vmselect/prometheus/labels_count_response.qtpl:10
|
||||
qw422016.N().S(`:`)
|
||||
//line app/vmselect/prometheus/labels_count_response.qtpl:10
|
||||
qw422016.N().D(len(e.Values))
|
||||
//line app/vmselect/prometheus/labels_count_response.qtpl:11
|
||||
if i+1 < len(labelEntries) {
|
||||
//line app/vmselect/prometheus/labels_count_response.qtpl:11
|
||||
qw422016.N().S(`,`)
|
||||
//line app/vmselect/prometheus/labels_count_response.qtpl:11
|
||||
}
|
||||
//line app/vmselect/prometheus/labels_count_response.qtpl:12
|
||||
}
|
||||
//line app/vmselect/prometheus/labels_count_response.qtpl:12
|
||||
qw422016.N().S(`}}`)
|
||||
//line app/vmselect/prometheus/labels_count_response.qtpl:15
|
||||
}
|
||||
|
||||
//line app/vmselect/prometheus/labels_count_response.qtpl:15
|
||||
func WriteLabelsCountResponse(qq422016 qtio422016.Writer, labelEntries []storage.TagEntry) {
|
||||
//line app/vmselect/prometheus/labels_count_response.qtpl:15
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line app/vmselect/prometheus/labels_count_response.qtpl:15
|
||||
StreamLabelsCountResponse(qw422016, labelEntries)
|
||||
//line app/vmselect/prometheus/labels_count_response.qtpl:15
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line app/vmselect/prometheus/labels_count_response.qtpl:15
|
||||
}
|
||||
|
||||
//line app/vmselect/prometheus/labels_count_response.qtpl:15
|
||||
func LabelsCountResponse(labelEntries []storage.TagEntry) string {
|
||||
//line app/vmselect/prometheus/labels_count_response.qtpl:15
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line app/vmselect/prometheus/labels_count_response.qtpl:15
|
||||
WriteLabelsCountResponse(qb422016, labelEntries)
|
||||
//line app/vmselect/prometheus/labels_count_response.qtpl:15
|
||||
qs422016 := string(qb422016.B)
|
||||
//line app/vmselect/prometheus/labels_count_response.qtpl:15
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line app/vmselect/prometheus/labels_count_response.qtpl:15
|
||||
return qs422016
|
||||
//line app/vmselect/prometheus/labels_count_response.qtpl:15
|
||||
}
|
||||
@@ -37,9 +37,21 @@ func FederateHandler(w http.ResponseWriter, r *http.Request) error {
|
||||
return fmt.Errorf("cannot parse request form values: %s", err)
|
||||
}
|
||||
matches := r.Form["match[]"]
|
||||
maxLookback := getDuration(r, "max_lookback", defaultStep)
|
||||
start := getTime(r, "start", ct-maxLookback)
|
||||
end := getTime(r, "end", ct)
|
||||
if len(matches) == 0 {
|
||||
return fmt.Errorf("missing `match[]` arg")
|
||||
}
|
||||
maxLookback, err := getDuration(r, "max_lookback", defaultStep)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
start, err := getTime(r, "start", ct-maxLookback)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
end, err := getTime(r, "end", ct)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deadline := getDeadline(r)
|
||||
if start >= end {
|
||||
start = end - defaultStep
|
||||
@@ -97,10 +109,19 @@ func ExportHandler(w http.ResponseWriter, r *http.Request) error {
|
||||
if len(matches) == 0 {
|
||||
// Maintain backwards compatibility
|
||||
match := r.FormValue("match")
|
||||
if len(match) == 0 {
|
||||
return fmt.Errorf("missing `match[]` arg")
|
||||
}
|
||||
matches = []string{match}
|
||||
}
|
||||
start := getTime(r, "start", 0)
|
||||
end := getTime(r, "end", ct)
|
||||
start, err := getTime(r, "start", 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
end, err := getTime(r, "end", ct)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
format := r.FormValue("format")
|
||||
deadline := getDeadline(r)
|
||||
if start >= end {
|
||||
@@ -156,6 +177,11 @@ func exportHandler(w http.ResponseWriter, matches []string, start, end int64, fo
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
writeResponseFunc(w, resultsCh)
|
||||
|
||||
// Consume all the data from resultsCh in the event writeResponseFunc
|
||||
// fails to consume all the data.
|
||||
for bb := range resultsCh {
|
||||
quicktemplate.ReleaseByteBuffer(bb)
|
||||
}
|
||||
err = <-doneCh
|
||||
if err != nil {
|
||||
return fmt.Errorf("error during data fetching: %s", err)
|
||||
@@ -175,6 +201,9 @@ func DeleteHandler(r *http.Request) error {
|
||||
return fmt.Errorf("start and end aren't supported. Remove these args from the query in order to delete all the matching metrics")
|
||||
}
|
||||
matches := r.Form["match[]"]
|
||||
if len(matches) == 0 {
|
||||
return fmt.Errorf("missing `match[]` arg")
|
||||
}
|
||||
tagFilterss, err := getTagFilterssFromMatches(matches)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -214,6 +243,23 @@ func LabelValuesHandler(labelName string, w http.ResponseWriter, r *http.Request
|
||||
|
||||
var labelValuesDuration = metrics.NewSummary(`vm_request_duration_seconds{path="/api/v1/label/{}/values"}`)
|
||||
|
||||
// LabelsCountHandler processes /api/v1/labels/count request.
|
||||
func LabelsCountHandler(w http.ResponseWriter, r *http.Request) error {
|
||||
startTime := time.Now()
|
||||
deadline := getDeadline(r)
|
||||
labelEntries, err := netstorage.GetLabelEntries(deadline)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`cannot obtain label entries: %s`, err)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
WriteLabelsCountResponse(w, labelEntries)
|
||||
labelsCountDuration.UpdateDuration(startTime)
|
||||
return nil
|
||||
}
|
||||
|
||||
var labelsCountDuration = metrics.NewSummary(`vm_request_duration_seconds{path="/api/v1/labels/count"}`)
|
||||
|
||||
// LabelsHandler processes /api/v1/labels request.
|
||||
//
|
||||
// See https://prometheus.io/docs/prometheus/latest/querying/api/#getting-label-names
|
||||
@@ -260,8 +306,17 @@ func SeriesHandler(w http.ResponseWriter, r *http.Request) error {
|
||||
return fmt.Errorf("cannot parse form values: %s", err)
|
||||
}
|
||||
matches := r.Form["match[]"]
|
||||
start := getTime(r, "start", ct-defaultStep)
|
||||
end := getTime(r, "end", ct)
|
||||
if len(matches) == 0 {
|
||||
return fmt.Errorf("missing `match[]` arg")
|
||||
}
|
||||
start, err := getTime(r, "start", ct-defaultStep)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
end, err := getTime(r, "end", ct)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deadline := getDeadline(r)
|
||||
|
||||
tagFilterss, err := getTagFilterssFromMatches(matches)
|
||||
@@ -297,11 +352,10 @@ func SeriesHandler(w http.ResponseWriter, r *http.Request) error {
|
||||
WriteSeriesResponse(w, resultsCh)
|
||||
|
||||
// Consume all the data from resultsCh in the event WriteSeriesResponse
|
||||
// fail to consume all the data.
|
||||
// fails to consume all the data.
|
||||
for bb := range resultsCh {
|
||||
quicktemplate.ReleaseByteBuffer(bb)
|
||||
}
|
||||
|
||||
err = <-doneCh
|
||||
if err != nil {
|
||||
return fmt.Errorf("error during data fetching: %s", err)
|
||||
@@ -320,8 +374,17 @@ func QueryHandler(w http.ResponseWriter, r *http.Request) error {
|
||||
ct := currentTime()
|
||||
|
||||
query := r.FormValue("query")
|
||||
start := getTime(r, "time", ct)
|
||||
step := getDuration(r, "step", latencyOffset)
|
||||
if len(query) == 0 {
|
||||
return fmt.Errorf("missing `query` arg")
|
||||
}
|
||||
start, err := getTime(r, "time", ct)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
step, err := getDuration(r, "step", latencyOffset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deadline := getDeadline(r)
|
||||
|
||||
if len(query) > *maxQueryLen {
|
||||
@@ -384,9 +447,21 @@ func QueryRangeHandler(w http.ResponseWriter, r *http.Request) error {
|
||||
ct := currentTime()
|
||||
|
||||
query := r.FormValue("query")
|
||||
start := getTime(r, "start", ct-defaultStep)
|
||||
end := getTime(r, "end", ct)
|
||||
step := getDuration(r, "step", defaultStep)
|
||||
if len(query) == 0 {
|
||||
return fmt.Errorf("missing `query` arg")
|
||||
}
|
||||
start, err := getTime(r, "start", ct-defaultStep)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
end, err := getTime(r, "end", ct)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
step, err := getDuration(r, "step", defaultStep)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deadline := getDeadline(r)
|
||||
mayCache := !getBool(r, "nocache")
|
||||
|
||||
@@ -463,25 +538,25 @@ func adjustLastPoints(tss []netstorage.Result) {
|
||||
}
|
||||
}
|
||||
|
||||
func getTime(r *http.Request, argKey string, defaultValue int64) int64 {
|
||||
func getTime(r *http.Request, argKey string, defaultValue int64) (int64, error) {
|
||||
argValue := r.FormValue(argKey)
|
||||
if len(argValue) == 0 {
|
||||
return defaultValue
|
||||
return defaultValue, nil
|
||||
}
|
||||
secs, err := strconv.ParseFloat(argValue, 64)
|
||||
if err != nil {
|
||||
// Try parsing string format
|
||||
t, err := time.Parse(time.RFC3339, argValue)
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
return 0, fmt.Errorf("cannot parse %q=%q: %s", argKey, argValue, err)
|
||||
}
|
||||
secs = float64(t.UnixNano()) / 1e9
|
||||
}
|
||||
msecs := int64(secs * 1e3)
|
||||
if msecs < minTimeMsecs || msecs > maxTimeMsecs {
|
||||
return defaultValue
|
||||
return 0, fmt.Errorf("%q=%dms is out of allowed range [%d ... %d]", argKey, msecs, minTimeMsecs, maxTimeMsecs)
|
||||
}
|
||||
return msecs
|
||||
return msecs, nil
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -490,31 +565,34 @@ const (
|
||||
maxTimeMsecs = int64(1<<63-1) / 1e6
|
||||
)
|
||||
|
||||
func getDuration(r *http.Request, argKey string, defaultValue int64) int64 {
|
||||
func getDuration(r *http.Request, argKey string, defaultValue int64) (int64, error) {
|
||||
argValue := r.FormValue(argKey)
|
||||
if len(argValue) == 0 {
|
||||
return defaultValue
|
||||
return defaultValue, nil
|
||||
}
|
||||
secs, err := strconv.ParseFloat(argValue, 64)
|
||||
if err != nil {
|
||||
// Try parsing string format
|
||||
d, err := time.ParseDuration(argValue)
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
return 0, fmt.Errorf("cannot parse %q=%q: %s", argKey, argValue, err)
|
||||
}
|
||||
secs = d.Seconds()
|
||||
}
|
||||
msecs := int64(secs * 1e3)
|
||||
if msecs <= 0 || msecs > maxDurationMsecs {
|
||||
return defaultValue
|
||||
return 0, fmt.Errorf("%q=%dms is out of allowed range [%d ... %d]", argKey, msecs, 0, maxDurationMsecs)
|
||||
}
|
||||
return msecs
|
||||
return msecs, nil
|
||||
}
|
||||
|
||||
const maxDurationMsecs = 100 * 365 * 24 * 3600 * 1000
|
||||
|
||||
func getDeadline(r *http.Request) netstorage.Deadline {
|
||||
d := getDuration(r, "timeout", 0)
|
||||
d, err := getDuration(r, "timeout", 0)
|
||||
if err != nil {
|
||||
d = 0
|
||||
}
|
||||
dMax := int64(maxQueryDuration.Seconds() * 1e3)
|
||||
if d <= 0 || d > dMax {
|
||||
d = dMax
|
||||
|
||||
@@ -26,6 +26,8 @@ var aggrFuncs = map[string]aggrFunc{
|
||||
"median": aggrFuncMedian,
|
||||
"limitk": aggrFuncLimitK,
|
||||
"distinct": newAggrFunc(aggrFuncDistinct),
|
||||
"sum2": newAggrFunc(aggrFuncSum2),
|
||||
"geomean": newAggrFunc(aggrFuncGeomean),
|
||||
}
|
||||
|
||||
type aggrFunc func(afa *aggrFuncArg) ([]*timeseries, error)
|
||||
@@ -100,10 +102,18 @@ func aggrFuncExt(afe func(tss []*timeseries) []*timeseries, argOrig []*timeserie
|
||||
}
|
||||
bbPool.Put(bb)
|
||||
|
||||
srcTssCount := 0
|
||||
dstTssCount := 0
|
||||
rvs := make([]*timeseries, 0, len(m))
|
||||
for _, tss := range m {
|
||||
rv := afe(tss)
|
||||
rvs = append(rvs, rv...)
|
||||
srcTssCount += len(tss)
|
||||
dstTssCount += len(rv)
|
||||
if dstTssCount > 2000 && dstTssCount > 16*srcTssCount {
|
||||
// This looks like count_values explosion.
|
||||
return nil, fmt.Errorf(`too many timeseries after aggragation; got %d; want less than %d`, dstTssCount, 16*srcTssCount)
|
||||
}
|
||||
}
|
||||
return rvs, nil
|
||||
}
|
||||
@@ -132,6 +142,52 @@ func aggrFuncSum(tss []*timeseries) []*timeseries {
|
||||
return tss[:1]
|
||||
}
|
||||
|
||||
func aggrFuncSum2(tss []*timeseries) []*timeseries {
|
||||
dst := tss[0]
|
||||
for i := range dst.Values {
|
||||
sum2 := float64(0)
|
||||
count := 0
|
||||
for _, ts := range tss {
|
||||
v := ts.Values[i]
|
||||
if math.IsNaN(v) {
|
||||
continue
|
||||
}
|
||||
sum2 += v * v
|
||||
count++
|
||||
}
|
||||
if count == 0 {
|
||||
sum2 = nan
|
||||
}
|
||||
dst.Values[i] = sum2
|
||||
}
|
||||
return tss[:1]
|
||||
}
|
||||
|
||||
func aggrFuncGeomean(tss []*timeseries) []*timeseries {
|
||||
if len(tss) == 1 {
|
||||
// Fast path - nothing to geomean.
|
||||
return tss
|
||||
}
|
||||
dst := tss[0]
|
||||
for i := range dst.Values {
|
||||
p := 1.0
|
||||
count := 0
|
||||
for _, ts := range tss {
|
||||
v := ts.Values[i]
|
||||
if math.IsNaN(v) {
|
||||
continue
|
||||
}
|
||||
p *= v
|
||||
count++
|
||||
}
|
||||
if count == 0 {
|
||||
p = nan
|
||||
}
|
||||
dst.Values[i] = math.Pow(p, 1/float64(count))
|
||||
}
|
||||
return tss[:1]
|
||||
}
|
||||
|
||||
func aggrFuncMin(tss []*timeseries) []*timeseries {
|
||||
if len(tss) == 1 {
|
||||
// Fast path - nothing to min.
|
||||
@@ -301,6 +357,9 @@ func aggrFuncCountValues(afa *aggrFuncArg) ([]*timeseries, error) {
|
||||
m := make(map[float64]bool)
|
||||
for _, ts := range tss {
|
||||
for _, v := range ts.Values {
|
||||
if math.IsNaN(v) {
|
||||
continue
|
||||
}
|
||||
m[v] = true
|
||||
}
|
||||
}
|
||||
@@ -313,7 +372,7 @@ func aggrFuncCountValues(afa *aggrFuncArg) ([]*timeseries, error) {
|
||||
var rvs []*timeseries
|
||||
for _, v := range values {
|
||||
var dst timeseries
|
||||
dst.CopyFrom(tss[0])
|
||||
dst.CopyFromShallowTimestamps(tss[0])
|
||||
dst.MetricName.RemoveTag(dstLabel)
|
||||
dst.MetricName.AddTag(dstLabel, strconv.FormatFloat(v, 'g', -1, 64))
|
||||
for i := range dst.Values {
|
||||
@@ -457,6 +516,7 @@ func newAggrQuantileFunc(phis []float64) func(tss []*timeseries) []*timeseries {
|
||||
idx := int(math.Round(float64(len(tss)-1) * phi))
|
||||
dst.Values[n] = tss[idx].Values[n]
|
||||
}
|
||||
tss[0] = dst
|
||||
return tss[:1]
|
||||
}
|
||||
}
|
||||
|
||||
3
app/vmselect/promql/arch_arm64.go
Normal file
3
app/vmselect/promql/arch_arm64.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package promql
|
||||
|
||||
const maxByteSliceLen = 1 << 40
|
||||
@@ -271,7 +271,7 @@ func adjustBinaryOpTags(be *binaryOpExpr, left, right []*timeseries) ([]*timeser
|
||||
rvsLeft := make([]*timeseries, len(right))
|
||||
tsLeft := left[0]
|
||||
for i, tsRight := range right {
|
||||
tsRight.MetricName.ResetMetricGroup()
|
||||
resetMetricGroupIfRequired(be, tsRight)
|
||||
rvsLeft[i] = tsLeft
|
||||
}
|
||||
return rvsLeft, right, right, nil
|
||||
@@ -281,7 +281,7 @@ func adjustBinaryOpTags(be *binaryOpExpr, left, right []*timeseries) ([]*timeser
|
||||
rvsRight := make([]*timeseries, len(left))
|
||||
tsRight := right[0]
|
||||
for i, tsLeft := range left {
|
||||
tsLeft.MetricName.ResetMetricGroup()
|
||||
resetMetricGroupIfRequired(be, tsLeft)
|
||||
rvsRight[i] = tsRight
|
||||
}
|
||||
return left, rvsRight, left, nil
|
||||
@@ -296,8 +296,13 @@ func adjustBinaryOpTags(be *binaryOpExpr, left, right []*timeseries) ([]*timeser
|
||||
if len(tss) == 1 {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf(`duplicate timeseries on the %s side of %q: %s %s`, side, be.Op, stringMetricTags(&tss[0].MetricName), be.GroupModifier.AppendString(nil))
|
||||
if mergeNonOverlappingTimeseries(tss) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf(`duplicate timeseries on the %s side of %s %s: %s and %s`, side, be.Op, be.GroupModifier.AppendString(nil),
|
||||
stringMetricTags(&tss[0].MetricName), stringMetricTags(&tss[1].MetricName))
|
||||
}
|
||||
|
||||
var rvsLeft, rvsRight []*timeseries
|
||||
mLeft, mRight := createTimeseriesMapByTagSet(be, left, right)
|
||||
joinOp := strings.ToLower(be.JoinModifier.Op)
|
||||
@@ -335,7 +340,7 @@ func adjustBinaryOpTags(be *binaryOpExpr, left, right []*timeseries) ([]*timeser
|
||||
if err := ensureOneX("right", tssRight); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
tssLeft[0].MetricName.ResetMetricGroup()
|
||||
resetMetricGroupIfRequired(be, tssLeft[0])
|
||||
rvsLeft = append(rvsLeft, tssLeft[0])
|
||||
rvsRight = append(rvsRight, tssRight[0])
|
||||
default:
|
||||
@@ -349,6 +354,19 @@ func adjustBinaryOpTags(be *binaryOpExpr, left, right []*timeseries) ([]*timeser
|
||||
return rvsLeft, rvsRight, dst, nil
|
||||
}
|
||||
|
||||
func resetMetricGroupIfRequired(be *binaryOpExpr, ts *timeseries) {
|
||||
if isBinaryOpCmp(be.Op) && !be.Bool {
|
||||
// Do not reset MetricGroup for non-boolean `compare` binary ops like Prometheus does.
|
||||
return
|
||||
}
|
||||
switch be.Op {
|
||||
case "default", "if", "ifnot":
|
||||
// Do not reset MetricGroup for these ops.
|
||||
return
|
||||
}
|
||||
ts.MetricName.ResetMetricGroup()
|
||||
}
|
||||
|
||||
func binaryOpPlus(left, right float64) float64 {
|
||||
return left + right
|
||||
}
|
||||
@@ -497,3 +515,26 @@ func isScalar(arg []*timeseries) bool {
|
||||
}
|
||||
return len(mn.Tags) == 0
|
||||
}
|
||||
|
||||
func mergeNonOverlappingTimeseries(tss []*timeseries) bool {
|
||||
if len(tss) < 2 {
|
||||
logger.Panicf("BUG: expecting at least two timeseries. Got %d", len(tss))
|
||||
}
|
||||
|
||||
// Check whether time series in tss overlap.
|
||||
var dst timeseries
|
||||
dst.CopyFromShallowTimestamps(tss[0])
|
||||
dstValues := dst.Values
|
||||
for _, ts := range tss[1:] {
|
||||
for i, value := range ts.Values {
|
||||
if math.IsNaN(dstValues[i]) {
|
||||
dstValues[i] = value
|
||||
} else if !math.IsNaN(value) {
|
||||
// Time series overlap.
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
tss[0].CopyFromShallowTimestamps(&dst)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -16,10 +16,10 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
maxPointsPerTimeseries = flag.Int("search.maxPointsPerTimeseries", 10e3, "The maximum points per a single timeseries returned from the search")
|
||||
maxPointsPerTimeseries = flag.Int("search.maxPointsPerTimeseries", 30e3, "The maximum points per a single timeseries returned from the search")
|
||||
)
|
||||
|
||||
// The minumum number of points per timeseries for enabling time rounding.
|
||||
// The minimum number of points per timeseries for enabling time rounding.
|
||||
// This improves cache hit ratio for frequently requested queries over
|
||||
// big time ranges.
|
||||
const minTimeseriesPointsForTimeRounding = 50
|
||||
@@ -31,7 +31,7 @@ const minTimeseriesPointsForTimeRounding = 50
|
||||
func ValidateMaxPointsPerTimeseries(start, end, step int64) error {
|
||||
points := (end-start)/step + 1
|
||||
if uint64(points) > uint64(*maxPointsPerTimeseries) {
|
||||
return fmt.Errorf(`too many points for the given step=%d, start=%d and end=%d: %d; cannot exceed %d points`,
|
||||
return fmt.Errorf(`too many points for the given step=%d, start=%d and end=%d: %d; cannot exceed -search.maxPointsPerTimeseries=%d`,
|
||||
step, start, end, uint64(points), *maxPointsPerTimeseries)
|
||||
}
|
||||
return nil
|
||||
@@ -379,8 +379,7 @@ func evalRollupFuncWithSubquery(ec *EvalConfig, name string, rf rollupFunc, re *
|
||||
}
|
||||
|
||||
ecSQ := newEvalConfig(ec)
|
||||
ecSQ.Start -= window + maxSilenceInterval
|
||||
ecSQ.End += step
|
||||
ecSQ.Start -= window + maxSilenceInterval + step
|
||||
ecSQ.Step = step
|
||||
if err := ValidateMaxPointsPerTimeseries(ecSQ.Start, ecSQ.End, ecSQ.Step); err != nil {
|
||||
return nil, err
|
||||
@@ -472,22 +471,6 @@ func removeNanValues(dstValues []float64, dstTimestamps []int64, values []float6
|
||||
return dstValues, dstTimestamps
|
||||
}
|
||||
|
||||
func getMaxPointsPerRollup() int {
|
||||
maxPointsPerRollupOnce.Do(func() {
|
||||
n := memory.Allowed() / 16 / 8
|
||||
if n <= 16 {
|
||||
n = 16
|
||||
}
|
||||
maxPointsPerRollup = n
|
||||
})
|
||||
return maxPointsPerRollup
|
||||
}
|
||||
|
||||
var (
|
||||
maxPointsPerRollup int
|
||||
maxPointsPerRollupOnce sync.Once
|
||||
)
|
||||
|
||||
var (
|
||||
rollupResultCacheFullHits = metrics.NewCounter(`vm_rollup_result_cache_full_hits_total`)
|
||||
rollupResultCachePartialHits = metrics.NewCounter(`vm_rollup_result_cache_partial_hits_total`)
|
||||
@@ -533,12 +516,17 @@ func evalRollupFuncWithMetricExpr(ec *EvalConfig, name string, rf rollupFunc, me
|
||||
// Verify timeseries fit available memory after the rollup.
|
||||
// Take into account points from tssCached.
|
||||
pointsPerTimeseries := 1 + (ec.End-ec.Start)/ec.Step
|
||||
if uint64(pointsPerTimeseries) > uint64(getMaxPointsPerRollup()/rssLen/len(rcs)) {
|
||||
rollupPoints := mulNoOverflow(pointsPerTimeseries, int64(rssLen*len(rcs)))
|
||||
rollupMemorySize := mulNoOverflow(rollupPoints, 16)
|
||||
rml := getRollupMemoryLimiter()
|
||||
if !rml.Get(uint64(rollupMemorySize)) {
|
||||
rss.Cancel()
|
||||
return nil, fmt.Errorf("cannot process more than %d data points for %d time series with %d points in each time series; "+
|
||||
"possible solutions are: reducing the number of matching time series; switching to node with more RAM; increasing `step` query arg (%gs)",
|
||||
getMaxPointsPerRollup(), rssLen*len(rcs), pointsPerTimeseries, float64(ec.Step)/1e3)
|
||||
return nil, fmt.Errorf("not enough memory for processing %d data points across %d time series with %d points in each time series; "+
|
||||
"possible solutions are: reducing the number of matching time series; switching to node with more RAM; "+
|
||||
"increasing -memory.allowedPercent; increasing `step` query arg (%gs)",
|
||||
rollupPoints, rssLen*len(rcs), pointsPerTimeseries, float64(ec.Step)/1e3)
|
||||
}
|
||||
defer rml.Put(uint64(rollupMemorySize))
|
||||
|
||||
// Evaluate rollup
|
||||
tss := make([]*timeseries, 0, rssLen*len(rcs))
|
||||
@@ -575,6 +563,18 @@ func evalRollupFuncWithMetricExpr(ec *EvalConfig, name string, rf rollupFunc, me
|
||||
return tss, nil
|
||||
}
|
||||
|
||||
var (
|
||||
rollupMemoryLimiter memoryLimiter
|
||||
rollupMemoryLimiterOnce sync.Once
|
||||
)
|
||||
|
||||
func getRollupMemoryLimiter() *memoryLimiter {
|
||||
rollupMemoryLimiterOnce.Do(func() {
|
||||
rollupMemoryLimiter.MaxSize = uint64(memory.Allowed()) / 4
|
||||
})
|
||||
return &rollupMemoryLimiter
|
||||
}
|
||||
|
||||
func getRollupConfigs(name string, rf rollupFunc, start, end, step, window int64, sharedTimestamps []int64) (func(values []float64, timestamps []int64), []*rollupConfig) {
|
||||
preFunc := func(values []float64, timestamps []int64) {}
|
||||
if rollupFuncsRemoveCounterResets[name] {
|
||||
@@ -584,13 +584,14 @@ func getRollupConfigs(name string, rf rollupFunc, start, end, step, window int64
|
||||
}
|
||||
newRollupConfig := func(rf rollupFunc, tagValue string) *rollupConfig {
|
||||
return &rollupConfig{
|
||||
TagValue: tagValue,
|
||||
Func: rf,
|
||||
Start: start,
|
||||
End: end,
|
||||
Step: step,
|
||||
Window: window,
|
||||
Timestamps: sharedTimestamps,
|
||||
TagValue: tagValue,
|
||||
Func: rf,
|
||||
Start: start,
|
||||
End: end,
|
||||
Step: step,
|
||||
Window: window,
|
||||
MayAdjustWindow: rollupFuncsMayAdjustWindow[name],
|
||||
Timestamps: sharedTimestamps,
|
||||
}
|
||||
}
|
||||
appendRollupConfigs := func(dst []*rollupConfig) []*rollupConfig {
|
||||
@@ -653,3 +654,11 @@ func evalTime(ec *EvalConfig) []*timeseries {
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
func mulNoOverflow(a, b int64) int64 {
|
||||
if math.MaxInt64/b < a {
|
||||
// Overflow
|
||||
return math.MaxInt64
|
||||
}
|
||||
return a * b
|
||||
}
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
package promql
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/netstorage"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
)
|
||||
|
||||
var logSlowQueryDuration = flag.Duration("search.logSlowQueryDuration", 5*time.Second, "Log queries with execution time exceeding this value. Zero disables slow query logging")
|
||||
|
||||
// ExpandWithExprs expands WITH expressions inside q and returns the resulting
|
||||
// PromQL without WITH expressions.
|
||||
func ExpandWithExprs(q string) (string, error) {
|
||||
@@ -24,6 +29,16 @@ func ExpandWithExprs(q string) (string, error) {
|
||||
|
||||
// Exec executes q for the given ec until the deadline.
|
||||
func Exec(ec *EvalConfig, q string) ([]netstorage.Result, error) {
|
||||
if *logSlowQueryDuration > 0 {
|
||||
startTime := time.Now()
|
||||
defer func() {
|
||||
d := time.Since(startTime)
|
||||
if d >= *logSlowQueryDuration {
|
||||
logger.Infof("slow query: duration=%s, start=%d, end=%d, step=%d, query=%q", d, ec.Start/1000, ec.End/1000, ec.Step/1000, q)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
ec.validate()
|
||||
|
||||
e, err := parsePromQLWithCache(q)
|
||||
|
||||
@@ -286,7 +286,7 @@ func TestExecSuccess(t *testing.T) {
|
||||
q := `time()[300s:100s] offset 100s`
|
||||
r := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{800, 1000, 1200, 1400, 1600, 1800},
|
||||
Values: []float64{900, 1100, 1300, 1500, 1700, 1900},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
resultExpected := []netstorage.Result{r}
|
||||
@@ -297,7 +297,7 @@ func TestExecSuccess(t *testing.T) {
|
||||
q := `time()[1.5i:0.5i] offset 0.5i`
|
||||
r := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{800, 1000, 1200, 1400, 1600, 1800},
|
||||
Values: []float64{900, 1100, 1300, 1500, 1700, 1900},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
resultExpected := []netstorage.Result{r}
|
||||
@@ -308,7 +308,7 @@ func TestExecSuccess(t *testing.T) {
|
||||
q := `time()[300s] offset 100s`
|
||||
r := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{700, 900, 1100, 1300, 1500, 1700},
|
||||
Values: []float64{900, 1100, 1300, 1500, 1700, 1900},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
resultExpected := []netstorage.Result{r}
|
||||
@@ -319,7 +319,7 @@ func TestExecSuccess(t *testing.T) {
|
||||
q := `time()[300s]`
|
||||
r := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{800, 1000, 1200, 1400, 1600, 1800},
|
||||
Values: []float64{1000, 1200, 1400, 1600, 1800, 2000},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
resultExpected := []netstorage.Result{r}
|
||||
@@ -789,6 +789,18 @@ func TestExecSuccess(t *testing.T) {
|
||||
resultExpected := []netstorage.Result{}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`alias()`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `alias(time(), "foobar")`
|
||||
r := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{1000, 1200, 1400, 1600, 1800, 2000},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r.MetricName.MetricGroup = []byte("foobar")
|
||||
resultExpected := []netstorage.Result{r}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`label_set(tag)`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `label_set(time(), "tagname", "tagvalue")`
|
||||
@@ -1266,6 +1278,34 @@ func TestExecSuccess(t *testing.T) {
|
||||
resultExpected := []netstorage.Result{r}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`label_transform(mismatch)`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `label_transform(time(), "__name__", "foobar", "xx")`
|
||||
r := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{1000, 1200, 1400, 1600, 1800, 2000},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
resultExpected := []netstorage.Result{r}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`label_transform(match)`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `label_transform(
|
||||
label_set(time(), "foo", "a.bar.baz"),
|
||||
"foo", "\\.", "-")`
|
||||
r := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{1000, 1200, 1400, 1600, 1800, 2000},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r.MetricName.Tags = []storage.Tag{{
|
||||
Key: []byte("foo"),
|
||||
Value: []byte("a-bar-baz"),
|
||||
}}
|
||||
resultExpected := []netstorage.Result{r}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`label_replace(mismatch)`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `label_replace(time(), "__name__", "x${1}y", "foo", ".+")`
|
||||
@@ -1410,6 +1450,62 @@ func TestExecSuccess(t *testing.T) {
|
||||
resultExpected := []netstorage.Result{r1, r2}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`a cmp scalar (leave MetricGroup)`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `sort_desc((
|
||||
label_set(time(), "__name__", "foo", "a", "x"),
|
||||
label_set(time()+200, "__name__", "bar", "a", "x"),
|
||||
) > 1300)`
|
||||
r1 := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{nan, 1400, 1600, 1800, 2000, 2200},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r1.MetricName.MetricGroup = []byte("bar")
|
||||
r1.MetricName.Tags = []storage.Tag{{
|
||||
Key: []byte("a"),
|
||||
Value: []byte("x"),
|
||||
}}
|
||||
r2 := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{nan, nan, 1400, 1600, 1800, 2000},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r2.MetricName.MetricGroup = []byte("foo")
|
||||
r2.MetricName.Tags = []storage.Tag{{
|
||||
Key: []byte("a"),
|
||||
Value: []byte("x"),
|
||||
}}
|
||||
resultExpected := []netstorage.Result{r1, r2}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`a cmp bool scalar (drop MetricGroup)`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `sort_desc((
|
||||
label_set(time(), "__name__", "foo", "a", "x"),
|
||||
label_set(time()+200, "__name__", "bar", "a", "y"),
|
||||
) >= bool 1200)`
|
||||
r1 := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{1, 1, 1, 1, 1, 1},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r1.MetricName.Tags = []storage.Tag{{
|
||||
Key: []byte("a"),
|
||||
Value: []byte("y"),
|
||||
}}
|
||||
r2 := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{0, 1, 1, 1, 1, 1},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r2.MetricName.Tags = []storage.Tag{{
|
||||
Key: []byte("a"),
|
||||
Value: []byte("x"),
|
||||
}}
|
||||
resultExpected := []netstorage.Result{r1, r2}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`1 > 2`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `1 > 2`
|
||||
@@ -1512,13 +1608,14 @@ func TestExecSuccess(t *testing.T) {
|
||||
t.Run(`vector default scalar`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `sort_desc(union(
|
||||
label_set(time() > 1400, "foo", "bar"),
|
||||
label_set(time() < 1700, "foo", "baz")) default 123)`
|
||||
label_set(time() > 1400, "__name__", "x", "foo", "bar"),
|
||||
label_set(time() < 1700, "__name__", "y", "foo", "baz")) default 123)`
|
||||
r1 := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{123, 123, 123, 1600, 1800, 2000},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r1.MetricName.MetricGroup = []byte("x")
|
||||
r1.MetricName.Tags = []storage.Tag{{
|
||||
Key: []byte("foo"),
|
||||
Value: []byte("bar"),
|
||||
@@ -1528,6 +1625,7 @@ func TestExecSuccess(t *testing.T) {
|
||||
Values: []float64{1000, 1200, 1400, 1600, 123, 123},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r2.MetricName.MetricGroup = []byte("y")
|
||||
r2.MetricName.Tags = []storage.Tag{{
|
||||
Key: []byte("foo"),
|
||||
Value: []byte("baz"),
|
||||
@@ -1699,6 +1797,24 @@ func TestExecSuccess(t *testing.T) {
|
||||
resultExpected := []netstorage.Result{r}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`vector * on(foo) group_left() duplicate_timeseries`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `label_set(time()/10, "foo", "bar") + on(foo) group_left() (
|
||||
label_set(time() < 1400, "foo", "bar", "op", "le"),
|
||||
label_set(time() >= 1400, "foo", "bar", "op", "ge"),
|
||||
)`
|
||||
r1 := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{1100, 1320, 1540, 1760, 1980, 2200},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r1.MetricName.Tags = []storage.Tag{{
|
||||
Key: []byte("foo"),
|
||||
Value: []byte("bar"),
|
||||
}}
|
||||
resultExpected := []netstorage.Result{r1}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`vector * on() group_left scalar`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `sort_desc((label_set(time(), "foo", "bar") or label_set(10, "foo", "qwert")) * on() group_left 2)`
|
||||
@@ -2079,6 +2195,51 @@ func TestExecSuccess(t *testing.T) {
|
||||
resultExpected := []netstorage.Result{r}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`geomean(time)`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `geomean(time()/100)`
|
||||
r := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{10, 12, 14, 16, 18, 20},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
resultExpected := []netstorage.Result{r}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`geomean_over_time(time)`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `round(geomean_over_time(alias(time()/100, "foobar")[3i]), 0.1)`
|
||||
r := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{6.8, 8.8, 10.9, 12.9, 14.9, 16.9},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r.MetricName.MetricGroup = []byte("foobar")
|
||||
resultExpected := []netstorage.Result{r}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`sum2(time)`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `sum2(time()/100)`
|
||||
r := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{100, 144, 196, 256, 324, 400},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
resultExpected := []netstorage.Result{r}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`sum2_over_time(time)`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `sum2_over_time(alias(time()/100, "foobar")[3i])`
|
||||
r := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{155, 251, 371, 515, 683, 875},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
resultExpected := []netstorage.Result{r}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`sum(multi-vector)`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `sum(label_set(10, "foo", "bar") or label_set(time()/100, "baz", "sss"))`
|
||||
@@ -2090,6 +2251,39 @@ func TestExecSuccess(t *testing.T) {
|
||||
resultExpected := []netstorage.Result{r}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`geomean(multi-vector)`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `round(geomean(label_set(10, "foo", "bar") or label_set(time()/100, "baz", "sss")), 0.1)`
|
||||
r := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{10, 11, 11.8, 12.6, 13.4, 14.1},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
resultExpected := []netstorage.Result{r}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`sum2(multi-vector)`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `sum2(label_set(10, "foo", "bar") or label_set(time()/100, "baz", "sss"))`
|
||||
r := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{200, 244, 296, 356, 424, 500},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
resultExpected := []netstorage.Result{r}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`sqrt(sum2(multi-vector))`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `round(sqrt(sum2(label_set(10, "foo", "bar") or label_set(time()/100, "baz", "sss"))))`
|
||||
r := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{14, 16, 17, 19, 21, 22},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
resultExpected := []netstorage.Result{r}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`avg(multi-vector)`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `avg(label_set(10, "foo", "bar") or label_set(time()/100, "baz", "sss"))`
|
||||
@@ -2378,7 +2572,7 @@ func TestExecSuccess(t *testing.T) {
|
||||
q := `distinct_over_time((time() < 1700)[500s])`
|
||||
r1 := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{3, 3, 3, 2, 1, nan},
|
||||
Values: []float64{3, 3, 3, 3, 2, 1},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
resultExpected := []netstorage.Result{r1}
|
||||
@@ -2389,7 +2583,7 @@ func TestExecSuccess(t *testing.T) {
|
||||
q := `distinct_over_time((time() < 1700)[2.5i])`
|
||||
r1 := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{3, 3, 3, 2, 1, nan},
|
||||
Values: []float64{3, 3, 3, 3, 2, 1},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
resultExpected := []netstorage.Result{r1}
|
||||
@@ -2645,7 +2839,7 @@ func TestExecSuccess(t *testing.T) {
|
||||
q := `integrate(time()*1e-3)`
|
||||
r := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{200, 240.00000000000003, 280, 320, 360, 400},
|
||||
Values: []float64{160, 200, 240.00000000000003, 280, 320, 360},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
resultExpected := []netstorage.Result{r}
|
||||
@@ -2667,7 +2861,7 @@ func TestExecSuccess(t *testing.T) {
|
||||
q := `rate(2000-time())`
|
||||
r := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{4.5, 3.5, 2.5, 1.5, 0.5, -0.5},
|
||||
Values: []float64{5.5, 4.5, 3.5, 2.5, 1.5, 0.5},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
resultExpected := []netstorage.Result{r}
|
||||
@@ -2678,7 +2872,7 @@ func TestExecSuccess(t *testing.T) {
|
||||
q := `rate((2000-time())[100s])`
|
||||
r := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{4.5, 3.5, 2.5, 1.5, 0.5, -0.5},
|
||||
Values: []float64{5.5, 4.5, 3.5, 2.5, 1.5, 0.5},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
resultExpected := []netstorage.Result{r}
|
||||
@@ -2689,7 +2883,7 @@ func TestExecSuccess(t *testing.T) {
|
||||
q := `rate((2000-time())[100s:])`
|
||||
r := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{4.5, 3.5, 2.5, 1.5, 0.5, -0.5},
|
||||
Values: []float64{5.5, 4.5, 3.5, 2.5, 1.5, 0.5},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
resultExpected := []netstorage.Result{r}
|
||||
@@ -2700,7 +2894,7 @@ func TestExecSuccess(t *testing.T) {
|
||||
q := `rate((2000-time())[100s:100s])`
|
||||
r := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{4, 6.5, 4.5, 2.5, 0.5, -1.5},
|
||||
Values: []float64{5.5, 4.5, 6.5, 4.5, 2.5, 0.5},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
resultExpected := []netstorage.Result{r}
|
||||
@@ -2711,7 +2905,7 @@ func TestExecSuccess(t *testing.T) {
|
||||
q := `rate((2000-time())[100s:100s] offset 100s)`
|
||||
r := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{4.5, 3.5, 5.5, 3.5, 1.5, -0.5},
|
||||
Values: []float64{6, 5, 7.5, 5.5, 3.5, 1.5},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
resultExpected := []netstorage.Result{r}
|
||||
@@ -2722,7 +2916,7 @@ func TestExecSuccess(t *testing.T) {
|
||||
q := `rate((2000-time())[100s:100s] offset 100s)[:] offset 100s`
|
||||
r := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{6, 5, 7.5, 5.5, 3.5, 1.5},
|
||||
Values: []float64{7, 6, 5, 7.5, 5.5, 3.5},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
resultExpected := []netstorage.Result{r}
|
||||
@@ -2744,7 +2938,7 @@ func TestExecSuccess(t *testing.T) {
|
||||
q := `increase(2000-time())`
|
||||
r := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{900, 700, 500, 300, 100, -100},
|
||||
Values: []float64{1100, 900, 700, 500, 300, 100},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
resultExpected := []netstorage.Result{r}
|
||||
@@ -3047,7 +3241,7 @@ func TestExecSuccess(t *testing.T) {
|
||||
q := `sort(rollup(time()[:50s]))`
|
||||
r1 := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{1050, 1250, 1450, 1650, 1850, 2050},
|
||||
Values: []float64{850, 1050, 1250, 1450, 1650, 1850},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r1.MetricName.Tags = []storage.Tag{{
|
||||
@@ -3056,21 +3250,21 @@ func TestExecSuccess(t *testing.T) {
|
||||
}}
|
||||
r2 := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{1200, 1400, 1600, 1800, 2000, 2200},
|
||||
Values: []float64{925, 1125, 1325, 1525, 1725, 1925},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r2.MetricName.Tags = []storage.Tag{{
|
||||
Key: []byte("rollup"),
|
||||
Value: []byte("max"),
|
||||
Value: []byte("avg"),
|
||||
}}
|
||||
r3 := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{1125, 1325, 1525, 1725, 1925, 2125},
|
||||
Values: []float64{1000, 1200, 1400, 1600, 1800, 2000},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r3.MetricName.Tags = []storage.Tag{{
|
||||
Key: []byte("rollup"),
|
||||
Value: []byte("avg"),
|
||||
Value: []byte("max"),
|
||||
}}
|
||||
resultExpected := []netstorage.Result{r1, r2, r3}
|
||||
f(q, resultExpected)
|
||||
@@ -3461,6 +3655,7 @@ func TestExecError(t *testing.T) {
|
||||
f(`hour(1,2)`)
|
||||
f(`label_join()`)
|
||||
f(`label_replace(1)`)
|
||||
f(`label_transform(1)`)
|
||||
f(`label_set()`)
|
||||
f(`label_set(1, "foo")`)
|
||||
f(`label_del()`)
|
||||
@@ -3506,6 +3701,9 @@ func TestExecError(t *testing.T) {
|
||||
f(`keep_last_value()`)
|
||||
f(`distinct_over_time()`)
|
||||
f(`distinct()`)
|
||||
f(`alias()`)
|
||||
f(`alias(1)`)
|
||||
f(`alias(1, "foo", "bar")`)
|
||||
|
||||
// Invalid argument type
|
||||
f(`median_over_time({}, 2)`)
|
||||
@@ -3535,6 +3733,11 @@ func TestExecError(t *testing.T) {
|
||||
f(`label_replace(1, "foo", "bar", 4, 5)`)
|
||||
f(`label_replace(1, "foo", "bar", "baz", 5)`)
|
||||
f(`label_replace(1, "foo", "bar", "baz", "invalid(regexp")`)
|
||||
f(`label_transform(1, 2, 3, 4)`)
|
||||
f(`label_transform(1, "foo", 3, 4)`)
|
||||
f(`label_transform(1, "foo", "bar", 4)`)
|
||||
f(`label_transform(1, "foo", "invalid(regexp", "baz`)
|
||||
f(`alias(1, 2)`)
|
||||
|
||||
// Duplicate timeseries
|
||||
f(`(label_set(1, "foo", "bar") or label_set(2, "foo", "baz"))
|
||||
@@ -3545,12 +3748,24 @@ func TestExecError(t *testing.T) {
|
||||
f(`1 + group_left() (label_set(1, "foo", bar"), label_set(2, "foo", "baz"))`)
|
||||
f(`1 + on() group_left() (label_set(1, "foo", bar"), label_set(2, "foo", "baz"))`)
|
||||
f(`1 + on(a) group_left(b) (label_set(1, "foo", bar"), label_set(2, "foo", "baz"))`)
|
||||
f(`label_set(1, "foo", "bar") + on(foo) group_left() (label_set(1, "foo", "bar", "a", "b"), label_set(1, "foo", "bar", "a", "c"))`)
|
||||
f(`(label_set(1, "foo", bar"), label_set(2, "foo", "baz")) + group_right 1`)
|
||||
f(`(label_set(1, "foo", bar"), label_set(2, "foo", "baz")) + on() group_right 1`)
|
||||
f(`(label_set(1, "foo", bar"), label_set(2, "foo", "baz")) + on(a) group_right(b,c) 1`)
|
||||
f(`(label_set(1, "foo", bar"), label_set(2, "foo", "baz")) + on() 1`)
|
||||
f(`(label_set(1, "foo", "bar", "a", "b"), label_set(1, "foo", "bar", "a", "c")) + on(foo) group_right() label_set(1, "foo", "bar")`)
|
||||
f(`1 + on() (label_set(1, "foo", bar"), label_set(2, "foo", "baz"))`)
|
||||
|
||||
// duplicate metrics after binary op
|
||||
f(`(
|
||||
label_set(time(), "__name__", "foo", "a", "x"),
|
||||
label_set(time()+200, "__name__", "bar", "a", "x"),
|
||||
) > bool 1300`)
|
||||
f(`(
|
||||
label_set(time(), "__name__", "foo", "a", "x"),
|
||||
label_set(time()+200, "__name__", "bar", "a", "x"),
|
||||
) + 10`)
|
||||
|
||||
// With expressions
|
||||
f(`ttf()`)
|
||||
f(`ttf(1, 2)`)
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
)
|
||||
|
||||
type lexer struct {
|
||||
@@ -85,10 +87,7 @@ again:
|
||||
goto tokenFoundLabel
|
||||
}
|
||||
if isIdentPrefix(s) {
|
||||
token, err = scanIdent(s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
token = scanIdent(s)
|
||||
goto tokenFoundLabel
|
||||
}
|
||||
if isStringPrefix(s) {
|
||||
@@ -210,15 +209,103 @@ func scanPositiveNumber(s string) (string, error) {
|
||||
return s[:j], nil
|
||||
}
|
||||
|
||||
func scanIdent(s string) (string, error) {
|
||||
if len(s) == 0 {
|
||||
return "", fmt.Errorf("ident cannot be empty")
|
||||
}
|
||||
func scanIdent(s string) string {
|
||||
i := 0
|
||||
for i < len(s) && isIdentChar(s[i]) {
|
||||
i++
|
||||
for i < len(s) {
|
||||
if isIdentChar(s[i]) {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if s[i] != '\\' {
|
||||
break
|
||||
}
|
||||
|
||||
// Do not verify the next char, since it is escaped.
|
||||
i += 2
|
||||
if i > len(s) {
|
||||
i--
|
||||
break
|
||||
}
|
||||
}
|
||||
return s[:i], nil
|
||||
if i == 0 {
|
||||
logger.Panicf("BUG: scanIdent couldn't find a single ident char; make sure isIdentPrefix called before scanIdent")
|
||||
}
|
||||
return s[:i]
|
||||
}
|
||||
|
||||
func unescapeIdent(s string) string {
|
||||
n := strings.IndexByte(s, '\\')
|
||||
if n < 0 {
|
||||
return s
|
||||
}
|
||||
dst := make([]byte, 0, len(s))
|
||||
for {
|
||||
dst = append(dst, s[:n]...)
|
||||
s = s[n+1:]
|
||||
if len(s) == 0 {
|
||||
return string(dst)
|
||||
}
|
||||
if s[0] == 'x' && len(s) >= 3 {
|
||||
h1 := fromHex(s[1])
|
||||
h2 := fromHex(s[2])
|
||||
if h1 >= 0 && h2 >= 0 {
|
||||
dst = append(dst, byte((h1<<4)|h2))
|
||||
s = s[3:]
|
||||
} else {
|
||||
dst = append(dst, s[0])
|
||||
s = s[1:]
|
||||
}
|
||||
} else {
|
||||
dst = append(dst, s[0])
|
||||
s = s[1:]
|
||||
}
|
||||
n = strings.IndexByte(s, '\\')
|
||||
if n < 0 {
|
||||
dst = append(dst, s...)
|
||||
return string(dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fromHex(ch byte) int {
|
||||
if ch >= '0' && ch <= '9' {
|
||||
return int(ch - '0')
|
||||
}
|
||||
if ch >= 'a' && ch <= 'f' {
|
||||
return int((ch - 'a') + 10)
|
||||
}
|
||||
if ch >= 'A' && ch <= 'F' {
|
||||
return int((ch - 'A') + 10)
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func toHex(n byte) byte {
|
||||
if n < 10 {
|
||||
return '0' + n
|
||||
}
|
||||
return 'a' + (n - 10)
|
||||
}
|
||||
|
||||
func appendEscapedIdent(dst, s []byte) []byte {
|
||||
for i := 0; i < len(s); i++ {
|
||||
ch := s[i]
|
||||
if isIdentChar(ch) {
|
||||
if i == 0 && !isFirstIdentChar(ch) {
|
||||
// hex-encode the first char
|
||||
dst = append(dst, '\\', 'x', toHex(ch>>4), toHex(ch&0xf))
|
||||
} else {
|
||||
dst = append(dst, ch)
|
||||
}
|
||||
} else if ch >= 0x20 && ch < 0x7f {
|
||||
// Leave ASCII printable chars as is
|
||||
dst = append(dst, '\\', ch)
|
||||
} else {
|
||||
// hex-encode non-printable chars
|
||||
dst = append(dst, '\\', 'x', toHex(ch>>4), toHex(ch&0xf))
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func (lex *lexer) Prev() {
|
||||
@@ -353,6 +440,10 @@ func isIdentPrefix(s string) bool {
|
||||
if len(s) == 0 {
|
||||
return false
|
||||
}
|
||||
if s[0] == '\\' {
|
||||
// Assume this is an escape char for the next char.
|
||||
return true
|
||||
}
|
||||
return isFirstIdentChar(s[0])
|
||||
}
|
||||
|
||||
@@ -367,7 +458,7 @@ func isIdentChar(ch byte) bool {
|
||||
if isFirstIdentChar(ch) {
|
||||
return true
|
||||
}
|
||||
return isDecimalChar(ch) || ch == ':' || ch == '.'
|
||||
return isDecimalChar(ch) || ch == '.'
|
||||
}
|
||||
|
||||
func isSpaceChar(ch byte) bool {
|
||||
|
||||
@@ -5,6 +5,57 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUnescapeIdent(t *testing.T) {
|
||||
f := func(s, resultExpected string) {
|
||||
t.Helper()
|
||||
result := unescapeIdent(s)
|
||||
if result != resultExpected {
|
||||
t.Fatalf("unexpected result for unescapeIdent(%q); got %q; want %q", s, result, resultExpected)
|
||||
}
|
||||
}
|
||||
f("", "")
|
||||
f("a", "a")
|
||||
f("\\", "")
|
||||
f(`\\`, `\`)
|
||||
f(`\foo\-bar`, `foo-bar`)
|
||||
f(`a\\\\b\"c\d`, `a\\b"cd`)
|
||||
f(`foo.bar:baz_123`, `foo.bar:baz_123`)
|
||||
f(`foo\ bar`, `foo bar`)
|
||||
f(`\x21`, `!`)
|
||||
f(`\xeDfoo\x2Fbar\-\xqw\x`, "\xedfoo\x2fbar-xqwx")
|
||||
}
|
||||
|
||||
func TestAppendEscapedIdent(t *testing.T) {
|
||||
f := func(s, resultExpected string) {
|
||||
t.Helper()
|
||||
result := appendEscapedIdent(nil, []byte(s))
|
||||
if string(result) != resultExpected {
|
||||
t.Fatalf("unexpected result for appendEscapedIdent(%q); got %q; want %q", s, result, resultExpected)
|
||||
}
|
||||
}
|
||||
f(`a`, `a`)
|
||||
f(`a.b:c_23`, `a.b:c_23`)
|
||||
f(`a b-cd+dd\`, `a\ b\-cd\+dd\\`)
|
||||
f("a\x1E\x20\xee", `a\x1e\ \xee`)
|
||||
f("\x2e\x2e", `\x2e.`)
|
||||
}
|
||||
|
||||
func TestScanIdent(t *testing.T) {
|
||||
f := func(s, resultExpected string) {
|
||||
t.Helper()
|
||||
result := scanIdent(s)
|
||||
if result != resultExpected {
|
||||
t.Fatalf("unexpected result for scanIdent(%q): got %q; want %q", s, result, resultExpected)
|
||||
}
|
||||
}
|
||||
f("a", "a")
|
||||
f("foo.bar:baz_123", "foo.bar:baz_123")
|
||||
f("a+b", "a")
|
||||
f("foo()", "foo")
|
||||
f(`a\-b+c`, `a\-b`)
|
||||
f(`a\ b\\\ c\`, `a\ b\\\ c\`)
|
||||
}
|
||||
|
||||
func TestLexerNextPrev(t *testing.T) {
|
||||
var lex lexer
|
||||
lex.Init("foo bar baz")
|
||||
|
||||
33
app/vmselect/promql/memory_limiter.go
Normal file
33
app/vmselect/promql/memory_limiter.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package promql
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
)
|
||||
|
||||
type memoryLimiter struct {
|
||||
MaxSize uint64
|
||||
|
||||
mu sync.Mutex
|
||||
usage uint64
|
||||
}
|
||||
|
||||
func (ml *memoryLimiter) Get(n uint64) bool {
|
||||
ml.mu.Lock()
|
||||
ok := n <= ml.MaxSize && ml.MaxSize-n >= ml.usage
|
||||
if ok {
|
||||
ml.usage += n
|
||||
}
|
||||
ml.mu.Unlock()
|
||||
return ok
|
||||
}
|
||||
|
||||
func (ml *memoryLimiter) Put(n uint64) {
|
||||
ml.mu.Lock()
|
||||
if n > ml.usage {
|
||||
logger.Panicf("BUG: n=%d cannot exceed %d", n, ml.usage)
|
||||
}
|
||||
ml.usage -= n
|
||||
ml.mu.Unlock()
|
||||
}
|
||||
56
app/vmselect/promql/memory_limiter_test.go
Normal file
56
app/vmselect/promql/memory_limiter_test.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package promql
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMemoryLimiter(t *testing.T) {
|
||||
var ml memoryLimiter
|
||||
ml.MaxSize = 100
|
||||
|
||||
// Allocate memory
|
||||
if !ml.Get(10) {
|
||||
t.Fatalf("cannot get 10 out of %d bytes", ml.MaxSize)
|
||||
}
|
||||
if ml.usage != 10 {
|
||||
t.Fatalf("unexpected usage; got %d; want %d", ml.usage, 10)
|
||||
}
|
||||
if !ml.Get(20) {
|
||||
t.Fatalf("cannot get 20 out of 90 bytes")
|
||||
}
|
||||
if ml.usage != 30 {
|
||||
t.Fatalf("unexpected usage; got %d; want %d", ml.usage, 30)
|
||||
}
|
||||
if ml.Get(1000) {
|
||||
t.Fatalf("unexpected get for 1000 bytes")
|
||||
}
|
||||
if ml.usage != 30 {
|
||||
t.Fatalf("unexpected usage; got %d; want %d", ml.usage, 30)
|
||||
}
|
||||
if ml.Get(71) {
|
||||
t.Fatalf("unexpected get for 71 bytes")
|
||||
}
|
||||
if ml.usage != 30 {
|
||||
t.Fatalf("unexpected usage; got %d; want %d", ml.usage, 30)
|
||||
}
|
||||
if !ml.Get(70) {
|
||||
t.Fatalf("cannot get 70 bytes")
|
||||
}
|
||||
if ml.usage != 100 {
|
||||
t.Fatalf("unexpected usage; got %d; want %d", ml.usage, 100)
|
||||
}
|
||||
|
||||
// Return memory back
|
||||
ml.Put(10)
|
||||
ml.Put(70)
|
||||
if ml.usage != 20 {
|
||||
t.Fatalf("unexpected usage; got %d; want %d", ml.usage, 20)
|
||||
}
|
||||
if !ml.Get(30) {
|
||||
t.Fatalf("cannot get 30 bytes")
|
||||
}
|
||||
ml.Put(50)
|
||||
if ml.usage != 0 {
|
||||
t.Fatalf("unexpected usage; got %d; want %d", ml.usage, 0)
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
|
||||
)
|
||||
@@ -19,12 +18,13 @@ func getDefaultWithArgExprs() []*withArgExpr {
|
||||
|
||||
// ttf - time to fuckup
|
||||
`ttf(freev) = smooth_exponential(
|
||||
clamp_max(clamp_min(freev, 0) / clamp_min(deriv(-freev), 0), 365*24*3600),
|
||||
clamp_max(clamp_max(-freev, 0) / clamp_max(deriv_fast(freev), 0), 365*24*3600),
|
||||
clamp_max(step()/300, 1)
|
||||
)`,
|
||||
|
||||
`median_over_time(m) = quantile_over_time(0.5, m)`,
|
||||
`range_median(q) = range_quantile(0.5, q)`,
|
||||
`alias(q, name) = label_set(q, "__name__", name)`,
|
||||
})
|
||||
})
|
||||
return defaultWithArgExprs
|
||||
@@ -744,7 +744,7 @@ func expandWithExpr(was []*withArgExpr, e expr) (expr, error) {
|
||||
if !t.HasNonEmptyMetricGroup() {
|
||||
return t, nil
|
||||
}
|
||||
k := bytesutil.ToUnsafeString(t.TagFilters[0].Value)
|
||||
k := string(appendEscapedIdent(nil, t.TagFilters[0].Value))
|
||||
wa := getWithArgExpr(was, k)
|
||||
if wa == nil {
|
||||
return t, nil
|
||||
@@ -811,7 +811,9 @@ func expandModifierArgs(was []*withArgExpr, args []string) ([]string, error) {
|
||||
continue
|
||||
}
|
||||
if len(wa.Args) > 0 {
|
||||
return nil, fmt.Errorf("cannot use func %q instead of %q in %s", wa.Name, arg, args)
|
||||
// Template funcs cannot be used inside modifier list. Leave the arg as is.
|
||||
dstArgs = append(dstArgs, arg)
|
||||
continue
|
||||
}
|
||||
me, ok := wa.Expr.(*metricExpr)
|
||||
if ok {
|
||||
@@ -851,6 +853,10 @@ func expandModifierArgs(was []*withArgExpr, args []string) ([]string, error) {
|
||||
|
||||
func expandWithExprExt(was []*withArgExpr, wa *withArgExpr, args []expr) (expr, error) {
|
||||
if len(wa.Args) != len(args) {
|
||||
if args == nil {
|
||||
// Just return metricExpr with the wa.Name name.
|
||||
return newMetricExpr(wa.Name), nil
|
||||
}
|
||||
return nil, fmt.Errorf("invalid number of args for %q; got %d; want %d", wa.Name, len(args), len(wa.Args))
|
||||
}
|
||||
wasNew := make([]*withArgExpr, 0, len(was)+len(args))
|
||||
@@ -869,6 +875,14 @@ func expandWithExprExt(was []*withArgExpr, wa *withArgExpr, args []expr) (expr,
|
||||
return expandWithExpr(wasNew, wa.Expr)
|
||||
}
|
||||
|
||||
func newMetricExpr(name string) *metricExpr {
|
||||
return &metricExpr{
|
||||
TagFilters: []storage.TagFilter{{
|
||||
Value: []byte(name),
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
func extractStringValue(token string) (string, error) {
|
||||
if !isStringPrefix(token) {
|
||||
return "", fmt.Errorf(`stringExpr must contain only string literals; got %q`, token)
|
||||
@@ -1074,9 +1088,6 @@ func (p *parser) parseTagFilterExpr() (*tagFilterExpr, error) {
|
||||
}
|
||||
var tfe tagFilterExpr
|
||||
tfe.Key = p.lex.Token
|
||||
if tfe.Key == "__name__" {
|
||||
tfe.Key = ""
|
||||
}
|
||||
if err := p.lex.Next(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1125,8 +1136,16 @@ func (tfe *tagFilterExpr) toTagFilter() (*storage.TagFilter, error) {
|
||||
}
|
||||
|
||||
var tf storage.TagFilter
|
||||
tf.Key = []byte(tfe.Key)
|
||||
tf.Value = []byte(tfe.Value.S)
|
||||
tf.Key = []byte(unescapeIdent(tfe.Key))
|
||||
if len(tfe.Key) == 0 {
|
||||
tf.Value = []byte(unescapeIdent(tfe.Value.S))
|
||||
} else {
|
||||
tf.Value = []byte(tfe.Value.S)
|
||||
}
|
||||
if string(tf.Key) == "__name__" {
|
||||
// This is required for storage.Search
|
||||
tf.Key = nil
|
||||
}
|
||||
tf.IsRegexp = tfe.IsRegexp
|
||||
tf.IsNegative = tfe.IsNegative
|
||||
if !tf.IsRegexp {
|
||||
@@ -1507,7 +1526,7 @@ func (wa *withArgExpr) AppendString(dst []byte) []byte {
|
||||
}
|
||||
|
||||
type rollupExpr struct {
|
||||
// The expression for the rollup. Usually it is metricExpr, but may be arbitary expr
|
||||
// The expression for the rollup. Usually it is metricExpr, but may be arbitrary expr
|
||||
// if subquery is used. https://prometheus.io/blog/2019/01/28/subquery-support/
|
||||
Expr expr
|
||||
|
||||
@@ -1585,7 +1604,7 @@ func (me *metricExpr) AppendString(dst []byte) []byte {
|
||||
if len(tfs) > 0 {
|
||||
tf := &tfs[0]
|
||||
if len(tf.Key) == 0 && !tf.IsNegative && !tf.IsRegexp {
|
||||
dst = append(dst, tf.Value...)
|
||||
dst = appendEscapedIdent(dst, tf.Value)
|
||||
tfs = tfs[1:]
|
||||
}
|
||||
}
|
||||
@@ -1627,7 +1646,7 @@ func appendStringTagFilter(dst []byte, tf *storage.TagFilter) []byte {
|
||||
if len(tf.Key) == 0 {
|
||||
dst = append(dst, "__name__"...)
|
||||
} else {
|
||||
dst = append(dst, tf.Key...)
|
||||
dst = appendEscapedIdent(dst, tf.Key)
|
||||
}
|
||||
var op string
|
||||
if tf.IsNegative {
|
||||
|
||||
@@ -118,6 +118,17 @@ func TestParsePromQLSuccess(t *testing.T) {
|
||||
same("with")
|
||||
same("WITH")
|
||||
same("With")
|
||||
same("alias")
|
||||
same(`alias{foo="bar"}`)
|
||||
same(`aLIas{alias="aa"}`)
|
||||
another(`al\ias`, `alias`)
|
||||
// identifiers with with escape chars
|
||||
same(`foo\ bar`)
|
||||
same(`foo\-bar\{{baz\+bar="aa"}`)
|
||||
another(`\x2E\x2ef\oo{b\xEF\ar="aa"}`, `\x2e.foo{b\xefar="aa"}`)
|
||||
// Duplicate filters
|
||||
same(`foo{__name__="bar"}`)
|
||||
same(`foo{a="b", a="c", __name__="aaa", b="d"}`)
|
||||
// Metric filters ending with comma
|
||||
another(`m{foo="bar",}`, `m{foo="bar"}`)
|
||||
// String concat in tag value
|
||||
@@ -251,6 +262,8 @@ func TestParsePromQLSuccess(t *testing.T) {
|
||||
same(`rate(rate(m[5m]))`)
|
||||
same(`rate(rate(m[5m])[1h:])`)
|
||||
same(`rate(rate(m[5m])[1h:3s])`)
|
||||
// funcName with escape chars
|
||||
same(`foo\(ba\-r()`)
|
||||
|
||||
// aggrFuncExpr
|
||||
same(`sum(http_server_request) by ()`)
|
||||
@@ -295,10 +308,14 @@ func TestParsePromQLSuccess(t *testing.T) {
|
||||
another(`with (ct={job="test", i="bar"}) ct + {ct, x="d"} + foo{ct, ct} + ctx(1)`,
|
||||
`(({job="test", i="bar"} + {job="test", i="bar", x="d"}) + foo{job="test", i="bar"}) + ctx(1)`)
|
||||
another(`with (foo = bar) {__name__=~"foo"}`, `{__name__=~"foo"}`)
|
||||
another(`with (foo = bar) {__name__="foo"}`, `bar`)
|
||||
another(`with (foo = bar) foo{__name__="foo"}`, `bar`)
|
||||
another(`with (foo = bar) {__name__="foo", x="y"}`, `bar{x="y"}`)
|
||||
another(`with (foo(bar) = {__name__!="bar"}) foo(x)`, `{__name__!="bar"}`)
|
||||
another(`with (foo(bar) = {__name__="bar"}) foo(x)`, `x`)
|
||||
another(`with (foo(bar) = bar{__name__="bar"}) foo(x)`, `x`)
|
||||
another(`with (foo\-bar(baz) = baz + baz) foo\-bar((x,y))`, `(x, y) + (x, y)`)
|
||||
another(`with (foo\-bar(baz) = baz + baz) foo\-bar(x*y)`, `(x * y) + (x * y)`)
|
||||
another(`with (foo\-bar(baz) = baz + baz) foo\-bar(x\*y)`, `x\*y + x\*y`)
|
||||
another(`with (foo\-bar(b\ az) = b\ az + b\ az) foo\-bar(x\*y)`, `x\*y + x\*y`)
|
||||
// override ttf to something new.
|
||||
another(`with (ttf = a) ttf + b`, `a + b`)
|
||||
// override ttf to ru
|
||||
@@ -332,8 +349,11 @@ func TestParsePromQLSuccess(t *testing.T) {
|
||||
another(`with (x="a", y=x) y+"bc"`, `"abc"`)
|
||||
another(`with (x="a", y="b"+x) "we"+y+"z"+f()`, `"webaz" + f()`)
|
||||
another(`with (f(x) = m{foo=x+"y", bar="y"+x, baz=x} + x) f("qwe")`, `m{foo="qwey", bar="yqwe", baz="qwe"} + "qwe"`)
|
||||
another(`with (f(a)=a) f`, `f`)
|
||||
another(`with (f\q(a)=a) f\q`, `fq`)
|
||||
|
||||
// Verify withExpr for aggr func modifiers
|
||||
another(`with (f(x) = x, y = sum(m) by (f)) y`, `sum(m) by (f)`)
|
||||
another(`with (f(x) = sum(m) by (x)) f(foo)`, `sum(m) by (foo)`)
|
||||
another(`with (f(x) = sum(m) by (x)) f((foo, bar, foo))`, `sum(m) by (foo, bar)`)
|
||||
another(`with (f(x) = sum(m) without (x,y)) f((a, b))`, `sum(m) without (a, b, y)`)
|
||||
@@ -658,7 +678,7 @@ func TestParsePromQLError(t *testing.T) {
|
||||
f(`with (x=m) f(b, a{x})`)
|
||||
f(`with (x=m) sum(a{x})`)
|
||||
f(`with (x=m) (a{x})`)
|
||||
f(`with (f(a)=a) f`)
|
||||
f(`with (f(a)=a) f(1, 2)`)
|
||||
f(`with (f(x)=x{foo="bar"}) f(1)`)
|
||||
f(`with (f(x)=x{foo="bar"}) f(m + n)`)
|
||||
f(`with (f = with`)
|
||||
@@ -668,8 +688,7 @@ func TestParsePromQLError(t *testing.T) {
|
||||
f(`with (f(,)=x) x`)
|
||||
f(`with (x(a) = {b="c"}) foo{x}`)
|
||||
f(`with (f(x) = m{foo=xx}) f("qwe")`)
|
||||
f(`a + with(f(x)=x) f`)
|
||||
f(`with (f(x) = x, y = sum(m) by (f)) y`)
|
||||
f(`a + with(f(x)=x) f(1,2)`)
|
||||
f(`with (f(x) = sum(m) by (x)) f({foo="bar"})`)
|
||||
f(`with (f(x) = sum(m) by (x)) f((xx(), {foo="bar"}))`)
|
||||
f(`with (f(x) = m + on (x) n) f(xx())`)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package promql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -10,12 +9,16 @@ import (
|
||||
)
|
||||
|
||||
func compileRegexpAnchored(re string) (*regexp.Regexp, error) {
|
||||
reAnchored := "^(?:" + re + ")$"
|
||||
return compileRegexp(reAnchored)
|
||||
}
|
||||
|
||||
func compileRegexp(re string) (*regexp.Regexp, error) {
|
||||
rcv := regexpCacheV.Get(re)
|
||||
if rcv != nil {
|
||||
return rcv.r, rcv.err
|
||||
}
|
||||
regexAnchored := fmt.Sprintf("^(?:%s)$", re)
|
||||
r, err := regexp.Compile(regexAnchored)
|
||||
r, err := regexp.Compile(re)
|
||||
rcv = ®expCacheValue{
|
||||
r: r,
|
||||
err: err,
|
||||
@@ -77,7 +80,7 @@ func (rc *regexpCache) Get(regexp string) *regexpCacheValue {
|
||||
rcv := rc.m[regexp]
|
||||
rc.mu.RUnlock()
|
||||
|
||||
if rc == nil {
|
||||
if rcv == nil {
|
||||
atomic.AddUint64(&rc.misses, 1)
|
||||
}
|
||||
return rcv
|
||||
|
||||
@@ -19,13 +19,14 @@ var rollupFuncs = map[string]newRollupFunc{
|
||||
// See funcs accepting range-vector on https://prometheus.io/docs/prometheus/latest/querying/functions/ .
|
||||
"changes": newRollupFuncOneArg(rollupChanges),
|
||||
"delta": newRollupFuncOneArg(rollupDelta),
|
||||
"deriv": newRollupFuncOneArg(rollupDeriv),
|
||||
"deriv": newRollupFuncOneArg(rollupDerivSlow),
|
||||
"deriv_fast": newRollupFuncOneArg(rollupDerivFast),
|
||||
"holt_winters": newRollupHoltWinters,
|
||||
"idelta": newRollupFuncOneArg(rollupIdelta),
|
||||
"increase": newRollupFuncOneArg(rollupDelta), // + rollupFuncsRemoveCounterResets
|
||||
"irate": newRollupFuncOneArg(rollupIderiv), // + rollupFuncsRemoveCounterResets
|
||||
"predict_linear": newRollupPredictLinear,
|
||||
"rate": newRollupFuncOneArg(rollupDeriv), // + rollupFuncsRemoveCounterResets
|
||||
"rate": newRollupFuncOneArg(rollupDerivFast), // + rollupFuncsRemoveCounterResets
|
||||
"resets": newRollupFuncOneArg(rollupResets),
|
||||
"avg_over_time": newRollupFuncOneArg(rollupAvg),
|
||||
"min_over_time": newRollupFuncOneArg(rollupMin),
|
||||
@@ -37,6 +38,8 @@ var rollupFuncs = map[string]newRollupFunc{
|
||||
"stdvar_over_time": newRollupFuncOneArg(rollupStdvar),
|
||||
|
||||
// Additional rollup funcs.
|
||||
"sum2_over_time": newRollupFuncOneArg(rollupSum2),
|
||||
"geomean_over_time": newRollupFuncOneArg(rollupGeomean),
|
||||
"first_over_time": newRollupFuncOneArg(rollupFirst),
|
||||
"last_over_time": newRollupFuncOneArg(rollupLast),
|
||||
"distinct_over_time": newRollupFuncOneArg(rollupDistinct),
|
||||
@@ -49,6 +52,13 @@ var rollupFuncs = map[string]newRollupFunc{
|
||||
"rollup_increase": newRollupFuncOneArg(rollupFake), // + rollupFuncsRemoveCounterResets
|
||||
}
|
||||
|
||||
var rollupFuncsMayAdjustWindow = map[string]bool{
|
||||
"deriv": true,
|
||||
"deriv_fast": true,
|
||||
"irate": true,
|
||||
"rate": true,
|
||||
}
|
||||
|
||||
var rollupFuncsRemoveCounterResets = map[string]bool{
|
||||
"increase": true,
|
||||
"irate": true,
|
||||
@@ -64,6 +74,7 @@ var rollupFuncsKeepMetricGroup = map[string]bool{
|
||||
"max_over_time": true,
|
||||
"quantile_over_time": true,
|
||||
"rollup": true,
|
||||
"geomean_over_time": true,
|
||||
}
|
||||
|
||||
func getRollupArgIdx(funcName string) int {
|
||||
@@ -120,6 +131,13 @@ type rollupConfig struct {
|
||||
Step int64
|
||||
Window int64
|
||||
|
||||
// Whether window may be adjusted to 2 x interval between data points.
|
||||
// This is needed for functions which have dt in the denominator
|
||||
// such as rate, deriv, etc.
|
||||
// Without the adjustement their value would jump in unexpected directions
|
||||
// when using window smaller than 2 x scrape_interval.
|
||||
MayAdjustWindow bool
|
||||
|
||||
Timestamps []int64
|
||||
}
|
||||
|
||||
@@ -162,7 +180,7 @@ func (rc *rollupConfig) Do(dstValues []float64, values []float64, timestamps []i
|
||||
if window <= 0 {
|
||||
window = rc.Step
|
||||
}
|
||||
if window < maxPrevInterval {
|
||||
if rc.MayAdjustWindow && window < maxPrevInterval {
|
||||
window = maxPrevInterval
|
||||
}
|
||||
rfa := getRollupFuncArg()
|
||||
@@ -171,8 +189,7 @@ func (rc *rollupConfig) Do(dstValues []float64, values []float64, timestamps []i
|
||||
|
||||
i := 0
|
||||
j := 0
|
||||
for _, ts := range rc.Timestamps {
|
||||
tEnd := ts + rc.Step
|
||||
for _, tEnd := range rc.Timestamps {
|
||||
tStart := tEnd - window
|
||||
n := sort.Search(len(timestamps)-i, func(n int) bool {
|
||||
return timestamps[i+n] > tStart
|
||||
@@ -296,11 +313,11 @@ func newRollupHoltWinters(args []interface{}) (rollupFunc, error) {
|
||||
return nil, err
|
||||
}
|
||||
rf := func(rfa *rollupFuncArg) float64 {
|
||||
// There is no need in handling NaNs here, since they must be cleanup up
|
||||
// There is no need in handling NaNs here, since they must be cleaned up
|
||||
// before calling rollup funcs.
|
||||
values := rfa.values
|
||||
if len(values) == 0 {
|
||||
return nan
|
||||
return rfa.prevValue
|
||||
}
|
||||
sf := sfs[rfa.idx]
|
||||
if sf <= 0 || sf >= 1 {
|
||||
@@ -342,41 +359,55 @@ func newRollupPredictLinear(args []interface{}) (rollupFunc, error) {
|
||||
return nil, err
|
||||
}
|
||||
rf := func(rfa *rollupFuncArg) float64 {
|
||||
// There is no need in handling NaNs here, since they must be cleanup up
|
||||
// before calling rollup funcs.
|
||||
values := rfa.values
|
||||
timestamps := rfa.timestamps
|
||||
if len(values) == 0 {
|
||||
v, k := linearRegression(rfa)
|
||||
if math.IsNaN(v) {
|
||||
return nan
|
||||
}
|
||||
|
||||
// See https://en.wikipedia.org/wiki/Simple_linear_regression#Numerical_example
|
||||
// TODO: determine whether this shit really works.
|
||||
tFirst := rfa.prevTimestamp
|
||||
vSum := rfa.prevValue
|
||||
if math.IsNaN(rfa.prevValue) {
|
||||
tFirst = timestamps[0]
|
||||
vSum = 0
|
||||
}
|
||||
tSum := float64(0)
|
||||
tvSum := float64(0)
|
||||
ttSum := float64(0)
|
||||
for i, v := range values {
|
||||
dt := float64(timestamps[i]-tFirst) * 1e-3
|
||||
vSum += v
|
||||
tSum += dt
|
||||
tvSum += dt * v
|
||||
ttSum += dt * dt
|
||||
}
|
||||
n := float64(len(values))
|
||||
k := (n*tvSum - tSum*vSum) / (n*ttSum - tSum*tSum)
|
||||
v := (vSum - k*tSum) / n
|
||||
sec := secs[rfa.idx]
|
||||
return v + k*sec
|
||||
}
|
||||
return rf, nil
|
||||
}
|
||||
|
||||
func linearRegression(rfa *rollupFuncArg) (float64, float64) {
|
||||
// There is no need in handling NaNs here, since they must be cleaned up
|
||||
// before calling rollup funcs.
|
||||
values := rfa.values
|
||||
timestamps := rfa.timestamps
|
||||
if len(values) == 0 {
|
||||
return rfa.prevValue, 0
|
||||
}
|
||||
|
||||
// See https://en.wikipedia.org/wiki/Simple_linear_regression#Numerical_example
|
||||
tFirst := rfa.prevTimestamp
|
||||
vSum := rfa.prevValue
|
||||
tSum := float64(0)
|
||||
tvSum := float64(0)
|
||||
ttSum := float64(0)
|
||||
n := 1.0
|
||||
if math.IsNaN(rfa.prevValue) {
|
||||
tFirst = timestamps[0]
|
||||
vSum = 0
|
||||
n = 0
|
||||
}
|
||||
for i, v := range values {
|
||||
dt := float64(timestamps[i]-tFirst) * 1e-3
|
||||
vSum += v
|
||||
tSum += dt
|
||||
tvSum += dt * v
|
||||
ttSum += dt * dt
|
||||
}
|
||||
n += float64(len(values))
|
||||
if n == 1 {
|
||||
return vSum, 0
|
||||
}
|
||||
k := (n*tvSum - tSum*vSum) / (n*ttSum - tSum*tSum)
|
||||
v := (vSum - k*tSum) / n
|
||||
// Adjust v to the last timestamp on the given time range.
|
||||
v += k * (float64(timestamps[len(timestamps)-1]-tFirst) * 1e-3)
|
||||
return v, k
|
||||
}
|
||||
|
||||
func newRollupQuantile(args []interface{}) (rollupFunc, error) {
|
||||
if err := expectRollupArgsNum(args, 2); err != nil {
|
||||
return nil, err
|
||||
@@ -386,11 +417,15 @@ func newRollupQuantile(args []interface{}) (rollupFunc, error) {
|
||||
return nil, err
|
||||
}
|
||||
rf := func(rfa *rollupFuncArg) float64 {
|
||||
// There is no need in handling NaNs here, since they must be cleanup up
|
||||
// There is no need in handling NaNs here, since they must be cleaned up
|
||||
// before calling rollup funcs.
|
||||
values := rfa.values
|
||||
if len(values) == 0 {
|
||||
return nan
|
||||
return rfa.prevValue
|
||||
}
|
||||
if len(values) == 1 {
|
||||
// Fast path - only a single value.
|
||||
return values[0]
|
||||
}
|
||||
hf := histogram.GetFast()
|
||||
for _, v := range values {
|
||||
@@ -408,11 +443,11 @@ func rollupAvg(rfa *rollupFuncArg) float64 {
|
||||
// Do not use `Rapid calculation methods` at https://en.wikipedia.org/wiki/Standard_deviation,
|
||||
// since it is slower and has no significant benefits in precision.
|
||||
|
||||
// There is no need in handling NaNs here, since they must be cleanup up
|
||||
// There is no need in handling NaNs here, since they must be cleaned up
|
||||
// before calling rollup funcs.
|
||||
values := rfa.values
|
||||
if len(values) == 0 {
|
||||
return nan
|
||||
return rfa.prevValue
|
||||
}
|
||||
var sum float64
|
||||
for _, v := range values {
|
||||
@@ -422,11 +457,11 @@ func rollupAvg(rfa *rollupFuncArg) float64 {
|
||||
}
|
||||
|
||||
func rollupMin(rfa *rollupFuncArg) float64 {
|
||||
// There is no need in handling NaNs here, since they must be cleanup up
|
||||
// There is no need in handling NaNs here, since they must be cleaned up
|
||||
// before calling rollup funcs.
|
||||
values := rfa.values
|
||||
if len(values) == 0 {
|
||||
return nan
|
||||
return rfa.prevValue
|
||||
}
|
||||
minValue := values[0]
|
||||
for _, v := range values {
|
||||
@@ -438,11 +473,11 @@ func rollupMin(rfa *rollupFuncArg) float64 {
|
||||
}
|
||||
|
||||
func rollupMax(rfa *rollupFuncArg) float64 {
|
||||
// There is no need in handling NaNs here, since they must be cleanup up
|
||||
// There is no need in handling NaNs here, since they must be cleaned up
|
||||
// before calling rollup funcs.
|
||||
values := rfa.values
|
||||
if len(values) == 0 {
|
||||
return nan
|
||||
return rfa.prevValue
|
||||
}
|
||||
maxValue := values[0]
|
||||
for _, v := range values {
|
||||
@@ -454,11 +489,11 @@ func rollupMax(rfa *rollupFuncArg) float64 {
|
||||
}
|
||||
|
||||
func rollupSum(rfa *rollupFuncArg) float64 {
|
||||
// There is no need in handling NaNs here, since they must be cleanup up
|
||||
// There is no need in handling NaNs here, since they must be cleaned up
|
||||
// before calling rollup funcs.
|
||||
values := rfa.values
|
||||
if len(values) == 0 {
|
||||
return nan
|
||||
return rfa.prevValue
|
||||
}
|
||||
var sum float64
|
||||
for _, v := range values {
|
||||
@@ -467,12 +502,43 @@ func rollupSum(rfa *rollupFuncArg) float64 {
|
||||
return sum
|
||||
}
|
||||
|
||||
func rollupCount(rfa *rollupFuncArg) float64 {
|
||||
// There is no need in handling NaNs here, since they must be cleanup up
|
||||
func rollupSum2(rfa *rollupFuncArg) float64 {
|
||||
// There is no need in handling NaNs here, since they must be cleaned up
|
||||
// before calling rollup funcs.
|
||||
values := rfa.values
|
||||
if len(values) == 0 {
|
||||
return nan
|
||||
return rfa.prevValue * rfa.prevValue
|
||||
}
|
||||
var sum2 float64
|
||||
for _, v := range values {
|
||||
sum2 += v * v
|
||||
}
|
||||
return sum2
|
||||
}
|
||||
|
||||
func rollupGeomean(rfa *rollupFuncArg) float64 {
|
||||
// There is no need in handling NaNs here, since they must be cleaned up
|
||||
// before calling rollup funcs.
|
||||
values := rfa.values
|
||||
if len(values) == 0 {
|
||||
return rfa.prevValue
|
||||
}
|
||||
p := 1.0
|
||||
for _, v := range values {
|
||||
p *= v
|
||||
}
|
||||
return math.Pow(p, 1/float64(len(values)))
|
||||
}
|
||||
|
||||
func rollupCount(rfa *rollupFuncArg) float64 {
|
||||
// There is no need in handling NaNs here, since they must be cleaned up
|
||||
// before calling rollup funcs.
|
||||
values := rfa.values
|
||||
if len(values) == 0 {
|
||||
if math.IsNaN(rfa.prevValue) {
|
||||
return nan
|
||||
}
|
||||
return 0
|
||||
}
|
||||
return float64(len(values))
|
||||
}
|
||||
@@ -485,11 +551,18 @@ func rollupStddev(rfa *rollupFuncArg) float64 {
|
||||
func rollupStdvar(rfa *rollupFuncArg) float64 {
|
||||
// See `Rapid calculation methods` at https://en.wikipedia.org/wiki/Standard_deviation
|
||||
|
||||
// There is no need in handling NaNs here, since they must be cleanup up
|
||||
// There is no need in handling NaNs here, since they must be cleaned up
|
||||
// before calling rollup funcs.
|
||||
values := rfa.values
|
||||
if len(values) == 0 {
|
||||
return nan
|
||||
if math.IsNaN(rfa.prevValue) {
|
||||
return nan
|
||||
}
|
||||
return 0
|
||||
}
|
||||
if len(values) == 1 {
|
||||
// Fast path.
|
||||
return values[0]
|
||||
}
|
||||
var avg float64
|
||||
var count float64
|
||||
@@ -504,7 +577,7 @@ func rollupStdvar(rfa *rollupFuncArg) float64 {
|
||||
}
|
||||
|
||||
func rollupDelta(rfa *rollupFuncArg) float64 {
|
||||
// There is no need in handling NaNs here, since they must be cleanup up
|
||||
// There is no need in handling NaNs here, since they must be cleaned up
|
||||
// before calling rollup funcs.
|
||||
values := rfa.values
|
||||
prevValue := rfa.prevValue
|
||||
@@ -516,32 +589,42 @@ func rollupDelta(rfa *rollupFuncArg) float64 {
|
||||
values = values[1:]
|
||||
}
|
||||
if len(values) == 0 {
|
||||
return nan
|
||||
return 0
|
||||
}
|
||||
return values[len(values)-1] - prevValue
|
||||
}
|
||||
|
||||
func rollupIdelta(rfa *rollupFuncArg) float64 {
|
||||
// There is no need in handling NaNs here, since they must be cleanup up
|
||||
// There is no need in handling NaNs here, since they must be cleaned up
|
||||
// before calling rollup funcs.
|
||||
values := rfa.values
|
||||
if len(values) == 0 {
|
||||
return nan
|
||||
if math.IsNaN(rfa.prevValue) {
|
||||
return nan
|
||||
}
|
||||
return 0
|
||||
}
|
||||
lastValue := values[len(values)-1]
|
||||
values = values[:len(values)-1]
|
||||
if len(values) == 0 {
|
||||
prevValue := rfa.prevValue
|
||||
if math.IsNaN(prevValue) {
|
||||
return nan
|
||||
return 0
|
||||
}
|
||||
return lastValue - prevValue
|
||||
}
|
||||
return lastValue - values[len(values)-1]
|
||||
}
|
||||
|
||||
func rollupDeriv(rfa *rollupFuncArg) float64 {
|
||||
// There is no need in handling NaNs here, since they must be cleanup up
|
||||
func rollupDerivSlow(rfa *rollupFuncArg) float64 {
|
||||
// Use linear regression like Prometheus does.
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/73
|
||||
_, k := linearRegression(rfa)
|
||||
return k
|
||||
}
|
||||
|
||||
func rollupDerivFast(rfa *rollupFuncArg) float64 {
|
||||
// There is no need in handling NaNs here, since they must be cleaned up
|
||||
// before calling rollup funcs.
|
||||
values := rfa.values
|
||||
timestamps := rfa.timestamps
|
||||
@@ -557,7 +640,7 @@ func rollupDeriv(rfa *rollupFuncArg) float64 {
|
||||
timestamps = timestamps[1:]
|
||||
}
|
||||
if len(values) == 0 {
|
||||
return nan
|
||||
return 0
|
||||
}
|
||||
vEnd := values[len(values)-1]
|
||||
tEnd := timestamps[len(timestamps)-1]
|
||||
@@ -567,12 +650,15 @@ func rollupDeriv(rfa *rollupFuncArg) float64 {
|
||||
}
|
||||
|
||||
func rollupIderiv(rfa *rollupFuncArg) float64 {
|
||||
// There is no need in handling NaNs here, since they must be cleanup up
|
||||
// There is no need in handling NaNs here, since they must be cleaned up
|
||||
// before calling rollup funcs.
|
||||
values := rfa.values
|
||||
timestamps := rfa.timestamps
|
||||
if len(values) == 0 {
|
||||
return nan
|
||||
if math.IsNaN(rfa.prevValue) {
|
||||
return nan
|
||||
}
|
||||
return 0
|
||||
}
|
||||
vEnd := values[len(values)-1]
|
||||
tEnd := timestamps[len(timestamps)-1]
|
||||
@@ -582,7 +668,7 @@ func rollupIderiv(rfa *rollupFuncArg) float64 {
|
||||
prevTimestamp := rfa.prevTimestamp
|
||||
if len(values) == 0 {
|
||||
if math.IsNaN(prevValue) {
|
||||
return nan
|
||||
return 0
|
||||
}
|
||||
} else {
|
||||
prevValue = values[len(values)-1]
|
||||
@@ -594,16 +680,18 @@ func rollupIderiv(rfa *rollupFuncArg) float64 {
|
||||
}
|
||||
|
||||
func rollupChanges(rfa *rollupFuncArg) float64 {
|
||||
// There is no need in handling NaNs here, since they must be cleanup up
|
||||
// There is no need in handling NaNs here, since they must be cleaned up
|
||||
// before calling rollup funcs.
|
||||
values := rfa.values
|
||||
if len(values) == 0 {
|
||||
return nan
|
||||
}
|
||||
n := 0
|
||||
prevValue := rfa.prevValue
|
||||
n := 0
|
||||
if math.IsNaN(prevValue) {
|
||||
if len(values) == 0 {
|
||||
return nan
|
||||
}
|
||||
prevValue = values[0]
|
||||
values = values[1:]
|
||||
n++
|
||||
}
|
||||
for _, v := range values {
|
||||
if v != prevValue {
|
||||
@@ -615,11 +703,14 @@ func rollupChanges(rfa *rollupFuncArg) float64 {
|
||||
}
|
||||
|
||||
func rollupResets(rfa *rollupFuncArg) float64 {
|
||||
// There is no need in handling NaNs here, since they must be cleanup up
|
||||
// There is no need in handling NaNs here, since they must be cleaned up
|
||||
// before calling rollup funcs.
|
||||
values := rfa.values
|
||||
if len(values) == 0 {
|
||||
return nan
|
||||
if math.IsNaN(rfa.prevValue) {
|
||||
return nan
|
||||
}
|
||||
return 0
|
||||
}
|
||||
prevValue := rfa.prevValue
|
||||
if math.IsNaN(prevValue) {
|
||||
@@ -627,7 +718,7 @@ func rollupResets(rfa *rollupFuncArg) float64 {
|
||||
values = values[1:]
|
||||
}
|
||||
if len(values) == 0 {
|
||||
return nan
|
||||
return 0
|
||||
}
|
||||
n := 0
|
||||
for _, v := range values {
|
||||
@@ -646,7 +737,7 @@ func rollupFirst(rfa *rollupFuncArg) float64 {
|
||||
return v
|
||||
}
|
||||
|
||||
// There is no need in handling NaNs here, since they must be cleanup up
|
||||
// There is no need in handling NaNs here, since they must be cleaned up
|
||||
// before calling rollup funcs.
|
||||
values := rfa.values
|
||||
if len(values) == 0 {
|
||||
@@ -655,24 +746,27 @@ func rollupFirst(rfa *rollupFuncArg) float64 {
|
||||
return values[0]
|
||||
}
|
||||
|
||||
var rollupDefault = rollupFirst
|
||||
var rollupDefault = rollupLast
|
||||
|
||||
func rollupLast(rfa *rollupFuncArg) float64 {
|
||||
// There is no need in handling NaNs here, since they must be cleanup up
|
||||
// There is no need in handling NaNs here, since they must be cleaned up
|
||||
// before calling rollup funcs.
|
||||
values := rfa.values
|
||||
if len(values) == 0 {
|
||||
return nan
|
||||
return rfa.prevValue
|
||||
}
|
||||
return values[len(values)-1]
|
||||
}
|
||||
|
||||
func rollupDistinct(rfa *rollupFuncArg) float64 {
|
||||
// There is no need in handling NaNs here, since they must be cleanup up
|
||||
// There is no need in handling NaNs here, since they must be cleaned up
|
||||
// before calling rollup funcs.
|
||||
values := rfa.values
|
||||
if len(values) == 0 {
|
||||
return nan
|
||||
if math.IsNaN(rfa.prevValue) {
|
||||
return nan
|
||||
}
|
||||
return 0
|
||||
}
|
||||
m := make(map[float64]struct{})
|
||||
for _, v := range values {
|
||||
@@ -684,12 +778,15 @@ func rollupDistinct(rfa *rollupFuncArg) float64 {
|
||||
func rollupIntegrate(rfa *rollupFuncArg) float64 {
|
||||
prevTimestamp := rfa.prevTimestamp
|
||||
|
||||
// There is no need in handling NaNs here, since they must be cleanup up
|
||||
// There is no need in handling NaNs here, since they must be cleaned up
|
||||
// before calling rollup funcs.
|
||||
values := rfa.values
|
||||
timestamps := rfa.timestamps
|
||||
if len(values) == 0 {
|
||||
return nan
|
||||
if math.IsNaN(rfa.prevValue) {
|
||||
return nan
|
||||
}
|
||||
return 0
|
||||
}
|
||||
prevValue := rfa.prevValue
|
||||
if math.IsNaN(prevValue) {
|
||||
@@ -699,7 +796,7 @@ func rollupIntegrate(rfa *rollupFuncArg) float64 {
|
||||
timestamps = timestamps[1:]
|
||||
}
|
||||
if len(values) == 0 {
|
||||
return nan
|
||||
return 0
|
||||
}
|
||||
|
||||
var sum float64
|
||||
|
||||
@@ -2,6 +2,7 @@ package promql
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"flag"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sync"
|
||||
@@ -15,6 +16,8 @@ import (
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
)
|
||||
|
||||
var disableCache = flag.Bool("search.disableCache", false, "Whether to disable response caching. This may be useful during data backfilling")
|
||||
|
||||
var rollupResultCacheV = &rollupResultCache{
|
||||
fastcache.New(1024 * 1024), // This is a cache for testing.
|
||||
}
|
||||
@@ -47,6 +50,10 @@ func InitRollupResultCache(cachePath string) {
|
||||
} else {
|
||||
c = fastcache.New(getRollupResultCacheSize())
|
||||
}
|
||||
if *disableCache {
|
||||
c.Reset()
|
||||
}
|
||||
|
||||
stats := &fastcache.Stats{}
|
||||
var statsLock sync.Mutex
|
||||
var statsLastUpdate time.Time
|
||||
@@ -119,7 +126,7 @@ func ResetRollupResultCache() {
|
||||
}
|
||||
|
||||
func (rrc *rollupResultCache) Get(funcName string, ec *EvalConfig, me *metricExpr, window int64) (tss []*timeseries, newStart int64) {
|
||||
if !ec.mayCache() {
|
||||
if *disableCache || !ec.mayCache() {
|
||||
return nil, ec.Start
|
||||
}
|
||||
|
||||
@@ -190,7 +197,7 @@ func (rrc *rollupResultCache) Get(funcName string, ec *EvalConfig, me *metricExp
|
||||
}
|
||||
|
||||
func (rrc *rollupResultCache) Put(funcName string, ec *EvalConfig, me *metricExpr, window int64, tss []*timeseries) {
|
||||
if len(tss) == 0 || !ec.mayCache() {
|
||||
if *disableCache || len(tss) == 0 || !ec.mayCache() {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ func TestRollupResultCache(t *testing.T) {
|
||||
t.Run("start-overlap", func(t *testing.T) {
|
||||
ResetRollupResultCache()
|
||||
tss := []*timeseries{
|
||||
×eries{
|
||||
{
|
||||
Timestamps: []int64{800, 1000, 1200},
|
||||
Values: []float64{0, 1, 2},
|
||||
},
|
||||
@@ -50,7 +50,7 @@ func TestRollupResultCache(t *testing.T) {
|
||||
t.Fatalf("unexpected newStart; got %d; want %d", newStart, 1400)
|
||||
}
|
||||
tssExpected := []*timeseries{
|
||||
×eries{
|
||||
{
|
||||
Timestamps: []int64{1000, 1200},
|
||||
Values: []float64{1, 2},
|
||||
},
|
||||
@@ -62,7 +62,7 @@ func TestRollupResultCache(t *testing.T) {
|
||||
t.Run("end-overlap", func(t *testing.T) {
|
||||
ResetRollupResultCache()
|
||||
tss := []*timeseries{
|
||||
×eries{
|
||||
{
|
||||
Timestamps: []int64{1800, 2000, 2200, 2400},
|
||||
Values: []float64{333, 0, 1, 2},
|
||||
},
|
||||
@@ -81,7 +81,7 @@ func TestRollupResultCache(t *testing.T) {
|
||||
t.Run("full-cover", func(t *testing.T) {
|
||||
ResetRollupResultCache()
|
||||
tss := []*timeseries{
|
||||
×eries{
|
||||
{
|
||||
Timestamps: []int64{1200, 1400, 1600},
|
||||
Values: []float64{0, 1, 2},
|
||||
},
|
||||
@@ -100,7 +100,7 @@ func TestRollupResultCache(t *testing.T) {
|
||||
t.Run("before-start", func(t *testing.T) {
|
||||
ResetRollupResultCache()
|
||||
tss := []*timeseries{
|
||||
×eries{
|
||||
{
|
||||
Timestamps: []int64{200, 400, 600},
|
||||
Values: []float64{0, 1, 2},
|
||||
},
|
||||
@@ -119,7 +119,7 @@ func TestRollupResultCache(t *testing.T) {
|
||||
t.Run("after-end", func(t *testing.T) {
|
||||
ResetRollupResultCache()
|
||||
tss := []*timeseries{
|
||||
×eries{
|
||||
{
|
||||
Timestamps: []int64{2200, 2400, 2600},
|
||||
Values: []float64{0, 1, 2},
|
||||
},
|
||||
@@ -138,7 +138,7 @@ func TestRollupResultCache(t *testing.T) {
|
||||
t.Run("bigger-than-start-end", func(t *testing.T) {
|
||||
ResetRollupResultCache()
|
||||
tss := []*timeseries{
|
||||
×eries{
|
||||
{
|
||||
Timestamps: []int64{800, 1000, 1200, 1400, 1600, 1800, 2000, 2200},
|
||||
Values: []float64{0, 1, 2, 3, 4, 5, 6, 7},
|
||||
},
|
||||
@@ -149,7 +149,7 @@ func TestRollupResultCache(t *testing.T) {
|
||||
t.Fatalf("unexpected newStart; got %d; want %d", newStart, 2200)
|
||||
}
|
||||
tssExpected := []*timeseries{
|
||||
×eries{
|
||||
{
|
||||
Timestamps: []int64{1000, 1200, 1400, 1600, 1800, 2000},
|
||||
Values: []float64{1, 2, 3, 4, 5, 6},
|
||||
},
|
||||
@@ -161,7 +161,7 @@ func TestRollupResultCache(t *testing.T) {
|
||||
t.Run("start-end-match", func(t *testing.T) {
|
||||
ResetRollupResultCache()
|
||||
tss := []*timeseries{
|
||||
×eries{
|
||||
{
|
||||
Timestamps: []int64{1000, 1200, 1400, 1600, 1800, 2000},
|
||||
Values: []float64{1, 2, 3, 4, 5, 6},
|
||||
},
|
||||
@@ -172,7 +172,7 @@ func TestRollupResultCache(t *testing.T) {
|
||||
t.Fatalf("unexpected newStart; got %d; want %d", newStart, 2200)
|
||||
}
|
||||
tssExpected := []*timeseries{
|
||||
×eries{
|
||||
{
|
||||
Timestamps: []int64{1000, 1200, 1400, 1600, 1800, 2000},
|
||||
Values: []float64{1, 2, 3, 4, 5, 6},
|
||||
},
|
||||
@@ -203,19 +203,19 @@ func TestRollupResultCache(t *testing.T) {
|
||||
t.Run("multi-timeseries", func(t *testing.T) {
|
||||
ResetRollupResultCache()
|
||||
tss1 := []*timeseries{
|
||||
×eries{
|
||||
{
|
||||
Timestamps: []int64{800, 1000, 1200},
|
||||
Values: []float64{0, 1, 2},
|
||||
},
|
||||
}
|
||||
tss2 := []*timeseries{
|
||||
×eries{
|
||||
{
|
||||
Timestamps: []int64{1800, 2000, 2200, 2400},
|
||||
Values: []float64{333, 0, 1, 2},
|
||||
},
|
||||
}
|
||||
tss3 := []*timeseries{
|
||||
×eries{
|
||||
{
|
||||
Timestamps: []int64{1200, 1400, 1600},
|
||||
Values: []float64{0, 1, 2},
|
||||
},
|
||||
@@ -228,7 +228,7 @@ func TestRollupResultCache(t *testing.T) {
|
||||
t.Fatalf("unexpected newStart; got %d; want %d", newStart, 1400)
|
||||
}
|
||||
tssExpected := []*timeseries{
|
||||
×eries{
|
||||
{
|
||||
Timestamps: []int64{1000, 1200},
|
||||
Values: []float64{1, 2},
|
||||
},
|
||||
@@ -249,14 +249,14 @@ func TestMergeTimeseries(t *testing.T) {
|
||||
t.Run("bStart=ec.Start", func(t *testing.T) {
|
||||
a := []*timeseries{}
|
||||
b := []*timeseries{
|
||||
×eries{
|
||||
{
|
||||
Timestamps: []int64{1000, 1200, 1400, 1600, 1800, 2000},
|
||||
Values: []float64{1, 2, 3, 4, 5, 6},
|
||||
},
|
||||
}
|
||||
tss := mergeTimeseries(a, b, 1000, ec)
|
||||
tssExpected := []*timeseries{
|
||||
×eries{
|
||||
{
|
||||
Timestamps: []int64{1000, 1200, 1400, 1600, 1800, 2000},
|
||||
Values: []float64{1, 2, 3, 4, 5, 6},
|
||||
},
|
||||
@@ -266,14 +266,14 @@ func TestMergeTimeseries(t *testing.T) {
|
||||
t.Run("a-empty", func(t *testing.T) {
|
||||
a := []*timeseries{}
|
||||
b := []*timeseries{
|
||||
×eries{
|
||||
{
|
||||
Timestamps: []int64{1400, 1600, 1800, 2000},
|
||||
Values: []float64{3, 4, 5, 6},
|
||||
},
|
||||
}
|
||||
tss := mergeTimeseries(a, b, bStart, ec)
|
||||
tssExpected := []*timeseries{
|
||||
×eries{
|
||||
{
|
||||
Timestamps: []int64{1000, 1200, 1400, 1600, 1800, 2000},
|
||||
Values: []float64{nan, nan, 3, 4, 5, 6},
|
||||
},
|
||||
@@ -282,7 +282,7 @@ func TestMergeTimeseries(t *testing.T) {
|
||||
})
|
||||
t.Run("b-empty", func(t *testing.T) {
|
||||
a := []*timeseries{
|
||||
×eries{
|
||||
{
|
||||
Timestamps: []int64{1000, 1200},
|
||||
Values: []float64{2, 1},
|
||||
},
|
||||
@@ -290,7 +290,7 @@ func TestMergeTimeseries(t *testing.T) {
|
||||
b := []*timeseries{}
|
||||
tss := mergeTimeseries(a, b, bStart, ec)
|
||||
tssExpected := []*timeseries{
|
||||
×eries{
|
||||
{
|
||||
Timestamps: []int64{1000, 1200, 1400, 1600, 1800, 2000},
|
||||
Values: []float64{2, 1, nan, nan, nan, nan},
|
||||
},
|
||||
@@ -299,20 +299,20 @@ func TestMergeTimeseries(t *testing.T) {
|
||||
})
|
||||
t.Run("non-empty", func(t *testing.T) {
|
||||
a := []*timeseries{
|
||||
×eries{
|
||||
{
|
||||
Timestamps: []int64{1000, 1200},
|
||||
Values: []float64{2, 1},
|
||||
},
|
||||
}
|
||||
b := []*timeseries{
|
||||
×eries{
|
||||
{
|
||||
Timestamps: []int64{1400, 1600, 1800, 2000},
|
||||
Values: []float64{3, 4, 5, 6},
|
||||
},
|
||||
}
|
||||
tss := mergeTimeseries(a, b, bStart, ec)
|
||||
tssExpected := []*timeseries{
|
||||
×eries{
|
||||
{
|
||||
Timestamps: []int64{1000, 1200, 1400, 1600, 1800, 2000},
|
||||
Values: []float64{2, 1, 3, 4, 5, 6},
|
||||
},
|
||||
@@ -321,14 +321,14 @@ func TestMergeTimeseries(t *testing.T) {
|
||||
})
|
||||
t.Run("non-empty-distinct-metric-names", func(t *testing.T) {
|
||||
a := []*timeseries{
|
||||
×eries{
|
||||
{
|
||||
Timestamps: []int64{1000, 1200},
|
||||
Values: []float64{2, 1},
|
||||
},
|
||||
}
|
||||
a[0].MetricName.MetricGroup = []byte("bar")
|
||||
b := []*timeseries{
|
||||
×eries{
|
||||
{
|
||||
Timestamps: []int64{1400, 1600, 1800, 2000},
|
||||
Values: []float64{3, 4, 5, 6},
|
||||
},
|
||||
@@ -336,14 +336,14 @@ func TestMergeTimeseries(t *testing.T) {
|
||||
b[0].MetricName.MetricGroup = []byte("foo")
|
||||
tss := mergeTimeseries(a, b, bStart, ec)
|
||||
tssExpected := []*timeseries{
|
||||
×eries{
|
||||
{
|
||||
MetricName: storage.MetricName{
|
||||
MetricGroup: []byte("foo"),
|
||||
},
|
||||
Timestamps: []int64{1000, 1200, 1400, 1600, 1800, 2000},
|
||||
Values: []float64{nan, nan, 3, 4, 5, 6},
|
||||
},
|
||||
×eries{
|
||||
{
|
||||
MetricName: storage.MetricName{
|
||||
MetricGroup: []byte("bar"),
|
||||
},
|
||||
|
||||
@@ -143,10 +143,10 @@ func TestRollupPredictLinear(t *testing.T) {
|
||||
testRollupFunc(t, "predict_linear", args, &me, vExpected)
|
||||
}
|
||||
|
||||
f(0e-3, 63.739757761102624)
|
||||
f(50e-3, 50.39682764539959)
|
||||
f(100e-3, 37.053897529696556)
|
||||
f(200e-3, 10.368037298290488)
|
||||
f(0e-3, 30.382432471845043)
|
||||
f(50e-3, 17.03950235614201)
|
||||
f(100e-3, 3.696572240438975)
|
||||
f(200e-3, -22.989287990967092)
|
||||
}
|
||||
|
||||
func TestRollupHoltWinters(t *testing.T) {
|
||||
@@ -189,10 +189,11 @@ func TestRollupNewRollupFuncSuccess(t *testing.T) {
|
||||
testRollupFunc(t, funcName, args, &me, vExpected)
|
||||
}
|
||||
|
||||
f("default_rollup", 123)
|
||||
f("changes", 10)
|
||||
f("default_rollup", 34)
|
||||
f("changes", 11)
|
||||
f("delta", -89)
|
||||
f("deriv", -712)
|
||||
f("deriv", -266.85860231406065)
|
||||
f("deriv_fast", -712)
|
||||
f("idelta", 0)
|
||||
f("increase", 275)
|
||||
f("irate", 0)
|
||||
@@ -202,12 +203,16 @@ func TestRollupNewRollupFuncSuccess(t *testing.T) {
|
||||
f("min_over_time", 12)
|
||||
f("max_over_time", 123)
|
||||
f("sum_over_time", 565)
|
||||
f("sum2_over_time", 37951)
|
||||
f("geomean_over_time", 39.33466603189148)
|
||||
f("count_over_time", 12)
|
||||
f("stddev_over_time", 30.752935722554287)
|
||||
f("stdvar_over_time", 945.7430555555555)
|
||||
f("first_over_time", 123)
|
||||
f("last_over_time", 34)
|
||||
f("integrate", 61.0275)
|
||||
f("distinct_over_time", 8)
|
||||
f("ideriv", 0)
|
||||
}
|
||||
|
||||
func TestRollupNewRollupFuncError(t *testing.T) {
|
||||
@@ -259,7 +264,7 @@ func TestRollupNoWindowNoPoints(t *testing.T) {
|
||||
}
|
||||
rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step)
|
||||
values := rc.Do(nil, testValues, testTimestamps)
|
||||
valuesExpected := []float64{nan, nan, nan, nan, 123}
|
||||
valuesExpected := []float64{nan, nan, nan, nan, nan}
|
||||
timestampsExpected := []int64{0, 1, 2, 3, 4}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
@@ -267,14 +272,14 @@ func TestRollupNoWindowNoPoints(t *testing.T) {
|
||||
rc := rollupConfig{
|
||||
Func: rollupDelta,
|
||||
Start: 120,
|
||||
End: 144,
|
||||
End: 148,
|
||||
Step: 4,
|
||||
Window: 0,
|
||||
}
|
||||
rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step)
|
||||
values := rc.Do(nil, testValues, testTimestamps)
|
||||
valuesExpected := []float64{2, 2, 0, 0, 0, nan, nan}
|
||||
timestampsExpected := []int64{120, 124, 128, 132, 136, 140, 144}
|
||||
valuesExpected := []float64{2, 0, 0, 0, 0, 0, 0, nan}
|
||||
timestampsExpected := []int64{120, 124, 128, 132, 136, 140, 144, 148}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
}
|
||||
@@ -290,7 +295,7 @@ func TestRollupWindowNoPoints(t *testing.T) {
|
||||
}
|
||||
rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step)
|
||||
values := rc.Do(nil, testValues, testTimestamps)
|
||||
valuesExpected := []float64{nan, nan, nan, nan, 123}
|
||||
valuesExpected := []float64{nan, nan, nan, nan, nan}
|
||||
timestampsExpected := []int64{0, 1, 2, 3, 4}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
@@ -315,14 +320,14 @@ func TestRollupNoWindowPartialPoints(t *testing.T) {
|
||||
rc := rollupConfig{
|
||||
Func: rollupFirst,
|
||||
Start: 0,
|
||||
End: 20,
|
||||
End: 25,
|
||||
Step: 5,
|
||||
Window: 0,
|
||||
}
|
||||
rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step)
|
||||
values := rc.Do(nil, testValues, testTimestamps)
|
||||
valuesExpected := []float64{123, 123, 123, 123, 123}
|
||||
timestampsExpected := []int64{0, 5, 10, 15, 20}
|
||||
valuesExpected := []float64{nan, 123, 123, 123, 34, 34}
|
||||
timestampsExpected := []int64{0, 5, 10, 15, 20, 25}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
t.Run("afterEnd", func(t *testing.T) {
|
||||
@@ -335,7 +340,7 @@ func TestRollupNoWindowPartialPoints(t *testing.T) {
|
||||
}
|
||||
rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step)
|
||||
values := rc.Do(nil, testValues, testTimestamps)
|
||||
valuesExpected := []float64{44, 34, 34, nan}
|
||||
valuesExpected := []float64{12, 44, 34, 34}
|
||||
timestampsExpected := []int64{100, 120, 140, 160}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
@@ -349,7 +354,7 @@ func TestRollupNoWindowPartialPoints(t *testing.T) {
|
||||
}
|
||||
rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step)
|
||||
values := rc.Do(nil, testValues, testTimestamps)
|
||||
valuesExpected := []float64{nan, 123, 54, 44, nan}
|
||||
valuesExpected := []float64{nan, nan, 123, 54, 44}
|
||||
timestampsExpected := []int64{-50, 0, 50, 100, 150}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
@@ -366,7 +371,7 @@ func TestRollupWindowPartialPoints(t *testing.T) {
|
||||
}
|
||||
rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step)
|
||||
values := rc.Do(nil, testValues, testTimestamps)
|
||||
valuesExpected := []float64{123, 123, 34, 34, 44}
|
||||
valuesExpected := []float64{nan, 123, 123, 34, 34}
|
||||
timestampsExpected := []int64{0, 5, 10, 15, 20}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
@@ -380,7 +385,7 @@ func TestRollupWindowPartialPoints(t *testing.T) {
|
||||
}
|
||||
rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step)
|
||||
values := rc.Do(nil, testValues, testTimestamps)
|
||||
valuesExpected := []float64{34, 34, nan, nan}
|
||||
valuesExpected := []float64{44, 34, 34, nan}
|
||||
timestampsExpected := []int64{100, 120, 140, 160}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
@@ -394,7 +399,7 @@ func TestRollupWindowPartialPoints(t *testing.T) {
|
||||
}
|
||||
rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step)
|
||||
values := rc.Do(nil, testValues, testTimestamps)
|
||||
valuesExpected := []float64{54, 44, nan, nan}
|
||||
valuesExpected := []float64{nan, 54, 44, 34}
|
||||
timestampsExpected := []int64{0, 50, 100, 150}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
@@ -411,7 +416,7 @@ func TestRollupFuncsNoWindow(t *testing.T) {
|
||||
}
|
||||
rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step)
|
||||
values := rc.Do(nil, testValues, testTimestamps)
|
||||
valuesExpected := []float64{123, 21, 12, 34, nan}
|
||||
valuesExpected := []float64{nan, 123, 21, 12, 34}
|
||||
timestampsExpected := []int64{0, 40, 80, 120, 160}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
@@ -425,7 +430,7 @@ func TestRollupFuncsNoWindow(t *testing.T) {
|
||||
}
|
||||
rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step)
|
||||
values := rc.Do(nil, testValues, testTimestamps)
|
||||
valuesExpected := []float64{4, 4, 3, 1, nan}
|
||||
valuesExpected := []float64{nan, 4, 4, 3, 1}
|
||||
timestampsExpected := []int64{0, 40, 80, 120, 160}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
@@ -439,7 +444,7 @@ func TestRollupFuncsNoWindow(t *testing.T) {
|
||||
}
|
||||
rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step)
|
||||
values := rc.Do(nil, testValues, testTimestamps)
|
||||
valuesExpected := []float64{21, 12, 32, 34, nan}
|
||||
valuesExpected := []float64{nan, 21, 12, 32, 34}
|
||||
timestampsExpected := []int64{0, 40, 80, 120, 160}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
@@ -453,7 +458,7 @@ func TestRollupFuncsNoWindow(t *testing.T) {
|
||||
}
|
||||
rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step)
|
||||
values := rc.Do(nil, testValues, testTimestamps)
|
||||
valuesExpected := []float64{123, 99, 44, 34, nan}
|
||||
valuesExpected := []float64{nan, 123, 99, 44, 34}
|
||||
timestampsExpected := []int64{0, 40, 80, 120, 160}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
@@ -467,7 +472,7 @@ func TestRollupFuncsNoWindow(t *testing.T) {
|
||||
}
|
||||
rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step)
|
||||
values := rc.Do(nil, testValues, testTimestamps)
|
||||
valuesExpected := []float64{222, 199, 110, 34, nan}
|
||||
valuesExpected := []float64{nan, 222, 199, 110, 34}
|
||||
timestampsExpected := []int64{0, 40, 80, 120, 160}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
@@ -481,7 +486,7 @@ func TestRollupFuncsNoWindow(t *testing.T) {
|
||||
}
|
||||
rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step)
|
||||
values := rc.Do(nil, testValues, testTimestamps)
|
||||
valuesExpected := []float64{-102, -9, 22, 0, nan}
|
||||
valuesExpected := []float64{nan, -102, -9, 22, 0}
|
||||
timestampsExpected := []int64{0, 40, 80, 120, 160}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
@@ -495,7 +500,7 @@ func TestRollupFuncsNoWindow(t *testing.T) {
|
||||
}
|
||||
rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step)
|
||||
values := rc.Do(nil, testValues, testTimestamps)
|
||||
valuesExpected := []float64{33, -87, 0, nan}
|
||||
valuesExpected := []float64{0, 33, -87, 0}
|
||||
timestampsExpected := []int64{10, 50, 90, 130}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
@@ -509,10 +514,24 @@ func TestRollupFuncsNoWindow(t *testing.T) {
|
||||
}
|
||||
rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step)
|
||||
values := rc.Do(nil, testValues, testTimestamps)
|
||||
valuesExpected := []float64{3, 4, 3, 0, nan}
|
||||
valuesExpected := []float64{nan, 4, 4, 3, 0}
|
||||
timestampsExpected := []int64{0, 40, 80, 120, 160}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
t.Run("changes_small_window", func(t *testing.T) {
|
||||
rc := rollupConfig{
|
||||
Func: rollupChanges,
|
||||
Start: 0,
|
||||
End: 45,
|
||||
Step: 9,
|
||||
Window: 9,
|
||||
}
|
||||
rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step)
|
||||
values := rc.Do(nil, testValues, testTimestamps)
|
||||
valuesExpected := []float64{nan, 1, 1, 1, 1, 0}
|
||||
timestampsExpected := []int64{0, 9, 18, 27, 36, 45}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
t.Run("resets", func(t *testing.T) {
|
||||
rc := rollupConfig{
|
||||
Func: rollupResets,
|
||||
@@ -523,7 +542,7 @@ func TestRollupFuncsNoWindow(t *testing.T) {
|
||||
}
|
||||
rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step)
|
||||
values := rc.Do(nil, testValues, testTimestamps)
|
||||
valuesExpected := []float64{2, 2, 1, 0, nan}
|
||||
valuesExpected := []float64{nan, 2, 2, 1, 0}
|
||||
timestampsExpected := []int64{0, 40, 80, 120, 160}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
@@ -537,13 +556,13 @@ func TestRollupFuncsNoWindow(t *testing.T) {
|
||||
}
|
||||
rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step)
|
||||
values := rc.Do(nil, testValues, testTimestamps)
|
||||
valuesExpected := []float64{55.5, 49.75, 36.666666666666664, 34, nan}
|
||||
valuesExpected := []float64{nan, 55.5, 49.75, 36.666666666666664, 34}
|
||||
timestampsExpected := []int64{0, 40, 80, 120, 160}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
t.Run("deriv", func(t *testing.T) {
|
||||
rc := rollupConfig{
|
||||
Func: rollupDeriv,
|
||||
Func: rollupDerivSlow,
|
||||
Start: 0,
|
||||
End: 160,
|
||||
Step: 40,
|
||||
@@ -551,7 +570,7 @@ func TestRollupFuncsNoWindow(t *testing.T) {
|
||||
}
|
||||
rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step)
|
||||
values := rc.Do(nil, testValues, testTimestamps)
|
||||
valuesExpected := []float64{-3290.3225806451615, -204.54545454545456, 550, 0, nan}
|
||||
valuesExpected := []float64{0, -2879.310344827587, 558.0608793686592, 422.84569138276544, 0}
|
||||
timestampsExpected := []int64{0, 40, 80, 120, 160}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
@@ -565,7 +584,7 @@ func TestRollupFuncsNoWindow(t *testing.T) {
|
||||
}
|
||||
rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step)
|
||||
values := rc.Do(nil, testValues, testTimestamps)
|
||||
valuesExpected := []float64{-1916.6666666666665, -43500, 400, 0, nan}
|
||||
valuesExpected := []float64{nan, -1916.6666666666665, -43500, 400, 0}
|
||||
timestampsExpected := []int64{0, 40, 80, 120, 160}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
@@ -579,7 +598,7 @@ func TestRollupFuncsNoWindow(t *testing.T) {
|
||||
}
|
||||
rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step)
|
||||
values := rc.Do(nil, testValues, testTimestamps)
|
||||
valuesExpected := []float64{39.81519810323691, 32.080952292598795, 5.2493385826745405, 0, nan}
|
||||
valuesExpected := []float64{nan, 39.81519810323691, 32.080952292598795, 5.2493385826745405, 5.830951894845301}
|
||||
timestampsExpected := []int64{0, 40, 80, 120, 160}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
@@ -593,7 +612,7 @@ func TestRollupFuncsNoWindow(t *testing.T) {
|
||||
}
|
||||
rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step)
|
||||
values := rc.Do(nil, testValues, testTimestamps)
|
||||
valuesExpected := []float64{4.6035, 4.3934999999999995, 2.166, 0.34, nan}
|
||||
valuesExpected := []float64{nan, 4.6035, 4.3934999999999995, 2.166, 0.34}
|
||||
timestampsExpected := []int64{0, 40, 80, 120, 160}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
@@ -607,7 +626,7 @@ func TestRollupFuncsNoWindow(t *testing.T) {
|
||||
}
|
||||
rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step)
|
||||
values := rc.Do(nil, testValues, testTimestamps)
|
||||
valuesExpected := []float64{4, 4, 3, 1, nan}
|
||||
valuesExpected := []float64{nan, 4, 4, 3, 1}
|
||||
timestampsExpected := []int64{0, 40, 80, 120, 160}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
|
||||
@@ -38,11 +38,13 @@ func (ts *timeseries) String() string {
|
||||
return fmt.Sprintf("MetricName=%s, Values=%g, Timestamps=%d", &ts.MetricName, ts.Values, ts.Timestamps)
|
||||
}
|
||||
|
||||
func (ts *timeseries) CopyFrom(src *timeseries) {
|
||||
func (ts *timeseries) CopyFromShallowTimestamps(src *timeseries) {
|
||||
ts.Reset()
|
||||
ts.MetricName.CopyFrom(&src.MetricName)
|
||||
ts.Values = append(ts.Values[:0], src.Values...)
|
||||
ts.Timestamps = append(ts.Timestamps[:0], src.Timestamps...)
|
||||
ts.Timestamps = src.Timestamps
|
||||
|
||||
ts.denyReuse = true
|
||||
}
|
||||
|
||||
func (ts *timeseries) CopyFromMetricNames(src *timeseries) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -61,6 +62,7 @@ var transformFuncs = map[string]transformFunc{
|
||||
"label_keep": transformLabelKeep,
|
||||
"label_copy": transformLabelCopy,
|
||||
"label_move": transformLabelMove,
|
||||
"label_transform": transformLabelTransform,
|
||||
"union": transformUnion,
|
||||
"": transformUnion, // empty func is a synonim to union
|
||||
"keep_last_value": transformKeepLastValue,
|
||||
@@ -294,14 +296,12 @@ func transformHistogramQuantile(tfa *transformFuncArg) ([]*timeseries, error) {
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var dst timeseries
|
||||
dst.CopyFrom(ts)
|
||||
dst.MetricName.ResetMetricGroup()
|
||||
dst.MetricName.RemoveTag("le")
|
||||
bb.B = marshalMetricTagsSorted(bb.B[:0], &dst.MetricName)
|
||||
ts.MetricName.ResetMetricGroup()
|
||||
ts.MetricName.RemoveTag("le")
|
||||
bb.B = marshalMetricTagsSorted(bb.B[:0], &ts.MetricName)
|
||||
m[string(bb.B)] = append(m[string(bb.B)], x{
|
||||
le: le,
|
||||
ts: &dst,
|
||||
ts: ts,
|
||||
})
|
||||
}
|
||||
bbPool.Put(bb)
|
||||
@@ -394,13 +394,6 @@ func runningAvg(a, b float64, idx int) float64 {
|
||||
return a + (b-a)/float64(idx+1)
|
||||
}
|
||||
|
||||
func keepLastValue(a, b float64, idx int) float64 {
|
||||
if math.IsNaN(b) {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func skipLeadingNaNs(values []float64) []float64 {
|
||||
i := 0
|
||||
for i < len(values) && math.IsNaN(values[i]) {
|
||||
@@ -641,9 +634,7 @@ func transformUnion(tfa *transformFuncArg) ([]*timeseries, error) {
|
||||
continue
|
||||
}
|
||||
m[string(bb.B)] = true
|
||||
var dst timeseries
|
||||
dst.CopyFrom(ts)
|
||||
rvs = append(rvs, &dst)
|
||||
rvs = append(rvs, ts)
|
||||
}
|
||||
}
|
||||
bbPool.Put(bb)
|
||||
@@ -816,6 +807,31 @@ func transformLabelJoin(tfa *transformFuncArg) ([]*timeseries, error) {
|
||||
return rvs, nil
|
||||
}
|
||||
|
||||
func transformLabelTransform(tfa *transformFuncArg) ([]*timeseries, error) {
|
||||
args := tfa.args
|
||||
if err := expectTransformArgsNum(args, 4); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
label, err := getString(args[1], 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
regex, err := getString(args[2], 2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
replacement, err := getString(args[3], 3)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r, err := compileRegexp(regex)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`cannot compile regex %q: %s`, regex, err)
|
||||
}
|
||||
return labelReplace(args[0], label, r, label, replacement)
|
||||
}
|
||||
|
||||
func transformLabelReplace(tfa *transformFuncArg) ([]*timeseries, error) {
|
||||
args := tfa.args
|
||||
if err := expectTransformArgsNum(args, 5); err != nil {
|
||||
@@ -842,11 +858,12 @@ func transformLabelReplace(tfa *transformFuncArg) ([]*timeseries, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`cannot compile regex %q: %s`, regex, err)
|
||||
}
|
||||
return labelReplace(args[0], srcLabel, r, dstLabel, replacement)
|
||||
}
|
||||
|
||||
func labelReplace(tss []*timeseries, srcLabel string, r *regexp.Regexp, dstLabel, replacement string) ([]*timeseries, error) {
|
||||
replacementBytes := []byte(replacement)
|
||||
|
||||
rvs := args[0]
|
||||
for _, ts := range rvs {
|
||||
for _, ts := range tss {
|
||||
mn := &ts.MetricName
|
||||
dstValue := getDstValue(mn, dstLabel)
|
||||
srcValue := mn.GetTagValue(srcLabel)
|
||||
@@ -856,7 +873,7 @@ func transformLabelReplace(tfa *transformFuncArg) ([]*timeseries, error) {
|
||||
mn.RemoveTag(dstLabel)
|
||||
}
|
||||
}
|
||||
return rvs, nil
|
||||
return tss, nil
|
||||
}
|
||||
|
||||
func transformLn(v float64) float64 {
|
||||
|
||||
@@ -96,6 +96,14 @@ func SearchTagValues(tagKey []byte, maxTagValues int) ([]string, error) {
|
||||
return values, err
|
||||
}
|
||||
|
||||
// SearchTagEntries searches for tag entries.
|
||||
func SearchTagEntries(maxTagKeys, maxTagValues int) ([]storage.TagEntry, error) {
|
||||
WG.Add(1)
|
||||
tagEntries, err := Storage.SearchTagEntries(maxTagKeys, maxTagValues)
|
||||
WG.Done()
|
||||
return tagEntries, err
|
||||
}
|
||||
|
||||
// GetSeriesCount returns the number of time series in the storage.
|
||||
func GetSeriesCount() (uint64, error) {
|
||||
WG.Add(1)
|
||||
@@ -285,6 +293,18 @@ func registerStorageMetrics(strg *storage.Storage) {
|
||||
metrics.NewGauge(`vm_missing_tsids_for_metric_id_total`, func() float64 {
|
||||
return float64(idbm().MissingTSIDsForMetricID)
|
||||
})
|
||||
metrics.NewGauge(`vm_recent_hour_metric_ids_search_calls_total`, func() float64 {
|
||||
return float64(idbm().RecentHourMetricIDsSearchCalls)
|
||||
})
|
||||
metrics.NewGauge(`vm_recent_hour_metric_ids_search_hits_total`, func() float64 {
|
||||
return float64(idbm().RecentHourMetricIDsSearchHits)
|
||||
})
|
||||
metrics.NewGauge(`vm_date_metric_ids_search_calls_total`, func() float64 {
|
||||
return float64(idbm().DateMetricIDsSearchCalls)
|
||||
})
|
||||
metrics.NewGauge(`vm_date_metric_ids_search_hits_total`, func() float64 {
|
||||
return float64(idbm().DateMetricIDsSearchHits)
|
||||
})
|
||||
|
||||
metrics.NewGauge(`vm_assisted_merges_total{type="storage/small"}`, func() float64 {
|
||||
return float64(tm().SmallAssistedMerges)
|
||||
@@ -342,6 +362,9 @@ func registerStorageMetrics(strg *storage.Storage) {
|
||||
metrics.NewGauge(`vm_cache_entries{type="storage/date_metricID"}`, func() float64 {
|
||||
return float64(m().DateMetricIDCacheSize)
|
||||
})
|
||||
metrics.NewGauge(`vm_cache_entries{type="storage/hour_metric_ids"}`, func() float64 {
|
||||
return float64(m().HourMetricIDCacheSize)
|
||||
})
|
||||
metrics.NewGauge(`vm_cache_entries{type="storage/bigIndexBlocks"}`, func() float64 {
|
||||
return float64(tm().BigIndexBlocksCacheSize)
|
||||
})
|
||||
@@ -357,6 +380,9 @@ func registerStorageMetrics(strg *storage.Storage) {
|
||||
metrics.NewGauge(`vm_cache_entries{type="indexdb/tagFilters"}`, func() float64 {
|
||||
return float64(idbm().TagCacheSize)
|
||||
})
|
||||
metrics.NewGauge(`vm_cache_entries{type="indexdb/uselessTagFilters"}`, func() float64 {
|
||||
return float64(idbm().UselessTagFiltersCacheSize)
|
||||
})
|
||||
metrics.NewGauge(`vm_cache_entries{type="storage/regexps"}`, func() float64 {
|
||||
return float64(storage.RegexpCacheSize())
|
||||
})
|
||||
@@ -376,6 +402,9 @@ func registerStorageMetrics(strg *storage.Storage) {
|
||||
metrics.NewGauge(`vm_cache_size_bytes{type="indexdb/tagFilters"}`, func() float64 {
|
||||
return float64(idbm().TagCacheBytesSize)
|
||||
})
|
||||
metrics.NewGauge(`vm_cache_size_bytes{type="indexdb/uselessTagFilters"}`, func() float64 {
|
||||
return float64(idbm().UselessTagFiltersCacheBytesSize)
|
||||
})
|
||||
|
||||
metrics.NewGauge(`vm_cache_requests_total{type="storage/tsid"}`, func() float64 {
|
||||
return float64(m().TSIDCacheRequests)
|
||||
@@ -404,6 +433,9 @@ func registerStorageMetrics(strg *storage.Storage) {
|
||||
metrics.NewGauge(`vm_cache_requests_total{type="indexdb/tagFilters"}`, func() float64 {
|
||||
return float64(idbm().TagCacheRequests)
|
||||
})
|
||||
metrics.NewGauge(`vm_cache_requests_total{type="indexdb/uselessTagFilters"}`, func() float64 {
|
||||
return float64(idbm().UselessTagFiltersCacheRequests)
|
||||
})
|
||||
metrics.NewGauge(`vm_cache_requests_total{type="storage/regexps"}`, func() float64 {
|
||||
return float64(storage.RegexpCacheRequests())
|
||||
})
|
||||
@@ -435,6 +467,9 @@ func registerStorageMetrics(strg *storage.Storage) {
|
||||
metrics.NewGauge(`vm_cache_misses_total{type="indexdb/tagFilters"}`, func() float64 {
|
||||
return float64(idbm().TagCacheMisses)
|
||||
})
|
||||
metrics.NewGauge(`vm_cache_misses_total{type="indexdb/uselessTagFilters"}`, func() float64 {
|
||||
return float64(idbm().UselessTagFiltersCacheMisses)
|
||||
})
|
||||
metrics.NewGauge(`vm_cache_misses_total{type="storage/regexps"}`, func() float64 {
|
||||
return float64(storage.RegexpCacheMisses())
|
||||
})
|
||||
|
||||
1882
dashboards/victoriametrics.json
Normal file
1882
dashboards/victoriametrics.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
DOCKER_NAMESPACE := valyala
|
||||
BUILDER_IMAGE := local/builder:go1.12.5
|
||||
DOCKER_NAMESPACE := victoriametrics
|
||||
BUILDER_IMAGE := local/builder:go1.12.6
|
||||
CERTS_IMAGE := local/certs:1.0.2
|
||||
|
||||
package-certs:
|
||||
@@ -18,6 +18,7 @@ app-via-docker: package-certs package-builder
|
||||
-w /VictoriaMetrics \
|
||||
--mount type=bind,src="$(shell pwd)/gocache-for-docker",dst=/gocache \
|
||||
--env GOCACHE=/gocache \
|
||||
--env GO111MODULE=on \
|
||||
$(BUILDER_IMAGE) \
|
||||
go build $(RACE) -mod=vendor -ldflags "-s -w -extldflags '-static' $(GO_BUILDINFO)" -tags 'netgo osusergo' -o bin/$(APP_NAME)-prod $(PKG_PREFIX)/app/$(APP_NAME)
|
||||
|
||||
|
||||
33
deployment/docker/README.md
Normal file
33
deployment/docker/README.md
Normal file
@@ -0,0 +1,33 @@
|
||||
### Folder contains basic images and tools for building and running Victoria Metrics in docker
|
||||
|
||||
#### Docker compose
|
||||
|
||||
To spin-up setup of VictoriaMetrics, Prometheus and Grafana run following command:
|
||||
|
||||
`docker-compose up`
|
||||
|
||||
##### VictoriaMetrics
|
||||
|
||||
VictoriaMetrics opens following ports:
|
||||
* `--graphiteListenAddr=:2003`
|
||||
* `--opentsdbListenAddr=:4242`
|
||||
* `--httpListenAddr=:8428`
|
||||
|
||||
##### Prometheus
|
||||
|
||||
To access service open following [link](http://localhost:9090).
|
||||
|
||||
Prometheus is already configured to use VictoriaMetrics as remote storage.
|
||||
|
||||
##### Grafana
|
||||
|
||||
To access service open following [link](http://localhost:3000).
|
||||
|
||||
Default creds:
|
||||
* login - `admin`
|
||||
* password - `admin`
|
||||
|
||||
Grafana is provisioned by default with following entities:
|
||||
* VictoriaMetrics datasource
|
||||
* Prometheus datasource
|
||||
* VictoriaMetrics overview dashboard
|
||||
@@ -1 +1,2 @@
|
||||
FROM golang:1.12.5
|
||||
FROM golang:1.12.6
|
||||
STOPSIGNAL SIGINT
|
||||
|
||||
61
deployment/docker/docker-compose.yml
Normal file
61
deployment/docker/docker-compose.yml
Normal file
@@ -0,0 +1,61 @@
|
||||
version: '3.5'
|
||||
services:
|
||||
prometheus:
|
||||
container_name: prometheus
|
||||
image: prom/prometheus:v2.10.0
|
||||
depends_on:
|
||||
- "victoriametrics"
|
||||
ports:
|
||||
- 9090:9090
|
||||
volumes:
|
||||
- promdata:/prometheus
|
||||
- ./prometheus.yml:/etc/prometheus/prometheus.yml
|
||||
command:
|
||||
- '--config.file=/etc/prometheus/prometheus.yml'
|
||||
- '--storage.tsdb.path=/prometheus'
|
||||
networks:
|
||||
- vm_net
|
||||
restart: always
|
||||
victoriametrics:
|
||||
container_name: victoriametrics
|
||||
image: victoriametrics/victoria-metrics
|
||||
ports:
|
||||
- 8428:8428
|
||||
- 2003:2003
|
||||
- 4242:4242
|
||||
volumes:
|
||||
- vmdata:/storage
|
||||
command:
|
||||
- '--storageDataPath=/storage'
|
||||
- '--graphiteListenAddr=:2003'
|
||||
- '--opentsdbListenAddr=:4242'
|
||||
- '--httpListenAddr=:8428'
|
||||
networks:
|
||||
- vm_net
|
||||
restart: always
|
||||
grafana:
|
||||
container_name: grafana
|
||||
image: grafana/grafana:6.2.1
|
||||
entrypoint: >
|
||||
/bin/sh -c "
|
||||
cd /var/lib/grafana &&
|
||||
mkdir -p dashboards &&
|
||||
sed 's/$${DS_PROMETHEUS}/Prometheus/g' vm.json > dashboards/vm.json &&
|
||||
/run.sh"
|
||||
depends_on:
|
||||
- "victoriametrics"
|
||||
ports:
|
||||
- 3000:3000
|
||||
volumes:
|
||||
- grafanadata:/var/lib/grafana
|
||||
- ./provisioning/:/etc/grafana/provisioning/
|
||||
- ./../../dashboards/victoriametrics.json:/var/lib/grafana/vm.json
|
||||
networks:
|
||||
- vm_net
|
||||
restart: always
|
||||
volumes:
|
||||
promdata: {}
|
||||
vmdata: {}
|
||||
grafanadata: {}
|
||||
networks:
|
||||
vm_net:
|
||||
16
deployment/docker/prometheus.yml
Normal file
16
deployment/docker/prometheus.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
global:
|
||||
scrape_interval: 10s
|
||||
evaluation_interval: 10s
|
||||
|
||||
remote_write:
|
||||
- url: "http://victoriametrics:8428/api/v1/write"
|
||||
queue_config:
|
||||
max_samples_per_send: 10000
|
||||
|
||||
scrape_configs:
|
||||
- job_name: 'prometheus'
|
||||
static_configs:
|
||||
- targets: ['prometheus:9090']
|
||||
- job_name: 'victoriametrics'
|
||||
static_configs:
|
||||
- targets: ['victoriametrics:8428']
|
||||
9
deployment/docker/provisioning/dashboards/dashboard.yml
Normal file
9
deployment/docker/provisioning/dashboards/dashboard.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
apiVersion: 1
|
||||
|
||||
providers:
|
||||
- name: Prometheus
|
||||
orgId: 1
|
||||
folder: ''
|
||||
type: file
|
||||
options:
|
||||
path: /var/lib/grafana/dashboards
|
||||
14
deployment/docker/provisioning/datasources/datasource.yml
Normal file
14
deployment/docker/provisioning/datasources/datasource.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
apiVersion: 1
|
||||
|
||||
datasources:
|
||||
- name: Prometheus
|
||||
type: prometheus
|
||||
access: proxy
|
||||
url: http://prometheus:9090
|
||||
isDefault: false
|
||||
|
||||
- name: VictoriaMetrics
|
||||
type: prometheus
|
||||
access: proxy
|
||||
url: http://victoriametrics:8428
|
||||
isDefault: true
|
||||
8
go.mod
8
go.mod
@@ -1,16 +1,16 @@
|
||||
module github.com/VictoriaMetrics/VictoriaMetrics
|
||||
|
||||
require (
|
||||
github.com/VictoriaMetrics/fastcache v1.5.0
|
||||
github.com/VictoriaMetrics/metrics v1.4.0
|
||||
github.com/VictoriaMetrics/fastcache v1.5.1
|
||||
github.com/VictoriaMetrics/metrics v1.5.0
|
||||
github.com/cespare/xxhash/v2 v2.0.1-0.20190104013014-3767db7a7e18
|
||||
github.com/golang/snappy v0.0.1
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
github.com/valyala/fastjson v1.4.1
|
||||
github.com/valyala/gozstd v1.5.0
|
||||
github.com/valyala/gozstd v1.5.1
|
||||
github.com/valyala/histogram v1.0.1
|
||||
github.com/valyala/quicktemplate v1.1.1
|
||||
golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82
|
||||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f
|
||||
)
|
||||
|
||||
go 1.12
|
||||
|
||||
16
go.sum
16
go.sum
@@ -1,10 +1,10 @@
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI=
|
||||
github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
|
||||
github.com/VictoriaMetrics/fastcache v1.5.0 h1:z8t2QV/CDXWVJ9vy9yRtGGDoOvk9W2aXQBijbLk0KCc=
|
||||
github.com/VictoriaMetrics/fastcache v1.5.0/go.mod h1:+jv9Ckb+za/P1ZRg/sulP5Ni1v49daAVERr0H3CuscE=
|
||||
github.com/VictoriaMetrics/metrics v1.4.0 h1:3+XdciC4E8sywx+0PStXhtIdWxXP2bdJ06Whw0mViQE=
|
||||
github.com/VictoriaMetrics/metrics v1.4.0/go.mod h1:QZAL5yLaXvhSPeib0ahluGo9VK0HXDZHovKaKlpuWvs=
|
||||
github.com/VictoriaMetrics/fastcache v1.5.1 h1:qHgHjyoNFV7jgucU8QZUuU4gcdhfs8QW1kw68OD2Lag=
|
||||
github.com/VictoriaMetrics/fastcache v1.5.1/go.mod h1:+jv9Ckb+za/P1ZRg/sulP5Ni1v49daAVERr0H3CuscE=
|
||||
github.com/VictoriaMetrics/metrics v1.5.0 h1:WvQqPn+z9pR1U7J58CgaGiWrN8phNGSpr2xUSxJnfpE=
|
||||
github.com/VictoriaMetrics/metrics v1.5.0/go.mod h1:QZAL5yLaXvhSPeib0ahluGo9VK0HXDZHovKaKlpuWvs=
|
||||
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/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
@@ -36,13 +36,13 @@ github.com/valyala/fastjson v1.4.1 h1:hrltpHpIpkaxll8QltMU8c3QZ5+qIiCL8yKqPFJI/y
|
||||
github.com/valyala/fastjson v1.4.1/go.mod h1:nV6MsjxL2IMJQUoHDIrjEI7oLyeqK6aBD7EFWPsvP8o=
|
||||
github.com/valyala/fastrand v1.0.0 h1:LUKT9aKer2dVQNUi3waewTbKV+7H17kvWFNKs2ObdkI=
|
||||
github.com/valyala/fastrand v1.0.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
|
||||
github.com/valyala/gozstd v1.5.0 h1:OI7Z2e+GkvmmdRFiJeyuByhT/WMTLuRLa43Z2Tjzenw=
|
||||
github.com/valyala/gozstd v1.5.0/go.mod h1:oYOS+oJovjw9ewtrwEYb9+ybolEXd6pHyLMuAWN5zts=
|
||||
github.com/valyala/gozstd v1.5.1 h1:ZLepItgu2g+B2CfVQy6KCV/as8lnJ7ef1KU6DPxQSS0=
|
||||
github.com/valyala/gozstd v1.5.1/go.mod h1:oYOS+oJovjw9ewtrwEYb9+ybolEXd6pHyLMuAWN5zts=
|
||||
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/quicktemplate v1.1.1 h1:C58y/wN0FMTi2PR0n3onltemfFabany53j7M6SDDB8k=
|
||||
github.com/valyala/quicktemplate v1.1.1/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4=
|
||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
||||
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82 h1:vsphBvatvfbhlb4PO1BYSr9dzugGxJ/SQHoNufZJq1w=
|
||||
golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4=
|
||||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
||||
@@ -13,6 +13,7 @@ var (
|
||||
// Verify ByteBuffer implements the given interfaces.
|
||||
_ io.Writer = &ByteBuffer{}
|
||||
_ fs.ReadAtCloser = &ByteBuffer{}
|
||||
_ io.ReaderFrom = &ByteBuffer{}
|
||||
|
||||
// Verify reader implement filestream.ReadCloser interface.
|
||||
_ filestream.ReadCloser = &reader{}
|
||||
@@ -44,7 +45,31 @@ func (bb *ByteBuffer) ReadAt(p []byte, offset int64) {
|
||||
logger.Panicf("BUG: too big offset=%d; cannot exceed len(bb.B)=%d", offset, len(bb.B))
|
||||
}
|
||||
if n := copy(p, bb.B[offset:]); n < len(p) {
|
||||
logger.Panicf("BUG: EOF occured after reading %d bytes out of %d bytes at offset %d", n, len(p), offset)
|
||||
logger.Panicf("BUG: EOF occurred after reading %d bytes out of %d bytes at offset %d", n, len(p), offset)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadFrom reads all the data from r to bb until EOF.
|
||||
func (bb *ByteBuffer) ReadFrom(r io.Reader) (int64, error) {
|
||||
b := bb.B
|
||||
bLen := len(b)
|
||||
b = Resize(b, 4*1024)
|
||||
b = b[:cap(b)]
|
||||
offset := bLen
|
||||
for {
|
||||
if free := len(b) - offset; free < offset {
|
||||
n := len(b)
|
||||
b = append(b, make([]byte, n)...)
|
||||
}
|
||||
n, err := r.Read(b[offset:])
|
||||
offset += n
|
||||
if err != nil {
|
||||
bb.B = b[:offset]
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
return int64(offset - bLen), err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package bytesutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
@@ -66,6 +67,92 @@ func TestByteBuffer(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestByteBufferReadFrom(t *testing.T) {
|
||||
var bbPool ByteBufferPool
|
||||
|
||||
t.Run("zero_bytes", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
bb := bbPool.Get()
|
||||
defer bbPool.Put(bb)
|
||||
src := bytes.NewBufferString("")
|
||||
n, err := bb.ReadFrom(src)
|
||||
if err != nil {
|
||||
t.Fatalf("error when reading empty string: %s", err)
|
||||
}
|
||||
if n != 0 {
|
||||
t.Fatalf("unexpected number of bytes read; got %d; want %d", n, 0)
|
||||
}
|
||||
if len(bb.B) != 0 {
|
||||
t.Fatalf("unexpejcted len(bb.B); got %d; want %d", len(bb.B), 0)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("non_zero_bytes", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
bb := bbPool.Get()
|
||||
defer bbPool.Put(bb)
|
||||
s := "foobarbaz"
|
||||
src := bytes.NewBufferString(s)
|
||||
n, err := bb.ReadFrom(src)
|
||||
if err != nil {
|
||||
t.Fatalf("error when reading non-empty string: %s", err)
|
||||
}
|
||||
if n != int64(len(s)) {
|
||||
t.Fatalf("unexpected number of bytes read; got %d; want %d", n, len(s))
|
||||
}
|
||||
if string(bb.B) != s {
|
||||
t.Fatalf("unexpected value read; got %q; want %q", bb.B, s)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("big_number_of_bytes", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
bb := bbPool.Get()
|
||||
defer bbPool.Put(bb)
|
||||
b := make([]byte, 1024*1024+234)
|
||||
for i := range b {
|
||||
b[i] = byte(i)
|
||||
}
|
||||
src := bytes.NewBuffer(b)
|
||||
n, err := bb.ReadFrom(src)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot read big value: %s", err)
|
||||
}
|
||||
if n != int64(len(b)) {
|
||||
t.Fatalf("unexpected number of bytes read; got %d; want %d", n, len(b))
|
||||
}
|
||||
if string(bb.B) != string(b) {
|
||||
t.Fatalf("unexpected value read; got %q; want %q", bb.B, b)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("non_empty_bb", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
bb := bbPool.Get()
|
||||
defer bbPool.Put(bb)
|
||||
prefix := []byte("prefix")
|
||||
bb.B = append(bb.B[:0], prefix...)
|
||||
s := "aosdfdsafdjsf"
|
||||
src := bytes.NewBufferString(s)
|
||||
n, err := bb.ReadFrom(src)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot read to non-empty bb: %s", err)
|
||||
}
|
||||
if n != int64(len(s)) {
|
||||
t.Fatalf("unexpected number of bytes read; got %d; want %d", n, len(s))
|
||||
}
|
||||
if len(bb.B) != len(prefix)+len(s) {
|
||||
t.Fatalf("unexpected bb.B len; got %d; want %d", len(bb.B), len(prefix)+len(s))
|
||||
}
|
||||
if string(bb.B[:len(prefix)]) != string(prefix) {
|
||||
t.Fatalf("unexpected prefix; got %q; want %q", bb.B[:len(prefix)], prefix)
|
||||
}
|
||||
if string(bb.B[len(prefix):]) != s {
|
||||
t.Fatalf("unexpected data read; got %q; want %q", bb.B[len(prefix):], s)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestByteBufferRead(t *testing.T) {
|
||||
var bb ByteBuffer
|
||||
|
||||
|
||||
@@ -186,6 +186,7 @@ func maxUpExponent(v int64) int16 {
|
||||
v = -v
|
||||
}
|
||||
if v < 0 {
|
||||
// Handle corner case for v=-1<<63
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
@@ -1,27 +1,34 @@
|
||||
package encoding
|
||||
|
||||
import (
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
"github.com/valyala/gozstd"
|
||||
)
|
||||
|
||||
// CompressZSTD compresses src, appends the result to dst and returns
|
||||
// the appended dst.
|
||||
//
|
||||
// src must be non-empty.
|
||||
func CompressZSTD(dst, src []byte) []byte {
|
||||
return gozstd.CompressLevel(dst, src, 5)
|
||||
}
|
||||
|
||||
// CompressZSTDLevel appends compressed src to dst and returns
|
||||
// the appended dst.
|
||||
//
|
||||
// The given compressLevel is used for the compression.
|
||||
func CompressZSTDLevel(dst, src []byte, compressLevel int) []byte {
|
||||
return gozstd.CompressLevel(dst, src, compressLevel)
|
||||
compressCalls.Inc()
|
||||
originalBytes.Add(len(src))
|
||||
dstLen := len(dst)
|
||||
dst = gozstd.CompressLevel(dst, src, compressLevel)
|
||||
compressedBytes.Add(len(dst) - dstLen)
|
||||
return dst
|
||||
}
|
||||
|
||||
// DecompressZSTD decompresses src, appends the result to dst and returns
|
||||
// the appended dst.
|
||||
func DecompressZSTD(dst, src []byte) ([]byte, error) {
|
||||
decompressCalls.Inc()
|
||||
return gozstd.Decompress(dst, src)
|
||||
}
|
||||
|
||||
var (
|
||||
compressCalls = metrics.NewCounter(`vm_zstd_block_compress_calls_total`)
|
||||
decompressCalls = metrics.NewCounter(`vm_zstd_block_decompress_calls_total`)
|
||||
|
||||
originalBytes = metrics.NewCounter(`vm_zstd_block_original_bytes_total`)
|
||||
compressedBytes = metrics.NewCounter(`vm_zstd_block_compressed_bytes_total`)
|
||||
)
|
||||
|
||||
@@ -17,7 +17,7 @@ func TestCompressDecompressZSTD(t *testing.T) {
|
||||
}
|
||||
|
||||
func testCompressDecompressZSTD(t *testing.T, b []byte) {
|
||||
bc := CompressZSTD(nil, b)
|
||||
bc := CompressZSTDLevel(nil, b, 5)
|
||||
bNew, err := DecompressZSTD(nil, bc)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error when decompressing b=%x from bc=%x: %s", b, bc, err)
|
||||
@@ -27,7 +27,7 @@ func testCompressDecompressZSTD(t *testing.T, b []byte) {
|
||||
}
|
||||
|
||||
prefix := []byte{1, 2, 33}
|
||||
bcNew := CompressZSTD(prefix, b)
|
||||
bcNew := CompressZSTDLevel(prefix, b, 5)
|
||||
if string(bcNew[:len(prefix)]) != string(prefix) {
|
||||
t.Fatalf("invalid prefix for b=%x; got\n%x; expecting\n%x", b, bcNew[:len(prefix)], prefix)
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ func marshalInt64Array(dst []byte, a []int64, precisionBits uint8) (result []byt
|
||||
|
||||
bb := bbPool.Get()
|
||||
if isGauge(a) {
|
||||
// Guage values are better compressed with delta encoding.
|
||||
// Gauge values are better compressed with delta encoding.
|
||||
mt = MarshalTypeZSTDNearestDelta
|
||||
pb := precisionBits
|
||||
if pb < 6 {
|
||||
|
||||
9
lib/filestream/filestream_windows.go
Normal file
9
lib/filestream/filestream_windows.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package filestream
|
||||
|
||||
func (st *streamTracker) adviseDontNeed(n int, fdatasync bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *streamTracker) close() error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package flagutil
|
||||
|
||||
import "strings"
|
||||
|
||||
// Array holds an array of flag values
|
||||
type Array []string
|
||||
|
||||
// String implements flag.Value interface
|
||||
func (a *Array) String() string {
|
||||
return strings.Join(*a, ",")
|
||||
}
|
||||
|
||||
// Set implements flag.Value interface
|
||||
func (a *Array) Set(value string) error {
|
||||
values := strings.Split(value, ",")
|
||||
*a = append(*a, values...)
|
||||
return nil
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package flagutil
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var fooFlag Array
|
||||
|
||||
func init() {
|
||||
os.Args = append(os.Args, "--fooFlag=foo", "--fooFlag=bar")
|
||||
flag.Var(&fooFlag, "fooFlag", "test")
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
flag.Parse()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestArray(t *testing.T) {
|
||||
expected := map[string]struct{}{
|
||||
"foo": {},
|
||||
"bar": {},
|
||||
}
|
||||
if len(expected) != len(fooFlag) {
|
||||
t.Errorf("len array flag (%d) is not equal to %d", len(fooFlag), len(expected))
|
||||
}
|
||||
for _, i := range fooFlag {
|
||||
if _, ok := expected[i]; !ok {
|
||||
t.Errorf("unexpected item in array %v", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
106
lib/fs/fs.go
106
lib/fs/fs.go
@@ -5,6 +5,8 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/filestream"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
@@ -70,8 +72,8 @@ var (
|
||||
readersCount = metrics.NewCounter(`vm_fs_readers`)
|
||||
)
|
||||
|
||||
// SyncPath syncs contents of the given path.
|
||||
func SyncPath(path string) {
|
||||
// MustSyncPath syncs contents of the given path.
|
||||
func MustSyncPath(path string) {
|
||||
d, err := os.Open(path)
|
||||
if err != nil {
|
||||
logger.Panicf("FATAL: cannot open %q: %s", path, err)
|
||||
@@ -111,8 +113,8 @@ func WriteFile(path string, data []byte) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot obtain absolute path to %q: %s", path, err)
|
||||
}
|
||||
parentDirPath, _ := filepath.Split(absPath)
|
||||
SyncPath(parentDirPath)
|
||||
parentDirPath := filepath.Dir(absPath)
|
||||
MustSyncPath(parentDirPath)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -122,7 +124,7 @@ func MkdirAllIfNotExist(path string) error {
|
||||
if IsPathExist(path) {
|
||||
return nil
|
||||
}
|
||||
return os.MkdirAll(path, 0755)
|
||||
return mkdirSync(path)
|
||||
}
|
||||
|
||||
// MkdirAllFailIfExist creates the given path dir if it isn't exist.
|
||||
@@ -132,7 +134,18 @@ func MkdirAllFailIfExist(path string) error {
|
||||
if IsPathExist(path) {
|
||||
return fmt.Errorf("the %q already exists", path)
|
||||
}
|
||||
return os.MkdirAll(path, 0755)
|
||||
return mkdirSync(path)
|
||||
}
|
||||
|
||||
func mkdirSync(path string) error {
|
||||
if err := os.MkdirAll(path, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
// Sync the parent directory, so the created directory becomes visible
|
||||
// in the fs after power loss.
|
||||
parentDirPath := filepath.Dir(path)
|
||||
MustSyncPath(parentDirPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveDirContents removes all the contents of the given dir it it exists.
|
||||
@@ -159,11 +172,9 @@ func RemoveDirContents(dir string) {
|
||||
continue
|
||||
}
|
||||
fullPath := dir + "/" + name
|
||||
if err := os.RemoveAll(fullPath); err != nil {
|
||||
logger.Panicf("FATAL: cannot remove %q: %s", fullPath, err)
|
||||
}
|
||||
MustRemoveAll(fullPath)
|
||||
}
|
||||
SyncPath(dir)
|
||||
MustSyncPath(dir)
|
||||
}
|
||||
|
||||
// MustClose must close the given file f.
|
||||
@@ -185,18 +196,79 @@ func IsPathExist(path string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// MustRemoveAllSynced removes path with all the contents
|
||||
// and syncs the parent directory, so it no longer contains the path.
|
||||
func MustRemoveAllSynced(path string) {
|
||||
if err := os.RemoveAll(path); err != nil {
|
||||
func mustSyncParentDirIfExists(path string) {
|
||||
parentDirPath := filepath.Dir(path)
|
||||
if !IsPathExist(parentDirPath) {
|
||||
return
|
||||
}
|
||||
MustSyncPath(parentDirPath)
|
||||
}
|
||||
|
||||
// MustRemoveAll removes path with all the contents.
|
||||
//
|
||||
// It properly handles NFS issue https://github.com/VictoriaMetrics/VictoriaMetrics/issues/61 .
|
||||
func MustRemoveAll(path string) {
|
||||
err := os.RemoveAll(path)
|
||||
if err == nil {
|
||||
// Make sure the parent directory doesn't contain references
|
||||
// to the current directory.
|
||||
mustSyncParentDirIfExists(path)
|
||||
return
|
||||
}
|
||||
if !isTemporaryNFSError(err) {
|
||||
logger.Panicf("FATAL: cannot remove %q: %s", path, err)
|
||||
}
|
||||
SyncPath(filepath.Dir(path))
|
||||
// NFS prevents from removing directories with open files.
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/61 .
|
||||
// Schedule for later directory removal.
|
||||
select {
|
||||
case removeDirCh <- path:
|
||||
default:
|
||||
logger.Panicf("FATAL: cannot schedule %s for removal, since the removal queue is full (%d entries)", path, cap(removeDirCh))
|
||||
}
|
||||
}
|
||||
|
||||
var removeDirCh = make(chan string, 1024)
|
||||
|
||||
func dirRemover() {
|
||||
for path := range removeDirCh {
|
||||
attempts := 0
|
||||
for {
|
||||
err := os.RemoveAll(path)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if !isTemporaryNFSError(err) {
|
||||
logger.Panicf("FATAL: cannot remove %q: %s", path, err)
|
||||
}
|
||||
// NFS prevents from removing directories with open files.
|
||||
// Sleep for a while and try again in the hope open files will be closed.
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/61 .
|
||||
attempts++
|
||||
if attempts > 10 {
|
||||
logger.Panicf("FATAL: cannot remove %q in %d attempts: %s", path, attempts, err)
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
// Make sure the parent directory doesn't contain references
|
||||
// to the current directory.
|
||||
mustSyncParentDirIfExists(path)
|
||||
}
|
||||
}
|
||||
|
||||
func isTemporaryNFSError(err error) bool {
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/61 for details.
|
||||
errStr := err.Error()
|
||||
return strings.Contains(errStr, "directory not empty") || strings.Contains(errStr, "device or resource busy")
|
||||
}
|
||||
|
||||
func init() {
|
||||
go dirRemover()
|
||||
}
|
||||
|
||||
// HardLinkFiles makes hard links for all the files from srcDir in dstDir.
|
||||
func HardLinkFiles(srcDir, dstDir string) error {
|
||||
if err := os.MkdirAll(dstDir, 0755); err != nil {
|
||||
if err := mkdirSync(dstDir); err != nil {
|
||||
return fmt.Errorf("cannot create dstDir=%q: %s", dstDir, err)
|
||||
}
|
||||
|
||||
@@ -227,7 +299,7 @@ func HardLinkFiles(srcDir, dstDir string) error {
|
||||
}
|
||||
}
|
||||
|
||||
SyncPath(dstDir)
|
||||
MustSyncPath(dstDir)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ var (
|
||||
httpAuthPassword = flag.String("httpAuth.password", "", "Password for HTTP Basic Auth. The authentication is disabled -httpAuth.username is empty")
|
||||
metricsAuthKey = flag.String("metricsAuthKey", "", "Auth key for /metrics. It overrides httpAuth settings")
|
||||
pprofAuthKey = flag.String("pprofAuthKey", "", "Auth key for /debug/pprof. It overrides httpAuth settings")
|
||||
|
||||
disableResponseCompression = flag.Bool("http.disableResponseCompression", false, "Disable compression of HTTP responses for saving CPU resources. By default compression is enabled to save network bandwidth")
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -51,6 +53,8 @@ type RequestHandler func(w http.ResponseWriter, r *http.Request) bool
|
||||
// By default all the responses are transparently compressed, since Google
|
||||
// charges a lot for the egress traffic. The compression may be disabled
|
||||
// by calling DisableResponseCompression before writing the first byte to w.
|
||||
//
|
||||
// The compression is also disabled if -http.disableResponseCompression flag is set.
|
||||
func Serve(addr string, rh RequestHandler) {
|
||||
scheme := "http"
|
||||
if *tlsEnable {
|
||||
@@ -224,6 +228,9 @@ func checkBasicAuth(w http.ResponseWriter, r *http.Request) bool {
|
||||
}
|
||||
|
||||
func maybeGzipResponseWriter(w http.ResponseWriter, r *http.Request) http.ResponseWriter {
|
||||
if *disableResponseCompression {
|
||||
return w
|
||||
}
|
||||
ae := r.Header.Get("Accept-Encoding")
|
||||
if ae == "" {
|
||||
return w
|
||||
|
||||
40
lib/memory/memory_windows.go
Normal file
40
lib/memory/memory_windows.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package memory
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
)
|
||||
|
||||
// This has been adapted from https://github.com/pbnjay/memory.
|
||||
|
||||
type memStatusEx struct {
|
||||
dwLength uint32
|
||||
dwMemoryLoad uint32
|
||||
ullTotalPhys uint64
|
||||
unused [6]uint64
|
||||
}
|
||||
|
||||
func sysTotalMemory() int {
|
||||
kernel32, err := syscall.LoadDLL("kernel32.dll")
|
||||
if err != nil {
|
||||
logger.Panicf("FATAL: cannot load kernel32.dll: %s", err)
|
||||
}
|
||||
globalMemoryStatusEx, err := kernel32.FindProc("GlobalMemoryStatusEx")
|
||||
if err != nil {
|
||||
logger.Panicf("FATAL: cannot find GlobalMemoryStatusEx: %s", err)
|
||||
}
|
||||
msx := &memStatusEx{
|
||||
dwLength: uint32(unsafe.Sizeof(memStatusEx{})),
|
||||
}
|
||||
r, _, err := globalMemoryStatusEx.Call(uintptr(unsafe.Pointer(msx)))
|
||||
if r == 0 {
|
||||
logger.Panicf("FATAL: error in GlobalMemoryStatusEx: %s", err)
|
||||
}
|
||||
n := int(msx.ullTotalPhys)
|
||||
if uint64(n) != msx.ullTotalPhys {
|
||||
logger.Panicf("FATAL: int overflow for msx.ullTotalPhys=%d", msx.ullTotalPhys)
|
||||
}
|
||||
return n
|
||||
}
|
||||
@@ -91,6 +91,13 @@ func (bsr *blockStreamReader) reset() {
|
||||
bsr.err = nil
|
||||
}
|
||||
|
||||
func (bsr *blockStreamReader) String() string {
|
||||
if len(bsr.path) > 0 {
|
||||
return bsr.path
|
||||
}
|
||||
return bsr.ph.String()
|
||||
}
|
||||
|
||||
// InitFromInmemoryPart initializes bsr from the given ip.
|
||||
func (bsr *blockStreamReader) InitFromInmemoryPart(ip *inmemoryPart) {
|
||||
bsr.reset()
|
||||
|
||||
@@ -2,7 +2,6 @@ package mergeset
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
@@ -92,7 +91,7 @@ func (bsw *blockStreamWriter) InitFromFilePart(path string, nocache bool, compre
|
||||
metaindexPath := path + "/metaindex.bin"
|
||||
metaindexFile, err := filestream.Create(metaindexPath, false)
|
||||
if err != nil {
|
||||
_ = os.RemoveAll(path)
|
||||
fs.MustRemoveAll(path)
|
||||
return fmt.Errorf("cannot create metaindex file: %s", err)
|
||||
}
|
||||
|
||||
@@ -100,7 +99,7 @@ func (bsw *blockStreamWriter) InitFromFilePart(path string, nocache bool, compre
|
||||
indexFile, err := filestream.Create(indexPath, nocache)
|
||||
if err != nil {
|
||||
metaindexFile.MustClose()
|
||||
_ = os.RemoveAll(path)
|
||||
fs.MustRemoveAll(path)
|
||||
return fmt.Errorf("cannot create index file: %s", err)
|
||||
}
|
||||
|
||||
@@ -109,7 +108,7 @@ func (bsw *blockStreamWriter) InitFromFilePart(path string, nocache bool, compre
|
||||
if err != nil {
|
||||
metaindexFile.MustClose()
|
||||
indexFile.MustClose()
|
||||
_ = os.RemoveAll(path)
|
||||
fs.MustRemoveAll(path)
|
||||
return fmt.Errorf("cannot create items file: %s", err)
|
||||
}
|
||||
|
||||
@@ -119,7 +118,7 @@ func (bsw *blockStreamWriter) InitFromFilePart(path string, nocache bool, compre
|
||||
metaindexFile.MustClose()
|
||||
indexFile.MustClose()
|
||||
itemsFile.MustClose()
|
||||
_ = os.RemoveAll(path)
|
||||
fs.MustRemoveAll(path)
|
||||
return fmt.Errorf("cannot create lens file: %s", err)
|
||||
}
|
||||
|
||||
@@ -152,6 +151,12 @@ func (bsw *blockStreamWriter) MustClose() {
|
||||
bsw.itemsWriter.MustClose()
|
||||
bsw.lensWriter.MustClose()
|
||||
|
||||
// Sync bsw.path contents to make sure it doesn't disappear
|
||||
// after system crash or power loss.
|
||||
if bsw.path != "" {
|
||||
fs.MustSyncPath(bsw.path)
|
||||
}
|
||||
|
||||
bsw.reset()
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ func mergeBlockStreams(ph *partHeader, bsw *blockStreamWriter, bsrs []*blockStre
|
||||
if err == errForciblyStopped {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("cannot merge block streams: %s", err)
|
||||
return fmt.Errorf("cannot merge %d block streams: %s: %s", len(bsrs), bsrs, err)
|
||||
}
|
||||
|
||||
var bsmPool = &sync.Pool{
|
||||
|
||||
@@ -208,7 +208,7 @@ func (tb *Table) MustClose() {
|
||||
logger.Infof("%d inmemory parts have been flushed to files in %s on %q", len(pws), time.Since(startTime), tb.path)
|
||||
|
||||
// Remove references to parts from the tb, so they may be eventually closed
|
||||
// after all the seraches are done.
|
||||
// after all the searches are done.
|
||||
tb.partsLock.Lock()
|
||||
parts := tb.parts
|
||||
tb.parts = nil
|
||||
@@ -846,22 +846,18 @@ func openParts(path string) ([]*partWrapper, error) {
|
||||
}
|
||||
|
||||
txnDir := path + "/txn"
|
||||
if err := os.RemoveAll(txnDir); err != nil {
|
||||
return nil, fmt.Errorf("cannot remove %q: %s", txnDir, err)
|
||||
}
|
||||
fs.MustRemoveAll(txnDir)
|
||||
if err := fs.MkdirAllFailIfExist(txnDir); err != nil {
|
||||
return nil, fmt.Errorf("cannot create %q: %s", txnDir, err)
|
||||
}
|
||||
|
||||
tmpDir := path + "/tmp"
|
||||
if err := os.RemoveAll(tmpDir); err != nil {
|
||||
return nil, fmt.Errorf("cannot remove %q: %s", tmpDir, err)
|
||||
}
|
||||
fs.MustRemoveAll(tmpDir)
|
||||
if err := fs.MkdirAllFailIfExist(tmpDir); err != nil {
|
||||
return nil, fmt.Errorf("cannot create %q: %s", tmpDir, err)
|
||||
}
|
||||
|
||||
fs.SyncPath(path)
|
||||
fs.MustSyncPath(path)
|
||||
|
||||
// Open parts.
|
||||
fis, err := d.Readdir(-1)
|
||||
@@ -965,9 +961,9 @@ func (tb *Table) CreateSnapshotAt(dstDir string) error {
|
||||
}
|
||||
}
|
||||
|
||||
fs.SyncPath(dstDir)
|
||||
fs.MustSyncPath(dstDir)
|
||||
parentDir := filepath.Dir(dstDir)
|
||||
fs.SyncPath(parentDir)
|
||||
fs.MustSyncPath(parentDir)
|
||||
|
||||
logger.Infof("created Table snapshot of %q at %q in %s", srcDir, dstDir, time.Since(startTime))
|
||||
return nil
|
||||
@@ -1033,9 +1029,7 @@ func runTransaction(txnLock *sync.RWMutex, pathPrefix, txnPath string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid path to remove: %s", err)
|
||||
}
|
||||
if err := os.RemoveAll(path); err != nil {
|
||||
return fmt.Errorf("cannot remove %q: %s", path, err)
|
||||
}
|
||||
fs.MustRemoveAll(path)
|
||||
}
|
||||
|
||||
// Move the new part to new directory.
|
||||
@@ -1061,7 +1055,7 @@ func runTransaction(txnLock *sync.RWMutex, pathPrefix, txnPath string) error {
|
||||
}
|
||||
|
||||
// Flush pathPrefix directory metadata to the underying storage.
|
||||
fs.SyncPath(pathPrefix)
|
||||
fs.MustSyncPath(pathPrefix)
|
||||
|
||||
// Remove the transaction file.
|
||||
if err := os.Remove(txnPath); err != nil {
|
||||
|
||||
@@ -33,6 +33,9 @@ func benchmarkTableSearch(b *testing.B, itemsCount int) {
|
||||
// Force finishing pending merges
|
||||
tb.MustClose()
|
||||
tb, err = OpenTable(path)
|
||||
if err != nil {
|
||||
b.Fatalf("unexpected error when re-opening table %q: %s", path, err)
|
||||
}
|
||||
defer tb.MustClose()
|
||||
|
||||
keys := make([][]byte, len(items))
|
||||
|
||||
@@ -47,8 +47,6 @@ type TCPListener struct {
|
||||
|
||||
net.Listener
|
||||
|
||||
name string
|
||||
|
||||
accepts *metrics.Counter
|
||||
acceptErrors *metrics.Counter
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// WaitForSigterm waits fro either SIGTERM or SIGINT
|
||||
// WaitForSigterm waits for either SIGTERM or SIGINT
|
||||
//
|
||||
// Returns the caught signal.
|
||||
func WaitForSigterm() os.Signal {
|
||||
|
||||
@@ -11,13 +11,9 @@ import (
|
||||
// ReadSnappy reads r, unpacks it using snappy, appends it to dst
|
||||
// and returns the result.
|
||||
func ReadSnappy(dst []byte, r io.Reader, maxSize int64) ([]byte, error) {
|
||||
bb := bodyBufferPool.Get()
|
||||
bb.B = bb.B[:0]
|
||||
cb := copyBufferPool.Get()
|
||||
cb.B = bytesutil.Resize(cb.B, 16*1024)
|
||||
lr := io.LimitReader(r, maxSize+1)
|
||||
reqLen, err := io.CopyBuffer(bb, lr, cb.B)
|
||||
copyBufferPool.Put(cb)
|
||||
bb := bodyBufferPool.Get()
|
||||
reqLen, err := bb.ReadFrom(lr)
|
||||
if err != nil {
|
||||
bodyBufferPool.Put(bb)
|
||||
return dst, fmt.Errorf("cannot read compressed request: %s", err)
|
||||
@@ -45,7 +41,6 @@ func ReadSnappy(dst []byte, r io.Reader, maxSize int64) ([]byte, error) {
|
||||
}
|
||||
|
||||
var bodyBufferPool bytesutil.ByteBufferPool
|
||||
var copyBufferPool bytesutil.ByteBufferPool
|
||||
|
||||
// Reset resets wr.
|
||||
func (wr *WriteRequest) Reset() {
|
||||
|
||||
@@ -2,7 +2,6 @@ package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -85,7 +84,7 @@ func (bsw *blockStreamWriter) InitFromFilePart(path string, nocache bool, compre
|
||||
timestampsPath := path + "/timestamps.bin"
|
||||
timestampsFile, err := filestream.Create(timestampsPath, nocache)
|
||||
if err != nil {
|
||||
_ = os.RemoveAll(path)
|
||||
fs.MustRemoveAll(path)
|
||||
return fmt.Errorf("cannot create timestamps file: %s", err)
|
||||
}
|
||||
|
||||
@@ -93,7 +92,7 @@ func (bsw *blockStreamWriter) InitFromFilePart(path string, nocache bool, compre
|
||||
valuesFile, err := filestream.Create(valuesPath, nocache)
|
||||
if err != nil {
|
||||
timestampsFile.MustClose()
|
||||
_ = os.RemoveAll(path)
|
||||
fs.MustRemoveAll(path)
|
||||
return fmt.Errorf("cannot create values file: %s", err)
|
||||
}
|
||||
|
||||
@@ -102,7 +101,7 @@ func (bsw *blockStreamWriter) InitFromFilePart(path string, nocache bool, compre
|
||||
if err != nil {
|
||||
timestampsFile.MustClose()
|
||||
valuesFile.MustClose()
|
||||
_ = os.RemoveAll(path)
|
||||
fs.MustRemoveAll(path)
|
||||
return fmt.Errorf("cannot create index file: %s", err)
|
||||
}
|
||||
|
||||
@@ -114,7 +113,7 @@ func (bsw *blockStreamWriter) InitFromFilePart(path string, nocache bool, compre
|
||||
timestampsFile.MustClose()
|
||||
valuesFile.MustClose()
|
||||
indexFile.MustClose()
|
||||
_ = os.RemoveAll(path)
|
||||
fs.MustRemoveAll(path)
|
||||
return fmt.Errorf("cannot create metaindex file: %s", err)
|
||||
}
|
||||
|
||||
@@ -147,6 +146,12 @@ func (bsw *blockStreamWriter) MustClose() {
|
||||
bsw.indexWriter.MustClose()
|
||||
bsw.metaindexWriter.MustClose()
|
||||
|
||||
// Sync bsw.path contents to make sure it doesn't disappear
|
||||
// after system crash or power loss.
|
||||
if bsw.path != "" {
|
||||
fs.MustSyncPath(bsw.path)
|
||||
}
|
||||
|
||||
bsw.reset()
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"sync"
|
||||
@@ -15,6 +14,7 @@ import (
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/memory"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/mergeset"
|
||||
@@ -60,6 +60,10 @@ type indexDB struct {
|
||||
// Cache for fast MetricID -> MetricName lookup.
|
||||
metricNameCache *fastcache.Cache
|
||||
|
||||
// Cache holding useless TagFilters entries, which have no tag filters
|
||||
// matching low number of metrics.
|
||||
uselessTagFiltersCache *fastcache.Cache
|
||||
|
||||
indexSearchPool sync.Pool
|
||||
|
||||
// An inmemory map[uint64]struct{} of deleted metricIDs.
|
||||
@@ -71,15 +75,46 @@ type indexDB struct {
|
||||
deletedMetricIDs atomic.Value
|
||||
deletedMetricIDsUpdateLock sync.Mutex
|
||||
|
||||
// Global lists of metric ids for the current and the previous hours.
|
||||
// They are used for fast lookups on small time ranges covering
|
||||
// up to two last hours.
|
||||
currHourMetricIDs *atomic.Value
|
||||
prevHourMetricIDs *atomic.Value
|
||||
|
||||
// The number of missing MetricID -> TSID entries.
|
||||
// High rate for this value means corrupted indexDB.
|
||||
missingTSIDsForMetricID uint64
|
||||
|
||||
// The number of calls to search for metric ids for recent hours.
|
||||
recentHourMetricIDsSearchCalls uint64
|
||||
|
||||
// The number of cache hits during search for metric ids in recent hours.
|
||||
recentHourMetricIDsSearchHits uint64
|
||||
|
||||
// The number of searches for metric ids by days.
|
||||
dateMetricIDsSearchCalls uint64
|
||||
|
||||
// The number of successful searches for metric ids by days.
|
||||
dateMetricIDsSearchHits uint64
|
||||
|
||||
mustDrop uint64
|
||||
}
|
||||
|
||||
// openIndexDB opens index db from the given path with the given caches.
|
||||
func openIndexDB(path string, metricIDCache, metricNameCache *fastcache.Cache) (*indexDB, error) {
|
||||
func openIndexDB(path string, metricIDCache, metricNameCache *fastcache.Cache, currHourMetricIDs, prevHourMetricIDs *atomic.Value) (*indexDB, error) {
|
||||
if metricIDCache == nil {
|
||||
logger.Panicf("BUG: metricIDCache must be non-nil")
|
||||
}
|
||||
if metricNameCache == nil {
|
||||
logger.Panicf("BUG: metricNameCache must be non-nil")
|
||||
}
|
||||
if currHourMetricIDs == nil {
|
||||
logger.Panicf("BUG: currHourMetricIDs must be non-nil")
|
||||
}
|
||||
if prevHourMetricIDs == nil {
|
||||
logger.Panicf("BUG: prevHourMetricIDs must be non-nil")
|
||||
}
|
||||
|
||||
tb, err := mergeset.OpenTable(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot open indexDB %q: %s", path, err)
|
||||
@@ -89,16 +124,19 @@ func openIndexDB(path string, metricIDCache, metricNameCache *fastcache.Cache) (
|
||||
|
||||
// Do not persist tagCache in files, since it is very volatile.
|
||||
mem := memory.Allowed()
|
||||
tagCache := fastcache.New(mem / 32)
|
||||
|
||||
db := &indexDB{
|
||||
refCount: 1,
|
||||
tb: tb,
|
||||
name: name,
|
||||
|
||||
tagCache: tagCache,
|
||||
metricIDCache: metricIDCache,
|
||||
metricNameCache: metricNameCache,
|
||||
tagCache: fastcache.New(mem / 32),
|
||||
metricIDCache: metricIDCache,
|
||||
metricNameCache: metricNameCache,
|
||||
uselessTagFiltersCache: fastcache.New(mem / 128),
|
||||
|
||||
currHourMetricIDs: currHourMetricIDs,
|
||||
prevHourMetricIDs: prevHourMetricIDs,
|
||||
}
|
||||
|
||||
is := db.getIndexSearch()
|
||||
@@ -119,12 +157,22 @@ type IndexDBMetrics struct {
|
||||
TagCacheRequests uint64
|
||||
TagCacheMisses uint64
|
||||
|
||||
UselessTagFiltersCacheSize uint64
|
||||
UselessTagFiltersCacheBytesSize uint64
|
||||
UselessTagFiltersCacheRequests uint64
|
||||
UselessTagFiltersCacheMisses uint64
|
||||
|
||||
DeletedMetricsCount uint64
|
||||
|
||||
IndexDBRefCount uint64
|
||||
|
||||
MissingTSIDsForMetricID uint64
|
||||
|
||||
RecentHourMetricIDsSearchCalls uint64
|
||||
RecentHourMetricIDsSearchHits uint64
|
||||
DateMetricIDsSearchCalls uint64
|
||||
DateMetricIDsSearchHits uint64
|
||||
|
||||
mergeset.TableMetrics
|
||||
}
|
||||
|
||||
@@ -135,16 +183,29 @@ func (db *indexDB) scheduleToDrop() {
|
||||
// UpdateMetrics updates m with metrics from the db.
|
||||
func (db *indexDB) UpdateMetrics(m *IndexDBMetrics) {
|
||||
var cs fastcache.Stats
|
||||
|
||||
cs.Reset()
|
||||
db.tagCache.UpdateStats(&cs)
|
||||
m.TagCacheSize += cs.EntriesCount
|
||||
m.TagCacheBytesSize += cs.BytesSize
|
||||
m.TagCacheRequests += cs.GetBigCalls
|
||||
m.TagCacheMisses += cs.Misses
|
||||
|
||||
cs.Reset()
|
||||
db.uselessTagFiltersCache.UpdateStats(&cs)
|
||||
m.UselessTagFiltersCacheSize += cs.EntriesCount
|
||||
m.UselessTagFiltersCacheBytesSize += cs.BytesSize
|
||||
m.UselessTagFiltersCacheRequests += cs.GetBigCalls
|
||||
m.UselessTagFiltersCacheMisses += cs.Misses
|
||||
|
||||
m.DeletedMetricsCount += uint64(len(db.getDeletedMetricIDs()))
|
||||
|
||||
m.IndexDBRefCount += atomic.LoadUint64(&db.refCount)
|
||||
m.MissingTSIDsForMetricID += atomic.LoadUint64(&db.missingTSIDsForMetricID)
|
||||
m.RecentHourMetricIDsSearchCalls += atomic.LoadUint64(&db.recentHourMetricIDsSearchCalls)
|
||||
m.RecentHourMetricIDsSearchHits += atomic.LoadUint64(&db.recentHourMetricIDsSearchHits)
|
||||
m.DateMetricIDsSearchCalls += atomic.LoadUint64(&db.dateMetricIDsSearchCalls)
|
||||
m.DateMetricIDsSearchHits += atomic.LoadUint64(&db.dateMetricIDsSearchHits)
|
||||
|
||||
db.tb.UpdateMetrics(&m.TableMetrics)
|
||||
db.doExtDB(func(extDB *indexDB) {
|
||||
@@ -200,7 +261,7 @@ func (db *indexDB) incRef() {
|
||||
|
||||
func (db *indexDB) decRef() {
|
||||
n := atomic.AddUint64(&db.refCount, ^uint64(0))
|
||||
if n < 0 {
|
||||
if int64(n) < 0 {
|
||||
logger.Panicf("BUG: negative refCount: %d", n)
|
||||
}
|
||||
if n > 0 {
|
||||
@@ -211,14 +272,21 @@ func (db *indexDB) decRef() {
|
||||
db.tb.MustClose()
|
||||
db.SetExtDB(nil)
|
||||
|
||||
// Free space occupied by caches owned by db.
|
||||
db.tagCache.Reset()
|
||||
db.uselessTagFiltersCache.Reset()
|
||||
|
||||
db.tagCache = nil
|
||||
db.metricIDCache = nil
|
||||
db.metricNameCache = nil
|
||||
db.uselessTagFiltersCache = nil
|
||||
|
||||
if atomic.LoadUint64(&db.mustDrop) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Infof("dropping indexDB %q", tbPath)
|
||||
if err := os.RemoveAll(tbPath); err != nil {
|
||||
logger.Panicf("FATAL: cannot remove %q: %s", tbPath, err)
|
||||
}
|
||||
fs.MustRemoveAll(tbPath)
|
||||
logger.Infof("indexDB %q has been dropped", tbPath)
|
||||
}
|
||||
|
||||
@@ -273,8 +341,11 @@ func (db *indexDB) putMetricNameToCache(metricID uint64, metricName []byte) {
|
||||
db.metricNameCache.Set(key[:], metricName)
|
||||
}
|
||||
|
||||
func marshalTagFiltersKey(dst []byte, tfss []*TagFilters) []byte {
|
||||
prefix := atomic.LoadUint64(&tagFiltersKeyGen)
|
||||
func marshalTagFiltersKey(dst []byte, tfss []*TagFilters, versioned bool) []byte {
|
||||
prefix := ^uint64(0)
|
||||
if versioned {
|
||||
prefix = atomic.LoadUint64(&tagFiltersKeyGen)
|
||||
}
|
||||
dst = encoding.MarshalUint64(dst, prefix)
|
||||
for _, tfs := range tfss {
|
||||
dst = append(dst, 0) // separator between tfs groups.
|
||||
@@ -718,7 +789,7 @@ func (is *indexSearch) searchTagValues(tvs map[string]struct{}, prefix []byte, m
|
||||
// up to two times - in db and extDB.
|
||||
func (db *indexDB) GetSeriesCount() (uint64, error) {
|
||||
is := db.getIndexSearch()
|
||||
n, err := getSeriesCount(&is.ts, &is.kb)
|
||||
n, err := is.getSeriesCount()
|
||||
db.putIndexSearch(is)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -727,7 +798,7 @@ func (db *indexDB) GetSeriesCount() (uint64, error) {
|
||||
var nExt uint64
|
||||
ok := db.doExtDB(func(extDB *indexDB) {
|
||||
is := extDB.getIndexSearch()
|
||||
nExt, err = getSeriesCount(&is.ts, &is.kb)
|
||||
nExt, err = is.getSeriesCount()
|
||||
extDB.putIndexSearch(is)
|
||||
})
|
||||
if ok && err != nil {
|
||||
@@ -872,7 +943,7 @@ func (db *indexDB) searchTSIDs(tfss []*TagFilters, tr TimeRange, maxMetrics int)
|
||||
tfKeyBuf := tagFiltersKeyBufPool.Get()
|
||||
defer tagFiltersKeyBufPool.Put(tfKeyBuf)
|
||||
|
||||
tfKeyBuf.B = marshalTagFiltersKey(tfKeyBuf.B[:0], tfss)
|
||||
tfKeyBuf.B = marshalTagFiltersKey(tfKeyBuf.B[:0], tfss, true)
|
||||
tsids, ok := db.getFromTagCache(tfKeyBuf.B)
|
||||
if ok {
|
||||
// Fast path - tsids found in the cache.
|
||||
@@ -889,7 +960,12 @@ func (db *indexDB) searchTSIDs(tfss []*TagFilters, tr TimeRange, maxMetrics int)
|
||||
|
||||
var extTSIDs []TSID
|
||||
if db.doExtDB(func(extDB *indexDB) {
|
||||
tsids, ok := extDB.getFromTagCache(tfKeyBuf.B)
|
||||
tfKeyExtBuf := tagFiltersKeyBufPool.Get()
|
||||
defer tagFiltersKeyBufPool.Put(tfKeyExtBuf)
|
||||
|
||||
// Data in extDB cannot be changed, so use unversioned keys for tag cache.
|
||||
tfKeyExtBuf.B = marshalTagFiltersKey(tfKeyExtBuf.B[:0], tfss, false)
|
||||
tsids, ok := extDB.getFromTagCache(tfKeyExtBuf.B)
|
||||
if ok {
|
||||
extTSIDs = tsids
|
||||
return
|
||||
@@ -898,8 +974,7 @@ func (db *indexDB) searchTSIDs(tfss []*TagFilters, tr TimeRange, maxMetrics int)
|
||||
extTSIDs, err = is.searchTSIDs(tfss, tr, maxMetrics)
|
||||
extDB.putIndexSearch(is)
|
||||
|
||||
// Do not store found tsids into extDB.tagCache,
|
||||
// since they will be stored into outer cache instead.
|
||||
db.putToTagCache(tsids, tfKeyExtBuf.B)
|
||||
}) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -945,7 +1020,7 @@ func (is *indexSearch) getTSIDByMetricName(dst *TSID, metricName []byte) error {
|
||||
if len(dmis) > 0 {
|
||||
// Verify whether the dst is marked as deleted.
|
||||
if _, deleted := dmis[dst.MetricID]; deleted {
|
||||
// The dst is deleted. Continue seraching.
|
||||
// The dst is deleted. Continue searching.
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -1009,6 +1084,15 @@ func mergeTSIDs(a, b []TSID) []TSID {
|
||||
}
|
||||
|
||||
func (is *indexSearch) searchTSIDs(tfss []*TagFilters, tr TimeRange, maxMetrics int) ([]TSID, error) {
|
||||
// Verify whether `is` contains data for the given tr.
|
||||
ok, err := is.containsTimeRange(tr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in containsTimeRange(%s): %s", &tr, err)
|
||||
}
|
||||
if !ok {
|
||||
// Fast path: nothing to search.
|
||||
return nil, nil
|
||||
}
|
||||
metricIDs, err := is.searchMetricIDs(tfss, tr, maxMetrics)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1078,7 +1162,9 @@ func (is *indexSearch) getTSIDByMetricID(dst *TSID, metricID uint64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getSeriesCount(ts *mergeset.TableSearch, kb *bytesutil.ByteBuffer) (uint64, error) {
|
||||
func (is *indexSearch) getSeriesCount() (uint64, error) {
|
||||
ts := &is.ts
|
||||
kb := &is.kb
|
||||
var n uint64
|
||||
kb.B = append(kb.B[:0], nsPrefixMetricIDToTSID)
|
||||
ts.Seek(kb.B)
|
||||
@@ -1095,9 +1181,9 @@ func getSeriesCount(ts *mergeset.TableSearch, kb *bytesutil.ByteBuffer) (uint64,
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// searchMetricIDsMapByMetricNameMatch matches metricName values for the given srcMetricIDs against tfs
|
||||
// updateMetricIDsByMetricNameMatch matches metricName values for the given srcMetricIDs against tfs
|
||||
// and adds matching metrics to metricIDs.
|
||||
func (is *indexSearch) searchMetricIDsMapByMetricNameMatch(metricIDs, srcMetricIDs map[uint64]struct{}, tfs []*tagFilter) error {
|
||||
func (is *indexSearch) updateMetricIDsByMetricNameMatch(metricIDs, srcMetricIDs map[uint64]struct{}, tfs []*tagFilter) error {
|
||||
// sort srcMetricIDs in order to speed up Seek below.
|
||||
sortedMetricIDs := make([]uint64, 0, len(srcMetricIDs))
|
||||
for metricID := range srcMetricIDs {
|
||||
@@ -1132,7 +1218,66 @@ func (is *indexSearch) searchMetricIDsMapByMetricNameMatch(metricIDs, srcMetricI
|
||||
return nil
|
||||
}
|
||||
|
||||
func (is *indexSearch) getTagFilterWithMinMetricIDsMap(tfs *TagFilters, maxMetrics int) (*tagFilter, map[uint64]struct{}, error) {
|
||||
func (is *indexSearch) getTagFilterWithMinMetricIDsCountAdaptive(tfs *TagFilters, maxMetrics int) (*tagFilter, map[uint64]struct{}, error) {
|
||||
maxMetrics = is.adjustMaxMetricsAdaptive(maxMetrics)
|
||||
kb := &is.kb
|
||||
kb.B = tfs.marshal(kb.B[:0])
|
||||
kb.B = encoding.MarshalUint64(kb.B, uint64(maxMetrics))
|
||||
if len(is.db.uselessTagFiltersCache.Get(nil, kb.B)) > 0 {
|
||||
// Skip useless work below, since the tfs doesn't contain tag filters matching less than maxMetrics metrics.
|
||||
return nil, nil, errTooManyMetrics
|
||||
}
|
||||
|
||||
// Iteratively increase maxAllowedMetrics up to maxMetrics in order to limit
|
||||
// the time required for founding the tag filter with minimum matching metrics.
|
||||
maxAllowedMetrics := 16
|
||||
if maxAllowedMetrics > maxMetrics {
|
||||
maxAllowedMetrics = maxMetrics
|
||||
}
|
||||
for {
|
||||
minTf, minMetricIDs, err := is.getTagFilterWithMinMetricIDsCount(tfs, maxAllowedMetrics)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(minMetricIDs) < maxAllowedMetrics {
|
||||
// Found the tag filter with the minimum number of metrics.
|
||||
return minTf, minMetricIDs, nil
|
||||
}
|
||||
|
||||
// Too many metrics matched.
|
||||
if maxAllowedMetrics >= maxMetrics {
|
||||
// The tag filter with minimum matching metrics matches at least maxMetrics metrics.
|
||||
kb.B = tfs.marshal(kb.B[:0])
|
||||
kb.B = encoding.MarshalUint64(kb.B, uint64(maxMetrics))
|
||||
is.db.uselessTagFiltersCache.Set(kb.B, []byte("1"))
|
||||
return nil, nil, errTooManyMetrics
|
||||
}
|
||||
|
||||
// Increase maxAllowedMetrics and try again.
|
||||
maxAllowedMetrics *= 4
|
||||
if maxAllowedMetrics > maxMetrics {
|
||||
maxAllowedMetrics = maxMetrics
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var errTooManyMetrics = errors.New("all the tag filters match too many metrics")
|
||||
|
||||
func (is *indexSearch) adjustMaxMetricsAdaptive(maxMetrics int) int {
|
||||
hmPrev := is.db.prevHourMetricIDs.Load().(*hourMetricIDs)
|
||||
if !hmPrev.isFull {
|
||||
return maxMetrics
|
||||
}
|
||||
hourMetrics := len(hmPrev.m)
|
||||
if hourMetrics >= 256 && maxMetrics > hourMetrics/4 {
|
||||
// It is cheaper to filter on the hour or day metrics if the minimum
|
||||
// number of matching metrics across tfs exceeds hourMetrics / 4.
|
||||
return hourMetrics / 4
|
||||
}
|
||||
return maxMetrics
|
||||
}
|
||||
|
||||
func (is *indexSearch) getTagFilterWithMinMetricIDsCount(tfs *TagFilters, maxMetrics int) (*tagFilter, map[uint64]struct{}, error) {
|
||||
var minMetricIDs map[uint64]struct{}
|
||||
var minTf *tagFilter
|
||||
for i := range tfs.tfs {
|
||||
@@ -1141,7 +1286,7 @@ func (is *indexSearch) getTagFilterWithMinMetricIDsMap(tfs *TagFilters, maxMetri
|
||||
// Skip negative filters.
|
||||
continue
|
||||
}
|
||||
metricIDs, err := is.getMetricIDsMapForTagFilter(tf, maxMetrics)
|
||||
metricIDs, err := is.getMetricIDsForTagFilter(tf, maxMetrics)
|
||||
if err != nil {
|
||||
if err == errFallbackToMetricNameMatch {
|
||||
// Skip tag filters requiring to scan for too many metrics.
|
||||
@@ -1167,7 +1312,7 @@ func (is *indexSearch) getTagFilterWithMinMetricIDsMap(tfs *TagFilters, maxMetri
|
||||
// There is no positive filter with small number of matching metrics.
|
||||
// Create it, so it matches all the MetricIDs for tfs.commonPrefix.
|
||||
metricIDs := make(map[uint64]struct{})
|
||||
if err := is.getMetricIDsMapForCommonPrefix(metricIDs, tfs.commonPrefix, maxMetrics); err != nil {
|
||||
if err := is.updateMetricIDsForCommonPrefix(metricIDs, tfs.commonPrefix, maxMetrics); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return nil, metricIDs, nil
|
||||
@@ -1236,84 +1381,60 @@ func matchTagFilter(b []byte, tf *tagFilter) (bool, error) {
|
||||
}
|
||||
|
||||
func (is *indexSearch) searchMetricIDs(tfss []*TagFilters, tr TimeRange, maxMetrics int) ([]uint64, error) {
|
||||
metricIDsMap := make(map[uint64]struct{})
|
||||
metricIDs := make(map[uint64]struct{})
|
||||
for _, tfs := range tfss {
|
||||
if len(tfs.tfs) == 0 {
|
||||
// Return all the metric ids
|
||||
if err := is.getMetricIDsMapForCommonPrefix(metricIDsMap, tfs.commonPrefix, maxMetrics+1); err != nil {
|
||||
if err := is.updateMetricIDsForCommonPrefix(metricIDs, tfs.commonPrefix, maxMetrics+1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(metricIDsMap) > maxMetrics {
|
||||
if len(metricIDs) > maxMetrics {
|
||||
return nil, fmt.Errorf("the number or unique timeseries exceeds %d; either narrow down the search or increase -search.maxUniqueTimeseries", maxMetrics)
|
||||
}
|
||||
// Stop the iteration, since we cannot find more metric ids with the remaining tfss.
|
||||
break
|
||||
}
|
||||
if err := is.searchMetricIDsMap(metricIDsMap, tfs, tr, maxMetrics+1); err != nil {
|
||||
if err := is.updateMetricIDsForTagFilters(metricIDs, tfs, tr, maxMetrics+1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(metricIDsMap) > maxMetrics {
|
||||
if len(metricIDs) > maxMetrics {
|
||||
return nil, fmt.Errorf("the number or matching unique timeseries exceeds %d; either narrow down the search or increase -search.maxUniqueTimeseries", maxMetrics)
|
||||
}
|
||||
}
|
||||
if len(metricIDsMap) == 0 {
|
||||
if len(metricIDs) == 0 {
|
||||
// Nothing found
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
metricIDs := getSortedMetricIDs(metricIDsMap)
|
||||
sortedMetricIDs := getSortedMetricIDs(metricIDs)
|
||||
|
||||
// Filter out deleted metricIDs.
|
||||
dmis := is.db.getDeletedMetricIDs()
|
||||
if len(dmis) > 0 {
|
||||
metricIDsFiltered := metricIDs[:0]
|
||||
for _, metricID := range metricIDs {
|
||||
metricIDsFiltered := sortedMetricIDs[:0]
|
||||
for _, metricID := range sortedMetricIDs {
|
||||
if _, deleted := dmis[metricID]; !deleted {
|
||||
metricIDsFiltered = append(metricIDsFiltered, metricID)
|
||||
}
|
||||
}
|
||||
metricIDs = metricIDsFiltered
|
||||
sortedMetricIDs = metricIDsFiltered
|
||||
}
|
||||
|
||||
return metricIDs, nil
|
||||
return sortedMetricIDs, nil
|
||||
}
|
||||
|
||||
func (is *indexSearch) searchMetricIDsMap(metricIDs map[uint64]struct{}, tfs *TagFilters, tr TimeRange, maxMetrics int) error {
|
||||
func (is *indexSearch) updateMetricIDsForTagFilters(metricIDs map[uint64]struct{}, tfs *TagFilters, tr TimeRange, maxMetrics int) error {
|
||||
// Sort tag filters for faster ts.Seek below.
|
||||
sort.Slice(tfs.tfs, func(i, j int) bool { return bytes.Compare(tfs.tfs[i].prefix, tfs.tfs[j].prefix) < 0 })
|
||||
|
||||
// Find the filter with minimum matching metrics.
|
||||
// Iteratively increase maxAllowedMetrics up to maxMetrics in order to limit
|
||||
// the time required for founding the tag filter with minimum matching metrics.
|
||||
var minTf *tagFilter
|
||||
var minMetricIDs map[uint64]struct{}
|
||||
maxAllowedMetrics := 16
|
||||
if maxAllowedMetrics > maxMetrics {
|
||||
maxAllowedMetrics = maxMetrics
|
||||
}
|
||||
for {
|
||||
var err error
|
||||
minTf, minMetricIDs, err = is.getTagFilterWithMinMetricIDsMap(tfs, maxAllowedMetrics)
|
||||
if err != nil {
|
||||
minTf, minMetricIDs, err := is.getTagFilterWithMinMetricIDsCountAdaptive(tfs, maxMetrics)
|
||||
if err != nil {
|
||||
if err != errTooManyMetrics {
|
||||
return err
|
||||
}
|
||||
if len(minMetricIDs) < maxAllowedMetrics {
|
||||
// Found the tag filter with the minimum number of metrics.
|
||||
break
|
||||
}
|
||||
|
||||
// Too many metrics matched.
|
||||
if maxAllowedMetrics < maxMetrics {
|
||||
// Increase maxAllowedMetrics and try again.
|
||||
maxAllowedMetrics *= 4
|
||||
if maxAllowedMetrics > maxMetrics {
|
||||
maxAllowedMetrics = maxMetrics
|
||||
}
|
||||
continue
|
||||
}
|
||||
// All the tag filters match too many metrics.
|
||||
|
||||
// The tag filter with minimum matching metrics matches at least maxMetrics.
|
||||
//
|
||||
// Slow path: try filtering the matching metrics by time range.
|
||||
// This should work well for cases when old metrics are constantly substituted
|
||||
// by big number of new metrics. For example, prometheus-operator creates many new
|
||||
@@ -1324,11 +1445,8 @@ func (is *indexSearch) searchMetricIDsMap(metricIDs map[uint64]struct{}, tfs *Ta
|
||||
maxTimeRangeMetrics := 20 * maxMetrics
|
||||
metricIDsForTimeRange, err := is.getMetricIDsForTimeRange(tr, maxTimeRangeMetrics+1)
|
||||
if err == errMissingMetricIDsForDate {
|
||||
// Give up.
|
||||
for metricID := range minMetricIDs {
|
||||
metricIDs[metricID] = struct{}{}
|
||||
}
|
||||
return nil
|
||||
return fmt.Errorf("cannot find tag filter matching less than %d time series; either increase -search.maxUniqueTimeseries or use more specific tag filters",
|
||||
maxMetrics)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1339,7 +1457,6 @@ func (is *indexSearch) searchMetricIDsMap(metricIDs map[uint64]struct{}, tfs *Ta
|
||||
}
|
||||
minMetricIDs = metricIDsForTimeRange
|
||||
minTf = nil
|
||||
break
|
||||
}
|
||||
|
||||
// Find intersection of minTf with other tfs.
|
||||
@@ -1349,7 +1466,7 @@ func (is *indexSearch) searchMetricIDsMap(metricIDs map[uint64]struct{}, tfs *Ta
|
||||
if tf == minTf {
|
||||
continue
|
||||
}
|
||||
mIDs, err := is.intersectMetricIDsMapForTagFilter(tf, minMetricIDs)
|
||||
mIDs, err := is.intersectMetricIDsWithTagFilter(tf, minMetricIDs)
|
||||
if err == errFallbackToMetricNameMatch {
|
||||
// The tag filter requires too many index scans. Postpone it,
|
||||
// so tag filters with lower number of index scans may be applied.
|
||||
@@ -1362,9 +1479,9 @@ func (is *indexSearch) searchMetricIDsMap(metricIDs map[uint64]struct{}, tfs *Ta
|
||||
minMetricIDs = mIDs
|
||||
}
|
||||
for i, tf := range tfsPostponed {
|
||||
mIDs, err := is.intersectMetricIDsMapForTagFilter(tf, minMetricIDs)
|
||||
mIDs, err := is.intersectMetricIDsWithTagFilter(tf, minMetricIDs)
|
||||
if err == errFallbackToMetricNameMatch {
|
||||
return is.searchMetricIDsMapByMetricNameMatch(metricIDs, minMetricIDs, tfsPostponed[i:])
|
||||
return is.updateMetricIDsByMetricNameMatch(metricIDs, minMetricIDs, tfsPostponed[i:])
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1377,14 +1494,14 @@ func (is *indexSearch) searchMetricIDsMap(metricIDs map[uint64]struct{}, tfs *Ta
|
||||
return nil
|
||||
}
|
||||
|
||||
func (is *indexSearch) getMetricIDsMapForTagFilter(tf *tagFilter, maxMetrics int) (map[uint64]struct{}, error) {
|
||||
func (is *indexSearch) getMetricIDsForTagFilter(tf *tagFilter, maxMetrics int) (map[uint64]struct{}, error) {
|
||||
if tf.isNegative {
|
||||
logger.Panicf("BUG: isNegative must be false")
|
||||
}
|
||||
metricIDs := make(map[uint64]struct{}, maxMetrics)
|
||||
if len(tf.orSuffixes) > 0 {
|
||||
// Fast path for orSuffixes - seek for rows for each value from orSuffxies.
|
||||
if err := is.updateMetricIDsMapForOrSuffixesNoFilter(tf, maxMetrics, metricIDs); err != nil {
|
||||
if err := is.updateMetricIDsForOrSuffixesNoFilter(tf, maxMetrics, metricIDs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return metricIDs, nil
|
||||
@@ -1430,7 +1547,7 @@ func (is *indexSearch) getMetricIDsMapForTagFilter(tf *tagFilter, maxMetrics int
|
||||
return metricIDs, nil
|
||||
}
|
||||
|
||||
func (is *indexSearch) updateMetricIDsMapForOrSuffixesNoFilter(tf *tagFilter, maxMetrics int, metricIDs map[uint64]struct{}) error {
|
||||
func (is *indexSearch) updateMetricIDsForOrSuffixesNoFilter(tf *tagFilter, maxMetrics int, metricIDs map[uint64]struct{}) error {
|
||||
if tf.isNegative {
|
||||
logger.Panicf("BUG: isNegative must be false")
|
||||
}
|
||||
@@ -1440,7 +1557,7 @@ func (is *indexSearch) updateMetricIDsMapForOrSuffixesNoFilter(tf *tagFilter, ma
|
||||
kb.B = append(kb.B[:0], tf.prefix...)
|
||||
kb.B = append(kb.B, orSuffix...)
|
||||
kb.B = append(kb.B, tagSeparatorChar)
|
||||
if err := is.updateMetricIDsMapForOrSuffixNoFilter(kb.B, maxMetrics, metricIDs); err != nil {
|
||||
if err := is.updateMetricIDsForOrSuffixNoFilter(kb.B, maxMetrics, metricIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(metricIDs) >= maxMetrics {
|
||||
@@ -1450,7 +1567,7 @@ func (is *indexSearch) updateMetricIDsMapForOrSuffixesNoFilter(tf *tagFilter, ma
|
||||
return nil
|
||||
}
|
||||
|
||||
func (is *indexSearch) updateMetricIDsMapForOrSuffixesWithFilter(tf *tagFilter, metricIDs, filter map[uint64]struct{}) error {
|
||||
func (is *indexSearch) updateMetricIDsForOrSuffixesWithFilter(tf *tagFilter, metricIDs, filter map[uint64]struct{}) error {
|
||||
sortedFilter := getSortedMetricIDs(filter)
|
||||
kb := kbPool.Get()
|
||||
defer kbPool.Put(kb)
|
||||
@@ -1458,14 +1575,14 @@ func (is *indexSearch) updateMetricIDsMapForOrSuffixesWithFilter(tf *tagFilter,
|
||||
kb.B = append(kb.B[:0], tf.prefix...)
|
||||
kb.B = append(kb.B, orSuffix...)
|
||||
kb.B = append(kb.B, tagSeparatorChar)
|
||||
if err := is.updateMetricIDsMapForOrSuffixWithFilter(kb.B, metricIDs, sortedFilter, tf.isNegative); err != nil {
|
||||
if err := is.updateMetricIDsForOrSuffixWithFilter(kb.B, metricIDs, sortedFilter, tf.isNegative); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (is *indexSearch) updateMetricIDsMapForOrSuffixNoFilter(prefix []byte, maxMetrics int, metricIDs map[uint64]struct{}) error {
|
||||
func (is *indexSearch) updateMetricIDsForOrSuffixNoFilter(prefix []byte, maxMetrics int, metricIDs map[uint64]struct{}) error {
|
||||
ts := &is.ts
|
||||
maxLoops := maxMetrics * maxIndexScanLoopsPerMetric
|
||||
loops := 0
|
||||
@@ -1492,7 +1609,7 @@ func (is *indexSearch) updateMetricIDsMapForOrSuffixNoFilter(prefix []byte, maxM
|
||||
return nil
|
||||
}
|
||||
|
||||
func (is *indexSearch) updateMetricIDsMapForOrSuffixWithFilter(prefix []byte, metricIDs map[uint64]struct{}, sortedFilter []uint64, isNegative bool) error {
|
||||
func (is *indexSearch) updateMetricIDsForOrSuffixWithFilter(prefix []byte, metricIDs map[uint64]struct{}, sortedFilter []uint64, isNegative bool) error {
|
||||
ts := &is.ts
|
||||
kb := &is.kb
|
||||
for {
|
||||
@@ -1533,7 +1650,7 @@ func (is *indexSearch) updateMetricIDsMapForOrSuffixWithFilter(prefix []byte, me
|
||||
return nil
|
||||
}
|
||||
|
||||
var errFallbackToMetricNameMatch = errors.New("fall back to searchMetricIDsMapByMetricNameMatch because of too many index scan loops")
|
||||
var errFallbackToMetricNameMatch = errors.New("fall back to updateMetricIDsByMetricNameMatch because of too many index scan loops")
|
||||
|
||||
var errMissingMetricIDsForDate = errors.New("missing metricIDs for date")
|
||||
|
||||
@@ -1541,22 +1658,81 @@ func (is *indexSearch) getMetricIDsForTimeRange(tr TimeRange, maxMetrics int) (m
|
||||
if tr.isZero() {
|
||||
return nil, errMissingMetricIDsForDate
|
||||
}
|
||||
minDate := tr.MinTimestamp / msecPerDay
|
||||
maxDate := tr.MaxTimestamp / msecPerDay
|
||||
atomic.AddUint64(&is.db.recentHourMetricIDsSearchCalls, 1)
|
||||
if metricIDs, ok := is.getMetricIDsForRecentHours(tr, maxMetrics); ok {
|
||||
// Fast path: tr covers the current and / or the previous hour.
|
||||
// Return the full list of metric ids for this time range.
|
||||
atomic.AddUint64(&is.db.recentHourMetricIDsSearchHits, 1)
|
||||
return metricIDs, nil
|
||||
}
|
||||
|
||||
// Slow path: collect the metric ids for all the days covering the given tr.
|
||||
atomic.AddUint64(&is.db.dateMetricIDsSearchCalls, 1)
|
||||
minDate := uint64(tr.MinTimestamp) / msecPerDay
|
||||
maxDate := uint64(tr.MaxTimestamp) / msecPerDay
|
||||
if maxDate-minDate > 40 {
|
||||
// Too much dates must be covered. Give up.
|
||||
return nil, errMissingMetricIDsForDate
|
||||
}
|
||||
metricIDs := make(map[uint64]struct{}, maxMetrics)
|
||||
for minDate <= maxDate {
|
||||
if err := is.getMetricIDsForDate(uint64(minDate), metricIDs, maxMetrics); err != nil {
|
||||
if err := is.getMetricIDsForDate(minDate, metricIDs, maxMetrics); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
minDate++
|
||||
}
|
||||
atomic.AddUint64(&is.db.dateMetricIDsSearchHits, 1)
|
||||
return metricIDs, nil
|
||||
}
|
||||
|
||||
func (is *indexSearch) getMetricIDsForRecentHours(tr TimeRange, maxMetrics int) (map[uint64]struct{}, bool) {
|
||||
minHour := uint64(tr.MinTimestamp) / msecPerHour
|
||||
maxHour := uint64(tr.MaxTimestamp) / msecPerHour
|
||||
hmCurr := is.db.currHourMetricIDs.Load().(*hourMetricIDs)
|
||||
if maxHour == hmCurr.hour && minHour == maxHour && hmCurr.isFull {
|
||||
// The tr fits the current hour.
|
||||
// Return a copy of hmCurr.m, because the caller may modify
|
||||
// the returned map.
|
||||
if len(hmCurr.m) > maxMetrics {
|
||||
return nil, false
|
||||
}
|
||||
return getMetricIDsCopy(hmCurr.m), true
|
||||
}
|
||||
hmPrev := is.db.prevHourMetricIDs.Load().(*hourMetricIDs)
|
||||
if maxHour == hmPrev.hour && minHour == maxHour && hmPrev.isFull {
|
||||
// The tr fits the previous hour.
|
||||
// Return a copy of hmPrev.m, because the caller may modify
|
||||
// the returned map.
|
||||
if len(hmPrev.m) > maxMetrics {
|
||||
return nil, false
|
||||
}
|
||||
return getMetricIDsCopy(hmPrev.m), true
|
||||
}
|
||||
if maxHour == hmCurr.hour && minHour == hmPrev.hour && hmCurr.isFull && hmPrev.isFull {
|
||||
// The tr spans the previous and the current hours.
|
||||
if len(hmCurr.m)+len(hmPrev.m) > maxMetrics {
|
||||
return nil, false
|
||||
}
|
||||
metricIDs := make(map[uint64]struct{}, len(hmCurr.m)+len(hmPrev.m))
|
||||
for metricID := range hmCurr.m {
|
||||
metricIDs[metricID] = struct{}{}
|
||||
}
|
||||
for metricID := range hmPrev.m {
|
||||
metricIDs[metricID] = struct{}{}
|
||||
}
|
||||
return metricIDs, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func getMetricIDsCopy(src map[uint64]struct{}) map[uint64]struct{} {
|
||||
dst := make(map[uint64]struct{}, len(src))
|
||||
for metricID := range src {
|
||||
dst[metricID] = struct{}{}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func (db *indexDB) storeDateMetricID(date, metricID uint64) error {
|
||||
is := db.getIndexSearch()
|
||||
ok, err := is.hasDateMetricID(date, metricID)
|
||||
@@ -1629,7 +1805,29 @@ func (is *indexSearch) getMetricIDsForDate(date uint64, metricIDs map[uint64]str
|
||||
return nil
|
||||
}
|
||||
|
||||
func (is *indexSearch) getMetricIDsMapForCommonPrefix(metricIDs map[uint64]struct{}, commonPrefix []byte, maxMetrics int) error {
|
||||
func (is *indexSearch) containsTimeRange(tr TimeRange) (bool, error) {
|
||||
ts := &is.ts
|
||||
kb := &is.kb
|
||||
|
||||
// Verify whether the maximum date in `ts` covers tr.MinTimestamp.
|
||||
minDate := uint64(tr.MinTimestamp) / msecPerDay
|
||||
kb.B = marshalCommonPrefix(kb.B[:0], nsPrefixDateToMetricID)
|
||||
kb.B = encoding.MarshalUint64(kb.B, minDate)
|
||||
ts.Seek(kb.B)
|
||||
if !ts.NextItem() {
|
||||
if err := ts.Error(); err != nil {
|
||||
return false, fmt.Errorf("error when searching for minDate=%d, prefix %q: %s", minDate, kb.B, err)
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
if !bytes.HasPrefix(ts.Item, kb.B[:1]) {
|
||||
// minDate exceeds max date from ts.
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (is *indexSearch) updateMetricIDsForCommonPrefix(metricIDs map[uint64]struct{}, commonPrefix []byte, maxMetrics int) error {
|
||||
ts := &is.ts
|
||||
ts.Seek(commonPrefix)
|
||||
for len(metricIDs) < maxMetrics && ts.NextItem() {
|
||||
@@ -1654,11 +1852,11 @@ func (is *indexSearch) getMetricIDsMapForCommonPrefix(metricIDs map[uint64]struc
|
||||
}
|
||||
|
||||
// The maximum number of index scan loops per already found metric.
|
||||
// Bigger number of loops is slower than searchMetricIDsMapByMetricNameMatch
|
||||
// Bigger number of loops is slower than updateMetricIDsByMetricNameMatch
|
||||
// over the found metrics.
|
||||
const maxIndexScanLoopsPerMetric = 32
|
||||
|
||||
func (is *indexSearch) intersectMetricIDsMapForTagFilter(tf *tagFilter, filter map[uint64]struct{}) (map[uint64]struct{}, error) {
|
||||
func (is *indexSearch) intersectMetricIDsWithTagFilter(tf *tagFilter, filter map[uint64]struct{}) (map[uint64]struct{}, error) {
|
||||
if len(filter) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -1668,7 +1866,7 @@ func (is *indexSearch) intersectMetricIDsMapForTagFilter(tf *tagFilter, filter m
|
||||
}
|
||||
if len(tf.orSuffixes) > 0 {
|
||||
// Fast path for orSuffixes - seek for rows for each value from orSuffixes.
|
||||
if err := is.updateMetricIDsMapForOrSuffixesWithFilter(tf, metricIDs, filter); err != nil {
|
||||
if err := is.updateMetricIDsForOrSuffixesWithFilter(tf, metricIDs, filter); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return metricIDs, nil
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"math/rand"
|
||||
"os"
|
||||
"reflect"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -60,8 +61,14 @@ func TestIndexDBOpenClose(t *testing.T) {
|
||||
metricNameCache := fastcache.New(1234)
|
||||
defer metricIDCache.Reset()
|
||||
defer metricNameCache.Reset()
|
||||
|
||||
var hmCurr atomic.Value
|
||||
hmCurr.Store(&hourMetricIDs{})
|
||||
var hmPrev atomic.Value
|
||||
hmPrev.Store(&hourMetricIDs{})
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
db, err := openIndexDB("test-index-db", metricIDCache, metricNameCache)
|
||||
db, err := openIndexDB("test-index-db", metricIDCache, metricNameCache, &hmCurr, &hmPrev)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot open indexDB: %s", err)
|
||||
}
|
||||
@@ -82,8 +89,14 @@ func TestIndexDB(t *testing.T) {
|
||||
metricNameCache := fastcache.New(1234)
|
||||
defer metricIDCache.Reset()
|
||||
defer metricNameCache.Reset()
|
||||
|
||||
var hmCurr atomic.Value
|
||||
hmCurr.Store(&hourMetricIDs{})
|
||||
var hmPrev atomic.Value
|
||||
hmPrev.Store(&hourMetricIDs{})
|
||||
|
||||
dbName := "test-index-db-serial"
|
||||
db, err := openIndexDB(dbName, metricIDCache, metricNameCache)
|
||||
db, err := openIndexDB(dbName, metricIDCache, metricNameCache, &hmCurr, &hmPrev)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot open indexDB: %s", err)
|
||||
}
|
||||
@@ -113,7 +126,7 @@ func TestIndexDB(t *testing.T) {
|
||||
|
||||
// Re-open the db and verify it works as expected.
|
||||
db.MustClose()
|
||||
db, err = openIndexDB(dbName, metricIDCache, metricNameCache)
|
||||
db, err = openIndexDB(dbName, metricIDCache, metricNameCache, &hmCurr, &hmPrev)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot open indexDB: %s", err)
|
||||
}
|
||||
@@ -133,8 +146,14 @@ func TestIndexDB(t *testing.T) {
|
||||
metricNameCache := fastcache.New(1234)
|
||||
defer metricIDCache.Reset()
|
||||
defer metricNameCache.Reset()
|
||||
|
||||
var hmCurr atomic.Value
|
||||
hmCurr.Store(&hourMetricIDs{})
|
||||
var hmPrev atomic.Value
|
||||
hmPrev.Store(&hourMetricIDs{})
|
||||
|
||||
dbName := "test-index-db-concurrent"
|
||||
db, err := openIndexDB(dbName, metricIDCache, metricNameCache)
|
||||
db, err := openIndexDB(dbName, metricIDCache, metricNameCache, &hmCurr, &hmPrev)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot open indexDB: %s", err)
|
||||
}
|
||||
@@ -293,6 +312,16 @@ func testIndexDBGetOrCreateTSIDByName(db *indexDB, accountsCount, projectsCount,
|
||||
}
|
||||
|
||||
func testIndexDBCheckTSIDByName(db *indexDB, mns []MetricName, tsids []TSID, isConcurrent bool) error {
|
||||
// fill Date -> MetricID cache
|
||||
date := uint64(timestampFromTime(time.Now())) / msecPerDay
|
||||
for i := range tsids {
|
||||
tsid := &tsids[i]
|
||||
if err := db.storeDateMetricID(date, tsid.MetricID); err != nil {
|
||||
return fmt.Errorf("error in storeDateMetricID(%d, %d): %s", date, tsid.MetricID, err)
|
||||
}
|
||||
}
|
||||
db.tb.DebugFlush()
|
||||
|
||||
hasValue := func(tvs []string, v []byte) bool {
|
||||
for _, tv := range tvs {
|
||||
if string(v) == tv {
|
||||
@@ -382,6 +411,19 @@ func testIndexDBCheckTSIDByName(db *indexDB, mns []MetricName, tsids []TSID, isC
|
||||
}
|
||||
}
|
||||
|
||||
// Check timerseriesCounters only for serial test.
|
||||
// Concurrent test may create duplicate timeseries, so GetSeriesCount
|
||||
// would return more timeseries than needed.
|
||||
if !isConcurrent {
|
||||
n, err := db.GetSeriesCount()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unexpected error in GetSeriesCount(): %s", err)
|
||||
}
|
||||
if n != uint64(len(timeseriesCounters)) {
|
||||
return fmt.Errorf("unexpected GetSeriesCount(); got %d; want %d", n, uint64(len(timeseriesCounters)))
|
||||
}
|
||||
}
|
||||
|
||||
// Try tag filters.
|
||||
for i := range mns {
|
||||
mn := &mns[i]
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"github.com/VictoriaMetrics/fastcache"
|
||||
@@ -16,8 +17,14 @@ func BenchmarkIndexDBAddTSIDs(b *testing.B) {
|
||||
metricNameCache := fastcache.New(1234)
|
||||
defer metricIDCache.Reset()
|
||||
defer metricNameCache.Reset()
|
||||
|
||||
var hmCurr atomic.Value
|
||||
hmCurr.Store(&hourMetricIDs{})
|
||||
var hmPrev atomic.Value
|
||||
hmPrev.Store(&hourMetricIDs{})
|
||||
|
||||
const dbName = "bench-index-db-add-tsids"
|
||||
db, err := openIndexDB(dbName, metricIDCache, metricNameCache)
|
||||
db, err := openIndexDB(dbName, metricIDCache, metricNameCache, &hmCurr, &hmPrev)
|
||||
if err != nil {
|
||||
b.Fatalf("cannot open indexDB: %s", err)
|
||||
}
|
||||
@@ -76,8 +83,14 @@ func BenchmarkIndexDBSearchTSIDs(b *testing.B) {
|
||||
metricNameCache := fastcache.New(1234)
|
||||
defer metricIDCache.Reset()
|
||||
defer metricNameCache.Reset()
|
||||
|
||||
var hmCurr atomic.Value
|
||||
hmCurr.Store(&hourMetricIDs{})
|
||||
var hmPrev atomic.Value
|
||||
hmPrev.Store(&hourMetricIDs{})
|
||||
|
||||
const dbName = "bench-index-db-search-tsids"
|
||||
db, err := openIndexDB(dbName, metricIDCache, metricNameCache)
|
||||
db, err := openIndexDB(dbName, metricIDCache, metricNameCache, &hmCurr, &hmPrev)
|
||||
if err != nil {
|
||||
b.Fatalf("cannot open indexDB: %s", err)
|
||||
}
|
||||
@@ -88,8 +101,6 @@ func BenchmarkIndexDBSearchTSIDs(b *testing.B) {
|
||||
}
|
||||
}()
|
||||
|
||||
const accountsCount = 111
|
||||
const projectsCount = 33333
|
||||
const recordsCount = 1e5
|
||||
|
||||
// Fill the db with recordsCount records.
|
||||
@@ -147,8 +158,14 @@ func BenchmarkIndexDBGetTSIDs(b *testing.B) {
|
||||
metricNameCache := fastcache.New(1234)
|
||||
defer metricIDCache.Reset()
|
||||
defer metricNameCache.Reset()
|
||||
|
||||
var hmCurr atomic.Value
|
||||
hmCurr.Store(&hourMetricIDs{})
|
||||
var hmPrev atomic.Value
|
||||
hmPrev.Store(&hourMetricIDs{})
|
||||
|
||||
const dbName = "bench-index-db-get-tsids"
|
||||
db, err := openIndexDB(dbName, metricIDCache, metricNameCache)
|
||||
db, err := openIndexDB(dbName, metricIDCache, metricNameCache, &hmCurr, &hmPrev)
|
||||
if err != nil {
|
||||
b.Fatalf("cannot open indexDB: %s", err)
|
||||
}
|
||||
@@ -160,8 +177,6 @@ func BenchmarkIndexDBGetTSIDs(b *testing.B) {
|
||||
}()
|
||||
|
||||
const recordsPerLoop = 1000
|
||||
const accountsCount = 111
|
||||
const projectsCount = 33333
|
||||
const recordsCount = 1e5
|
||||
|
||||
// Fill the db with recordsCount records.
|
||||
|
||||
@@ -29,7 +29,7 @@ func mergeBlockStreams(ph *partHeader, bsw *blockStreamWriter, bsrs []*blockStre
|
||||
if err == errForciblyStopped {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("cannot merge %d streams: %s", len(bsrs), err)
|
||||
return fmt.Errorf("cannot merge %d streams: %s: %s", len(bsrs), bsrs, err)
|
||||
}
|
||||
|
||||
var bsmPool = &sync.Pool{
|
||||
|
||||
@@ -586,13 +586,6 @@ func copyTags(dst, src []Tag) []Tag {
|
||||
return dst
|
||||
}
|
||||
|
||||
func (mn *MetricName) tagsLess(i, j int) bool {
|
||||
a, b := mn.Tags[i].Key, mn.Tags[j].Key
|
||||
a = normalizeTagKey(a)
|
||||
b = normalizeTagKey(b)
|
||||
return bytes.Compare(a, b) < 0
|
||||
}
|
||||
|
||||
var commonTagKeys = func() map[string][]byte {
|
||||
lcm := map[string][]byte{
|
||||
// job-like tags must go first in MetricName.Tags.
|
||||
|
||||
@@ -212,14 +212,8 @@ func createPartition(timestamp int64, smallPartitionsPath, bigPartitionsPath str
|
||||
// The pt must be detached from table before calling pt.Drop.
|
||||
func (pt *partition) Drop() {
|
||||
logger.Infof("dropping partition %q at smallPartsPath=%q, bigPartsPath=%q", pt.name, pt.smallPartsPath, pt.bigPartsPath)
|
||||
|
||||
if err := os.RemoveAll(pt.smallPartsPath); err != nil {
|
||||
logger.Panicf("FATAL: cannot remove small parts directory %q: %s", pt.smallPartsPath, err)
|
||||
}
|
||||
if err := os.RemoveAll(pt.bigPartsPath); err != nil {
|
||||
logger.Panicf("FATAL: cannot remove big parts directory %q: %s", pt.bigPartsPath, err)
|
||||
}
|
||||
|
||||
fs.MustRemoveAll(pt.smallPartsPath)
|
||||
fs.MustRemoveAll(pt.bigPartsPath)
|
||||
logger.Infof("partition %q has been dropped", pt.name)
|
||||
}
|
||||
|
||||
@@ -599,7 +593,7 @@ func (pt *partition) MustClose() {
|
||||
logger.Infof("%d inmemory parts have been flushed to files in %s on %q", len(pws), time.Since(startTime), pt.smallPartsPath)
|
||||
|
||||
// Remove references to smallParts from the pt, so they may be eventually closed
|
||||
// after all the seraches are done.
|
||||
// after all the searches are done.
|
||||
pt.partsLock.Lock()
|
||||
smallParts := pt.smallParts
|
||||
pt.smallParts = nil
|
||||
@@ -1223,13 +1217,9 @@ func openParts(pathPrefix1, pathPrefix2, path string) ([]*partWrapper, error) {
|
||||
}
|
||||
|
||||
txnDir := path + "/txn"
|
||||
if err := os.RemoveAll(txnDir); err != nil {
|
||||
return nil, fmt.Errorf("cannot delete transaction directory %q: %s", txnDir, err)
|
||||
}
|
||||
fs.MustRemoveAll(txnDir)
|
||||
tmpDir := path + "/tmp"
|
||||
if err := os.RemoveAll(tmpDir); err != nil {
|
||||
return nil, fmt.Errorf("cannot remove temporary directory %q: %s", tmpDir, err)
|
||||
}
|
||||
fs.MustRemoveAll(tmpDir)
|
||||
if err := createPartitionDirs(path); err != nil {
|
||||
return nil, fmt.Errorf("cannot create directories for partition %q: %s", path, err)
|
||||
}
|
||||
@@ -1342,8 +1332,8 @@ func (pt *partition) createSnapshot(srcDir, dstDir string) error {
|
||||
}
|
||||
}
|
||||
|
||||
fs.SyncPath(dstDir)
|
||||
fs.SyncPath(filepath.Dir(dstDir))
|
||||
fs.MustSyncPath(dstDir)
|
||||
fs.MustSyncPath(filepath.Dir(dstDir))
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1408,9 +1398,7 @@ func runTransaction(txnLock *sync.RWMutex, pathPrefix1, pathPrefix2, txnPath str
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid path to remove: %s", err)
|
||||
}
|
||||
if err := os.RemoveAll(path); err != nil {
|
||||
return fmt.Errorf("cannot remove %q: %s", path, err)
|
||||
}
|
||||
fs.MustRemoveAll(path)
|
||||
}
|
||||
|
||||
// Move the new part to new directory.
|
||||
@@ -1438,14 +1426,12 @@ func runTransaction(txnLock *sync.RWMutex, pathPrefix1, pathPrefix2, txnPath str
|
||||
}
|
||||
} else {
|
||||
// Just remove srcPath.
|
||||
if err := os.RemoveAll(srcPath); err != nil {
|
||||
return fmt.Errorf("cannot remove %q: %s", srcPath, err)
|
||||
}
|
||||
fs.MustRemoveAll(srcPath)
|
||||
}
|
||||
|
||||
// Flush pathPrefix* directory metadata to the underying storage.
|
||||
fs.SyncPath(pathPrefix1)
|
||||
fs.SyncPath(pathPrefix2)
|
||||
fs.MustSyncPath(pathPrefix1)
|
||||
fs.MustSyncPath(pathPrefix2)
|
||||
|
||||
// Remove the transaction file.
|
||||
if err := os.Remove(txnPath); err != nil {
|
||||
@@ -1487,6 +1473,6 @@ func createPartitionDirs(path string) error {
|
||||
if err := fs.MkdirAllFailIfExist(tmpPath); err != nil {
|
||||
return fmt.Errorf("cannot create tmp directory %q: %s", tmpPath, err)
|
||||
}
|
||||
fs.SyncPath(path)
|
||||
fs.MustSyncPath(path)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -112,15 +112,15 @@ func testAppendPartsToMerge(t *testing.T, maxPartsToMerge int, initialRowsCount,
|
||||
|
||||
// Verify appending to prefix
|
||||
prefix := []*partWrapper{
|
||||
&partWrapper{
|
||||
{
|
||||
p: &part{
|
||||
ph: partHeader{
|
||||
RowsCount: 1234,
|
||||
},
|
||||
},
|
||||
},
|
||||
&partWrapper{},
|
||||
&partWrapper{},
|
||||
{},
|
||||
{},
|
||||
}
|
||||
pms = appendPartsToMerge(prefix, pws, maxPartsToMerge, 1e9)
|
||||
if !reflect.DeepEqual(pms[:len(prefix)], prefix) {
|
||||
|
||||
@@ -19,7 +19,7 @@ type rawRow struct {
|
||||
// Value is time series value for the given timestamp.
|
||||
Value float64
|
||||
|
||||
// PrecisionBits is the number of the siginificant bits in the Value
|
||||
// PrecisionBits is the number of the significant bits in the Value
|
||||
// to store. Possible values are [1..64].
|
||||
// 1 means max. 50% error, 2 - 25%, 3 - 12.5%, 64 means no error, i.e.
|
||||
// Value stored without information loss.
|
||||
|
||||
@@ -44,7 +44,7 @@ func TestSearchQueryMarshalUnmarshal(t *testing.T) {
|
||||
if sq1.MaxTimestamp != sq2.MaxTimestamp {
|
||||
t.Fatalf("unexpected MaxTimestamp; got %d; want %d", sq2.MaxTimestamp, sq1.MaxTimestamp)
|
||||
}
|
||||
if len(sq1.TagFilterss) != len(sq1.TagFilterss) {
|
||||
if len(sq1.TagFilterss) != len(sq2.TagFilterss) {
|
||||
t.Fatalf("unexpected TagFilterss len; got %d; want %d", len(sq2.TagFilterss), len(sq1.TagFilterss))
|
||||
}
|
||||
for ii := range sq1.TagFilterss {
|
||||
|
||||
@@ -2,6 +2,7 @@ package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -18,6 +19,7 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/memory"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timerpool"
|
||||
"github.com/VictoriaMetrics/fastcache"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
@@ -30,7 +32,7 @@ type Storage struct {
|
||||
cachePath string
|
||||
retentionMonths int
|
||||
|
||||
// lock file for excluse access to the storage on the given path.
|
||||
// lock file for exclusive access to the storage on the given path.
|
||||
flockF *os.File
|
||||
|
||||
idbCurr atomic.Value
|
||||
@@ -49,8 +51,20 @@ type Storage struct {
|
||||
// dateMetricIDCache is (Date, MetricID) cache.
|
||||
dateMetricIDCache *fastcache.Cache
|
||||
|
||||
stop chan struct{}
|
||||
retentionWatcherWG sync.WaitGroup
|
||||
// Fast cache for MetricID values occured during the current hour.
|
||||
currHourMetricIDs atomic.Value
|
||||
|
||||
// Fast cache for MetricID values occured during the previous hour.
|
||||
prevHourMetricIDs atomic.Value
|
||||
|
||||
// Pending MetricID values to be added to currHourMetricIDs.
|
||||
pendingHourMetricIDsLock sync.Mutex
|
||||
pendingHourMetricIDs map[uint64]struct{}
|
||||
|
||||
stop chan struct{}
|
||||
|
||||
currHourMetricIDsUpdaterWG sync.WaitGroup
|
||||
retentionWatcherWG sync.WaitGroup
|
||||
}
|
||||
|
||||
// OpenStorage opens storage on the given path with the given number of retention months.
|
||||
@@ -99,13 +113,20 @@ func OpenStorage(path string, retentionMonths int) (*Storage, error) {
|
||||
s.metricNameCache = s.mustLoadCache("MetricID->MetricName", "metricID_metricName", mem/8)
|
||||
s.dateMetricIDCache = s.mustLoadCache("Date->MetricID", "date_metricID", mem/32)
|
||||
|
||||
hour := uint64(timestampFromTime(time.Now())) / msecPerHour
|
||||
hmCurr := s.mustLoadHourMetricIDs(hour, "curr_hour_metric_ids")
|
||||
hmPrev := s.mustLoadHourMetricIDs(hour-1, "prev_hour_metric_ids")
|
||||
s.currHourMetricIDs.Store(hmCurr)
|
||||
s.prevHourMetricIDs.Store(hmPrev)
|
||||
s.pendingHourMetricIDs = make(map[uint64]struct{})
|
||||
|
||||
// Load indexdb
|
||||
idbPath := path + "/indexdb"
|
||||
idbSnapshotsPath := idbPath + "/snapshots"
|
||||
if err := fs.MkdirAllIfNotExist(idbSnapshotsPath); err != nil {
|
||||
return nil, fmt.Errorf("cannot create %q: %s", idbSnapshotsPath, err)
|
||||
}
|
||||
idbCurr, idbPrev, err := openIndexDBTables(idbPath, s.metricIDCache, s.metricNameCache)
|
||||
idbCurr, idbPrev, err := openIndexDBTables(idbPath, s.metricIDCache, s.metricNameCache, &s.currHourMetricIDs, &s.prevHourMetricIDs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot open indexdb tables at %q: %s", idbPath, err)
|
||||
}
|
||||
@@ -121,6 +142,7 @@ func OpenStorage(path string, retentionMonths int) (*Storage, error) {
|
||||
}
|
||||
s.tb = tb
|
||||
|
||||
s.startCurrHourMetricIDsUpdater()
|
||||
s.startRetentionWatcher()
|
||||
|
||||
return s, nil
|
||||
@@ -164,7 +186,7 @@ func (s *Storage) CreateSnapshot() (string, error) {
|
||||
if err := fs.SymlinkRelative(bigDir, dstBigDir); err != nil {
|
||||
return "", fmt.Errorf("cannot create symlink from %q to %q: %s", bigDir, dstBigDir, err)
|
||||
}
|
||||
fs.SyncPath(dstDataDir)
|
||||
fs.MustSyncPath(dstDataDir)
|
||||
|
||||
idbSnapshot := fmt.Sprintf("%s/indexdb/snapshots/%s", s.path, snapshotName)
|
||||
idb := s.idb()
|
||||
@@ -184,8 +206,8 @@ func (s *Storage) CreateSnapshot() (string, error) {
|
||||
return "", fmt.Errorf("cannot create symlink from %q to %q: %s", idbSnapshot, dstIdbDir, err)
|
||||
}
|
||||
|
||||
fs.SyncPath(dstDir)
|
||||
fs.SyncPath(srcDir + "/snapshots")
|
||||
fs.MustSyncPath(dstDir)
|
||||
fs.MustSyncPath(srcDir + "/snapshots")
|
||||
|
||||
logger.Infof("created Storage snapshot for %q at %q in %s", srcDir, dstDir, time.Since(startTime))
|
||||
return snapshotName, nil
|
||||
@@ -229,8 +251,8 @@ func (s *Storage) DeleteSnapshot(snapshotName string) error {
|
||||
|
||||
s.tb.MustDeleteSnapshot(snapshotName)
|
||||
idbPath := fmt.Sprintf("%s/indexdb/snapshots/%s", s.path, snapshotName)
|
||||
fs.MustRemoveAllSynced(idbPath)
|
||||
fs.MustRemoveAllSynced(snapshotPath)
|
||||
fs.MustRemoveAll(idbPath)
|
||||
fs.MustRemoveAll(snapshotPath)
|
||||
|
||||
logger.Infof("deleted snapshot %q in %s", snapshotPath, time.Since(startTime))
|
||||
|
||||
@@ -273,6 +295,8 @@ type Metrics struct {
|
||||
DateMetricIDCacheMisses uint64
|
||||
DateMetricIDCacheCollisions uint64
|
||||
|
||||
HourMetricIDCacheSize uint64
|
||||
|
||||
IndexDBMetrics IndexDBMetrics
|
||||
TableMetrics TableMetrics
|
||||
}
|
||||
@@ -316,6 +340,14 @@ func (s *Storage) UpdateMetrics(m *Metrics) {
|
||||
m.DateMetricIDCacheMisses += cs.Misses
|
||||
m.DateMetricIDCacheCollisions += cs.Collisions
|
||||
|
||||
hmCurr := s.currHourMetricIDs.Load().(*hourMetricIDs)
|
||||
hmPrev := s.prevHourMetricIDs.Load().(*hourMetricIDs)
|
||||
hourMetricIDsLen := len(hmPrev.m)
|
||||
if len(hmCurr.m) > hourMetricIDsLen {
|
||||
hourMetricIDsLen = len(hmCurr.m)
|
||||
}
|
||||
m.HourMetricIDCacheSize += uint64(hourMetricIDsLen)
|
||||
|
||||
s.idb().UpdateMetrics(&m.IndexDBMetrics)
|
||||
s.tb.UpdateMetrics(&m.TableMetrics)
|
||||
}
|
||||
@@ -340,11 +372,34 @@ func (s *Storage) retentionWatcher() {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Storage) startCurrHourMetricIDsUpdater() {
|
||||
s.currHourMetricIDsUpdaterWG.Add(1)
|
||||
go func() {
|
||||
s.currHourMetricIDsUpdater()
|
||||
s.currHourMetricIDsUpdaterWG.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
var currHourMetricIDsUpdateInterval = time.Second * 10
|
||||
|
||||
func (s *Storage) currHourMetricIDsUpdater() {
|
||||
t := time.NewTimer(currHourMetricIDsUpdateInterval)
|
||||
for {
|
||||
select {
|
||||
case <-s.stop:
|
||||
return
|
||||
case <-t.C:
|
||||
s.updateCurrHourMetricIDs()
|
||||
t.Reset(currHourMetricIDsUpdateInterval)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Storage) mustRotateIndexDB() {
|
||||
// Create new indexdb table.
|
||||
newTableName := nextIndexDBTableName()
|
||||
idbNewPath := s.path + "/indexdb/" + newTableName
|
||||
idbNew, err := openIndexDB(idbNewPath, s.metricIDCache, s.metricNameCache)
|
||||
idbNew, err := openIndexDB(idbNewPath, s.metricIDCache, s.metricNameCache, &s.currHourMetricIDs, &s.prevHourMetricIDs)
|
||||
if err != nil {
|
||||
logger.Panicf("FATAL: cannot create new indexDB at %q: %s", idbNewPath, err)
|
||||
}
|
||||
@@ -361,7 +416,7 @@ func (s *Storage) mustRotateIndexDB() {
|
||||
s.idbCurr.Store(idbNew)
|
||||
|
||||
// Persist changes on the file system.
|
||||
fs.SyncPath(s.path)
|
||||
fs.MustSyncPath(s.path)
|
||||
|
||||
// Flush tsidCache, so idbNew can be populated with fresh data.
|
||||
s.tsidCache.Reset()
|
||||
@@ -378,6 +433,7 @@ func (s *Storage) MustClose() {
|
||||
close(s.stop)
|
||||
|
||||
s.retentionWatcherWG.Wait()
|
||||
s.currHourMetricIDsUpdaterWG.Wait()
|
||||
|
||||
s.tb.MustClose()
|
||||
s.idb().MustClose()
|
||||
@@ -388,12 +444,83 @@ func (s *Storage) MustClose() {
|
||||
s.mustSaveCache(s.metricNameCache, "MetricID->MetricName", "metricID_metricName")
|
||||
s.mustSaveCache(s.dateMetricIDCache, "Date->MetricID", "date_metricID")
|
||||
|
||||
hmCurr := s.currHourMetricIDs.Load().(*hourMetricIDs)
|
||||
s.mustSaveHourMetricIDs(hmCurr, "curr_hour_metric_ids")
|
||||
hmPrev := s.prevHourMetricIDs.Load().(*hourMetricIDs)
|
||||
s.mustSaveHourMetricIDs(hmPrev, "prev_hour_metric_ids")
|
||||
|
||||
// Release lock file.
|
||||
if err := s.flockF.Close(); err != nil {
|
||||
logger.Panicf("FATAL: cannot close lock file %q: %s", s.flockF.Name(), err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Storage) mustLoadHourMetricIDs(hour uint64, name string) *hourMetricIDs {
|
||||
path := s.cachePath + "/" + name
|
||||
logger.Infof("loading %s from %q...", name, path)
|
||||
startTime := time.Now()
|
||||
if !fs.IsPathExist(path) {
|
||||
logger.Infof("nothing to load from %q", path)
|
||||
return &hourMetricIDs{}
|
||||
}
|
||||
src, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
logger.Panicf("FATAL: cannot read %s: %s", path, err)
|
||||
}
|
||||
srcOrigLen := len(src)
|
||||
if len(src) < 24 {
|
||||
logger.Errorf("discarding %s, since it has broken header; got %d bytes; want %d bytes", path, len(src), 24)
|
||||
return &hourMetricIDs{}
|
||||
}
|
||||
isFull := encoding.UnmarshalUint64(src)
|
||||
src = src[8:]
|
||||
hourLoaded := encoding.UnmarshalUint64(src)
|
||||
src = src[8:]
|
||||
if hourLoaded != hour {
|
||||
logger.Infof("discarding %s, since it is outdated", name)
|
||||
return &hourMetricIDs{}
|
||||
}
|
||||
hmLen := encoding.UnmarshalUint64(src)
|
||||
src = src[8:]
|
||||
if uint64(len(src)) != 8*hmLen {
|
||||
logger.Errorf("discarding %s, since it has broken body; got %d bytes; want %d bytes", path, len(src), 8*hmLen)
|
||||
return &hourMetricIDs{}
|
||||
}
|
||||
m := make(map[uint64]struct{}, hmLen)
|
||||
for i := uint64(0); i < hmLen; i++ {
|
||||
metricID := encoding.UnmarshalUint64(src)
|
||||
src = src[8:]
|
||||
m[metricID] = struct{}{}
|
||||
}
|
||||
logger.Infof("loaded %s from %q in %s; entriesCount: %d; bytesSize: %d", name, path, time.Since(startTime), hmLen, srcOrigLen)
|
||||
return &hourMetricIDs{
|
||||
m: m,
|
||||
hour: hourLoaded,
|
||||
isFull: isFull != 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Storage) mustSaveHourMetricIDs(hm *hourMetricIDs, name string) {
|
||||
path := s.cachePath + "/" + name
|
||||
logger.Infof("saving %s to %q...", name, path)
|
||||
startTime := time.Now()
|
||||
dst := make([]byte, 0, len(hm.m)*8+24)
|
||||
isFull := uint64(0)
|
||||
if hm.isFull {
|
||||
isFull = 1
|
||||
}
|
||||
dst = encoding.MarshalUint64(dst, isFull)
|
||||
dst = encoding.MarshalUint64(dst, hm.hour)
|
||||
dst = encoding.MarshalUint64(dst, uint64(len(hm.m)))
|
||||
for metricID := range hm.m {
|
||||
dst = encoding.MarshalUint64(dst, metricID)
|
||||
}
|
||||
if err := ioutil.WriteFile(path, dst, 0644); err != nil {
|
||||
logger.Panicf("FATAL: cannot write %d bytes to %q: %s", len(dst), path, err)
|
||||
}
|
||||
logger.Infof("saved %s to %q in %s; entriesCount: %d; bytesSize: %d", name, path, time.Since(startTime), len(hm.m), len(dst))
|
||||
}
|
||||
|
||||
func (s *Storage) mustLoadCache(info, name string, bytesSize int) *fastcache.Cache {
|
||||
path := s.cachePath + "/" + name
|
||||
logger.Infof("loading %s cache from %q...", info, path)
|
||||
@@ -475,6 +602,39 @@ func (s *Storage) SearchTagValues(tagKey []byte, maxTagValues int) ([]string, er
|
||||
return s.idb().SearchTagValues(tagKey, maxTagValues)
|
||||
}
|
||||
|
||||
// SearchTagEntries returns a list of (tagName -> tagValues) for (accountID, projectID).
|
||||
func (s *Storage) SearchTagEntries(maxTagKeys, maxTagValues int) ([]TagEntry, error) {
|
||||
idb := s.idb()
|
||||
keys, err := idb.SearchTagKeys(maxTagKeys)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot search tag keys: %s", err)
|
||||
}
|
||||
|
||||
// Sort keys for faster seeks below
|
||||
sort.Strings(keys)
|
||||
|
||||
tes := make([]TagEntry, len(keys))
|
||||
for i, key := range keys {
|
||||
values, err := idb.SearchTagValues([]byte(key), maxTagValues)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot search values for tag %q: %s", key, err)
|
||||
}
|
||||
te := &tes[i]
|
||||
te.Key = key
|
||||
te.Values = values
|
||||
}
|
||||
return tes, nil
|
||||
}
|
||||
|
||||
// TagEntry contains (tagName -> tagValues) mapping
|
||||
type TagEntry struct {
|
||||
// Key is tagName
|
||||
Key string
|
||||
|
||||
// Values contains all the values for Key.
|
||||
Values []string
|
||||
}
|
||||
|
||||
// GetSeriesCount returns the approximate number of unique time series.
|
||||
//
|
||||
// It includes the deleted series too and may count the same series
|
||||
@@ -553,12 +713,13 @@ func (s *Storage) AddRows(mrs []MetricRow, precisionBits uint8) error {
|
||||
// Limit the number of concurrent goroutines that may add rows to the storage.
|
||||
// This should prevent from out of memory errors and CPU trashing when too many
|
||||
// goroutines call AddRows.
|
||||
t := time.NewTimer(addRowsTimeout)
|
||||
t := timerpool.Get(addRowsTimeout)
|
||||
select {
|
||||
case addRowsConcurrencyCh <- struct{}{}:
|
||||
t.Stop()
|
||||
timerpool.Put(t)
|
||||
defer func() { <-addRowsConcurrencyCh }()
|
||||
case <-t.C:
|
||||
timerpool.Put(t)
|
||||
return fmt.Errorf("Cannot add %d rows to storage in %s, since it is overloaded with %d concurrent writers. Add more CPUs or reduce load",
|
||||
len(mrs), addRowsTimeout, cap(addRowsConcurrencyCh))
|
||||
}
|
||||
@@ -656,13 +817,14 @@ func (s *Storage) add(rows []rawRow, mrs []MetricRow, precisionBits uint8) ([]ra
|
||||
errors = s.updateDateMetricIDCache(rows, errors)
|
||||
if len(errors) > 0 {
|
||||
// Return only the first error, since it has no sense in returning all errors.
|
||||
return rows, fmt.Errorf("errors occured during rows addition: %s", errors[0])
|
||||
return rows, fmt.Errorf("errors occurred during rows addition: %s", errors[0])
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func (s *Storage) updateDateMetricIDCache(rows []rawRow, errors []error) []error {
|
||||
var date uint64
|
||||
var hour uint64
|
||||
var prevTimestamp int64
|
||||
kb := kbPool.Get()
|
||||
defer kbPool.Put(kb)
|
||||
@@ -674,17 +836,30 @@ func (s *Storage) updateDateMetricIDCache(rows []rawRow, errors []error) []error
|
||||
r := &rows[i]
|
||||
if r.Timestamp != prevTimestamp {
|
||||
date = uint64(r.Timestamp) / msecPerDay
|
||||
hour = uint64(r.Timestamp) / msecPerHour
|
||||
prevTimestamp = r.Timestamp
|
||||
}
|
||||
metricID := r.TSID.MetricID
|
||||
hm := s.currHourMetricIDs.Load().(*hourMetricIDs)
|
||||
if hour == hm.hour {
|
||||
// The r belongs to the current hour. Check for the current hour cache.
|
||||
if _, ok := hm.m[metricID]; ok {
|
||||
// Fast path: the metricID is in the current hour cache.
|
||||
continue
|
||||
}
|
||||
s.pendingHourMetricIDsLock.Lock()
|
||||
s.pendingHourMetricIDs[metricID] = struct{}{}
|
||||
s.pendingHourMetricIDsLock.Unlock()
|
||||
}
|
||||
|
||||
// Slower path: check global cache for (date, metricID) entry.
|
||||
a[0] = date
|
||||
a[1] = metricID
|
||||
if s.dateMetricIDCache.Has(keyBuf) {
|
||||
// Fast path: the (date, metricID) entry is in the cache.
|
||||
continue
|
||||
}
|
||||
|
||||
// Slow path: store the entry in the cache and in the indexDB.
|
||||
// Slow path: store the entry in the (date, metricID) cache and in the indexDB.
|
||||
// It is OK if the (date, metricID) entry is added multiple times to db
|
||||
// by concurrent goroutines.
|
||||
s.dateMetricIDCache.Set(keyBuf, nil)
|
||||
@@ -696,6 +871,54 @@ func (s *Storage) updateDateMetricIDCache(rows []rawRow, errors []error) []error
|
||||
return errors
|
||||
}
|
||||
|
||||
func (s *Storage) updateCurrHourMetricIDs() {
|
||||
hm := s.currHourMetricIDs.Load().(*hourMetricIDs)
|
||||
s.pendingHourMetricIDsLock.Lock()
|
||||
newMetricIDsLen := len(s.pendingHourMetricIDs)
|
||||
s.pendingHourMetricIDsLock.Unlock()
|
||||
hour := uint64(timestampFromTime(time.Now())) / msecPerHour
|
||||
if newMetricIDsLen == 0 && hm.hour == hour {
|
||||
// Fast path: nothing to update.
|
||||
return
|
||||
}
|
||||
|
||||
// Slow path: hm.m must be updated with non-empty s.pendingHourMetricIDs.
|
||||
var m map[uint64]struct{}
|
||||
isFull := hm.isFull
|
||||
if hm.hour == hour {
|
||||
m = make(map[uint64]struct{}, len(hm.m)+newMetricIDsLen)
|
||||
for metricID := range hm.m {
|
||||
m[metricID] = struct{}{}
|
||||
}
|
||||
} else {
|
||||
m = make(map[uint64]struct{}, newMetricIDsLen)
|
||||
isFull = true
|
||||
}
|
||||
s.pendingHourMetricIDsLock.Lock()
|
||||
newMetricIDs := s.pendingHourMetricIDs
|
||||
s.pendingHourMetricIDs = make(map[uint64]struct{}, len(newMetricIDs))
|
||||
s.pendingHourMetricIDsLock.Unlock()
|
||||
for metricID := range newMetricIDs {
|
||||
m[metricID] = struct{}{}
|
||||
}
|
||||
|
||||
hmNew := &hourMetricIDs{
|
||||
m: m,
|
||||
hour: hour,
|
||||
isFull: isFull,
|
||||
}
|
||||
s.currHourMetricIDs.Store(hmNew)
|
||||
if hm.hour != hour {
|
||||
s.prevHourMetricIDs.Store(hm)
|
||||
}
|
||||
}
|
||||
|
||||
type hourMetricIDs struct {
|
||||
m map[uint64]struct{}
|
||||
hour uint64
|
||||
isFull bool
|
||||
}
|
||||
|
||||
func (s *Storage) getTSIDFromCache(dst *TSID, metricName []byte) bool {
|
||||
buf := (*[unsafe.Sizeof(*dst)]byte)(unsafe.Pointer(dst))[:]
|
||||
buf = s.tsidCache.Get(buf[:0], metricName)
|
||||
@@ -707,7 +930,7 @@ func (s *Storage) putTSIDToCache(tsid *TSID, metricName []byte) {
|
||||
s.tsidCache.Set(metricName, buf)
|
||||
}
|
||||
|
||||
func openIndexDBTables(path string, metricIDCache, metricNameCache *fastcache.Cache) (curr, prev *indexDB, err error) {
|
||||
func openIndexDBTables(path string, metricIDCache, metricNameCache *fastcache.Cache, currHourMetricIDs, prevHourMetricIDs *atomic.Value) (curr, prev *indexDB, err error) {
|
||||
if err := fs.MkdirAllIfNotExist(path); err != nil {
|
||||
return nil, nil, fmt.Errorf("cannot create directory %q: %s", path, err)
|
||||
}
|
||||
@@ -756,24 +979,22 @@ func openIndexDBTables(path string, metricIDCache, metricNameCache *fastcache.Ca
|
||||
for _, tn := range tableNames[:len(tableNames)-2] {
|
||||
pathToRemove := path + "/" + tn
|
||||
logger.Infof("removing obsolete indexdb dir %q...", pathToRemove)
|
||||
if err := os.RemoveAll(pathToRemove); err != nil {
|
||||
return nil, nil, fmt.Errorf("cannot remove obsolete indexdb dir %q: %s", pathToRemove, err)
|
||||
}
|
||||
fs.MustRemoveAll(pathToRemove)
|
||||
logger.Infof("removed obsolete indexdb dir %q", pathToRemove)
|
||||
}
|
||||
|
||||
// Persist changes on the file system.
|
||||
fs.SyncPath(path)
|
||||
fs.MustSyncPath(path)
|
||||
|
||||
// Open the last two tables.
|
||||
currPath := path + "/" + tableNames[len(tableNames)-1]
|
||||
|
||||
curr, err = openIndexDB(currPath, metricIDCache, metricNameCache)
|
||||
curr, err = openIndexDB(currPath, metricIDCache, metricNameCache, currHourMetricIDs, prevHourMetricIDs)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("cannot open curr indexdb table at %q: %s", currPath, err)
|
||||
}
|
||||
prevPath := path + "/" + tableNames[len(tableNames)-2]
|
||||
prev, err = openIndexDB(prevPath, metricIDCache, metricNameCache)
|
||||
prev, err = openIndexDB(prevPath, metricIDCache, metricNameCache, currHourMetricIDs, prevHourMetricIDs)
|
||||
if err != nil {
|
||||
curr.MustClose()
|
||||
return nil, nil, fmt.Errorf("cannot open prev indexdb table at %q: %s", prevPath, err)
|
||||
|
||||
@@ -11,6 +11,184 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestUpdateCurrHourMetricIDs(t *testing.T) {
|
||||
newStorage := func() *Storage {
|
||||
var s Storage
|
||||
s.currHourMetricIDs.Store(&hourMetricIDs{})
|
||||
s.prevHourMetricIDs.Store(&hourMetricIDs{})
|
||||
s.pendingHourMetricIDs = make(map[uint64]struct{})
|
||||
return &s
|
||||
}
|
||||
t.Run("empty_pedning_metric_ids_stale_curr_hour", func(t *testing.T) {
|
||||
s := newStorage()
|
||||
hour := uint64(timestampFromTime(time.Now())) / msecPerHour
|
||||
hmOrig := &hourMetricIDs{
|
||||
m: map[uint64]struct{}{
|
||||
12: {},
|
||||
34: {},
|
||||
},
|
||||
hour: 123,
|
||||
}
|
||||
s.currHourMetricIDs.Store(hmOrig)
|
||||
s.updateCurrHourMetricIDs()
|
||||
hmCurr := s.currHourMetricIDs.Load().(*hourMetricIDs)
|
||||
if hmCurr.hour != hour {
|
||||
// It is possible new hour occured. Update the hour and verify it again.
|
||||
hour = uint64(timestampFromTime(time.Now())) / msecPerHour
|
||||
if hmCurr.hour != hour {
|
||||
t.Fatalf("unexpected hmCurr.hour; got %d; want %d", hmCurr.hour, hour)
|
||||
}
|
||||
}
|
||||
if len(hmCurr.m) != 0 {
|
||||
t.Fatalf("unexpected length of hm.m; got %d; want %d", len(hmCurr.m), 0)
|
||||
}
|
||||
if !hmCurr.isFull {
|
||||
t.Fatalf("unexpected hmCurr.isFull; got %v; want %v", hmCurr.isFull, true)
|
||||
}
|
||||
|
||||
hmPrev := s.prevHourMetricIDs.Load().(*hourMetricIDs)
|
||||
if !reflect.DeepEqual(hmPrev, hmOrig) {
|
||||
t.Fatalf("unexpected hmPrev; got %v; want %v", hmPrev, hmOrig)
|
||||
}
|
||||
|
||||
if len(s.pendingHourMetricIDs) != 0 {
|
||||
t.Fatalf("unexpected len(s.pendingHourMetricIDs); got %d; want %d", len(s.pendingHourMetricIDs), 0)
|
||||
}
|
||||
})
|
||||
t.Run("empty_pedning_metric_ids_valid_curr_hour", func(t *testing.T) {
|
||||
s := newStorage()
|
||||
hour := uint64(timestampFromTime(time.Now())) / msecPerHour
|
||||
hmOrig := &hourMetricIDs{
|
||||
m: map[uint64]struct{}{
|
||||
12: {},
|
||||
34: {},
|
||||
},
|
||||
hour: hour,
|
||||
}
|
||||
s.currHourMetricIDs.Store(hmOrig)
|
||||
s.updateCurrHourMetricIDs()
|
||||
hmCurr := s.currHourMetricIDs.Load().(*hourMetricIDs)
|
||||
if hmCurr.hour != hour {
|
||||
// It is possible new hour occured. Update the hour and verify it again.
|
||||
hour = uint64(timestampFromTime(time.Now())) / msecPerHour
|
||||
if hmCurr.hour != hour {
|
||||
t.Fatalf("unexpected hmCurr.hour; got %d; want %d", hmCurr.hour, hour)
|
||||
}
|
||||
// Do not run other checks, since they may fail.
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(hmCurr, hmOrig) {
|
||||
t.Fatalf("unexpected hmCurr; got %v; want %v", hmCurr, hmOrig)
|
||||
}
|
||||
if hmCurr.isFull {
|
||||
t.Fatalf("unexpected hmCurr.isFull; got %v; want %v", hmCurr.isFull, false)
|
||||
}
|
||||
|
||||
hmPrev := s.prevHourMetricIDs.Load().(*hourMetricIDs)
|
||||
hmEmpty := &hourMetricIDs{}
|
||||
if !reflect.DeepEqual(hmPrev, hmEmpty) {
|
||||
t.Fatalf("unexpected hmPrev; got %v; want %v", hmPrev, hmEmpty)
|
||||
}
|
||||
|
||||
if len(s.pendingHourMetricIDs) != 0 {
|
||||
t.Fatalf("unexpected len(s.pendingHourMetricIDs); got %d; want %d", len(s.pendingHourMetricIDs), 0)
|
||||
}
|
||||
})
|
||||
t.Run("nonempty_pending_metric_ids_stale_curr_hour", func(t *testing.T) {
|
||||
s := newStorage()
|
||||
pendingHourMetricIDs := map[uint64]struct{}{
|
||||
343: {},
|
||||
32424: {},
|
||||
8293432: {},
|
||||
}
|
||||
s.pendingHourMetricIDs = pendingHourMetricIDs
|
||||
|
||||
hour := uint64(timestampFromTime(time.Now())) / msecPerHour
|
||||
hmOrig := &hourMetricIDs{
|
||||
m: map[uint64]struct{}{
|
||||
12: {},
|
||||
34: {},
|
||||
},
|
||||
hour: 123,
|
||||
}
|
||||
s.currHourMetricIDs.Store(hmOrig)
|
||||
s.updateCurrHourMetricIDs()
|
||||
hmCurr := s.currHourMetricIDs.Load().(*hourMetricIDs)
|
||||
if hmCurr.hour != hour {
|
||||
// It is possible new hour occured. Update the hour and verify it again.
|
||||
hour = uint64(timestampFromTime(time.Now())) / msecPerHour
|
||||
if hmCurr.hour != hour {
|
||||
t.Fatalf("unexpected hmCurr.hour; got %d; want %d", hmCurr.hour, hour)
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(hmCurr.m, pendingHourMetricIDs) {
|
||||
t.Fatalf("unexpected hm.m; got %v; want %v", hmCurr.m, pendingHourMetricIDs)
|
||||
}
|
||||
if !hmCurr.isFull {
|
||||
t.Fatalf("unexpected hmCurr.isFull; got %v; want %v", hmCurr.isFull, true)
|
||||
}
|
||||
|
||||
hmPrev := s.prevHourMetricIDs.Load().(*hourMetricIDs)
|
||||
if !reflect.DeepEqual(hmPrev, hmOrig) {
|
||||
t.Fatalf("unexpected hmPrev; got %v; want %v", hmPrev, hmOrig)
|
||||
}
|
||||
|
||||
if len(s.pendingHourMetricIDs) != 0 {
|
||||
t.Fatalf("unexpected len(s.pendingHourMetricIDs); got %d; want %d", len(s.pendingHourMetricIDs), 0)
|
||||
}
|
||||
})
|
||||
t.Run("nonempty_pending_metric_ids_valid_curr_hour", func(t *testing.T) {
|
||||
s := newStorage()
|
||||
pendingHourMetricIDs := map[uint64]struct{}{
|
||||
343: {},
|
||||
32424: {},
|
||||
8293432: {},
|
||||
}
|
||||
s.pendingHourMetricIDs = pendingHourMetricIDs
|
||||
|
||||
hour := uint64(timestampFromTime(time.Now())) / msecPerHour
|
||||
hmOrig := &hourMetricIDs{
|
||||
m: map[uint64]struct{}{
|
||||
12: {},
|
||||
34: {},
|
||||
},
|
||||
hour: hour,
|
||||
}
|
||||
s.currHourMetricIDs.Store(hmOrig)
|
||||
s.updateCurrHourMetricIDs()
|
||||
hmCurr := s.currHourMetricIDs.Load().(*hourMetricIDs)
|
||||
if hmCurr.hour != hour {
|
||||
// It is possible new hour occured. Update the hour and verify it again.
|
||||
hour = uint64(timestampFromTime(time.Now())) / msecPerHour
|
||||
if hmCurr.hour != hour {
|
||||
t.Fatalf("unexpected hmCurr.hour; got %d; want %d", hmCurr.hour, hour)
|
||||
}
|
||||
// Do not run other checks, since they may fail.
|
||||
return
|
||||
}
|
||||
m := getMetricIDsCopy(pendingHourMetricIDs)
|
||||
for metricID := range hmOrig.m {
|
||||
m[metricID] = struct{}{}
|
||||
}
|
||||
if !reflect.DeepEqual(hmCurr.m, m) {
|
||||
t.Fatalf("unexpected hm.m; got %v; want %v", hmCurr.m, m)
|
||||
}
|
||||
if hmCurr.isFull {
|
||||
t.Fatalf("unexpected hmCurr.isFull; got %v; want %v", hmCurr.isFull, false)
|
||||
}
|
||||
|
||||
hmPrev := s.prevHourMetricIDs.Load().(*hourMetricIDs)
|
||||
hmEmpty := &hourMetricIDs{}
|
||||
if !reflect.DeepEqual(hmPrev, hmEmpty) {
|
||||
t.Fatalf("unexpected hmPrev; got %v; want %v", hmPrev, hmEmpty)
|
||||
}
|
||||
|
||||
if len(s.pendingHourMetricIDs) != 0 {
|
||||
t.Fatalf("unexpected len(s.pendingHourMetricIDs); got %d; want %d", len(s.pendingHourMetricIDs), 0)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMetricRowMarshalUnmarshal(t *testing.T) {
|
||||
var buf []byte
|
||||
typ := reflect.TypeOf(&MetricRow{})
|
||||
|
||||
@@ -164,10 +164,10 @@ func (tb *table) CreateSnapshot(snapshotName string) (string, string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
fs.SyncPath(dstSmallDir)
|
||||
fs.SyncPath(dstBigDir)
|
||||
fs.SyncPath(filepath.Dir(dstSmallDir))
|
||||
fs.SyncPath(filepath.Dir(dstBigDir))
|
||||
fs.MustSyncPath(dstSmallDir)
|
||||
fs.MustSyncPath(dstBigDir)
|
||||
fs.MustSyncPath(filepath.Dir(dstSmallDir))
|
||||
fs.MustSyncPath(filepath.Dir(dstBigDir))
|
||||
|
||||
logger.Infof("created table snapshot for %q at (%q, %q) in %s", tb.path, dstSmallDir, dstBigDir, time.Since(startTime))
|
||||
return dstSmallDir, dstBigDir, nil
|
||||
@@ -176,9 +176,9 @@ func (tb *table) CreateSnapshot(snapshotName string) (string, string, error) {
|
||||
// MustDeleteSnapshot deletes snapshot with the given snapshotName.
|
||||
func (tb *table) MustDeleteSnapshot(snapshotName string) {
|
||||
smallDir := fmt.Sprintf("%s/small/snapshots/%s", tb.path, snapshotName)
|
||||
fs.MustRemoveAllSynced(smallDir)
|
||||
fs.MustRemoveAll(smallDir)
|
||||
bigDir := fmt.Sprintf("%s/big/snapshots/%s", tb.path, snapshotName)
|
||||
fs.MustRemoveAllSynced(bigDir)
|
||||
fs.MustRemoveAll(bigDir)
|
||||
}
|
||||
|
||||
func (tb *table) addPartitionNolock(pt *partition) {
|
||||
|
||||
@@ -92,7 +92,7 @@ func createBenchTable(b *testing.B, path string, startTimestamp int64, rowsPerIn
|
||||
r.Value = value
|
||||
}
|
||||
if err := tb.AddRows(rows); err != nil {
|
||||
b.Fatalf("cannot add %d rows: %s", rowsPerInsert, err)
|
||||
panic(fmt.Errorf("cannot add %d rows: %s", rowsPerInsert, err))
|
||||
}
|
||||
}
|
||||
wg.Done()
|
||||
|
||||
@@ -81,6 +81,13 @@ func (tfs *TagFilters) Reset() {
|
||||
tfs.commonPrefix = marshalCommonPrefix(tfs.commonPrefix[:0], nsPrefixTagToMetricID)
|
||||
}
|
||||
|
||||
func (tfs *TagFilters) marshal(dst []byte) []byte {
|
||||
for i := range tfs.tfs {
|
||||
dst = tfs.tfs[i].Marshal(dst)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// tagFilter represents a filter used for filtering tags.
|
||||
type tagFilter struct {
|
||||
key []byte
|
||||
|
||||
@@ -67,3 +67,5 @@ func (tr *TimeRange) fromPartitionTime(t time.Time) {
|
||||
}
|
||||
|
||||
const msecPerDay = 24 * 3600 * 1000
|
||||
|
||||
const msecPerHour = 3600 * 1000
|
||||
|
||||
38
lib/timerpool/timerpool.go
Normal file
38
lib/timerpool/timerpool.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package timerpool
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
)
|
||||
|
||||
// Get returns a timer for the given duration d from the pool.
|
||||
//
|
||||
// Return back the timer to the pool with Put.
|
||||
func Get(d time.Duration) *time.Timer {
|
||||
if v := timerPool.Get(); v != nil {
|
||||
t := v.(*time.Timer)
|
||||
if t.Reset(d) {
|
||||
logger.Panicf("BUG: active timer trapped to the pool!")
|
||||
}
|
||||
return t
|
||||
}
|
||||
return time.NewTimer(d)
|
||||
}
|
||||
|
||||
// Put returns t to the pool.
|
||||
//
|
||||
// t cannot be accessed after returning to the pool.
|
||||
func Put(t *time.Timer) {
|
||||
if !t.Stop() {
|
||||
// Drain t.C if it wasn't obtained by the caller yet.
|
||||
select {
|
||||
case <-t.C:
|
||||
default:
|
||||
}
|
||||
}
|
||||
timerPool.Put(t)
|
||||
}
|
||||
|
||||
var timerPool sync.Pool
|
||||
15
vendor/github.com/VictoriaMetrics/fastcache/fastcache.go
generated
vendored
15
vendor/github.com/VictoriaMetrics/fastcache/fastcache.go
generated
vendored
@@ -17,6 +17,10 @@ const chunkSize = 64 * 1024
|
||||
|
||||
const bucketSizeBits = 40
|
||||
|
||||
const genSizeBits = 64 - bucketSizeBits
|
||||
|
||||
const maxGen = 1<<genSizeBits - 1
|
||||
|
||||
const maxBucketSize uint64 = 1 << bucketSizeBits
|
||||
|
||||
// Stats represents cache stats.
|
||||
@@ -254,13 +258,13 @@ func (b *bucket) Reset() {
|
||||
|
||||
func (b *bucket) Clean() {
|
||||
b.mu.Lock()
|
||||
bGen := b.gen
|
||||
bGen := b.gen & ((1 << genSizeBits) - 1)
|
||||
bIdx := b.idx
|
||||
bm := b.m
|
||||
for k, v := range bm {
|
||||
gen := v >> bucketSizeBits
|
||||
idx := v & ((1 << bucketSizeBits) - 1)
|
||||
if gen == bGen && idx < bIdx || gen+1 == bGen && idx >= bIdx {
|
||||
if gen == bGen && idx < bIdx || gen+1 == bGen && idx >= bIdx || gen == maxGen && bGen == 1 && idx >= bIdx {
|
||||
continue
|
||||
}
|
||||
delete(bm, k)
|
||||
@@ -317,8 +321,8 @@ func (b *bucket) Set(k, v []byte, h uint64) {
|
||||
idxNew = kvLen
|
||||
chunkIdx = 0
|
||||
b.gen++
|
||||
if b.gen == 0 {
|
||||
b.gen = 1
|
||||
if b.gen&((1<<genSizeBits)-1) == 0 {
|
||||
b.gen++
|
||||
}
|
||||
} else {
|
||||
idx = chunkIdxNew * chunkSize
|
||||
@@ -346,10 +350,11 @@ func (b *bucket) Get(dst, k []byte, h uint64, returnDst bool) ([]byte, bool) {
|
||||
found := false
|
||||
b.mu.RLock()
|
||||
v := b.m[h]
|
||||
bGen := b.gen & ((1 << genSizeBits) - 1)
|
||||
if v > 0 {
|
||||
gen := v >> bucketSizeBits
|
||||
idx := v & ((1 << bucketSizeBits) - 1)
|
||||
if gen == b.gen && idx < b.idx || gen+1 == b.gen && idx >= b.idx {
|
||||
if gen == bGen && idx < b.idx || gen+1 == bGen && idx >= b.idx || gen == maxGen && bGen == 1 && idx >= b.idx {
|
||||
chunkIdx := idx / chunkSize
|
||||
if chunkIdx >= uint64(len(b.chunks)) {
|
||||
// Corrupted data during the load from file. Just skip it.
|
||||
|
||||
31
vendor/github.com/VictoriaMetrics/metrics/counter.go
generated
vendored
31
vendor/github.com/VictoriaMetrics/metrics/counter.go
generated
vendored
@@ -17,9 +17,7 @@ import (
|
||||
//
|
||||
// The returned counter is safe to use from concurrent goroutines.
|
||||
func NewCounter(name string) *Counter {
|
||||
c := &Counter{}
|
||||
registerMetric(name, c)
|
||||
return c
|
||||
return defaultSet.NewCounter(name)
|
||||
}
|
||||
|
||||
// Counter is a counter.
|
||||
@@ -75,30 +73,5 @@ func (c *Counter) marshalTo(prefix string, w io.Writer) {
|
||||
//
|
||||
// Performance tip: prefer NewCounter instead of GetOrCreateCounter.
|
||||
func GetOrCreateCounter(name string) *Counter {
|
||||
metricsMapLock.Lock()
|
||||
nm := metricsMap[name]
|
||||
metricsMapLock.Unlock()
|
||||
if nm == nil {
|
||||
// Slow path - create and register missing counter.
|
||||
if err := validateMetric(name); err != nil {
|
||||
panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err))
|
||||
}
|
||||
nmNew := &namedMetric{
|
||||
name: name,
|
||||
metric: &Counter{},
|
||||
}
|
||||
metricsMapLock.Lock()
|
||||
nm = metricsMap[name]
|
||||
if nm == nil {
|
||||
nm = nmNew
|
||||
metricsMap[name] = nm
|
||||
metricsList = append(metricsList, nm)
|
||||
}
|
||||
metricsMapLock.Unlock()
|
||||
}
|
||||
c, ok := nm.metric.(*Counter)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("BUG: metric %q isn't a Counter. It is %T", name, nm.metric))
|
||||
}
|
||||
return c
|
||||
return defaultSet.GetOrCreateCounter(name)
|
||||
}
|
||||
|
||||
41
vendor/github.com/VictoriaMetrics/metrics/gauge.go
generated
vendored
41
vendor/github.com/VictoriaMetrics/metrics/gauge.go
generated
vendored
@@ -19,14 +19,7 @@ import (
|
||||
//
|
||||
// The returned gauge is safe to use from concurrent goroutines.
|
||||
func NewGauge(name string, f func() float64) *Gauge {
|
||||
if f == nil {
|
||||
panic(fmt.Errorf("BUG: f cannot be nil"))
|
||||
}
|
||||
g := &Gauge{
|
||||
f: f,
|
||||
}
|
||||
registerMetric(name, g)
|
||||
return g
|
||||
return defaultSet.NewGauge(name, f)
|
||||
}
|
||||
|
||||
// Gauge is a float64 gauge.
|
||||
@@ -66,35 +59,5 @@ func (g *Gauge) marshalTo(prefix string, w io.Writer) {
|
||||
//
|
||||
// Performance tip: prefer NewGauge instead of GetOrCreateGauge.
|
||||
func GetOrCreateGauge(name string, f func() float64) *Gauge {
|
||||
metricsMapLock.Lock()
|
||||
nm := metricsMap[name]
|
||||
metricsMapLock.Unlock()
|
||||
if nm == nil {
|
||||
// Slow path - create and register missing gauge.
|
||||
if f == nil {
|
||||
panic(fmt.Errorf("BUG: f cannot be nil"))
|
||||
}
|
||||
if err := validateMetric(name); err != nil {
|
||||
panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err))
|
||||
}
|
||||
nmNew := &namedMetric{
|
||||
name: name,
|
||||
metric: &Gauge{
|
||||
f: f,
|
||||
},
|
||||
}
|
||||
metricsMapLock.Lock()
|
||||
nm = metricsMap[name]
|
||||
if nm == nil {
|
||||
nm = nmNew
|
||||
metricsMap[name] = nm
|
||||
metricsList = append(metricsList, nm)
|
||||
}
|
||||
metricsMapLock.Unlock()
|
||||
}
|
||||
g, ok := nm.metric.(*Gauge)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("BUG: metric %q isn't a Gauge. It is %T", name, nm.metric))
|
||||
}
|
||||
return g
|
||||
return defaultSet.GetOrCreateGauge(name, f)
|
||||
}
|
||||
|
||||
47
vendor/github.com/VictoriaMetrics/metrics/metrics.go
generated
vendored
47
vendor/github.com/VictoriaMetrics/metrics/metrics.go
generated
vendored
@@ -8,25 +8,18 @@
|
||||
// 1. Register the required metrics via New* functions.
|
||||
// 2. Expose them to `/metrics` page via WritePrometheus.
|
||||
// 3. Update the registered metrics during application lifetime.
|
||||
//
|
||||
// The package has been extracted from https://victoriametrics.com/
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/valyala/histogram"
|
||||
)
|
||||
|
||||
var (
|
||||
metricsMapLock sync.Mutex
|
||||
metricsList []*namedMetric
|
||||
metricsMap = make(map[string]*namedMetric)
|
||||
)
|
||||
|
||||
type namedMetric struct {
|
||||
name string
|
||||
metric metric
|
||||
@@ -36,6 +29,8 @@ type metric interface {
|
||||
marshalTo(prefix string, w io.Writer)
|
||||
}
|
||||
|
||||
var defaultSet = NewSet()
|
||||
|
||||
// WritePrometheus writes all the registered metrics in Prometheus format to w.
|
||||
//
|
||||
// If exposeProcessMetrics is true, then various `go_*` metrics are exposed
|
||||
@@ -48,17 +43,7 @@ type metric interface {
|
||||
// })
|
||||
//
|
||||
func WritePrometheus(w io.Writer, exposeProcessMetrics bool) {
|
||||
lessFunc := func(i, j int) bool {
|
||||
return metricsList[i].name < metricsList[j].name
|
||||
}
|
||||
metricsMapLock.Lock()
|
||||
if !sort.SliceIsSorted(metricsList, lessFunc) {
|
||||
sort.Slice(metricsList, lessFunc)
|
||||
}
|
||||
for _, nm := range metricsList {
|
||||
nm.metric.marshalTo(nm.name, w)
|
||||
}
|
||||
metricsMapLock.Unlock()
|
||||
defaultSet.WritePrometheus(w)
|
||||
if exposeProcessMetrics {
|
||||
writeProcessMetrics(w)
|
||||
}
|
||||
@@ -118,25 +103,3 @@ func writeProcessMetrics(w io.Writer) {
|
||||
fmt.Fprintf(w, "go_info_ext{compiler=%q, GOARCH=%q, GOOS=%q, GOROOT=%q} 1\n",
|
||||
runtime.Compiler, runtime.GOARCH, runtime.GOOS, runtime.GOROOT())
|
||||
}
|
||||
|
||||
var startTime = time.Now()
|
||||
|
||||
func registerMetric(name string, m metric) {
|
||||
if err := validateMetric(name); err != nil {
|
||||
panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err))
|
||||
}
|
||||
metricsMapLock.Lock()
|
||||
nm, ok := metricsMap[name]
|
||||
if !ok {
|
||||
nm = &namedMetric{
|
||||
name: name,
|
||||
metric: m,
|
||||
}
|
||||
metricsMap[name] = nm
|
||||
metricsList = append(metricsList, nm)
|
||||
}
|
||||
metricsMapLock.Unlock()
|
||||
if ok {
|
||||
panic(fmt.Errorf("BUG: metric %q is already registered", name))
|
||||
}
|
||||
}
|
||||
|
||||
308
vendor/github.com/VictoriaMetrics/metrics/set.go
generated
vendored
Normal file
308
vendor/github.com/VictoriaMetrics/metrics/set.go
generated
vendored
Normal file
@@ -0,0 +1,308 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Set is a set of metrics.
|
||||
//
|
||||
// Metrics belonging to a set are exported separately from global metrics.
|
||||
//
|
||||
// Set.WritePrometheus must be called for exporting metrics from the set.
|
||||
type Set struct {
|
||||
mu sync.Mutex
|
||||
a []*namedMetric
|
||||
m map[string]*namedMetric
|
||||
}
|
||||
|
||||
// NewSet creates new set of metrics.
|
||||
func NewSet() *Set {
|
||||
return &Set{
|
||||
m: make(map[string]*namedMetric),
|
||||
}
|
||||
}
|
||||
|
||||
// WritePrometheus writes all the metrics from s to w in Prometheus format.
|
||||
func (s *Set) WritePrometheus(w io.Writer) {
|
||||
lessFunc := func(i, j int) bool {
|
||||
return s.a[i].name < s.a[j].name
|
||||
}
|
||||
s.mu.Lock()
|
||||
if !sort.SliceIsSorted(s.a, lessFunc) {
|
||||
sort.Slice(s.a, lessFunc)
|
||||
}
|
||||
for _, nm := range s.a {
|
||||
nm.metric.marshalTo(nm.name, w)
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
// NewCounter registers and returns new counter with the given name in the s.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible lables.
|
||||
// For instance,
|
||||
//
|
||||
// * foo
|
||||
// * foo{bar="baz"}
|
||||
// * foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// The returned counter is safe to use from concurrent goroutines.
|
||||
func (s *Set) NewCounter(name string) *Counter {
|
||||
c := &Counter{}
|
||||
s.registerMetric(name, c)
|
||||
return c
|
||||
}
|
||||
|
||||
// GetOrCreateCounter returns registered counter in s with the given name
|
||||
// or creates new counter if s doesn't contain counter with the given name.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible lables.
|
||||
// For instance,
|
||||
//
|
||||
// * foo
|
||||
// * foo{bar="baz"}
|
||||
// * foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// The returned counter is safe to use from concurrent goroutines.
|
||||
//
|
||||
// Performance tip: prefer NewCounter instead of GetOrCreateCounter.
|
||||
func (s *Set) GetOrCreateCounter(name string) *Counter {
|
||||
s.mu.Lock()
|
||||
nm := s.m[name]
|
||||
s.mu.Unlock()
|
||||
if nm == nil {
|
||||
// Slow path - create and register missing counter.
|
||||
if err := validateMetric(name); err != nil {
|
||||
panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err))
|
||||
}
|
||||
nmNew := &namedMetric{
|
||||
name: name,
|
||||
metric: &Counter{},
|
||||
}
|
||||
s.mu.Lock()
|
||||
nm = s.m[name]
|
||||
if nm == nil {
|
||||
nm = nmNew
|
||||
s.m[name] = nm
|
||||
s.a = append(s.a, nm)
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
c, ok := nm.metric.(*Counter)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("BUG: metric %q isn't a Counter. It is %T", name, nm.metric))
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// NewGauge registers and returns gauge with the given name in s, which calls f
|
||||
// to obtain gauge value.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible labels.
|
||||
// For instance,
|
||||
//
|
||||
// * foo
|
||||
// * foo{bar="baz"}
|
||||
// * foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// f must be safe for concurrent calls.
|
||||
//
|
||||
// The returned gauge is safe to use from concurrent goroutines.
|
||||
func (s *Set) NewGauge(name string, f func() float64) *Gauge {
|
||||
if f == nil {
|
||||
panic(fmt.Errorf("BUG: f cannot be nil"))
|
||||
}
|
||||
g := &Gauge{
|
||||
f: f,
|
||||
}
|
||||
s.registerMetric(name, g)
|
||||
return g
|
||||
}
|
||||
|
||||
// GetOrCreateGauge returns registered gauge with the given name in s
|
||||
// or creates new gauge if s doesn't contain gauge with the given name.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible lables.
|
||||
// For instance,
|
||||
//
|
||||
// * foo
|
||||
// * foo{bar="baz"}
|
||||
// * foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// The returned gauge is safe to use from concurrent goroutines.
|
||||
//
|
||||
// Performance tip: prefer NewGauge instead of GetOrCreateGauge.
|
||||
func (s *Set) GetOrCreateGauge(name string, f func() float64) *Gauge {
|
||||
s.mu.Lock()
|
||||
nm := s.m[name]
|
||||
s.mu.Unlock()
|
||||
if nm == nil {
|
||||
// Slow path - create and register missing gauge.
|
||||
if f == nil {
|
||||
panic(fmt.Errorf("BUG: f cannot be nil"))
|
||||
}
|
||||
if err := validateMetric(name); err != nil {
|
||||
panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err))
|
||||
}
|
||||
nmNew := &namedMetric{
|
||||
name: name,
|
||||
metric: &Gauge{
|
||||
f: f,
|
||||
},
|
||||
}
|
||||
s.mu.Lock()
|
||||
nm = s.m[name]
|
||||
if nm == nil {
|
||||
nm = nmNew
|
||||
s.m[name] = nm
|
||||
s.a = append(s.a, nm)
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
g, ok := nm.metric.(*Gauge)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("BUG: metric %q isn't a Gauge. It is %T", name, nm.metric))
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
// NewSummary creates and returns new summary with the given name in s.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible lables.
|
||||
// For instance,
|
||||
//
|
||||
// * foo
|
||||
// * foo{bar="baz"}
|
||||
// * foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// The returned summary is safe to use from concurrent goroutines.
|
||||
func (s *Set) NewSummary(name string) *Summary {
|
||||
return s.NewSummaryExt(name, defaultSummaryWindow, defaultSummaryQuantiles)
|
||||
}
|
||||
|
||||
// NewSummaryExt creates and returns new summary in s with the given name,
|
||||
// window and quantiles.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible lables.
|
||||
// For instance,
|
||||
//
|
||||
// * foo
|
||||
// * foo{bar="baz"}
|
||||
// * foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// The returned summary is safe to use from concurrent goroutines.
|
||||
func (s *Set) NewSummaryExt(name string, window time.Duration, quantiles []float64) *Summary {
|
||||
sm := newSummary(window, quantiles)
|
||||
s.registerMetric(name, sm)
|
||||
registerSummary(sm)
|
||||
s.registerSummaryQuantiles(name, sm)
|
||||
return sm
|
||||
}
|
||||
|
||||
// GetOrCreateSummary returns registered summary with the given name in s
|
||||
// or creates new summary if s doesn't contain summary with the given name.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible lables.
|
||||
// For instance,
|
||||
//
|
||||
// * foo
|
||||
// * foo{bar="baz"}
|
||||
// * foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// The returned summary is safe to use from concurrent goroutines.
|
||||
//
|
||||
// Performance tip: prefer NewSummary instead of GetOrCreateSummary.
|
||||
func (s *Set) GetOrCreateSummary(name string) *Summary {
|
||||
return s.GetOrCreateSummaryExt(name, defaultSummaryWindow, defaultSummaryQuantiles)
|
||||
}
|
||||
|
||||
// GetOrCreateSummaryExt returns registered summary with the given name,
|
||||
// window and quantiles in s or creates new summary if s doesn't
|
||||
// contain summary with the given name.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible lables.
|
||||
// For instance,
|
||||
//
|
||||
// * foo
|
||||
// * foo{bar="baz"}
|
||||
// * foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// The returned summary is safe to use from concurrent goroutines.
|
||||
//
|
||||
// Performance tip: prefer NewSummaryExt instead of GetOrCreateSummaryExt.
|
||||
func (s *Set) GetOrCreateSummaryExt(name string, window time.Duration, quantiles []float64) *Summary {
|
||||
s.mu.Lock()
|
||||
nm := s.m[name]
|
||||
s.mu.Unlock()
|
||||
if nm == nil {
|
||||
// Slow path - create and register missing summary.
|
||||
if err := validateMetric(name); err != nil {
|
||||
panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err))
|
||||
}
|
||||
sm := newSummary(window, quantiles)
|
||||
nmNew := &namedMetric{
|
||||
name: name,
|
||||
metric: sm,
|
||||
}
|
||||
mustRegisterQuantiles := false
|
||||
s.mu.Lock()
|
||||
nm = s.m[name]
|
||||
if nm == nil {
|
||||
nm = nmNew
|
||||
s.m[name] = nm
|
||||
s.a = append(s.a, nm)
|
||||
registerSummary(sm)
|
||||
mustRegisterQuantiles = true
|
||||
}
|
||||
s.mu.Unlock()
|
||||
if mustRegisterQuantiles {
|
||||
s.registerSummaryQuantiles(name, sm)
|
||||
}
|
||||
}
|
||||
sm, ok := nm.metric.(*Summary)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("BUG: metric %q isn't a Summary. It is %T", name, nm.metric))
|
||||
}
|
||||
if sm.window != window {
|
||||
panic(fmt.Errorf("BUG: invalid window requested for the summary %q; requested %s; need %s", name, window, sm.window))
|
||||
}
|
||||
if !isEqualQuantiles(sm.quantiles, quantiles) {
|
||||
panic(fmt.Errorf("BUG: invalid quantiles requested from the summary %q; requested %v; need %v", name, quantiles, sm.quantiles))
|
||||
}
|
||||
return sm
|
||||
}
|
||||
|
||||
func (s *Set) registerSummaryQuantiles(name string, sm *Summary) {
|
||||
for i, q := range sm.quantiles {
|
||||
quantileValueName := addTag(name, fmt.Sprintf(`quantile="%g"`, q))
|
||||
qv := &quantileValue{
|
||||
sm: sm,
|
||||
idx: i,
|
||||
}
|
||||
s.registerMetric(quantileValueName, qv)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Set) registerMetric(name string, m metric) {
|
||||
if err := validateMetric(name); err != nil {
|
||||
panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err))
|
||||
}
|
||||
s.mu.Lock()
|
||||
nm, ok := s.m[name]
|
||||
if !ok {
|
||||
nm = &namedMetric{
|
||||
name: name,
|
||||
metric: m,
|
||||
}
|
||||
s.m[name] = nm
|
||||
s.a = append(s.a, nm)
|
||||
}
|
||||
s.mu.Unlock()
|
||||
if ok {
|
||||
panic(fmt.Errorf("BUG: metric %q is already registered", name))
|
||||
}
|
||||
}
|
||||
123
vendor/github.com/VictoriaMetrics/metrics/summary.go
generated
vendored
123
vendor/github.com/VictoriaMetrics/metrics/summary.go
generated
vendored
@@ -38,7 +38,7 @@ type Summary struct {
|
||||
//
|
||||
// The returned summary is safe to use from concurrent goroutines.
|
||||
func NewSummary(name string) *Summary {
|
||||
return NewSummaryExt(name, defaultSummaryWindow, defaultSummaryQuantiles)
|
||||
return defaultSet.NewSummary(name)
|
||||
}
|
||||
|
||||
// NewSummaryExt creates and returns new summary with the given name,
|
||||
@@ -53,36 +53,21 @@ func NewSummary(name string) *Summary {
|
||||
//
|
||||
// The returned summary is safe to use from concurrent goroutines.
|
||||
func NewSummaryExt(name string, window time.Duration, quantiles []float64) *Summary {
|
||||
s := newSummary(window, quantiles)
|
||||
registerMetric(name, s)
|
||||
registerSummary(s)
|
||||
registerSummaryQuantiles(name, s)
|
||||
return s
|
||||
return defaultSet.NewSummaryExt(name, window, quantiles)
|
||||
}
|
||||
|
||||
func newSummary(window time.Duration, quantiles []float64) *Summary {
|
||||
// Make a copy of quantiles in order to prevent from their modification by the caller.
|
||||
quantiles = append([]float64{}, quantiles...)
|
||||
validateQuantiles(quantiles)
|
||||
s := &Summary{
|
||||
sm := &Summary{
|
||||
curr: histogram.NewFast(),
|
||||
next: histogram.NewFast(),
|
||||
quantiles: quantiles,
|
||||
quantileValues: make([]float64, len(quantiles)),
|
||||
window: window,
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func registerSummaryQuantiles(name string, s *Summary) {
|
||||
for i, q := range s.quantiles {
|
||||
quantileValueName := addTag(name, fmt.Sprintf(`quantile="%g"`, q))
|
||||
qv := &quantileValue{
|
||||
s: s,
|
||||
idx: i,
|
||||
}
|
||||
registerMetric(quantileValueName, qv)
|
||||
}
|
||||
return sm
|
||||
}
|
||||
|
||||
func validateQuantiles(quantiles []float64) {
|
||||
@@ -94,29 +79,29 @@ func validateQuantiles(quantiles []float64) {
|
||||
}
|
||||
|
||||
// Update updates the summary.
|
||||
func (s *Summary) Update(v float64) {
|
||||
s.mu.Lock()
|
||||
s.curr.Update(v)
|
||||
s.next.Update(v)
|
||||
s.mu.Unlock()
|
||||
func (sm *Summary) Update(v float64) {
|
||||
sm.mu.Lock()
|
||||
sm.curr.Update(v)
|
||||
sm.next.Update(v)
|
||||
sm.mu.Unlock()
|
||||
}
|
||||
|
||||
// UpdateDuration updates request duration based on the given startTime.
|
||||
func (s *Summary) UpdateDuration(startTime time.Time) {
|
||||
func (sm *Summary) UpdateDuration(startTime time.Time) {
|
||||
d := time.Since(startTime).Seconds()
|
||||
s.Update(d)
|
||||
sm.Update(d)
|
||||
}
|
||||
|
||||
func (s *Summary) marshalTo(prefix string, w io.Writer) {
|
||||
// Just update s.quantileValues and don't write anything to w.
|
||||
// s.quantileValues will be marshaled later via quantileValue.marshalTo.
|
||||
s.updateQuantiles()
|
||||
func (sm *Summary) marshalTo(prefix string, w io.Writer) {
|
||||
// Just update sm.quantileValues and don't write anything to w.
|
||||
// sm.quantileValues will be marshaled later via quantileValue.marshalTo.
|
||||
sm.updateQuantiles()
|
||||
}
|
||||
|
||||
func (s *Summary) updateQuantiles() {
|
||||
s.mu.Lock()
|
||||
s.quantileValues = s.curr.Quantiles(s.quantileValues[:0], s.quantiles)
|
||||
s.mu.Unlock()
|
||||
func (sm *Summary) updateQuantiles() {
|
||||
sm.mu.Lock()
|
||||
sm.quantileValues = sm.curr.Quantiles(sm.quantileValues[:0], sm.quantiles)
|
||||
sm.mu.Unlock()
|
||||
}
|
||||
|
||||
// GetOrCreateSummary returns registered summary with the given name
|
||||
@@ -134,7 +119,7 @@ func (s *Summary) updateQuantiles() {
|
||||
//
|
||||
// Performance tip: prefer NewSummary instead of GetOrCreateSummary.
|
||||
func GetOrCreateSummary(name string) *Summary {
|
||||
return GetOrCreateSummaryExt(name, defaultSummaryWindow, defaultSummaryQuantiles)
|
||||
return defaultSet.GetOrCreateSummary(name)
|
||||
}
|
||||
|
||||
// GetOrCreateSummaryExt returns registered summary with the given name,
|
||||
@@ -152,45 +137,7 @@ func GetOrCreateSummary(name string) *Summary {
|
||||
//
|
||||
// Performance tip: prefer NewSummaryExt instead of GetOrCreateSummaryExt.
|
||||
func GetOrCreateSummaryExt(name string, window time.Duration, quantiles []float64) *Summary {
|
||||
metricsMapLock.Lock()
|
||||
nm := metricsMap[name]
|
||||
metricsMapLock.Unlock()
|
||||
if nm == nil {
|
||||
// Slow path - create and register missing summary.
|
||||
if err := validateMetric(name); err != nil {
|
||||
panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err))
|
||||
}
|
||||
s := newSummary(window, quantiles)
|
||||
nmNew := &namedMetric{
|
||||
name: name,
|
||||
metric: s,
|
||||
}
|
||||
mustRegisterQuantiles := false
|
||||
metricsMapLock.Lock()
|
||||
nm = metricsMap[name]
|
||||
if nm == nil {
|
||||
nm = nmNew
|
||||
metricsMap[name] = nm
|
||||
metricsList = append(metricsList, nm)
|
||||
registerSummary(s)
|
||||
mustRegisterQuantiles = true
|
||||
}
|
||||
metricsMapLock.Unlock()
|
||||
if mustRegisterQuantiles {
|
||||
registerSummaryQuantiles(name, s)
|
||||
}
|
||||
}
|
||||
s, ok := nm.metric.(*Summary)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("BUG: metric %q isn't a Summary. It is %T", name, nm.metric))
|
||||
}
|
||||
if s.window != window {
|
||||
panic(fmt.Errorf("BUG: invalid window requested for the summary %q; requested %s; need %s", name, window, s.window))
|
||||
}
|
||||
if !isEqualQuantiles(s.quantiles, quantiles) {
|
||||
panic(fmt.Errorf("BUG: invalid quantiles requested from the summary %q; requested %v; need %v", name, quantiles, s.quantiles))
|
||||
}
|
||||
return s
|
||||
return defaultSet.GetOrCreateSummaryExt(name, window, quantiles)
|
||||
}
|
||||
|
||||
func isEqualQuantiles(a, b []float64) bool {
|
||||
@@ -207,14 +154,14 @@ func isEqualQuantiles(a, b []float64) bool {
|
||||
}
|
||||
|
||||
type quantileValue struct {
|
||||
s *Summary
|
||||
sm *Summary
|
||||
idx int
|
||||
}
|
||||
|
||||
func (qv *quantileValue) marshalTo(prefix string, w io.Writer) {
|
||||
qv.s.mu.Lock()
|
||||
v := qv.s.quantileValues[qv.idx]
|
||||
qv.s.mu.Unlock()
|
||||
qv.sm.mu.Lock()
|
||||
v := qv.sm.quantileValues[qv.idx]
|
||||
qv.sm.mu.Unlock()
|
||||
if !math.IsNaN(v) {
|
||||
fmt.Fprintf(w, "%s %g\n", prefix, v)
|
||||
}
|
||||
@@ -227,10 +174,10 @@ func addTag(name, tag string) string {
|
||||
return fmt.Sprintf("%s,%s}", name[:len(name)-1], tag)
|
||||
}
|
||||
|
||||
func registerSummary(s *Summary) {
|
||||
window := s.window
|
||||
func registerSummary(sm *Summary) {
|
||||
window := sm.window
|
||||
summariesLock.Lock()
|
||||
summaries[window] = append(summaries[window], s)
|
||||
summaries[window] = append(summaries[window], sm)
|
||||
if len(summaries[window]) == 1 {
|
||||
go summariesSwapCron(window)
|
||||
}
|
||||
@@ -241,13 +188,13 @@ func summariesSwapCron(window time.Duration) {
|
||||
for {
|
||||
time.Sleep(window / 2)
|
||||
summariesLock.Lock()
|
||||
for _, s := range summaries[window] {
|
||||
s.mu.Lock()
|
||||
tmp := s.curr
|
||||
s.curr = s.next
|
||||
s.next = tmp
|
||||
s.next.Reset()
|
||||
s.mu.Unlock()
|
||||
for _, sm := range summaries[window] {
|
||||
sm.mu.Lock()
|
||||
tmp := sm.curr
|
||||
sm.curr = sm.next
|
||||
sm.next = tmp
|
||||
sm.next.Reset()
|
||||
sm.mu.Unlock()
|
||||
}
|
||||
summariesLock.Unlock()
|
||||
}
|
||||
|
||||
10
vendor/github.com/valyala/gozstd/README.md
generated
vendored
10
vendor/github.com/valyala/gozstd/README.md
generated
vendored
@@ -56,14 +56,18 @@ There is also [StreamDecompress](https://godoc.org/github.com/valyala/gozstd#Str
|
||||
and [Reader](https://godoc.org/github.com/valyala/gozstd#Reader) for stream decompression.
|
||||
|
||||
|
||||
### Who uses gozstd?
|
||||
|
||||
* [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics)
|
||||
|
||||
|
||||
## FAQ
|
||||
|
||||
* Q: _Which go version is supported?_
|
||||
A: `go1.10` and newer. Pull requests for older go versions support
|
||||
are accepted.
|
||||
A: `go1.10` and newer. Pull requests for older go versions are accepted.
|
||||
|
||||
* Q: _Which platforms/architectures are supported?_
|
||||
A: `linux/amd64`, `linux/arm`, `linux/arm64`, `darwin/amd64`, `windows/amd64`. Pull requests for other platforms/architectures support
|
||||
A: `linux/amd64`, `linux/arm`, `linux/arm64`, `darwin/amd64`, `windows/amd64`. Pull requests for other platforms/architectures
|
||||
are accepted.
|
||||
|
||||
* Q: _I don't trust `libzstd*.a` binary files from the repo or these files dont't work on my OS/ARCH. How to rebuild them?_
|
||||
|
||||
4
vendor/github.com/valyala/gozstd/doc.go
generated
vendored
Normal file
4
vendor/github.com/valyala/gozstd/doc.go
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
// Package gozstd is Go wrapper for zstd.
|
||||
//
|
||||
// Gozstd is used in https://github.com/VictoriaMetrics/VictoriaMetrics .
|
||||
package gozstd
|
||||
BIN
vendor/github.com/valyala/gozstd/libzstd_windows_amd64.a
generated
vendored
BIN
vendor/github.com/valyala/gozstd/libzstd_windows_amd64.a
generated
vendored
Binary file not shown.
54
vendor/golang.org/x/sys/unix/asm_linux_riscv64.s
generated
vendored
Normal file
54
vendor/golang.org/x/sys/unix/asm_linux_riscv64.s
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build riscv64,!gccgo
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
//
|
||||
// System calls for linux/riscv64.
|
||||
//
|
||||
// Where available, just jump to package syscall's implementation of
|
||||
// these functions.
|
||||
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-56
|
||||
JMP syscall·Syscall(SB)
|
||||
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-80
|
||||
JMP syscall·Syscall6(SB)
|
||||
|
||||
TEXT ·SyscallNoError(SB),NOSPLIT,$0-48
|
||||
CALL runtime·entersyscall(SB)
|
||||
MOV a1+8(FP), A0
|
||||
MOV a2+16(FP), A1
|
||||
MOV a3+24(FP), A2
|
||||
MOV $0, A3
|
||||
MOV $0, A4
|
||||
MOV $0, A5
|
||||
MOV $0, A6
|
||||
MOV trap+0(FP), A7 // syscall entry
|
||||
ECALL
|
||||
MOV A0, r1+32(FP) // r1
|
||||
MOV A1, r2+40(FP) // r2
|
||||
CALL runtime·exitsyscall(SB)
|
||||
RET
|
||||
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-56
|
||||
JMP syscall·RawSyscall(SB)
|
||||
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80
|
||||
JMP syscall·RawSyscall6(SB)
|
||||
|
||||
TEXT ·RawSyscallNoError(SB),NOSPLIT,$0-48
|
||||
MOV a1+8(FP), A0
|
||||
MOV a2+16(FP), A1
|
||||
MOV a3+24(FP), A2
|
||||
MOV ZERO, A3
|
||||
MOV ZERO, A4
|
||||
MOV ZERO, A5
|
||||
MOV trap+0(FP), A7 // syscall entry
|
||||
ECALL
|
||||
MOV A0, r1+32(FP)
|
||||
MOV A1, r2+40(FP)
|
||||
RET
|
||||
8
vendor/golang.org/x/sys/unix/mkall.sh
generated
vendored
8
vendor/golang.org/x/sys/unix/mkall.sh
generated
vendored
@@ -105,25 +105,25 @@ dragonfly_amd64)
|
||||
freebsd_386)
|
||||
mkerrors="$mkerrors -m32"
|
||||
mksyscall="go run mksyscall.go -l32"
|
||||
mksysnum="go run mksysnum.go 'https://svn.freebsd.org/base/stable/10/sys/kern/syscalls.master'"
|
||||
mksysnum="go run mksysnum.go 'https://svn.freebsd.org/base/stable/11/sys/kern/syscalls.master'"
|
||||
mktypes="GOARCH=$GOARCH go tool cgo -godefs"
|
||||
;;
|
||||
freebsd_amd64)
|
||||
mkerrors="$mkerrors -m64"
|
||||
mksysnum="go run mksysnum.go 'https://svn.freebsd.org/base/stable/10/sys/kern/syscalls.master'"
|
||||
mksysnum="go run mksysnum.go 'https://svn.freebsd.org/base/stable/11/sys/kern/syscalls.master'"
|
||||
mktypes="GOARCH=$GOARCH go tool cgo -godefs"
|
||||
;;
|
||||
freebsd_arm)
|
||||
mkerrors="$mkerrors"
|
||||
mksyscall="go run mksyscall.go -l32 -arm"
|
||||
mksysnum="go run mksysnum.go 'https://svn.freebsd.org/base/stable/10/sys/kern/syscalls.master'"
|
||||
mksysnum="go run mksysnum.go 'https://svn.freebsd.org/base/stable/11/sys/kern/syscalls.master'"
|
||||
# Let the type of C char be signed for making the bare syscall
|
||||
# API consistent across platforms.
|
||||
mktypes="GOARCH=$GOARCH go tool cgo -godefs -- -fsigned-char"
|
||||
;;
|
||||
freebsd_arm64)
|
||||
mkerrors="$mkerrors -m64"
|
||||
mksysnum="go run mksysnum.go 'https://svn.freebsd.org/base/stable/10/sys/kern/syscalls.master'"
|
||||
mksysnum="go run mksysnum.go 'https://svn.freebsd.org/base/stable/11/sys/kern/syscalls.master'"
|
||||
mktypes="GOARCH=$GOARCH go tool cgo -godefs"
|
||||
;;
|
||||
netbsd_386)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user