mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-06-07 19:06:17 +03:00
Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3c462f08a | ||
|
|
bea5a8700a | ||
|
|
1825893eef | ||
|
|
97f70ccda7 | ||
|
|
2fba7b6f35 | ||
|
|
d03827c57d | ||
|
|
bb530a0591 | ||
|
|
aea4c80dd7 | ||
|
|
5e8e0fbc80 | ||
|
|
1e8aa89a3b | ||
|
|
56595ae12a | ||
|
|
96ff8d9adb | ||
|
|
02f6566ce1 | ||
|
|
7535f20c98 | ||
|
|
bc645152cb | ||
|
|
f5ac9b0721 | ||
|
|
d95a43f392 | ||
|
|
87a8348062 | ||
|
|
cea5a14853 | ||
|
|
9787c228a4 | ||
|
|
c121608205 | ||
|
|
492f032b38 | ||
|
|
4624c060ac | ||
|
|
8454679d9f | ||
|
|
440a15111e | ||
|
|
6ddcd162ed | ||
|
|
6504f78ce4 | ||
|
|
73b2a3d4b7 | ||
|
|
07d5bc986b | ||
|
|
caa4eb72d9 | ||
|
|
3c076544bf | ||
|
|
35f5ca1def | ||
|
|
a7d80f62be | ||
|
|
40540397c3 | ||
|
|
c107f46b0e | ||
|
|
8cce513a15 | ||
|
|
b01ddfdd76 | ||
|
|
68e1cf8942 | ||
|
|
8501b4a48d | ||
|
|
0ed9258545 | ||
|
|
b0d88460de | ||
|
|
8db7660afe | ||
|
|
18369bca42 | ||
|
|
95328782c3 | ||
|
|
981cb66a95 | ||
|
|
f15d89bfe0 | ||
|
|
36feb7d3e4 | ||
|
|
d900184d8d | ||
|
|
293b541784 | ||
|
|
84b57e8974 | ||
|
|
b458e5a213 |
86
README.md
86
README.md
@@ -1,26 +1,34 @@
|
||||
[](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest)
|
||||
[](https://hub.docker.com/r/victoriametrics/victoria-metrics)
|
||||
[](http://slack.victoriametrics.com/)
|
||||
[](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/LICENSE)
|
||||
[](https://goreportcard.com/report/github.com/VictoriaMetrics/VictoriaMetrics)
|
||||
[](https://github.com/VictoriaMetrics/VictoriaMetrics/actions)
|
||||
[](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics)
|
||||
|
||||
<img alt="Victoria Metrics" src="logo.png">
|
||||
<img alt="Victoria Metrics" src="logo.png" height="200px">
|
||||
|
||||
## Single-node VictoriaMetrics
|
||||
## VictoriaMetrics
|
||||
|
||||
VictoriaMetrics is fast, cost-effective and scalable time-series database. It can be used as 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/victoriametrics/victoria-metrics/) and
|
||||
in [source code](https://github.com/VictoriaMetrics/VictoriaMetrics).
|
||||
in [source code](https://github.com/VictoriaMetrics/VictoriaMetrics). Just download VictoriaMetrics and see [how to start it](#how-to-start-victoriametrics).
|
||||
|
||||
Cluster version is available [here](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/cluster).
|
||||
|
||||
|
||||
## Case studies
|
||||
|
||||
* [Wix.com](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/CaseStudies#wixcom)
|
||||
* [Wedos.com](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/CaseStudies#wedoscom)
|
||||
* [Dreamteam](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/CaseStudies#dreamteam)
|
||||
|
||||
|
||||
## Prominent features
|
||||
|
||||
* Supports [Prometheus querying API](https://prometheus.io/docs/prometheus/latest/querying/api/), so it can be used as Prometheus drop-in replacement in Grafana.
|
||||
Additionally, VictoriaMetrics extends PromQL with opt-in [useful features](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/ExtendedPromQL).
|
||||
VictoriaMetrics implements [MetricsQL](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/ExtendedPromQL) query language, which is inspired by PromQL.
|
||||
* Supports global query view. Multiple Prometheus instances may write data into VictoriaMetrics. Later this data may be used in a single query.
|
||||
* High performance and good scalability for both [inserts](https://medium.com/@valyala/high-cardinality-tsdb-benchmarks-victoriametrics-vs-timescaledb-vs-influxdb-13e6ee64dd6b)
|
||||
and [selects](https://medium.com/@valyala/when-size-matters-benchmarking-victoriametrics-vs-timescale-and-influxdb-6035811952d4).
|
||||
@@ -43,11 +51,12 @@ Cluster version is available [here](https://github.com/VictoriaMetrics/VictoriaM
|
||||
* Storage is protected from corruption on unclean shutdown (i.e. OOM, hardware reset or `kill -9`) thanks to [the storage architecture](https://medium.com/@valyala/how-victoriametrics-makes-instant-snapshots-for-multi-terabyte-time-series-data-e1f3fb0e0282).
|
||||
* Supports metrics' ingestion and [backfilling](#backfilling) via the following protocols:
|
||||
* [Prometheus remote write API](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_write)
|
||||
* [InfluxDB line protocol](https://docs.influxdata.com/influxdb/v1.7/write_protocols/line_protocol_tutorial/)
|
||||
* [Graphite plaintext protocol](https://graphite.readthedocs.io/en/latest/feeding-carbon.html) with [tags](https://graphite.readthedocs.io/en/latest/tags.html#carbon)
|
||||
* [InfluxDB line protocol](#how-to-send-data-from-influxdb-compatible-agents-such-as-telegraf)
|
||||
* [Graphite plaintext protocol](#how-to-send-data-from-graphite-compatible-agents-such-as-statsd) 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.
|
||||
* [HTTP OpenTSDB /api/put requests](http://opentsdb.net/docs/build/html/api_http/put.html) if `-opentsdbHTTPListenAddr` is set.
|
||||
* [OpenTSDB put message](#sending-data-via-telnet-put-protocol) if `-opentsdbListenAddr` is set.
|
||||
* [HTTP OpenTSDB /api/put requests](#sending-opentsdb-data-via-http-apiput-requests) if `-opentsdbHTTPListenAddr` is set.
|
||||
* [/api/v1/import](#how-to-import-time-series-data)
|
||||
* Ideally works with big amounts of time series data from Kubernetes, IoT sensors, connected cars, industrial telemetry, financial data and various Enterprise workloads.
|
||||
* Has open source [cluster version](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/cluster).
|
||||
|
||||
@@ -66,6 +75,7 @@ Cluster version is available [here](https://github.com/VictoriaMetrics/VictoriaM
|
||||
- [How to send data from Graphite-compatible agents such as StatsD?](#how-to-send-data-from-graphite-compatible-agents-such-as-statsd)
|
||||
- [Querying Graphite data](#querying-graphite-data)
|
||||
- [How to send data from OpenTSDB-compatible agents?](#how-to-send-data-from-opentsdb-compatible-agents)
|
||||
- [Prometheus querying API usage](#prometheus-querying-api-usage)
|
||||
- [How to build from sources](#how-to-build-from-sources)
|
||||
- [Development build](#development-build)
|
||||
- [Production build](#production-build)
|
||||
@@ -78,6 +88,7 @@ Cluster version is available [here](https://github.com/VictoriaMetrics/VictoriaM
|
||||
- [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)
|
||||
- [How to import time series data?](#how-to-import-time-series-data)
|
||||
- [Federation](#federation)
|
||||
- [Capacity planning](#capacity-planning)
|
||||
- [High availability](#high-availability)
|
||||
@@ -302,7 +313,7 @@ The `/api/v1/export` endpoint should return the following response:
|
||||
### Querying Graphite data
|
||||
|
||||
Data sent to VictoriaMetrics via `Graphite plaintext protocol` may be read either via
|
||||
[Prometheus querying API](https://prometheus.io/docs/prometheus/latest/querying/api/)
|
||||
[Prometheus querying API](#prometheus-querying-api-usage)
|
||||
or via [go-graphite/carbonapi](https://github.com/go-graphite/carbonapi/blob/master/cmd/carbonapi/carbonapi.example.prometheus.yaml).
|
||||
|
||||
|
||||
@@ -382,6 +393,31 @@ The `/api/v1/export` endpoint should return the following response:
|
||||
```
|
||||
|
||||
|
||||
### Prometheus querying API usage
|
||||
|
||||
VictoriaMetrics supports the following handlers from [Prometheus querying API](https://prometheus.io/docs/prometheus/latest/querying/api/):
|
||||
|
||||
* [/api/v1/query](https://prometheus.io/docs/prometheus/latest/querying/api/#instant-queries)
|
||||
* [/api/v1/query_range](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries)
|
||||
* [/api/v1/series](https://prometheus.io/docs/prometheus/latest/querying/api/#finding-series-by-label-matchers)
|
||||
* [/api/v1/labels](https://prometheus.io/docs/prometheus/latest/querying/api/#getting-label-names)
|
||||
* [/api/v1/label/.../values](https://prometheus.io/docs/prometheus/latest/querying/api/#querying-label-values)
|
||||
|
||||
These handlers can be queried from Prometheus-compatible clients such as Grafana or curl.
|
||||
|
||||
VictoriaMetrics accepts additional args for `/api/v1/labels` and `/api/v1/label/.../values` handlers.
|
||||
See [this feature request](https://github.com/prometheus/prometheus/issues/6178) for details:
|
||||
|
||||
* Any number [time series selectors](https://prometheus.io/docs/prometheus/latest/querying/basics/#time-series-selectors) via `match[]` query arg.
|
||||
* Optional `start` and `end` query args for limiting the time range for the selected labels or label values.
|
||||
|
||||
Additionally VictoriaMetrics provides the following handlers:
|
||||
|
||||
* `/api/v1/series/count` - it returns the total number of time series in the database. Note that this handler scans all the inverted index,
|
||||
so it can be slow if the database contains tens of millions of time series.
|
||||
* `/api/v1/labels/count` - it returns a list of `label: values_count` entries. It can be used for determining labels with the maximum number of values.
|
||||
|
||||
|
||||
### How to build from sources
|
||||
|
||||
We recommend using either [binary releases](https://github.com/VictoriaMetrics/VictoriaMetrics/releases) or
|
||||
@@ -496,7 +532,8 @@ before actually deleting the metrics.
|
||||
|
||||
Send a request to `http://<victoriametrics-addr>:8428/api/v1/export?match[]=<timeseries_selector_for_export>`,
|
||||
where `<timeseries_selector_for_export>` may contain any [time series selector](https://prometheus.io/docs/prometheus/latest/querying/basics/#time-series-selectors)
|
||||
for metrics to export. The response would contain all the data for the selected time series in [JSON streaming format](https://en.wikipedia.org/wiki/JSON_streaming#Line-delimited_JSON).
|
||||
for metrics to export. Use `{__name__!=""}` selector for fetching all the time series.
|
||||
The response would contain all the data for the selected time series in [JSON streaming format](https://en.wikipedia.org/wiki/JSON_streaming#Line-delimited_JSON).
|
||||
Each JSON line would contain data for a single time series. An example output:
|
||||
|
||||
```
|
||||
@@ -507,6 +544,33 @@ Each JSON line would contain data for a single time series. An example output:
|
||||
Optional `start` and `end` args may be added to the request in order to limit the time frame for the exported data. These args may contain either
|
||||
unix timestamp in seconds or [RFC3339](https://www.ietf.org/rfc/rfc3339.txt) values.
|
||||
|
||||
Exported data can be imported via POST'ing it to [/api/v1/import](#how-to-import-time-series-data).
|
||||
|
||||
|
||||
### How to import time series data?
|
||||
|
||||
Time series data can be imported via any supported ingestion protocol:
|
||||
|
||||
* [Prometheus remote_write API](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_write)
|
||||
* [Influx line protocol](#how-to-send-data-from-influxdb-compatible-agents-such-as-telegraf)
|
||||
* [Graphite plaintext protocol](#how-to-send-data-from-graphite-compatible-agents-such-as-statsd)
|
||||
* [OpenTSDB telnet put protocol](#sending-data-via-telnet-put-protocol)
|
||||
* [OpenTSDB http /api/put](#sending-opentsdb-data-via-http-apiput-requests)
|
||||
* `/api/v1/import` http POST handler, which accepts data from [/api/v1/export](#how-to-export-time-series).
|
||||
|
||||
The most efficient protocol for importing data into VictoriaMetrics is `/api/v1/import`. Example for importing data obtained via `/api/v1/export`:
|
||||
|
||||
```
|
||||
# Export the data from <source-victoriametrics>:
|
||||
curl -s 'http://source-victoriametrics:8428/api/v1/export' -d 'match={__name__!=""}' > exported_data.jsonl
|
||||
|
||||
# Import the data to <destination-victoriametrics>:
|
||||
curl -X POST 'http://destination-victoriametrics:8428/api/v1/import' -T exported_data.jsonl
|
||||
```
|
||||
|
||||
Each request to `/api/v1/import` can load up to a single vCPU core on VictoriaMetrics. Import speed can be improved by splitting the original file into smaller parts
|
||||
and importing them concurrently. Note that the original file must be split on newlines.
|
||||
|
||||
|
||||
### Federation
|
||||
|
||||
@@ -670,7 +734,7 @@ mkfs.ext4 ... -O 64bit,huge_file,extent -T huge
|
||||
|
||||
VictoriaMetrics exports internal metrics in Prometheus format on the `/metrics` page.
|
||||
Add this page to Prometheus' scrape config in order to collect VictoriaMetrics metrics.
|
||||
There is [an official Grafana dashboard for single-node VictoriaMetrics](https://grafana.com/dashboards/10229).
|
||||
There are officials Grafana dashboards for [single-node VictoriaMetrics](https://grafana.com/dashboards/10229) and [clustered VictoriaMetrics](https://grafana.com/grafana/dashboards/11176).
|
||||
|
||||
The most interesting metrics are:
|
||||
|
||||
|
||||
@@ -6,9 +6,38 @@ victoria-metrics:
|
||||
victoria-metrics-prod:
|
||||
APP_NAME=victoria-metrics $(MAKE) app-via-docker
|
||||
|
||||
victoria-metrics-pure-prod:
|
||||
APP_NAME=victoria-metrics $(MAKE) app-via-docker-pure
|
||||
|
||||
victoria-metrics-arm-prod:
|
||||
APP_NAME=victoria-metrics $(MAKE) app-via-docker-arm
|
||||
|
||||
victoria-metrics-arm64-prod:
|
||||
APP_NAME=victoria-metrics $(MAKE) app-via-docker-arm64
|
||||
|
||||
victoria-metrics-ppc64le-prod:
|
||||
APP_NAME=victoria-metrics $(MAKE) app-via-docker-ppc64le
|
||||
|
||||
victoria-metrics-386-prod:
|
||||
APP_NAME=victoria-metrics $(MAKE) app-via-docker-386
|
||||
|
||||
package-victoria-metrics:
|
||||
APP_NAME=victoria-metrics \
|
||||
$(MAKE) package-via-docker
|
||||
APP_NAME=victoria-metrics $(MAKE) package-via-docker
|
||||
|
||||
package-victoria-metrics-pure:
|
||||
APP_NAME=victoria-metrics $(MAKE) package-via-docker-pure
|
||||
|
||||
package-victoria-metrics-arm:
|
||||
APP_NAME=victoria-metrics $(MAKE) package-via-docker-arm
|
||||
|
||||
package-victoria-metrics-arm64:
|
||||
APP_NAME=victoria-metrics $(MAKE) package-via-docker-arm64
|
||||
|
||||
package-victoria-metrics-ppc64le:
|
||||
APP_NAME=victoria-metrics $(MAKE) package-via-docker-ppc64le
|
||||
|
||||
package-victoria-metrics-386:
|
||||
APP_NAME=victoria-metrics $(MAKE) package-via-docker-386
|
||||
|
||||
publish-victoria-metrics:
|
||||
APP_NAME=victoria-metrics $(MAKE) publish-via-docker
|
||||
@@ -23,33 +52,18 @@ run-victoria-metrics:
|
||||
victoria-metrics-arm:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm GO111MODULE=on go build -mod=vendor -ldflags "$(GO_BUILDINFO)" -o bin/victoria-metrics-arm ./app/victoria-metrics
|
||||
|
||||
victoria-metrics-arm-prod:
|
||||
APP_NAME=victoria-metrics APP_SUFFIX='-arm' DOCKER_OPTS='--env CGO_ENABLED=0 --env GOARCH=arm' $(MAKE) app-via-docker
|
||||
|
||||
victoria-metrics-arm64:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 GO111MODULE=on go build -mod=vendor -ldflags "$(GO_BUILDINFO)" -o bin/victoria-metrics-arm64 ./app/victoria-metrics
|
||||
|
||||
victoria-metrics-arm64-prod:
|
||||
APP_NAME=victoria-metrics APP_SUFFIX='-arm64' DOCKER_OPTS='--env CGO_ENABLED=0 --env GOARCH=arm64' $(MAKE) app-via-docker
|
||||
|
||||
victoria-metrics-ppc64le:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=ppc64le GO111MODULE=on go build -mod=vendor -ldflags "$(GO_BUILDINFO)" -o bin/victoria-metrics-ppc64le ./app/victoria-metrics
|
||||
|
||||
victoria-metrics-ppc64le-prod:
|
||||
APP_NAME=victoria-metrics APP_SUFFIX='-ppc64le' DOCKER_OPTS='--env CGO_ENABLED=0 --env GOARCH=ppc64le' $(MAKE) app-via-docker
|
||||
|
||||
victoria-metrics-386:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=386 GO111MODULE=on go build -mod=vendor -ldflags "$(GO_BUILDINFO)" -o bin/victoria-metrics-386 ./app/victoria-metrics
|
||||
|
||||
victoria-metrics-386-prod:
|
||||
APP_NAME=victoria-metrics APP_SUFFIX='-386' DOCKER_OPTS='--env CGO_ENABLED=0 --env GOARCH=386' $(MAKE) app-via-docker
|
||||
|
||||
victoria-metrics-pure:
|
||||
APP_NAME=victoria-metrics $(MAKE) app-local-pure
|
||||
|
||||
victoria-metrics-pure-prod:
|
||||
APP_NAME=victoria-metrics APP_SUFFIX='-pure' DOCKER_OPTS='--env CGO_ENABLED=0' $(MAKE) app-via-docker
|
||||
|
||||
### Packaging as DEB - amd64
|
||||
victoria-metrics-package-deb: victoria-metrics-prod
|
||||
./package/package_deb.sh amd64
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
ARG certs_image
|
||||
FROM $certs_image AS certs
|
||||
FROM scratch
|
||||
COPY --from=local/certs:1.0.3 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
COPY bin/victoria-metrics-prod .
|
||||
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
ARG src_binary
|
||||
COPY $src_binary ./victoria-metrics-prod
|
||||
EXPOSE 8428
|
||||
ENTRYPOINT ["/victoria-metrics-prod"]
|
||||
|
||||
@@ -21,7 +21,7 @@ func main() {
|
||||
flag.Parse()
|
||||
buildinfo.Init()
|
||||
logger.Init()
|
||||
logger.Infof("starting VictoraMetrics at %q...", *httpListenAddr)
|
||||
logger.Infof("starting VictoriaMetrics at %q...", *httpListenAddr)
|
||||
startTime := time.Now()
|
||||
vmstorage.Init()
|
||||
vmselect.Init()
|
||||
|
||||
@@ -6,32 +6,53 @@ vmbackup:
|
||||
vmbackup-prod:
|
||||
APP_NAME=vmbackup $(MAKE) app-via-docker
|
||||
|
||||
vmbackup-pure-prod:
|
||||
APP_NAME=vmbackup $(MAKE) app-via-docker-pure
|
||||
|
||||
vmbackup--arm-prod:
|
||||
APP_NAME=vmbackup $(MAKE) app-via-docker-arm
|
||||
|
||||
vmbackup-arm64-prod:
|
||||
APP_NAME=vmbackup $(MAKE) app-via-docker-arm64
|
||||
|
||||
vmbackup-ppc64le-prod:
|
||||
APP_NAME=vmbackup $(MAKE) app-via-docker-ppc64le
|
||||
|
||||
vmbackup-386-prod:
|
||||
APP_NAME=vmbackup $(MAKE) app-via-docker-386
|
||||
|
||||
package-vmbackup:
|
||||
APP_NAME=vmbackup $(MAKE) package-via-docker
|
||||
|
||||
package-vmbackup-pure:
|
||||
APP_NAME=vmbackup $(MAKE) package-via-docker-pure
|
||||
|
||||
package-vmbackup-arm:
|
||||
APP_NAME=vmbackup $(MAKE) package-via-docker-arm
|
||||
|
||||
package-vmbackup-arm64:
|
||||
APP_NAME=vmbackup $(MAKE) package-via-docker-arm64
|
||||
|
||||
package-vmbackup-ppc64le:
|
||||
APP_NAME=vmbackup $(MAKE) package-via-docker-ppc64le
|
||||
|
||||
package-vmbackup-386:
|
||||
APP_NAME=vmbackup $(MAKE) package-via-docker-386
|
||||
|
||||
publish-vmbackup:
|
||||
APP_NAME=vmbackup $(MAKE) publish-via-docker
|
||||
|
||||
vmbackup-pure:
|
||||
APP_NAME=vmbackup $(MAKE) app-local-pure
|
||||
|
||||
vmbackup-arm:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm GO111MODULE=on go build -mod=vendor -ldflags "$(GO_BUILDINFO)" -o bin/vmbackup-arm ./app/vmbackup
|
||||
|
||||
vmbackup-arm-prod:
|
||||
APP_NAME=vmbackup APP_SUFFIX='-arm' DOCKER_OPTS='--env CGO_ENABLED=0 --env GOARCH=arm' $(MAKE) app-via-docker
|
||||
|
||||
vmbackup-arm64:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 GO111MODULE=on go build -mod=vendor -ldflags "$(GO_BUILDINFO)" -o bin/vmbackup-arm64 ./app/vmbackup
|
||||
|
||||
vmbackup-arm64-prod:
|
||||
APP_NAME=vmbackup APP_SUFFIX='-arm64' DOCKER_OPTS='--env CGO_ENABLED=0 --env GOARCH=arm64' $(MAKE) app-via-docker
|
||||
vmbackup-ppc64le:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=ppc64le GO111MODULE=on go build -mod=vendor -ldflags "$(GO_BUILDINFO)" -o bin/vmbackup-ppc64le ./app/vmbackup
|
||||
|
||||
vmbackup-386:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=386 GO111MODULE=on go build -mod=vendor -ldflags "$(GO_BUILDINFO)" -o bin/vmbackup-386 ./app/vmbackup
|
||||
|
||||
vmbackup-386-prod:
|
||||
APP_NAME=vmbackup APP_SUFFIX='-386' DOCKER_OPTS='--env CGO_ENABLED=0 --env GOARCH=386' $(MAKE) app-via-docker
|
||||
|
||||
vmbackup-pure:
|
||||
APP_NAME=vmbackup $(MAKE) app-local-pure
|
||||
|
||||
vmbackup-pure-prod:
|
||||
APP_NAME=vmbackup APP_SUFFIX='-pure' DOCKER_OPTS='--env CGO_ENABLED=0' $(MAKE) app-via-docker
|
||||
|
||||
@@ -6,7 +6,7 @@ Supported storage systems for backups:
|
||||
|
||||
* [GCS](https://cloud.google.com/storage/). Example: `gcs://<bucket>/<path/to/backup>`
|
||||
* [S3](https://aws.amazon.com/s3/). Example: `s3://<bucket>/<path/to/backup>`
|
||||
* Any S3-compatible storage such as [MinIO](https://github.com/minio/minio). See `-customS3Endpoint` command-line flag.
|
||||
* Any S3-compatible storage such as [MinIO](https://github.com/minio/minio), [Ceph](https://docs.ceph.com/docs/mimic/radosgw/s3/) or [Swift](https://www.swiftstack.com/docs/admin/middleware/s3_middleware.html). See `-customS3Endpoint` command-line flag.
|
||||
* Local filesystem. Example: `fs://</absolute/path/to/backup>`
|
||||
|
||||
Incremental backups and full backups are supported. Incremental backups are created automatically if the destination path already contains data from the previous backup.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
ARG certs_image
|
||||
FROM $certs_image AS certs
|
||||
FROM scratch
|
||||
COPY --from=local/certs:1.0.3 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
COPY bin/vmbackup-prod .
|
||||
EXPOSE 8428
|
||||
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
ARG src_binary
|
||||
COPY $src_binary ./vmbackup-prod
|
||||
ENTRYPOINT ["/vmbackup-prod"]
|
||||
|
||||
@@ -47,7 +47,7 @@ func (ctx *InsertCtx) marshalMetricNameRaw(prefix []byte, labels []prompb.Label)
|
||||
return metricNameRaw[:len(metricNameRaw):len(metricNameRaw)]
|
||||
}
|
||||
|
||||
// WriteDataPoint writes (timestamp, value) with the given prefix and lables into ctx buffer.
|
||||
// WriteDataPoint writes (timestamp, value) with the given prefix and labels into ctx buffer.
|
||||
func (ctx *InsertCtx) WriteDataPoint(prefix []byte, labels []prompb.Label, timestamp int64, value float64) {
|
||||
metricNameRaw := ctx.marshalMetricNameRaw(prefix, labels)
|
||||
ctx.addRow(metricNameRaw, timestamp, value)
|
||||
@@ -78,6 +78,26 @@ func (ctx *InsertCtx) addRow(metricNameRaw []byte, timestamp int64, value float6
|
||||
mr.Value = value
|
||||
}
|
||||
|
||||
// AddLabelBytes adds (name, value) label to ctx.Labels.
|
||||
//
|
||||
// name and value must exist until ctx.Labels is used.
|
||||
func (ctx *InsertCtx) AddLabelBytes(name, value []byte) {
|
||||
labels := ctx.Labels
|
||||
if cap(labels) > len(labels) {
|
||||
labels = labels[:len(labels)+1]
|
||||
} else {
|
||||
labels = append(labels, prompb.Label{})
|
||||
}
|
||||
label := &labels[len(labels)-1]
|
||||
|
||||
// Do not copy name and value contents for performance reasons.
|
||||
// This reduces GC overhead on the number of objects and allocations.
|
||||
label.Name = name
|
||||
label.Value = value
|
||||
|
||||
ctx.Labels = labels
|
||||
}
|
||||
|
||||
// AddLabel adds (name, value) label to ctx.Labels.
|
||||
//
|
||||
// name and value must exist until ctx.Labels is used.
|
||||
|
||||
@@ -20,6 +20,17 @@ const defaultBlockSize = 64 * 1024
|
||||
//
|
||||
// Returns (dstBuf, tailBuf).
|
||||
func ReadLinesBlock(r io.Reader, dstBuf, tailBuf []byte) ([]byte, []byte, error) {
|
||||
return ReadLinesBlockExt(r, dstBuf, tailBuf, maxLineSize)
|
||||
}
|
||||
|
||||
// ReadLinesBlockExt 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).
|
||||
//
|
||||
// maxLineLen limits the maximum length of a single line.
|
||||
func ReadLinesBlockExt(r io.Reader, dstBuf, tailBuf []byte, maxLineLen int) ([]byte, []byte, error) {
|
||||
if cap(dstBuf) < defaultBlockSize {
|
||||
dstBuf = bytesutil.Resize(dstBuf, defaultBlockSize)
|
||||
}
|
||||
@@ -48,8 +59,8 @@ again:
|
||||
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 len(dstBuf) > maxLineLen {
|
||||
return dstBuf, tailBuf, fmt.Errorf("too long line: more than %d bytes", maxLineLen)
|
||||
}
|
||||
if cap(dstBuf) < 2*len(dstBuf) {
|
||||
// Increase dsbBuf capacity, so more data could be read into it.
|
||||
|
||||
@@ -61,13 +61,13 @@ func (ctx *pushCtx) InsertRows() error {
|
||||
const flushTimeout = 3 * time.Second
|
||||
|
||||
func (ctx *pushCtx) Read(r io.Reader) bool {
|
||||
graphiteReadCalls.Inc()
|
||||
readCalls.Inc()
|
||||
if ctx.err != nil {
|
||||
return false
|
||||
}
|
||||
if c, ok := r.(net.Conn); ok {
|
||||
if err := c.SetReadDeadline(time.Now().Add(flushTimeout)); err != nil {
|
||||
graphiteReadErrors.Inc()
|
||||
readErrors.Inc()
|
||||
ctx.err = fmt.Errorf("cannot set read deadline: %s", err)
|
||||
return false
|
||||
}
|
||||
@@ -79,7 +79,7 @@ func (ctx *pushCtx) Read(r io.Reader) bool {
|
||||
ctx.err = nil
|
||||
} else {
|
||||
if ctx.err != io.EOF {
|
||||
graphiteReadErrors.Inc()
|
||||
readErrors.Inc()
|
||||
ctx.err = fmt.Errorf("cannot read graphite plaintext protocol data: %s", ctx.err)
|
||||
}
|
||||
return false
|
||||
@@ -132,8 +132,8 @@ func (ctx *pushCtx) reset() {
|
||||
}
|
||||
|
||||
var (
|
||||
graphiteReadCalls = metrics.NewCounter(`vm_read_calls_total{name="graphite"}`)
|
||||
graphiteReadErrors = metrics.NewCounter(`vm_read_errors_total{name="graphite"}`)
|
||||
readCalls = metrics.NewCounter(`vm_read_calls_total{name="graphite"}`)
|
||||
readErrors = metrics.NewCounter(`vm_read_errors_total{name="graphite"}`)
|
||||
)
|
||||
|
||||
func getPushCtx() *pushCtx {
|
||||
|
||||
@@ -21,36 +21,62 @@ var (
|
||||
writeErrorsUDP = metrics.NewCounter(`vm_graphite_request_errors_total{name="write", net="udp"}`)
|
||||
)
|
||||
|
||||
// Serve starts graphite server on the given addr.
|
||||
func Serve(addr string) {
|
||||
// Server accepts Graphite plaintext lines over TCP and UDP.
|
||||
type Server struct {
|
||||
addr string
|
||||
lnTCP net.Listener
|
||||
lnUDP net.PacketConn
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// MustStart starts graphite server on the given addr.
|
||||
//
|
||||
// MustStop must be called on the returned server when it is no longer needed.
|
||||
func MustStart(addr string) *Server {
|
||||
logger.Infof("starting TCP Graphite server at %q", addr)
|
||||
lnTCP, err := netutil.NewTCPListener("graphite", addr)
|
||||
if err != nil {
|
||||
logger.Fatalf("cannot start TCP Graphite server at %q: %s", addr, err)
|
||||
}
|
||||
listenerTCP = lnTCP
|
||||
|
||||
logger.Infof("starting UDP Graphite server at %q", addr)
|
||||
lnUDP, err := net.ListenPacket("udp4", addr)
|
||||
if err != nil {
|
||||
logger.Fatalf("cannot start UDP Graphite server at %q: %s", addr, err)
|
||||
}
|
||||
listenerUDP = lnUDP
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
s := &Server{
|
||||
addr: addr,
|
||||
lnTCP: lnTCP,
|
||||
lnUDP: lnUDP,
|
||||
}
|
||||
s.wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
serveTCP(listenerTCP)
|
||||
defer s.wg.Done()
|
||||
serveTCP(lnTCP)
|
||||
logger.Infof("stopped TCP Graphite server at %q", addr)
|
||||
}()
|
||||
wg.Add(1)
|
||||
s.wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
serveUDP(listenerUDP)
|
||||
defer s.wg.Done()
|
||||
serveUDP(lnUDP)
|
||||
logger.Infof("stopped UDP Graphite server at %q", addr)
|
||||
}()
|
||||
wg.Wait()
|
||||
return s
|
||||
}
|
||||
|
||||
// MustStop stops the server.
|
||||
func (s *Server) MustStop() {
|
||||
logger.Infof("stopping TCP Graphite server at %q...", s.addr)
|
||||
if err := s.lnTCP.Close(); err != nil {
|
||||
logger.Errorf("cannot close TCP Graphite server: %s", err)
|
||||
}
|
||||
logger.Infof("stopping UDP Graphite server at %q...", s.addr)
|
||||
if err := s.lnUDP.Close(); err != nil {
|
||||
logger.Errorf("cannot close UDP Graphite server: %s", err)
|
||||
}
|
||||
s.wg.Wait()
|
||||
logger.Infof("TCP and UDP Graphite servers at %q have been stopped", s.addr)
|
||||
}
|
||||
|
||||
func serveTCP(ln net.Listener) {
|
||||
@@ -59,6 +85,7 @@ func serveTCP(ln net.Listener) {
|
||||
if err != nil {
|
||||
if ne, ok := err.(net.Error); ok {
|
||||
if ne.Temporary() {
|
||||
logger.Errorf("graphite: temporary error when listening for TCP addr %q: %s", ln.Addr(), err)
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
@@ -97,6 +124,7 @@ func serveUDP(ln net.PacketConn) {
|
||||
writeErrorsUDP.Inc()
|
||||
if ne, ok := err.(net.Error); ok {
|
||||
if ne.Temporary() {
|
||||
logger.Errorf("graphite: temporary error when listening for UDP addr %q: %s", ln.LocalAddr(), err)
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
@@ -119,20 +147,3 @@ func serveUDP(ln net.PacketConn) {
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
var (
|
||||
listenerTCP net.Listener
|
||||
listenerUDP net.PacketConn
|
||||
)
|
||||
|
||||
// Stop stops the server.
|
||||
func Stop() {
|
||||
logger.Infof("stopping TCP Graphite server at %q...", listenerTCP.Addr())
|
||||
if err := listenerTCP.Close(); err != nil {
|
||||
logger.Errorf("cannot close TCP Graphite server: %s", err)
|
||||
}
|
||||
logger.Infof("stopping UDP Graphite server at %q...", listenerUDP.LocalAddr())
|
||||
if err := listenerUDP.Close(); err != nil {
|
||||
logger.Errorf("cannot close UDP Graphite server: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,6 +348,36 @@ func TestRowsUnmarshalSuccess(t *testing.T) {
|
||||
}},
|
||||
}},
|
||||
})
|
||||
// Test case from https://community.librenms.org/t/integration-with-victoriametrics/9689
|
||||
f("ports,foo=a,bar=et\\ +\\ V,baz=ype INDISCARDS=245333676,OUTDISCARDS=1798680", &Rows{
|
||||
Rows: []Row{{
|
||||
Measurement: "ports",
|
||||
Tags: []Tag{
|
||||
{
|
||||
Key: "foo",
|
||||
Value: "a",
|
||||
},
|
||||
{
|
||||
Key: "bar",
|
||||
Value: "et + V",
|
||||
},
|
||||
{
|
||||
Key: "baz",
|
||||
Value: "ype",
|
||||
},
|
||||
},
|
||||
Fields: []Field{
|
||||
{
|
||||
Key: "INDISCARDS",
|
||||
Value: 245333676,
|
||||
},
|
||||
{
|
||||
Key: "OUTDISCARDS",
|
||||
Value: 1798680,
|
||||
},
|
||||
},
|
||||
}},
|
||||
})
|
||||
|
||||
// Multiple lines
|
||||
f("foo,tag=xyz field=1.23 48934\n"+
|
||||
|
||||
@@ -36,7 +36,7 @@ func InsertHandler(req *http.Request) error {
|
||||
}
|
||||
|
||||
func insertHandlerInternal(req *http.Request) error {
|
||||
influxReadCalls.Inc()
|
||||
readCalls.Inc()
|
||||
|
||||
r := req.Body
|
||||
if req.Header.Get("Content-Encoding") == "gzip" {
|
||||
@@ -82,7 +82,7 @@ func (ctx *pushCtx) InsertRows(db string) error {
|
||||
rows := ctx.Rows.Rows
|
||||
rowsLen := 0
|
||||
for i := range rows {
|
||||
rowsLen += len(rows[i].Tags)
|
||||
rowsLen += len(rows[i].Fields)
|
||||
}
|
||||
ic := &ctx.Common
|
||||
ic.Reset(rowsLen)
|
||||
@@ -132,7 +132,7 @@ func (ctx *pushCtx) Read(r io.Reader, tsMultiplier int64) bool {
|
||||
ctx.reqBuf, ctx.tailBuf, ctx.err = common.ReadLinesBlock(r, ctx.reqBuf, ctx.tailBuf)
|
||||
if ctx.err != nil {
|
||||
if ctx.err != io.EOF {
|
||||
influxReadErrors.Inc()
|
||||
readErrors.Inc()
|
||||
ctx.err = fmt.Errorf("cannot read influx line protocol data: %s", ctx.err)
|
||||
}
|
||||
return false
|
||||
@@ -166,8 +166,8 @@ func (ctx *pushCtx) Read(r io.Reader, tsMultiplier int64) bool {
|
||||
}
|
||||
|
||||
var (
|
||||
influxReadCalls = metrics.NewCounter(`vm_read_calls_total{name="influx"}`)
|
||||
influxReadErrors = metrics.NewCounter(`vm_read_errors_total{name="influx"}`)
|
||||
readCalls = metrics.NewCounter(`vm_read_calls_total{name="influx"}`)
|
||||
readErrors = metrics.NewCounter(`vm_read_errors_total{name="influx"}`)
|
||||
)
|
||||
|
||||
type pushCtx struct {
|
||||
|
||||
@@ -12,45 +12,54 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/opentsdb"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/opentsdbhttp"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/prometheus"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/vmimport"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
)
|
||||
|
||||
var (
|
||||
graphiteListenAddr = flag.String("graphiteListenAddr", "", "TCP and UDP address to listen for Graphite plaintext data. Usually :2003 must be set. Doesn't work if empty")
|
||||
opentsdbListenAddr = flag.String("opentsdbListenAddr", "", "TCP and UDP address to listen for OpentTSDB put messages. Usually :4242 must be set. Doesn't work if empty")
|
||||
graphiteListenAddr = flag.String("graphiteListenAddr", "", "TCP and UDP address to listen for Graphite plaintext data. Usually :2003 must be set. Doesn't work if empty")
|
||||
opentsdbListenAddr = flag.String("opentsdbListenAddr", "", "TCP and UDP address to listen for OpentTSDB metrics. "+
|
||||
"Telnet put messages and HTTP /api/put messages are simultaneously served on TCP port. "+
|
||||
"Usually :4242 must be set. Doesn't work if empty")
|
||||
opentsdbHTTPListenAddr = flag.String("opentsdbHTTPListenAddr", "", "TCP address to listen for OpentTSDB HTTP put requests. Usually :4242 must be set. Doesn't work if empty")
|
||||
maxInsertRequestSize = flag.Int("maxInsertRequestSize", 32*1024*1024, "The maximum size of a single insert request in bytes")
|
||||
maxLabelsPerTimeseries = flag.Int("maxLabelsPerTimeseries", 30, "The maximum number of labels accepted per time series. Superflouos labels are dropped")
|
||||
)
|
||||
|
||||
var (
|
||||
graphiteServer *graphite.Server
|
||||
opentsdbServer *opentsdb.Server
|
||||
opentsdbhttpServer *opentsdbhttp.Server
|
||||
)
|
||||
|
||||
// Init initializes vminsert.
|
||||
func Init() {
|
||||
storage.SetMaxLabelsPerTimeseries(*maxLabelsPerTimeseries)
|
||||
|
||||
concurrencylimiter.Init()
|
||||
if len(*graphiteListenAddr) > 0 {
|
||||
go graphite.Serve(*graphiteListenAddr)
|
||||
graphiteServer = graphite.MustStart(*graphiteListenAddr)
|
||||
}
|
||||
if len(*opentsdbListenAddr) > 0 {
|
||||
go opentsdb.Serve(*opentsdbListenAddr)
|
||||
opentsdbServer = opentsdb.MustStart(*opentsdbListenAddr, int64(*maxInsertRequestSize))
|
||||
}
|
||||
if len(*opentsdbHTTPListenAddr) > 0 {
|
||||
go opentsdbhttp.Serve(*opentsdbHTTPListenAddr, int64(*maxInsertRequestSize))
|
||||
opentsdbhttpServer = opentsdbhttp.MustStart(*opentsdbHTTPListenAddr, int64(*maxInsertRequestSize))
|
||||
}
|
||||
}
|
||||
|
||||
// Stop stops vminsert.
|
||||
func Stop() {
|
||||
if len(*graphiteListenAddr) > 0 {
|
||||
graphite.Stop()
|
||||
graphiteServer.MustStop()
|
||||
}
|
||||
if len(*opentsdbListenAddr) > 0 {
|
||||
opentsdb.Stop()
|
||||
opentsdbServer.MustStop()
|
||||
}
|
||||
if len(*opentsdbHTTPListenAddr) > 0 {
|
||||
opentsdbhttp.Stop()
|
||||
opentsdbhttpServer.MustStop()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +76,15 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return true
|
||||
case "/api/v1/import":
|
||||
vmimportRequests.Inc()
|
||||
if err := vmimport.InsertHandler(r); err != nil {
|
||||
vmimportErrors.Inc()
|
||||
httpserver.Errorf(w, "error in %q: %s", r.URL.Path, err)
|
||||
return true
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return true
|
||||
case "/write", "/api/v2/write":
|
||||
influxWriteRequests.Inc()
|
||||
if err := influx.InsertHandler(r); err != nil {
|
||||
@@ -92,6 +110,9 @@ var (
|
||||
prometheusWriteRequests = metrics.NewCounter(`vm_http_requests_total{path="/api/v1/write", protocol="prometheus"}`)
|
||||
prometheusWriteErrors = metrics.NewCounter(`vm_http_request_errors_total{path="/api/v1/write", protocol="prometheus"}`)
|
||||
|
||||
vmimportRequests = metrics.NewCounter(`vm_http_requests_total{path="/api/v1/import", protocol="vm"}`)
|
||||
vmimportErrors = metrics.NewCounter(`vm_http_request_errors_total{path="/api/v1/import", protocol="vm"}`)
|
||||
|
||||
influxWriteRequests = metrics.NewCounter(`vm_http_requests_total{path="/write", protocol="influx"}`)
|
||||
influxWriteErrors = metrics.NewCounter(`vm_http_request_errors_total{path="/write", protocol="influx"}`)
|
||||
|
||||
|
||||
159
app/vminsert/opentsdb/listener_switch.go
Normal file
159
app/vminsert/opentsdb/listener_switch.go
Normal file
@@ -0,0 +1,159 @@
|
||||
package opentsdb
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
)
|
||||
|
||||
// listenerSwitch listens for incoming connections and multiplexes them to OpenTSDB http or telnet listeners
|
||||
// depending on the first byte in the accepted connection.
|
||||
//
|
||||
// It is expected that both listeners - http and telnet consume incoming connections as soon as possible.
|
||||
type listenerSwitch struct {
|
||||
ln net.Listener
|
||||
wg sync.WaitGroup
|
||||
|
||||
telnetConnsCh chan net.Conn
|
||||
httpConnsCh chan net.Conn
|
||||
|
||||
closeLock sync.Mutex
|
||||
closed bool
|
||||
acceptErr error
|
||||
closeErr error
|
||||
}
|
||||
|
||||
func newListenerSwitch(ln net.Listener) *listenerSwitch {
|
||||
ls := &listenerSwitch{
|
||||
ln: ln,
|
||||
}
|
||||
ls.telnetConnsCh = make(chan net.Conn)
|
||||
ls.httpConnsCh = make(chan net.Conn)
|
||||
ls.wg.Add(1)
|
||||
go func() {
|
||||
ls.worker()
|
||||
close(ls.telnetConnsCh)
|
||||
close(ls.httpConnsCh)
|
||||
ls.wg.Done()
|
||||
}()
|
||||
return ls
|
||||
}
|
||||
|
||||
func (ls *listenerSwitch) stop() error {
|
||||
var err error
|
||||
ls.closeLock.Lock()
|
||||
if !ls.closed {
|
||||
err = ls.ln.Close()
|
||||
ls.closeErr = err
|
||||
ls.closed = true
|
||||
}
|
||||
ls.closeLock.Unlock()
|
||||
|
||||
if err == nil {
|
||||
// Wait until worker detects the closed ls.ln and exits.
|
||||
ls.wg.Wait()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (ls *listenerSwitch) worker() {
|
||||
var buf [1]byte
|
||||
for {
|
||||
c, err := ls.ln.Accept()
|
||||
if err != nil {
|
||||
if ne, ok := err.(net.Error); ok && ne.Temporary() {
|
||||
logger.Infof("listenerSwitch: temporary error at %q: %s; sleeping for a second...", ls.ln.Addr(), err)
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
ls.closeLock.Lock()
|
||||
ls.acceptErr = err
|
||||
ls.closeLock.Unlock()
|
||||
return
|
||||
}
|
||||
if _, err := io.ReadFull(c, buf[:]); err != nil {
|
||||
logger.Errorf("listenerSwitch: cannot read one byte from the underlying connection for %q: %s", ls.ln.Addr(), err)
|
||||
_ = c.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
// It is expected that both listeners - http and telnet consume incoming connections as soon as possible,
|
||||
// so the below code shouldn't block for extended periods of time.
|
||||
pc := &peekedConn{
|
||||
Conn: c,
|
||||
firstChar: buf[0],
|
||||
}
|
||||
if buf[0] == 'p' {
|
||||
// Assume the request starts with `put`.
|
||||
ls.telnetConnsCh <- pc
|
||||
} else {
|
||||
// Assume the request starts with `POST`.
|
||||
ls.httpConnsCh <- pc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type peekedConn struct {
|
||||
net.Conn
|
||||
firstChar byte
|
||||
firstCharRead bool
|
||||
}
|
||||
|
||||
func (pc *peekedConn) Read(p []byte) (int, error) {
|
||||
// It is assumed that the pc cannot be read from concurrent goroutines.
|
||||
if pc.firstCharRead {
|
||||
// Fast path - first char already read.
|
||||
return pc.Conn.Read(p)
|
||||
}
|
||||
|
||||
// Slow path - read the first char.
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
p[0] = pc.firstChar
|
||||
pc.firstCharRead = true
|
||||
n, err := pc.Conn.Read(p[1:])
|
||||
return n + 1, err
|
||||
}
|
||||
|
||||
func (ls *listenerSwitch) newTelnetListener() *chanListener {
|
||||
return &chanListener{
|
||||
ls: ls,
|
||||
ch: ls.telnetConnsCh,
|
||||
}
|
||||
}
|
||||
|
||||
func (ls *listenerSwitch) newHTTPListener() *chanListener {
|
||||
return &chanListener{
|
||||
ls: ls,
|
||||
ch: ls.httpConnsCh,
|
||||
}
|
||||
}
|
||||
|
||||
type chanListener struct {
|
||||
ls *listenerSwitch
|
||||
ch chan net.Conn
|
||||
}
|
||||
|
||||
func (cl *chanListener) Accept() (net.Conn, error) {
|
||||
c, ok := <-cl.ch
|
||||
if ok {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
cl.ls.closeLock.Lock()
|
||||
err := cl.ls.acceptErr
|
||||
cl.ls.closeLock.Unlock()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (cl *chanListener) Close() error {
|
||||
return cl.ls.stop()
|
||||
}
|
||||
|
||||
func (cl *chanListener) Addr() net.Addr {
|
||||
return cl.ls.ln.Addr()
|
||||
}
|
||||
@@ -61,13 +61,13 @@ func (ctx *pushCtx) InsertRows() error {
|
||||
const flushTimeout = 3 * time.Second
|
||||
|
||||
func (ctx *pushCtx) Read(r io.Reader) bool {
|
||||
opentsdbReadCalls.Inc()
|
||||
readCalls.Inc()
|
||||
if ctx.err != nil {
|
||||
return false
|
||||
}
|
||||
if c, ok := r.(net.Conn); ok {
|
||||
if err := c.SetReadDeadline(time.Now().Add(flushTimeout)); err != nil {
|
||||
opentsdbReadErrors.Inc()
|
||||
readErrors.Inc()
|
||||
ctx.err = fmt.Errorf("cannot set read deadline: %s", err)
|
||||
return false
|
||||
}
|
||||
@@ -79,7 +79,7 @@ func (ctx *pushCtx) Read(r io.Reader) bool {
|
||||
ctx.err = nil
|
||||
} else {
|
||||
if ctx.err != io.EOF {
|
||||
opentsdbReadErrors.Inc()
|
||||
readErrors.Inc()
|
||||
ctx.err = fmt.Errorf("cannot read OpenTSDB put protocol data: %s", ctx.err)
|
||||
}
|
||||
return false
|
||||
@@ -131,8 +131,8 @@ func (ctx *pushCtx) reset() {
|
||||
}
|
||||
|
||||
var (
|
||||
opentsdbReadCalls = metrics.NewCounter(`vm_read_calls_total{name="opentsdb"}`)
|
||||
opentsdbReadErrors = metrics.NewCounter(`vm_read_errors_total{name="opentsdb"}`)
|
||||
readCalls = metrics.NewCounter(`vm_read_calls_total{name="opentsdb"}`)
|
||||
readErrors = metrics.NewCounter(`vm_read_errors_total{name="opentsdb"}`)
|
||||
)
|
||||
|
||||
func getPushCtx() *pushCtx {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/opentsdbhttp"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/netutil"
|
||||
@@ -21,44 +22,91 @@ var (
|
||||
writeErrorsUDP = metrics.NewCounter(`vm_opentsdb_request_errors_total{name="write", net="udp"}`)
|
||||
)
|
||||
|
||||
// Serve starts OpenTSDB collector on the given addr.
|
||||
func Serve(addr string) {
|
||||
// Server is a server for collecting OpenTSDB TCP and UDP metrics.
|
||||
//
|
||||
// It accepts simultaneously Telnet put requests and HTTP put requests over TCP.
|
||||
type Server struct {
|
||||
addr string
|
||||
ls *listenerSwitch
|
||||
httpServer *opentsdbhttp.Server
|
||||
lnUDP net.PacketConn
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// MustStart starts OpenTSDB collector on the given addr.
|
||||
//
|
||||
// MustStop must be called on the returned server when it is no longer needed.
|
||||
func MustStart(addr string, maxRequestSize int64) *Server {
|
||||
logger.Infof("starting TCP OpenTSDB collector at %q", addr)
|
||||
lnTCP, err := netutil.NewTCPListener("opentsdb", addr)
|
||||
if err != nil {
|
||||
logger.Fatalf("cannot start TCP OpenTSDB collector at %q: %s", addr, err)
|
||||
}
|
||||
listenerTCP = lnTCP
|
||||
ls := newListenerSwitch(lnTCP)
|
||||
lnHTTP := ls.newHTTPListener()
|
||||
lnTelnet := ls.newTelnetListener()
|
||||
httpServer := opentsdbhttp.MustServe(lnHTTP, maxRequestSize)
|
||||
|
||||
logger.Infof("starting UDP OpenTSDB collector at %q", addr)
|
||||
lnUDP, err := net.ListenPacket("udp4", addr)
|
||||
if err != nil {
|
||||
logger.Fatalf("cannot start UDP OpenTSDB collector at %q: %s", addr, err)
|
||||
}
|
||||
listenerUDP = lnUDP
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
s := &Server{
|
||||
addr: addr,
|
||||
ls: ls,
|
||||
httpServer: httpServer,
|
||||
lnUDP: lnUDP,
|
||||
}
|
||||
s.wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
serveTCP(listenerTCP)
|
||||
logger.Infof("stopped TCP OpenTSDB collector at %q", addr)
|
||||
defer s.wg.Done()
|
||||
serveTelnet(lnTelnet)
|
||||
logger.Infof("stopped TCP telnet OpenTSDB server at %q", addr)
|
||||
}()
|
||||
wg.Add(1)
|
||||
s.wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
serveUDP(listenerUDP)
|
||||
logger.Infof("stopped UDP OpenTSDB collector at %q", addr)
|
||||
defer s.wg.Done()
|
||||
httpServer.Wait()
|
||||
// Do not log when httpServer is stopped, since this is logged by the server itself.
|
||||
}()
|
||||
wg.Wait()
|
||||
s.wg.Add(1)
|
||||
go func() {
|
||||
defer s.wg.Done()
|
||||
serveUDP(lnUDP)
|
||||
logger.Infof("stopped UDP OpenTSDB server at %q", addr)
|
||||
}()
|
||||
return s
|
||||
}
|
||||
|
||||
func serveTCP(ln net.Listener) {
|
||||
// MustStop stops the server.
|
||||
func (s *Server) MustStop() {
|
||||
// Stop HTTP server. Do not emit log message, since it is emitted by the httpServer.
|
||||
s.httpServer.MustStop()
|
||||
|
||||
logger.Infof("stopping TCP telnet OpenTSDB server at %q...", s.addr)
|
||||
if err := s.ls.stop(); err != nil {
|
||||
logger.Errorf("cannot stop TCP telnet OpenTSDB server: %s", err)
|
||||
}
|
||||
|
||||
logger.Infof("stopping UDP OpenTSDB server at %q...", s.addr)
|
||||
if err := s.lnUDP.Close(); err != nil {
|
||||
logger.Errorf("cannot stop UDP OpenTSDB server: %s", err)
|
||||
}
|
||||
|
||||
// Wait until all the servers are stopped.
|
||||
s.wg.Wait()
|
||||
logger.Infof("TCP and UDP OpenTSDB servers at %q have been stopped", s.addr)
|
||||
}
|
||||
|
||||
func serveTelnet(ln net.Listener) {
|
||||
for {
|
||||
c, err := ln.Accept()
|
||||
if err != nil {
|
||||
if ne, ok := err.(net.Error); ok {
|
||||
if ne.Temporary() {
|
||||
logger.Errorf("opentsdb: temporary error when listening for TCP addr %q: %s", ln.Addr(), err)
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
@@ -97,6 +145,7 @@ func serveUDP(ln net.PacketConn) {
|
||||
writeErrorsUDP.Inc()
|
||||
if ne, ok := err.(net.Error); ok {
|
||||
if ne.Temporary() {
|
||||
logger.Errorf("opentsdb: temporary error when listening for UDP addr %q: %s", ln.LocalAddr(), err)
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
@@ -119,20 +168,3 @@ func serveUDP(ln net.PacketConn) {
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
var (
|
||||
listenerTCP net.Listener
|
||||
listenerUDP net.PacketConn
|
||||
)
|
||||
|
||||
// Stop stops the server.
|
||||
func Stop() {
|
||||
logger.Infof("stopping TCP OpenTSDB server at %q...", listenerTCP.Addr())
|
||||
if err := listenerTCP.Close(); err != nil {
|
||||
logger.Errorf("cannot close TCP OpenTSDB server: %s", err)
|
||||
}
|
||||
logger.Infof("stopping UDP OpenTSDB server at %q...", listenerUDP.LocalAddr())
|
||||
if err := listenerUDP.Close(); err != nil {
|
||||
logger.Errorf("cannot close UDP OpenTSDB server: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,9 +19,9 @@ var (
|
||||
rowsInserted = metrics.NewCounter(`vm_rows_inserted_total{type="opentsdb-http"}`)
|
||||
rowsPerInsert = metrics.NewSummary(`vm_rows_per_insert{type="opentsdb-http"}`)
|
||||
|
||||
opentsdbReadCalls = metrics.NewCounter(`vm_read_calls_total{name="opentsdb-http"}`)
|
||||
opentsdbReadErrors = metrics.NewCounter(`vm_read_errors_total{name="opentsdb-http"}`)
|
||||
opentsdbUnmarshalErrors = metrics.NewCounter(`vm_unmarshal_errors_total{name="opentsdb-http"}`)
|
||||
readCalls = metrics.NewCounter(`vm_read_calls_total{name="opentsdb-http"}`)
|
||||
readErrors = metrics.NewCounter(`vm_read_errors_total{name="opentsdb-http"}`)
|
||||
unmarshalErrors = metrics.NewCounter(`vm_unmarshal_errors_total{name="opentsdb-http"}`)
|
||||
)
|
||||
|
||||
// insertHandler processes HTTP OpenTSDB put requests.
|
||||
@@ -33,13 +33,13 @@ func insertHandler(req *http.Request, maxSize int64) error {
|
||||
}
|
||||
|
||||
func insertHandlerInternal(req *http.Request, maxSize int64) error {
|
||||
opentsdbReadCalls.Inc()
|
||||
readCalls.Inc()
|
||||
|
||||
r := req.Body
|
||||
if req.Header.Get("Content-Encoding") == "gzip" {
|
||||
zr, err := common.GetGzipReader(r)
|
||||
if err != nil {
|
||||
opentsdbReadErrors.Inc()
|
||||
readErrors.Inc()
|
||||
return fmt.Errorf("cannot read gzipped http protocol data: %s", err)
|
||||
}
|
||||
defer common.PutGzipReader(zr)
|
||||
@@ -53,11 +53,11 @@ func insertHandlerInternal(req *http.Request, maxSize int64) error {
|
||||
lr := io.LimitReader(r, maxSize+1)
|
||||
reqLen, err := ctx.reqBuf.ReadFrom(lr)
|
||||
if err != nil {
|
||||
opentsdbReadErrors.Inc()
|
||||
readErrors.Inc()
|
||||
return fmt.Errorf("cannot read HTTP OpenTSDB request: %s", err)
|
||||
}
|
||||
if reqLen > maxSize {
|
||||
opentsdbReadErrors.Inc()
|
||||
readErrors.Inc()
|
||||
return fmt.Errorf("too big HTTP OpenTSDB request; mustn't exceed %d bytes", maxSize)
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ func insertHandlerInternal(req *http.Request, maxSize int64) error {
|
||||
defer parserPool.Put(p)
|
||||
v, err := p.ParseBytes(ctx.reqBuf.B)
|
||||
if err != nil {
|
||||
opentsdbUnmarshalErrors.Inc()
|
||||
unmarshalErrors.Inc()
|
||||
return fmt.Errorf("cannot parse HTTP OpenTSDB json: %s", err)
|
||||
}
|
||||
ctx.Rows.Unmarshal(v)
|
||||
|
||||
@@ -2,11 +2,14 @@ package opentsdbhttp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/netutil"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
)
|
||||
|
||||
@@ -15,56 +18,84 @@ var (
|
||||
writeErrors = metrics.NewCounter(`vm_http_request_errors_total{path="/api/put", protocol="opentsdb-http"}`)
|
||||
)
|
||||
|
||||
var (
|
||||
httpServer *http.Server
|
||||
httpAddr string
|
||||
maxRequestSize int64
|
||||
)
|
||||
// Server represents HTTP OpenTSDB server.
|
||||
type Server struct {
|
||||
s *http.Server
|
||||
ln net.Listener
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// Serve starts HTTP OpenTSDB server on the given addr.
|
||||
func Serve(addr string, maxReqSize int64) {
|
||||
// MustStart starts HTTP OpenTSDB server on the given addr.
|
||||
//
|
||||
// MustStop must be called on the returned server when it is no longer needed.
|
||||
func MustStart(addr string, maxRequestSize int64) *Server {
|
||||
logger.Infof("starting HTTP OpenTSDB server at %q", addr)
|
||||
httpAddr = addr
|
||||
maxRequestSize = maxReqSize
|
||||
httpServer = &http.Server{
|
||||
Addr: addr,
|
||||
Handler: http.HandlerFunc(requestHandler),
|
||||
lnTCP, err := netutil.NewTCPListener("opentsdbhttp", addr)
|
||||
if err != nil {
|
||||
logger.Fatalf("cannot start HTTP OpenTSDB collector at %q: %s", addr, err)
|
||||
}
|
||||
return MustServe(lnTCP, maxRequestSize)
|
||||
}
|
||||
|
||||
// MustServe serves OpenTSDB HTTP put requests from ln with up to maxRequestSize size.
|
||||
//
|
||||
// MustStop must be called on the returned server when it is no longer needed.
|
||||
func MustServe(ln net.Listener, maxRequestSize int64) *Server {
|
||||
h := newRequestHandler(maxRequestSize)
|
||||
hs := &http.Server{
|
||||
Handler: h,
|
||||
ReadTimeout: 30 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
}
|
||||
s := &Server{
|
||||
s: hs,
|
||||
ln: ln,
|
||||
}
|
||||
s.wg.Add(1)
|
||||
go func() {
|
||||
err := httpServer.ListenAndServe()
|
||||
defer s.wg.Done()
|
||||
err := s.s.Serve(s.ln)
|
||||
if err == http.ErrServerClosed {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
logger.Fatalf("error serving HTTP OpenTSDB: %s", err)
|
||||
logger.Fatalf("error serving HTTP OpenTSDB at %q: %s", s.ln.Addr(), err)
|
||||
}
|
||||
}()
|
||||
return s
|
||||
}
|
||||
|
||||
// requestHandler handles HTTP OpenTSDB insert request.
|
||||
func requestHandler(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/api/put":
|
||||
writeRequests.Inc()
|
||||
if err := insertHandler(r, maxRequestSize); err != nil {
|
||||
writeErrors.Inc()
|
||||
httpserver.Errorf(w, "error in %q: %s", r.URL.Path, err)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
default:
|
||||
httpserver.Errorf(w, "unexpected path requested on HTTP OpenTSDB server: %q", r.URL.Path)
|
||||
}
|
||||
// Wait waits until the server is stopped with MustStop.
|
||||
func (s *Server) Wait() {
|
||||
s.wg.Wait()
|
||||
}
|
||||
|
||||
// Stop stops HTTP OpenTSDB server.
|
||||
func Stop() {
|
||||
logger.Infof("stopping HTTP OpenTSDB server at %q...", httpAddr)
|
||||
// MustStop stops HTTP OpenTSDB server.
|
||||
func (s *Server) MustStop() {
|
||||
logger.Infof("stopping HTTP OpenTSDB server at %q...", s.ln.Addr())
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
if err := httpServer.Shutdown(ctx); err != nil {
|
||||
logger.Fatalf("cannot close HTTP OpenTSDB server: %s", err)
|
||||
if err := s.s.Shutdown(ctx); err != nil {
|
||||
logger.Fatalf("cannot close HTTP OpenTSDB server at %q: %s", s.ln.Addr(), err)
|
||||
}
|
||||
s.wg.Wait()
|
||||
logger.Infof("OpenTSDB HTTP server at %q has been stopped", s.ln.Addr())
|
||||
}
|
||||
|
||||
func newRequestHandler(maxRequestSize int64) http.Handler {
|
||||
rh := func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/api/put":
|
||||
writeRequests.Inc()
|
||||
if err := insertHandler(r, maxRequestSize); err != nil {
|
||||
writeErrors.Inc()
|
||||
httpserver.Errorf(w, "error in %q: %s", r.URL.Path, err)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
default:
|
||||
httpserver.Errorf(w, "unexpected path requested on HTTP OpenTSDB server: %q", r.URL.Path)
|
||||
}
|
||||
}
|
||||
return http.HandlerFunc(rh)
|
||||
}
|
||||
|
||||
202
app/vminsert/vmimport/parser.go
Normal file
202
app/vminsert/vmimport/parser.go
Normal file
@@ -0,0 +1,202 @@
|
||||
package vmimport
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
"github.com/valyala/fastjson"
|
||||
)
|
||||
|
||||
// Rows contains parsed rows from `/api/v1/import` request.
|
||||
type Rows struct {
|
||||
Rows []Row
|
||||
|
||||
tu tagsUnmarshaler
|
||||
}
|
||||
|
||||
// Reset resets rs.
|
||||
func (rs *Rows) Reset() {
|
||||
for i := range rs.Rows {
|
||||
rs.Rows[i].reset()
|
||||
}
|
||||
rs.Rows = rs.Rows[:0]
|
||||
|
||||
rs.tu.reset()
|
||||
}
|
||||
|
||||
// Unmarshal unmarshals influx line protocol rows from s.
|
||||
//
|
||||
// See https://docs.influxdata.com/influxdb/v1.7/write_protocols/line_protocol_tutorial/
|
||||
//
|
||||
// s must be unchanged until rs is in use.
|
||||
func (rs *Rows) Unmarshal(s string) {
|
||||
rs.tu.reset()
|
||||
rs.Rows = unmarshalRows(rs.Rows[:0], s, &rs.tu)
|
||||
}
|
||||
|
||||
// Row is a single row from `/api/v1/import` request.
|
||||
type Row struct {
|
||||
Tags []Tag
|
||||
Values []float64
|
||||
Timestamps []int64
|
||||
}
|
||||
|
||||
func (r *Row) reset() {
|
||||
r.Tags = nil
|
||||
r.Values = r.Values[:0]
|
||||
r.Timestamps = r.Timestamps[:0]
|
||||
}
|
||||
|
||||
func (r *Row) unmarshal(s string, tu *tagsUnmarshaler) error {
|
||||
r.reset()
|
||||
v, err := tu.p.Parse(s)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse json line: %s", err)
|
||||
}
|
||||
|
||||
// Unmarshal tags
|
||||
metric := v.GetObject("metric")
|
||||
if metric == nil {
|
||||
return fmt.Errorf("missing `metric` object")
|
||||
}
|
||||
tagsStart := len(tu.tagsPool)
|
||||
if err := tu.unmarshalTags(metric); err != nil {
|
||||
return fmt.Errorf("cannot unmarshal `metric`: %s", err)
|
||||
}
|
||||
tags := tu.tagsPool[tagsStart:]
|
||||
r.Tags = tags[:len(tags):len(tags)]
|
||||
if len(r.Tags) == 0 {
|
||||
return fmt.Errorf("missing tags")
|
||||
}
|
||||
|
||||
// Unmarshal values
|
||||
values := v.GetArray("values")
|
||||
if len(values) == 0 {
|
||||
return fmt.Errorf("missing `values` array")
|
||||
}
|
||||
for i, v := range values {
|
||||
f, err := v.Float64()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot unmarshal value at position %d: %s", i, err)
|
||||
}
|
||||
r.Values = append(r.Values, f)
|
||||
}
|
||||
|
||||
// Unmarshal timestamps
|
||||
timestamps := v.GetArray("timestamps")
|
||||
if len(timestamps) == 0 {
|
||||
return fmt.Errorf("missing `timestamps` array")
|
||||
}
|
||||
for i, v := range timestamps {
|
||||
ts, err := v.Int64()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot unmarshal timestamp at position %d: %s", i, err)
|
||||
}
|
||||
r.Timestamps = append(r.Timestamps, ts)
|
||||
}
|
||||
|
||||
if len(r.Timestamps) != len(r.Values) {
|
||||
return fmt.Errorf("`timestamps` array size must match `values` array size; got %d; want %d", len(r.Timestamps), len(r.Values))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Tag represents `/api/v1/import` tag.
|
||||
type Tag struct {
|
||||
Key []byte
|
||||
Value []byte
|
||||
}
|
||||
|
||||
func (tag *Tag) reset() {
|
||||
// tag.Key and tag.Value point to tu.bytesPool, so there is no need in keeping these byte slices here.
|
||||
tag.Key = nil
|
||||
tag.Value = nil
|
||||
}
|
||||
|
||||
type tagsUnmarshaler struct {
|
||||
p fastjson.Parser
|
||||
tagsPool []Tag
|
||||
bytesPool []byte
|
||||
err error
|
||||
}
|
||||
|
||||
func (tu *tagsUnmarshaler) reset() {
|
||||
for i := range tu.tagsPool {
|
||||
tu.tagsPool[i].reset()
|
||||
}
|
||||
tu.tagsPool = tu.tagsPool[:0]
|
||||
|
||||
tu.bytesPool = tu.bytesPool[:0]
|
||||
tu.err = nil
|
||||
}
|
||||
|
||||
func (tu *tagsUnmarshaler) addTag() *Tag {
|
||||
dst := tu.tagsPool
|
||||
if cap(dst) > len(dst) {
|
||||
dst = dst[:len(dst)+1]
|
||||
} else {
|
||||
dst = append(dst, Tag{})
|
||||
}
|
||||
tag := &dst[len(dst)-1]
|
||||
tu.tagsPool = dst
|
||||
return tag
|
||||
}
|
||||
|
||||
func (tu *tagsUnmarshaler) addBytes(b []byte) []byte {
|
||||
bytesPoolLen := len(tu.bytesPool)
|
||||
tu.bytesPool = append(tu.bytesPool, b...)
|
||||
bCopy := tu.bytesPool[bytesPoolLen:]
|
||||
return bCopy[:len(bCopy):len(bCopy)]
|
||||
}
|
||||
|
||||
func (tu *tagsUnmarshaler) unmarshalTags(o *fastjson.Object) error {
|
||||
tu.err = nil
|
||||
o.Visit(func(key []byte, v *fastjson.Value) {
|
||||
tag := tu.addTag()
|
||||
tag.Key = tu.addBytes(key)
|
||||
sb, err := v.StringBytes()
|
||||
if err != nil && tu.err != nil {
|
||||
tu.err = fmt.Errorf("cannot parse value for tag %q: %s", tag.Key, err)
|
||||
}
|
||||
tag.Value = tu.addBytes(sb)
|
||||
})
|
||||
return tu.err
|
||||
}
|
||||
|
||||
func unmarshalRows(dst []Row, s string, tu *tagsUnmarshaler) []Row {
|
||||
for len(s) > 0 {
|
||||
n := strings.IndexByte(s, '\n')
|
||||
if n < 0 {
|
||||
// The last line.
|
||||
return unmarshalRow(dst, s, tu)
|
||||
}
|
||||
dst = unmarshalRow(dst, s[:n], tu)
|
||||
s = s[n+1:]
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func unmarshalRow(dst []Row, s string, tu *tagsUnmarshaler) []Row {
|
||||
if len(s) > 0 && s[len(s)-1] == '\r' {
|
||||
s = s[:len(s)-1]
|
||||
}
|
||||
if len(s) == 0 {
|
||||
return dst
|
||||
}
|
||||
if cap(dst) > len(dst) {
|
||||
dst = dst[:len(dst)+1]
|
||||
} else {
|
||||
dst = append(dst, Row{})
|
||||
}
|
||||
r := &dst[len(dst)-1]
|
||||
if err := r.unmarshal(s, tu); err != nil {
|
||||
dst = dst[:len(dst)-1]
|
||||
logger.Errorf("cannot unmarshal json line %q: %s; skipping it", s, err)
|
||||
invalidLines.Inc()
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
var invalidLines = metrics.NewCounter(`vm_rows_invalid_total{type="vmimport"}`)
|
||||
216
app/vminsert/vmimport/parser_test.go
Normal file
216
app/vminsert/vmimport/parser_test.go
Normal file
@@ -0,0 +1,216 @@
|
||||
package vmimport
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRowsUnmarshalFailure(t *testing.T) {
|
||||
f := func(s string) {
|
||||
t.Helper()
|
||||
var rows Rows
|
||||
rows.Unmarshal(s)
|
||||
if len(rows.Rows) != 0 {
|
||||
t.Fatalf("expecting zero rows; got %d rows", len(rows.Rows))
|
||||
}
|
||||
|
||||
// Try again
|
||||
rows.Unmarshal(s)
|
||||
if len(rows.Rows) != 0 {
|
||||
t.Fatalf("expecting zero rows; got %d rows", len(rows.Rows))
|
||||
}
|
||||
}
|
||||
|
||||
// Invalid json line
|
||||
f("")
|
||||
f("\n")
|
||||
f("foo\n")
|
||||
f("123")
|
||||
f("[1,3]")
|
||||
f("{}")
|
||||
f("[]")
|
||||
f(`{"foo":"bar"}`)
|
||||
|
||||
// Invalid metric
|
||||
f(`{"metric":123,"values":[1,2],"timestamps":[3,4]}`)
|
||||
f(`{"metric":[123],"values":[1,2],"timestamps":[3,4]}`)
|
||||
f(`{"metric":[],"values":[1,2],"timestamps":[3,4]}`)
|
||||
f(`{"metric":{},"values":[1,2],"timestamps":[3,4]}`)
|
||||
f(`{"metric":null,"values":[1,2],"timestamps":[3,4]}`)
|
||||
f(`{"values":[1,2],"timestamps":[3,4]}`)
|
||||
|
||||
// Invalid values
|
||||
f(`{"metric":{"foo":"bar"},"values":1,"timestamps":[3,4]}`)
|
||||
f(`{"metric":{"foo":"bar"},"values":{"x":1},"timestamps":[3,4]}`)
|
||||
f(`{"metric":{"foo":"bar"},"values":{"x":1},"timestamps":[3,4]}`)
|
||||
f(`{"metric":{"foo":"bar"},"values":null,"timestamps":[3,4]}`)
|
||||
f(`{"metric":{"foo":"bar"},"timestamps":[3,4]}`)
|
||||
|
||||
// Invalid timestamps
|
||||
f(`{"metric":{"foo":"bar"},"values":[1,2],"timestamps":3}`)
|
||||
f(`{"metric":{"foo":"bar"},"values":[1,2],"timestamps":false}`)
|
||||
f(`{"metric":{"foo":"bar"},"values":[1,2],"timestamps":{}}`)
|
||||
f(`{"metric":{"foo":"bar"},"values":[1,2]}`)
|
||||
|
||||
// values and timestamps count mismatch
|
||||
f(`{"metric":{"foo":"bar"},"values":[],"timestamps":[]}`)
|
||||
f(`{"metric":{"foo":"bar"},"values":[],"timestamps":[1]}`)
|
||||
f(`{"metric":{"foo":"bar"},"values":[2],"timestamps":[]}`)
|
||||
f(`{"metric":{"foo":"bar"},"values":[2],"timestamps":[3,4]}`)
|
||||
f(`{"metric":{"foo":"bar"},"values":[2,3],"timestamps":[4]}`)
|
||||
|
||||
// Garbage after the line
|
||||
f(`{"metric":{"foo":"bar"},"values":[2],"timestamps":[4]}{}`)
|
||||
}
|
||||
|
||||
func TestRowsUnmarshalSuccess(t *testing.T) {
|
||||
f := func(s string, rowsExpected *Rows) {
|
||||
t.Helper()
|
||||
var rows Rows
|
||||
rows.Unmarshal(s)
|
||||
if !reflect.DeepEqual(rows.Rows, rowsExpected.Rows) {
|
||||
t.Fatalf("unexpected rows;\ngot\n%+v;\nwant\n%+v", rows.Rows, rowsExpected.Rows)
|
||||
}
|
||||
|
||||
// Try unmarshaling again
|
||||
rows.Unmarshal(s)
|
||||
if !reflect.DeepEqual(rows.Rows, rowsExpected.Rows) {
|
||||
t.Fatalf("unexpected rows;\ngot\n%+v;\nwant\n%+v", rows.Rows, rowsExpected.Rows)
|
||||
}
|
||||
|
||||
rows.Reset()
|
||||
if len(rows.Rows) != 0 {
|
||||
t.Fatalf("non-empty rows after reset: %+v", rows.Rows)
|
||||
}
|
||||
}
|
||||
|
||||
// Empty line
|
||||
f("", &Rows{})
|
||||
f("\n\n", &Rows{})
|
||||
f("\n\r\n", &Rows{})
|
||||
|
||||
// Single line with a single tag
|
||||
f(`{"metric":{"foo":"bar"},"values":[1.23],"timestamps":[456]}`, &Rows{
|
||||
Rows: []Row{{
|
||||
Tags: []Tag{{
|
||||
Key: []byte("foo"),
|
||||
Value: []byte("bar"),
|
||||
}},
|
||||
Values: []float64{1.23},
|
||||
Timestamps: []int64{456},
|
||||
}},
|
||||
})
|
||||
|
||||
// Line with multiple tags
|
||||
f(`{"metric":{"foo":"bar","baz":"xx"},"values":[1.23, -3.21],"timestamps" : [456,789]}`, &Rows{
|
||||
Rows: []Row{{
|
||||
Tags: []Tag{
|
||||
{
|
||||
Key: []byte("foo"),
|
||||
Value: []byte("bar"),
|
||||
},
|
||||
{
|
||||
Key: []byte("baz"),
|
||||
Value: []byte("xx"),
|
||||
},
|
||||
},
|
||||
Values: []float64{1.23, -3.21},
|
||||
Timestamps: []int64{456, 789},
|
||||
}},
|
||||
})
|
||||
|
||||
// Multiple lines
|
||||
f(`{"metric":{"foo":"bar","baz":"xx"},"values":[1.23, -3.21],"timestamps" : [456,789]}
|
||||
{"metric":{"__name__":"xx"},"values":[34],"timestamps" : [11]}
|
||||
`, &Rows{
|
||||
Rows: []Row{
|
||||
{
|
||||
Tags: []Tag{
|
||||
{
|
||||
Key: []byte("foo"),
|
||||
Value: []byte("bar"),
|
||||
},
|
||||
{
|
||||
Key: []byte("baz"),
|
||||
Value: []byte("xx"),
|
||||
},
|
||||
},
|
||||
Values: []float64{1.23, -3.21},
|
||||
Timestamps: []int64{456, 789},
|
||||
},
|
||||
{
|
||||
Tags: []Tag{
|
||||
{
|
||||
Key: []byte("__name__"),
|
||||
Value: []byte("xx"),
|
||||
},
|
||||
},
|
||||
Values: []float64{34},
|
||||
Timestamps: []int64{11},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// Multiple lines with invalid line in the middle.
|
||||
f(`{"metric":{"xfoo":"bar","baz":"xx"},"values":[1.232, -3.21],"timestamps" : [456,7890]}
|
||||
garbage here
|
||||
{"metric":{"__name__":"xxy"},"values":[34],"timestamps" : [111]}`, &Rows{
|
||||
Rows: []Row{
|
||||
{
|
||||
Tags: []Tag{
|
||||
{
|
||||
Key: []byte("xfoo"),
|
||||
Value: []byte("bar"),
|
||||
},
|
||||
{
|
||||
Key: []byte("baz"),
|
||||
Value: []byte("xx"),
|
||||
},
|
||||
},
|
||||
Values: []float64{1.232, -3.21},
|
||||
Timestamps: []int64{456, 7890},
|
||||
},
|
||||
{
|
||||
Tags: []Tag{
|
||||
{
|
||||
Key: []byte("__name__"),
|
||||
Value: []byte("xxy"),
|
||||
},
|
||||
},
|
||||
Values: []float64{34},
|
||||
Timestamps: []int64{111},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// No newline after the second line.
|
||||
f(`{"metric":{"foo":"bar","baz":"xx"},"values":[1.23, -3.21],"timestamps" : [456,789]}
|
||||
{"metric":{"__name__":"xx"},"values":[34],"timestamps" : [11]}`, &Rows{
|
||||
Rows: []Row{
|
||||
{
|
||||
Tags: []Tag{
|
||||
{
|
||||
Key: []byte("foo"),
|
||||
Value: []byte("bar"),
|
||||
},
|
||||
{
|
||||
Key: []byte("baz"),
|
||||
Value: []byte("xx"),
|
||||
},
|
||||
},
|
||||
Values: []float64{1.23, -3.21},
|
||||
Timestamps: []int64{456, 789},
|
||||
},
|
||||
{
|
||||
Tags: []Tag{
|
||||
{
|
||||
Key: []byte("__name__"),
|
||||
Value: []byte("xx"),
|
||||
},
|
||||
},
|
||||
Values: []float64{34},
|
||||
Timestamps: []int64{11},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
25
app/vminsert/vmimport/parser_timing_test.go
Normal file
25
app/vminsert/vmimport/parser_timing_test.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package vmimport
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkRowsUnmarshal(b *testing.B) {
|
||||
s := `{"metric":{"__name__":"up","job":"node_exporter","instance":"localhost:9100"},"values":[0,0,0],"timestamps":[1549891472010,1549891487724,1549891503438]}
|
||||
{"metric":{"__name__":"up","job":"prometheus","instance":"localhost:9090"},"values":[1,1,1],"timestamps":[1549891461511,1549891476511,1549891491511]}
|
||||
{"metric":{"__name__":"up","job":"node_exporter","instance":"foobar.com:9100"},"values":[0,0,0],"timestamps":[1549891472010,1549891487724,1549891503438]}
|
||||
{"metric":{"__name__":"up","job":"prometheus","instance":"xxx.yyy.zzz:9090"},"values":[1,1,1],"timestamps":[1549891461511,1549891476511,1549891491511]}
|
||||
`
|
||||
b.SetBytes(int64(len(s)))
|
||||
b.ReportAllocs()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var rows Rows
|
||||
for pb.Next() {
|
||||
rows.Unmarshal(s)
|
||||
if len(rows.Rows) != 4 {
|
||||
panic(fmt.Errorf("unexpected number of rows parsed; got %d; want 4", len(rows.Rows)))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
160
app/vminsert/vmimport/request_handler.go
Normal file
160
app/vminsert/vmimport/request_handler.go
Normal file
@@ -0,0 +1,160 @@
|
||||
package vmimport
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/common"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/concurrencylimiter"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
)
|
||||
|
||||
var maxLineLen = flag.Int("import.maxLineLen", 100*1024*1024, "The maximum length in bytes of a single line accepted by `/api/v1/import`")
|
||||
|
||||
var (
|
||||
rowsInserted = metrics.NewCounter(`vm_rows_inserted_total{type="vmimport"}`)
|
||||
rowsPerInsert = metrics.NewSummary(`vm_rows_per_insert{type="vmimport"}`)
|
||||
)
|
||||
|
||||
// InsertHandler processes `/api/v1/import` request.
|
||||
//
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6
|
||||
func InsertHandler(req *http.Request) error {
|
||||
return concurrencylimiter.Do(func() error {
|
||||
return insertHandlerInternal(req)
|
||||
})
|
||||
}
|
||||
|
||||
func insertHandlerInternal(req *http.Request) error {
|
||||
readCalls.Inc()
|
||||
|
||||
r := req.Body
|
||||
if req.Header.Get("Content-Encoding") == "gzip" {
|
||||
zr, err := common.GetGzipReader(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot read gzipped vmimport data: %s", err)
|
||||
}
|
||||
defer common.PutGzipReader(zr)
|
||||
r = zr
|
||||
}
|
||||
|
||||
ctx := getPushCtx()
|
||||
defer putPushCtx(ctx)
|
||||
for ctx.Read(r) {
|
||||
if err := ctx.InsertRows(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return ctx.Error()
|
||||
}
|
||||
|
||||
func (ctx *pushCtx) InsertRows() error {
|
||||
rows := ctx.Rows.Rows
|
||||
rowsLen := 0
|
||||
for i := range rows {
|
||||
rowsLen += len(rows[i].Values)
|
||||
}
|
||||
ic := &ctx.Common
|
||||
ic.Reset(rowsLen)
|
||||
rowsTotal := 0
|
||||
for i := range rows {
|
||||
r := &rows[i]
|
||||
ic.Labels = ic.Labels[:0]
|
||||
for j := range r.Tags {
|
||||
tag := &r.Tags[j]
|
||||
ic.AddLabelBytes(tag.Key, tag.Value)
|
||||
}
|
||||
ctx.metricNameBuf = storage.MarshalMetricNameRaw(ctx.metricNameBuf[:0], ic.Labels)
|
||||
values := r.Values
|
||||
timestamps := r.Timestamps
|
||||
_ = timestamps[len(values)-1]
|
||||
for j, value := range values {
|
||||
timestamp := timestamps[j]
|
||||
ic.WriteDataPoint(ctx.metricNameBuf, nil, timestamp, value)
|
||||
}
|
||||
rowsTotal += len(values)
|
||||
}
|
||||
rowsInserted.Add(rowsTotal)
|
||||
rowsPerInsert.Update(float64(rowsTotal))
|
||||
return ic.FlushBufs()
|
||||
}
|
||||
|
||||
func (ctx *pushCtx) Read(r io.Reader) bool {
|
||||
if ctx.err != nil {
|
||||
return false
|
||||
}
|
||||
ctx.reqBuf, ctx.tailBuf, ctx.err = common.ReadLinesBlockExt(r, ctx.reqBuf, ctx.tailBuf, *maxLineLen)
|
||||
if ctx.err != nil {
|
||||
if ctx.err != io.EOF {
|
||||
readErrors.Inc()
|
||||
ctx.err = fmt.Errorf("cannot read vmimport data: %s", ctx.err)
|
||||
}
|
||||
return false
|
||||
}
|
||||
ctx.Rows.Unmarshal(bytesutil.ToUnsafeString(ctx.reqBuf))
|
||||
return true
|
||||
}
|
||||
|
||||
var (
|
||||
readCalls = metrics.NewCounter(`vm_read_calls_total{name="vmimport"}`)
|
||||
readErrors = metrics.NewCounter(`vm_read_errors_total{name="vmimport"}`)
|
||||
)
|
||||
|
||||
type pushCtx struct {
|
||||
Rows Rows
|
||||
Common common.InsertCtx
|
||||
|
||||
reqBuf []byte
|
||||
tailBuf []byte
|
||||
metricNameBuf []byte
|
||||
|
||||
err error
|
||||
}
|
||||
|
||||
func (ctx *pushCtx) Error() error {
|
||||
if ctx.err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return ctx.err
|
||||
}
|
||||
|
||||
func (ctx *pushCtx) reset() {
|
||||
ctx.Rows.Reset()
|
||||
ctx.Common.Reset(0)
|
||||
|
||||
ctx.reqBuf = ctx.reqBuf[:0]
|
||||
ctx.tailBuf = ctx.tailBuf[:0]
|
||||
ctx.metricNameBuf = ctx.metricNameBuf[:0]
|
||||
|
||||
ctx.err = nil
|
||||
}
|
||||
|
||||
func getPushCtx() *pushCtx {
|
||||
select {
|
||||
case ctx := <-pushCtxPoolCh:
|
||||
return ctx
|
||||
default:
|
||||
if v := pushCtxPool.Get(); v != nil {
|
||||
return v.(*pushCtx)
|
||||
}
|
||||
return &pushCtx{}
|
||||
}
|
||||
}
|
||||
|
||||
func putPushCtx(ctx *pushCtx) {
|
||||
ctx.reset()
|
||||
select {
|
||||
case pushCtxPoolCh <- ctx:
|
||||
default:
|
||||
pushCtxPool.Put(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
var pushCtxPool sync.Pool
|
||||
var pushCtxPoolCh = make(chan *pushCtx, runtime.GOMAXPROCS(-1))
|
||||
@@ -6,32 +6,53 @@ vmrestore:
|
||||
vmrestore-prod:
|
||||
APP_NAME=vmrestore $(MAKE) app-via-docker
|
||||
|
||||
vmrestore-pure-prod:
|
||||
APP_NAME=vmrestore $(MAKE) app-via-docker-pure
|
||||
|
||||
vmrestore--arm-prod:
|
||||
APP_NAME=vmrestore $(MAKE) app-via-docker-arm
|
||||
|
||||
vmrestore-arm64-prod:
|
||||
APP_NAME=vmrestore $(MAKE) app-via-docker-arm64
|
||||
|
||||
vmrestore-ppc64le-prod:
|
||||
APP_NAME=vmrestore $(MAKE) app-via-docker-ppc64le
|
||||
|
||||
vmrestore-386-prod:
|
||||
APP_NAME=vmrestore $(MAKE) app-via-docker-386
|
||||
|
||||
package-vmrestore:
|
||||
APP_NAME=vmrestore $(MAKE) package-via-docker
|
||||
|
||||
package-vmrestore-pure:
|
||||
APP_NAME=vmrestore $(MAKE) package-via-docker-pure
|
||||
|
||||
package-vmrestore-arm:
|
||||
APP_NAME=vmrestore $(MAKE) package-via-docker-arm
|
||||
|
||||
package-vmrestore-arm64:
|
||||
APP_NAME=vmrestore $(MAKE) package-via-docker-arm64
|
||||
|
||||
package-vmrestore-ppc64le:
|
||||
APP_NAME=vmrestore $(MAKE) package-via-docker-ppc64le
|
||||
|
||||
package-vmrestore-386:
|
||||
APP_NAME=vmrestore $(MAKE) package-via-docker-386
|
||||
|
||||
publish-vmrestore:
|
||||
APP_NAME=vmrestore $(MAKE) publish-via-docker
|
||||
|
||||
vmrestore-pure:
|
||||
APP_NAME=vmrestore $(MAKE) app-local-pure
|
||||
|
||||
vmrestore-arm:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm GO111MODULE=on go build -mod=vendor -ldflags "$(GO_BUILDINFO)" -o bin/vmrestore-arm ./app/vmrestore
|
||||
|
||||
vmrestore-arm-prod:
|
||||
APP_NAME=vmrestore APP_SUFFIX='-arm' DOCKER_OPTS='--env CGO_ENABLED=0 --env GOARCH=arm' $(MAKE) app-via-docker
|
||||
|
||||
vmrestore-arm64:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 GO111MODULE=on go build -mod=vendor -ldflags "$(GO_BUILDINFO)" -o bin/vmrestore-arm64 ./app/vmrestore
|
||||
|
||||
vmrestore-arm64-prod:
|
||||
APP_NAME=vmrestore APP_SUFFIX='-arm64' DOCKER_OPTS='--env CGO_ENABLED=0 --env GOARCH=arm64' $(MAKE) app-via-docker
|
||||
vmrestore-ppc64le:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=ppc64le GO111MODULE=on go build -mod=vendor -ldflags "$(GO_BUILDINFO)" -o bin/vmrestore-ppc64le ./app/vmrestore
|
||||
|
||||
vmrestore-386:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=386 GO111MODULE=on go build -mod=vendor -ldflags "$(GO_BUILDINFO)" -o bin/vmrestore-386 ./app/vmrestore
|
||||
|
||||
vmrestore-386-prod:
|
||||
APP_NAME=vmrestore APP_SUFFIX='-386' DOCKER_OPTS='--env CGO_ENABLED=0 --env GOARCH=386' $(MAKE) app-via-docker
|
||||
|
||||
vmrestore-pure:
|
||||
APP_NAME=vmrestore $(MAKE) app-local-pure
|
||||
|
||||
vmrestore-pure-prod:
|
||||
APP_NAME=vmrestore APP_SUFFIX='-pure' DOCKER_OPTS='--env CGO_ENABLED=0' $(MAKE) app-via-docker
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
ARG certs_image
|
||||
FROM $certs_image AS certs
|
||||
FROM scratch
|
||||
COPY --from=local/certs:1.0.3 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
COPY bin/vmrestore-prod .
|
||||
EXPOSE 8428
|
||||
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
ARG src_binary
|
||||
COPY $src_binary ./vmrestore-prod
|
||||
ENTRYPOINT ["/vmrestore-prod"]
|
||||
|
||||
@@ -432,13 +432,10 @@ func GetLabelEntries(deadline Deadline) ([]storage.TagEntry, error) {
|
||||
// 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 len(a) > len(b)
|
||||
}
|
||||
if len(a) > len(b) {
|
||||
return false
|
||||
}
|
||||
return labelEntries[i].Key < labelEntries[j].Key
|
||||
return labelEntries[i].Key > labelEntries[j].Key
|
||||
})
|
||||
|
||||
return labelEntries, nil
|
||||
|
||||
@@ -23,9 +23,10 @@ import (
|
||||
var (
|
||||
latencyOffset = flag.Duration("search.latencyOffset", time.Second*30, "The time when data points become visible in query results after the colection. "+
|
||||
"Too small value can result in incomplete last points for query results")
|
||||
maxQueryDuration = flag.Duration("search.maxQueryDuration", time.Second*30, "The maximum time for search query execution")
|
||||
maxQueryLen = flag.Int("search.maxQueryLen", 16*1024, "The maximum search query length in bytes")
|
||||
maxLookback = flag.Duration("search.maxLookback", 0, "Synonim to `-search.lookback-delta` from Prometheus. "+
|
||||
maxExportDuration = flag.Duration("search.maxExportDuration", 10*time.Minute, "The maximum duration for `/api/v1/export` call")
|
||||
maxQueryDuration = flag.Duration("search.maxQueryDuration", time.Second*30, "The maximum duration for search query execution")
|
||||
maxQueryLen = flag.Int("search.maxQueryLen", 16*1024, "The maximum search query length in bytes")
|
||||
maxLookback = flag.Duration("search.maxLookback", 0, "Synonim to `-search.lookback-delta` from Prometheus. "+
|
||||
"The value is dynamically detected from interval between time series datapoints if not set. It can be overridden on per-query basis via `max_lookback` arg")
|
||||
)
|
||||
|
||||
@@ -58,7 +59,7 @@ func FederateHandler(w http.ResponseWriter, r *http.Request) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deadline := getDeadline(r)
|
||||
deadline := getDeadlineForQuery(r)
|
||||
if start >= end {
|
||||
start = end - defaultStep
|
||||
}
|
||||
@@ -129,7 +130,7 @@ func ExportHandler(w http.ResponseWriter, r *http.Request) error {
|
||||
return err
|
||||
}
|
||||
format := r.FormValue("format")
|
||||
deadline := getDeadline(r)
|
||||
deadline := getDeadlineForExport(r)
|
||||
if start >= end {
|
||||
end = start + defaultStep
|
||||
}
|
||||
@@ -235,7 +236,7 @@ var deleteDuration = metrics.NewSummary(`vm_request_duration_seconds{path="/api/
|
||||
// See https://prometheus.io/docs/prometheus/latest/querying/api/#querying-label-values
|
||||
func LabelValuesHandler(labelName string, w http.ResponseWriter, r *http.Request) error {
|
||||
startTime := time.Now()
|
||||
deadline := getDeadline(r)
|
||||
deadline := getDeadlineForQuery(r)
|
||||
|
||||
if err := r.ParseForm(); err != nil {
|
||||
return fmt.Errorf("cannot parse form values: %s", err)
|
||||
@@ -285,6 +286,13 @@ func labelValuesWithMatches(labelName string, matches []string, start, end int64
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i, tfs := range tagFilterss {
|
||||
// Add `labelName!=''` tag filter in order to filter out series without the labelName.
|
||||
tagFilterss[i] = append(tfs, storage.TagFilter{
|
||||
Key: []byte(labelName),
|
||||
IsNegative: true,
|
||||
})
|
||||
}
|
||||
if start >= end {
|
||||
end = start + defaultStep
|
||||
}
|
||||
@@ -326,7 +334,7 @@ var labelValuesDuration = metrics.NewSummary(`vm_request_duration_seconds{path="
|
||||
// LabelsCountHandler processes /api/v1/labels/count request.
|
||||
func LabelsCountHandler(w http.ResponseWriter, r *http.Request) error {
|
||||
startTime := time.Now()
|
||||
deadline := getDeadline(r)
|
||||
deadline := getDeadlineForQuery(r)
|
||||
labelEntries, err := netstorage.GetLabelEntries(deadline)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`cannot obtain label entries: %s`, err)
|
||||
@@ -345,10 +353,38 @@ var labelsCountDuration = metrics.NewSummary(`vm_request_duration_seconds{path="
|
||||
// See https://prometheus.io/docs/prometheus/latest/querying/api/#getting-label-names
|
||||
func LabelsHandler(w http.ResponseWriter, r *http.Request) error {
|
||||
startTime := time.Now()
|
||||
deadline := getDeadline(r)
|
||||
labels, err := netstorage.GetLabels(deadline)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot obtain labels: %s", err)
|
||||
deadline := getDeadlineForQuery(r)
|
||||
|
||||
if err := r.ParseForm(); err != nil {
|
||||
return fmt.Errorf("cannot parse form values: %s", err)
|
||||
}
|
||||
var labels []string
|
||||
if len(r.Form["match[]"]) == 0 && len(r.Form["start"]) == 0 && len(r.Form["end"]) == 0 {
|
||||
var err error
|
||||
labels, err = netstorage.GetLabels(deadline)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot obtain labels: %s", err)
|
||||
}
|
||||
} else {
|
||||
// Extended functionality that allows filtering by label filters and time range
|
||||
// i.e. /api/v1/labels?match[]=foobar{baz="abc"}&start=...&end=...
|
||||
matches := r.Form["match[]"]
|
||||
if len(matches) == 0 {
|
||||
matches = []string{"{__name__!=''}"}
|
||||
}
|
||||
ct := currentTime()
|
||||
end, err := getTime(r, "end", ct)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
start, err := getTime(r, "start", end-defaultStep)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
labels, err = labelsWithMatches(matches, start, end, deadline)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot obtain labels for match[]=%q, start=%d, end=%d: %s", matches, start, end, err)
|
||||
}
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
@@ -357,12 +393,57 @@ func LabelsHandler(w http.ResponseWriter, r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func labelsWithMatches(matches []string, start, end int64, deadline netstorage.Deadline) ([]string, error) {
|
||||
if len(matches) == 0 {
|
||||
logger.Panicf("BUG: matches must be non-empty")
|
||||
}
|
||||
tagFilterss, err := getTagFilterssFromMatches(matches)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if start >= end {
|
||||
end = start + defaultStep
|
||||
}
|
||||
sq := &storage.SearchQuery{
|
||||
MinTimestamp: start,
|
||||
MaxTimestamp: end,
|
||||
TagFilterss: tagFilterss,
|
||||
}
|
||||
rss, err := netstorage.ProcessSearchQuery(sq, false, deadline)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot fetch data for %q: %s", sq, err)
|
||||
}
|
||||
|
||||
m := make(map[string]struct{})
|
||||
var mLock sync.Mutex
|
||||
err = rss.RunParallel(func(rs *netstorage.Result, workerID uint) {
|
||||
mLock.Lock()
|
||||
tags := rs.MetricName.Tags
|
||||
for i := range tags {
|
||||
t := &tags[i]
|
||||
m[string(t.Key)] = struct{}{}
|
||||
}
|
||||
m["__name__"] = struct{}{}
|
||||
mLock.Unlock()
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error when data fetching: %s", err)
|
||||
}
|
||||
|
||||
labels := make([]string, 0, len(m))
|
||||
for label := range m {
|
||||
labels = append(labels, label)
|
||||
}
|
||||
sort.Strings(labels)
|
||||
return labels, nil
|
||||
}
|
||||
|
||||
var labelsDuration = metrics.NewSummary(`vm_request_duration_seconds{path="/api/v1/labels"}`)
|
||||
|
||||
// SeriesCountHandler processes /api/v1/series/count request.
|
||||
func SeriesCountHandler(w http.ResponseWriter, r *http.Request) error {
|
||||
startTime := time.Now()
|
||||
deadline := getDeadline(r)
|
||||
deadline := getDeadlineForQuery(r)
|
||||
n, err := netstorage.GetSeriesCount(deadline)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot obtain series count: %s", err)
|
||||
@@ -402,7 +483,7 @@ func SeriesHandler(w http.ResponseWriter, r *http.Request) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deadline := getDeadline(r)
|
||||
deadline := getDeadlineForQuery(r)
|
||||
|
||||
tagFilterss, err := getTagFilterssFromMatches(matches)
|
||||
if err != nil {
|
||||
@@ -471,7 +552,7 @@ func QueryHandler(w http.ResponseWriter, r *http.Request) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deadline := getDeadline(r)
|
||||
deadline := getDeadlineForQuery(r)
|
||||
lookbackDelta, err := getMaxLookback(r)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -486,21 +567,13 @@ func QueryHandler(w http.ResponseWriter, r *http.Request) error {
|
||||
start = ct - queryOffset
|
||||
}
|
||||
if childQuery, windowStr, offsetStr := promql.IsMetricSelectorWithRollup(query); childQuery != "" {
|
||||
var window int64
|
||||
if len(windowStr) > 0 {
|
||||
var err error
|
||||
window, err = promql.DurationValue(windowStr, step)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
window, err := parsePositiveDuration(windowStr, step)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse window: %s", err)
|
||||
}
|
||||
var offset int64
|
||||
if len(offsetStr) > 0 {
|
||||
var err error
|
||||
offset, err = promql.DurationValue(offsetStr, step)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
offset, err := parseDuration(offsetStr, step)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse offset: %s", err)
|
||||
}
|
||||
start -= offset
|
||||
end := start
|
||||
@@ -511,6 +584,31 @@ func QueryHandler(w http.ResponseWriter, r *http.Request) error {
|
||||
queryDuration.UpdateDuration(startTime)
|
||||
return nil
|
||||
}
|
||||
if childQuery, windowStr, stepStr, offsetStr := promql.IsRollup(query); childQuery != "" {
|
||||
newStep, err := parsePositiveDuration(stepStr, step)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse step: %s", err)
|
||||
}
|
||||
if newStep > 0 {
|
||||
step = newStep
|
||||
}
|
||||
window, err := parsePositiveDuration(windowStr, step)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse window: %s", err)
|
||||
}
|
||||
offset, err := parseDuration(offsetStr, step)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse offset: %s", err)
|
||||
}
|
||||
start -= offset
|
||||
end := start
|
||||
start = end - window
|
||||
if err := queryRangeHandler(w, childQuery, start, end, step, r, ct); err != nil {
|
||||
return err
|
||||
}
|
||||
queryDuration.UpdateDuration(startTime)
|
||||
return nil
|
||||
}
|
||||
|
||||
ec := promql.EvalConfig{
|
||||
Start: start,
|
||||
@@ -532,6 +630,20 @@ func QueryHandler(w http.ResponseWriter, r *http.Request) error {
|
||||
|
||||
var queryDuration = metrics.NewSummary(`vm_request_duration_seconds{path="/api/v1/query"}`)
|
||||
|
||||
func parseDuration(s string, step int64) (int64, error) {
|
||||
if len(s) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
return promql.DurationValue(s, step)
|
||||
}
|
||||
|
||||
func parsePositiveDuration(s string, step int64) (int64, error) {
|
||||
if len(s) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
return promql.PositiveDurationValue(s, step)
|
||||
}
|
||||
|
||||
// QueryRangeHandler processes /api/v1/query_range request.
|
||||
//
|
||||
// See https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries
|
||||
@@ -555,7 +667,15 @@ func QueryRangeHandler(w http.ResponseWriter, r *http.Request) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deadline := getDeadline(r)
|
||||
if err := queryRangeHandler(w, query, start, end, step, r, ct); err != nil {
|
||||
return err
|
||||
}
|
||||
queryRangeDuration.UpdateDuration(startTime)
|
||||
return nil
|
||||
}
|
||||
|
||||
func queryRangeHandler(w http.ResponseWriter, query string, start, end, step int64, r *http.Request, ct int64) error {
|
||||
deadline := getDeadlineForQuery(r)
|
||||
mayCache := !getBool(r, "nocache")
|
||||
lookbackDelta, err := getMaxLookback(r)
|
||||
if err != nil {
|
||||
@@ -599,7 +719,6 @@ func QueryRangeHandler(w http.ResponseWriter, r *http.Request) error {
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
WriteQueryRangeResponse(w, result)
|
||||
queryRangeDuration.UpdateDuration(startTime)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -748,12 +867,21 @@ func getMaxLookback(r *http.Request) (int64, error) {
|
||||
return getDuration(r, "max_lookback", d)
|
||||
}
|
||||
|
||||
func getDeadline(r *http.Request) netstorage.Deadline {
|
||||
func getDeadlineForQuery(r *http.Request) netstorage.Deadline {
|
||||
dMax := int64(maxQueryDuration.Seconds() * 1e3)
|
||||
return getDeadlineWithMaxDuration(r, dMax)
|
||||
}
|
||||
|
||||
func getDeadlineForExport(r *http.Request) netstorage.Deadline {
|
||||
dMax := int64(maxExportDuration.Seconds() * 1e3)
|
||||
return getDeadlineWithMaxDuration(r, dMax)
|
||||
}
|
||||
|
||||
func getDeadlineWithMaxDuration(r *http.Request, dMax int64) netstorage.Deadline {
|
||||
d, err := getDuration(r, "timeout", 0)
|
||||
if err != nil {
|
||||
d = 0
|
||||
}
|
||||
dMax := int64(maxQueryDuration.Seconds() * 1e3)
|
||||
if d <= 0 || d > dMax {
|
||||
d = dMax
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ var aggrFuncs = map[string]aggrFunc{
|
||||
"topk": newAggrFuncTopK(false),
|
||||
"quantile": aggrFuncQuantile,
|
||||
|
||||
// Extended PromQL funcs
|
||||
// PromQL extension funcs
|
||||
"median": aggrFuncMedian,
|
||||
"limitk": aggrFuncLimitK,
|
||||
"distinct": newAggrFunc(aggrFuncDistinct),
|
||||
|
||||
@@ -440,7 +440,7 @@ func evalRollupFuncWithSubquery(ec *EvalConfig, name string, rf rollupFunc, re *
|
||||
var step int64
|
||||
if len(re.Step) > 0 {
|
||||
var err error
|
||||
step, err = DurationValue(re.Step, ec.Step)
|
||||
step, err = PositiveDurationValue(re.Step, ec.Step)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -450,7 +450,7 @@ func evalRollupFuncWithSubquery(ec *EvalConfig, name string, rf rollupFunc, re *
|
||||
var window int64
|
||||
if len(re.Window) > 0 {
|
||||
var err error
|
||||
window, err = DurationValue(re.Window, ec.Step)
|
||||
window, err = PositiveDurationValue(re.Window, ec.Step)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -551,7 +551,7 @@ func evalRollupFuncWithMetricExpr(ec *EvalConfig, name string, rf rollupFunc, me
|
||||
var window int64
|
||||
if len(windowStr) > 0 {
|
||||
var err error
|
||||
window, err = DurationValue(windowStr, ec.Step)
|
||||
window, err = PositiveDurationValue(windowStr, ec.Step)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -198,6 +198,17 @@ func TestExecSuccess(t *testing.T) {
|
||||
resultExpected := []netstorage.Result{r}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run("time() offset -100s", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `time() offset -100s`
|
||||
r := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{1000, 1200, 1400, 1600, 1800, 2000},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
resultExpected := []netstorage.Result{r}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run("(a, b) offset 100s", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `sort((label_set(time(), "foo", "bar"), label_set(time()+10, "foo", "baz")) offset 100s)`
|
||||
@@ -270,6 +281,30 @@ func TestExecSuccess(t *testing.T) {
|
||||
resultExpected := []netstorage.Result{r1, r2}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run("(a offset -100s, b offset -50s) offset -400s", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `sort((label_set(time() offset -100s, "foo", "bar"), label_set(time()+10, "foo", "baz") offset -50s) offset -400s)`
|
||||
r1 := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{1260, 1460, 1660, 1860, 2060, 2260},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r1.MetricName.Tags = []storage.Tag{{
|
||||
Key: []byte("foo"),
|
||||
Value: []byte("baz"),
|
||||
}}
|
||||
r2 := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{1300, 1500, 1700, 1900, 2100, 2300},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r2.MetricName.Tags = []storage.Tag{{
|
||||
Key: []byte("foo"),
|
||||
Value: []byte("bar"),
|
||||
}}
|
||||
resultExpected := []netstorage.Result{r1, r2}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run("time()[:100s] offset 100s", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `time()[:100s] offset 100s`
|
||||
@@ -2289,6 +2324,35 @@ func TestExecSuccess(t *testing.T) {
|
||||
resultExpected := []netstorage.Result{r}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`histogram_quantile(single-value-valid-le, boundsLabel)`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `sort(histogram_quantile(0.6, label_set(100, "le", "200"), "foobar"))`
|
||||
r1 := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{0, 0, 0, 0, 0, 0},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r1.MetricName.Tags = []storage.Tag{{
|
||||
Key: []byte("foobar"),
|
||||
Value: []byte("lower"),
|
||||
}}
|
||||
r2 := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{120, 120, 120, 120, 120, 120},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r3 := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{200, 200, 200, 200, 200, 200},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r3.MetricName.Tags = []storage.Tag{{
|
||||
Key: []byte("foobar"),
|
||||
Value: []byte("upper"),
|
||||
}}
|
||||
resultExpected := []netstorage.Result{r1, r2, r3}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`histogram_quantile(single-value-valid-le-max-phi)`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `histogram_quantile(1, (
|
||||
@@ -2426,6 +2490,56 @@ func TestExecSuccess(t *testing.T) {
|
||||
resultExpected := []netstorage.Result{r}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`histogram_quantile(normal-bucket-count, boundsLabel)`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `sort(histogram_quantile(0.2,
|
||||
label_set(0, "foo", "bar", "le", "10")
|
||||
or label_set(100, "foo", "bar", "le", "30")
|
||||
or label_set(300, "foo", "bar", "le", "+Inf"),
|
||||
"xxx"
|
||||
))`
|
||||
r1 := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{10, 10, 10, 10, 10, 10},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r1.MetricName.Tags = []storage.Tag{
|
||||
{
|
||||
Key: []byte("foo"),
|
||||
Value: []byte("bar"),
|
||||
},
|
||||
{
|
||||
Key: []byte("xxx"),
|
||||
Value: []byte("lower"),
|
||||
},
|
||||
}
|
||||
r2 := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{22, 22, 22, 22, 22, 22},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r2.MetricName.Tags = []storage.Tag{{
|
||||
Key: []byte("foo"),
|
||||
Value: []byte("bar"),
|
||||
}}
|
||||
r3 := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{30, 30, 30, 30, 30, 30},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r3.MetricName.Tags = []storage.Tag{
|
||||
{
|
||||
Key: []byte("foo"),
|
||||
Value: []byte("bar"),
|
||||
},
|
||||
{
|
||||
Key: []byte("xxx"),
|
||||
Value: []byte("upper"),
|
||||
},
|
||||
}
|
||||
resultExpected := []netstorage.Result{r1, r2, r3}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`histogram_quantile(zero-bucket-count)`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `histogram_quantile(0.6,
|
||||
|
||||
@@ -105,7 +105,7 @@ again:
|
||||
token = s[:n]
|
||||
goto tokenFoundLabel
|
||||
}
|
||||
if n := scanDuration(s); n > 0 {
|
||||
if n := scanDuration(s, false); n > 0 {
|
||||
token = s[:n]
|
||||
goto tokenFoundLabel
|
||||
}
|
||||
@@ -368,15 +368,30 @@ func isPositiveNumberPrefix(s string) bool {
|
||||
return isDecimalChar(s[1])
|
||||
}
|
||||
|
||||
func isDuration(s string) bool {
|
||||
n := scanDuration(s)
|
||||
func isPositiveDuration(s string) bool {
|
||||
n := scanDuration(s, false)
|
||||
return n == len(s)
|
||||
}
|
||||
|
||||
// PositiveDurationValue returns the duration in milliseconds for the given s
|
||||
// and the given step.
|
||||
func PositiveDurationValue(s string, step int64) (int64, error) {
|
||||
d, err := DurationValue(s, step)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if d < 0 {
|
||||
return 0, fmt.Errorf("duration cannot be negative; got %q", s)
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// DurationValue returns the duration in milliseconds for the given s
|
||||
// and the given step.
|
||||
//
|
||||
// The returned duration value can be negative.
|
||||
func DurationValue(s string, step int64) (int64, error) {
|
||||
n := scanDuration(s)
|
||||
n := scanDuration(s, true)
|
||||
if n != len(s) {
|
||||
return 0, fmt.Errorf("cannot parse duration %q", s)
|
||||
}
|
||||
@@ -408,8 +423,14 @@ func DurationValue(s string, step int64) (int64, error) {
|
||||
return int64(mp * f * 1e3), nil
|
||||
}
|
||||
|
||||
func scanDuration(s string) int {
|
||||
func scanDuration(s string, canBeNegative bool) int {
|
||||
if len(s) == 0 {
|
||||
return -1
|
||||
}
|
||||
i := 0
|
||||
if s[0] == '-' && canBeNegative {
|
||||
i++
|
||||
}
|
||||
for i < len(s) && isDecimalChar(s[i]) {
|
||||
i++
|
||||
}
|
||||
|
||||
@@ -286,61 +286,116 @@ func testLexerError(t *testing.T, s string) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDurationSuccess(t *testing.T) {
|
||||
func TestPositiveDurationSuccess(t *testing.T) {
|
||||
f := func(s string, step, expectedD int64) {
|
||||
t.Helper()
|
||||
d, err := PositiveDurationValue(s, step)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
if d != expectedD {
|
||||
t.Fatalf("unexpected duration; got %d; want %d", d, expectedD)
|
||||
}
|
||||
}
|
||||
|
||||
// Integer durations
|
||||
testDurationSuccess(t, "123s", 42, 123*1000)
|
||||
testDurationSuccess(t, "123m", 42, 123*60*1000)
|
||||
testDurationSuccess(t, "1h", 42, 1*60*60*1000)
|
||||
testDurationSuccess(t, "2d", 42, 2*24*60*60*1000)
|
||||
testDurationSuccess(t, "3w", 42, 3*7*24*60*60*1000)
|
||||
testDurationSuccess(t, "4y", 42, 4*365*24*60*60*1000)
|
||||
testDurationSuccess(t, "1i", 42*1000, 42*1000)
|
||||
testDurationSuccess(t, "3i", 42, 3*42)
|
||||
f("123s", 42, 123*1000)
|
||||
f("123m", 42, 123*60*1000)
|
||||
f("1h", 42, 1*60*60*1000)
|
||||
f("2d", 42, 2*24*60*60*1000)
|
||||
f("3w", 42, 3*7*24*60*60*1000)
|
||||
f("4y", 42, 4*365*24*60*60*1000)
|
||||
f("1i", 42*1000, 42*1000)
|
||||
f("3i", 42, 3*42)
|
||||
|
||||
// Float durations
|
||||
testDurationSuccess(t, "0.234s", 42, 234)
|
||||
testDurationSuccess(t, "1.5s", 42, 1.5*1000)
|
||||
testDurationSuccess(t, "1.5m", 42, 1.5*60*1000)
|
||||
testDurationSuccess(t, "1.2h", 42, 1.2*60*60*1000)
|
||||
testDurationSuccess(t, "1.1d", 42, 1.1*24*60*60*1000)
|
||||
testDurationSuccess(t, "1.1w", 42, 1.1*7*24*60*60*1000)
|
||||
testDurationSuccess(t, "1.3y", 42, 1.3*365*24*60*60*1000)
|
||||
testDurationSuccess(t, "0.1i", 12340, 0.1*12340)
|
||||
f("0.234s", 42, 234)
|
||||
f("1.5s", 42, 1.5*1000)
|
||||
f("1.5m", 42, 1.5*60*1000)
|
||||
f("1.2h", 42, 1.2*60*60*1000)
|
||||
f("1.1d", 42, 1.1*24*60*60*1000)
|
||||
f("1.1w", 42, 1.1*7*24*60*60*1000)
|
||||
f("1.3y", 42, 1.3*365*24*60*60*1000)
|
||||
f("0.1i", 12340, 0.1*12340)
|
||||
}
|
||||
|
||||
func testDurationSuccess(t *testing.T, s string, step, expectedD int64) {
|
||||
t.Helper()
|
||||
d, err := DurationValue(s, step)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
func TestPositiveDurationError(t *testing.T) {
|
||||
f := func(s string) {
|
||||
t.Helper()
|
||||
if isPositiveDuration(s) {
|
||||
t.Fatalf("unexpected valid duration %q", s)
|
||||
}
|
||||
d, err := PositiveDurationValue(s, 42)
|
||||
if err == nil {
|
||||
t.Fatalf("expecting non-nil error for duration %q", s)
|
||||
}
|
||||
if d != 0 {
|
||||
t.Fatalf("expecting zero duration; got %d", d)
|
||||
}
|
||||
}
|
||||
if d != expectedD {
|
||||
t.Fatalf("unexpected duration; got %d; want %d", d, expectedD)
|
||||
f("")
|
||||
f("foo")
|
||||
f("m")
|
||||
f("12")
|
||||
f("1.23")
|
||||
f("1.23mm")
|
||||
f("123q")
|
||||
f("-123s")
|
||||
}
|
||||
|
||||
func TestDurationSuccess(t *testing.T) {
|
||||
f := func(s string, step, expectedD int64) {
|
||||
t.Helper()
|
||||
d, err := DurationValue(s, step)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
if d != expectedD {
|
||||
t.Fatalf("unexpected duration; got %d; want %d", d, expectedD)
|
||||
}
|
||||
}
|
||||
|
||||
// Integer durations
|
||||
f("123s", 42, 123*1000)
|
||||
f("-123s", 42, -123*1000)
|
||||
f("123m", 42, 123*60*1000)
|
||||
f("1h", 42, 1*60*60*1000)
|
||||
f("2d", 42, 2*24*60*60*1000)
|
||||
f("3w", 42, 3*7*24*60*60*1000)
|
||||
f("4y", 42, 4*365*24*60*60*1000)
|
||||
f("1i", 42*1000, 42*1000)
|
||||
f("3i", 42, 3*42)
|
||||
f("-3i", 42, -3*42)
|
||||
|
||||
// Float durations
|
||||
f("0.234s", 42, 234)
|
||||
f("-0.234s", 42, -234)
|
||||
f("1.5s", 42, 1.5*1000)
|
||||
f("1.5m", 42, 1.5*60*1000)
|
||||
f("1.2h", 42, 1.2*60*60*1000)
|
||||
f("1.1d", 42, 1.1*24*60*60*1000)
|
||||
f("1.1w", 42, 1.1*7*24*60*60*1000)
|
||||
f("1.3y", 42, 1.3*365*24*60*60*1000)
|
||||
f("-1.3y", 42, -1.3*365*24*60*60*1000)
|
||||
f("0.1i", 12340, 0.1*12340)
|
||||
}
|
||||
|
||||
func TestDurationError(t *testing.T) {
|
||||
testDurationError(t, "")
|
||||
testDurationError(t, "foo")
|
||||
testDurationError(t, "m")
|
||||
testDurationError(t, "12")
|
||||
testDurationError(t, "1.23")
|
||||
testDurationError(t, "1.23mm")
|
||||
testDurationError(t, "123q")
|
||||
}
|
||||
|
||||
func testDurationError(t *testing.T, s string) {
|
||||
t.Helper()
|
||||
|
||||
if isDuration(s) {
|
||||
t.Fatalf("unexpected valud duration %q", s)
|
||||
}
|
||||
|
||||
d, err := DurationValue(s, 42)
|
||||
if err == nil {
|
||||
t.Fatalf("expecting non-nil error for duration %q", s)
|
||||
}
|
||||
if d != 0 {
|
||||
t.Fatalf("expecting zero duration; got %d", d)
|
||||
}
|
||||
f := func(s string) {
|
||||
t.Helper()
|
||||
d, err := DurationValue(s, 42)
|
||||
if err == nil {
|
||||
t.Fatalf("expecting non-nil error for duration %q", s)
|
||||
}
|
||||
if d != 0 {
|
||||
t.Fatalf("expecting zero duration; got %d", d)
|
||||
}
|
||||
}
|
||||
f("")
|
||||
f("foo")
|
||||
f("m")
|
||||
f("12")
|
||||
f("1.23")
|
||||
f("1.23mm")
|
||||
f("123q")
|
||||
}
|
||||
|
||||
@@ -1173,7 +1173,7 @@ func (p *parser) parseWindowAndStep() (string, string, bool, error) {
|
||||
}
|
||||
var window string
|
||||
if !strings.HasPrefix(p.lex.Token, ":") {
|
||||
window, err = p.parseDuration()
|
||||
window, err = p.parsePositiveDuration()
|
||||
if err != nil {
|
||||
return "", "", false, err
|
||||
}
|
||||
@@ -1192,7 +1192,7 @@ func (p *parser) parseWindowAndStep() (string, string, bool, error) {
|
||||
}
|
||||
}
|
||||
if p.lex.Token != "]" {
|
||||
step, err = p.parseDuration()
|
||||
step, err = p.parsePositiveDuration()
|
||||
if err != nil {
|
||||
return "", "", false, err
|
||||
}
|
||||
@@ -1222,13 +1222,34 @@ func (p *parser) parseOffset() (string, error) {
|
||||
}
|
||||
|
||||
func (p *parser) parseDuration() (string, error) {
|
||||
if !isDuration(p.lex.Token) {
|
||||
isNegative := false
|
||||
if p.lex.Token == "-" {
|
||||
isNegative = true
|
||||
if err := p.lex.Next(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
if !isPositiveDuration(p.lex.Token) {
|
||||
return "", fmt.Errorf(`duration: unexpected token %q; want "duration"`, p.lex.Token)
|
||||
}
|
||||
d := p.lex.Token
|
||||
if err := p.lex.Next(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if isNegative {
|
||||
d = "-" + d
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (p *parser) parsePositiveDuration() (string, error) {
|
||||
d, err := p.parseDuration()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if strings.HasPrefix(d, "-") {
|
||||
return "", fmt.Errorf("positiveDuration: expecting positive duration; got %q", d)
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
@@ -1269,6 +1290,22 @@ func (p *parser) parseIdentExpr() (expr, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// IsRollup verifies whether s is a rollup with non-empty window.
|
||||
//
|
||||
// It returns the wrapped query with the corresponding window, step and offset.
|
||||
func IsRollup(s string) (childQuery string, window, step, offset string) {
|
||||
expr, err := parsePromQLWithCache(s)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
re, ok := expr.(*rollupExpr)
|
||||
if !ok || len(re.Window) == 0 {
|
||||
return
|
||||
}
|
||||
wrappedQuery := re.Expr.AppendString(nil)
|
||||
return string(wrappedQuery), re.Window, re.Step, re.Offset
|
||||
}
|
||||
|
||||
// IsMetricSelectorWithRollup verifies whether s contains PromQL metric selector
|
||||
// wrapped into rollup.
|
||||
//
|
||||
|
||||
@@ -77,15 +77,18 @@ func TestParsePromQLSuccess(t *testing.T) {
|
||||
same(`{}[5m:3s]`)
|
||||
another(`{}[ 5m : 3s ]`, `{}[5m:3s]`)
|
||||
same(`{} offset 5m`)
|
||||
same(`{} offset -5m`)
|
||||
same(`{}[5m] offset 10y`)
|
||||
same(`{}[5.3m:3.4s] offset 10y`)
|
||||
same(`{}[:3.4s] offset 10y`)
|
||||
same(`{}[:3.4s] offset -10y`)
|
||||
same(`{Foo="bAR"}`)
|
||||
same(`{foo="bar"}`)
|
||||
same(`{foo="bar"}[5m]`)
|
||||
same(`{foo="bar"}[5m:]`)
|
||||
same(`{foo="bar"}[5m:3s]`)
|
||||
same(`{foo="bar"} offset 10y`)
|
||||
same(`{foo="bar"} offset -10y`)
|
||||
same(`{foo="bar"}[5m] offset 10y`)
|
||||
same(`{foo="bar"}[5m:3s] offset 10y`)
|
||||
another(`{foo="bar"}[5m] oFFSEt 10y`, `{foo="bar"}[5m] offset 10y`)
|
||||
@@ -458,7 +461,6 @@ func TestParsePromQLError(t *testing.T) {
|
||||
|
||||
// invalid metricExpr
|
||||
f(`{__name__="ff"} offset 55`)
|
||||
f(`{__name__="ff"} offset -5m`)
|
||||
f(`foo[55]`)
|
||||
f(`m[-5m]`)
|
||||
f(`{`)
|
||||
@@ -491,6 +493,9 @@ func TestParsePromQLError(t *testing.T) {
|
||||
f(`m[5m:-`)
|
||||
f(`m[5m:-1`)
|
||||
f(`m[5m:-1]`)
|
||||
f(`m[5m:-1s]`)
|
||||
f(`m[-5m:1s]`)
|
||||
f(`m[-5m:-1s]`)
|
||||
f(`m[:`)
|
||||
f(`m[:-`)
|
||||
f(`m[:1]`)
|
||||
|
||||
@@ -400,17 +400,27 @@ func vmrangeBucketsToLE(tss []*timeseries) []*timeseries {
|
||||
|
||||
func transformHistogramQuantile(tfa *transformFuncArg) ([]*timeseries, error) {
|
||||
args := tfa.args
|
||||
if err := expectTransformArgsNum(args, 2); err != nil {
|
||||
return nil, err
|
||||
if len(args) < 2 || len(args) > 3 {
|
||||
return nil, fmt.Errorf("unexpected number of args; got %d; want 2...3", len(args))
|
||||
}
|
||||
phis, err := getScalar(args[0], 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("cannot parse phi: %s", err)
|
||||
}
|
||||
|
||||
// Convert buckets with `vmrange` labels to buckets with `le` labels.
|
||||
tss := vmrangeBucketsToLE(args[1])
|
||||
|
||||
// Parse boundsLabel. See https://github.com/prometheus/prometheus/issues/5706 for details.
|
||||
var boundsLabel string
|
||||
if len(args) > 2 {
|
||||
s, err := getString(args[2], 2)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse boundsLabel (arg #3): %s", err)
|
||||
}
|
||||
boundsLabel = s
|
||||
}
|
||||
|
||||
// Group metrics by all tags excluding "le"
|
||||
type x struct {
|
||||
le float64
|
||||
@@ -453,10 +463,10 @@ func transformHistogramQuantile(tfa *transformFuncArg) ([]*timeseries, error) {
|
||||
}
|
||||
return nan
|
||||
}
|
||||
quantile := func(i int, phis []float64, xss []x) float64 {
|
||||
quantile := func(i int, phis []float64, xss []x) (q, lower, upper float64) {
|
||||
phi := phis[i]
|
||||
if math.IsNaN(phi) {
|
||||
return nan
|
||||
return nan, nan, nan
|
||||
}
|
||||
// Fix broken buckets.
|
||||
// They are already sorted by le, so their values must be in ascending order,
|
||||
@@ -479,13 +489,13 @@ func transformHistogramQuantile(tfa *transformFuncArg) ([]*timeseries, error) {
|
||||
xss = xss[:len(xss)-1]
|
||||
}
|
||||
if vLast == 0 || math.IsNaN(vLast) {
|
||||
return nan
|
||||
return nan, nan, nan
|
||||
}
|
||||
if phi < 0 {
|
||||
return -inf
|
||||
return -inf, -inf, xss[0].ts.Values[i]
|
||||
}
|
||||
if phi > 1 {
|
||||
return inf
|
||||
return inf, vLast, inf
|
||||
}
|
||||
vReq := vLast * phi
|
||||
vPrev = 0
|
||||
@@ -509,14 +519,17 @@ func transformHistogramQuantile(tfa *transformFuncArg) ([]*timeseries, error) {
|
||||
continue
|
||||
}
|
||||
if math.IsInf(le, 0) {
|
||||
return lastNonInf(i, xss)
|
||||
vv := lastNonInf(i, xss)
|
||||
return vv, vv, inf
|
||||
}
|
||||
if v == vPrev {
|
||||
return lePrev
|
||||
return lePrev, lePrev, v
|
||||
}
|
||||
return lePrev + (le-lePrev)*(vReq-vPrev)/(v-vPrev)
|
||||
vv := lePrev + (le-lePrev)*(vReq-vPrev)/(v-vPrev)
|
||||
return vv, lePrev, le
|
||||
}
|
||||
return lastNonInf(i, xss)
|
||||
vv := lastNonInf(i, xss)
|
||||
return vv, vv, inf
|
||||
}
|
||||
rvs := make([]*timeseries, 0, len(m))
|
||||
for _, xss := range m {
|
||||
@@ -524,10 +537,30 @@ func transformHistogramQuantile(tfa *transformFuncArg) ([]*timeseries, error) {
|
||||
return xss[i].le < xss[j].le
|
||||
})
|
||||
dst := xss[0].ts
|
||||
var tsLower, tsUpper *timeseries
|
||||
if len(boundsLabel) > 0 {
|
||||
tsLower = ×eries{}
|
||||
tsLower.CopyFromShallowTimestamps(dst)
|
||||
tsLower.MetricName.RemoveTag(boundsLabel)
|
||||
tsLower.MetricName.AddTag(boundsLabel, "lower")
|
||||
tsUpper = ×eries{}
|
||||
tsUpper.CopyFromShallowTimestamps(dst)
|
||||
tsUpper.MetricName.RemoveTag(boundsLabel)
|
||||
tsUpper.MetricName.AddTag(boundsLabel, "upper")
|
||||
}
|
||||
for i := range dst.Values {
|
||||
dst.Values[i] = quantile(i, phis, xss)
|
||||
v, lower, upper := quantile(i, phis, xss)
|
||||
dst.Values[i] = v
|
||||
if len(boundsLabel) > 0 {
|
||||
tsLower.Values[i] = lower
|
||||
tsUpper.Values[i] = upper
|
||||
}
|
||||
}
|
||||
rvs = append(rvs, dst)
|
||||
if len(boundsLabel) > 0 {
|
||||
rvs = append(rvs, tsLower)
|
||||
rvs = append(rvs, tsUpper)
|
||||
}
|
||||
}
|
||||
return rvs, nil
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"type": "grafana",
|
||||
"id": "grafana",
|
||||
"name": "Grafana",
|
||||
"version": "6.4.4"
|
||||
"version": "6.5.0"
|
||||
},
|
||||
{
|
||||
"type": "panel",
|
||||
@@ -60,12 +60,12 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": "Overview for single node VictoriaMetrics v1.29.0 or higher",
|
||||
"description": "Overview for single node VictoriaMetrics v1.30.3 or higher",
|
||||
"editable": true,
|
||||
"gnetId": 10229,
|
||||
"graphTooltip": 0,
|
||||
"id": null,
|
||||
"iteration": 1573509727687,
|
||||
"iteration": 1575825261972,
|
||||
"links": [
|
||||
{
|
||||
"icon": "doc",
|
||||
@@ -499,6 +499,7 @@
|
||||
"x": 0,
|
||||
"y": 11
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 12,
|
||||
"legend": {
|
||||
"alignAsTable": true,
|
||||
@@ -592,6 +593,7 @@
|
||||
"x": 12,
|
||||
"y": 11
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 22,
|
||||
"legend": {
|
||||
"alignAsTable": true,
|
||||
@@ -685,6 +687,7 @@
|
||||
"x": 0,
|
||||
"y": 19
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 51,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
@@ -781,6 +784,7 @@
|
||||
"x": 12,
|
||||
"y": 19
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 33,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
@@ -885,6 +889,7 @@
|
||||
"x": 0,
|
||||
"y": 27
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 66,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
@@ -972,6 +977,7 @@
|
||||
"x": 12,
|
||||
"y": 27
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 35,
|
||||
"legend": {
|
||||
"alignAsTable": true,
|
||||
@@ -1065,6 +1071,7 @@
|
||||
"x": 0,
|
||||
"y": 35
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 60,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
@@ -1157,6 +1164,7 @@
|
||||
"x": 12,
|
||||
"y": 35
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 59,
|
||||
"legend": {
|
||||
"alignAsTable": true,
|
||||
@@ -1266,6 +1274,7 @@
|
||||
"x": 0,
|
||||
"y": 43
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 37,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
@@ -1358,6 +1367,7 @@
|
||||
"x": 12,
|
||||
"y": 43
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 49,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
@@ -1464,6 +1474,7 @@
|
||||
"x": 0,
|
||||
"y": 52
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 10,
|
||||
"legend": {
|
||||
"alignAsTable": true,
|
||||
@@ -1563,6 +1574,7 @@
|
||||
"x": 12,
|
||||
"y": 52
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 34,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
@@ -1668,6 +1680,7 @@
|
||||
"x": 0,
|
||||
"y": 60
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 30,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
@@ -1758,6 +1771,7 @@
|
||||
"x": 12,
|
||||
"y": 60
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 36,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
@@ -1848,6 +1862,7 @@
|
||||
"x": 0,
|
||||
"y": 68
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 53,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
@@ -1938,6 +1953,7 @@
|
||||
"x": 12,
|
||||
"y": 68
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 55,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
@@ -2027,6 +2043,7 @@
|
||||
"x": 0,
|
||||
"y": 76
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 62,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
@@ -2115,6 +2132,7 @@
|
||||
"x": 12,
|
||||
"y": 76
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 64,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
@@ -2203,6 +2221,7 @@
|
||||
"x": 0,
|
||||
"y": 84
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 58,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
@@ -2280,6 +2299,99 @@
|
||||
"alignLevel": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"description": "Shows the rate of logging the messages by their level. Unexpected spike in rate is a good reason to check logs.",
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 84
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 67,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
"current": false,
|
||||
"max": false,
|
||||
"min": false,
|
||||
"show": true,
|
||||
"total": false,
|
||||
"values": false
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"links": [],
|
||||
"nullPointMode": "null",
|
||||
"options": {
|
||||
"dataLinks": []
|
||||
},
|
||||
"percentage": false,
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"spaceLength": 10,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(rate(vm_log_messages_total{job=\"$job\"}[5m])) by (level) ",
|
||||
"format": "time_series",
|
||||
"hide": false,
|
||||
"intervalFactor": 1,
|
||||
"legendFormat": "{{level}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeRegions": [],
|
||||
"timeShift": null,
|
||||
"title": "Logging rate",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"buckets": null,
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"decimals": null,
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"yaxis": {
|
||||
"align": false,
|
||||
"alignLevel": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
@@ -2309,6 +2421,7 @@
|
||||
"x": 0,
|
||||
"y": 93
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 44,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
@@ -2415,6 +2528,7 @@
|
||||
"x": 12,
|
||||
"y": 93
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 57,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
@@ -2504,6 +2618,7 @@
|
||||
"x": 0,
|
||||
"y": 101
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 47,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
@@ -2594,6 +2709,7 @@
|
||||
"x": 12,
|
||||
"y": 101
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 42,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
@@ -2683,6 +2799,7 @@
|
||||
"x": 0,
|
||||
"y": 109
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 48,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
@@ -2761,7 +2878,7 @@
|
||||
}
|
||||
],
|
||||
"refresh": "30s",
|
||||
"schemaVersion": 20,
|
||||
"schemaVersion": 21,
|
||||
"style": "dark",
|
||||
"tags": [],
|
||||
"templating": {
|
||||
@@ -2844,5 +2961,5 @@
|
||||
"timezone": "",
|
||||
"title": "VictoriaMetrics",
|
||||
"uid": "wNf0q_kZk",
|
||||
"version": 4
|
||||
"version": 1
|
||||
}
|
||||
@@ -1,13 +1,15 @@
|
||||
DOCKER_NAMESPACE := victoriametrics
|
||||
BUILDER_IMAGE := local/builder:go1.13.4
|
||||
# All these commands must run from repository root.
|
||||
|
||||
DOCKER_NAMESPACE := docker.io/victoriametrics
|
||||
BUILDER_IMAGE := local/builder:go1.13.5
|
||||
CERTS_IMAGE := local/certs:1.0.3
|
||||
|
||||
package-certs:
|
||||
(docker image ls --format '{{.Repository}}:{{.Tag}}' | grep -q '$(CERTS_IMAGE)') \
|
||||
(docker image ls --format '{{.Repository}}:{{.Tag}}' | grep -q '$(CERTS_IMAGE)$$') \
|
||||
|| docker build -t $(CERTS_IMAGE) deployment/docker/certs
|
||||
|
||||
package-builder:
|
||||
(docker image ls --format '{{.Repository}}:{{.Tag}}' | grep -q '$(BUILDER_IMAGE)') \
|
||||
(docker image ls --format '{{.Repository}}:{{.Tag}}' | grep -q '$(BUILDER_IMAGE)$$') \
|
||||
|| docker build -t $(BUILDER_IMAGE) deployment/docker/builder
|
||||
|
||||
app-via-docker: package-certs package-builder
|
||||
@@ -25,21 +27,106 @@ app-via-docker: package-certs package-builder
|
||||
-o bin/$(APP_NAME)$(APP_SUFFIX)-prod $(PKG_PREFIX)/app/$(APP_NAME)
|
||||
|
||||
package-via-docker:
|
||||
(docker image ls --format '{{.Repository}}:{{.Tag}}' | grep -q '$(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)$(RACE)') || (\
|
||||
(docker image ls --format '{{.Repository}}:{{.Tag}}' | grep -q '$(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)$(APP_SUFFIX)$(RACE)$$') || (\
|
||||
$(MAKE) app-via-docker && \
|
||||
docker build -t $(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)$(RACE) -f app/$(APP_NAME)/deployment/Dockerfile .)
|
||||
docker build \
|
||||
--build-arg src_binary=$(APP_NAME)$(APP_SUFFIX)-prod \
|
||||
--build-arg certs_image=$(CERTS_IMAGE) \
|
||||
-t $(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)$(APP_SUFFIX)$(RACE) \
|
||||
-f app/$(APP_NAME)/deployment/Dockerfile bin)
|
||||
|
||||
publish-via-docker: package-via-docker
|
||||
docker push $(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)$(RACE)
|
||||
docker tag $(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)$(RACE) $(DOCKER_NAMESPACE)/$(APP_NAME):latest
|
||||
docker push $(DOCKER_NAMESPACE)/$(APP_NAME):latest
|
||||
package-manifest: \
|
||||
package-via-docker-amd64 \
|
||||
package-via-docker-arm \
|
||||
package-via-docker-arm64 \
|
||||
package-via-docker-ppc64le \
|
||||
package-via-docker-386
|
||||
$(MAKE) package-manifest-internal
|
||||
|
||||
package-manifest-internal:
|
||||
docker push $(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)-amd64$(RACE)
|
||||
docker push $(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)-arm$(RACE)
|
||||
docker push $(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)-arm64$(RACE)
|
||||
docker push $(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)-ppc64le$(RACE)
|
||||
docker push $(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)-386$(RACE)
|
||||
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create --amend $(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)$(RACE) \
|
||||
$(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)-amd64$(RACE) \
|
||||
$(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)-arm$(RACE) \
|
||||
$(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)-arm64$(RACE) \
|
||||
$(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)-ppc64le$(RACE) \
|
||||
$(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)-386$(RACE)
|
||||
GOARCH=amd64 $(MAKE) package-manifest-annotate-goarch
|
||||
GOARCH=arm $(MAKE) package-manifest-annotate-goarch
|
||||
GOARCH=arm64 $(MAKE) package-manifest-annotate-goarch
|
||||
GOARCH=ppc64le $(MAKE) package-manifest-annotate-goarch
|
||||
GOARCH=386 $(MAKE) package-manifest-annotate-goarch
|
||||
|
||||
package-manifest-annotate-goarch:
|
||||
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest annotate $(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)$(RACE) \
|
||||
$(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)-$(GOARCH)$(RACE) --os linux --arch $(GOARCH)
|
||||
|
||||
publish-via-docker: package-manifest
|
||||
docker tag $(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)-amd64$(RACE) $(DOCKER_NAMESPACE)/$(APP_NAME):latest-amd64$(RACE)
|
||||
docker tag $(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)-arm$(RACE) $(DOCKER_NAMESPACE)/$(APP_NAME):latest-arm$(RACE)
|
||||
docker tag $(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)-arm64$(RACE) $(DOCKER_NAMESPACE)/$(APP_NAME):latest-arm64$(RACE)
|
||||
docker tag $(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)-ppc64le$(RACE) $(DOCKER_NAMESPACE)/$(APP_NAME):latest-ppc64le$(RACE)
|
||||
docker tag $(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)-386$(RACE) $(DOCKER_NAMESPACE)/$(APP_NAME):latest-386$(RACE)
|
||||
PKG_TAG=latest $(MAKE) package-manifest-internal
|
||||
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push --purge $(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)$(RACE)
|
||||
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push --purge $(DOCKER_NAMESPACE)/$(APP_NAME):latest$(RACE)
|
||||
|
||||
run-via-docker: package-via-docker
|
||||
docker run -it --rm \
|
||||
--user $(shell id -u):$(shell id -g) \
|
||||
--net host \
|
||||
$(DOCKER_OPTS) \
|
||||
$(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)$(RACE) $(ARGS)
|
||||
$(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)$(APP_SUFFIX)$(RACE) $(ARGS)
|
||||
|
||||
app-via-docker-goarch:
|
||||
APP_SUFFIX='-$(GOARCH)' \
|
||||
DOCKER_OPTS='--env CGO_ENABLED=0 --env GOOS=linux --env GOARCH=$(GOARCH)' \
|
||||
$(MAKE) app-via-docker
|
||||
|
||||
app-via-docker-pure:
|
||||
APP_SUFFIX='-pure' DOCKER_OPTS='--env CGO_ENABLED=0' $(MAKE) app-via-docker
|
||||
|
||||
app-via-docker-amd64:
|
||||
GOARCH=amd64 $(MAKE) app-via-docker-goarch
|
||||
|
||||
app-via-docker-arm:
|
||||
GOARCH=arm $(MAKE) app-via-docker-goarch
|
||||
|
||||
app-via-docker-arm64:
|
||||
GOARCH=arm64 $(MAKE) app-via-docker-goarch
|
||||
|
||||
app-via-docker-ppc64le:
|
||||
GOARCH=ppc64le $(MAKE) app-via-docker-goarch
|
||||
|
||||
app-via-docker-386:
|
||||
GOARCH=386 $(MAKE) app-via-docker-goarch
|
||||
|
||||
package-via-docker-goarch:
|
||||
APP_SUFFIX='-$(GOARCH)' \
|
||||
DOCKER_OPTS='--env CGO_ENABLED=0 --env GOOS=linux --env GOARCH=$(GOARCH)' \
|
||||
$(MAKE) package-via-docker
|
||||
|
||||
package-via-docker-pure:
|
||||
APP_SUFFIX='-pure' DOCKER_OPTS='--env CGO_ENABLED=0' $(MAKE) package-via-docker
|
||||
|
||||
package-via-docker-amd64:
|
||||
GOARCH=amd64 $(MAKE) package-via-docker-goarch
|
||||
|
||||
package-via-docker-arm:
|
||||
GOARCH=arm $(MAKE) package-via-docker-goarch
|
||||
|
||||
package-via-docker-arm64:
|
||||
GOARCH=arm64 $(MAKE) package-via-docker-goarch
|
||||
|
||||
package-via-docker-ppc64le:
|
||||
GOARCH=ppc64le $(MAKE) package-via-docker-goarch
|
||||
|
||||
package-via-docker-386:
|
||||
GOARCH=386 $(MAKE) package-via-docker-goarch
|
||||
|
||||
remove-docker-images:
|
||||
docker image ls --format '{{.Repository}}\t{{.ID}}' | grep $(DOCKER_NAMESPACE)/ | grep -v /builder | awk '{print $$2}' | xargs docker image rm -f
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
FROM golang:1.13.4
|
||||
FROM golang:1.13.5
|
||||
STOPSIGNAL SIGINT
|
||||
|
||||
64
docs/CaseStudies.md
Normal file
64
docs/CaseStudies.md
Normal file
@@ -0,0 +1,64 @@
|
||||
## Case studies
|
||||
|
||||
Below are approved public case studies from VictoriaMetrics users. Join our [community Slack channel](http://slack.victoriametrics.com/)
|
||||
and feel free asking for references, reviews and additional case studies from real VictoriaMetrics users there.
|
||||
|
||||
|
||||
### Wix.com
|
||||
|
||||
[Wix.com](https://en.wikipedia.org/wiki/Wix.com) is the leading web development platform.
|
||||
|
||||
> We needed to redesign metric infrastructure from the ground up after the move to Kubernethes. A few approaches/designs have been tried before the one that works great has been chosen: Prometheus instance in every datacenter with 2 hours retention for local storage and remote write into [HA pair of single-node VictoriaMetrics instances](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/README.md#high-availability).
|
||||
|
||||
Numbers:
|
||||
|
||||
* The number of active time series per VictoriaMetrics instance is 20M.
|
||||
* The total number of time series per VictoriaMetrics instance is 400M+.
|
||||
* Ingestion rate per VictoriaMetrics instance is 800K data points per second.
|
||||
* The average time series churn rate is ~3M per day.
|
||||
* The average query rate is ~1K per minute (mostly alert queries).
|
||||
* Query duration: median is ~70ms, 99th percentile is ~2sec.
|
||||
* Retention: 6 months.
|
||||
|
||||
> Alternatives that we’ve played with before choosing VictoriaMetrics are: federated Prometheus, Cortex, IronDB and Thanos.
|
||||
> Points that were critical to us when we were choosing a central tsdb, in order of importance:
|
||||
|
||||
* At least 3 month worth of history.
|
||||
* Raw data, no aggregation, no sampling.
|
||||
* High query speed.
|
||||
* Clean fail state for HA (multi-node clusters may return partial data resulting in false alerts).
|
||||
* Enough head room/scaling capacity for future growth, up to 100M active time series.
|
||||
* Ability to split DB replicas per workload. Alert queries go to one replica, user queries go to another (speed for users, effective cache).
|
||||
|
||||
> Optimizing for those points and our specific workload VictoriaMetrics proved to be the best option. As an icing on a cake we’ve got [PromQL extensions](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/ExtendedPromQL) - `default 0` and `histogram` are my favorite ones, for example. What we specially like is having a lot of tsdb params easily available via config options, that makes tsdb easy to tune for specific use case. Also worth noting is a great community in [Slack channel](http://slack.victoriametrics.com/) and of course maintainer support.
|
||||
|
||||
Alex Ulstein, Head of Monitoring, Wix.com
|
||||
|
||||
|
||||
### Wedos.com
|
||||
|
||||
> [Wedos](https://www.wedos.com/) is the Biggest Czech Hosting. We have our own private data center, that holds only our servers and technologies. The second data center, where the servers will be cooled in an oil bath, is being built. We started using [cluster VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/cluster/README.md) to store Prometheus metrics from all our infrastructure after receiving positive references from our friends who successfully use VictoriaMetrics.
|
||||
|
||||
Numbers:
|
||||
|
||||
* The number of acitve time series: 5M.
|
||||
* Ingestion rate: 170K data points per second.
|
||||
* Query duration: median is ~2ms, 99th percentile is ~50ms.
|
||||
|
||||
> We like configuration simplicity and zero maintenance for VictoriaMetrics - once installed and forgot about it. It works out of the box without any issues.
|
||||
|
||||
|
||||
### Dreamteam
|
||||
|
||||
[Dreamteam](https://dreamteam.gg/) successfully uses single-node VictoriaMetrics in multiple environments.
|
||||
|
||||
Numbers:
|
||||
|
||||
* Active time series: from 350K to 725K.
|
||||
* Total number of time series: from 100M to 320M.
|
||||
* Total number of datapoints: from 120 billions to 155 billions.
|
||||
* Retention: 3 months.
|
||||
|
||||
VictoriaMetrics in production environment runs on 2 M5 EC2 instances in "HA" mode, managed by Terraform and Ansible TF module.
|
||||
2 Prometheus instances are writing to both VMs, with 2 [Promxy](https://github.com/jacksontj/promxy) replicas
|
||||
as load balancer for reads.
|
||||
@@ -27,6 +27,9 @@ VictoriaMetrics cluster consists of the following services:
|
||||
- `vmselect` - performs incoming queries using the data from `vmstorage`
|
||||
|
||||
Each service may scale independently and may run on the most suitable hardware.
|
||||
`vmstorage` nodes don't know about each other, don't communicate with each other and don't share any data.
|
||||
This is [shared nothing architecture](https://en.wikipedia.org/wiki/Shared-nothing_architecture).
|
||||
It increases cluster availability, simplifies cluster maintenance and cluster scaling.
|
||||
|
||||
<img src="https://docs.google.com/drawings/d/e/2PACX-1vTvk2raU9kFgZ84oF-OKolrGwHaePhHRsZEcfQ1I_EC5AB_XPWwB392XshxPramLJ8E4bqptTnFn5LL/pub?w=1104&h=746">
|
||||
|
||||
@@ -130,6 +133,7 @@ with [the official Grafana dashboard for VictoriaMetrics cluster](https://grafan
|
||||
- `<suffix>` may have the following values:
|
||||
- `prometheus` - for inserting data with [Prometheus remote write API](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_write)
|
||||
- `influx/write` or `influx/api/v2/write` - for inserting data with [Influx line protocol](https://docs.influxdata.com/influxdb/v1.7/write_protocols/line_protocol_tutorial/)
|
||||
- `prometheus/api/v1/import` - for importing data obtained via `api/v1/export` on `vmselect` (see below).
|
||||
|
||||
* URLs for querying: `http://<vmselect>:8481/select/<accountID>/prometheus/<suffix>`, where:
|
||||
- `<accountID>` is an arbitrary number identifying data namespace for the query (aka tenant)
|
||||
|
||||
@@ -1,18 +1,35 @@
|
||||
# Extended PromQL
|
||||
# MetricsQL
|
||||
|
||||
VictoriaMetrics supports [standard PromQL](https://prometheus.io/docs/prometheus/latest/querying/basics/)
|
||||
including [subqueries](https://prometheus.io/blog/2019/01/28/subquery-support/).
|
||||
Additionally it supports useful extensions mentioned below.
|
||||
Try these extensions on [an editable Grafana dashboard](http://play-grafana.victoriametrics.com:3000/d/4ome8yJmz/node-exporter-on-victoriametrics-demo).
|
||||
VictoriaMetrics implements MetricsQL - query language inspired by [PromQL](https://prometheus.io/docs/prometheus/latest/querying/basics/).
|
||||
It is backwards compatible with PromQL, so Grafana dashboards backed by Prometheus datasource should work the same after switching from Prometheus to VictoriaMetrics.
|
||||
|
||||
The following functionality is implemented differently in MetricsQL comparing to PromQL in order to improve user experience:
|
||||
* MetricsQL takes into account the previous point before the window in square brackets for range functions such as `rate` and `increase`.
|
||||
It also doesn't extrapolate range function results. This addresses [this issue from Prometheus](https://github.com/prometheus/prometheus/issues/3746).
|
||||
* MetricsQL returns the expected non-empty responses for requests with `step` values smaller than scrape interval. This addresses [this issue from Grafana](https://github.com/grafana/grafana/issues/11451).
|
||||
* MetricsQL treats `scalar` type the same as `instant vector` without labels, since subtle difference between these types usually confuses users.
|
||||
See [the corresponding Prometheus docs](https://prometheus.io/docs/prometheus/latest/querying/basics/#expression-language-data-types) for details.
|
||||
|
||||
Other PromQL functionality should work the same in MetricsQL. [File an issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues)
|
||||
if you notice discrepancies between PromQL and MetricsQL results other than mentioned above.
|
||||
|
||||
MetricsQL provides additional functionality mentioned below, which is aimed towards solving practical cases.
|
||||
Feel free [filing a feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues) if you think MetricsQL misses certain useful functionality.
|
||||
|
||||
*Note that the functionality mentioned below doesn't work in PromQL, so it is impossible switching back to Prometheus after you start using it.*
|
||||
|
||||
This functionality can be tried at [an editable Grafana dashboard](http://play-grafana.victoriametrics.com:3000/d/4ome8yJmz/node-exporter-on-victoriametrics-demo).
|
||||
|
||||
- [`WITH` templates](https://play.victoriametrics.com/promql/expand-with-exprs). This feature simplifies writing and managing complex queries. Go to [`WITH` templates playground](https://victoriametrics.com/promql/expand-with-exprs) and try it.
|
||||
- Metric names and metric labels may contain escaped chars. For instance, `foo\-bar{baz\=aa="b"}` is valid expression. It returns time series with name `foo-bar` containing label `baz=aa` with value `b`. Additionally, `\xXX` escape sequence is supported, where `XX` is hexadecimal representation of escaped char.
|
||||
- `offset`, range duration and step value for range vector may refer to the current step aka `$__interval` value from Grafana.
|
||||
For instance, `rate(metric[10i] offset 5i)` would return per-second rate over a range covering 10 previous steps with the offset of 5 steps.
|
||||
- `offset` may be put anywere in the query. For instance, `sum(foo) offset 24h`.
|
||||
- `offset` may be negative. For example, `q offset -1h`.
|
||||
- `default` binary operator. `q1 default q2` substitutes `NaN` values from `q1` with the corresponding values from `q2`.
|
||||
- `histogram_quantile` accepts optional third arg - `boundsLabel`. In this case it returns `lower` and `upper` bounds for the estimated percentile. See [this issue for details](https://github.com/prometheus/prometheus/issues/5706).
|
||||
- `if` binary operator. `q1 if q2` removes values from `q1` for `NaN` values from `q2`.
|
||||
- `ifnot` binary operator. `q1 ifnot q2` removes values from `q1` for non-`NaN` values from `q2`.
|
||||
- `offset` may be put anywere in the query. For instance, `sum(foo) offset 24h`.
|
||||
- Trailing commas on all the lists are allowed - label filters, function args and with expressions. For instance, the following queries are valid: `m{foo="bar",}`, `f(a, b,)`, `WITH (x=y,) x`. This simplifies maintenance of multi-line queries.
|
||||
- String literals may be concatenated. This is useful with `WITH` templates: `WITH (commonPrefix="long_metric_prefix_") {__name__=commonPrefix+"suffix1"} / {__name__=commonPrefix+"suffix2"}`.
|
||||
- Range duration in functions such as [rate](https://prometheus.io/docs/prometheus/latest/querying/functions/#rate()) may be omitted. VictoriaMetrics automatically selects range duration depending on the current step used for building the graph. For instance, the following query is valid in VictoriaMetrics: `rate(node_network_receive_bytes_total)`.
|
||||
@@ -60,7 +77,7 @@ Try these extensions on [an editable Grafana dashboard](http://play-grafana.vict
|
||||
- `rand()`, `rand_normal()` and `rand_exponential()` functions - for generating pseudo-random series with even, normal and exponential distribution.
|
||||
- `increases_over_time(m[d])` and `decreases_over_time(m[d])` - returns the number of `m` increases or decreases over the given duration `d`.
|
||||
- `prometheus_buckets(q)` - converts [VictoriaMetrics histogram](https://godoc.org/github.com/VictoriaMetrics/metrics#Histogram) buckets to Prometheus buckets with `le` labels.
|
||||
- `histogram(q)` - calculates aggregate histogram over `q` time series for each point on the graph.
|
||||
- `histogram(q)` - calculates aggregate histogram over `q` time series for each point on the graph. See [this article](https://medium.com/@valyala/improving-histogram-usability-for-prometheus-and-grafana-bc7e5df0e350) for more details.
|
||||
- `topk_*` and `bottomk_*` aggregate functions, which return up to K time series. Note that the standard `topk` function may return more than K time series -
|
||||
see [this article](https://www.robustperception.io/graph-top-n-time-series-in-grafana) for details.
|
||||
- `topk_min(k, q)` - returns top K time series with the max minimums on the given time range
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
* [Quick start](Quick-Start)
|
||||
* [`WITH` templates playground](https://play.victoriametrics.com/promql/expand-with-exprs)
|
||||
* [Grafana playground](http://play-grafana.victoriametrics.com:3000/d/4ome8yJmz/node-exporter-on-victoriametrics-demo)
|
||||
* [Extended PromQL](ExtendedPromQL)
|
||||
* [MetricsQL](ExtendedPromQL)
|
||||
* [Single-node version](Single-server-VictoriaMetrics)
|
||||
* [FAQ](FAQ)
|
||||
* [Cluster version](Cluster-VictoriaMetrics)
|
||||
* [Articles](Articles)
|
||||
|
||||
* [Case Studies](CaseStudies)
|
||||
* [vmbackup](vmbackup)
|
||||
* [vmrestore](vmrestore)
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
# Single-node
|
||||
## VictoriaMetrics
|
||||
|
||||
VictoriaMetrics is fast, cost-effective and scalable time-series database. It can be used as 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/victoriametrics/victoria-metrics/) and
|
||||
in [source code](https://github.com/VictoriaMetrics/VictoriaMetrics).
|
||||
in [source code](https://github.com/VictoriaMetrics/VictoriaMetrics). Just download VictoriaMetrics and see [how to start it](#how-to-start-victoriametrics).
|
||||
|
||||
Cluster version is available [here](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/cluster).
|
||||
|
||||
|
||||
## Case studies
|
||||
|
||||
* [Wix.com](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/CaseStudies#wixcom)
|
||||
* [Wedos.com](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/CaseStudies#wedoscom)
|
||||
* [Dreamteam](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/CaseStudies#dreamteam)
|
||||
|
||||
|
||||
## Prominent features
|
||||
|
||||
* Supports [Prometheus querying API](https://prometheus.io/docs/prometheus/latest/querying/api/), so it can be used as Prometheus drop-in replacement in Grafana.
|
||||
Additionally, VictoriaMetrics extends PromQL with opt-in [useful features](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/ExtendedPromQL).
|
||||
VictoriaMetrics implements [MetricsQL](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/ExtendedPromQL) query language, which is inspired by PromQL.
|
||||
* Supports global query view. Multiple Prometheus instances may write data into VictoriaMetrics. Later this data may be used in a single query.
|
||||
* High performance and good scalability for both [inserts](https://medium.com/@valyala/high-cardinality-tsdb-benchmarks-victoriametrics-vs-timescaledb-vs-influxdb-13e6ee64dd6b)
|
||||
and [selects](https://medium.com/@valyala/when-size-matters-benchmarking-victoriametrics-vs-timescale-and-influxdb-6035811952d4).
|
||||
@@ -34,11 +41,12 @@ Cluster version is available [here](https://github.com/VictoriaMetrics/VictoriaM
|
||||
* Storage is protected from corruption on unclean shutdown (i.e. OOM, hardware reset or `kill -9`) thanks to [the storage architecture](https://medium.com/@valyala/how-victoriametrics-makes-instant-snapshots-for-multi-terabyte-time-series-data-e1f3fb0e0282).
|
||||
* Supports metrics' ingestion and [backfilling](#backfilling) via the following protocols:
|
||||
* [Prometheus remote write API](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_write)
|
||||
* [InfluxDB line protocol](https://docs.influxdata.com/influxdb/v1.7/write_protocols/line_protocol_tutorial/)
|
||||
* [Graphite plaintext protocol](https://graphite.readthedocs.io/en/latest/feeding-carbon.html) with [tags](https://graphite.readthedocs.io/en/latest/tags.html#carbon)
|
||||
* [InfluxDB line protocol](#how-to-send-data-from-influxdb-compatible-agents-such-as-telegraf)
|
||||
* [Graphite plaintext protocol](#how-to-send-data-from-graphite-compatible-agents-such-as-statsd) 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.
|
||||
* [HTTP OpenTSDB /api/put requests](http://opentsdb.net/docs/build/html/api_http/put.html) if `-opentsdbHTTPListenAddr` is set.
|
||||
* [OpenTSDB put message](#sending-data-via-telnet-put-protocol) if `-opentsdbListenAddr` is set.
|
||||
* [HTTP OpenTSDB /api/put requests](#sending-opentsdb-data-via-http-apiput-requests) if `-opentsdbHTTPListenAddr` is set.
|
||||
* [/api/v1/import](#how-to-import-time-series-data)
|
||||
* Ideally works with big amounts of time series data from Kubernetes, IoT sensors, connected cars, industrial telemetry, financial data and various Enterprise workloads.
|
||||
* Has open source [cluster version](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/cluster).
|
||||
|
||||
@@ -57,6 +65,7 @@ Cluster version is available [here](https://github.com/VictoriaMetrics/VictoriaM
|
||||
- [How to send data from Graphite-compatible agents such as StatsD?](#how-to-send-data-from-graphite-compatible-agents-such-as-statsd)
|
||||
- [Querying Graphite data](#querying-graphite-data)
|
||||
- [How to send data from OpenTSDB-compatible agents?](#how-to-send-data-from-opentsdb-compatible-agents)
|
||||
- [Prometheus querying API usage](#prometheus-querying-api-usage)
|
||||
- [How to build from sources](#how-to-build-from-sources)
|
||||
- [Development build](#development-build)
|
||||
- [Production build](#production-build)
|
||||
@@ -69,6 +78,7 @@ Cluster version is available [here](https://github.com/VictoriaMetrics/VictoriaM
|
||||
- [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)
|
||||
- [How to import time series data?](#how-to-import-time-series-data)
|
||||
- [Federation](#federation)
|
||||
- [Capacity planning](#capacity-planning)
|
||||
- [High availability](#high-availability)
|
||||
@@ -293,7 +303,7 @@ The `/api/v1/export` endpoint should return the following response:
|
||||
### Querying Graphite data
|
||||
|
||||
Data sent to VictoriaMetrics via `Graphite plaintext protocol` may be read either via
|
||||
[Prometheus querying API](https://prometheus.io/docs/prometheus/latest/querying/api/)
|
||||
[Prometheus querying API](#prometheus-querying-api-usage)
|
||||
or via [go-graphite/carbonapi](https://github.com/go-graphite/carbonapi/blob/master/cmd/carbonapi/carbonapi.example.prometheus.yaml).
|
||||
|
||||
|
||||
@@ -373,6 +383,31 @@ The `/api/v1/export` endpoint should return the following response:
|
||||
```
|
||||
|
||||
|
||||
### Prometheus querying API usage
|
||||
|
||||
VictoriaMetrics supports the following handlers from [Prometheus querying API](https://prometheus.io/docs/prometheus/latest/querying/api/):
|
||||
|
||||
* [/api/v1/query](https://prometheus.io/docs/prometheus/latest/querying/api/#instant-queries)
|
||||
* [/api/v1/query_range](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries)
|
||||
* [/api/v1/series](https://prometheus.io/docs/prometheus/latest/querying/api/#finding-series-by-label-matchers)
|
||||
* [/api/v1/labels](https://prometheus.io/docs/prometheus/latest/querying/api/#getting-label-names)
|
||||
* [/api/v1/label/.../values](https://prometheus.io/docs/prometheus/latest/querying/api/#querying-label-values)
|
||||
|
||||
These handlers can be queried from Prometheus-compatible clients such as Grafana or curl.
|
||||
|
||||
VictoriaMetrics accepts additional args for `/api/v1/labels` and `/api/v1/label/.../values` handlers.
|
||||
See [this feature request](https://github.com/prometheus/prometheus/issues/6178) for details:
|
||||
|
||||
* Any number [time series selectors](https://prometheus.io/docs/prometheus/latest/querying/basics/#time-series-selectors) via `match[]` query arg.
|
||||
* Optional `start` and `end` query args for limiting the time range for the selected labels or label values.
|
||||
|
||||
Additionally VictoriaMetrics provides the following handlers:
|
||||
|
||||
* `/api/v1/series/count` - it returns the total number of time series in the database. Note that this handler scans all the inverted index,
|
||||
so it can be slow if the database contains tens of millions of time series.
|
||||
* `/api/v1/labels/count` - it returns a list of `label: values_count` entries. It can be used for determining labels with the maximum number of values.
|
||||
|
||||
|
||||
### How to build from sources
|
||||
|
||||
We recommend using either [binary releases](https://github.com/VictoriaMetrics/VictoriaMetrics/releases) or
|
||||
@@ -487,7 +522,8 @@ before actually deleting the metrics.
|
||||
|
||||
Send a request to `http://<victoriametrics-addr>:8428/api/v1/export?match[]=<timeseries_selector_for_export>`,
|
||||
where `<timeseries_selector_for_export>` may contain any [time series selector](https://prometheus.io/docs/prometheus/latest/querying/basics/#time-series-selectors)
|
||||
for metrics to export. The response would contain all the data for the selected time series in [JSON streaming format](https://en.wikipedia.org/wiki/JSON_streaming#Line-delimited_JSON).
|
||||
for metrics to export. Use `{__name__!=""}` selector for fetching all the time series.
|
||||
The response would contain all the data for the selected time series in [JSON streaming format](https://en.wikipedia.org/wiki/JSON_streaming#Line-delimited_JSON).
|
||||
Each JSON line would contain data for a single time series. An example output:
|
||||
|
||||
```
|
||||
@@ -498,6 +534,33 @@ Each JSON line would contain data for a single time series. An example output:
|
||||
Optional `start` and `end` args may be added to the request in order to limit the time frame for the exported data. These args may contain either
|
||||
unix timestamp in seconds or [RFC3339](https://www.ietf.org/rfc/rfc3339.txt) values.
|
||||
|
||||
Exported data can be imported via POST'ing it to [/api/v1/import](#how-to-import-time-series-data).
|
||||
|
||||
|
||||
### How to import time series data?
|
||||
|
||||
Time series data can be imported via any supported ingestion protocol:
|
||||
|
||||
* [Prometheus remote_write API](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_write)
|
||||
* [Influx line protocol](#how-to-send-data-from-influxdb-compatible-agents-such-as-telegraf)
|
||||
* [Graphite plaintext protocol](#how-to-send-data-from-graphite-compatible-agents-such-as-statsd)
|
||||
* [OpenTSDB telnet put protocol](#sending-data-via-telnet-put-protocol)
|
||||
* [OpenTSDB http /api/put](#sending-opentsdb-data-via-http-apiput-requests)
|
||||
* `/api/v1/import` http POST handler, which accepts data from [/api/v1/export](#how-to-export-time-series).
|
||||
|
||||
The most efficient protocol for importing data into VictoriaMetrics is `/api/v1/import`. Example for importing data obtained via `/api/v1/export`:
|
||||
|
||||
```
|
||||
# Export the data from <source-victoriametrics>:
|
||||
curl -s 'http://source-victoriametrics:8428/api/v1/export' -d 'match={__name__!=""}' > exported_data.jsonl
|
||||
|
||||
# Import the data to <destination-victoriametrics>:
|
||||
curl -X POST 'http://destination-victoriametrics:8428/api/v1/import' -T exported_data.jsonl
|
||||
```
|
||||
|
||||
Each request to `/api/v1/import` can load up to a single vCPU core on VictoriaMetrics. Import speed can be improved by splitting the original file into smaller parts
|
||||
and importing them concurrently. Note that the original file must be split on newlines.
|
||||
|
||||
|
||||
### Federation
|
||||
|
||||
@@ -661,7 +724,7 @@ mkfs.ext4 ... -O 64bit,huge_file,extent -T huge
|
||||
|
||||
VictoriaMetrics exports internal metrics in Prometheus format on the `/metrics` page.
|
||||
Add this page to Prometheus' scrape config in order to collect VictoriaMetrics metrics.
|
||||
There is [an official Grafana dashboard for single-node VictoriaMetrics](https://grafana.com/dashboards/10229).
|
||||
There are officials Grafana dashboards for [single-node VictoriaMetrics](https://grafana.com/dashboards/10229) and [clustered VictoriaMetrics](https://grafana.com/grafana/dashboards/11176).
|
||||
|
||||
The most interesting metrics are:
|
||||
|
||||
|
||||
181
docs/vmbackup.md
Normal file
181
docs/vmbackup.md
Normal file
@@ -0,0 +1,181 @@
|
||||
## vmbackup
|
||||
|
||||
`vmbackup` creates VictoriaMetrics data backups from [instant snapshots](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/README.md#how-to-work-with-snapshots).
|
||||
|
||||
Supported storage systems for backups:
|
||||
|
||||
* [GCS](https://cloud.google.com/storage/). Example: `gcs://<bucket>/<path/to/backup>`
|
||||
* [S3](https://aws.amazon.com/s3/). Example: `s3://<bucket>/<path/to/backup>`
|
||||
* Any S3-compatible storage such as [MinIO](https://github.com/minio/minio), [Ceph](https://docs.ceph.com/docs/mimic/radosgw/s3/) or [Swift](https://www.swiftstack.com/docs/admin/middleware/s3_middleware.html). See `-customS3Endpoint` command-line flag.
|
||||
* Local filesystem. Example: `fs://</absolute/path/to/backup>`
|
||||
|
||||
Incremental backups and full backups are supported. Incremental backups are created automatically if the destination path already contains data from the previous backup.
|
||||
Full backups can be sped up with `-origin` pointing to already existing backup on the same remote storage. In this case `vmbackup` makes server-side copy for the shared
|
||||
data between the existing backup and new backup. This saves time and costs on data transfer.
|
||||
|
||||
Backup process can be interrupted at any time. It is automatically resumed from the interruption point when restarting `vmbackup` with the same args.
|
||||
|
||||
Backed up data can be restored with [vmrestore](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmrestore/README.md).
|
||||
|
||||
See [this article](https://medium.com/@valyala/speeding-up-backups-for-big-time-series-databases-533c1a927883) for more details.
|
||||
|
||||
|
||||
### Use cases
|
||||
|
||||
#### Regular backups
|
||||
|
||||
Regular backup can be performed with the following command:
|
||||
|
||||
```
|
||||
vmbackup -storageDataPath=</path/to/victoria-metrics-data> -snapshotName=<local-snapshot> -dst=gcs://<bucket>/<path/to/new/backup>
|
||||
```
|
||||
|
||||
* `</path/to/victoria-metrics-data>` - path to VictoriaMetrics data pointed by `-storageDataPath` command-line flag in single-node VictoriaMetrics or in cluster `vmstorage`.
|
||||
There is no need to stop VictoriaMetrics for creating backups, since they are performed from immutable [instant snapshots](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/README.md#how-to-work-with-snapshots).
|
||||
* `<local-snapshot>` is the snapshot to backup. See [how to create instant snapshots](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/README.md#how-to-work-with-snapshots).
|
||||
* `<bucket>` is already existing name for [GCS bucket](https://cloud.google.com/storage/docs/creating-buckets).
|
||||
* `<path/to/new/backup>` is the destination path where new backup will be placed.
|
||||
|
||||
|
||||
#### Regular backups with server-side copy from existing backup
|
||||
|
||||
If the destination GCS bucket already contains the previous backup at `-origin` path, then new backup can be sped up
|
||||
with the following command:
|
||||
|
||||
```
|
||||
vmbackup -storageDataPath=</path/to/victoria-metrics-data> -snapshotName=<local-snapshot> -dst=gcs://<bucket>/<path/to/new/backup> -origin=gcs://<bucket>/<path/to/existing/backup>
|
||||
```
|
||||
|
||||
This saves time and network bandwidth costs by performing server-side copy for the shared data from the `-origin` to `-dst`.
|
||||
|
||||
|
||||
#### Incremental backups
|
||||
|
||||
Incremental backups are performed if `-dst` points to already existing backup. In this case only new data is uploaded to remote storage.
|
||||
This saves time and network bandwidth costs when working with big backups:
|
||||
|
||||
```
|
||||
vmbackup -storageDataPath=</path/to/victoria-metrics-data> -snapshotName=<local-snapshot> -dst=gcs://<bucket>/<path/to/existing/backup>
|
||||
```
|
||||
|
||||
|
||||
#### Smart backups
|
||||
|
||||
Smart backups mean storing full daily backups into `YYYYMMDD` folders and creating incremental hourly backup into `latest` folder:
|
||||
|
||||
* Run the following command every hour:
|
||||
|
||||
```
|
||||
vmbackup -snapshotName=<latest-snapshot> -dst=gcs://<bucket>/latest
|
||||
```
|
||||
|
||||
Where `<latest-snapshot>` is the latest [snapshot](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/README.md#how-to-work-with-snapshots).
|
||||
The command will upload only changed data to `gcs://<bucket>/latest`.
|
||||
|
||||
* Run the following command once a day:
|
||||
|
||||
```
|
||||
vmbackup -snapshotName=<daily-snapshot> -dst=gcs://<bucket>/<YYYYMMDD> -origin=gcs://<bucket>/latest
|
||||
```
|
||||
|
||||
Where `<daily-snapshot>` is the snapshot for the last day `<YYYYMMDD>`.
|
||||
|
||||
|
||||
This apporach saves network bandwidth costs on hourly backups (since they are incremental) and allows recovering data from either the last hour (`latest` backup)
|
||||
or from any day (`YYYYMMDD` backups). Note that hourly backup shouldn't run when creating daily backup.
|
||||
|
||||
Do not forget removing old snapshots and backups when they are no longer needed for saving storage costs.
|
||||
|
||||
|
||||
### How does it work?
|
||||
|
||||
The backup algorithm is the following:
|
||||
|
||||
1. Collect information about files in the `-snapshotName`, in the `-dst` and in the `-origin`.
|
||||
2. Determine files in `-dst`, which are missing in `-snapshotName`, and delete them. These are usually small files, which are already merged into bigger files in the snapshot.
|
||||
3. Determine files from `-snapshotName`, which are missing in `-dst`. These are usually small new files and bigger merged files.
|
||||
4. Determine files from step 3, which exist in the `-origin`, and perform server-side copy of these files from `-origin` to `-dst`.
|
||||
This are usually the biggest and the oldest files, which are shared between backups.
|
||||
5. Upload the remaining files from setp 3 from `-snapshotName` to `-dst`.
|
||||
|
||||
The algorithm splits source files into 100MB chunks in the backup. Each chunk is stored as a separate file in the backup.
|
||||
Such splitting minimizes the amounts of data to re-transfer after temporary errors.
|
||||
|
||||
`vmbackup` relies on [instant snapshot](https://medium.com/@valyala/how-victoriametrics-makes-instant-snapshots-for-multi-terabyte-time-series-data-e1f3fb0e0282) properties:
|
||||
|
||||
- All the files in the snapshot are immutable.
|
||||
- Old files are periodically merged into new files.
|
||||
- Smaller files have higher probability to be merged.
|
||||
- Consecutive snapshots share many identical files.
|
||||
|
||||
These properties allow performing fast and cheap incremental backups and server-side copying from `-origin` paths.
|
||||
See [this article](https://medium.com/@valyala/speeding-up-backups-for-big-time-series-databases-533c1a927883) for more details.
|
||||
`vmbackup` can work improperly or slowly when these properties are violated.
|
||||
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
* If the backup is slow, then try setting higher value for `-concurrency` flag. This will increase the number of concurrent workers that upload data to backup storage.
|
||||
* If `vmbackup` eats all the network bandwidth, then set `-maxBytesPerSecond` to the desired value.
|
||||
* If `vmbackup` has been interrupted due to temporary error, then just restart it with the same args. It will resume the backup process.
|
||||
|
||||
|
||||
### Advanced usage
|
||||
|
||||
Run `vmbackup -help` in order to see all the available options:
|
||||
|
||||
```
|
||||
-concurrency int
|
||||
The number of concurrent workers. Higher concurrency may reduce backup duration (default 10)
|
||||
-configFilePath string
|
||||
Path to file with S3 configs. Configs are loaded from default location if not set.
|
||||
See https://docs.aws.amazon.com/general/latest/gr/aws-security-credentials.html
|
||||
-configProfile string
|
||||
Profile name for S3 configs (default "default")
|
||||
-credsFilePath string
|
||||
Path to file with GCS or S3 credentials. Credentials are loaded from default locations if not set.
|
||||
See https://cloud.google.com/iam/docs/creating-managing-service-account-keys and https://docs.aws.amazon.com/general/latest/gr/aws-security-credentials.html
|
||||
-customS3Endpoint string
|
||||
Custom S3 endpoint for use with S3-compatible storages (e.g. MinIO). S3 is used if not set
|
||||
-dst string
|
||||
Where to put the backup on the remote storage. Example: gcs://bucket/path/to/backup/dir, s3://bucket/path/to/backup/dir or fs:///path/to/local/backup/dir
|
||||
-dst can point to the previous backup. In this case incremental backup is performed, i.e. only changed data is uploaded
|
||||
-loggerLevel string
|
||||
Minimum level of errors to log. Possible values: INFO, ERROR, FATAL, PANIC (default "INFO")
|
||||
-maxBytesPerSecond int
|
||||
The maximum upload speed. There is no limit if it is set to 0
|
||||
-memory.allowedPercent float
|
||||
Allowed percent of system memory VictoriaMetrics caches may occupy (default 60)
|
||||
-origin string
|
||||
Optional origin directory on the remote storage with old backup for server-side copying when performing full backup. This speeds up full backups
|
||||
-snapshotName string
|
||||
Name for the snapshot to backup. See https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/README.md#how-to-work-with-snapshots
|
||||
-storageDataPath string
|
||||
Path to VictoriaMetrics data. Must match -storageDataPath from VictoriaMetrics or vmstorage (default "victoria-metrics-data")
|
||||
-version
|
||||
Show VictoriaMetrics version
|
||||
```
|
||||
|
||||
|
||||
### How to build from sources
|
||||
|
||||
It is recommended using [binary releases](https://github.com/VictoriaMetrics/VictoriaMetrics/releases) - see `vmutils-*` archives there.
|
||||
|
||||
|
||||
#### Development build
|
||||
|
||||
1. [Install Go](https://golang.org/doc/install). The minimum supported version is Go 1.12.
|
||||
2. Run `make vmbackup` from the root folder of the repository.
|
||||
It builds `vmbackup` binary and puts it into the `bin` folder.
|
||||
|
||||
#### Production build
|
||||
|
||||
1. [Install docker](https://docs.docker.com/install/).
|
||||
2. Run `make vmbackup-prod` from the root folder of the repository.
|
||||
It builds `vmbackup-prod` binary and puts it into the `bin` folder.
|
||||
|
||||
#### Building docker images
|
||||
|
||||
Run `make package-vmbackup`. It builds `victoriametrics/vmbackup:<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-vmbackup`.
|
||||
86
docs/vmrestore.md
Normal file
86
docs/vmrestore.md
Normal file
@@ -0,0 +1,86 @@
|
||||
## vmrestore
|
||||
|
||||
`vmrestore` restores data from backups created by [vmbackup](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmbackup/README.md).
|
||||
VictoriaMetrics `v1.29.0` and newer versions must be used for working with the restored data.
|
||||
|
||||
Restore process can be interrupted at any time. It is automatically resumed from the inerruption point
|
||||
when restarting `vmrestore` with the same args.
|
||||
|
||||
|
||||
### Usage
|
||||
|
||||
VictoriaMetrics must be stopped during the restore process.
|
||||
|
||||
```
|
||||
vmrestore -src=gcs://<bucket>/<path/to/backup> -storageDataPath=<local/path/to/restore>
|
||||
|
||||
```
|
||||
|
||||
* `<bucket>` is [GCS bucket](https://cloud.google.com/storage/docs/creating-buckets) name.
|
||||
* `<path/to/backup>` is the path to backup made with [vmbackup](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmbackup/README.md) on GCS bucket.
|
||||
* `<local/path/to/restore>` is the path to folder where data will be restored. This folder must be passed
|
||||
to VictoriaMetrics in `-storageDataPath` command-line flag after the restore process is complete.
|
||||
|
||||
The original `-storageDataPath` directory may contain old files. They will be susbstituted by the files from backup.
|
||||
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
* If `vmrestore` eats all the network bandwidth, then set `-maxBytesPerSecond` to the desired value.
|
||||
* If `vmrestore` has been interrupted due to temporary error, then just restart it with the same args. It will resume the restore process.
|
||||
|
||||
|
||||
### Advanced usage
|
||||
|
||||
Run `vmrestore -help` in order to see all the available options:
|
||||
|
||||
```
|
||||
-concurrency int
|
||||
The number of concurrent workers. Higher concurrency may reduce restore duration (default 10)
|
||||
-configFilePath string
|
||||
Path to file with S3 configs. Configs are loaded from default location if not set.
|
||||
See https://docs.aws.amazon.com/general/latest/gr/aws-security-credentials.html
|
||||
-configProfile string
|
||||
Profile name for S3 configs (default "default")
|
||||
-credsFilePath string
|
||||
Path to file with GCS or S3 credentials. Credentials are loaded from default locations if not set.
|
||||
See https://cloud.google.com/iam/docs/creating-managing-service-account-keys and https://docs.aws.amazon.com/general/latest/gr/aws-security-credentials.html
|
||||
-customS3Endpoint string
|
||||
Custom S3 endpoint for use with S3-compatible storages (e.g. MinIO). S3 is used if not set
|
||||
-loggerLevel string
|
||||
Minimum level of errors to log. Possible values: INFO, ERROR, FATAL, PANIC (default "INFO")
|
||||
-maxBytesPerSecond int
|
||||
The maximum download speed. There is no limit if it is set to 0
|
||||
-memory.allowedPercent float
|
||||
Allowed percent of system memory VictoriaMetrics caches may occupy (default 60)
|
||||
-src string
|
||||
Source path with backup on the remote storage. Example: gcs://bucket/path/to/backup/dir, s3://bucket/path/to/backup/dir or fs:///path/to/local/backup/dir
|
||||
-storageDataPath string
|
||||
Destination path where backup must be restored. VictoriaMetrics must be stopped when restoring from backup. -storageDataPath dir can be non-empty. In this case only missing data is downloaded from backup (default "victoria-metrics-data")
|
||||
-version
|
||||
Show VictoriaMetrics version
|
||||
```
|
||||
|
||||
|
||||
### How to build from sources
|
||||
|
||||
It is recommended using [binary releases](https://github.com/VictoriaMetrics/VictoriaMetrics/releases) - see `vmutils-*` archives there.
|
||||
|
||||
|
||||
#### Development build
|
||||
|
||||
1. [Install Go](https://golang.org/doc/install). The minimum supported version is Go 1.12.
|
||||
2. Run `make vmrestore` from the root folder of the repository.
|
||||
It builds `vmrestore` binary and puts it into the `bin` folder.
|
||||
|
||||
#### Production build
|
||||
|
||||
1. [Install docker](https://docs.docker.com/install/).
|
||||
2. Run `make vmrestore-prod` from the root folder of the repository.
|
||||
It builds `vmrestore-prod` binary and puts it into the `bin` folder.
|
||||
|
||||
#### Building docker images
|
||||
|
||||
Run `make package-vmrestore`. It builds `victoriametrics/vmrestore:<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-vmrestore`.
|
||||
18
go.mod
18
go.mod
@@ -4,28 +4,28 @@ require (
|
||||
cloud.google.com/go v0.49.0 // indirect
|
||||
cloud.google.com/go/storage v1.4.0
|
||||
github.com/VictoriaMetrics/fastcache v1.5.4
|
||||
github.com/VictoriaMetrics/metrics v1.9.2
|
||||
github.com/aws/aws-sdk-go v1.25.43
|
||||
github.com/VictoriaMetrics/metrics v1.9.3
|
||||
github.com/aws/aws-sdk-go v1.26.0
|
||||
github.com/cespare/xxhash/v2 v2.1.1
|
||||
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 // indirect
|
||||
github.com/golang/snappy v0.0.1
|
||||
github.com/jstemmer/go-junit-report v0.9.1 // indirect
|
||||
github.com/klauspost/compress v1.9.2
|
||||
github.com/klauspost/compress v1.9.4
|
||||
github.com/valyala/fastjson v1.4.1
|
||||
github.com/valyala/fastrand v1.0.0
|
||||
github.com/valyala/gozstd v1.6.3
|
||||
github.com/valyala/histogram v1.0.1
|
||||
github.com/valyala/quicktemplate v1.4.1
|
||||
go.opencensus.io v0.22.2 // indirect
|
||||
golang.org/x/exp v0.0.0-20191127035308-9964a5a80460 // indirect
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587 // indirect
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f // indirect
|
||||
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20191122200657-5d9234df094c // indirect
|
||||
golang.org/x/sys v0.0.0-20191127021746-63cb32ae39b2
|
||||
golang.org/x/tools v0.0.0-20191127064951-724660f1afeb // indirect
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 // indirect
|
||||
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449
|
||||
golang.org/x/tools v0.0.0-20191212051200-825cb0626375 // indirect
|
||||
google.golang.org/api v0.14.0
|
||||
google.golang.org/appengine v1.6.5 // indirect
|
||||
google.golang.org/genproto v0.0.0-20191115221424-83cc0476cb11 // indirect
|
||||
google.golang.org/genproto v0.0.0-20191206224255-0243a4be9c8f // indirect
|
||||
google.golang.org/grpc v1.25.1 // indirect
|
||||
)
|
||||
|
||||
|
||||
36
go.sum
36
go.sum
@@ -22,12 +22,12 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/VictoriaMetrics/fastcache v1.5.4 h1:0BaXbRH01RycJk79OOBwMCXlNryko9z4yEf6RqbP+Xo=
|
||||
github.com/VictoriaMetrics/fastcache v1.5.4/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8=
|
||||
github.com/VictoriaMetrics/metrics v1.9.2 h1:+CNV5OOPe1PuHff3AsOa5cbN8qCWkWwZZA7eOmFjOwQ=
|
||||
github.com/VictoriaMetrics/metrics v1.9.2/go.mod h1:LU2j9qq7xqZYXz8tF3/RQnB2z2MbZms5TDiIg9/NHiQ=
|
||||
github.com/VictoriaMetrics/metrics v1.9.3 h1:+1kZnOIb8RY825Nb9q9yMrPcOYuPE2GrZWxUh59XnHI=
|
||||
github.com/VictoriaMetrics/metrics v1.9.3/go.mod h1:LU2j9qq7xqZYXz8tF3/RQnB2z2MbZms5TDiIg9/NHiQ=
|
||||
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8=
|
||||
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
|
||||
github.com/aws/aws-sdk-go v1.25.43 h1:R5YqHQFIulYVfgRySz9hvBRTWBjudISa+r0C8XQ1ufg=
|
||||
github.com/aws/aws-sdk-go v1.25.43/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.26.0 h1:uddjCeunG1nroOMjcMO57g/vfwY7jJ1zYSEGmdeCeEs=
|
||||
github.com/aws/aws-sdk-go v1.26.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
@@ -76,8 +76,8 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.9.2 h1:LfVyl+ZlLlLDeQ/d2AqfGIIH4qEDu0Ed2S5GyhCWIWY=
|
||||
github.com/klauspost/compress v1.9.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.9.4 h1:xhvAeUPQ2drNUhKtrGdTGNvV9nNafHMUkRyLkzxJoB4=
|
||||
github.com/klauspost/compress v1.9.4/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
@@ -121,8 +121,8 @@ golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxT
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136 h1:A1gGSx58LAGVHUUsOf7IiR0u8Xb6W51gRwfDBhkdcaw=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191127035308-9964a5a80460 h1:zNL062UG4d0GC48Bhm+lEI9lTOMsEHNL0WITb/cw7/s=
|
||||
golang.org/x/exp v0.0.0-20191127035308-9964a5a80460/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587 h1:5Uz0rkjCFu9BC9gCRN7EkwVvhNyQgGWb8KNJrPwBoHY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@@ -150,14 +150,14 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 h1:e6HwijUxhDe+hPNjZQQn9bA5PW3vNmnN64U2ZW759Lk=
|
||||
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191122200657-5d9234df094c h1:HjRaKPaiWks0f5tA6ELVF7ZfqSppfPwOEEAvsrKUTO4=
|
||||
golang.org/x/oauth2 v0.0.0-20191122200657-5d9234df094c/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 h1:pE8b58s1HRDMi8RDc79m0HISf9D4TzseP40cEA6IGfs=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -172,8 +172,8 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191127021746-63cb32ae39b2 h1:/J2nHFg1MTqaRLFO7M+J78ASNsJoz3r0cvHBPQ77fsE=
|
||||
golang.org/x/sys v0.0.0-20191127021746-63cb32ae39b2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 h1:gSbV7h1NRL2G1xTg/owz62CST1oJBmxy4QpMMregXVQ=
|
||||
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
@@ -197,8 +197,8 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191127064951-724660f1afeb h1:K4JMHRJSgd1q/yXZNrKKyneQJcLm1rn7JsokEs/xE9I=
|
||||
golang.org/x/tools v0.0.0-20191127064951-724660f1afeb/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191212051200-825cb0626375 h1:hrBCt+pkb1irxFjg/GeadSn24gLxi5/Z861I53OkQbc=
|
||||
golang.org/x/tools v0.0.0-20191212051200-825cb0626375/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
@@ -221,8 +221,8 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115221424-83cc0476cb11 h1:51D++eCgOHufw5VfDE9Uzqyyc+OyQIjb9hkYy9LN5Fk=
|
||||
google.golang.org/genproto v0.0.0-20191115221424-83cc0476cb11/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191206224255-0243a4be9c8f h1:naitw5DILWPQvG0oG04mR9jF8fmKpRdW3E3zzKA4D0Y=
|
||||
google.golang.org/genproto v0.0.0-20191206224255-0243a4be9c8f/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
|
||||
@@ -155,9 +155,6 @@ var metricsHandlerDuration = metrics.NewHistogram(`vm_http_request_duration_seco
|
||||
|
||||
func handlerWrapper(w http.ResponseWriter, r *http.Request, rh RequestHandler) {
|
||||
requestsTotal.Inc()
|
||||
if !checkAuth(w, r) {
|
||||
return
|
||||
}
|
||||
switch r.URL.Path {
|
||||
case "/health":
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
@@ -172,25 +169,36 @@ func handlerWrapper(w http.ResponseWriter, r *http.Request, rh RequestHandler) {
|
||||
}
|
||||
w.WriteHeader(status)
|
||||
return
|
||||
case "/metrics":
|
||||
startTime := time.Now()
|
||||
metricsRequests.Inc()
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
writePrometheusMetrics(w)
|
||||
metricsHandlerDuration.UpdateDuration(startTime)
|
||||
return
|
||||
case "/favicon.ico":
|
||||
faviconRequests.Inc()
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
case "/metrics":
|
||||
metricsRequests.Inc()
|
||||
if len(*metricsAuthKey) > 0 && r.FormValue("authKey") != *metricsAuthKey {
|
||||
http.Error(w, "The provided authKey doesn't match -metricsAuthKey", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
startTime := time.Now()
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
writePrometheusMetrics(w)
|
||||
metricsHandlerDuration.UpdateDuration(startTime)
|
||||
return
|
||||
default:
|
||||
if strings.HasPrefix(r.URL.Path, "/debug/pprof/") {
|
||||
pprofRequests.Inc()
|
||||
if len(*pprofAuthKey) > 0 && r.FormValue("authKey") != *pprofAuthKey {
|
||||
http.Error(w, "The provided authKey doesn't match -pprofAuthKey", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
DisableResponseCompression(w)
|
||||
pprofHandler(r.URL.Path[len("/debug/pprof/"):], w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if !checkBasicAuth(w, r) {
|
||||
return
|
||||
}
|
||||
if rh(w, r) {
|
||||
return
|
||||
}
|
||||
@@ -201,27 +209,6 @@ func handlerWrapper(w http.ResponseWriter, r *http.Request, rh RequestHandler) {
|
||||
}
|
||||
}
|
||||
|
||||
func checkAuth(w http.ResponseWriter, r *http.Request) bool {
|
||||
path := r.URL.Path
|
||||
if path == "/metrics" && len(*metricsAuthKey) > 0 {
|
||||
authKey := r.FormValue("authKey")
|
||||
if *metricsAuthKey == authKey {
|
||||
return true
|
||||
}
|
||||
http.Error(w, "The provided authKey doesn't match -metricsAuthKey", http.StatusUnauthorized)
|
||||
return false
|
||||
}
|
||||
if strings.HasPrefix(path, "/debug/pprof/") && len(*pprofAuthKey) > 0 {
|
||||
authKey := r.FormValue("authKey")
|
||||
if *pprofAuthKey == authKey {
|
||||
return true
|
||||
}
|
||||
http.Error(w, "The provided authKey doesn't match -pprofAuthKey", http.StatusUnauthorized)
|
||||
return false
|
||||
}
|
||||
return checkBasicAuth(w, r)
|
||||
}
|
||||
|
||||
func checkBasicAuth(w http.ResponseWriter, r *http.Request) bool {
|
||||
if len(*httpAuthUsername) == 0 {
|
||||
// HTTP Basic Auth is disabled.
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
@@ -16,7 +15,10 @@ import (
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
)
|
||||
|
||||
var loggerLevel = flag.String("loggerLevel", "INFO", "Minimum level of errors to log. Possible values: INFO, ERROR, FATAL, PANIC")
|
||||
var (
|
||||
loggerLevel = flag.String("loggerLevel", "INFO", "Minimum level of errors to log. Possible values: INFO, ERROR, FATAL, PANIC")
|
||||
loggerFormat = flag.String("loggerFormat", "default", "Format for logs. Possible values: default, json")
|
||||
)
|
||||
|
||||
// Init initializes the logger.
|
||||
//
|
||||
@@ -25,7 +27,9 @@ var loggerLevel = flag.String("loggerLevel", "INFO", "Minimum level of errors to
|
||||
// There is no need in calling Init from tests.
|
||||
func Init() {
|
||||
validateLoggerLevel()
|
||||
validateLoggerFormat()
|
||||
go errorsLoggedCleaner()
|
||||
go logMessageWriter()
|
||||
logAllFlags()
|
||||
}
|
||||
|
||||
@@ -38,6 +42,15 @@ func validateLoggerLevel() {
|
||||
}
|
||||
}
|
||||
|
||||
func validateLoggerFormat() {
|
||||
switch *loggerFormat {
|
||||
case "default", "json":
|
||||
default:
|
||||
// We cannot use logger.Pancif here, since the logger isn't initialized yet.
|
||||
panic(fmt.Errorf("FATAL: unsupported `-loggerFormat` value: %q; supported values are: default, json", *loggerFormat))
|
||||
}
|
||||
}
|
||||
|
||||
var stdErrorLogger = log.New(&logWriter{}, "", 0)
|
||||
|
||||
// StdErrorLogger returns standard error logger.
|
||||
@@ -101,7 +114,7 @@ func (lw *logWriter) Write(p []byte) (int, error) {
|
||||
}
|
||||
|
||||
func logMessage(level, msg string, skipframes int) {
|
||||
timestamp := time.Now().UTC().Format("2006-01-02T15:04:05.000+0000")
|
||||
timestamp := time.Now().UTC().Format("2006-01-02T15:04:05.000Z")
|
||||
levelLowercase := strings.ToLower(level)
|
||||
_, file, line, ok := runtime.Caller(skipframes)
|
||||
if !ok {
|
||||
@@ -115,12 +128,22 @@ func logMessage(level, msg string, skipframes int) {
|
||||
for len(msg) > 0 && msg[len(msg)-1] == '\n' {
|
||||
msg = msg[:len(msg)-1]
|
||||
}
|
||||
logMsg := fmt.Sprintf("%s\t%s\t%s:%d\t%s\n", timestamp, levelLowercase, file, line, msg)
|
||||
var logMsg string
|
||||
switch *loggerFormat {
|
||||
case "json":
|
||||
caller := fmt.Sprintf("%s:%d", file, line)
|
||||
logMsg = fmt.Sprintf(`{"ts":%q,"level":%q,"caller":%q,"msg":%q}`+"\n", timestamp, levelLowercase, caller, msg)
|
||||
default:
|
||||
logMsg = fmt.Sprintf("%s\t%s\t%s:%d\t%s\n", timestamp, levelLowercase, file, line, msg)
|
||||
}
|
||||
|
||||
// Serialize writes to log.
|
||||
mu.Lock()
|
||||
fmt.Fprint(os.Stderr, logMsg)
|
||||
mu.Unlock()
|
||||
select {
|
||||
case logMessageCh <- logMsg:
|
||||
default:
|
||||
// Writing to log can stuck if the log output isn't consumed in timely manner.
|
||||
// Handle this case via `vm_log_messages_dropped_total`
|
||||
logMessagesDropped.Inc()
|
||||
}
|
||||
|
||||
// Increment vm_log_messages_total
|
||||
location := fmt.Sprintf("%s:%d", file, line)
|
||||
@@ -135,7 +158,15 @@ func logMessage(level, msg string, skipframes int) {
|
||||
}
|
||||
}
|
||||
|
||||
var mu sync.Mutex
|
||||
var logMessagesDropped = metrics.NewCounter(`vm_log_messages_dropped_total`)
|
||||
|
||||
func logMessageWriter() {
|
||||
for msg := range logMessageCh {
|
||||
fmt.Fprint(os.Stderr, msg)
|
||||
}
|
||||
}
|
||||
|
||||
var logMessageCh = make(chan string, 100)
|
||||
|
||||
func shouldSkipLog(level string) bool {
|
||||
switch *loggerLevel {
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
type partSearch struct {
|
||||
// Item contains the last item found after the call to NextItem.
|
||||
//
|
||||
// The Item content is valud intil the next call to NextItem.
|
||||
// The Item content is valid until the next call to NextItem.
|
||||
Item []byte
|
||||
|
||||
// p is a part to search.
|
||||
|
||||
@@ -1152,11 +1152,12 @@ func runTransaction(txnLock *sync.RWMutex, pathPrefix, txnPath string) error {
|
||||
if err := os.Rename(srcPath, dstPath); err != nil {
|
||||
return fmt.Errorf("cannot rename %q to %q: %s", srcPath, dstPath, err)
|
||||
}
|
||||
} else {
|
||||
// Verify dstPath exists.
|
||||
if !fs.IsPathExist(dstPath) {
|
||||
return fmt.Errorf("cannot find both source and destination paths: %q -> %q", srcPath, dstPath)
|
||||
}
|
||||
} else if !fs.IsPathExist(dstPath) {
|
||||
// Emit info message for the expected condition after unclean shutdown on NFS disk.
|
||||
// The dstPath part may be missing because it could be already merged into bigger part
|
||||
// while old source parts for the current txn weren't still deleted due to NFS locks.
|
||||
logger.Infof("cannot find both source and destination paths: %q -> %q; this may be the case after unclean shutdown (OOM, `kill -9`, hard reset) on NFS disk",
|
||||
srcPath, dstPath)
|
||||
}
|
||||
|
||||
// Flush pathPrefix directory metadata to the underying storage.
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
)
|
||||
|
||||
@@ -72,6 +73,8 @@ func (ln *TCPListener) Accept() (net.Conn, error) {
|
||||
ln.accepts.Inc()
|
||||
if err != nil {
|
||||
if ne, ok := err.(net.Error); ok && ne.Temporary() {
|
||||
logger.Errorf("temporary error when listening for TCP addr %q: %s", ln.Addr(), err)
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
ln.acceptErrors.Inc()
|
||||
|
||||
@@ -61,6 +61,11 @@ const defaultPartsToMerge = 15
|
||||
// write amplification.
|
||||
const finalPartsToMerge = 3
|
||||
|
||||
// The number of shards for rawRow entries per partition.
|
||||
//
|
||||
// Higher number of shards reduces CPU contention and increases the max bandwidth on multi-core systems.
|
||||
var rawRowsShardsPerPartition = (runtime.GOMAXPROCS(-1) + 7) / 8
|
||||
|
||||
// getMaxRowsPerPartition returns the maximum number of rows that haven't been converted into parts yet.
|
||||
func getMaxRawRowsPerPartition() int {
|
||||
maxRawRowsPerPartitionOnce.Do(func() {
|
||||
@@ -71,7 +76,7 @@ func getMaxRawRowsPerPartition() int {
|
||||
if n > 500e3 {
|
||||
n = 500e3
|
||||
}
|
||||
maxRawRowsPerPartition = n
|
||||
maxRawRowsPerPartition = n / rawRowsShardsPerPartition
|
||||
})
|
||||
return maxRawRowsPerPartition
|
||||
}
|
||||
@@ -129,16 +134,10 @@ type partition struct {
|
||||
// Contains file-based parts with big number of items.
|
||||
bigParts []*partWrapper
|
||||
|
||||
// rawRowsLock protects rawRows.
|
||||
rawRowsLock sync.Mutex
|
||||
|
||||
// rawRows contains recently added rows that haven't been converted into parts yet.
|
||||
//
|
||||
// rawRows aren't used in search for performance reasons.
|
||||
rawRows []rawRow
|
||||
|
||||
// rawRowsLastFlushTime is the last time rawRows are flushed.
|
||||
rawRowsLastFlushTime time.Time
|
||||
rawRows rawRowsShards
|
||||
|
||||
snapshotLock sync.RWMutex
|
||||
|
||||
@@ -264,18 +263,18 @@ func openPartition(smallPartsPath, bigPartsPath string, getDeletedMetricIDs func
|
||||
}
|
||||
|
||||
func newPartition(name, smallPartsPath, bigPartsPath string, getDeletedMetricIDs func() *uint64set.Set) *partition {
|
||||
return &partition{
|
||||
p := &partition{
|
||||
name: name,
|
||||
smallPartsPath: smallPartsPath,
|
||||
bigPartsPath: bigPartsPath,
|
||||
|
||||
getDeletedMetricIDs: getDeletedMetricIDs,
|
||||
|
||||
rawRows: getRawRowsMaxSize().rows,
|
||||
|
||||
mergeIdx: uint64(time.Now().UnixNano()),
|
||||
stopCh: make(chan struct{}),
|
||||
}
|
||||
p.rawRows.init()
|
||||
return p
|
||||
}
|
||||
|
||||
// partitionMetrics contains essential metrics for the partition.
|
||||
@@ -322,10 +321,9 @@ type partitionMetrics struct {
|
||||
|
||||
// UpdateMetrics updates m with metrics from pt.
|
||||
func (pt *partition) UpdateMetrics(m *partitionMetrics) {
|
||||
pt.rawRowsLock.Lock()
|
||||
m.PendingRows += uint64(len(pt.rawRows))
|
||||
m.SmallRowsCount += uint64(len(pt.rawRows))
|
||||
pt.rawRowsLock.Unlock()
|
||||
rawRowsLen := uint64(pt.rawRows.Len())
|
||||
m.PendingRows += rawRowsLen
|
||||
m.SmallRowsCount += rawRowsLen
|
||||
|
||||
pt.partsLock.Lock()
|
||||
|
||||
@@ -399,29 +397,82 @@ func (pt *partition) AddRows(rows []rawRow) {
|
||||
}
|
||||
}
|
||||
|
||||
// Try adding rows.
|
||||
var rrs []*rawRows
|
||||
pt.rawRowsLock.Lock()
|
||||
pt.rawRows.addRows(pt, rows)
|
||||
}
|
||||
|
||||
type rawRowsShards struct {
|
||||
lock sync.Mutex
|
||||
shardIdx int
|
||||
|
||||
// Shards reduce lock contention when adding rows on multi-CPU systems.
|
||||
shards []rawRowsShard
|
||||
}
|
||||
|
||||
func (rrs *rawRowsShards) init() {
|
||||
rrs.shards = make([]rawRowsShard, rawRowsShardsPerPartition)
|
||||
}
|
||||
|
||||
func (rrs *rawRowsShards) addRows(pt *partition, rows []rawRow) {
|
||||
rrs.lock.Lock()
|
||||
rrs.shardIdx++
|
||||
if rrs.shardIdx >= len(rrs.shards) {
|
||||
rrs.shardIdx = 0
|
||||
}
|
||||
shard := &rrs.shards[rrs.shardIdx]
|
||||
rrs.lock.Unlock()
|
||||
|
||||
shard.addRows(pt, rows)
|
||||
}
|
||||
|
||||
func (rrs *rawRowsShards) Len() int {
|
||||
n := 0
|
||||
for i := range rrs.shards[:] {
|
||||
n += rrs.shards[i].Len()
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
type rawRowsShard struct {
|
||||
lock sync.Mutex
|
||||
rows []rawRow
|
||||
lastFlushTime time.Time
|
||||
}
|
||||
|
||||
func (rrs *rawRowsShard) Len() int {
|
||||
rrs.lock.Lock()
|
||||
n := len(rrs.rows)
|
||||
rrs.lock.Unlock()
|
||||
return n
|
||||
}
|
||||
|
||||
func (rrs *rawRowsShard) addRows(pt *partition, rows []rawRow) {
|
||||
var rrss []*rawRows
|
||||
|
||||
rrs.lock.Lock()
|
||||
if cap(rrs.rows) == 0 {
|
||||
rrs.rows = getRawRowsMaxSize().rows
|
||||
}
|
||||
maxRowsCount := getMaxRawRowsPerPartition()
|
||||
for {
|
||||
capacity := cap(pt.rawRows) - len(pt.rawRows)
|
||||
capacity := maxRowsCount - len(rrs.rows)
|
||||
if capacity >= len(rows) {
|
||||
// Fast path - rows fit capacity.
|
||||
pt.rawRows = append(pt.rawRows, rows...)
|
||||
rrs.rows = append(rrs.rows, rows...)
|
||||
break
|
||||
}
|
||||
|
||||
// Slow path - rows don't fit capacity.
|
||||
// Fill rawRows to capacity and convert it to a part.
|
||||
pt.rawRows = append(pt.rawRows, rows[:capacity]...)
|
||||
rrs.rows = append(rrs.rows, rows[:capacity]...)
|
||||
rows = rows[capacity:]
|
||||
rr := getRawRowsMaxSize()
|
||||
pt.rawRows, rr.rows = rr.rows, pt.rawRows
|
||||
rrs = append(rrs, rr)
|
||||
pt.rawRowsLastFlushTime = time.Now()
|
||||
rrs.rows, rr.rows = rr.rows, rrs.rows
|
||||
rrss = append(rrss, rr)
|
||||
rrs.lastFlushTime = time.Now()
|
||||
}
|
||||
pt.rawRowsLock.Unlock()
|
||||
rrs.lock.Unlock()
|
||||
|
||||
for _, rr := range rrs {
|
||||
for _, rr := range rrss {
|
||||
pt.addRowsPart(rr.rows)
|
||||
putRawRows(rr)
|
||||
}
|
||||
@@ -583,7 +634,7 @@ func (pt *partition) MustClose() {
|
||||
startTime = time.Now()
|
||||
|
||||
// Flush raw rows the last time before exit.
|
||||
pt.flushRawRows(nil, true)
|
||||
pt.flushRawRows(true)
|
||||
|
||||
// Flush inmemory parts to disk.
|
||||
var pws []*partWrapper
|
||||
@@ -637,7 +688,6 @@ func (pt *partition) startRawRowsFlusher() {
|
||||
}
|
||||
|
||||
func (pt *partition) rawRowsFlusher() {
|
||||
var rawRows []rawRow
|
||||
t := time.NewTimer(rawRowsFlushInterval)
|
||||
for {
|
||||
select {
|
||||
@@ -646,29 +696,35 @@ func (pt *partition) rawRowsFlusher() {
|
||||
case <-t.C:
|
||||
t.Reset(rawRowsFlushInterval)
|
||||
}
|
||||
|
||||
rawRows = pt.flushRawRows(rawRows[:0], false)
|
||||
pt.flushRawRows(false)
|
||||
}
|
||||
}
|
||||
|
||||
func (pt *partition) flushRawRows(newRawRows []rawRow, isFinal bool) []rawRow {
|
||||
oldRawRows := newRawRows[:0]
|
||||
mustFlush := false
|
||||
func (pt *partition) flushRawRows(isFinal bool) {
|
||||
pt.rawRows.flush(pt, isFinal)
|
||||
}
|
||||
|
||||
func (rrs *rawRowsShards) flush(pt *partition, isFinal bool) {
|
||||
for i := range rrs.shards[:] {
|
||||
rrs.shards[i].flush(pt, isFinal)
|
||||
}
|
||||
}
|
||||
|
||||
func (rrs *rawRowsShard) flush(pt *partition, isFinal bool) {
|
||||
var rr *rawRows
|
||||
currentTime := time.Now()
|
||||
|
||||
pt.rawRowsLock.Lock()
|
||||
if isFinal || currentTime.Sub(pt.rawRowsLastFlushTime) > rawRowsFlushInterval {
|
||||
mustFlush = true
|
||||
oldRawRows = pt.rawRows
|
||||
pt.rawRows = newRawRows[:0]
|
||||
pt.rawRowsLastFlushTime = currentTime
|
||||
rrs.lock.Lock()
|
||||
if isFinal || currentTime.Sub(rrs.lastFlushTime) > rawRowsFlushInterval {
|
||||
rr = getRawRowsMaxSize()
|
||||
rrs.rows, rr.rows = rr.rows, rrs.rows
|
||||
}
|
||||
pt.rawRowsLock.Unlock()
|
||||
rrs.lock.Unlock()
|
||||
|
||||
if mustFlush {
|
||||
pt.addRowsPart(oldRawRows)
|
||||
if rr != nil {
|
||||
pt.addRowsPart(rr.rows)
|
||||
putRawRows(rr)
|
||||
}
|
||||
return oldRawRows
|
||||
}
|
||||
|
||||
func (pt *partition) startInmemoryPartsFlusher() {
|
||||
@@ -1334,7 +1390,7 @@ func (pt *partition) CreateSnapshotAt(smallPath, bigPath string) error {
|
||||
startTime := time.Now()
|
||||
|
||||
// Flush inmemory data to disk.
|
||||
pt.flushRawRows(nil, true)
|
||||
pt.flushRawRows(true)
|
||||
if _, err := pt.flushInmemoryParts(nil, true); err != nil {
|
||||
return fmt.Errorf("cannot flush inmemory parts: %s", err)
|
||||
}
|
||||
@@ -1486,11 +1542,12 @@ func runTransaction(txnLock *sync.RWMutex, pathPrefix1, pathPrefix2, txnPath str
|
||||
if err := os.Rename(srcPath, dstPath); err != nil {
|
||||
return fmt.Errorf("cannot rename %q to %q: %s", srcPath, dstPath, err)
|
||||
}
|
||||
} else {
|
||||
// Verify dstPath exists.
|
||||
if !fs.IsPathExist(dstPath) {
|
||||
return fmt.Errorf("cannot find both source and destination paths: %q -> %q", srcPath, dstPath)
|
||||
}
|
||||
} else if !fs.IsPathExist(dstPath) {
|
||||
// Emit info message for the expected condition after unclean shutdown on NFS disk.
|
||||
// The dstPath part may be missing because it could be already merged into bigger part
|
||||
// while old source parts for the current txn weren't still deleted due to NFS locks.
|
||||
logger.Infof("cannot find both source and destination paths: %q -> %q; this may be the case after unclean shutdown (OOM, `kill -9`, hard reset) on NFS disk",
|
||||
srcPath, dstPath)
|
||||
}
|
||||
} else {
|
||||
// Just remove srcPath.
|
||||
|
||||
@@ -185,7 +185,7 @@ func testPartitionSearchEx(t *testing.T, ptt int64, tr TimeRange, partsCount, ma
|
||||
pt.AddRows(rows)
|
||||
|
||||
// Flush just added rows to a separate partition.
|
||||
pt.flushRawRows(nil, true)
|
||||
pt.flushRawRows(true)
|
||||
}
|
||||
testPartitionSearch(t, pt, tsids, tr, rbsExpected, -1)
|
||||
pt.MustClose()
|
||||
|
||||
@@ -803,6 +803,11 @@ func (s *Storage) add(rows []rawRow, mrs []MetricRow, precisionBits uint8) ([]ra
|
||||
}
|
||||
rows = rows[:rowsLen+len(mrs)]
|
||||
j := 0
|
||||
var (
|
||||
// These vars are used for speeding up bulk imports of multiple adjancent rows for the same metricName.
|
||||
prevTSID TSID
|
||||
prevMetricNameRaw []byte
|
||||
)
|
||||
minTimestamp, maxTimestamp := s.tb.getMinMaxTimestamps()
|
||||
// Return only the last error, since it has no sense in returning all errors.
|
||||
var lastWarn error
|
||||
@@ -830,9 +835,17 @@ func (s *Storage) add(rows []rawRow, mrs []MetricRow, precisionBits uint8) ([]ra
|
||||
r.Timestamp = mr.Timestamp
|
||||
r.Value = mr.Value
|
||||
r.PrecisionBits = precisionBits
|
||||
if string(mr.MetricNameRaw) == string(prevMetricNameRaw) {
|
||||
// Fast path - the current mr contains the same metric name as the previous mr, so it contains the same TSID.
|
||||
// This path should trigger on bulk imports when many rows contain the same MetricNameRaw.
|
||||
r.TSID = prevTSID
|
||||
continue
|
||||
}
|
||||
if s.getTSIDFromCache(&r.TSID, mr.MetricNameRaw) {
|
||||
if !dmis.Has(r.TSID.MetricID) {
|
||||
// Fast path - the TSID for the given MetricName has been found in cache and isn't deleted.
|
||||
prevTSID = r.TSID
|
||||
prevMetricNameRaw = mr.MetricNameRaw
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -890,6 +903,12 @@ func (s *Storage) updatePerDateData(rows []rawRow, lastError error) error {
|
||||
var date uint64
|
||||
var hour uint64
|
||||
var prevTimestamp int64
|
||||
var (
|
||||
// These vars are used for speeding up bulk imports when multiple adjancent rows
|
||||
// contain the same (metricID, date) pairs.
|
||||
prevMatchedDate uint64
|
||||
prevMatchedMetricID uint64
|
||||
)
|
||||
idb := s.idb()
|
||||
hm := s.currHourMetricIDs.Load().(*hourMetricIDs)
|
||||
for i := range rows {
|
||||
@@ -913,8 +932,14 @@ func (s *Storage) updatePerDateData(rows []rawRow, lastError error) error {
|
||||
}
|
||||
|
||||
// Slower path: check global cache for (date, metricID) entry.
|
||||
if metricID == prevMatchedMetricID && date == prevMatchedDate {
|
||||
// Fast path for bulk import of multiple rows with the same (date, metricID) pairs.
|
||||
continue
|
||||
}
|
||||
if s.dateMetricIDCache.Has(date, metricID) {
|
||||
// The metricID has been already added to per-day inverted index.
|
||||
prevMatchedDate = date
|
||||
prevMatchedMetricID = metricID
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -220,7 +220,7 @@ func (tb *table) flushRawRows() {
|
||||
defer tb.PutPartitions(ptws)
|
||||
|
||||
for _, ptw := range ptws {
|
||||
ptw.pt.flushRawRows(nil, true)
|
||||
ptw.pt.flushRawRows(true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
2
vendor/github.com/VictoriaMetrics/metrics/histogram.go
generated
vendored
2
vendor/github.com/VictoriaMetrics/metrics/histogram.go
generated
vendored
@@ -34,7 +34,7 @@ const (
|
||||
// - <counter> - the number of hits to the given bucket during Update* calls
|
||||
//
|
||||
// Histogram buckets can be converted to Prometheus-like buckets with `le` labels
|
||||
// with `prometheus_buckets(<metric_name>_bucket)` function from Extended PromQL in VictoriaMetrics.
|
||||
// with `prometheus_buckets(<metric_name>_bucket)` function from PromQL extensions in VictoriaMetrics.
|
||||
// (see https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/ExtendedPromQL ):
|
||||
//
|
||||
// prometheus_buckets(request_duration_bucket)
|
||||
|
||||
93
vendor/github.com/aws/aws-sdk-go/aws/arn/arn.go
generated
vendored
Normal file
93
vendor/github.com/aws/aws-sdk-go/aws/arn/arn.go
generated
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
// Package arn provides a parser for interacting with Amazon Resource Names.
|
||||
package arn
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
arnDelimiter = ":"
|
||||
arnSections = 6
|
||||
arnPrefix = "arn:"
|
||||
|
||||
// zero-indexed
|
||||
sectionPartition = 1
|
||||
sectionService = 2
|
||||
sectionRegion = 3
|
||||
sectionAccountID = 4
|
||||
sectionResource = 5
|
||||
|
||||
// errors
|
||||
invalidPrefix = "arn: invalid prefix"
|
||||
invalidSections = "arn: not enough sections"
|
||||
)
|
||||
|
||||
// ARN captures the individual fields of an Amazon Resource Name.
|
||||
// See http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html for more information.
|
||||
type ARN struct {
|
||||
// The partition that the resource is in. For standard AWS regions, the partition is "aws". If you have resources in
|
||||
// other partitions, the partition is "aws-partitionname". For example, the partition for resources in the China
|
||||
// (Beijing) region is "aws-cn".
|
||||
Partition string
|
||||
|
||||
// The service namespace that identifies the AWS product (for example, Amazon S3, IAM, or Amazon RDS). For a list of
|
||||
// namespaces, see
|
||||
// http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html#genref-aws-service-namespaces.
|
||||
Service string
|
||||
|
||||
// The region the resource resides in. Note that the ARNs for some resources do not require a region, so this
|
||||
// component might be omitted.
|
||||
Region string
|
||||
|
||||
// The ID of the AWS account that owns the resource, without the hyphens. For example, 123456789012. Note that the
|
||||
// ARNs for some resources don't require an account number, so this component might be omitted.
|
||||
AccountID string
|
||||
|
||||
// The content of this part of the ARN varies by service. It often includes an indicator of the type of resource —
|
||||
// for example, an IAM user or Amazon RDS database - followed by a slash (/) or a colon (:), followed by the
|
||||
// resource name itself. Some services allows paths for resource names, as described in
|
||||
// http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html#arns-paths.
|
||||
Resource string
|
||||
}
|
||||
|
||||
// Parse parses an ARN into its constituent parts.
|
||||
//
|
||||
// Some example ARNs:
|
||||
// arn:aws:elasticbeanstalk:us-east-1:123456789012:environment/My App/MyEnvironment
|
||||
// arn:aws:iam::123456789012:user/David
|
||||
// arn:aws:rds:eu-west-1:123456789012:db:mysql-db
|
||||
// arn:aws:s3:::my_corporate_bucket/exampleobject.png
|
||||
func Parse(arn string) (ARN, error) {
|
||||
if !strings.HasPrefix(arn, arnPrefix) {
|
||||
return ARN{}, errors.New(invalidPrefix)
|
||||
}
|
||||
sections := strings.SplitN(arn, arnDelimiter, arnSections)
|
||||
if len(sections) != arnSections {
|
||||
return ARN{}, errors.New(invalidSections)
|
||||
}
|
||||
return ARN{
|
||||
Partition: sections[sectionPartition],
|
||||
Service: sections[sectionService],
|
||||
Region: sections[sectionRegion],
|
||||
AccountID: sections[sectionAccountID],
|
||||
Resource: sections[sectionResource],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// IsARN returns whether the given string is an ARN by looking for
|
||||
// whether the string starts with "arn:" and contains the correct number
|
||||
// of sections delimited by colons(:).
|
||||
func IsARN(arn string) bool {
|
||||
return strings.HasPrefix(arn, arnPrefix) && strings.Count(arn, ":") >= arnSections-1
|
||||
}
|
||||
|
||||
// String returns the canonical representation of the ARN
|
||||
func (arn ARN) String() string {
|
||||
return arnPrefix +
|
||||
arn.Partition + arnDelimiter +
|
||||
arn.Service + arnDelimiter +
|
||||
arn.Region + arnDelimiter +
|
||||
arn.AccountID + arnDelimiter +
|
||||
arn.Resource
|
||||
}
|
||||
15
vendor/github.com/aws/aws-sdk-go/aws/config.go
generated
vendored
15
vendor/github.com/aws/aws-sdk-go/aws/config.go
generated
vendored
@@ -161,6 +161,10 @@ type Config struct {
|
||||
// on GetObject API calls.
|
||||
S3DisableContentMD5Validation *bool
|
||||
|
||||
// Set this to `true` to have the S3 service client to use the region specified
|
||||
// in the ARN, when an ARN is provided as an argument to a bucket parameter.
|
||||
S3UseARNRegion *bool
|
||||
|
||||
// Set this to `true` to disable the EC2Metadata client from overriding the
|
||||
// default http.Client's Timeout. This is helpful if you do not want the
|
||||
// EC2Metadata client to create a new http.Client. This options is only
|
||||
@@ -385,6 +389,13 @@ func (c *Config) WithS3DisableContentMD5Validation(enable bool) *Config {
|
||||
|
||||
}
|
||||
|
||||
// WithS3UseARNRegion sets a config S3UseARNRegion value and
|
||||
// returning a Config pointer for chaining
|
||||
func (c *Config) WithS3UseARNRegion(enable bool) *Config {
|
||||
c.S3UseARNRegion = &enable
|
||||
return c
|
||||
}
|
||||
|
||||
// WithUseDualStack sets a config UseDualStack value returning a Config
|
||||
// pointer for chaining.
|
||||
func (c *Config) WithUseDualStack(enable bool) *Config {
|
||||
@@ -513,6 +524,10 @@ func mergeInConfig(dst *Config, other *Config) {
|
||||
dst.S3DisableContentMD5Validation = other.S3DisableContentMD5Validation
|
||||
}
|
||||
|
||||
if other.S3UseARNRegion != nil {
|
||||
dst.S3UseARNRegion = other.S3UseARNRegion
|
||||
}
|
||||
|
||||
if other.UseDualStack != nil {
|
||||
dst.UseDualStack = other.UseDualStack
|
||||
}
|
||||
|
||||
17
vendor/github.com/aws/aws-sdk-go/aws/credentials/stscreds/assume_role_provider.go
generated
vendored
17
vendor/github.com/aws/aws-sdk-go/aws/credentials/stscreds/assume_role_provider.go
generated
vendored
@@ -144,6 +144,13 @@ type AssumeRoleProvider struct {
|
||||
// Session name, if you wish to reuse the credentials elsewhere.
|
||||
RoleSessionName string
|
||||
|
||||
// Optional, you can pass tag key-value pairs to your session. These tags are called session tags.
|
||||
Tags []*sts.Tag
|
||||
|
||||
// A list of keys for session tags that you want to set as transitive.
|
||||
// If you set a tag key as transitive, the corresponding key and value passes to subsequent sessions in a role chain.
|
||||
TransitiveTagKeys []*string
|
||||
|
||||
// Expiry duration of the STS credentials. Defaults to 15 minutes if not set.
|
||||
Duration time.Duration
|
||||
|
||||
@@ -269,10 +276,12 @@ func (p *AssumeRoleProvider) Retrieve() (credentials.Value, error) {
|
||||
}
|
||||
jitter := time.Duration(sdkrand.SeededRand.Float64() * p.MaxJitterFrac * float64(p.Duration))
|
||||
input := &sts.AssumeRoleInput{
|
||||
DurationSeconds: aws.Int64(int64((p.Duration - jitter) / time.Second)),
|
||||
RoleArn: aws.String(p.RoleARN),
|
||||
RoleSessionName: aws.String(p.RoleSessionName),
|
||||
ExternalId: p.ExternalID,
|
||||
DurationSeconds: aws.Int64(int64((p.Duration - jitter) / time.Second)),
|
||||
RoleArn: aws.String(p.RoleARN),
|
||||
RoleSessionName: aws.String(p.RoleSessionName),
|
||||
ExternalId: p.ExternalID,
|
||||
Tags: p.Tags,
|
||||
TransitiveTagKeys: p.TransitiveTagKeys,
|
||||
}
|
||||
if p.Policy != nil {
|
||||
input.Policy = p.Policy
|
||||
|
||||
113
vendor/github.com/aws/aws-sdk-go/aws/endpoints/defaults.go
generated
vendored
113
vendor/github.com/aws/aws-sdk-go/aws/endpoints/defaults.go
generated
vendored
@@ -209,12 +209,42 @@ var awsPartition = partition{
|
||||
"eu-west-1": endpoint{},
|
||||
"eu-west-2": endpoint{},
|
||||
"eu-west-3": endpoint{},
|
||||
"me-south-1": endpoint{},
|
||||
"sa-east-1": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-east-2": endpoint{},
|
||||
"us-west-1": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
"fips-ca-central-1": endpoint{
|
||||
Hostname: "acm-pca-fips.ca-central-1.amazonaws.com",
|
||||
CredentialScope: credentialScope{
|
||||
Region: "ca-central-1",
|
||||
},
|
||||
},
|
||||
"fips-us-east-1": endpoint{
|
||||
Hostname: "acm-pca-fips.us-east-1.amazonaws.com",
|
||||
CredentialScope: credentialScope{
|
||||
Region: "us-east-1",
|
||||
},
|
||||
},
|
||||
"fips-us-east-2": endpoint{
|
||||
Hostname: "acm-pca-fips.us-east-2.amazonaws.com",
|
||||
CredentialScope: credentialScope{
|
||||
Region: "us-east-2",
|
||||
},
|
||||
},
|
||||
"fips-us-west-1": endpoint{
|
||||
Hostname: "acm-pca-fips.us-west-1.amazonaws.com",
|
||||
CredentialScope: credentialScope{
|
||||
Region: "us-west-1",
|
||||
},
|
||||
},
|
||||
"fips-us-west-2": endpoint{
|
||||
Hostname: "acm-pca-fips.us-west-2.amazonaws.com",
|
||||
CredentialScope: credentialScope{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
},
|
||||
"me-south-1": endpoint{},
|
||||
"sa-east-1": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-east-2": endpoint{},
|
||||
"us-west-1": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
},
|
||||
},
|
||||
"api.ecr": service{
|
||||
@@ -2077,6 +2107,27 @@ var awsPartition = partition{
|
||||
},
|
||||
},
|
||||
},
|
||||
"iotsecuredtunneling": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"ap-northeast-1": endpoint{},
|
||||
"ap-northeast-2": endpoint{},
|
||||
"ap-south-1": endpoint{},
|
||||
"ap-southeast-1": endpoint{},
|
||||
"ap-southeast-2": endpoint{},
|
||||
"ca-central-1": endpoint{},
|
||||
"eu-central-1": endpoint{},
|
||||
"eu-north-1": endpoint{},
|
||||
"eu-west-1": endpoint{},
|
||||
"eu-west-2": endpoint{},
|
||||
"eu-west-3": endpoint{},
|
||||
"sa-east-1": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-east-2": endpoint{},
|
||||
"us-west-1": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
},
|
||||
},
|
||||
"iotthingsgraph": service{
|
||||
Defaults: endpoint{
|
||||
CredentialScope: credentialScope{
|
||||
@@ -2444,9 +2495,10 @@ var awsPartition = partition{
|
||||
},
|
||||
},
|
||||
Endpoints: endpoints{
|
||||
"eu-west-1": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
"ap-southeast-2": endpoint{},
|
||||
"eu-west-1": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
},
|
||||
},
|
||||
"monitoring": service{
|
||||
@@ -2733,8 +2785,30 @@ var awsPartition = partition{
|
||||
"ap-southeast-2": endpoint{},
|
||||
"eu-central-1": endpoint{},
|
||||
"eu-west-1": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
"fips-us-east-1": endpoint{
|
||||
Hostname: "pinpoint-fips.us-east-1.amazonaws.com",
|
||||
CredentialScope: credentialScope{
|
||||
Region: "us-east-1",
|
||||
},
|
||||
},
|
||||
"fips-us-west-2": endpoint{
|
||||
Hostname: "pinpoint-fips.us-west-2.amazonaws.com",
|
||||
CredentialScope: credentialScope{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
},
|
||||
"us-east-1": endpoint{
|
||||
Hostname: "pinpoint.us-east-1.amazonaws.com",
|
||||
CredentialScope: credentialScope{
|
||||
Region: "us-east-1",
|
||||
},
|
||||
},
|
||||
"us-west-2": endpoint{
|
||||
Hostname: "pinpoint.us-west-2.amazonaws.com",
|
||||
CredentialScope: credentialScope{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"polly": service{
|
||||
@@ -3035,9 +3109,10 @@ var awsPartition = partition{
|
||||
},
|
||||
},
|
||||
Endpoints: endpoints{
|
||||
"eu-west-1": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
"ap-southeast-2": endpoint{},
|
||||
"eu-west-1": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
},
|
||||
},
|
||||
"runtime.sagemaker": service{
|
||||
@@ -3316,6 +3391,16 @@ var awsPartition = partition{
|
||||
},
|
||||
},
|
||||
},
|
||||
"schemas": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"ap-northeast-1": endpoint{},
|
||||
"eu-west-1": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-east-2": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
},
|
||||
},
|
||||
"sdb": service{
|
||||
Defaults: endpoint{
|
||||
Protocols: []string{"http", "https"},
|
||||
|
||||
1
vendor/github.com/aws/aws-sdk-go/aws/request/retryer.go
generated
vendored
1
vendor/github.com/aws/aws-sdk-go/aws/request/retryer.go
generated
vendored
@@ -83,6 +83,7 @@ var retryableCodes = map[string]struct{}{
|
||||
|
||||
var throttleCodes = map[string]struct{}{
|
||||
"ProvisionedThroughputExceededException": {},
|
||||
"ThrottledException": {}, // SNS, XRay, ResourceGroupsTagging API
|
||||
"Throttling": {},
|
||||
"ThrottlingException": {},
|
||||
"RequestLimitExceeded": {},
|
||||
|
||||
25
vendor/github.com/aws/aws-sdk-go/aws/session/env_config.go
generated
vendored
25
vendor/github.com/aws/aws-sdk-go/aws/session/env_config.go
generated
vendored
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
@@ -141,6 +142,12 @@ type envConfig struct {
|
||||
// AWS_S3_US_EAST_1_REGIONAL_ENDPOINT=regional
|
||||
// This can take value as `regional` or `legacy`
|
||||
S3UsEast1RegionalEndpoint endpoints.S3UsEast1RegionalEndpoint
|
||||
|
||||
// Specifies if the S3 service should allow ARNs to direct the region
|
||||
// the client's requests are sent to.
|
||||
//
|
||||
// AWS_S3_USE_ARN_REGION=true
|
||||
S3UseARNRegion bool
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -201,6 +208,9 @@ var (
|
||||
s3UsEast1RegionalEndpoint = []string{
|
||||
"AWS_S3_US_EAST_1_REGIONAL_ENDPOINT",
|
||||
}
|
||||
s3UseARNRegionEnvKey = []string{
|
||||
"AWS_S3_USE_ARN_REGION",
|
||||
}
|
||||
)
|
||||
|
||||
// loadEnvConfig retrieves the SDK's environment configuration.
|
||||
@@ -307,6 +317,21 @@ func envConfigLoad(enableSharedConfig bool) (envConfig, error) {
|
||||
}
|
||||
}
|
||||
|
||||
var s3UseARNRegion string
|
||||
setFromEnvVal(&s3UseARNRegion, s3UseARNRegionEnvKey)
|
||||
if len(s3UseARNRegion) != 0 {
|
||||
switch {
|
||||
case strings.EqualFold(s3UseARNRegion, "false"):
|
||||
cfg.S3UseARNRegion = false
|
||||
case strings.EqualFold(s3UseARNRegion, "true"):
|
||||
cfg.S3UseARNRegion = true
|
||||
default:
|
||||
return envConfig{}, fmt.Errorf(
|
||||
"invalid value for environment variable, %s=%s, need true or false",
|
||||
s3UseARNRegionEnvKey[0], s3UseARNRegion)
|
||||
}
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
|
||||
9
vendor/github.com/aws/aws-sdk-go/aws/session/session.go
generated
vendored
9
vendor/github.com/aws/aws-sdk-go/aws/session/session.go
generated
vendored
@@ -580,6 +580,14 @@ func mergeConfigSrcs(cfg, userCfg *aws.Config,
|
||||
cfg.Credentials = creds
|
||||
}
|
||||
|
||||
cfg.S3UseARNRegion = userCfg.S3UseARNRegion
|
||||
if cfg.S3UseARNRegion == nil {
|
||||
cfg.S3UseARNRegion = &envCfg.S3UseARNRegion
|
||||
}
|
||||
if cfg.S3UseARNRegion == nil {
|
||||
cfg.S3UseARNRegion = &sharedCfg.S3UseARNRegion
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -643,6 +651,7 @@ func (s *Session) ClientConfig(service string, cfgs ...*aws.Config) client.Confi
|
||||
return client.Config{
|
||||
Config: s.Config,
|
||||
Handlers: s.Handlers,
|
||||
PartitionID: resolved.PartitionID,
|
||||
Endpoint: resolved.URL,
|
||||
SigningRegion: resolved.SigningRegion,
|
||||
SigningNameDerived: resolved.SigningNameDerived,
|
||||
|
||||
21
vendor/github.com/aws/aws-sdk-go/aws/session/shared_config.go
generated
vendored
21
vendor/github.com/aws/aws-sdk-go/aws/session/shared_config.go
generated
vendored
@@ -51,6 +51,9 @@ const (
|
||||
// loading configuration from the config files if another profile name
|
||||
// is not provided.
|
||||
DefaultSharedConfigProfile = `default`
|
||||
|
||||
// S3 ARN Region Usage
|
||||
s3UseARNRegionKey = "s3_use_arn_region"
|
||||
)
|
||||
|
||||
// sharedConfig represents the configuration fields of the SDK config files.
|
||||
@@ -89,6 +92,7 @@ type sharedConfig struct {
|
||||
//
|
||||
// endpoint_discovery_enabled = true
|
||||
EnableEndpointDiscovery *bool
|
||||
|
||||
// CSM Options
|
||||
CSMEnabled *bool
|
||||
CSMHost string
|
||||
@@ -106,6 +110,12 @@ type sharedConfig struct {
|
||||
// s3_us_east_1_regional_endpoint = regional
|
||||
// This can take value as `LegacyS3UsEast1Endpoint` or `RegionalS3UsEast1Endpoint`
|
||||
S3UsEast1RegionalEndpoint endpoints.S3UsEast1RegionalEndpoint
|
||||
|
||||
// Specifies if the S3 service should allow ARNs to direct the region
|
||||
// the client's requests are sent to.
|
||||
//
|
||||
// s3_use_arn_region=true
|
||||
S3UseARNRegion bool
|
||||
}
|
||||
|
||||
type sharedConfigFile struct {
|
||||
@@ -306,6 +316,8 @@ func (cfg *sharedConfig) setFromIniFile(profile string, file sharedConfigFile, e
|
||||
updateString(&cfg.CSMPort, section, csmPortKey)
|
||||
updateString(&cfg.CSMClientID, section, csmClientIDKey)
|
||||
|
||||
updateBool(&cfg.S3UseARNRegion, section, s3UseARNRegionKey)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -398,6 +410,15 @@ func updateString(dst *string, section ini.Section, key string) {
|
||||
*dst = section.String(key)
|
||||
}
|
||||
|
||||
// updateBool will only update the dst with the value in the section key, key
|
||||
// is present in the section.
|
||||
func updateBool(dst *bool, section ini.Section, key string) {
|
||||
if !section.Has(key) {
|
||||
return
|
||||
}
|
||||
*dst = section.Bool(key)
|
||||
}
|
||||
|
||||
// updateBoolPtr will only update the dst with the value in the section key,
|
||||
// key is present in the section.
|
||||
func updateBoolPtr(dst **bool, section ini.Section, key string) {
|
||||
|
||||
2
vendor/github.com/aws/aws-sdk-go/aws/version.go
generated
vendored
2
vendor/github.com/aws/aws-sdk-go/aws/version.go
generated
vendored
@@ -5,4 +5,4 @@ package aws
|
||||
const SDKName = "aws-sdk-go"
|
||||
|
||||
// SDKVersion is the version of this SDK
|
||||
const SDKVersion = "1.25.43"
|
||||
const SDKVersion = "1.26.0"
|
||||
|
||||
2290
vendor/github.com/aws/aws-sdk-go/service/s3/api.go
generated
vendored
2290
vendor/github.com/aws/aws-sdk-go/service/s3/api.go
generated
vendored
File diff suppressed because it is too large
Load Diff
10
vendor/github.com/aws/aws-sdk-go/service/s3/customizations.go
generated
vendored
10
vendor/github.com/aws/aws-sdk-go/service/s3/customizations.go
generated
vendored
@@ -4,6 +4,7 @@ import (
|
||||
"github.com/aws/aws-sdk-go/aws/client"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/internal/s3err"
|
||||
"github.com/aws/aws-sdk-go/service/s3/internal/arn"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -13,7 +14,7 @@ func init() {
|
||||
|
||||
func defaultInitClientFn(c *client.Client) {
|
||||
// Support building custom endpoints based on config
|
||||
c.Handlers.Build.PushFront(updateEndpointForS3Config)
|
||||
c.Handlers.Build.PushFront(endpointHandler)
|
||||
|
||||
// Require SSL when using SSE keys
|
||||
c.Handlers.Validate.PushBack(validateSSERequiresSSL)
|
||||
@@ -27,7 +28,7 @@ func defaultInitClientFn(c *client.Client) {
|
||||
}
|
||||
|
||||
func defaultInitRequestFn(r *request.Request) {
|
||||
// Add reuest handlers for specific platforms.
|
||||
// Add request handlers for specific platforms.
|
||||
// e.g. 100-continue support for PUT requests using Go 1.6
|
||||
platformRequestHandlers(r)
|
||||
|
||||
@@ -73,3 +74,8 @@ type sseCustomerKeyGetter interface {
|
||||
type copySourceSSECustomerKeyGetter interface {
|
||||
getCopySourceSSECustomerKey() string
|
||||
}
|
||||
|
||||
type endpointARNGetter interface {
|
||||
getEndpointARN() (arn.Resource, error)
|
||||
hasEndpointARN() bool
|
||||
}
|
||||
|
||||
233
vendor/github.com/aws/aws-sdk-go/service/s3/endpoint.go
generated
vendored
Normal file
233
vendor/github.com/aws/aws-sdk-go/service/s3/endpoint.go
generated
vendored
Normal file
@@ -0,0 +1,233 @@
|
||||
package s3
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
awsarn "github.com/aws/aws-sdk-go/aws/arn"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/aws/endpoints"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/private/protocol"
|
||||
"github.com/aws/aws-sdk-go/service/s3/internal/arn"
|
||||
)
|
||||
|
||||
// Used by shapes with members decorated as endpoint ARN.
|
||||
func parseEndpointARN(v string) (arn.Resource, error) {
|
||||
return arn.ParseResource(v, accessPointResourceParser)
|
||||
}
|
||||
|
||||
func accessPointResourceParser(a awsarn.ARN) (arn.Resource, error) {
|
||||
resParts := arn.SplitResource(a.Resource)
|
||||
switch resParts[0] {
|
||||
case "accesspoint":
|
||||
return arn.ParseAccessPointResource(a, resParts[1:])
|
||||
default:
|
||||
return nil, arn.InvalidARNError{ARN: a, Reason: "unknown resource type"}
|
||||
}
|
||||
}
|
||||
|
||||
func endpointHandler(req *request.Request) {
|
||||
endpoint, ok := req.Params.(endpointARNGetter)
|
||||
if !ok || !endpoint.hasEndpointARN() {
|
||||
updateBucketEndpointFromParams(req)
|
||||
return
|
||||
}
|
||||
|
||||
resource, err := endpoint.getEndpointARN()
|
||||
if err != nil {
|
||||
req.Error = newInvalidARNError(nil, err)
|
||||
return
|
||||
}
|
||||
|
||||
resReq := resourceRequest{
|
||||
Resource: resource,
|
||||
Request: req,
|
||||
}
|
||||
|
||||
if resReq.IsCrossPartition() {
|
||||
req.Error = newClientPartitionMismatchError(resource,
|
||||
req.ClientInfo.PartitionID, aws.StringValue(req.Config.Region), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if !resReq.AllowCrossRegion() && resReq.IsCrossRegion() {
|
||||
req.Error = newClientRegionMismatchError(resource,
|
||||
req.ClientInfo.PartitionID, aws.StringValue(req.Config.Region), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if resReq.HasCustomEndpoint() {
|
||||
req.Error = newInvalidARNWithCustomEndpointError(resource, nil)
|
||||
return
|
||||
}
|
||||
|
||||
switch tv := resource.(type) {
|
||||
case arn.AccessPointARN:
|
||||
err = updateRequestAccessPointEndpoint(req, tv)
|
||||
if err != nil {
|
||||
req.Error = err
|
||||
}
|
||||
default:
|
||||
req.Error = newInvalidARNError(resource, nil)
|
||||
}
|
||||
}
|
||||
|
||||
type resourceRequest struct {
|
||||
Resource arn.Resource
|
||||
Request *request.Request
|
||||
}
|
||||
|
||||
func (r resourceRequest) ARN() awsarn.ARN {
|
||||
return r.Resource.GetARN()
|
||||
}
|
||||
|
||||
func (r resourceRequest) AllowCrossRegion() bool {
|
||||
return aws.BoolValue(r.Request.Config.S3UseARNRegion)
|
||||
}
|
||||
|
||||
func (r resourceRequest) UseFIPS() bool {
|
||||
return isFIPS(aws.StringValue(r.Request.Config.Region))
|
||||
}
|
||||
|
||||
func (r resourceRequest) IsCrossPartition() bool {
|
||||
return r.Request.ClientInfo.PartitionID != r.Resource.GetARN().Partition
|
||||
}
|
||||
|
||||
func (r resourceRequest) IsCrossRegion() bool {
|
||||
return isCrossRegion(r.Request, r.Resource.GetARN().Region)
|
||||
}
|
||||
|
||||
func (r resourceRequest) HasCustomEndpoint() bool {
|
||||
return len(aws.StringValue(r.Request.Config.Endpoint)) > 0
|
||||
}
|
||||
|
||||
func isFIPS(clientRegion string) bool {
|
||||
return strings.HasPrefix(clientRegion, "fips-") || strings.HasSuffix(clientRegion, "-fips")
|
||||
}
|
||||
func isCrossRegion(req *request.Request, otherRegion string) bool {
|
||||
return req.ClientInfo.SigningRegion != otherRegion
|
||||
}
|
||||
|
||||
func updateBucketEndpointFromParams(r *request.Request) {
|
||||
bucket, ok := bucketNameFromReqParams(r.Params)
|
||||
if !ok {
|
||||
// Ignore operation requests if the bucket name was not provided
|
||||
// if this is an input validation error the validation handler
|
||||
// will report it.
|
||||
return
|
||||
}
|
||||
updateEndpointForS3Config(r, bucket)
|
||||
}
|
||||
|
||||
func updateRequestAccessPointEndpoint(req *request.Request, accessPoint arn.AccessPointARN) error {
|
||||
// Accelerate not supported
|
||||
if aws.BoolValue(req.Config.S3UseAccelerate) {
|
||||
return newClientConfiguredForAccelerateError(accessPoint,
|
||||
req.ClientInfo.PartitionID, aws.StringValue(req.Config.Region), nil)
|
||||
}
|
||||
|
||||
// Ignore the disable host prefix for access points since custom endpoints
|
||||
// are not supported.
|
||||
req.Config.DisableEndpointHostPrefix = aws.Bool(false)
|
||||
|
||||
if err := accessPointEndpointBuilder(accessPoint).Build(req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
removeBucketFromPath(req.HTTPRequest.URL)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeBucketFromPath(u *url.URL) {
|
||||
u.Path = strings.Replace(u.Path, "/{Bucket}", "", -1)
|
||||
if u.Path == "" {
|
||||
u.Path = "/"
|
||||
}
|
||||
}
|
||||
|
||||
type accessPointEndpointBuilder arn.AccessPointARN
|
||||
|
||||
const (
|
||||
accessPointPrefixLabel = "accesspoint"
|
||||
accountIDPrefixLabel = "accountID"
|
||||
accesPointPrefixTemplate = "{" + accessPointPrefixLabel + "}-{" + accountIDPrefixLabel + "}."
|
||||
)
|
||||
|
||||
func (a accessPointEndpointBuilder) Build(req *request.Request) error {
|
||||
resolveRegion := arn.AccessPointARN(a).Region
|
||||
cfgRegion := aws.StringValue(req.Config.Region)
|
||||
|
||||
if isFIPS(cfgRegion) {
|
||||
if aws.BoolValue(req.Config.S3UseARNRegion) && isCrossRegion(req, resolveRegion) {
|
||||
// FIPS with cross region is not supported, the SDK must fail
|
||||
// because there is no well defined method for SDK to construct a
|
||||
// correct FIPS endpoint.
|
||||
return newClientConfiguredForCrossRegionFIPSError(arn.AccessPointARN(a),
|
||||
req.ClientInfo.PartitionID, cfgRegion, nil)
|
||||
}
|
||||
resolveRegion = cfgRegion
|
||||
}
|
||||
|
||||
endpoint, err := resolveRegionalEndpoint(req, resolveRegion)
|
||||
if err != nil {
|
||||
return newFailedToResolveEndpointError(arn.AccessPointARN(a),
|
||||
req.ClientInfo.PartitionID, cfgRegion, err)
|
||||
}
|
||||
|
||||
if err = updateRequestEndpoint(req, endpoint.URL); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
const serviceEndpointLabel = "s3-accesspoint"
|
||||
|
||||
// dualstack provided by endpoint resolver
|
||||
cfgHost := req.HTTPRequest.URL.Host
|
||||
if strings.HasPrefix(cfgHost, "s3") {
|
||||
req.HTTPRequest.URL.Host = serviceEndpointLabel + cfgHost[2:]
|
||||
}
|
||||
|
||||
protocol.HostPrefixBuilder{
|
||||
Prefix: accesPointPrefixTemplate,
|
||||
LabelsFn: a.hostPrefixLabelValues,
|
||||
}.Build(req)
|
||||
|
||||
req.ClientInfo.SigningName = endpoint.SigningName
|
||||
req.ClientInfo.SigningRegion = endpoint.SigningRegion
|
||||
|
||||
err = protocol.ValidateEndpointHost(req.Operation.Name, req.HTTPRequest.URL.Host)
|
||||
if err != nil {
|
||||
return newInvalidARNError(arn.AccessPointARN(a), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a accessPointEndpointBuilder) hostPrefixLabelValues() map[string]string {
|
||||
return map[string]string{
|
||||
accessPointPrefixLabel: arn.AccessPointARN(a).AccessPointName,
|
||||
accountIDPrefixLabel: arn.AccessPointARN(a).AccountID,
|
||||
}
|
||||
}
|
||||
|
||||
func resolveRegionalEndpoint(r *request.Request, region string) (endpoints.ResolvedEndpoint, error) {
|
||||
return r.Config.EndpointResolver.EndpointFor(EndpointsID, region, func(opts *endpoints.Options) {
|
||||
opts.DisableSSL = aws.BoolValue(r.Config.DisableSSL)
|
||||
opts.UseDualStack = aws.BoolValue(r.Config.UseDualStack)
|
||||
opts.S3UsEast1RegionalEndpoint = endpoints.RegionalS3UsEast1Endpoint
|
||||
})
|
||||
}
|
||||
|
||||
func updateRequestEndpoint(r *request.Request, endpoint string) (err error) {
|
||||
endpoint = endpoints.AddScheme(endpoint, aws.BoolValue(r.Config.DisableSSL))
|
||||
|
||||
r.HTTPRequest.URL, err = url.Parse(endpoint + r.Operation.HTTPPath)
|
||||
if err != nil {
|
||||
return awserr.New(request.ErrCodeSerialization,
|
||||
"failed to parse endpoint URL", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
151
vendor/github.com/aws/aws-sdk-go/service/s3/endpoint_errors.go
generated
vendored
Normal file
151
vendor/github.com/aws/aws-sdk-go/service/s3/endpoint_errors.go
generated
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
package s3
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/service/s3/internal/arn"
|
||||
)
|
||||
|
||||
const (
|
||||
invalidARNErrorErrCode = "InvalidARNError"
|
||||
configurationErrorErrCode = "ConfigurationError"
|
||||
)
|
||||
|
||||
type invalidARNError struct {
|
||||
message string
|
||||
resource arn.Resource
|
||||
origErr error
|
||||
}
|
||||
|
||||
func (e invalidARNError) Error() string {
|
||||
var extra string
|
||||
if e.resource != nil {
|
||||
extra = "ARN: " + e.resource.String()
|
||||
}
|
||||
return awserr.SprintError(e.Code(), e.Message(), extra, e.origErr)
|
||||
}
|
||||
|
||||
func (e invalidARNError) Code() string {
|
||||
return invalidARNErrorErrCode
|
||||
}
|
||||
|
||||
func (e invalidARNError) Message() string {
|
||||
return e.message
|
||||
}
|
||||
|
||||
func (e invalidARNError) OrigErr() error {
|
||||
return e.origErr
|
||||
}
|
||||
|
||||
func newInvalidARNError(resource arn.Resource, err error) invalidARNError {
|
||||
return invalidARNError{
|
||||
message: "invalid ARN",
|
||||
origErr: err,
|
||||
resource: resource,
|
||||
}
|
||||
}
|
||||
|
||||
func newInvalidARNWithCustomEndpointError(resource arn.Resource, err error) invalidARNError {
|
||||
return invalidARNError{
|
||||
message: "resource ARN not supported with custom client endpoints",
|
||||
origErr: err,
|
||||
resource: resource,
|
||||
}
|
||||
}
|
||||
|
||||
// ARN not supported for the target partition
|
||||
func newInvalidARNWithUnsupportedPartitionError(resource arn.Resource, err error) invalidARNError {
|
||||
return invalidARNError{
|
||||
message: "resource ARN not supported for the target ARN partition",
|
||||
origErr: err,
|
||||
resource: resource,
|
||||
}
|
||||
}
|
||||
|
||||
type configurationError struct {
|
||||
message string
|
||||
resource arn.Resource
|
||||
clientPartitionID string
|
||||
clientRegion string
|
||||
origErr error
|
||||
}
|
||||
|
||||
func (e configurationError) Error() string {
|
||||
extra := fmt.Sprintf("ARN: %s, client partition: %s, client region: %s",
|
||||
e.resource, e.clientPartitionID, e.clientRegion)
|
||||
|
||||
return awserr.SprintError(e.Code(), e.Message(), extra, e.origErr)
|
||||
}
|
||||
|
||||
func (e configurationError) Code() string {
|
||||
return configurationErrorErrCode
|
||||
}
|
||||
|
||||
func (e configurationError) Message() string {
|
||||
return e.message
|
||||
}
|
||||
|
||||
func (e configurationError) OrigErr() error {
|
||||
return e.origErr
|
||||
}
|
||||
|
||||
func newClientPartitionMismatchError(resource arn.Resource, clientPartitionID, clientRegion string, err error) configurationError {
|
||||
return configurationError{
|
||||
message: "client partition does not match provided ARN partition",
|
||||
origErr: err,
|
||||
resource: resource,
|
||||
clientPartitionID: clientPartitionID,
|
||||
clientRegion: clientRegion,
|
||||
}
|
||||
}
|
||||
|
||||
func newClientRegionMismatchError(resource arn.Resource, clientPartitionID, clientRegion string, err error) configurationError {
|
||||
return configurationError{
|
||||
message: "client region does not match provided ARN region",
|
||||
origErr: err,
|
||||
resource: resource,
|
||||
clientPartitionID: clientPartitionID,
|
||||
clientRegion: clientRegion,
|
||||
}
|
||||
}
|
||||
|
||||
func newFailedToResolveEndpointError(resource arn.Resource, clientPartitionID, clientRegion string, err error) configurationError {
|
||||
return configurationError{
|
||||
message: "endpoint resolver failed to find an endpoint for the provided ARN region",
|
||||
origErr: err,
|
||||
resource: resource,
|
||||
clientPartitionID: clientPartitionID,
|
||||
clientRegion: clientRegion,
|
||||
}
|
||||
}
|
||||
|
||||
func newClientConfiguredForFIPSError(resource arn.Resource, clientPartitionID, clientRegion string, err error) configurationError {
|
||||
return configurationError{
|
||||
message: "client configured for fips but cross-region resource ARN provided",
|
||||
origErr: err,
|
||||
resource: resource,
|
||||
clientPartitionID: clientPartitionID,
|
||||
clientRegion: clientRegion,
|
||||
}
|
||||
}
|
||||
|
||||
func newClientConfiguredForAccelerateError(resource arn.Resource, clientPartitionID, clientRegion string, err error) configurationError {
|
||||
return configurationError{
|
||||
message: "client configured for S3 Accelerate but is supported with resource ARN",
|
||||
origErr: err,
|
||||
resource: resource,
|
||||
clientPartitionID: clientPartitionID,
|
||||
clientRegion: clientRegion,
|
||||
}
|
||||
}
|
||||
|
||||
func newClientConfiguredForCrossRegionFIPSError(resource arn.Resource, clientPartitionID, clientRegion string, err error) configurationError {
|
||||
return configurationError{
|
||||
message: "client configured for FIPS with cross-region enabled but is supported with cross-region resource ARN",
|
||||
origErr: err,
|
||||
resource: resource,
|
||||
clientPartitionID: clientPartitionID,
|
||||
clientRegion: clientRegion,
|
||||
}
|
||||
}
|
||||
8
vendor/github.com/aws/aws-sdk-go/service/s3/errors.go
generated
vendored
8
vendor/github.com/aws/aws-sdk-go/service/s3/errors.go
generated
vendored
@@ -15,9 +15,9 @@ const (
|
||||
// "BucketAlreadyOwnedByYou".
|
||||
//
|
||||
// The bucket you tried to create already exists, and you own it. Amazon S3
|
||||
// returns this error in all AWS Regions except in the North Virginia region.
|
||||
// returns this error in all AWS Regions except in the North Virginia Region.
|
||||
// For legacy compatibility, if you re-create an existing bucket that you already
|
||||
// own in the North Virginia region, Amazon S3 returns 200 OK and resets the
|
||||
// own in the North Virginia Region, Amazon S3 returns 200 OK and resets the
|
||||
// bucket access control lists (ACLs).
|
||||
ErrCodeBucketAlreadyOwnedByYou = "BucketAlreadyOwnedByYou"
|
||||
|
||||
@@ -42,13 +42,13 @@ const (
|
||||
// ErrCodeObjectAlreadyInActiveTierError for service response error code
|
||||
// "ObjectAlreadyInActiveTierError".
|
||||
//
|
||||
// This operation is not allowed against this storage tier
|
||||
// This operation is not allowed against this storage tier.
|
||||
ErrCodeObjectAlreadyInActiveTierError = "ObjectAlreadyInActiveTierError"
|
||||
|
||||
// ErrCodeObjectNotInActiveTierError for service response error code
|
||||
// "ObjectNotInActiveTierError".
|
||||
//
|
||||
// The source object of the COPY operation is not in the active tier and is
|
||||
// only stored in Amazon Glacier.
|
||||
// only stored in Amazon S3 Glacier.
|
||||
ErrCodeObjectNotInActiveTierError = "ObjectNotInActiveTierError"
|
||||
)
|
||||
|
||||
43
vendor/github.com/aws/aws-sdk-go/service/s3/host_style_bucket.go
generated
vendored
43
vendor/github.com/aws/aws-sdk-go/service/s3/host_style_bucket.go
generated
vendored
@@ -30,10 +30,10 @@ var accelerateOpBlacklist = operationBlacklist{
|
||||
opListBuckets, opCreateBucket, opDeleteBucket,
|
||||
}
|
||||
|
||||
// Request handler to automatically add the bucket name to the endpoint domain
|
||||
// Automatically add the bucket name to the endpoint domain
|
||||
// if possible. This style of bucket is valid for all bucket names which are
|
||||
// DNS compatible and do not contain "."
|
||||
func updateEndpointForS3Config(r *request.Request) {
|
||||
func updateEndpointForS3Config(r *request.Request, bucketName string) {
|
||||
forceHostStyle := aws.BoolValue(r.Config.S3ForcePathStyle)
|
||||
accelerate := aws.BoolValue(r.Config.S3UseAccelerate)
|
||||
|
||||
@@ -43,45 +43,29 @@ func updateEndpointForS3Config(r *request.Request) {
|
||||
r.Config.Logger.Log("ERROR: aws.Config.S3UseAccelerate is not compatible with aws.Config.S3ForcePathStyle, ignoring S3ForcePathStyle.")
|
||||
}
|
||||
}
|
||||
updateEndpointForAccelerate(r)
|
||||
updateEndpointForAccelerate(r, bucketName)
|
||||
} else if !forceHostStyle && r.Operation.Name != opGetBucketLocation {
|
||||
updateEndpointForHostStyle(r)
|
||||
updateEndpointForHostStyle(r, bucketName)
|
||||
}
|
||||
}
|
||||
|
||||
func updateEndpointForHostStyle(r *request.Request) {
|
||||
bucket, ok := bucketNameFromReqParams(r.Params)
|
||||
if !ok {
|
||||
// Ignore operation requests if the bucketname was not provided
|
||||
// if this is an input validation error the validation handler
|
||||
// will report it.
|
||||
return
|
||||
}
|
||||
|
||||
if !hostCompatibleBucketName(r.HTTPRequest.URL, bucket) {
|
||||
func updateEndpointForHostStyle(r *request.Request, bucketName string) {
|
||||
if !hostCompatibleBucketName(r.HTTPRequest.URL, bucketName) {
|
||||
// bucket name must be valid to put into the host
|
||||
return
|
||||
}
|
||||
|
||||
moveBucketToHost(r.HTTPRequest.URL, bucket)
|
||||
moveBucketToHost(r.HTTPRequest.URL, bucketName)
|
||||
}
|
||||
|
||||
var (
|
||||
accelElem = []byte("s3-accelerate.dualstack.")
|
||||
)
|
||||
|
||||
func updateEndpointForAccelerate(r *request.Request) {
|
||||
bucket, ok := bucketNameFromReqParams(r.Params)
|
||||
if !ok {
|
||||
// Ignore operation requests if the bucketname was not provided
|
||||
// if this is an input validation error the validation handler
|
||||
// will report it.
|
||||
return
|
||||
}
|
||||
|
||||
if !hostCompatibleBucketName(r.HTTPRequest.URL, bucket) {
|
||||
func updateEndpointForAccelerate(r *request.Request, bucketName string) {
|
||||
if !hostCompatibleBucketName(r.HTTPRequest.URL, bucketName) {
|
||||
r.Error = awserr.New("InvalidParameterException",
|
||||
fmt.Sprintf("bucket name %s is not compatible with S3 Accelerate", bucket),
|
||||
fmt.Sprintf("bucket name %s is not compatible with S3 Accelerate", bucketName),
|
||||
nil)
|
||||
return
|
||||
}
|
||||
@@ -106,7 +90,7 @@ func updateEndpointForAccelerate(r *request.Request) {
|
||||
|
||||
r.HTTPRequest.URL.Host = strings.Join(parts, ".")
|
||||
|
||||
moveBucketToHost(r.HTTPRequest.URL, bucket)
|
||||
moveBucketToHost(r.HTTPRequest.URL, bucketName)
|
||||
}
|
||||
|
||||
// Attempts to retrieve the bucket name from the request input parameters.
|
||||
@@ -148,8 +132,5 @@ func dnsCompatibleBucketName(bucket string) bool {
|
||||
// moveBucketToHost moves the bucket name from the URI path to URL host.
|
||||
func moveBucketToHost(u *url.URL, bucket string) {
|
||||
u.Host = bucket + "." + u.Host
|
||||
u.Path = strings.Replace(u.Path, "/{Bucket}", "", -1)
|
||||
if u.Path == "" {
|
||||
u.Path = "/"
|
||||
}
|
||||
removeBucketFromPath(u)
|
||||
}
|
||||
|
||||
45
vendor/github.com/aws/aws-sdk-go/service/s3/internal/arn/accesspoint_arn.go
generated
vendored
Normal file
45
vendor/github.com/aws/aws-sdk-go/service/s3/internal/arn/accesspoint_arn.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
package arn
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws/arn"
|
||||
)
|
||||
|
||||
// AccessPointARN provides representation
|
||||
type AccessPointARN struct {
|
||||
arn.ARN
|
||||
AccessPointName string
|
||||
}
|
||||
|
||||
// GetARN returns the base ARN for the Access Point resource
|
||||
func (a AccessPointARN) GetARN() arn.ARN {
|
||||
return a.ARN
|
||||
}
|
||||
|
||||
// ParseAccessPointResource attempts to parse the ARN's resource as an
|
||||
// AccessPoint resource.
|
||||
func ParseAccessPointResource(a arn.ARN, resParts []string) (AccessPointARN, error) {
|
||||
if len(a.Region) == 0 {
|
||||
return AccessPointARN{}, InvalidARNError{a, "region not set"}
|
||||
}
|
||||
if len(a.AccountID) == 0 {
|
||||
return AccessPointARN{}, InvalidARNError{a, "account-id not set"}
|
||||
}
|
||||
if len(resParts) == 0 {
|
||||
return AccessPointARN{}, InvalidARNError{a, "resource-id not set"}
|
||||
}
|
||||
if len(resParts) > 1 {
|
||||
return AccessPointARN{}, InvalidARNError{a, "sub resource not supported"}
|
||||
}
|
||||
|
||||
resID := resParts[0]
|
||||
if len(strings.TrimSpace(resID)) == 0 {
|
||||
return AccessPointARN{}, InvalidARNError{a, "resource-id not set"}
|
||||
}
|
||||
|
||||
return AccessPointARN{
|
||||
ARN: a,
|
||||
AccessPointName: resID,
|
||||
}, nil
|
||||
}
|
||||
71
vendor/github.com/aws/aws-sdk-go/service/s3/internal/arn/arn.go
generated
vendored
Normal file
71
vendor/github.com/aws/aws-sdk-go/service/s3/internal/arn/arn.go
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
package arn
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws/arn"
|
||||
)
|
||||
|
||||
// Resource provides the interfaces abstracting ARNs of specific resource
|
||||
// types.
|
||||
type Resource interface {
|
||||
GetARN() arn.ARN
|
||||
String() string
|
||||
}
|
||||
|
||||
// ResourceParser provides the function for parsing an ARN's resource
|
||||
// component into a typed resource.
|
||||
type ResourceParser func(arn.ARN) (Resource, error)
|
||||
|
||||
// ParseResource parses an AWS ARN into a typed resource for the S3 API.
|
||||
func ParseResource(s string, resParser ResourceParser) (resARN Resource, err error) {
|
||||
a, err := arn.Parse(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(a.Partition) == 0 {
|
||||
return nil, InvalidARNError{a, "partition not set"}
|
||||
}
|
||||
if a.Service != "s3" {
|
||||
return nil, InvalidARNError{a, "service is not S3"}
|
||||
}
|
||||
if len(a.Resource) == 0 {
|
||||
return nil, InvalidARNError{a, "resource not set"}
|
||||
}
|
||||
|
||||
return resParser(a)
|
||||
}
|
||||
|
||||
// SplitResource splits the resource components by the ARN resource delimiters.
|
||||
func SplitResource(v string) []string {
|
||||
var parts []string
|
||||
var offset int
|
||||
|
||||
for offset <= len(v) {
|
||||
idx := strings.IndexAny(v[offset:], "/:")
|
||||
if idx < 0 {
|
||||
parts = append(parts, v[offset:])
|
||||
break
|
||||
}
|
||||
parts = append(parts, v[offset:idx+offset])
|
||||
offset += idx + 1
|
||||
}
|
||||
|
||||
return parts
|
||||
}
|
||||
|
||||
// IsARN returns whether the given string is an ARN
|
||||
func IsARN(s string) bool {
|
||||
return arn.IsARN(s)
|
||||
}
|
||||
|
||||
// InvalidARNError provides the error for an invalid ARN error.
|
||||
type InvalidARNError struct {
|
||||
ARN arn.ARN
|
||||
Reason string
|
||||
}
|
||||
|
||||
func (e InvalidARNError) Error() string {
|
||||
return "invalid Amazon S3 ARN, " + e.Reason + ", " + e.ARN.String()
|
||||
}
|
||||
35
vendor/github.com/aws/aws-sdk-go/service/s3/s3manager/upload_input.go
generated
vendored
35
vendor/github.com/aws/aws-sdk-go/service/s3/s3manager/upload_input.go
generated
vendored
@@ -21,7 +21,14 @@ type UploadInput struct {
|
||||
// The readable body payload to send to S3.
|
||||
Body io.Reader
|
||||
|
||||
// Name of the bucket to which the PUT operation was initiated.
|
||||
// Bucket name to which the PUT operation was initiated.
|
||||
//
|
||||
// When using this API with an access point, you must direct requests to the
|
||||
// access point hostname. The access point hostname takes the form AccessPointName-AccountId.s3-accesspoint.Region.amazonaws.com.
|
||||
// When using this operation using an access point through the AWS SDKs, you
|
||||
// provide the access point ARN in place of the bucket name. For more information
|
||||
// about access point ARNs, see Using Access Points (https://docs.aws.amazon.com/AmazonS3/latest/dev/using-access-points.html)
|
||||
// in the Amazon Simple Storage Service Developer Guide.
|
||||
//
|
||||
// Bucket is a required field
|
||||
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
|
||||
@@ -92,23 +99,25 @@ type UploadInput struct {
|
||||
|
||||
// Confirms that the requester knows that she or he will be charged for the
|
||||
// request. Bucket owners need not specify this parameter in their requests.
|
||||
// Documentation on downloading objects from requester pays buckets can be found
|
||||
// at http://docs.aws.amazon.com/AmazonS3/latest/dev/ObjectsinRequesterPaysBuckets.html
|
||||
// For information about downloading objects from Requester Pays buckets, see
|
||||
// Downloading Objects in Requestor Pays Buckets (https://docs.aws.amazon.com/http:/docs.aws.amazon.com/AmazonS3/latest/dev/ObjectsinRequesterPaysBuckets.html)
|
||||
// in the Amazon S3 Developer Guide.
|
||||
RequestPayer *string `location:"header" locationName:"x-amz-request-payer" type:"string" enum:"RequestPayer"`
|
||||
|
||||
// Specifies the algorithm to use to when encrypting the object (e.g., AES256).
|
||||
// Specifies the algorithm to use to when encrypting the object (for example,
|
||||
// AES256).
|
||||
SSECustomerAlgorithm *string `location:"header" locationName:"x-amz-server-side-encryption-customer-algorithm" type:"string"`
|
||||
|
||||
// Specifies the customer-provided encryption key for Amazon S3 to use in encrypting
|
||||
// data. This value is used to store the object and then it is discarded; Amazon
|
||||
// does not store the encryption key. The key must be appropriate for use with
|
||||
// the algorithm specified in the x-amz-server-side-encryption-customer-algorithm
|
||||
// S3 does not store the encryption key. The key must be appropriate for use
|
||||
// with the algorithm specified in the x-amz-server-side-encryption-customer-algorithm
|
||||
// header.
|
||||
SSECustomerKey *string `marshal-as:"blob" location:"header" locationName:"x-amz-server-side-encryption-customer-key" type:"string" sensitive:"true"`
|
||||
|
||||
// Specifies the 128-bit MD5 digest of the encryption key according to RFC 1321.
|
||||
// Amazon S3 uses this header for a message integrity check to ensure the encryption
|
||||
// key was transmitted without error.
|
||||
// Amazon S3 uses this header for a message integrity check to ensure that the
|
||||
// encryption key was transmitted without error.
|
||||
SSECustomerKeyMD5 *string `location:"header" locationName:"x-amz-server-side-encryption-customer-key-MD5" type:"string"`
|
||||
|
||||
// Specifies the AWS KMS Encryption Context to use for object encryption. The
|
||||
@@ -116,18 +125,18 @@ type UploadInput struct {
|
||||
// encryption context key-value pairs.
|
||||
SSEKMSEncryptionContext *string `location:"header" locationName:"x-amz-server-side-encryption-context" type:"string" sensitive:"true"`
|
||||
|
||||
// If the x-amz-server-side-encryption is present and has the value of aws:kms,
|
||||
// If x-amz-server-side-encryption is present and has the value of aws:kms,
|
||||
// this header specifies the ID of the AWS Key Management Service (AWS KMS)
|
||||
// customer master key (CMK) that was used for the object.
|
||||
//
|
||||
// If the value of x-amz-server-side-encryption is aws:kms, this header specifies
|
||||
// the ID of the AWS KMS CMK that will be used for the object. If you specify
|
||||
// x-amz-server-side-encryption:aws:kms, but do not provide x-amz-server-side-encryption-aws-kms-key-id,
|
||||
// x-amz-server-side-encryption:aws:kms, but do not providex-amz-server-side-encryption-aws-kms-key-id,
|
||||
// Amazon S3 uses the AWS managed CMK in AWS to protect the data.
|
||||
SSEKMSKeyId *string `location:"header" locationName:"x-amz-server-side-encryption-aws-kms-key-id" type:"string" sensitive:"true"`
|
||||
|
||||
// The Server-side encryption algorithm used when storing this object in S3
|
||||
// (e.g., AES256, aws:kms).
|
||||
// The server-side encryption algorithm used when storing this object in Amazon
|
||||
// S3 (for example, AES256, aws:kms).
|
||||
ServerSideEncryption *string `location:"header" locationName:"x-amz-server-side-encryption" type:"string" enum:"ServerSideEncryption"`
|
||||
|
||||
// If you don't specify, Standard is the default storage class. Amazon S3 supports
|
||||
@@ -141,7 +150,7 @@ type UploadInput struct {
|
||||
// If the bucket is configured as a website, redirects requests for this object
|
||||
// to another object in the same bucket or to an external URL. Amazon S3 stores
|
||||
// the value of this header in the object metadata. For information about object
|
||||
// metadata, see .
|
||||
// metadata, see Object Key and Metadata (https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html).
|
||||
//
|
||||
// In the following example, the request header sets the redirect to an object
|
||||
// (anotherPage.html) in the same bucket:
|
||||
|
||||
2
vendor/github.com/aws/aws-sdk-go/service/s3/service.go
generated
vendored
2
vendor/github.com/aws/aws-sdk-go/service/s3/service.go
generated
vendored
@@ -31,7 +31,7 @@ var initRequest func(*request.Request)
|
||||
const (
|
||||
ServiceName = "s3" // Name of service.
|
||||
EndpointsID = ServiceName // ID to lookup a service endpoint with.
|
||||
ServiceID = "S3" // ServiceID is a unique identifer of a specific service.
|
||||
ServiceID = "S3" // ServiceID is a unique identifier of a specific service.
|
||||
)
|
||||
|
||||
// New creates a new instance of the S3 client with a session.
|
||||
|
||||
2
vendor/github.com/aws/aws-sdk-go/service/sts/service.go
generated
vendored
2
vendor/github.com/aws/aws-sdk-go/service/sts/service.go
generated
vendored
@@ -31,7 +31,7 @@ var initRequest func(*request.Request)
|
||||
const (
|
||||
ServiceName = "sts" // Name of service.
|
||||
EndpointsID = ServiceName // ID to lookup a service endpoint with.
|
||||
ServiceID = "STS" // ServiceID is a unique identifer of a specific service.
|
||||
ServiceID = "STS" // ServiceID is a unique identifier of a specific service.
|
||||
)
|
||||
|
||||
// New creates a new instance of the STS client with a session.
|
||||
|
||||
119
vendor/github.com/klauspost/compress/huff0/decompress.go
generated
vendored
119
vendor/github.com/klauspost/compress/huff0/decompress.go
generated
vendored
@@ -15,8 +15,7 @@ type dTable struct {
|
||||
|
||||
// single-symbols decoding
|
||||
type dEntrySingle struct {
|
||||
byte uint8
|
||||
nBits uint8
|
||||
entry uint16
|
||||
}
|
||||
|
||||
// double-symbols decoding
|
||||
@@ -76,14 +75,15 @@ func ReadTable(in []byte, s *Scratch) (s2 *Scratch, remain []byte, err error) {
|
||||
}
|
||||
|
||||
// collect weight stats
|
||||
var rankStats [tableLogMax + 1]uint32
|
||||
var rankStats [16]uint32
|
||||
weightTotal := uint32(0)
|
||||
for _, v := range s.huffWeight[:s.symbolLen] {
|
||||
if v > tableLogMax {
|
||||
return s, nil, errors.New("corrupt input: weight too large")
|
||||
}
|
||||
rankStats[v]++
|
||||
weightTotal += (1 << (v & 15)) >> 1
|
||||
v2 := v & 15
|
||||
rankStats[v2]++
|
||||
weightTotal += (1 << v2) >> 1
|
||||
}
|
||||
if weightTotal == 0 {
|
||||
return s, nil, errors.New("corrupt input: weights zero")
|
||||
@@ -134,15 +134,17 @@ func ReadTable(in []byte, s *Scratch) (s2 *Scratch, remain []byte, err error) {
|
||||
if len(s.dt.single) != tSize {
|
||||
s.dt.single = make([]dEntrySingle, tSize)
|
||||
}
|
||||
|
||||
for n, w := range s.huffWeight[:s.symbolLen] {
|
||||
if w == 0 {
|
||||
continue
|
||||
}
|
||||
length := (uint32(1) << w) >> 1
|
||||
d := dEntrySingle{
|
||||
byte: uint8(n),
|
||||
nBits: s.actualTableLog + 1 - w,
|
||||
entry: uint16(s.actualTableLog+1-w) | (uint16(n) << 8),
|
||||
}
|
||||
for u := rankStats[w]; u < rankStats[w]+length; u++ {
|
||||
s.dt.single[u] = d
|
||||
single := s.dt.single[rankStats[w] : rankStats[w]+length]
|
||||
for i := range single {
|
||||
single[i] = d
|
||||
}
|
||||
rankStats[w] += length
|
||||
}
|
||||
@@ -167,12 +169,12 @@ func (s *Scratch) Decompress1X(in []byte) (out []byte, err error) {
|
||||
decode := func() byte {
|
||||
val := br.peekBitsFast(s.actualTableLog) /* note : actualTableLog >= 1 */
|
||||
v := s.dt.single[val]
|
||||
br.bitsRead += v.nBits
|
||||
return v.byte
|
||||
br.bitsRead += uint8(v.entry)
|
||||
return uint8(v.entry >> 8)
|
||||
}
|
||||
hasDec := func(v dEntrySingle) byte {
|
||||
br.bitsRead += v.nBits
|
||||
return v.byte
|
||||
br.bitsRead += uint8(v.entry)
|
||||
return uint8(v.entry >> 8)
|
||||
}
|
||||
|
||||
// Avoid bounds check by always having full sized table.
|
||||
@@ -269,8 +271,8 @@ func (s *Scratch) Decompress4X(in []byte, dstSize int) (out []byte, err error) {
|
||||
decode := func(br *bitReader) byte {
|
||||
val := br.peekBitsFast(s.actualTableLog) /* note : actualTableLog >= 1 */
|
||||
v := single[val&tlMask]
|
||||
br.bitsRead += v.nBits
|
||||
return v.byte
|
||||
br.bitsRead += uint8(v.entry)
|
||||
return uint8(v.entry >> 8)
|
||||
}
|
||||
|
||||
// Use temp table to avoid bound checks/append penalty.
|
||||
@@ -283,20 +285,67 @@ func (s *Scratch) Decompress4X(in []byte, dstSize int) (out []byte, err error) {
|
||||
bigloop:
|
||||
for {
|
||||
for i := range br {
|
||||
if br[i].off < 4 {
|
||||
br := &br[i]
|
||||
if br.off < 4 {
|
||||
break bigloop
|
||||
}
|
||||
br[i].fillFast()
|
||||
br.fillFast()
|
||||
}
|
||||
tmp[off] = decode(&br[0])
|
||||
tmp[off+bufoff] = decode(&br[1])
|
||||
tmp[off+bufoff*2] = decode(&br[2])
|
||||
tmp[off+bufoff*3] = decode(&br[3])
|
||||
tmp[off+1] = decode(&br[0])
|
||||
tmp[off+1+bufoff] = decode(&br[1])
|
||||
tmp[off+1+bufoff*2] = decode(&br[2])
|
||||
tmp[off+1+bufoff*3] = decode(&br[3])
|
||||
|
||||
{
|
||||
const stream = 0
|
||||
val := br[stream].peekBitsFast(s.actualTableLog)
|
||||
v := single[val&tlMask]
|
||||
br[stream].bitsRead += uint8(v.entry)
|
||||
|
||||
val2 := br[stream].peekBitsFast(s.actualTableLog)
|
||||
v2 := single[val2&tlMask]
|
||||
tmp[off+bufoff*stream+1] = uint8(v2.entry >> 8)
|
||||
tmp[off+bufoff*stream] = uint8(v.entry >> 8)
|
||||
br[stream].bitsRead += uint8(v2.entry)
|
||||
}
|
||||
|
||||
{
|
||||
const stream = 1
|
||||
val := br[stream].peekBitsFast(s.actualTableLog)
|
||||
v := single[val&tlMask]
|
||||
br[stream].bitsRead += uint8(v.entry)
|
||||
|
||||
val2 := br[stream].peekBitsFast(s.actualTableLog)
|
||||
v2 := single[val2&tlMask]
|
||||
tmp[off+bufoff*stream+1] = uint8(v2.entry >> 8)
|
||||
tmp[off+bufoff*stream] = uint8(v.entry >> 8)
|
||||
br[stream].bitsRead += uint8(v2.entry)
|
||||
}
|
||||
|
||||
{
|
||||
const stream = 2
|
||||
val := br[stream].peekBitsFast(s.actualTableLog)
|
||||
v := single[val&tlMask]
|
||||
br[stream].bitsRead += uint8(v.entry)
|
||||
|
||||
val2 := br[stream].peekBitsFast(s.actualTableLog)
|
||||
v2 := single[val2&tlMask]
|
||||
tmp[off+bufoff*stream+1] = uint8(v2.entry >> 8)
|
||||
tmp[off+bufoff*stream] = uint8(v.entry >> 8)
|
||||
br[stream].bitsRead += uint8(v2.entry)
|
||||
}
|
||||
|
||||
{
|
||||
const stream = 3
|
||||
val := br[stream].peekBitsFast(s.actualTableLog)
|
||||
v := single[val&tlMask]
|
||||
br[stream].bitsRead += uint8(v.entry)
|
||||
|
||||
val2 := br[stream].peekBitsFast(s.actualTableLog)
|
||||
v2 := single[val2&tlMask]
|
||||
tmp[off+bufoff*stream+1] = uint8(v2.entry >> 8)
|
||||
tmp[off+bufoff*stream] = uint8(v.entry >> 8)
|
||||
br[stream].bitsRead += uint8(v2.entry)
|
||||
}
|
||||
|
||||
off += 2
|
||||
|
||||
if off == bufoff {
|
||||
if bufoff > dstEvery {
|
||||
return nil, errors.New("corruption detected: stream overrun 1")
|
||||
@@ -367,7 +416,7 @@ func (s *Scratch) matches(ct cTable, w io.Writer) {
|
||||
broken++
|
||||
if enc.nBits == 0 {
|
||||
for _, dec := range dt {
|
||||
if dec.byte == byte(sym) {
|
||||
if uint8(dec.entry>>8) == byte(sym) {
|
||||
fmt.Fprintf(w, "symbol %x has decoder, but no encoder\n", sym)
|
||||
errs++
|
||||
break
|
||||
@@ -383,12 +432,12 @@ func (s *Scratch) matches(ct cTable, w io.Writer) {
|
||||
top := enc.val << ub
|
||||
// decoder looks at top bits.
|
||||
dec := dt[top]
|
||||
if dec.nBits != enc.nBits {
|
||||
fmt.Fprintf(w, "symbol 0x%x bit size mismatch (enc: %d, dec:%d).\n", sym, enc.nBits, dec.nBits)
|
||||
if uint8(dec.entry) != enc.nBits {
|
||||
fmt.Fprintf(w, "symbol 0x%x bit size mismatch (enc: %d, dec:%d).\n", sym, enc.nBits, uint8(dec.entry))
|
||||
errs++
|
||||
}
|
||||
if dec.byte != uint8(sym) {
|
||||
fmt.Fprintf(w, "symbol 0x%x decoder output mismatch (enc: %d, dec:%d).\n", sym, sym, dec.byte)
|
||||
if uint8(dec.entry>>8) != uint8(sym) {
|
||||
fmt.Fprintf(w, "symbol 0x%x decoder output mismatch (enc: %d, dec:%d).\n", sym, sym, uint8(dec.entry>>8))
|
||||
errs++
|
||||
}
|
||||
if errs > 0 {
|
||||
@@ -399,12 +448,12 @@ func (s *Scratch) matches(ct cTable, w io.Writer) {
|
||||
for i := uint16(0); i < (1 << ub); i++ {
|
||||
vval := top | i
|
||||
dec := dt[vval]
|
||||
if dec.nBits != enc.nBits {
|
||||
fmt.Fprintf(w, "symbol 0x%x bit size mismatch (enc: %d, dec:%d).\n", vval, enc.nBits, dec.nBits)
|
||||
if uint8(dec.entry) != enc.nBits {
|
||||
fmt.Fprintf(w, "symbol 0x%x bit size mismatch (enc: %d, dec:%d).\n", vval, enc.nBits, uint8(dec.entry))
|
||||
errs++
|
||||
}
|
||||
if dec.byte != uint8(sym) {
|
||||
fmt.Fprintf(w, "symbol 0x%x decoder output mismatch (enc: %d, dec:%d).\n", vval, sym, dec.byte)
|
||||
if uint8(dec.entry>>8) != uint8(sym) {
|
||||
fmt.Fprintf(w, "symbol 0x%x decoder output mismatch (enc: %d, dec:%d).\n", vval, sym, uint8(dec.entry>>8))
|
||||
errs++
|
||||
}
|
||||
if errs > 20 {
|
||||
|
||||
14
vendor/github.com/klauspost/compress/zstd/README.md
generated
vendored
14
vendor/github.com/klauspost/compress/zstd/README.md
generated
vendored
@@ -26,8 +26,12 @@ Godoc Documentation: https://godoc.org/github.com/klauspost/compress/zstd
|
||||
|
||||
### Status:
|
||||
|
||||
BETA - there may still be subtle bugs, but a wide variety of content has been tested.
|
||||
There may still be implementation specific stuff in regards to error handling that could lead to edge cases.
|
||||
STABLE - there may always be subtle bugs, a wide variety of content has been tested and the library is actively
|
||||
used by several projects. This library is being continuously [fuzz-tested](https://github.com/klauspost/compress-fuzz),
|
||||
kindly supplied by [fuzzit.dev](https://fuzzit.dev/).
|
||||
|
||||
There may still be specific combinations of data types/size/settings that could lead to edge cases,
|
||||
so as always, testing is recommended.
|
||||
|
||||
For now, a high speed (fastest) and medium-fast (default) compressor has been implemented.
|
||||
|
||||
@@ -251,8 +255,12 @@ The converter `s` can be reused to avoid allocations, even after errors.
|
||||
|
||||
## Decompressor
|
||||
|
||||
STATUS: Release Candidate - there may still be subtle bugs, but a wide variety of content has been tested.
|
||||
Staus: STABLE - there may still be subtle bugs, but a wide variety of content has been tested.
|
||||
|
||||
This library is being continuously [fuzz-tested](https://github.com/klauspost/compress-fuzz),
|
||||
kindly supplied by [fuzzit.dev](https://fuzzit.dev/).
|
||||
The main purpose of the fuzz testing is to ensure that it is not possible to crash the decoder,
|
||||
or run it past its limits with ANY input provided.
|
||||
|
||||
### Usage
|
||||
|
||||
|
||||
4
vendor/github.com/klauspost/compress/zstd/blockdec.go
generated
vendored
4
vendor/github.com/klauspost/compress/zstd/blockdec.go
generated
vendored
@@ -89,6 +89,7 @@ type blockDec struct {
|
||||
sequenceBuf []seq
|
||||
tmp [4]byte
|
||||
err error
|
||||
decWG sync.WaitGroup
|
||||
}
|
||||
|
||||
func (b *blockDec) String() string {
|
||||
@@ -105,6 +106,7 @@ func newBlockDec(lowMem bool) *blockDec {
|
||||
input: make(chan struct{}, 1),
|
||||
history: make(chan *history, 1),
|
||||
}
|
||||
b.decWG.Add(1)
|
||||
go b.startDecoder()
|
||||
return &b
|
||||
}
|
||||
@@ -183,11 +185,13 @@ func (b *blockDec) Close() {
|
||||
close(b.input)
|
||||
close(b.history)
|
||||
close(b.result)
|
||||
b.decWG.Wait()
|
||||
}
|
||||
|
||||
// decodeAsync will prepare decoding the block when it receives input.
|
||||
// This will separate output and history.
|
||||
func (b *blockDec) startDecoder() {
|
||||
defer b.decWG.Done()
|
||||
for range b.input {
|
||||
//println("blockDec: Got block input")
|
||||
switch b.Type {
|
||||
|
||||
12
vendor/github.com/klauspost/compress/zstd/blockenc.go
generated
vendored
12
vendor/github.com/klauspost/compress/zstd/blockenc.go
generated
vendored
@@ -300,13 +300,13 @@ func (b *blockEnc) encodeRaw(a []byte) {
|
||||
}
|
||||
|
||||
// encodeLits can be used if the block is only litLen.
|
||||
func (b *blockEnc) encodeLits() error {
|
||||
func (b *blockEnc) encodeLits(raw bool) error {
|
||||
var bh blockHeader
|
||||
bh.setLast(b.last)
|
||||
bh.setSize(uint32(len(b.literals)))
|
||||
|
||||
// Don't compress extremely small blocks
|
||||
if len(b.literals) < 32 {
|
||||
if len(b.literals) < 32 || raw {
|
||||
if debug {
|
||||
println("Adding RAW block, length", len(b.literals))
|
||||
}
|
||||
@@ -438,9 +438,9 @@ func fuzzFseEncoder(data []byte) int {
|
||||
}
|
||||
|
||||
// encode will encode the block and put the output in b.output.
|
||||
func (b *blockEnc) encode() error {
|
||||
func (b *blockEnc) encode(raw bool) error {
|
||||
if len(b.sequences) == 0 {
|
||||
return b.encodeLits()
|
||||
return b.encodeLits(raw)
|
||||
}
|
||||
// We want some difference
|
||||
if len(b.literals) > (b.size - (b.size >> 5)) {
|
||||
@@ -458,10 +458,10 @@ func (b *blockEnc) encode() error {
|
||||
reUsed, single bool
|
||||
err error
|
||||
)
|
||||
if len(b.literals) >= 1024 {
|
||||
if len(b.literals) >= 1024 && !raw {
|
||||
// Use 4 Streams.
|
||||
out, reUsed, err = huff0.Compress4X(b.literals, b.litEnc)
|
||||
} else if len(b.literals) > 32 {
|
||||
} else if len(b.literals) > 32 && !raw {
|
||||
// Use 1 stream
|
||||
single = true
|
||||
out, reUsed, err = huff0.Compress1X(b.literals, b.litEnc)
|
||||
|
||||
4
vendor/github.com/klauspost/compress/zstd/encoder.go
generated
vendored
4
vendor/github.com/klauspost/compress/zstd/encoder.go
generated
vendored
@@ -262,7 +262,7 @@ func (e *Encoder) nextBlock(final bool) error {
|
||||
// If we got the exact same number of literals as input,
|
||||
// assume the literals cannot be compressed.
|
||||
if len(src) != len(blk.literals) || len(src) != e.o.blockSize {
|
||||
err = blk.encode()
|
||||
err = blk.encode(e.o.noEntropy)
|
||||
}
|
||||
switch err {
|
||||
case errIncompressible:
|
||||
@@ -473,7 +473,7 @@ func (e *Encoder) EncodeAll(src, dst []byte) []byte {
|
||||
// If we got the exact same number of literals as input,
|
||||
// assume the literals cannot be compressed.
|
||||
if len(blk.literals) != len(todo) || len(todo) != e.o.blockSize {
|
||||
err = blk.encode()
|
||||
err = blk.encode(e.o.noEntropy)
|
||||
}
|
||||
|
||||
switch err {
|
||||
|
||||
11
vendor/github.com/klauspost/compress/zstd/encoder_options.go
generated
vendored
11
vendor/github.com/klauspost/compress/zstd/encoder_options.go
generated
vendored
@@ -20,6 +20,7 @@ type encoderOptions struct {
|
||||
windowSize int
|
||||
level EncoderLevel
|
||||
fullZero bool
|
||||
noEntropy bool
|
||||
}
|
||||
|
||||
func (o *encoderOptions) setDefault() {
|
||||
@@ -202,6 +203,16 @@ func WithZeroFrames(b bool) EOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithNoEntropyCompression will always skip entropy compression of literals.
|
||||
// This can be useful if content has matches, but unlikely to benefit from entropy
|
||||
// compression. Usually the slight speed improvement is not worth enabling this.
|
||||
func WithNoEntropyCompression(b bool) EOption {
|
||||
return func(o *encoderOptions) error {
|
||||
o.noEntropy = b
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithSingleSegment will set the "single segment" flag when EncodeAll is used.
|
||||
// If this flag is set, data must be regenerated within a single continuous memory segment.
|
||||
// In this case, Window_Descriptor byte is skipped, but Frame_Content_Size is necessarily present.
|
||||
|
||||
3
vendor/github.com/klauspost/compress/zstd/framedec.go
generated
vendored
3
vendor/github.com/klauspost/compress/zstd/framedec.go
generated
vendored
@@ -478,9 +478,10 @@ func (d *frameDec) runDecoder(dst []byte, dec *blockDec) ([]byte, error) {
|
||||
if err == nil {
|
||||
if n != len(dst)-crcStart {
|
||||
err = io.ErrShortWrite
|
||||
} else {
|
||||
err = d.checkCRC()
|
||||
}
|
||||
}
|
||||
err = d.checkCRC()
|
||||
}
|
||||
}
|
||||
d.history.b = saved
|
||||
|
||||
8
vendor/github.com/klauspost/compress/zstd/snappy.go
generated
vendored
8
vendor/github.com/klauspost/compress/zstd/snappy.go
generated
vendored
@@ -111,7 +111,7 @@ func (r *SnappyConverter) Convert(in io.Reader, w io.Writer) (int64, error) {
|
||||
// Add empty last block
|
||||
r.block.reset(nil)
|
||||
r.block.last = true
|
||||
err := r.block.encodeLits()
|
||||
err := r.block.encodeLits(false)
|
||||
if err != nil {
|
||||
return written, err
|
||||
}
|
||||
@@ -178,7 +178,7 @@ func (r *SnappyConverter) Convert(in io.Reader, w io.Writer) (int64, error) {
|
||||
r.err = ErrSnappyCorrupt
|
||||
return written, r.err
|
||||
}
|
||||
err = r.block.encode()
|
||||
err = r.block.encode(false)
|
||||
switch err {
|
||||
case errIncompressible:
|
||||
r.block.popOffsets()
|
||||
@@ -188,7 +188,7 @@ func (r *SnappyConverter) Convert(in io.Reader, w io.Writer) (int64, error) {
|
||||
println("snappy.Decode:", err)
|
||||
return written, err
|
||||
}
|
||||
err = r.block.encodeLits()
|
||||
err = r.block.encodeLits(false)
|
||||
if err != nil {
|
||||
return written, err
|
||||
}
|
||||
@@ -235,7 +235,7 @@ func (r *SnappyConverter) Convert(in io.Reader, w io.Writer) (int64, error) {
|
||||
r.err = ErrSnappyCorrupt
|
||||
return written, r.err
|
||||
}
|
||||
err := r.block.encodeLits()
|
||||
err := r.block.encodeLits(false)
|
||||
if err != nil {
|
||||
return written, err
|
||||
}
|
||||
|
||||
79
vendor/golang.org/x/oauth2/transport.go
generated
vendored
79
vendor/golang.org/x/oauth2/transport.go
generated
vendored
@@ -6,7 +6,7 @@ package oauth2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
@@ -25,9 +25,6 @@ type Transport struct {
|
||||
// Base is the base RoundTripper used to make HTTP requests.
|
||||
// If nil, http.DefaultTransport is used.
|
||||
Base http.RoundTripper
|
||||
|
||||
mu sync.Mutex // guards modReq
|
||||
modReq map[*http.Request]*http.Request // original -> modified
|
||||
}
|
||||
|
||||
// RoundTrip authorizes and authenticates the request with an
|
||||
@@ -52,35 +49,22 @@ func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
|
||||
req2 := cloneRequest(req) // per RoundTripper contract
|
||||
token.SetAuthHeader(req2)
|
||||
t.setModReq(req, req2)
|
||||
res, err := t.base().RoundTrip(req2)
|
||||
|
||||
// req.Body is assumed to have been closed by the base RoundTripper.
|
||||
// req.Body is assumed to be closed by the base RoundTripper.
|
||||
reqBodyClosed = true
|
||||
|
||||
if err != nil {
|
||||
t.setModReq(req, nil)
|
||||
return nil, err
|
||||
}
|
||||
res.Body = &onEOFReader{
|
||||
rc: res.Body,
|
||||
fn: func() { t.setModReq(req, nil) },
|
||||
}
|
||||
return res, nil
|
||||
return t.base().RoundTrip(req2)
|
||||
}
|
||||
|
||||
// CancelRequest cancels an in-flight request by closing its connection.
|
||||
var cancelOnce sync.Once
|
||||
|
||||
// CancelRequest does nothing. It used to be a legacy cancellation mechanism
|
||||
// but now only it only logs on first use to warn that it's deprecated.
|
||||
//
|
||||
// Deprecated: use contexts for cancellation instead.
|
||||
func (t *Transport) CancelRequest(req *http.Request) {
|
||||
type canceler interface {
|
||||
CancelRequest(*http.Request)
|
||||
}
|
||||
if cr, ok := t.base().(canceler); ok {
|
||||
t.mu.Lock()
|
||||
modReq := t.modReq[req]
|
||||
delete(t.modReq, req)
|
||||
t.mu.Unlock()
|
||||
cr.CancelRequest(modReq)
|
||||
}
|
||||
cancelOnce.Do(func() {
|
||||
log.Printf("deprecated: golang.org/x/oauth2: Transport.CancelRequest no longer does anything; use contexts")
|
||||
})
|
||||
}
|
||||
|
||||
func (t *Transport) base() http.RoundTripper {
|
||||
@@ -90,19 +74,6 @@ func (t *Transport) base() http.RoundTripper {
|
||||
return http.DefaultTransport
|
||||
}
|
||||
|
||||
func (t *Transport) setModReq(orig, mod *http.Request) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.modReq == nil {
|
||||
t.modReq = make(map[*http.Request]*http.Request)
|
||||
}
|
||||
if mod == nil {
|
||||
delete(t.modReq, orig)
|
||||
} else {
|
||||
t.modReq[orig] = mod
|
||||
}
|
||||
}
|
||||
|
||||
// cloneRequest returns a clone of the provided *http.Request.
|
||||
// The clone is a shallow copy of the struct and its Header map.
|
||||
func cloneRequest(r *http.Request) *http.Request {
|
||||
@@ -116,29 +87,3 @@ func cloneRequest(r *http.Request) *http.Request {
|
||||
}
|
||||
return r2
|
||||
}
|
||||
|
||||
type onEOFReader struct {
|
||||
rc io.ReadCloser
|
||||
fn func()
|
||||
}
|
||||
|
||||
func (r *onEOFReader) Read(p []byte) (n int, err error) {
|
||||
n, err = r.rc.Read(p)
|
||||
if err == io.EOF {
|
||||
r.runFunc()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *onEOFReader) Close() error {
|
||||
err := r.rc.Close()
|
||||
r.runFunc()
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *onEOFReader) runFunc() {
|
||||
if fn := r.fn; fn != nil {
|
||||
fn()
|
||||
r.fn = nil
|
||||
}
|
||||
}
|
||||
|
||||
2
vendor/golang.org/x/sys/unix/mkall.sh
generated
vendored
2
vendor/golang.org/x/sys/unix/mkall.sh
generated
vendored
@@ -50,7 +50,7 @@ if [[ "$GOOS" = "linux" ]]; then
|
||||
# Use the Docker-based build system
|
||||
# Files generated through docker (use $cmd so you can Ctl-C the build or run)
|
||||
$cmd docker build --tag generate:$GOOS $GOOS
|
||||
$cmd docker run --interactive --tty --volume $(dirname "$(readlink -f "$0")"):/build generate:$GOOS
|
||||
$cmd docker run --interactive --tty --volume $(cd -- "$(dirname -- "$0")" && /bin/pwd):/build generate:$GOOS
|
||||
exit
|
||||
fi
|
||||
|
||||
|
||||
10
vendor/golang.org/x/sys/unix/syscall_netbsd.go
generated
vendored
10
vendor/golang.org/x/sys/unix/syscall_netbsd.go
generated
vendored
@@ -249,6 +249,14 @@ func Sendfile(outfd int, infd int, offset *int64, count int) (written int, err e
|
||||
return sendfile(outfd, infd, offset, count)
|
||||
}
|
||||
|
||||
func Fstatvfs(fd int, buf *Statvfs_t) (err error) {
|
||||
return Fstatvfs1(fd, buf, ST_WAIT)
|
||||
}
|
||||
|
||||
func Statvfs(path string, buf *Statvfs_t) (err error) {
|
||||
return Statvfs1(path, buf, ST_WAIT)
|
||||
}
|
||||
|
||||
/*
|
||||
* Exposed directly
|
||||
*/
|
||||
@@ -287,6 +295,7 @@ func Sendfile(outfd int, infd int, offset *int64, count int) (written int, err e
|
||||
//sys Fpathconf(fd int, name int) (val int, err error)
|
||||
//sys Fstat(fd int, stat *Stat_t) (err error)
|
||||
//sys Fstatat(fd int, path string, stat *Stat_t, flags int) (err error)
|
||||
//sys Fstatvfs1(fd int, buf *Statvfs_t) (err error) = SYS_FSTATVFS1
|
||||
//sys Fsync(fd int) (err error)
|
||||
//sys Ftruncate(fd int, length int64) (err error)
|
||||
//sysnb Getegid() (egid int)
|
||||
@@ -343,6 +352,7 @@ func Sendfile(outfd int, infd int, offset *int64, count int) (written int, err e
|
||||
//sysnb Settimeofday(tp *Timeval) (err error)
|
||||
//sysnb Setuid(uid int) (err error)
|
||||
//sys Stat(path string, stat *Stat_t) (err error)
|
||||
//sys Statvfs1(path string, buf *Statvfs_t) (err error) = SYS_STATVFS1
|
||||
//sys Symlink(path string, link string) (err error)
|
||||
//sys Symlinkat(oldpath string, newdirfd int, newpath string) (err error)
|
||||
//sys Sync() (err error)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by linux/mkall.go generatePtracePair(arm, arm64). DO NOT EDIT.
|
||||
// Code generated by linux/mkall.go generatePtracePair("arm", "arm64"). DO NOT EDIT.
|
||||
|
||||
// +build linux
|
||||
// +build arm arm64
|
||||
17
vendor/golang.org/x/sys/unix/zptrace_linux_arm64.go
generated
vendored
Normal file
17
vendor/golang.org/x/sys/unix/zptrace_linux_arm64.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
// Code generated by linux/mkall.go generatePtraceRegSet("arm64"). DO NOT EDIT.
|
||||
|
||||
package unix
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// PtraceGetRegSetArm64 fetches the registers used by arm64 binaries.
|
||||
func PtraceGetRegSetArm64(pid, addr int, regsout *PtraceRegsArm64) error {
|
||||
iovec := Iovec{(*byte)(unsafe.Pointer(regsout)), uint64(unsafe.Sizeof(*regsout))}
|
||||
return ptrace(PTRACE_GETREGSET, pid, uintptr(addr), uintptr(unsafe.Pointer(&iovec)))
|
||||
}
|
||||
|
||||
// PtraceSetRegSetArm64 sets the registers used by arm64 binaries.
|
||||
func PtraceSetRegSetArm64(pid, addr int, regs *PtraceRegsArm64) error {
|
||||
iovec := Iovec{(*byte)(unsafe.Pointer(regs)), uint64(unsafe.Sizeof(*regs))}
|
||||
return ptrace(PTRACE_SETREGSET, pid, uintptr(addr), uintptr(unsafe.Pointer(&iovec)))
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by linux/mkall.go generatePtracePair(mips, mips64). DO NOT EDIT.
|
||||
// Code generated by linux/mkall.go generatePtracePair("mips", "mips64"). DO NOT EDIT.
|
||||
|
||||
// +build linux
|
||||
// +build mips mips64
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by linux/mkall.go generatePtracePair(mipsle, mips64le). DO NOT EDIT.
|
||||
// Code generated by linux/mkall.go generatePtracePair("mipsle", "mips64le"). DO NOT EDIT.
|
||||
|
||||
// +build linux
|
||||
// +build mipsle mips64le
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by linux/mkall.go generatePtracePair(386, amd64). DO NOT EDIT.
|
||||
// Code generated by linux/mkall.go generatePtracePair("386", "amd64"). DO NOT EDIT.
|
||||
|
||||
// +build linux
|
||||
// +build 386 amd64
|
||||
25
vendor/golang.org/x/sys/unix/zsyscall_netbsd_386.go
generated
vendored
25
vendor/golang.org/x/sys/unix/zsyscall_netbsd_386.go
generated
vendored
@@ -926,6 +926,16 @@ func Fstatat(fd int, path string, stat *Stat_t, flags int) (err error) {
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Fstatvfs1(fd int, buf *Statvfs_t, flags int) (err error) {
|
||||
_, _, e1 := Syscall(SYS_FSTATVFS1, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(flags))
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Fsync(fd int) (err error) {
|
||||
_, _, e1 := Syscall(SYS_FSYNC, uintptr(fd), 0, 0)
|
||||
if e1 != 0 {
|
||||
@@ -1635,6 +1645,21 @@ func Stat(path string, stat *Stat_t) (err error) {
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Statvfs1(path string, buf *Statvfs_t, flags int) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, _, e1 := Syscall(SYS_STATVFS1, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(buf)), uintptr(flags))
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Symlink(path string, link string) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = BytePtrFromString(path)
|
||||
|
||||
25
vendor/golang.org/x/sys/unix/zsyscall_netbsd_amd64.go
generated
vendored
25
vendor/golang.org/x/sys/unix/zsyscall_netbsd_amd64.go
generated
vendored
@@ -926,6 +926,16 @@ func Fstatat(fd int, path string, stat *Stat_t, flags int) (err error) {
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Fstatvfs1(fd int, buf *Statvfs_t, flags int) (err error) {
|
||||
_, _, e1 := Syscall(SYS_FSTATVFS1, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(flags))
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Fsync(fd int) (err error) {
|
||||
_, _, e1 := Syscall(SYS_FSYNC, uintptr(fd), 0, 0)
|
||||
if e1 != 0 {
|
||||
@@ -1635,6 +1645,21 @@ func Stat(path string, stat *Stat_t) (err error) {
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Statvfs1(path string, buf *Statvfs_t, flags int) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, _, e1 := Syscall(SYS_STATVFS1, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(buf)), uintptr(flags))
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Symlink(path string, link string) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = BytePtrFromString(path)
|
||||
|
||||
25
vendor/golang.org/x/sys/unix/zsyscall_netbsd_arm.go
generated
vendored
25
vendor/golang.org/x/sys/unix/zsyscall_netbsd_arm.go
generated
vendored
@@ -926,6 +926,16 @@ func Fstatat(fd int, path string, stat *Stat_t, flags int) (err error) {
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Fstatvfs1(fd int, buf *Statvfs_t, flags int) (err error) {
|
||||
_, _, e1 := Syscall(SYS_FSTATVFS1, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(flags))
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Fsync(fd int) (err error) {
|
||||
_, _, e1 := Syscall(SYS_FSYNC, uintptr(fd), 0, 0)
|
||||
if e1 != 0 {
|
||||
@@ -1635,6 +1645,21 @@ func Stat(path string, stat *Stat_t) (err error) {
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Statvfs1(path string, buf *Statvfs_t, flags int) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, _, e1 := Syscall(SYS_STATVFS1, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(buf)), uintptr(flags))
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Symlink(path string, link string) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = BytePtrFromString(path)
|
||||
|
||||
25
vendor/golang.org/x/sys/unix/zsyscall_netbsd_arm64.go
generated
vendored
25
vendor/golang.org/x/sys/unix/zsyscall_netbsd_arm64.go
generated
vendored
@@ -926,6 +926,16 @@ func Fstatat(fd int, path string, stat *Stat_t, flags int) (err error) {
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Fstatvfs1(fd int, buf *Statvfs_t, flags int) (err error) {
|
||||
_, _, e1 := Syscall(SYS_FSTATVFS1, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(flags))
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Fsync(fd int) (err error) {
|
||||
_, _, e1 := Syscall(SYS_FSYNC, uintptr(fd), 0, 0)
|
||||
if e1 != 0 {
|
||||
@@ -1635,6 +1645,21 @@ func Stat(path string, stat *Stat_t) (err error) {
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Statvfs1(path string, buf *Statvfs_t, flags int) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, _, e1 := Syscall(SYS_STATVFS1, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(buf)), uintptr(flags))
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Symlink(path string, link string) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = BytePtrFromString(path)
|
||||
|
||||
32
vendor/golang.org/x/sys/unix/ztypes_netbsd_386.go
generated
vendored
32
vendor/golang.org/x/sys/unix/ztypes_netbsd_386.go
generated
vendored
@@ -78,6 +78,33 @@ type Stat_t struct {
|
||||
|
||||
type Statfs_t [0]byte
|
||||
|
||||
type Statvfs_t struct {
|
||||
Flag uint32
|
||||
Bsize uint32
|
||||
Frsize uint32
|
||||
Iosize uint32
|
||||
Blocks uint64
|
||||
Bfree uint64
|
||||
Bavail uint64
|
||||
Bresvd uint64
|
||||
Files uint64
|
||||
Ffree uint64
|
||||
Favail uint64
|
||||
Fresvd uint64
|
||||
Syncreads uint64
|
||||
Syncwrites uint64
|
||||
Asyncreads uint64
|
||||
Asyncwrites uint64
|
||||
Fsidx Fsid
|
||||
Fsid uint32
|
||||
Namemax uint32
|
||||
Owner uint32
|
||||
Spare [4]uint32
|
||||
Fstypename [32]byte
|
||||
Mntonname [1024]byte
|
||||
Mntfromname [1024]byte
|
||||
}
|
||||
|
||||
type Flock_t struct {
|
||||
Start int64
|
||||
Len int64
|
||||
@@ -103,6 +130,11 @@ const (
|
||||
PathMax = 0x400
|
||||
)
|
||||
|
||||
const (
|
||||
ST_WAIT = 0x1
|
||||
ST_NOWAIT = 0x2
|
||||
)
|
||||
|
||||
const (
|
||||
FADV_NORMAL = 0x0
|
||||
FADV_RANDOM = 0x1
|
||||
|
||||
33
vendor/golang.org/x/sys/unix/ztypes_netbsd_amd64.go
generated
vendored
33
vendor/golang.org/x/sys/unix/ztypes_netbsd_amd64.go
generated
vendored
@@ -82,6 +82,34 @@ type Stat_t struct {
|
||||
|
||||
type Statfs_t [0]byte
|
||||
|
||||
type Statvfs_t struct {
|
||||
Flag uint64
|
||||
Bsize uint64
|
||||
Frsize uint64
|
||||
Iosize uint64
|
||||
Blocks uint64
|
||||
Bfree uint64
|
||||
Bavail uint64
|
||||
Bresvd uint64
|
||||
Files uint64
|
||||
Ffree uint64
|
||||
Favail uint64
|
||||
Fresvd uint64
|
||||
Syncreads uint64
|
||||
Syncwrites uint64
|
||||
Asyncreads uint64
|
||||
Asyncwrites uint64
|
||||
Fsidx Fsid
|
||||
Fsid uint64
|
||||
Namemax uint64
|
||||
Owner uint32
|
||||
Spare [4]uint32
|
||||
Fstypename [32]byte
|
||||
Mntonname [1024]byte
|
||||
Mntfromname [1024]byte
|
||||
_ [4]byte
|
||||
}
|
||||
|
||||
type Flock_t struct {
|
||||
Start int64
|
||||
Len int64
|
||||
@@ -107,6 +135,11 @@ const (
|
||||
PathMax = 0x400
|
||||
)
|
||||
|
||||
const (
|
||||
ST_WAIT = 0x1
|
||||
ST_NOWAIT = 0x2
|
||||
)
|
||||
|
||||
const (
|
||||
FADV_NORMAL = 0x0
|
||||
FADV_RANDOM = 0x1
|
||||
|
||||
32
vendor/golang.org/x/sys/unix/ztypes_netbsd_arm.go
generated
vendored
32
vendor/golang.org/x/sys/unix/ztypes_netbsd_arm.go
generated
vendored
@@ -83,6 +83,33 @@ type Stat_t struct {
|
||||
|
||||
type Statfs_t [0]byte
|
||||
|
||||
type Statvfs_t struct {
|
||||
Flag uint32
|
||||
Bsize uint32
|
||||
Frsize uint32
|
||||
Iosize uint32
|
||||
Blocks uint64
|
||||
Bfree uint64
|
||||
Bavail uint64
|
||||
Bresvd uint64
|
||||
Files uint64
|
||||
Ffree uint64
|
||||
Favail uint64
|
||||
Fresvd uint64
|
||||
Syncreads uint64
|
||||
Syncwrites uint64
|
||||
Asyncreads uint64
|
||||
Asyncwrites uint64
|
||||
Fsidx Fsid
|
||||
Fsid uint32
|
||||
Namemax uint32
|
||||
Owner uint32
|
||||
Spare [4]uint32
|
||||
Fstypename [32]byte
|
||||
Mntonname [1024]byte
|
||||
Mntfromname [1024]byte
|
||||
}
|
||||
|
||||
type Flock_t struct {
|
||||
Start int64
|
||||
Len int64
|
||||
@@ -108,6 +135,11 @@ const (
|
||||
PathMax = 0x400
|
||||
)
|
||||
|
||||
const (
|
||||
ST_WAIT = 0x1
|
||||
ST_NOWAIT = 0x2
|
||||
)
|
||||
|
||||
const (
|
||||
FADV_NORMAL = 0x0
|
||||
FADV_RANDOM = 0x1
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user