mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-06-07 10:56:50 +03:00
Compare commits
83 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d186472081 | ||
|
|
7e2669f733 | ||
|
|
ff6d093e1b | ||
|
|
8311193293 | ||
|
|
80609fdf35 | ||
|
|
d7291487be | ||
|
|
f5a4731412 | ||
|
|
947009f459 | ||
|
|
4cf7238b73 | ||
|
|
c602284a99 | ||
|
|
2f35cf13c6 | ||
|
|
b4103e055a | ||
|
|
dde29c3c18 | ||
|
|
b3fcd726e3 | ||
|
|
5b6a9675d8 | ||
|
|
aa647637bf | ||
|
|
84860167d0 | ||
|
|
0101a2d7ca | ||
|
|
d2cab369b2 | ||
|
|
8905bc2a40 | ||
|
|
f9847352b4 | ||
|
|
d1a9d8aa1c | ||
|
|
b93b01bc6d | ||
|
|
dbd8beccfa | ||
|
|
6b23df2bec | ||
|
|
619d4959c7 | ||
|
|
70ea4e28a7 | ||
|
|
74a2943030 | ||
|
|
b3ec0fb5e2 | ||
|
|
9a3afea123 | ||
|
|
7705c19720 | ||
|
|
80c18b7275 | ||
|
|
cf87b810b7 | ||
|
|
538fdfe133 | ||
|
|
f52769f6ee | ||
|
|
e6a782498a | ||
|
|
a441cdd1d9 | ||
|
|
d0f08b4a58 | ||
|
|
aae8064285 | ||
|
|
7e173655ba | ||
|
|
92212f04da | ||
|
|
de60ad0cd6 | ||
|
|
7a8ef517ae | ||
|
|
d61bac9fd9 | ||
|
|
eac3da478e | ||
|
|
0890c780c2 | ||
|
|
fd1a6ce9ae | ||
|
|
9b90c841c6 | ||
|
|
93c87d28f6 | ||
|
|
23c55181ef | ||
|
|
b19ca3eb5f | ||
|
|
4e850cd6a7 | ||
|
|
8cb35974af | ||
|
|
a0380a0a91 | ||
|
|
3a68c47de0 | ||
|
|
697b6af10f | ||
|
|
93267f143f | ||
|
|
3412d5d138 | ||
|
|
27f7cca7ff | ||
|
|
cf38c3c62f | ||
|
|
c56b66210f | ||
|
|
82ffbcb9a6 | ||
|
|
82ccdfaa91 | ||
|
|
ab8f5545bc | ||
|
|
0eacea1de1 | ||
|
|
737d641920 | ||
|
|
4fc33163c4 | ||
|
|
f9f3afb6af | ||
|
|
8b32e7c3a0 | ||
|
|
1573ececb2 | ||
|
|
a249cd9d22 | ||
|
|
ef0e37cb9e | ||
|
|
0afd48d2ee | ||
|
|
42866fa754 | ||
|
|
827a3a7866 | ||
|
|
606585f7be | ||
|
|
21598ac417 | ||
|
|
894e5d2b9b | ||
|
|
415b1ddfb5 | ||
|
|
db7dd96346 | ||
|
|
ba48438b06 | ||
|
|
7882a0dbbf | ||
|
|
4fe67504f9 |
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
run: |
|
||||
go get -u golang.org/x/lint/golint
|
||||
go get -u github.com/kisielk/errcheck
|
||||
go get -u github.com/golangci/golangci-lint/cmd/golangci-lint
|
||||
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.27.0
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@master
|
||||
- name: Build
|
||||
|
||||
52
README.md
52
README.md
@@ -10,18 +10,25 @@
|
||||
|
||||
## VictoriaMetrics
|
||||
|
||||
VictoriaMetrics is fast, cost-effective and scalable time-series database. It can be used as long-term remote storage for Prometheus.
|
||||
VictoriaMetrics is fast, cost-effective and scalable time-series database.
|
||||
|
||||
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). 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).
|
||||
|
||||
See our [Wiki](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki) for additional documentation.
|
||||
|
||||
[Contact us](mailto:info@victoriametrics.com) if you need paid enterprise support for VictoriaMetrics.
|
||||
|
||||
|
||||
## Case studies and talks
|
||||
|
||||
* [Adidas](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/CaseStudies#adidas)
|
||||
* [CERN](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/CaseStudies#cern)
|
||||
* [COLOPL](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/CaseStudies#colopl)
|
||||
* [Zerodha](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/CaseStudies#zerodha)
|
||||
* [Wix.com](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/CaseStudies#wixcom)
|
||||
* [Wedos.com](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/CaseStudies#wedoscom)
|
||||
* [Synthesio](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/CaseStudies#synthesio)
|
||||
@@ -34,6 +41,8 @@ Cluster version is available [here](https://github.com/VictoriaMetrics/VictoriaM
|
||||
|
||||
## Prominent features
|
||||
|
||||
* VictoriaMetrics can be used as long-term storage for Prometheus or for [vmagent](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmagent/README.md).
|
||||
See [these docs](#prometheus-setup) for details.
|
||||
* Supports [Prometheus querying API](https://prometheus.io/docs/prometheus/latest/querying/api/), so it can be used as Prometheus drop-in replacement in Grafana.
|
||||
VictoriaMetrics implements [MetricsQL](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/MetricsQL) 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.
|
||||
@@ -116,6 +125,8 @@ Cluster version is available [here](https://github.com/VictoriaMetrics/VictoriaM
|
||||
* [Monitoring](#monitoring)
|
||||
* [Troubleshooting](#troubleshooting)
|
||||
* [Backfilling](#backfilling)
|
||||
* [Replication](#replication)
|
||||
* [Backups](#backups)
|
||||
* [Profiling](#profiling)
|
||||
* [Integrations](#integrations)
|
||||
* [Third-party contributions](#third-party-contributions)
|
||||
@@ -782,6 +793,8 @@ remote_write:
|
||||
kill -HUP `pidof prometheus`
|
||||
```
|
||||
|
||||
It is recommended to use [vmagent](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmagent/README.md) instead of Prometheus for highly loaded setups.
|
||||
|
||||
4) Now Prometheus should write data into all the configured `remote_write` urls in parallel.
|
||||
5) Set up [Promxy](https://github.com/jacksontj/promxy) in front of all the VictoriaMetrics replicas.
|
||||
6) Set up Prometheus datasource in Grafana that points to Promxy.
|
||||
@@ -792,6 +805,7 @@ to write data to `victoriametrics-addr-1`, while each `r2` should write data to
|
||||
Another option is to write data simultaneously from Prometheus HA pair to a pair of VictoriaMetrics instances
|
||||
with the enabled de-duplication. See [this section](#deduplication) for details.
|
||||
|
||||
|
||||
### Deduplication
|
||||
|
||||
VictoriaMetrics de-duplicates data points if `-dedup.minScrapeInterval` command-line flag
|
||||
@@ -809,6 +823,8 @@ Data is split in per-month subdirectories inside `<-storageDataPath>/data/small`
|
||||
Directories for months outside the configured retention are deleted on the first day of new month.
|
||||
In order to keep data according to `-retentionPeriod` max disk space usage is going to be `-retentionPeriod` + 1 month.
|
||||
For example if `-retentionPeriod` is set to 1, data for January is deleted on March 1st.
|
||||
It is safe to extend `-retentionPeriod` on existing data. If `-retentionPeriod` is set to lower
|
||||
value than before then data outside the configured period will be eventually deleted.
|
||||
|
||||
### Multiple retentions
|
||||
|
||||
@@ -874,6 +890,10 @@ Consider setting the following command-line flags:
|
||||
Explicitly set internal network interface for TCP and UDP ports for data ingestion with Graphite and OpenTSDB formats.
|
||||
For example, substitute `-graphiteListenAddr=:2003` with `-graphiteListenAddr=<internal_iface_ip>:2003`.
|
||||
|
||||
Prefer authorizing all the incoming requests from untrusted networks with [vmauth](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmauth/README.md)
|
||||
or similar auth proxy.
|
||||
|
||||
|
||||
### Tuning
|
||||
|
||||
* There is no need for VictoriaMetrics tuning since it uses reasonable defaults for command-line flags,
|
||||
@@ -910,6 +930,12 @@ The most interesting metrics are:
|
||||
* `sum(rate(vm_rows_inserted_total[5m]))` - ingestion rate, i.e. how many samples are inserted int the database per second.
|
||||
* `vm_free_disk_space_bytes` - free space left at `-storageDataPath`.
|
||||
* `sum(vm_data_size_bytes)` - the total size of data on disk.
|
||||
* `increase(vm_slow_row_inserts_total[5m])` - the number of slow inserts during the last 5 minutes.
|
||||
If this number remains high during extended periods of time, then it is likely more RAM is needed for optimal handling
|
||||
of the current number of active time series.
|
||||
* `increase(vm_slow_metric_name_loads_total[5m])` - the number of slow loads of metric names during the last 5 minutes.
|
||||
If this number remains high during extended periods of time, then it is likely more RAM is needed for optimal handling
|
||||
of the current number of active time series.
|
||||
|
||||
|
||||
### Troubleshooting
|
||||
@@ -922,8 +948,9 @@ The most interesting metrics are:
|
||||
|
||||
* If VictoriaMetrics works slowly and eats more than a CPU core per 100K ingested data points per second,
|
||||
then it is likely you have too many active time series for the current amount of RAM.
|
||||
VictoriaMetrics [exposes](#monitoring) `vm_slow_*` metrics, which could be used as an indicator of low amounts of RAM.
|
||||
It is recommended increasing the amount of RAM on the node with VictoriaMetrics in order to improve
|
||||
ingestion performance.
|
||||
ingestion and query performance in this case.
|
||||
Another option is to increase `-memory.allowedPercent` command-line flag value. Be careful with this
|
||||
option, since too big value for `-memory.allowedPercent` may result in high I/O usage.
|
||||
|
||||
@@ -970,6 +997,27 @@ the query cache, which could contain incomplete data cached during the backfilli
|
||||
Yet another solution is to increase `-search.cacheTimestampOffset` flag value in order to disable caching
|
||||
for data with timestamps close to the current time.
|
||||
|
||||
|
||||
### Replication
|
||||
|
||||
Single-node VictoriaMetrics relies on replicated durable persistent storage such as [Google Cloud disks](https://cloud.google.com/compute/docs/disks#pdspecs)
|
||||
or [Amazon EBS](https://aws.amazon.com/ebs/). It is also recommended making periodic backups,
|
||||
since [replication doesn't save from disaster](https://medium.com/@valyala/speeding-up-backups-for-big-time-series-databases-533c1a927883).
|
||||
See [backup docs](#backups) for details.
|
||||
|
||||
Cluster version of VictoriaMetrics supports replication. See [these docs](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/cluster/README.md#replication-and-data-safety)
|
||||
for details.
|
||||
|
||||
See also [high availability docs](#high-availability) and [docs about cluster version of VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/cluster/README.md).
|
||||
|
||||
|
||||
### Backups
|
||||
|
||||
VictoriaMetrics supports backups via [vmbackup](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmbackup/README.md)
|
||||
and [vmrestore](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmrestore/README.md) tools.
|
||||
We also provide provide `vmbackuper` tool for paid enterprise subscribers - see [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/466) for details.
|
||||
|
||||
|
||||
### Profiling
|
||||
|
||||
VictoriaMetrics provides handlers for collecting the following [Go profiles](https://blog.golang.org/profiling-go-programs):
|
||||
|
||||
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"flag"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert"
|
||||
@@ -25,6 +26,8 @@ var (
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Write flags and help message to stdout, since it is easier to grep or pipe.
|
||||
flag.CommandLine.SetOutput(os.Stdout)
|
||||
envflag.Parse()
|
||||
buildinfo.Init()
|
||||
logger.Init()
|
||||
|
||||
@@ -52,8 +52,9 @@ publish-vmagent:
|
||||
APP_NAME=vmagent $(MAKE) publish-via-docker
|
||||
|
||||
run-vmagent:
|
||||
mkdir -p vmagent-data
|
||||
DOCKER_OPTS='-v $(shell pwd)/vmagent-data:/vmagent-data' \
|
||||
mkdir -p vmagent-remotewrite-data
|
||||
DOCKER_OPTS='-v $(shell pwd)/vmagent-remotewrite-data:/vmagent-remotewrite-data' \
|
||||
ARGS='-remoteWrite.url=http://localhost:8428/api/v1/write' \
|
||||
APP_NAME=vmagent \
|
||||
$(MAKE) run-via-docker
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
## vmagent
|
||||
|
||||
`vmagent` is a tiny but brave agent, which helps you collecting metrics from various sources
|
||||
and storing them to [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics)
|
||||
or any other Prometheus-compatible storage system that supports `remote_write` protocol.
|
||||
`vmagent` is a tiny but brave agent, which helps you collect metrics from various sources
|
||||
and stores them in [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics)
|
||||
or any other Prometheus-compatible storage system that supports the `remote_write` protocol.
|
||||
|
||||
<img alt="vmagent" src="vmagent.png">
|
||||
|
||||
@@ -11,7 +11,7 @@ or any other Prometheus-compatible storage system that supports `remote_write` p
|
||||
|
||||
While VictoriaMetrics provides an efficient solution to store and observe metrics, our users needed something fast
|
||||
and RAM friendly to scrape metrics from Prometheus-compatible exporters to VictoriaMetrics.
|
||||
Also, we found that users’ infrastructure is like snowflakes - never alike, and we decided to add more flexibility
|
||||
Also, we found that users’ infrastructure are snowflakes - no two are alike, and we decided to add more flexibility
|
||||
to `vmagent` (like the ability to push metrics instead of pulling them). We did our best and plan to do even more.
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ to `vmagent` (like the ability to push metrics instead of pulling them). We did
|
||||
* Works in environments with unstable connections to remote storage. If the remote storage is unavailable, the collected metrics
|
||||
are buffered at `-remoteWrite.tmpDataPath`. The buffered metrics are sent to remote storage as soon as connection
|
||||
to remote storage is recovered. The maximum disk usage for the buffer can be limited with `-remoteWrite.maxDiskUsagePerURL`.
|
||||
* Uses lower amounts of RAM, CPU, disk IO and network bandwidth comparing to Prometheus.
|
||||
* Uses lower amounts of RAM, CPU, disk IO and network bandwidth compared to Prometheus.
|
||||
|
||||
|
||||
### Quick Start
|
||||
@@ -40,8 +40,7 @@ Just download `vmutils-*` archive from [releases page](https://github.com/Victor
|
||||
and pass the following flags to `vmagent` binary in order to start scraping Prometheus targets:
|
||||
|
||||
* `-promscrape.config` with the path to Prometheus config file (it is usually located at `/etc/prometheus/prometheus.yml`)
|
||||
* `-remoteWrite.url` with the remote storage endpoint such as VictoriaMetrics. Multiple `-remoteWrite.url` args can be set in parallel
|
||||
in order to replicate data concurrently to multiple remote storage systems.
|
||||
* `-remoteWrite.url` with the remote storage endpoint such as VictoriaMetrics. The `-remoteWrite.url` argument can be specified multiple times in order to replicate data concurrently to an arbitrary amount of remote storage systems.
|
||||
|
||||
Example command line:
|
||||
|
||||
@@ -49,7 +48,7 @@ Example command line:
|
||||
/path/to/vmagent -promscrape.config=/path/to/prometheus.yml -remoteWrite.url=https://victoria-metrics-host:8428/api/v1/write
|
||||
```
|
||||
|
||||
If you need collecting only Influx data, then the following command line would be enough:
|
||||
If you only need to collect Influx data, then the following is sufficient:
|
||||
|
||||
```
|
||||
/path/to/vmagent -remoteWrite.url=https://victoria-metrics-host:8428/api/v1/write
|
||||
@@ -79,14 +78,14 @@ See [the corresponding Makefile rules](https://github.com/VictoriaMetrics/Victor
|
||||
#### Drop-in replacement for Prometheus
|
||||
|
||||
If you use Prometheus only for scraping metrics from various targets and forwarding these metrics to remote storage,
|
||||
then `vmagent` can replace such Prometheus setup. Usually `vmagent` requires lower amounts of RAM, CPU and network bandwidth comparing to Prometheus for such setup.
|
||||
then `vmagent` can replace such Prometheus setup. Usually `vmagent` requires lower amounts of RAM, CPU and network bandwidth comparing to Prometheus for such a setup.
|
||||
See [these docs](#how-to-collect-metrics-in-prometheus-format) for details.
|
||||
|
||||
|
||||
#### Replication and high availability
|
||||
|
||||
`vmagent` replicates the collected metrics among multiple remote storage instances configured via `-remoteWrite.url` args.
|
||||
If a single remote storage instance temporarily goes out of service, then the collected data remains available in another remote storage instances.
|
||||
If a single remote storage instance temporarily is out of service, then the collected data remains available in another remote storage instances.
|
||||
`vmagent` buffers the collected data in files at `-remoteWrite.tmpDataPath` until the remote storage becomes available again.
|
||||
Then it sends the buffered data to the remote storage in order to prevent data gaps in the remote storage.
|
||||
|
||||
@@ -94,13 +93,13 @@ Then it sends the buffered data to the remote storage in order to prevent data g
|
||||
#### Relabeling and filtering
|
||||
|
||||
`vmagent` can add, remove or update labels on the collected data before sending it to remote storage. Additionally,
|
||||
it can remove unneeded samples via Prometheus-like relabeling before sending the collected data to remote storage.
|
||||
it can remove unwanted samples via Prometheus-like relabeling before sending the collected data to remote storage.
|
||||
See [these docs](#relabeling) for details.
|
||||
|
||||
|
||||
#### Splitting data streams among multiple systems
|
||||
|
||||
`vmagent` supports splitting of the collected data among muliple destinations with the help of `-remoteWrite.urlRelabelConfig`,
|
||||
`vmagent` supports splitting the collected data between muliple destinations with the help of `-remoteWrite.urlRelabelConfig`,
|
||||
which is applied independently for each configured `-remoteWrite.url` destination. For instance, it is possible to replicate or split
|
||||
data among long-term remote storage, short-term remote storage and real-time analytical system [built on top of Kafka](https://github.com/Telefonica/prometheus-kafka-adapter).
|
||||
Note that each destination can receive its own subset of the collected data thanks to per-destination relabeling via `-remoteWrite.urlRelabelConfig`.
|
||||
@@ -148,6 +147,10 @@ The following scrape types in [scrape_config](https://prometheus.io/docs/prometh
|
||||
* `dns_sd_configs` - for scraping targets discovered from DNS records (SRV, A and AAAA).
|
||||
See [dns_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#dns_sd_config) for details.
|
||||
|
||||
Note that `vmagent` doesn't support `refresh_interval` option these scrape configs. Use the corresponding `-promscrape.*CheckInterval`
|
||||
command-line flag instead. For example, `-promscrape.consulSDCheckInterval=60s` sets `refresh_interval` for all the `consul_sd_configs`
|
||||
entries to 60s. Run `vmagent -help` in order to see default values for `-promscrape.*CheckInterval` flags.
|
||||
|
||||
|
||||
File feature requests at [our issue tracker](https://github.com/VictoriaMetrics/VictoriaMetrics/issues) if you need other service discovery mechanisms to be supported by `vmagent`.
|
||||
|
||||
@@ -200,12 +203,12 @@ either via `vmagent` itself or via Prometheus, so the exported metrics could be
|
||||
* When `vmagent` scrapes many unreliable targets, it can flood error log with scrape errors. These errors can be suppressed
|
||||
by passing `-promscrape.suppressScrapeErrors` command-line flag to `vmagent`. The most recent scrape error per each target can be observed at `http://vmagent-host:8429/targets`.
|
||||
|
||||
* It is recommended increasing `-remoteWrite.queues` if `vmagent` collects more than 100K samples per second
|
||||
* It is recommended to increase `-remoteWrite.queues` if `vmagent` collects more than 100K samples per second
|
||||
and `vmagent_remotewrite_pending_data_bytes` metric exported at `http://vmagent-host:8429/metrics` page constantly grows.
|
||||
|
||||
* `vmagent` buffers scraped data at `-remoteWrite.tmpDataPath` directory until it is sent to `-remoteWrite.url`.
|
||||
The directory can grow big when remote storage is unavailable during extended periods of time and if `-remoteWrite.maxDiskUsagePerURL` isn't set.
|
||||
If you don't want sending all the data from the directory to remote storage, just stop `vmagent` and delete the directory.
|
||||
The directory can grow large when remote storage is unavailable for extended periods of time and if `-remoteWrite.maxDiskUsagePerURL` isn't set.
|
||||
If you don't want to send all the data from the directory to remote storage, simply stop `vmagent` and delete the directory.
|
||||
|
||||
|
||||
### How to build from sources
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -39,6 +40,8 @@ var (
|
||||
"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")
|
||||
dryRun = flag.Bool("dryRun", false, "Whether to check only config files without running vmagent. The following files are checked: "+
|
||||
"-promscrape.config, -remoteWrite.relabelConfig, -remoteWrite.urlRelabelConfig . See also -promscrape.config.dryRun")
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -49,9 +52,26 @@ var (
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Write flags and help message to stdout, since it is easier to grep or pipe.
|
||||
flag.CommandLine.SetOutput(os.Stdout)
|
||||
envflag.Parse()
|
||||
buildinfo.Init()
|
||||
logger.Init()
|
||||
|
||||
if *dryRun {
|
||||
if err := flag.Set("promscrape.config.strictParse", "true"); err != nil {
|
||||
logger.Panicf("BUG: cannot set promscrape.config.strictParse=true: %s", err)
|
||||
}
|
||||
if err := remotewrite.CheckRelabelConfigs(); err != nil {
|
||||
logger.Fatalf("error when checking relabel configs: %s", err)
|
||||
}
|
||||
if err := promscrape.CheckConfig(); err != nil {
|
||||
logger.Fatalf("error when checking Prometheus config: %s", err)
|
||||
}
|
||||
logger.Infof("all the configs are ok; exitting with 0 status code")
|
||||
return
|
||||
}
|
||||
|
||||
logger.Infof("starting vmagent at %q...", *httpListenAddr)
|
||||
startTime := time.Now()
|
||||
remotewrite.Init()
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/persistentqueue"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
@@ -15,7 +15,8 @@ import (
|
||||
|
||||
var (
|
||||
flushInterval = flag.Duration("remoteWrite.flushInterval", time.Second, "Interval for flushing the data to remote storage. "+
|
||||
"Higher value reduces network bandwidth usage at the cost of delayed push of scraped data to remote storage")
|
||||
"Higher value reduces network bandwidth usage at the cost of delayed push of scraped data to remote storage. "+
|
||||
"Minimum supported interval is 1 second")
|
||||
maxUnpackedBlockSize = flag.Int("remoteWrite.maxBlockSize", 32*1024*1024, "The maximum size in bytes of unpacked request to send to remote storage. "+
|
||||
"It shouldn't exceed -maxInsertRequestSize from VictoriaMetrics")
|
||||
)
|
||||
@@ -55,6 +56,10 @@ func (ps *pendingSeries) Push(tss []prompbmarshal.TimeSeries) {
|
||||
}
|
||||
|
||||
func (ps *pendingSeries) periodicFlusher() {
|
||||
flushSeconds := int64(flushInterval.Seconds())
|
||||
if flushSeconds <= 0 {
|
||||
flushSeconds = 1
|
||||
}
|
||||
ticker := time.NewTicker(*flushInterval)
|
||||
defer ticker.Stop()
|
||||
mustStop := false
|
||||
@@ -63,7 +68,7 @@ func (ps *pendingSeries) periodicFlusher() {
|
||||
case <-ps.stopCh:
|
||||
mustStop = true
|
||||
case <-ticker.C:
|
||||
if time.Since(ps.wr.lastFlushTime) < *flushInterval/2 {
|
||||
if fasttime.UnixTimestamp()-ps.wr.lastFlushTime < uint64(flushSeconds) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -76,7 +81,7 @@ func (ps *pendingSeries) periodicFlusher() {
|
||||
type writeRequest struct {
|
||||
wr prompbmarshal.WriteRequest
|
||||
pushBlock func(block []byte)
|
||||
lastFlushTime time.Time
|
||||
lastFlushTime uint64
|
||||
|
||||
tss []prompbmarshal.TimeSeries
|
||||
|
||||
@@ -108,7 +113,7 @@ func (wr *writeRequest) reset() {
|
||||
|
||||
func (wr *writeRequest) flush() {
|
||||
wr.wr.Timeseries = wr.tss
|
||||
wr.lastFlushTime = time.Now()
|
||||
wr.lastFlushTime = fasttime.UnixTimestamp()
|
||||
pushWriteRequest(&wr.wr, wr.pushBlock)
|
||||
wr.reset()
|
||||
}
|
||||
@@ -144,13 +149,8 @@ func (wr *writeRequest) copyTimeSeries(dst, src *prompbmarshal.TimeSeries) {
|
||||
}
|
||||
dst.Labels = labelsDst[labelsLen:]
|
||||
|
||||
samplesDst = append(samplesDst, prompbmarshal.Sample{})
|
||||
dstSample := &samplesDst[len(samplesDst)-1]
|
||||
if len(src.Samples) != 1 {
|
||||
logger.Panicf("BUG: unexpected number of samples in time series; got %d; want 1", len(src.Samples))
|
||||
}
|
||||
*dstSample = src.Samples[0]
|
||||
dst.Samples = samplesDst[len(samplesDst)-1:]
|
||||
samplesDst = append(samplesDst, src.Samples...)
|
||||
dst.Samples = samplesDst[len(samplesDst)-len(src.Samples):]
|
||||
|
||||
wr.samples = samplesDst
|
||||
wr.labels = labelsDst
|
||||
|
||||
@@ -32,6 +32,21 @@ var (
|
||||
"Disk usage is unlimited if the value is set to 0")
|
||||
)
|
||||
|
||||
// CheckRelabelConfigs checks -remoteWrite.relabelConfig and -remoteWrite.urlRelabelConfig.
|
||||
func CheckRelabelConfigs() error {
|
||||
if *relabelConfigPathGlobal != "" {
|
||||
if _, err := promrelabel.LoadRelabelConfigs(*relabelConfigPathGlobal); err != nil {
|
||||
return fmt.Errorf("cannot load -remoteWrite.relabelConfig=%q: %s", *relabelConfigPathGlobal, err)
|
||||
}
|
||||
}
|
||||
for _, path := range *relabelConfigPaths {
|
||||
if _, err := promrelabel.LoadRelabelConfigs(path); err != nil {
|
||||
return fmt.Errorf("cannot load relabel configs from -remoteWrite.urlRelabelConfig=%q: %s", path, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var rwctxs []*remoteWriteCtx
|
||||
|
||||
// Init initializes remotewrite.
|
||||
@@ -86,8 +101,6 @@ func Stop() {
|
||||
|
||||
// Push sends wr to remote storage systems set via `-remoteWrite.url`.
|
||||
//
|
||||
// Each timeseries in wr.Timeseries must contain one sample.
|
||||
//
|
||||
// Note that wr may be modified by Push due to relabeling.
|
||||
func Push(wr *prompbmarshal.WriteRequest) {
|
||||
var rctx *relabelCtx
|
||||
|
||||
@@ -52,14 +52,16 @@ publish-vmalert:
|
||||
APP_NAME=vmalert $(MAKE) publish-via-docker
|
||||
|
||||
test-vmalert:
|
||||
go test -race -cover ./app/vmalert
|
||||
go test -v -race -cover ./app/vmalert -loggerLevel=ERROR
|
||||
go test -v -race -cover ./app/vmalert/datasource
|
||||
go test -v -race -cover ./app/vmalert/notifier
|
||||
|
||||
run-vmalert: vmalert
|
||||
./bin/vmalert -rule=app/vmalert/testdata/rules0-good.rules \
|
||||
-datasource.url=http://localhost:8428 \
|
||||
-notifier.url=http://localhost:9093 \
|
||||
-remotewrite.url=http://localhost:8428 \
|
||||
-remoteread.url=http://localhost:8428 \
|
||||
-remoteWrite.url=http://localhost:8428 \
|
||||
-remoteRead.url=http://localhost:8428 \
|
||||
-evaluationInterval=3s
|
||||
|
||||
vmalert-amd64:
|
||||
|
||||
@@ -13,7 +13,7 @@ sends alerts to [Alert Manager](https://github.com/prometheus/alertmanager).
|
||||
* Lightweight without extra dependencies.
|
||||
|
||||
### TODO:
|
||||
* Configuration hot reload.
|
||||
* Support recording rules.
|
||||
|
||||
### QuickStart
|
||||
|
||||
@@ -50,9 +50,9 @@ Used as alert source in AlertManager.
|
||||
* `http://<vmalert-addr>/metrics` - application metrics.
|
||||
* `http://<vmalert-addr>/-/reload` - hot configuration reload.
|
||||
|
||||
`vmalert` may be configured with `-remotewrite` flag to write alerts state in form of timeseries
|
||||
`vmalert` may be configured with `-remoteWrite` flag to write alerts state in form of timeseries
|
||||
via remote write protocol. Alerts state will be written as `ALERTS` timeseries. These timeseries
|
||||
may be used to recover alerts state on `vmalert` restarts if `-remoteread` is configured.
|
||||
may be used to recover alerts state on `vmalert` restarts if `-remoteRead` is configured.
|
||||
|
||||
|
||||
### Configuration
|
||||
@@ -82,19 +82,21 @@ Usage of vmalert:
|
||||
Address to listen for http connections (default ":8880")
|
||||
-notifier.url string
|
||||
Prometheus alertmanager URL. Required parameter. e.g. http://127.0.0.1:9093
|
||||
-remoteread.basicAuth.password string
|
||||
Optional basic auth password for -remoteread.url
|
||||
-remoteread.basicAuth.username string
|
||||
Optional basic auth username for -remoteread.url
|
||||
-remoteread.lookback duration
|
||||
-remoteRead.basicAuth.password string
|
||||
Optional basic auth password for -remoteRead.url
|
||||
-remoteRead.basicAuth.username string
|
||||
Optional basic auth username for -remoteRead.url
|
||||
-remoteRead.lookback duration
|
||||
Lookback defines how far to look into past for alerts timeseries. For example, if lookback=1h then range from now() to now()-1h will be scanned. (default 1h0m0s)
|
||||
-remoteread.url vmalert
|
||||
Optional URL to Victoria Metrics or VMSelect that will be used to restore alerts state. This configuration makes sense only if vmalert was configured with `remotewrite.url` before and has been successfully persisted its state. E.g. http://127.0.0.1:8428
|
||||
-remotewrite.basicAuth.password string
|
||||
Optional basic auth password for -remotewrite.url
|
||||
-remotewrite.basicAuth.username string
|
||||
Optional basic auth username for -remotewrite.url
|
||||
-remotewrite.url string
|
||||
-remoteRead.url vmalert
|
||||
Optional URL to Victoria Metrics or VMSelect that will be used to restore alerts state. This configuration makes sense only if vmalert was configured with `remoteWrite.url` before and has been successfully persisted its state. E.g. http://127.0.0.1:8428
|
||||
-remoteWrite.basicAuth.password string
|
||||
Optional basic auth password for -remoteWrite.url
|
||||
-remoteWrite.basicAuth.username string
|
||||
Optional basic auth username for -remoteWrite.url
|
||||
-remoteWrite.maxQueueSize
|
||||
Defines the max number of pending datapoints to remote write endpoint
|
||||
-remoteWrite.url string
|
||||
Optional URL to Victoria Metrics or VMInsert where to persist alerts state in form of timeseries. E.g. http://127.0.0.1:8428
|
||||
-rule value
|
||||
Path to the file with alert rules.
|
||||
@@ -118,3 +120,22 @@ or send GET request to `/-/reload` endpoint.
|
||||
`vmalert` is mostly designed and built by VictoriaMetrics community.
|
||||
Feel free to share your experience and ideas for improving this
|
||||
software. Please keep simplicity as the main priority.
|
||||
|
||||
### How to build from sources
|
||||
|
||||
It is recommended using
|
||||
[binary releases](https://github.com/VictoriaMetrics/VictoriaMetrics/releases)
|
||||
- `vmalert` is located in `vmutils-*` archives there.
|
||||
|
||||
|
||||
#### Development build
|
||||
|
||||
1. [Install Go](https://golang.org/doc/install). The minimum supported version is Go 1.13.
|
||||
2. Run `make vmalert` from the root folder of the repository.
|
||||
It builds `vmalert` binary and puts it into the `bin` folder.
|
||||
|
||||
#### Production build
|
||||
|
||||
1. [Install docker](https://docs.docker.com/install/).
|
||||
2. Run `make vmalert-prod` from the root folder of the repository.
|
||||
It builds `vmalert-prod` binary and puts it into the `bin` folder.
|
||||
|
||||
@@ -32,8 +32,9 @@ func Parse(pathPatterns []string, validateAnnotations bool) ([]Group, error) {
|
||||
return nil, fmt.Errorf("one file can not contain groups with the same name %s, filepath:%s", g.Name, file)
|
||||
}
|
||||
g.File = file
|
||||
g.done = make(chan struct{})
|
||||
g.finished = make(chan struct{})
|
||||
g.doneCh = make(chan struct{})
|
||||
g.finishedCh = make(chan struct{})
|
||||
g.updateCh = make(chan Group)
|
||||
|
||||
groupsNames[g.Name] = struct{}{}
|
||||
for _, rule := range g.Rules {
|
||||
|
||||
@@ -19,13 +19,16 @@ type Group struct {
|
||||
File string
|
||||
Rules []*Rule
|
||||
|
||||
done chan struct{}
|
||||
finished chan struct{}
|
||||
doneCh chan struct{}
|
||||
finishedCh chan struct{}
|
||||
// channel accepts new Group obj
|
||||
// which supposed to update current group
|
||||
updateCh chan Group
|
||||
}
|
||||
|
||||
// ID return unique group ID that consists of
|
||||
// rules file and group name
|
||||
func (g Group) ID() uint64 {
|
||||
func (g *Group) ID() uint64 {
|
||||
hash := fnv.New64a()
|
||||
hash.Write([]byte(g.File))
|
||||
hash.Write([]byte("\xff"))
|
||||
@@ -48,6 +51,7 @@ func (g *Group) Restore(ctx context.Context, q datasource.Querier, lookback time
|
||||
|
||||
// updateWith updates existing group with
|
||||
// passed group object.
|
||||
// Not thread-safe.
|
||||
func (g *Group) updateWith(newGroup Group) {
|
||||
rulesRegistry := make(map[string]*Rule)
|
||||
for _, nr := range newGroup.Rules {
|
||||
@@ -58,15 +62,14 @@ func (g *Group) updateWith(newGroup Group) {
|
||||
nr, ok := rulesRegistry[or.id()]
|
||||
if !ok {
|
||||
// old rule is not present in the new list
|
||||
// and must be removed
|
||||
or = nil
|
||||
g.Rules = append(g.Rules[:i], g.Rules[i+1:]...)
|
||||
// so we mark it for removing
|
||||
g.Rules[i] = nil
|
||||
continue
|
||||
}
|
||||
|
||||
// copy all significant fields.
|
||||
// alerts state isn't copied since
|
||||
// it should be updated in next 2 Evals
|
||||
// it should be updated in next 2 Execs
|
||||
or.For = nr.For
|
||||
or.Expr = nr.Expr
|
||||
or.Labels = nr.Labels
|
||||
@@ -74,9 +77,19 @@ func (g *Group) updateWith(newGroup Group) {
|
||||
delete(rulesRegistry, nr.id())
|
||||
}
|
||||
|
||||
for _, nr := range rulesRegistry {
|
||||
g.Rules = append(g.Rules, nr)
|
||||
var newRules []*Rule
|
||||
for _, r := range g.Rules {
|
||||
if r == nil {
|
||||
// skip nil rules
|
||||
continue
|
||||
}
|
||||
newRules = append(newRules, r)
|
||||
}
|
||||
// add the rest of rules from registry
|
||||
for _, nr := range rulesRegistry {
|
||||
newRules = append(newRules, nr)
|
||||
}
|
||||
g.Rules = newRules
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -96,11 +109,11 @@ var (
|
||||
)
|
||||
|
||||
func (g *Group) close() {
|
||||
if g.done == nil {
|
||||
if g.doneCh == nil {
|
||||
return
|
||||
}
|
||||
close(g.done)
|
||||
<-g.finished
|
||||
close(g.doneCh)
|
||||
<-g.finishedCh
|
||||
}
|
||||
|
||||
func (g *Group) start(ctx context.Context, interval time.Duration,
|
||||
@@ -112,12 +125,14 @@ func (g *Group) start(ctx context.Context, interval time.Duration,
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
logger.Infof("group %q: context cancelled", g.Name)
|
||||
close(g.finished)
|
||||
close(g.finishedCh)
|
||||
return
|
||||
case <-g.done:
|
||||
case <-g.doneCh:
|
||||
logger.Infof("group %q: received stop signal", g.Name)
|
||||
close(g.finished)
|
||||
close(g.finishedCh)
|
||||
return
|
||||
case ng := <-g.updateCh:
|
||||
g.updateWith(ng)
|
||||
case <-t.C:
|
||||
iterationTotal.Inc()
|
||||
iterationStart := time.Now()
|
||||
@@ -136,30 +151,47 @@ func (g *Group) start(ctx context.Context, interval time.Duration,
|
||||
|
||||
var alertsToSend []notifier.Alert
|
||||
for _, a := range rule.alerts {
|
||||
if a.State != notifier.StatePending {
|
||||
switch a.State {
|
||||
case notifier.StateFiring:
|
||||
// set End to execStart + 3 intervals
|
||||
// so notifier can resolve it automatically if `vmalert`
|
||||
// won't be able to send resolve for some reason
|
||||
a.End = execStart.Add(3 * interval)
|
||||
alertsToSend = append(alertsToSend, *a)
|
||||
pushToRW(rw, rule, a, execStart)
|
||||
case notifier.StatePending:
|
||||
pushToRW(rw, rule, a, execStart)
|
||||
case notifier.StateInactive:
|
||||
// set End to execStart to notify
|
||||
// that it was just resolved
|
||||
a.End = execStart
|
||||
alertsToSend = append(alertsToSend, *a)
|
||||
}
|
||||
if a.State == notifier.StateInactive || rw == nil {
|
||||
continue
|
||||
}
|
||||
tss := rule.AlertToTimeSeries(a, execStart)
|
||||
for _, ts := range tss {
|
||||
remoteWriteSent.Inc()
|
||||
if err := rw.Push(ts); err != nil {
|
||||
remoteWriteErrors.Inc()
|
||||
logger.Errorf("failed to push timeseries to remotewrite: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(alertsToSend) > 0 {
|
||||
alertsSent.Add(len(alertsToSend))
|
||||
if err := nr.Send(ctx, alertsToSend); err != nil {
|
||||
alertsSendErrors.Inc()
|
||||
logger.Errorf("failed to send alert for rule %q.%q: %s", g.Name, rule.Name, err)
|
||||
}
|
||||
if len(alertsToSend) == 0 {
|
||||
continue
|
||||
}
|
||||
alertsSent.Add(len(alertsToSend))
|
||||
if err := nr.Send(ctx, alertsToSend); err != nil {
|
||||
alertsSendErrors.Inc()
|
||||
logger.Errorf("failed to send alert for rule %q.%q: %s", g.Name, rule.Name, err)
|
||||
}
|
||||
}
|
||||
iterationDuration.UpdateDuration(iterationStart)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func pushToRW(rw *remotewrite.Client, rule *Rule, a *notifier.Alert, timestamp time.Time) {
|
||||
if rw == nil {
|
||||
return
|
||||
}
|
||||
tss := rule.AlertToTimeSeries(a, timestamp)
|
||||
remoteWriteSent.Add(len(tss))
|
||||
for _, ts := range tss {
|
||||
if err := rw.Push(ts); err != nil {
|
||||
remoteWriteErrors.Inc()
|
||||
logger.Errorf("failed to push timeseries to remotewrite: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@ func TestUpdateWith(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
currentRules []*Rule
|
||||
newRules []*Rule
|
||||
// rules must be sorted by name
|
||||
newRules []*Rule
|
||||
}{
|
||||
{
|
||||
"new rule",
|
||||
@@ -55,8 +56,18 @@ func TestUpdateWith(t *testing.T) {
|
||||
},
|
||||
{
|
||||
"multiple rules",
|
||||
[]*Rule{{Name: "foo"}, {Name: "bar"}, {Name: "baz"}},
|
||||
[]*Rule{{Name: "foo"}, {Name: "baz"}},
|
||||
[]*Rule{{Name: "bar"}, {Name: "baz"}, {Name: "foo"}},
|
||||
[]*Rule{{Name: "baz"}, {Name: "foo"}},
|
||||
},
|
||||
{
|
||||
"replace rule",
|
||||
[]*Rule{{Name: "foo1"}},
|
||||
[]*Rule{{Name: "foo2"}},
|
||||
},
|
||||
{
|
||||
"replace multiple rules",
|
||||
[]*Rule{{Name: "foo1"}, {Name: "foo2"}},
|
||||
[]*Rule{{Name: "foo3"}, {Name: "foo4"}},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -69,6 +80,9 @@ func TestUpdateWith(t *testing.T) {
|
||||
t.Fatalf("expected to have %d rules; got: %d",
|
||||
len(g.Rules), len(tc.newRules))
|
||||
}
|
||||
sort.Slice(g.Rules, func(i, j int) bool {
|
||||
return g.Rules[i].Name < g.Rules[j].Name
|
||||
})
|
||||
for i, r := range g.Rules {
|
||||
got, want := r, tc.newRules[i]
|
||||
if got.Name != want.Name {
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/remotewrite"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/buildinfo"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/envflag"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
@@ -37,17 +38,18 @@ absolute path to all .yaml files in root.`)
|
||||
basicAuthUsername = flag.String("datasource.basicAuth.username", "", "Optional basic auth username for -datasource.url")
|
||||
basicAuthPassword = flag.String("datasource.basicAuth.password", "", "Optional basic auth password for -datasource.url")
|
||||
|
||||
remoteWriteURL = flag.String("remotewrite.url", "", "Optional URL to Victoria Metrics or VMInsert where to persist alerts state"+
|
||||
remoteWriteURL = flag.String("remoteWrite.url", "", "Optional URL to Victoria Metrics or VMInsert where to persist alerts state"+
|
||||
" in form of timeseries. E.g. http://127.0.0.1:8428")
|
||||
remoteWriteUsername = flag.String("remotewrite.basicAuth.username", "", "Optional basic auth username for -remotewrite.url")
|
||||
remoteWritePassword = flag.String("remotewrite.basicAuth.password", "", "Optional basic auth password for -remotewrite.url")
|
||||
remoteWriteUsername = flag.String("remoteWrite.basicAuth.username", "", "Optional basic auth username for -remoteWrite.url")
|
||||
remoteWritePassword = flag.String("remoteWrite.basicAuth.password", "", "Optional basic auth password for -remoteWrite.url")
|
||||
remoteWriteMaxQueueSize = flag.Int("remoteWrite.maxQueueSize", 10e3, "Defines the max number of pending datapoints to remote write endpoint")
|
||||
|
||||
remoteReadURL = flag.String("remoteread.url", "", "Optional URL to Victoria Metrics or VMSelect that will be used to restore alerts"+
|
||||
" state. This configuration makes sense only if `vmalert` was configured with `remotewrite.url` before and has been successfully persisted its state."+
|
||||
remoteReadURL = flag.String("remoteRead.url", "", "Optional URL to Victoria Metrics or VMSelect that will be used to restore alerts"+
|
||||
" state. This configuration makes sense only if `vmalert` was configured with `remoteWrite.url` before and has been successfully persisted its state."+
|
||||
" E.g. http://127.0.0.1:8428")
|
||||
remoteReadUsername = flag.String("remoteread.basicAuth.username", "", "Optional basic auth username for -remoteread.url")
|
||||
remoteReadPassword = flag.String("remoteread.basicAuth.password", "", "Optional basic auth password for -remoteread.url")
|
||||
remoteReadLookBack = flag.Duration("remoteread.lookback", time.Hour, "Lookback defines how far to look into past for alerts timeseries."+
|
||||
remoteReadUsername = flag.String("remoteRead.basicAuth.username", "", "Optional basic auth username for -remoteRead.url")
|
||||
remoteReadPassword = flag.String("remoteRead.basicAuth.password", "", "Optional basic auth password for -remoteRead.url")
|
||||
remoteReadLookBack = flag.Duration("remoteRead.lookback", time.Hour, "Lookback defines how far to look into past for alerts timeseries."+
|
||||
" For example, if lookback=1h then range from now() to now()-1h will be scanned.")
|
||||
|
||||
evaluationInterval = flag.Duration("evaluationInterval", time.Minute, "How often to evaluate the rules. Default 1m")
|
||||
@@ -56,6 +58,8 @@ absolute path to all .yaml files in root.`)
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Write flags and help message to stdout, since it is easier to grep or pipe.
|
||||
flag.CommandLine.SetOutput(os.Stdout)
|
||||
envflag.Parse()
|
||||
buildinfo.Init()
|
||||
logger.Init()
|
||||
@@ -77,6 +81,7 @@ func main() {
|
||||
if *remoteWriteURL != "" {
|
||||
c, err := remotewrite.NewClient(ctx, remotewrite.Config{
|
||||
Addr: *remoteWriteURL,
|
||||
MaxQueueSize: *remoteWriteMaxQueueSize,
|
||||
FlushInterval: *evaluationInterval,
|
||||
BasicAuthUser: *remoteWriteUsername,
|
||||
BasicAuthPass: *remoteWritePassword,
|
||||
@@ -97,7 +102,7 @@ func main() {
|
||||
go func() {
|
||||
// init reload metrics with positive values to improve alerting conditions
|
||||
configSuccess.Set(1)
|
||||
configTimestamp.Set(uint64(time.Now().UnixNano()) / 1e9)
|
||||
configTimestamp.Set(fasttime.UnixTimestamp())
|
||||
sigHup := procutil.NewSighupChan()
|
||||
for {
|
||||
<-sigHup
|
||||
@@ -110,13 +115,13 @@ func main() {
|
||||
continue
|
||||
}
|
||||
configSuccess.Set(1)
|
||||
configTimestamp.Set(uint64(time.Now().UnixNano()) / 1e9)
|
||||
configTimestamp.Set(fasttime.UnixTimestamp())
|
||||
logger.Infof("Rules reloaded successfully from %q", *rulePath)
|
||||
}
|
||||
}()
|
||||
|
||||
rh := &requestHandler{m: manager}
|
||||
go httpserver.Serve(*httpListenAddr, (rh).handler)
|
||||
go httpserver.Serve(*httpListenAddr, rh.handler)
|
||||
|
||||
sig := procutil.WaitForSigterm()
|
||||
logger.Infof("service received signal %s", sig)
|
||||
|
||||
@@ -57,7 +57,7 @@ func (m *manager) close() {
|
||||
}
|
||||
|
||||
func (m *manager) startGroup(ctx context.Context, group Group, restore bool) {
|
||||
if restore {
|
||||
if restore && m.rr != nil {
|
||||
err := group.Restore(ctx, m.rr, *remoteReadLookBack)
|
||||
if err != nil {
|
||||
logger.Errorf("error while restoring state for group %q: %s", group.Name, err)
|
||||
@@ -87,8 +87,7 @@ func (m *manager) update(ctx context.Context, path []string, validate, restore b
|
||||
|
||||
m.groupsMu.Lock()
|
||||
for _, og := range m.groups {
|
||||
id := og.ID()
|
||||
ng, ok := groupsRegistry[id]
|
||||
ng, ok := groupsRegistry[og.ID()]
|
||||
if !ok {
|
||||
// old group is not present in new list
|
||||
// and must be stopped and deleted
|
||||
@@ -97,7 +96,7 @@ func (m *manager) update(ctx context.Context, path []string, validate, restore b
|
||||
og = nil
|
||||
continue
|
||||
}
|
||||
og.updateWith(ng)
|
||||
og.updateCh <- ng
|
||||
delete(groupsRegistry, ng.ID())
|
||||
}
|
||||
|
||||
|
||||
@@ -26,24 +26,35 @@ func TestManagerUpdateError(t *testing.T) {
|
||||
// execution of configuration update.
|
||||
// Should be executed with -race flag
|
||||
func TestManagerUpdateConcurrent(t *testing.T) {
|
||||
m := &manager{groups: make(map[uint64]*Group)}
|
||||
m := &manager{
|
||||
groups: make(map[uint64]*Group),
|
||||
storage: &fakeQuerier{},
|
||||
notifier: &fakeNotifier{},
|
||||
}
|
||||
paths := []string{
|
||||
"testdata/dir/rules0-good.rules",
|
||||
"testdata/dir/rules1-good.rules",
|
||||
"testdata/rules0-good.rules",
|
||||
}
|
||||
*evaluationInterval = time.Millisecond
|
||||
if err := m.start(context.Background(), []string{paths[0]}, true); err != nil {
|
||||
t.Fatalf("failed to start: %s", err)
|
||||
}
|
||||
|
||||
const n = 500
|
||||
const workers = 500
|
||||
const iterations = 10
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(n)
|
||||
for i := 0; i < n; i++ {
|
||||
wg.Add(workers)
|
||||
for i := 0; i < workers; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
rnd := rand.Intn(len(paths))
|
||||
path := []string{paths[rnd]}
|
||||
err := m.update(context.Background(), path, true, false)
|
||||
if err != nil {
|
||||
t.Errorf("update error: %s", err)
|
||||
for i := 0; i < iterations; i++ {
|
||||
rnd := rand.Intn(len(paths))
|
||||
path := []string{paths[rnd]}
|
||||
err := m.update(context.Background(), path, true, false)
|
||||
if err != nil {
|
||||
t.Errorf("update error: %s", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -111,7 +122,7 @@ func TestManagerUpdate(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
m := &manager{groups: make(map[uint64]*Group)}
|
||||
m := &manager{groups: make(map[uint64]*Group), storage: &fakeQuerier{}}
|
||||
path := []string{tc.initPath}
|
||||
if err := m.update(ctx, path, true, false); err != nil {
|
||||
t.Fatalf("failed to complete initial rules update: %s", err)
|
||||
|
||||
@@ -18,6 +18,7 @@ type Alert struct {
|
||||
Annotations map[string]string
|
||||
State AlertState
|
||||
|
||||
Expr string
|
||||
Start time.Time
|
||||
End time.Time
|
||||
Value float64
|
||||
@@ -52,14 +53,15 @@ func (as AlertState) String() string {
|
||||
type alertTplData struct {
|
||||
Labels map[string]string
|
||||
Value float64
|
||||
Expr string
|
||||
}
|
||||
|
||||
const tplHeader = `{{ $value := .Value }}{{ $labels := .Labels }}`
|
||||
const tplHeader = `{{ $value := .Value }}{{ $labels := .Labels }}{{ $expr := .Expr }}`
|
||||
|
||||
// ExecTemplate executes the Alert template for give
|
||||
// map of annotations.
|
||||
func (a *Alert) ExecTemplate(annotations map[string]string) (map[string]string, error) {
|
||||
tplData := alertTplData{Value: a.Value, Labels: a.Labels}
|
||||
tplData := alertTplData{Value: a.Value, Labels: a.Labels, Expr: a.Expr}
|
||||
return templateAnnotations(annotations, tplHeader, tplData)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
package notifier
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAlert_ExecTemplate(t *testing.T) {
|
||||
u, _ := url.Parse("https://victoriametrics.com/path")
|
||||
InitTemplateFunc(u)
|
||||
testCases := []struct {
|
||||
name string
|
||||
alert *Alert
|
||||
annotations map[string]string
|
||||
expTpl map[string]string
|
||||
}{
|
||||
{
|
||||
name: "empty-alert",
|
||||
alert: &Alert{},
|
||||
annotations: map[string]string{},
|
||||
expTpl: map[string]string{},
|
||||
},
|
||||
{
|
||||
name: "no-template",
|
||||
alert: &Alert{
|
||||
Value: 1e4,
|
||||
Labels: map[string]string{
|
||||
@@ -27,6 +32,7 @@ func TestAlert_ExecTemplate(t *testing.T) {
|
||||
expTpl: map[string]string{},
|
||||
},
|
||||
{
|
||||
name: "label-template",
|
||||
alert: &Alert{
|
||||
Value: 1e4,
|
||||
Labels: map[string]string{
|
||||
@@ -43,10 +49,24 @@ func TestAlert_ExecTemplate(t *testing.T) {
|
||||
"description": "It is 10000 connections for localhost",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "expression-template",
|
||||
alert: &Alert{
|
||||
Expr: `vm_rows{"label"="bar"}>0`,
|
||||
},
|
||||
annotations: map[string]string{
|
||||
"exprEscapedQuery": "{{ $expr|quotesEscape|queryEscape }}",
|
||||
"exprEscapedPath": "{{ $expr|quotesEscape|pathEscape }}",
|
||||
},
|
||||
expTpl: map[string]string{
|
||||
"exprEscapedQuery": "vm_rows%7B%5C%22label%5C%22%3D%5C%22bar%5C%22%7D%3E0",
|
||||
"exprEscapedPath": "vm_rows%7B%5C%22label%5C%22=%5C%22bar%5C%22%7D%3E0",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
tpl, err := tc.alert.ExecTemplate(tc.annotations)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -1,131 +1,131 @@
|
||||
// Code generated by qtc from "alertmanager_request.qtpl". DO NOT EDIT.
|
||||
// See https://github.com/valyala/quicktemplate for details.
|
||||
|
||||
//line notifier/alertmanager_request.qtpl:1
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:1
|
||||
package notifier
|
||||
|
||||
//line notifier/alertmanager_request.qtpl:1
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:1
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
//line notifier/alertmanager_request.qtpl:7
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:7
|
||||
import (
|
||||
qtio422016 "io"
|
||||
|
||||
qt422016 "github.com/valyala/quicktemplate"
|
||||
)
|
||||
|
||||
//line notifier/alertmanager_request.qtpl:7
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:7
|
||||
var (
|
||||
_ = qtio422016.Copy
|
||||
_ = qt422016.AcquireByteBuffer
|
||||
)
|
||||
|
||||
//line notifier/alertmanager_request.qtpl:7
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:7
|
||||
func streamamRequest(qw422016 *qt422016.Writer, alerts []Alert, generatorURL func(string, string) string) {
|
||||
//line notifier/alertmanager_request.qtpl:7
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:7
|
||||
qw422016.N().S(`[`)
|
||||
//line notifier/alertmanager_request.qtpl:9
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:9
|
||||
for i, alert := range alerts {
|
||||
//line notifier/alertmanager_request.qtpl:9
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:9
|
||||
qw422016.N().S(`{"startsAt":`)
|
||||
//line notifier/alertmanager_request.qtpl:11
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:11
|
||||
qw422016.N().Q(alert.Start.Format(time.RFC3339Nano))
|
||||
//line notifier/alertmanager_request.qtpl:11
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:11
|
||||
qw422016.N().S(`,"generatorURL":`)
|
||||
//line notifier/alertmanager_request.qtpl:12
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:12
|
||||
qw422016.N().Q(generatorURL(strconv.FormatUint(alert.GroupID, 10), strconv.FormatUint(alert.ID, 10)))
|
||||
//line notifier/alertmanager_request.qtpl:12
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:12
|
||||
qw422016.N().S(`,`)
|
||||
//line notifier/alertmanager_request.qtpl:13
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:13
|
||||
if !alert.End.IsZero() {
|
||||
//line notifier/alertmanager_request.qtpl:13
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:13
|
||||
qw422016.N().S(`"endsAt":`)
|
||||
//line notifier/alertmanager_request.qtpl:14
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:14
|
||||
qw422016.N().Q(alert.End.Format(time.RFC3339Nano))
|
||||
//line notifier/alertmanager_request.qtpl:14
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:14
|
||||
qw422016.N().S(`,`)
|
||||
//line notifier/alertmanager_request.qtpl:15
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:15
|
||||
}
|
||||
//line notifier/alertmanager_request.qtpl:15
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:15
|
||||
qw422016.N().S(`"labels": {"alertname":`)
|
||||
//line notifier/alertmanager_request.qtpl:17
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:17
|
||||
qw422016.N().Q(alert.Name)
|
||||
//line notifier/alertmanager_request.qtpl:18
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:18
|
||||
for k, v := range alert.Labels {
|
||||
//line notifier/alertmanager_request.qtpl:18
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:18
|
||||
qw422016.N().S(`,`)
|
||||
//line notifier/alertmanager_request.qtpl:19
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:19
|
||||
qw422016.N().Q(k)
|
||||
//line notifier/alertmanager_request.qtpl:19
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:19
|
||||
qw422016.N().S(`:`)
|
||||
//line notifier/alertmanager_request.qtpl:19
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:19
|
||||
qw422016.N().Q(v)
|
||||
//line notifier/alertmanager_request.qtpl:20
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:20
|
||||
}
|
||||
//line notifier/alertmanager_request.qtpl:20
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:20
|
||||
qw422016.N().S(`},"annotations": {`)
|
||||
//line notifier/alertmanager_request.qtpl:23
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:23
|
||||
c := len(alert.Annotations)
|
||||
|
||||
//line notifier/alertmanager_request.qtpl:24
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:24
|
||||
for k, v := range alert.Annotations {
|
||||
//line notifier/alertmanager_request.qtpl:25
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:25
|
||||
c = c - 1
|
||||
|
||||
//line notifier/alertmanager_request.qtpl:26
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:26
|
||||
qw422016.N().Q(k)
|
||||
//line notifier/alertmanager_request.qtpl:26
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:26
|
||||
qw422016.N().S(`:`)
|
||||
//line notifier/alertmanager_request.qtpl:26
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:26
|
||||
qw422016.N().Q(v)
|
||||
//line notifier/alertmanager_request.qtpl:26
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:26
|
||||
if c > 0 {
|
||||
//line notifier/alertmanager_request.qtpl:26
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:26
|
||||
qw422016.N().S(`,`)
|
||||
//line notifier/alertmanager_request.qtpl:26
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:26
|
||||
}
|
||||
//line notifier/alertmanager_request.qtpl:27
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:27
|
||||
}
|
||||
//line notifier/alertmanager_request.qtpl:27
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:27
|
||||
qw422016.N().S(`}}`)
|
||||
//line notifier/alertmanager_request.qtpl:30
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:30
|
||||
if i != len(alerts)-1 {
|
||||
//line notifier/alertmanager_request.qtpl:30
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:30
|
||||
qw422016.N().S(`,`)
|
||||
//line notifier/alertmanager_request.qtpl:30
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:30
|
||||
}
|
||||
//line notifier/alertmanager_request.qtpl:31
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:31
|
||||
}
|
||||
//line notifier/alertmanager_request.qtpl:31
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:31
|
||||
qw422016.N().S(`]`)
|
||||
//line notifier/alertmanager_request.qtpl:33
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:33
|
||||
}
|
||||
|
||||
//line notifier/alertmanager_request.qtpl:33
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:33
|
||||
func writeamRequest(qq422016 qtio422016.Writer, alerts []Alert, generatorURL func(string, string) string) {
|
||||
//line notifier/alertmanager_request.qtpl:33
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:33
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line notifier/alertmanager_request.qtpl:33
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:33
|
||||
streamamRequest(qw422016, alerts, generatorURL)
|
||||
//line notifier/alertmanager_request.qtpl:33
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:33
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line notifier/alertmanager_request.qtpl:33
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:33
|
||||
}
|
||||
|
||||
//line notifier/alertmanager_request.qtpl:33
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:33
|
||||
func amRequest(alerts []Alert, generatorURL func(string, string) string) string {
|
||||
//line notifier/alertmanager_request.qtpl:33
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:33
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line notifier/alertmanager_request.qtpl:33
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:33
|
||||
writeamRequest(qb422016, alerts, generatorURL)
|
||||
//line notifier/alertmanager_request.qtpl:33
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:33
|
||||
qs422016 := string(qb422016.B)
|
||||
//line notifier/alertmanager_request.qtpl:33
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:33
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line notifier/alertmanager_request.qtpl:33
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:33
|
||||
return qs422016
|
||||
//line notifier/alertmanager_request.qtpl:33
|
||||
//line app/vmalert/notifier/alertmanager_request.qtpl:33
|
||||
}
|
||||
|
||||
@@ -142,6 +142,15 @@ func InitTemplateFunc(externalURL *url.URL) {
|
||||
"externalURL": func() string {
|
||||
return externalURL.String()
|
||||
},
|
||||
"pathEscape": func(u string) string {
|
||||
return url.PathEscape(u)
|
||||
},
|
||||
"queryEscape": func(q string) string {
|
||||
return url.QueryEscape(q)
|
||||
},
|
||||
"quotesEscape": func(q string) string {
|
||||
return strings.Replace(q, `"`, `\"`, -1)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -103,7 +103,8 @@ func (c *Client) Push(s prompbmarshal.TimeSeries) error {
|
||||
case c.input <- s:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("failed to push timeseries - queue is full (%d entries)",
|
||||
return fmt.Errorf("failed to push timeseries - queue is full (%d entries). "+
|
||||
"Queue size is controlled by -remoteWrite.maxQueueSize flag",
|
||||
c.maxQueueSize)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ func (r *Rule) Exec(ctx context.Context, q datasource.Querier) error {
|
||||
}
|
||||
|
||||
for h, a := range r.alerts {
|
||||
// cleanup inactive alerts from previous Eval
|
||||
// cleanup inactive alerts from previous Exec
|
||||
if a.State == notifier.StateInactive {
|
||||
delete(r.alerts, h)
|
||||
}
|
||||
@@ -109,19 +109,19 @@ func (r *Rule) Exec(ctx context.Context, q datasource.Querier) error {
|
||||
// if alert wasn't updated in this iteration
|
||||
// means it is resolved already
|
||||
if _, ok := updated[h]; !ok {
|
||||
if a.State == notifier.StatePending {
|
||||
// alert was in Pending state - it is not
|
||||
// active anymore
|
||||
delete(r.alerts, h)
|
||||
continue
|
||||
}
|
||||
a.State = notifier.StateInactive
|
||||
// set endTime to last execution time
|
||||
// so it can be sent by notifier on next step
|
||||
a.End = r.lastExecTime
|
||||
continue
|
||||
}
|
||||
if a.State == notifier.StatePending && time.Since(a.Start) >= r.For {
|
||||
a.State = notifier.StateFiring
|
||||
alertsFired.Inc()
|
||||
}
|
||||
if a.State == notifier.StateFiring {
|
||||
a.End = r.lastExecTime.Add(3 * *evaluationInterval)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -152,6 +152,7 @@ func (r *Rule) newAlert(m datasource.Metric) (*notifier.Alert, error) {
|
||||
Labels: map[string]string{},
|
||||
Value: m.Value,
|
||||
Start: time.Now(),
|
||||
Expr: r.Expr,
|
||||
// TODO: support End time
|
||||
}
|
||||
for _, l := range m.Labels {
|
||||
@@ -293,8 +294,12 @@ func newTimeSeries(value float64, labels map[string]string, timestamp time.Time)
|
||||
|
||||
// Restore restores the state of active alerts basing on previously written timeseries.
|
||||
// Restore restores only Start field. Field State will be always Pending and supposed
|
||||
// to be updated on next Eval, as well as Value field.
|
||||
// to be updated on next Exec, as well as Value field.
|
||||
func (r *Rule) Restore(ctx context.Context, q datasource.Querier, lookback time.Duration) error {
|
||||
if q == nil {
|
||||
return fmt.Errorf("querier is nil")
|
||||
}
|
||||
|
||||
// Get the last datapoint in range via MetricsQL `last_over_time`.
|
||||
// We don't use plain PromQL since Prometheus doesn't support
|
||||
// remote write protocol which is used for state persistence in vmalert.
|
||||
@@ -309,7 +314,7 @@ func (r *Rule) Restore(ctx context.Context, q datasource.Querier, lookback time.
|
||||
labels := m.Labels
|
||||
m.Labels = make([]datasource.Label, 0)
|
||||
// drop all extra labels, so hash key will
|
||||
// be identical to timeseries received in Eval
|
||||
// be identical to timeseries received in Exec
|
||||
for _, l := range labels {
|
||||
if l.Name == alertNameLabel {
|
||||
continue
|
||||
|
||||
@@ -296,16 +296,14 @@ func TestRule_Exec(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
newTestRule("for-pending=>inactive", time.Millisecond),
|
||||
newTestRule("for-pending=>empty", time.Second),
|
||||
[][]datasource.Metric{
|
||||
{metricWithLabels(t, "name", "foo")},
|
||||
{metricWithLabels(t, "name", "foo")},
|
||||
// empty step to reset pending alerts
|
||||
// empty step to reset and delete pending alerts
|
||||
{},
|
||||
},
|
||||
map[uint64]*notifier.Alert{
|
||||
hash(metricWithLabels(t, "name", "foo")): {State: notifier.StateInactive},
|
||||
},
|
||||
map[uint64]*notifier.Alert{},
|
||||
},
|
||||
{
|
||||
newTestRule("for-pending=>firing=>inactive", time.Millisecond),
|
||||
|
||||
1
app/vmalert/testdata/dir/rules0-good.rules
vendored
1
app/vmalert/testdata/dir/rules0-good.rules
vendored
@@ -6,6 +6,7 @@ groups:
|
||||
expr: vm_rows > 0
|
||||
labels:
|
||||
label: bar
|
||||
expr: "{{ $expr|queryEscape }}"
|
||||
annotations:
|
||||
summary: "{{ $value|humanize }}"
|
||||
description: "{{$labels}}"
|
||||
|
||||
@@ -53,6 +53,8 @@ publish-vmauth:
|
||||
|
||||
run-vmauth:
|
||||
APP_NAME=vmauth \
|
||||
DOCKER_OPTS='-v $(shell pwd)/app/vmauth/:/app/vmauth' \
|
||||
ARGS='-auth.config=app/vmauth/example_config.yml' \
|
||||
$(MAKE) run-via-docker
|
||||
|
||||
vmauth-amd64:
|
||||
|
||||
31
app/vmauth/example_config.yml
Normal file
31
app/vmauth/example_config.yml
Normal file
@@ -0,0 +1,31 @@
|
||||
# Arbitrary number of usernames may be put here.
|
||||
# Usernames must be unique.
|
||||
|
||||
users:
|
||||
|
||||
# The user for querying local single-node VictoriaMetrics.
|
||||
# All the requests to http://vmauth:8427 with the given Basic Auth (username:password)
|
||||
# will be routed to http://localhost:8428 .
|
||||
# For example, http://vmauth:8427/api/v1/query is routed to http://localhost:8428/api/v1/query
|
||||
- username: "local-single-node"
|
||||
password: "***"
|
||||
url_prefix: "http://localhost:8428"
|
||||
|
||||
# The user for querying account 123 in VictoriaMetrics cluster
|
||||
# See https://github.com/VictoriaMetrics/VictoriaMetrics/blob/cluster/README.md#url-format
|
||||
# All the requests to http://vmauth:8427 with the given Basic Auth (username:password)
|
||||
# will be routed to http://vmselect:8481/select/123/prometheus .
|
||||
# For example, http://vmauth:8427/api/v1/query is routed to http://vmselect:8481/select/123/prometheus/api/v1/select
|
||||
- username: "cluster-select-account-123"
|
||||
password: "***"
|
||||
url_prefix: "http://vmselect:8481/select/123/prometheus"
|
||||
|
||||
# The user for inserting Prometheus data into VictoriaMetrics cluster under account 42
|
||||
# See https://github.com/VictoriaMetrics/VictoriaMetrics/blob/cluster/README.md#url-format
|
||||
# All the reuqests to http://vmauth:8427 with the given Basic Auth (username:password)
|
||||
# will be routed to http://vminsert:8480/insert/42/prometheus .
|
||||
# For example, http://vmauth:8427/api/v1/write is routed to http://vminsert:8480/insert/42/prometheus/api/v1/write
|
||||
- username: "cluster-insert-account-42"
|
||||
password: "***"
|
||||
url_prefix: "http://vminsert:8480/insert/42/prometheus"
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/buildinfo"
|
||||
@@ -19,6 +20,8 @@ var (
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Write flags and help message to stdout, since it is easier to grep or pipe.
|
||||
flag.CommandLine.SetOutput(os.Stdout)
|
||||
envflag.Parse()
|
||||
buildinfo.Init()
|
||||
logger.Init()
|
||||
|
||||
@@ -19,6 +19,9 @@ Backed up data can be restored with [vmrestore](https://github.com/VictoriaMetri
|
||||
|
||||
See [this article](https://medium.com/@valyala/speeding-up-backups-for-big-time-series-databases-533c1a927883) for more details.
|
||||
|
||||
See also [vmbackuper](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/466) tool built on top of `vmbackup`. This tool simplifies
|
||||
creation of hourly, daily, weekly and monthly backups.
|
||||
|
||||
|
||||
### Use cases
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ var (
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Write flags and help message to stdout, since it is easier to grep or pipe.
|
||||
flag.CommandLine.SetOutput(os.Stdout)
|
||||
flag.Usage = usage
|
||||
envflag.Parse()
|
||||
buildinfo.Init()
|
||||
|
||||
@@ -21,7 +21,8 @@ vmrestore -src=gcs://<bucket>/<path/to/backup> -storageDataPath=<local/path/to/r
|
||||
* `<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.
|
||||
The original `-storageDataPath` directory may contain old files. They will be susbstituted by the files from backup,
|
||||
i.e. the end result would be similar to [rsync --delete](https://askubuntu.com/questions/476041/how-do-i-make-rsync-delete-files-that-have-been-deleted-from-the-source-folder).
|
||||
|
||||
|
||||
### Troubleshooting
|
||||
@@ -52,7 +53,7 @@ Run `vmrestore -help` in order to see all the available options:
|
||||
-envflag.prefix string
|
||||
Prefix for environment variables if -envflag.enable is set
|
||||
-fs.disableMmap
|
||||
Whether to use pread() instead of mmap() for reading data files
|
||||
Whether to use pread() instead of mmap() for reading data files. By default mmap() is used for 64-bit arches and pread() is used for 32-bit arches, since they cannot data files bigger than 2^32 bytes in memory
|
||||
-loggerFormat string
|
||||
Format for logs. Possible values: default, json (default "default")
|
||||
-loggerLevel string
|
||||
@@ -68,8 +69,8 @@ Run `vmrestore -help` in order to see all the available options:
|
||||
-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
|
||||
Destination path where backup must be restored. VictoriaMetrics must be stopped when restoring from backup. -storageDataPath dir can be non-empty. In this case the contents of -storageDataPath dir is synchronized with -src contents, i.e. it works like 'rsync --delete' (default "victoria-metrics-data")
|
||||
-version
|
||||
Show VictoriaMetrics version
|
||||
```
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/actions"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/common"
|
||||
@@ -16,13 +17,16 @@ var (
|
||||
src = flag.String("src", "", "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 = flag.String("storageDataPath", "victoria-metrics-data", "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")
|
||||
"VictoriaMetrics must be stopped when restoring from backup. -storageDataPath dir can be non-empty. In this case the contents of -storageDataPath dir "+
|
||||
"is synchronized with -src contents, i.e. it works like 'rsync --delete'")
|
||||
concurrency = flag.Int("concurrency", 10, "The number of concurrent workers. Higher concurrency may reduce restore duration")
|
||||
maxBytesPerSecond = flag.Int("maxBytesPerSecond", 0, "The maximum download speed. There is no limit if it is set to 0")
|
||||
skipBackupCompleteCheck = flag.Bool("skipBackupCompleteCheck", false, "Whether to skip checking for 'backup complete' file in -src. This may be useful for restoring from old backups, which were created without 'backup complete' file")
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Write flags and help message to stdout, since it is easier to grep or pipe.
|
||||
flag.CommandLine.SetOutput(os.Stdout)
|
||||
flag.Usage = usage
|
||||
envflag.Parse()
|
||||
buildinfo.Init()
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/netstorage"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/promql"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
@@ -395,14 +396,14 @@ func TSDBStatusHandler(startTime time.Time, w http.ResponseWriter, r *http.Reque
|
||||
if err := r.ParseForm(); err != nil {
|
||||
return fmt.Errorf("cannot parse form values: %s", err)
|
||||
}
|
||||
date := time.Now().Unix() / secsPerDay
|
||||
date := fasttime.UnixDate()
|
||||
dateStr := r.FormValue("date")
|
||||
if len(dateStr) > 0 {
|
||||
t, err := time.Parse("2006-01-02", dateStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse `date` arg %q: %s", dateStr, err)
|
||||
}
|
||||
date = t.Unix() / secsPerDay
|
||||
date = uint64(t.Unix()) / secsPerDay
|
||||
}
|
||||
topN := 10
|
||||
topNStr := r.FormValue("topN")
|
||||
@@ -419,7 +420,7 @@ func TSDBStatusHandler(startTime time.Time, w http.ResponseWriter, r *http.Reque
|
||||
}
|
||||
topN = n
|
||||
}
|
||||
status, err := netstorage.GetTSDBStatusForDate(deadline, uint64(date), topN)
|
||||
status, err := netstorage.GetTSDBStatusForDate(deadline, date, topN)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`cannot obtain tsdb status for date=%d, topN=%d: %s`, date, topN, err)
|
||||
}
|
||||
@@ -992,7 +993,7 @@ func getBool(r *http.Request, argKey string) bool {
|
||||
}
|
||||
|
||||
func currentTime() int64 {
|
||||
return int64(time.Now().UTC().Unix()) * 1e3
|
||||
return int64(fasttime.UnixTimestamp() * 1000)
|
||||
}
|
||||
|
||||
func getTagFilterssFromMatches(matches []string) ([][]storage.TagFilter, error) {
|
||||
|
||||
@@ -44,6 +44,7 @@ var aggrFuncs = map[string]aggrFunc{
|
||||
"bottomk_avg": newAggrFuncRangeTopK(avgValue, true),
|
||||
"bottomk_median": newAggrFuncRangeTopK(medianValue, true),
|
||||
"any": newAggrFunc(aggrFuncAny),
|
||||
"outliersk": aggrFuncOutliersK,
|
||||
}
|
||||
|
||||
type aggrFunc func(afa *aggrFuncArg) ([]*timeseries, error)
|
||||
@@ -483,11 +484,6 @@ func newAggrFuncTopK(isReverse bool) aggrFunc {
|
||||
}
|
||||
}
|
||||
|
||||
type tsWithValue struct {
|
||||
ts *timeseries
|
||||
value float64
|
||||
}
|
||||
|
||||
func newAggrFuncRangeTopK(f func(values []float64) float64, isReverse bool) aggrFunc {
|
||||
return func(afa *aggrFuncArg) ([]*timeseries, error) {
|
||||
args := afa.args
|
||||
@@ -499,34 +495,42 @@ func newAggrFuncRangeTopK(f func(values []float64) float64, isReverse bool) aggr
|
||||
return nil, err
|
||||
}
|
||||
afe := func(tss []*timeseries) []*timeseries {
|
||||
maxs := make([]tsWithValue, len(tss))
|
||||
for i, ts := range tss {
|
||||
value := f(ts.Values)
|
||||
maxs[i] = tsWithValue{
|
||||
ts: ts,
|
||||
value: value,
|
||||
}
|
||||
}
|
||||
sort.Slice(maxs, func(i, j int) bool {
|
||||
a := maxs[i].value
|
||||
b := maxs[j].value
|
||||
if isReverse {
|
||||
a, b = b, a
|
||||
}
|
||||
return lessWithNaNs(a, b)
|
||||
})
|
||||
for i := range maxs {
|
||||
tss[i] = maxs[i].ts
|
||||
}
|
||||
for i, k := range ks {
|
||||
fillNaNsAtIdx(i, k, tss)
|
||||
}
|
||||
return removeNaNs(tss)
|
||||
return getRangeTopKTimeseries(tss, ks, f, isReverse)
|
||||
}
|
||||
return aggrFuncExt(afe, args[1], &afa.ae.Modifier, afa.ae.Limit, true)
|
||||
}
|
||||
}
|
||||
|
||||
func getRangeTopKTimeseries(tss []*timeseries, ks []float64, f func(values []float64) float64, isReverse bool) []*timeseries {
|
||||
type tsWithValue struct {
|
||||
ts *timeseries
|
||||
value float64
|
||||
}
|
||||
maxs := make([]tsWithValue, len(tss))
|
||||
for i, ts := range tss {
|
||||
value := f(ts.Values)
|
||||
maxs[i] = tsWithValue{
|
||||
ts: ts,
|
||||
value: value,
|
||||
}
|
||||
}
|
||||
sort.Slice(maxs, func(i, j int) bool {
|
||||
a := maxs[i].value
|
||||
b := maxs[j].value
|
||||
if isReverse {
|
||||
a, b = b, a
|
||||
}
|
||||
return lessWithNaNs(a, b)
|
||||
})
|
||||
for i := range maxs {
|
||||
tss[i] = maxs[i].ts
|
||||
}
|
||||
for i, k := range ks {
|
||||
fillNaNsAtIdx(i, k, tss)
|
||||
}
|
||||
return removeNaNs(tss)
|
||||
}
|
||||
|
||||
func fillNaNsAtIdx(idx int, k float64, tss []*timeseries) {
|
||||
if math.IsNaN(k) {
|
||||
k = 0
|
||||
@@ -588,16 +592,54 @@ func avgValue(values []float64) float64 {
|
||||
func medianValue(values []float64) float64 {
|
||||
h := histogram.GetFast()
|
||||
for _, v := range values {
|
||||
if math.IsNaN(v) {
|
||||
continue
|
||||
if !math.IsNaN(v) {
|
||||
h.Update(v)
|
||||
}
|
||||
h.Update(v)
|
||||
}
|
||||
value := h.Quantile(0.5)
|
||||
histogram.PutFast(h)
|
||||
return value
|
||||
}
|
||||
|
||||
func aggrFuncOutliersK(afa *aggrFuncArg) ([]*timeseries, error) {
|
||||
args := afa.args
|
||||
if err := expectTransformArgsNum(args, 2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ks, err := getScalar(args[0], 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
afe := func(tss []*timeseries) []*timeseries {
|
||||
// Calculate medians for each point across tss.
|
||||
medians := make([]float64, len(ks))
|
||||
h := histogram.GetFast()
|
||||
for n := range ks {
|
||||
h.Reset()
|
||||
for j := range tss {
|
||||
v := tss[j].Values[n]
|
||||
if !math.IsNaN(v) {
|
||||
h.Update(v)
|
||||
}
|
||||
}
|
||||
medians[n] = h.Quantile(0.5)
|
||||
}
|
||||
histogram.PutFast(h)
|
||||
|
||||
// Return topK time series with the highest variance from median.
|
||||
f := func(values []float64) float64 {
|
||||
sum2 := float64(0)
|
||||
for n, v := range values {
|
||||
d := v - medians[n]
|
||||
sum2 += d * d
|
||||
}
|
||||
return sum2
|
||||
}
|
||||
return getRangeTopKTimeseries(tss, ks, f, false)
|
||||
}
|
||||
return aggrFuncExt(afe, args[1], &afa.ae.Modifier, afa.ae.Limit, true)
|
||||
}
|
||||
|
||||
func aggrFuncLimitK(afa *aggrFuncArg) ([]*timeseries, error) {
|
||||
args := afa.args
|
||||
if err := expectTransformArgsNum(args, 2); err != nil {
|
||||
@@ -658,24 +700,18 @@ func aggrFuncMedian(afa *aggrFuncArg) ([]*timeseries, error) {
|
||||
func newAggrQuantileFunc(phis []float64) func(tss []*timeseries) []*timeseries {
|
||||
return func(tss []*timeseries) []*timeseries {
|
||||
dst := tss[0]
|
||||
h := histogram.GetFast()
|
||||
defer histogram.PutFast(h)
|
||||
for n := range dst.Values {
|
||||
sort.Slice(tss, func(i, j int) bool {
|
||||
a := tss[i].Values[n]
|
||||
b := tss[j].Values[n]
|
||||
return lessWithNaNs(a, b)
|
||||
})
|
||||
h.Reset()
|
||||
for j := range tss {
|
||||
v := tss[j].Values[n]
|
||||
if !math.IsNaN(v) {
|
||||
h.Update(v)
|
||||
}
|
||||
}
|
||||
phi := phis[n]
|
||||
if math.IsNaN(phi) {
|
||||
phi = 1
|
||||
}
|
||||
if phi < 0 {
|
||||
phi = 0
|
||||
}
|
||||
if phi > 1 {
|
||||
phi = 1
|
||||
}
|
||||
idx := int(math.Round(float64(len(tss)-1) * phi))
|
||||
dst.Values[n] = tss[idx].Values[n]
|
||||
dst.Values[n] = h.Quantile(phi)
|
||||
}
|
||||
tss[0] = dst
|
||||
return tss[:1]
|
||||
|
||||
@@ -465,7 +465,6 @@ func finalizeAggrGeomean(iac *incrementalAggrContext) {
|
||||
}
|
||||
|
||||
func updateAggrAny(iac *incrementalAggrContext, values []float64) {
|
||||
dstValues := iac.ts.Values
|
||||
dstCounts := iac.values
|
||||
if dstCounts[0] > 0 {
|
||||
return
|
||||
@@ -473,17 +472,16 @@ func updateAggrAny(iac *incrementalAggrContext, values []float64) {
|
||||
for i := range values {
|
||||
dstCounts[i] = 1
|
||||
}
|
||||
dstValues = append(dstValues[:0], values...)
|
||||
iac.ts.Values = append(iac.ts.Values[:0], values...)
|
||||
}
|
||||
|
||||
func mergeAggrAny(dst, src *incrementalAggrContext) {
|
||||
srcValues := src.ts.Values
|
||||
dstValues := dst.ts.Values
|
||||
srcCounts := src.values
|
||||
dstCounts := dst.values
|
||||
if dstCounts[0] > 0 {
|
||||
return
|
||||
}
|
||||
dstCounts[0] = srcCounts[0]
|
||||
dstValues = append(dstValues[:0], srcValues...)
|
||||
dst.ts.Values = append(dst.ts.Values[:0], srcValues...)
|
||||
}
|
||||
|
||||
@@ -3577,7 +3577,7 @@ func TestExecSuccess(t *testing.T) {
|
||||
q := `sort(histogram_over_time(alias(label_set(rand(0)*1.3+1.1, "foo", "bar"), "xxx")[200s:5s]))`
|
||||
r1 := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{14, 15, 12, 13, 15, 11},
|
||||
Values: []float64{14, 16, 12, 13, 15, 11},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r1.MetricName.Tags = []storage.Tag{
|
||||
@@ -3607,7 +3607,7 @@ func TestExecSuccess(t *testing.T) {
|
||||
}
|
||||
r3 := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{13, 11, 16, 19, 13, 16},
|
||||
Values: []float64{13, 10, 16, 19, 13, 16},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r3.MetricName.Tags = []storage.Tag{
|
||||
@@ -3628,7 +3628,7 @@ func TestExecSuccess(t *testing.T) {
|
||||
q := `sort(sum(histogram_over_time(alias(label_set(rand(0)*1.3+1.1, "foo", "bar"), "xxx")[200s:5s])) by (vmrange))`
|
||||
r1 := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{14, 15, 12, 13, 15, 11},
|
||||
Values: []float64{14, 16, 12, 13, 15, 11},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r1.MetricName.Tags = []storage.Tag{
|
||||
@@ -3650,7 +3650,7 @@ func TestExecSuccess(t *testing.T) {
|
||||
}
|
||||
r3 := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{13, 11, 16, 19, 13, 16},
|
||||
Values: []float64{13, 10, 16, 19, 13, 16},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r3.MetricName.Tags = []storage.Tag{
|
||||
@@ -3678,7 +3678,7 @@ func TestExecSuccess(t *testing.T) {
|
||||
q := `topk_max(1, histogram_over_time(alias(label_set(rand(0)*1.3+1.1, "foo", "bar"), "xxx")[200s:5s]))`
|
||||
r := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{13, 11, 16, 19, 13, 16},
|
||||
Values: []float64{13, 10, 16, 19, 13, 16},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r.MetricName.Tags = []storage.Tag{
|
||||
@@ -4196,14 +4196,63 @@ func TestExecSuccess(t *testing.T) {
|
||||
t.Run(`quantile(NaN)`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `quantile(NaN, label_set(10, "foo", "bar") or label_set(time()/150, "baz", "sss"))`
|
||||
resultExpected := []netstorage.Result{}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`outliersk(0)`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `outliersk(0, (
|
||||
label_set(1300, "foo", "bar"),
|
||||
label_set(time(), "baz", "sss"),
|
||||
))`
|
||||
resultExpected := []netstorage.Result{}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`outliersk(1)`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `outliersk(1, (
|
||||
label_set(2000, "foo", "bar"),
|
||||
label_set(time(), "baz", "sss"),
|
||||
))`
|
||||
r := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{10, 10, 10, 10.666666666666666, 12, 13.333333333333334},
|
||||
Values: []float64{1000, 1200, 1400, 1600, 1800, 2000},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r.MetricName.Tags = []storage.Tag{{
|
||||
Key: []byte("baz"),
|
||||
Value: []byte("sss"),
|
||||
}}
|
||||
resultExpected := []netstorage.Result{r}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`outliersk(3)`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `sort_desc(outliersk(3, (
|
||||
label_set(1300, "foo", "bar"),
|
||||
label_set(time(), "baz", "sss"),
|
||||
)))`
|
||||
r1 := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{1000, 1200, 1400, 1600, 1800, 2000},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r1.MetricName.Tags = []storage.Tag{{
|
||||
Key: []byte("baz"),
|
||||
Value: []byte("sss"),
|
||||
}}
|
||||
r2 := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{1300, 1300, 1300, 1300, 1300, 1300},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r2.MetricName.Tags = []storage.Tag{{
|
||||
Key: []byte("foo"),
|
||||
Value: []byte("bar"),
|
||||
}}
|
||||
resultExpected := []netstorage.Result{r1, r2}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`range_quantile(0.5)`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `range_quantile(0.5, time())`
|
||||
@@ -5531,6 +5580,8 @@ func TestExecError(t *testing.T) {
|
||||
f(`hoeffding_bound_upper()`)
|
||||
f(`hoeffding_bound_upper(1)`)
|
||||
f(`hoeffding_bound_upper(0.99, foo, 1)`)
|
||||
f(`outliersk()`)
|
||||
f(`outliersk(1)`)
|
||||
|
||||
// Invalid argument type
|
||||
f(`median_over_time({}, 2)`)
|
||||
@@ -5570,6 +5621,7 @@ func TestExecError(t *testing.T) {
|
||||
f(`alias(1, 2)`)
|
||||
f(`aggr_over_time(1, 2)`)
|
||||
f(`aggr_over_time(("foo", "bar"), 3)`)
|
||||
f(`outliersk((label_set(1, "foo", "bar"), label_set(2, "x", "y")), 123)`)
|
||||
|
||||
// Duplicate timeseries
|
||||
f(`(label_set(1, "foo", "bar") or label_set(2, "foo", "baz"))
|
||||
|
||||
@@ -72,6 +72,8 @@ var rollupFuncs = map[string]newRollupFunc{
|
||||
"aggr_over_time": newRollupFuncTwoArgs(rollupFake),
|
||||
"hoeffding_bound_upper": newRollupHoeffdingBoundUpper,
|
||||
"hoeffding_bound_lower": newRollupHoeffdingBoundLower,
|
||||
"ascent_over_time": newRollupFuncOneArg(rollupAscentOverTime),
|
||||
"descent_over_time": newRollupFuncOneArg(rollupDescentOverTime),
|
||||
|
||||
// `timestamp` function must return timestamp for the last datapoint on the current window
|
||||
// in order to properly handle offset and timestamps unaligned to the current step.
|
||||
@@ -116,6 +118,9 @@ var rollupAggrFuncs = map[string]rollupFunc{
|
||||
"scrape_interval": rollupScrapeInterval,
|
||||
"tmin_over_time": rollupTmin,
|
||||
"tmax_over_time": rollupTmax,
|
||||
"ascent_over_time": rollupAscentOverTime,
|
||||
"descent_over_time": rollupDescentOverTime,
|
||||
"timestamp": rollupTimestamp,
|
||||
}
|
||||
|
||||
var rollupFuncsCannotAdjustWindow = map[string]bool{
|
||||
@@ -138,6 +143,8 @@ var rollupFuncsCannotAdjustWindow = map[string]bool{
|
||||
"increases_over_time": true,
|
||||
"decreases_over_time": true,
|
||||
"integrate": true,
|
||||
"ascent_over_time": true,
|
||||
"descent_over_time": true,
|
||||
}
|
||||
|
||||
var rollupFuncsRemoveCounterResets = map[string]bool{
|
||||
@@ -1527,6 +1534,52 @@ func rollupTimestamp(rfa *rollupFuncArg) float64 {
|
||||
return float64(timestamps[len(timestamps)-1]) / 1e3
|
||||
}
|
||||
|
||||
func rollupAscentOverTime(rfa *rollupFuncArg) float64 {
|
||||
// There is no need in handling NaNs here, since they must be cleaned up
|
||||
// before calling rollup funcs.
|
||||
values := rfa.values
|
||||
prevValue := rfa.prevValue
|
||||
if math.IsNaN(prevValue) {
|
||||
if len(values) == 0 {
|
||||
return nan
|
||||
}
|
||||
prevValue = values[0]
|
||||
values = values[1:]
|
||||
}
|
||||
s := float64(0)
|
||||
for _, v := range values {
|
||||
d := v - prevValue
|
||||
if d > 0 {
|
||||
s += d
|
||||
}
|
||||
prevValue = v
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func rollupDescentOverTime(rfa *rollupFuncArg) float64 {
|
||||
// There is no need in handling NaNs here, since they must be cleaned up
|
||||
// before calling rollup funcs.
|
||||
values := rfa.values
|
||||
prevValue := rfa.prevValue
|
||||
if math.IsNaN(prevValue) {
|
||||
if len(values) == 0 {
|
||||
return nan
|
||||
}
|
||||
prevValue = values[0]
|
||||
values = values[1:]
|
||||
}
|
||||
s := float64(0)
|
||||
for _, v := range values {
|
||||
d := prevValue - v
|
||||
if d > 0 {
|
||||
s += d
|
||||
}
|
||||
prevValue = v
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func rollupFirst(rfa *rollupFuncArg) float64 {
|
||||
// There is no need in handling NaNs here, since they must be cleaned up
|
||||
// before calling rollup funcs.
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/memory"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/workingsetcache"
|
||||
@@ -64,18 +65,18 @@ func InitRollupResultCache(cachePath string) {
|
||||
|
||||
stats := &fastcache.Stats{}
|
||||
var statsLock sync.Mutex
|
||||
var statsLastUpdate time.Time
|
||||
var statsLastUpdate uint64
|
||||
fcs := func() *fastcache.Stats {
|
||||
statsLock.Lock()
|
||||
defer statsLock.Unlock()
|
||||
|
||||
if time.Since(statsLastUpdate) < time.Second {
|
||||
if fasttime.UnixTimestamp()-statsLastUpdate < 2 {
|
||||
return stats
|
||||
}
|
||||
var fcs fastcache.Stats
|
||||
c.UpdateStats(&fcs)
|
||||
stats = &fcs
|
||||
statsLastUpdate = time.Now()
|
||||
statsLastUpdate = fasttime.UnixTimestamp()
|
||||
return stats
|
||||
}
|
||||
if len(rollupResultCachePath) > 0 {
|
||||
|
||||
@@ -389,6 +389,9 @@ func TestRollupNewRollupFuncSuccess(t *testing.T) {
|
||||
f("ideriv", 0)
|
||||
f("decreases_over_time", 5)
|
||||
f("increases_over_time", 5)
|
||||
f("ascent_over_time", 142)
|
||||
f("descent_over_time", 231)
|
||||
f("timestamp", 0.13)
|
||||
}
|
||||
|
||||
func TestRollupNewRollupFuncError(t *testing.T) {
|
||||
|
||||
@@ -409,6 +409,16 @@ func registerStorageMetrics() {
|
||||
return float64(m().AddRowsConcurrencyCurrent)
|
||||
})
|
||||
|
||||
metrics.NewGauge(`vm_slow_row_inserts_total`, func() float64 {
|
||||
return float64(m().SlowRowInserts)
|
||||
})
|
||||
metrics.NewGauge(`vm_slow_per_day_index_inserts_total`, func() float64 {
|
||||
return float64(m().SlowPerDayIndexInserts)
|
||||
})
|
||||
metrics.NewGauge(`vm_slow_metric_name_loads_total`, func() float64 {
|
||||
return float64(m().SlowMetricNameLoads)
|
||||
})
|
||||
|
||||
metrics.NewGauge(`vm_rows{type="storage/big"}`, func() float64 {
|
||||
return float64(tm().BigRowsCount)
|
||||
})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@ DOCKER_NAMESPACE := victoriametrics
|
||||
|
||||
ROOT_IMAGE ?= scratch
|
||||
CERTS_IMAGE := alpine:3.11
|
||||
GO_BUILDER_IMAGE := golang:1.14.2
|
||||
GO_BUILDER_IMAGE := golang:1.14.3
|
||||
BUILDER_IMAGE := local/builder:2.0.0-$(shell echo $(GO_BUILDER_IMAGE) | tr : _)
|
||||
BASE_IMAGE := local/base:1.1.1-$(shell echo $(ROOT_IMAGE) | tr : _)-$(shell echo $(CERTS_IMAGE) | tr : _)
|
||||
|
||||
@@ -33,7 +33,7 @@ app-via-docker: package-base package-builder
|
||||
$(DOCKER_OPTS) \
|
||||
$(BUILDER_IMAGE) \
|
||||
go build $(RACE) -mod=vendor -trimpath \
|
||||
-ldflags "-s -w -extldflags '-static' $(GO_BUILDINFO)" \
|
||||
-ldflags "-extldflags '-static' $(GO_BUILDINFO)" \
|
||||
-tags 'netgo osusergo nethttpomithttp2' \
|
||||
-o bin/$(APP_NAME)$(APP_SUFFIX)-prod $(PKG_PREFIX)/app/$(APP_NAME)
|
||||
|
||||
|
||||
@@ -26,11 +26,13 @@
|
||||
* [Billy: how VictoriaMetrics deals with more than 500 billion rows](https://medium.com/@valyala/billy-how-victoriametrics-deals-with-more-than-500-billion-rows-e82ff8f725da)
|
||||
|
||||
|
||||
## Third-party articles
|
||||
## Third-party articles and slides
|
||||
|
||||
* [Better Prometheus rate() function with VictoriaMetrics](https://www.percona.com/blog/2020/02/28/better-prometheus-rate-function-with-victoriametrics/)
|
||||
* [Infrastructure monitoring with Prometheus at Zerodha](https://zerodha.tech/blog/infra-monitoring-at-zerodha/)
|
||||
* [Monitoring K8S with VictoriaMetrics](https://docs.google.com/presentation/d/1g7yUyVEaAp4tPuRy-MZbPXKqJ1z78_5VKuV841aQfsg/edit)
|
||||
* [CMS monitoring R&D: Real-time monitoring and alerts](https://indico.cern.ch/event/877333/contributions/3696707/attachments/1972189/3281133/CMS_mon_RD_for_opInt.pdf)
|
||||
* [Disk usage: VictoriaMetrics vs Prometheus](https://stas.starikevich.com/posts/disk-usage-for-vm-versus-prometheus/)
|
||||
* [Benchmarking time series workloads on Apache Kudu using TSBS](https://blog.cloudera.com/benchmarking-time-series-workloads-on-apache-kudu-using-tsbs/)
|
||||
* [What are Open Source Time Series Databases?](https://www.iunera.com/kraken/fabric/time-series-database/)
|
||||
* [Evaluating performance and correctness](https://www.robustperception.io/evaluating-performance-and-correctness)
|
||||
|
||||
@@ -39,6 +39,28 @@ See [slides](https://speakerdeck.com/inletorder/monitoring-platform-with-victori
|
||||
from `Large-scale, super-load system monitoring platform built with VictoriaMetrics` talk at [Prometheus Meetup Tokyo #3](https://prometheus.connpass.com/event/157721/).
|
||||
|
||||
|
||||
## Zerodha
|
||||
|
||||
[Zerodha](https://zerodha.com/) is India's largest stock broker. Monitoring team at Zerodha faced with the following requirements:
|
||||
|
||||
* Multiple K8s clusters to monitor
|
||||
* Consistent monitoring infra for each cluster across the fleet
|
||||
* Ability to handle billions of timeseries events at any point of time
|
||||
* Easier to operate and cost effective
|
||||
|
||||
Thanos, Cortex and VictoriaMetrics were evaluated as a long-term storage for Prometheus. VictoriaMetrics has been selected due to the following reasons:
|
||||
|
||||
* Blazing fast benchmarks for a single node setup.
|
||||
* Single binary mode. Easy to scale vertically, very less operational headache.
|
||||
* Considerable [improvements on creating Histograms](https://medium.com/@valyala/improving-histogram-usability-for-prometheus-and-grafana-bc7e5df0e350).
|
||||
* [MetricsQL](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/MetricsQL) gives us the ability to extend PromQL with more aggregation operators.
|
||||
* API is compatible with Prometheus, almost all standard PromQL queries just work out of the box.
|
||||
* Handles storage well, with periodic compaction. Makes it easy to take snapshots.
|
||||
|
||||
See [Monitoring K8S with VictoriaMetrics](https://docs.google.com/presentation/d/1g7yUyVEaAp4tPuRy-MZbPXKqJ1z78_5VKuV841aQfsg/edit) slides,
|
||||
[video](https://youtu.be/ZJQYW-cFOms) and [Infrastructure monitoring with Prometheus at Zerodha](https://zerodha.tech/blog/infra-monitoring-at-zerodha/) blog post for more details.
|
||||
|
||||
|
||||
## Wix.com
|
||||
|
||||
[Wix.com](https://en.wikipedia.org/wiki/Wix.com) is the leading web development platform.
|
||||
@@ -209,7 +231,7 @@ Such a scheme has the following benefits comparing to Prometheus:
|
||||
|
||||
Cons are the following:
|
||||
|
||||
- VictoriaMetrics doesn't support replication - we run extra instance of VictoriaMetrics and Promxy in front of VictoriaMetrics pair for high availability.
|
||||
- VictoriaMetrics didn't support replication (it [supports replication now](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/cluster/README.md#replication-and-data-safety)) - we run extra instance of VictoriaMetrics and Promxy in front of VictoriaMetrics pair for high availability.
|
||||
- VictoriaMetrics stores 1 extra month for defined retention (if retention is set to N months, then VM stores N+1 months of data), but this is still better than other solutions.
|
||||
|
||||
Some numbers from our single-node VictoriaMetrics setup:
|
||||
|
||||
@@ -16,8 +16,9 @@ Join [our Slack](http://slack.victoriametrics.com/) or [contact us](mailto:info@
|
||||
## Prominent features
|
||||
|
||||
- Supports all the features of [single-node version](https://github.com/VictoriaMetrics/VictoriaMetrics).
|
||||
- Performance and capacity scales horizontally.
|
||||
- Supports multiple independent namespaces for time series data (aka multi-tenancy).
|
||||
- Performance and capacity scales horizontally. See [these docs for details](#cluster-resizing-and-scalability).
|
||||
- Supports multiple independent namespaces for time series data (aka multi-tenancy). See [these docs for details](#multitenancy).
|
||||
- Supports replication. See [these docs for details](#replication-and-data-safety).
|
||||
|
||||
|
||||
## Architecture overview
|
||||
@@ -45,7 +46,8 @@ See [these docs](#url-format) for details. Some facts about tenants in VictoriaM
|
||||
* Each `accountID` and `projectID` is identified by an arbitrary 32-bit integer in the range `[0 .. 2^32)`.
|
||||
If `projectID` is missing, then it is automatically assigned to `0`. It is expected that other information about tenants
|
||||
such as auth tokens, tenant names, limits, accounting, etc. is stored in a separate relational database. This database must be managed
|
||||
by a separate service sitting in front of VictoriaMetrics cluster.
|
||||
by a separate service sitting in front of VictoriaMetrics cluster such as [vmauth](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmauth/README.md).
|
||||
[Contact us](mailto:info@victoriametrics.com) if you need help with creating such a service.
|
||||
|
||||
* Tenants are automatically created when the first data point is written into the given tenant.
|
||||
|
||||
@@ -130,7 +132,7 @@ A minimal cluster must contain the following nodes:
|
||||
It is recommended to run at least two nodes for each service
|
||||
for high availability purposes.
|
||||
|
||||
An http load balancer must be put in front of `vminsert` and `vmselect` nodes:
|
||||
An http load balancer such as `nginx` must be put in front of `vminsert` and `vmselect` nodes:
|
||||
- requests starting with `/insert` must be routed to port `8480` on `vminsert` nodes.
|
||||
- requests starting with `/select` must be routed to port `8481` on `vmselect` nodes.
|
||||
|
||||
@@ -156,7 +158,8 @@ By default the following TCP ports are used:
|
||||
- `vmselect` - 8481
|
||||
- `vmstorage` - 8482
|
||||
|
||||
It is recommended setting up Prometheus to scrape `/metrics` pages from all the cluster components, so they can be monitored and analyzed
|
||||
It is recommended setting up [vmagent](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmagent/README.md)
|
||||
or Prometheus to scrape `/metrics` pages from all the cluster components, so they can be monitored and analyzed
|
||||
with [the official Grafana dashboard for VictoriaMetrics cluster](https://grafana.com/grafana/dashboards/11176)
|
||||
or [an alternative dashboard for VictoriaMetrics cluster](https://grafana.com/grafana/dashboards/11831).
|
||||
|
||||
@@ -167,21 +170,23 @@ or [an alternative dashboard for VictoriaMetrics cluster](https://grafana.com/gr
|
||||
- `<accountID>` is an arbitrary 32-bit integer identifying namespace for data ingestion (aka tenant). It is possible to set it as `accountID:projectID`,
|
||||
where `projectID` is also arbitrary 32-bit integer. If `projectID` isn't set, then it equals to `0`.
|
||||
- `<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` and `prometheus/api/v1/write` - for inserting data with [Prometheus remote write API](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_write)
|
||||
- `influx/write` and `influx/api/v2/write` - for inserting data with [Influx line protocol](https://docs.influxdata.com/influxdb/v1.7/write_protocols/line_protocol_tutorial/).
|
||||
- `opentsdb/api/put` - for accepting [OpenTSDB HTTP /api/put requests](http://opentsdb.net/docs/build/html/api_http/put.html).
|
||||
- `prometheus/api/v1/import` - for importing data obtained via `api/v1/export` on `vmselect` (see below).
|
||||
- `prometheus/api/v1/import/csv` - for importing arbitrary CSV data. See [these docs](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/README.md#how-to-import-csv-data) for details.
|
||||
|
||||
* URLs for querying: `http://<vmselect>:8481/select/<accountID>/prometheus/<suffix>`, where:
|
||||
- `<accountID>` is an arbitrary number identifying data namespace for the query (aka tenant)
|
||||
- `<suffix>` may have the following values:
|
||||
- `api/v1/query` - performs [PromQL instant query](https://prometheus.io/docs/prometheus/latest/querying/api/#instant-queries)
|
||||
- `api/v1/query_range` - performs [PromQL range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries)
|
||||
- `api/v1/series` - performs [series query](https://prometheus.io/docs/prometheus/latest/querying/api/#finding-series-by-label-matchers)
|
||||
- `api/v1/labels` - returns a [list of label names](https://prometheus.io/docs/prometheus/latest/querying/api/#getting-label-names)
|
||||
- `api/v1/label/<label_name>/values` - returns values for the given `<label_name>` according [to API](https://prometheus.io/docs/prometheus/latest/querying/api/#querying-label-values)
|
||||
- `federate` - returns [federated metrics](https://prometheus.io/docs/prometheus/latest/federation/)
|
||||
- `api/v1/export` - exports raw data. See [this article](https://medium.com/@valyala/analyzing-prometheus-data-with-external-tools-5f3e5e147639) for details
|
||||
- `api/v1/query` - performs [PromQL instant query](https://prometheus.io/docs/prometheus/latest/querying/api/#instant-queries).
|
||||
- `api/v1/query_range` - performs [PromQL range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries).
|
||||
- `api/v1/series` - performs [series query](https://prometheus.io/docs/prometheus/latest/querying/api/#finding-series-by-label-matchers).
|
||||
- `api/v1/labels` - returns a [list of label names](https://prometheus.io/docs/prometheus/latest/querying/api/#getting-label-names).
|
||||
- `api/v1/label/<label_name>/values` - returns values for the given `<label_name>` according [to API](https://prometheus.io/docs/prometheus/latest/querying/api/#querying-label-values).
|
||||
- `federate` - returns [federated metrics](https://prometheus.io/docs/prometheus/latest/federation/).
|
||||
- `api/v1/export` - exports raw data. See [this article](https://medium.com/@valyala/analyzing-prometheus-data-with-external-tools-5f3e5e147639) for details.
|
||||
- `api/v1/status/tsdb` - for time series stats. See [these docs](https://prometheus.io/docs/prometheus/latest/querying/api/#tsdb-stats) for details.
|
||||
|
||||
* URL for time series deletion: `http://<vmselect>:8481/delete/<accountID>/prometheus/api/v1/admin/tsdb/delete_series?match[]=<timeseries_selector_for_delete>`.
|
||||
Note that the `delete_series` handler should be used only in exceptional cases such as deletion of accidentally ingested incorrect time series. It shouldn't
|
||||
@@ -199,7 +204,7 @@ or [an alternative dashboard for VictoriaMetrics cluster](https://grafana.com/gr
|
||||
across `vmstorage` nodes.
|
||||
|
||||
|
||||
### Cluster resizing and scalability.
|
||||
### Cluster resizing and scalability
|
||||
|
||||
Cluster performance and capacity scales with adding new nodes.
|
||||
|
||||
@@ -246,6 +251,8 @@ Each instance type - `vminsert`, `vmselect` and `vmstorage` - can run on the mos
|
||||
* The recommended total number of vCPU cores for all the `vminsert` instances can be calculated from the ingestion rate: `vCPUs = ingestion_rate / 150K`.
|
||||
* The recommended number of vCPU cores per each `vminsert` instance should equal to the number of `vmstorage` instances in the cluster.
|
||||
* The amount of RAM per each `vminsert` instance should be 1GB or more. RAM is used as a buffer for spikes in ingestion rate.
|
||||
The maximum amount of used RAM per `vminsert` node can be tuned with `-memory.allowedPercent` command-line flag. For instance, `-memory.allowedPercent=20`
|
||||
limits the maximum amount of used RAM to 20% of the available RAM on the host system.
|
||||
* Sometimes `-rpc.disableCompression` command-line flag on `vminsert` instances could increase ingestion capacity at the cost
|
||||
of higher network bandwidth usage between `vminsert` and `vmstorage`.
|
||||
|
||||
@@ -277,7 +284,7 @@ Upgrade follows `Cluster resizing procedure` under the hood.
|
||||
|
||||
### Replication and data safety
|
||||
|
||||
VictoriaMetrics offloads replication to the underlying storage pointed by `-storageDataPath`.
|
||||
By default VictoriaMetrics offloads replication to the underlying storage pointed by `-storageDataPath`.
|
||||
It is recommended storing data on [Google Compute Engine persistent disks](https://cloud.google.com/compute/docs/disks/#pdspecs),
|
||||
since they are protected from data loss and data corruption. They also provide consistently high performance
|
||||
and [may be resized](https://cloud.google.com/compute/docs/disks/add-persistent-disk) without downtime.
|
||||
@@ -285,7 +292,13 @@ HDD-based persistent disks should be enough for the majority of use cases.
|
||||
|
||||
It is recommended using durable replicated persistent volumes in Kubernetes.
|
||||
|
||||
Note that [replication doesn't save from disaster](https://medium.com/@valyala/speeding-up-backups-for-big-time-series-databases-533c1a927883).
|
||||
If `-replicationFactor=N` command-line flag is passed to `vminsert`, then `vminsert` puts `N` copies of the ingested data to distinct `vmstorage` nodes.
|
||||
This guarantees that all the data remains available for querying if up to `N-1` `vmstorage` nodes are unavailable. Note that `-dedup.minScrapeInterval=1ms` command-line
|
||||
flag must be passed to `vmselect` if `-replicationFactor` exceeds 1 in order to de-duplicate replicated data during queries.
|
||||
It is OK if `-dedup.minScrapeInterval` exceeds 1ms.
|
||||
|
||||
Note that [replication doesn't save from disaster](https://medium.com/@valyala/speeding-up-backups-for-big-time-series-databases-533c1a927883),
|
||||
so it is recommended performing regular backups. See [these docs](#backups) for details.
|
||||
|
||||
|
||||
### Backups
|
||||
|
||||
11
docs/FAQ.md
11
docs/FAQ.md
@@ -136,6 +136,8 @@ Yes:
|
||||
* [TSBS benchmark on high-cardinality time series: VictoriaMetrics vs InfluxDB vs TimescaleDB](https://medium.com/@valyala/high-cardinality-tsdb-benchmarks-victoriametrics-vs-timescaledb-vs-influxdb-13e6ee64dd6b)
|
||||
* [Standard TSBS benchmark: VictoriaMetrics vs InfluxDB vs TimescaleDB](https://medium.com/@valyala/when-size-matters-benchmarking-victoriametrics-vs-timescale-and-influxdb-6035811952d4)
|
||||
|
||||
See also [other articles about VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/Articles).
|
||||
|
||||
|
||||
### What is the pricing for VictoriaMetrics?
|
||||
|
||||
@@ -145,11 +147,11 @@ The following versions are open source and free:
|
||||
|
||||
We provide commercial support for both versions. [Contact us](mailto:info@victoriametrics.com) for the pricing.
|
||||
|
||||
The following versions are commercial:
|
||||
The following commercial versions of VictoriaMetrics are planned:
|
||||
* Managed cluster in the Cloud.
|
||||
* SaaS version.
|
||||
|
||||
[Contact us](mailto:info@victoriametrics.com) for the pricing.
|
||||
[Contact us](mailto:info@victoriametrics.com) for more information and for the pricing.
|
||||
|
||||
|
||||
### Why VictoriaMetrics doesn't support [Prometheus remote read API](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#%3Cremote_read%3E)?
|
||||
@@ -168,6 +170,11 @@ or via [Prometheus datasource in Grafana](http://docs.grafana.org/features/datas
|
||||
Yes. See [these docs](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/README.md#deduplication) for details.
|
||||
|
||||
|
||||
### Does VictoriaMetrics support replication?
|
||||
|
||||
Yes. See [these docs](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/cluster/README.md#replication-and-data-safety) for details.
|
||||
|
||||
|
||||
### Where is the source code of VictoriaMetrics?
|
||||
|
||||
Source code for the following versions is available in the following places:
|
||||
|
||||
@@ -4,6 +4,8 @@ VictoriaMetrics implements MetricsQL - query language inspired by [PromQL](https
|
||||
It is backwards compatible with PromQL, so Grafana dashboards backed by Prometheus datasource should work the same after switching from Prometheus to VictoriaMetrics.
|
||||
[Standalone MetricsQL package](https://godoc.org/github.com/VictoriaMetrics/metricsql) can be used for parsing MetricsQL in external apps.
|
||||
|
||||
If you are unfamiliar with PromQL, then it is suggested reading [this tutorial for beginners](https://medium.com/@valyala/promql-tutorial-for-beginners-9ab455142085).
|
||||
|
||||
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).
|
||||
@@ -116,3 +118,7 @@ This functionality can be tried at [an editable Grafana dashboard](http://play-g
|
||||
for the given `phi` in the range `[0..1]`.
|
||||
- `last_over_time(m[d])` - returns the last value for `m` on the time range `d`.
|
||||
- `first_over_time(m[d])` - returns the first value for `m` on the time range `d`.
|
||||
- `outliersk(N, m)` - returns up to `N` outlier time series for `m`. Outlier time series have the highest deviation from the `median(m)`.
|
||||
This aggregate function is useful to detect anomalies across groups of similar time series.
|
||||
- `ascent_over_time(m[d])` - returns the sum of positive deltas between adjancent data points in `m` over `d`. Useful for tracking height gains in GPS track.
|
||||
- `descent_over_time(m[d])` - returns the absolute sum of negative deltas between adjancent data points in `m` over `d`. Useful for tracking height loss in GPS track.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Quick Start
|
||||
|
||||
1. Download the latest VictoriaMetrics release from [releases page](https://github.com/VictoriaMetrics/VictoriaMetrics/releases),
|
||||
from [Docker hub](https://hub.docker.com/r/valyala/victoria-metrics/)
|
||||
from [Docker hub](https://hub.docker.com/r/victoriametrics/victoria-metrics/)
|
||||
or [build it from sources](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/Single-server-VictoriaMetrics#how-to-build-from-sources).
|
||||
|
||||
2. Run the binary or Docker image with the desired command-line flags. Pass `-help` in order to see description for all the available flags
|
||||
@@ -17,8 +17,10 @@
|
||||
See [these instructions](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/43) in order to configure VictoriaMetrics as OS service.
|
||||
It is recommended setting up [VictoriaMetrics monitoring](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/README.md#monitoring).
|
||||
|
||||
3. Configure all the Prometheus instances to write data to VictoriaMetrics.
|
||||
See [these instructions](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/Single-server-VictoriaMetrics#prometheus-setup).
|
||||
3. Configure [vmagent](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmagent/README.md) or Prometheus to write data to VictoriaMetrics.
|
||||
It is recommended to use `vmagent` instead of Prometheus, since it is more resource efficient. If you still prefer Prometheus, then
|
||||
see [these instructions](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/Single-server-VictoriaMetrics#prometheus-setup)
|
||||
for details on how to configure Prometheus.
|
||||
|
||||
4. Configure Grafana to query VictoriaMetrics instead of Prometheus.
|
||||
See [these instructions](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/Single-server-VictoriaMetrics#grafana-setup).
|
||||
|
||||
@@ -10,18 +10,25 @@
|
||||
|
||||
## VictoriaMetrics
|
||||
|
||||
VictoriaMetrics is fast, cost-effective and scalable time-series database. It can be used as long-term remote storage for Prometheus.
|
||||
VictoriaMetrics is fast, cost-effective and scalable time-series database.
|
||||
|
||||
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). 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).
|
||||
|
||||
See our [Wiki](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki) for additional documentation.
|
||||
|
||||
[Contact us](mailto:info@victoriametrics.com) if you need paid enterprise support for VictoriaMetrics.
|
||||
|
||||
|
||||
## Case studies and talks
|
||||
|
||||
* [Adidas](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/CaseStudies#adidas)
|
||||
* [CERN](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/CaseStudies#cern)
|
||||
* [COLOPL](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/CaseStudies#colopl)
|
||||
* [Zerodha](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/CaseStudies#zerodha)
|
||||
* [Wix.com](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/CaseStudies#wixcom)
|
||||
* [Wedos.com](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/CaseStudies#wedoscom)
|
||||
* [Synthesio](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/CaseStudies#synthesio)
|
||||
@@ -34,6 +41,8 @@ Cluster version is available [here](https://github.com/VictoriaMetrics/VictoriaM
|
||||
|
||||
## Prominent features
|
||||
|
||||
* VictoriaMetrics can be used as long-term storage for Prometheus or for [vmagent](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmagent/README.md).
|
||||
See [these docs](#prometheus-setup) for details.
|
||||
* Supports [Prometheus querying API](https://prometheus.io/docs/prometheus/latest/querying/api/), so it can be used as Prometheus drop-in replacement in Grafana.
|
||||
VictoriaMetrics implements [MetricsQL](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/MetricsQL) 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.
|
||||
@@ -116,6 +125,8 @@ Cluster version is available [here](https://github.com/VictoriaMetrics/VictoriaM
|
||||
* [Monitoring](#monitoring)
|
||||
* [Troubleshooting](#troubleshooting)
|
||||
* [Backfilling](#backfilling)
|
||||
* [Replication](#replication)
|
||||
* [Backups](#backups)
|
||||
* [Profiling](#profiling)
|
||||
* [Integrations](#integrations)
|
||||
* [Third-party contributions](#third-party-contributions)
|
||||
@@ -782,6 +793,8 @@ remote_write:
|
||||
kill -HUP `pidof prometheus`
|
||||
```
|
||||
|
||||
It is recommended to use [vmagent](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmagent/README.md) instead of Prometheus for highly loaded setups.
|
||||
|
||||
4) Now Prometheus should write data into all the configured `remote_write` urls in parallel.
|
||||
5) Set up [Promxy](https://github.com/jacksontj/promxy) in front of all the VictoriaMetrics replicas.
|
||||
6) Set up Prometheus datasource in Grafana that points to Promxy.
|
||||
@@ -792,6 +805,7 @@ to write data to `victoriametrics-addr-1`, while each `r2` should write data to
|
||||
Another option is to write data simultaneously from Prometheus HA pair to a pair of VictoriaMetrics instances
|
||||
with the enabled de-duplication. See [this section](#deduplication) for details.
|
||||
|
||||
|
||||
### Deduplication
|
||||
|
||||
VictoriaMetrics de-duplicates data points if `-dedup.minScrapeInterval` command-line flag
|
||||
@@ -809,6 +823,8 @@ Data is split in per-month subdirectories inside `<-storageDataPath>/data/small`
|
||||
Directories for months outside the configured retention are deleted on the first day of new month.
|
||||
In order to keep data according to `-retentionPeriod` max disk space usage is going to be `-retentionPeriod` + 1 month.
|
||||
For example if `-retentionPeriod` is set to 1, data for January is deleted on March 1st.
|
||||
It is safe to extend `-retentionPeriod` on existing data. If `-retentionPeriod` is set to lower
|
||||
value than before then data outside the configured period will be eventually deleted.
|
||||
|
||||
### Multiple retentions
|
||||
|
||||
@@ -874,6 +890,10 @@ Consider setting the following command-line flags:
|
||||
Explicitly set internal network interface for TCP and UDP ports for data ingestion with Graphite and OpenTSDB formats.
|
||||
For example, substitute `-graphiteListenAddr=:2003` with `-graphiteListenAddr=<internal_iface_ip>:2003`.
|
||||
|
||||
Prefer authorizing all the incoming requests from untrusted networks with [vmauth](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmauth/README.md)
|
||||
or similar auth proxy.
|
||||
|
||||
|
||||
### Tuning
|
||||
|
||||
* There is no need for VictoriaMetrics tuning since it uses reasonable defaults for command-line flags,
|
||||
@@ -910,6 +930,12 @@ The most interesting metrics are:
|
||||
* `sum(rate(vm_rows_inserted_total[5m]))` - ingestion rate, i.e. how many samples are inserted int the database per second.
|
||||
* `vm_free_disk_space_bytes` - free space left at `-storageDataPath`.
|
||||
* `sum(vm_data_size_bytes)` - the total size of data on disk.
|
||||
* `increase(vm_slow_row_inserts_total[5m])` - the number of slow inserts during the last 5 minutes.
|
||||
If this number remains high during extended periods of time, then it is likely more RAM is needed for optimal handling
|
||||
of the current number of active time series.
|
||||
* `increase(vm_slow_metric_name_loads_total[5m])` - the number of slow loads of metric names during the last 5 minutes.
|
||||
If this number remains high during extended periods of time, then it is likely more RAM is needed for optimal handling
|
||||
of the current number of active time series.
|
||||
|
||||
|
||||
### Troubleshooting
|
||||
@@ -922,8 +948,9 @@ The most interesting metrics are:
|
||||
|
||||
* If VictoriaMetrics works slowly and eats more than a CPU core per 100K ingested data points per second,
|
||||
then it is likely you have too many active time series for the current amount of RAM.
|
||||
VictoriaMetrics [exposes](#monitoring) `vm_slow_*` metrics, which could be used as an indicator of low amounts of RAM.
|
||||
It is recommended increasing the amount of RAM on the node with VictoriaMetrics in order to improve
|
||||
ingestion performance.
|
||||
ingestion and query performance in this case.
|
||||
Another option is to increase `-memory.allowedPercent` command-line flag value. Be careful with this
|
||||
option, since too big value for `-memory.allowedPercent` may result in high I/O usage.
|
||||
|
||||
@@ -970,6 +997,27 @@ the query cache, which could contain incomplete data cached during the backfilli
|
||||
Yet another solution is to increase `-search.cacheTimestampOffset` flag value in order to disable caching
|
||||
for data with timestamps close to the current time.
|
||||
|
||||
|
||||
### Replication
|
||||
|
||||
Single-node VictoriaMetrics relies on replicated durable persistent storage such as [Google Cloud disks](https://cloud.google.com/compute/docs/disks#pdspecs)
|
||||
or [Amazon EBS](https://aws.amazon.com/ebs/). It is also recommended making periodic backups,
|
||||
since [replication doesn't save from disaster](https://medium.com/@valyala/speeding-up-backups-for-big-time-series-databases-533c1a927883).
|
||||
See [backup docs](#backups) for details.
|
||||
|
||||
Cluster version of VictoriaMetrics supports replication. See [these docs](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/cluster/README.md#replication-and-data-safety)
|
||||
for details.
|
||||
|
||||
See also [high availability docs](#high-availability) and [docs about cluster version of VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/cluster/README.md).
|
||||
|
||||
|
||||
### Backups
|
||||
|
||||
VictoriaMetrics supports backups via [vmbackup](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmbackup/README.md)
|
||||
and [vmrestore](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmrestore/README.md) tools.
|
||||
We also provide provide `vmbackuper` tool for paid enterprise subscribers - see [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/466) for details.
|
||||
|
||||
|
||||
### Profiling
|
||||
|
||||
VictoriaMetrics provides handlers for collecting the following [Go profiles](https://blog.golang.org/profiling-go-programs):
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
## vmagent
|
||||
|
||||
`vmagent` is a tiny but brave agent, which helps you collecting metrics from various sources
|
||||
and storing them to [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics)
|
||||
or any other Prometheus-compatible storage system that supports `remote_write` protocol.
|
||||
`vmagent` is a tiny but brave agent, which helps you collect metrics from various sources
|
||||
and stores them in [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics)
|
||||
or any other Prometheus-compatible storage system that supports the `remote_write` protocol.
|
||||
|
||||
<img alt="vmagent" src="vmagent.png">
|
||||
|
||||
@@ -11,7 +11,7 @@ or any other Prometheus-compatible storage system that supports `remote_write` p
|
||||
|
||||
While VictoriaMetrics provides an efficient solution to store and observe metrics, our users needed something fast
|
||||
and RAM friendly to scrape metrics from Prometheus-compatible exporters to VictoriaMetrics.
|
||||
Also, we found that users’ infrastructure is like snowflakes - never alike, and we decided to add more flexibility
|
||||
Also, we found that users’ infrastructure are snowflakes - no two are alike, and we decided to add more flexibility
|
||||
to `vmagent` (like the ability to push metrics instead of pulling them). We did our best and plan to do even more.
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ to `vmagent` (like the ability to push metrics instead of pulling them). We did
|
||||
* Works in environments with unstable connections to remote storage. If the remote storage is unavailable, the collected metrics
|
||||
are buffered at `-remoteWrite.tmpDataPath`. The buffered metrics are sent to remote storage as soon as connection
|
||||
to remote storage is recovered. The maximum disk usage for the buffer can be limited with `-remoteWrite.maxDiskUsagePerURL`.
|
||||
* Uses lower amounts of RAM, CPU, disk IO and network bandwidth comparing to Prometheus.
|
||||
* Uses lower amounts of RAM, CPU, disk IO and network bandwidth compared to Prometheus.
|
||||
|
||||
|
||||
### Quick Start
|
||||
@@ -40,8 +40,7 @@ Just download `vmutils-*` archive from [releases page](https://github.com/Victor
|
||||
and pass the following flags to `vmagent` binary in order to start scraping Prometheus targets:
|
||||
|
||||
* `-promscrape.config` with the path to Prometheus config file (it is usually located at `/etc/prometheus/prometheus.yml`)
|
||||
* `-remoteWrite.url` with the remote storage endpoint such as VictoriaMetrics. Multiple `-remoteWrite.url` args can be set in parallel
|
||||
in order to replicate data concurrently to multiple remote storage systems.
|
||||
* `-remoteWrite.url` with the remote storage endpoint such as VictoriaMetrics. The `-remoteWrite.url` argument can be specified multiple times in order to replicate data concurrently to an arbitrary amount of remote storage systems.
|
||||
|
||||
Example command line:
|
||||
|
||||
@@ -49,7 +48,7 @@ Example command line:
|
||||
/path/to/vmagent -promscrape.config=/path/to/prometheus.yml -remoteWrite.url=https://victoria-metrics-host:8428/api/v1/write
|
||||
```
|
||||
|
||||
If you need collecting only Influx data, then the following command line would be enough:
|
||||
If you only need to collect Influx data, then the following is sufficient:
|
||||
|
||||
```
|
||||
/path/to/vmagent -remoteWrite.url=https://victoria-metrics-host:8428/api/v1/write
|
||||
@@ -79,14 +78,14 @@ See [the corresponding Makefile rules](https://github.com/VictoriaMetrics/Victor
|
||||
#### Drop-in replacement for Prometheus
|
||||
|
||||
If you use Prometheus only for scraping metrics from various targets and forwarding these metrics to remote storage,
|
||||
then `vmagent` can replace such Prometheus setup. Usually `vmagent` requires lower amounts of RAM, CPU and network bandwidth comparing to Prometheus for such setup.
|
||||
then `vmagent` can replace such Prometheus setup. Usually `vmagent` requires lower amounts of RAM, CPU and network bandwidth comparing to Prometheus for such a setup.
|
||||
See [these docs](#how-to-collect-metrics-in-prometheus-format) for details.
|
||||
|
||||
|
||||
#### Replication and high availability
|
||||
|
||||
`vmagent` replicates the collected metrics among multiple remote storage instances configured via `-remoteWrite.url` args.
|
||||
If a single remote storage instance temporarily goes out of service, then the collected data remains available in another remote storage instances.
|
||||
If a single remote storage instance temporarily is out of service, then the collected data remains available in another remote storage instances.
|
||||
`vmagent` buffers the collected data in files at `-remoteWrite.tmpDataPath` until the remote storage becomes available again.
|
||||
Then it sends the buffered data to the remote storage in order to prevent data gaps in the remote storage.
|
||||
|
||||
@@ -94,13 +93,13 @@ Then it sends the buffered data to the remote storage in order to prevent data g
|
||||
#### Relabeling and filtering
|
||||
|
||||
`vmagent` can add, remove or update labels on the collected data before sending it to remote storage. Additionally,
|
||||
it can remove unneeded samples via Prometheus-like relabeling before sending the collected data to remote storage.
|
||||
it can remove unwanted samples via Prometheus-like relabeling before sending the collected data to remote storage.
|
||||
See [these docs](#relabeling) for details.
|
||||
|
||||
|
||||
#### Splitting data streams among multiple systems
|
||||
|
||||
`vmagent` supports splitting of the collected data among muliple destinations with the help of `-remoteWrite.urlRelabelConfig`,
|
||||
`vmagent` supports splitting the collected data between muliple destinations with the help of `-remoteWrite.urlRelabelConfig`,
|
||||
which is applied independently for each configured `-remoteWrite.url` destination. For instance, it is possible to replicate or split
|
||||
data among long-term remote storage, short-term remote storage and real-time analytical system [built on top of Kafka](https://github.com/Telefonica/prometheus-kafka-adapter).
|
||||
Note that each destination can receive its own subset of the collected data thanks to per-destination relabeling via `-remoteWrite.urlRelabelConfig`.
|
||||
@@ -148,6 +147,10 @@ The following scrape types in [scrape_config](https://prometheus.io/docs/prometh
|
||||
* `dns_sd_configs` - for scraping targets discovered from DNS records (SRV, A and AAAA).
|
||||
See [dns_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#dns_sd_config) for details.
|
||||
|
||||
Note that `vmagent` doesn't support `refresh_interval` option these scrape configs. Use the corresponding `-promscrape.*CheckInterval`
|
||||
command-line flag instead. For example, `-promscrape.consulSDCheckInterval=60s` sets `refresh_interval` for all the `consul_sd_configs`
|
||||
entries to 60s. Run `vmagent -help` in order to see default values for `-promscrape.*CheckInterval` flags.
|
||||
|
||||
|
||||
File feature requests at [our issue tracker](https://github.com/VictoriaMetrics/VictoriaMetrics/issues) if you need other service discovery mechanisms to be supported by `vmagent`.
|
||||
|
||||
@@ -200,12 +203,12 @@ either via `vmagent` itself or via Prometheus, so the exported metrics could be
|
||||
* When `vmagent` scrapes many unreliable targets, it can flood error log with scrape errors. These errors can be suppressed
|
||||
by passing `-promscrape.suppressScrapeErrors` command-line flag to `vmagent`. The most recent scrape error per each target can be observed at `http://vmagent-host:8429/targets`.
|
||||
|
||||
* It is recommended increasing `-remoteWrite.queues` if `vmagent` collects more than 100K samples per second
|
||||
* It is recommended to increase `-remoteWrite.queues` if `vmagent` collects more than 100K samples per second
|
||||
and `vmagent_remotewrite_pending_data_bytes` metric exported at `http://vmagent-host:8429/metrics` page constantly grows.
|
||||
|
||||
* `vmagent` buffers scraped data at `-remoteWrite.tmpDataPath` directory until it is sent to `-remoteWrite.url`.
|
||||
The directory can grow big when remote storage is unavailable during extended periods of time and if `-remoteWrite.maxDiskUsagePerURL` isn't set.
|
||||
If you don't want sending all the data from the directory to remote storage, just stop `vmagent` and delete the directory.
|
||||
The directory can grow large when remote storage is unavailable for extended periods of time and if `-remoteWrite.maxDiskUsagePerURL` isn't set.
|
||||
If you don't want to send all the data from the directory to remote storage, simply stop `vmagent` and delete the directory.
|
||||
|
||||
|
||||
### How to build from sources
|
||||
|
||||
@@ -13,7 +13,7 @@ sends alerts to [Alert Manager](https://github.com/prometheus/alertmanager).
|
||||
* Lightweight without extra dependencies.
|
||||
|
||||
### TODO:
|
||||
* Configuration hot reload.
|
||||
* Support recording rules.
|
||||
|
||||
### QuickStart
|
||||
|
||||
@@ -48,10 +48,11 @@ Rules in group evaluated one-by-one sequentially.
|
||||
* `http://<vmalert-addr>/api/v1/<groupName>/<alertID>/status" ` - get alert status by ID.
|
||||
Used as alert source in AlertManager.
|
||||
* `http://<vmalert-addr>/metrics` - application metrics.
|
||||
* `http://<vmalert-addr>/-/reload` - hot configuration reload.
|
||||
|
||||
`vmalert` may be configured with `-remotewrite` flag to write alerts state in form of timeseries
|
||||
`vmalert` may be configured with `-remoteWrite` flag to write alerts state in form of timeseries
|
||||
via remote write protocol. Alerts state will be written as `ALERTS` timeseries. These timeseries
|
||||
may be used to recover alerts state on `vmalert` restarts if `-remoteread` is configured.
|
||||
may be used to recover alerts state on `vmalert` restarts if `-remoteRead` is configured.
|
||||
|
||||
|
||||
### Configuration
|
||||
@@ -81,19 +82,21 @@ Usage of vmalert:
|
||||
Address to listen for http connections (default ":8880")
|
||||
-notifier.url string
|
||||
Prometheus alertmanager URL. Required parameter. e.g. http://127.0.0.1:9093
|
||||
-remoteread.basicAuth.password string
|
||||
Optional basic auth password for -remoteread.url
|
||||
-remoteread.basicAuth.username string
|
||||
Optional basic auth username for -remoteread.url
|
||||
-remoteread.lookback duration
|
||||
-remoteRead.basicAuth.password string
|
||||
Optional basic auth password for -remoteRead.url
|
||||
-remoteRead.basicAuth.username string
|
||||
Optional basic auth username for -remoteRead.url
|
||||
-remoteRead.lookback duration
|
||||
Lookback defines how far to look into past for alerts timeseries. For example, if lookback=1h then range from now() to now()-1h will be scanned. (default 1h0m0s)
|
||||
-remoteread.url vmalert
|
||||
Optional URL to Victoria Metrics or VMSelect that will be used to restore alerts state. This configuration makes sense only if vmalert was configured with `remotewrite.url` before and has been successfully persisted its state. E.g. http://127.0.0.1:8428
|
||||
-remotewrite.basicAuth.password string
|
||||
Optional basic auth password for -remotewrite.url
|
||||
-remotewrite.basicAuth.username string
|
||||
Optional basic auth username for -remotewrite.url
|
||||
-remotewrite.url string
|
||||
-remoteRead.url vmalert
|
||||
Optional URL to Victoria Metrics or VMSelect that will be used to restore alerts state. This configuration makes sense only if vmalert was configured with `remoteWrite.url` before and has been successfully persisted its state. E.g. http://127.0.0.1:8428
|
||||
-remoteWrite.basicAuth.password string
|
||||
Optional basic auth password for -remoteWrite.url
|
||||
-remoteWrite.basicAuth.username string
|
||||
Optional basic auth username for -remoteWrite.url
|
||||
-remoteWrite.maxQueueSize
|
||||
Defines the max number of pending datapoints to remote write endpoint
|
||||
-remoteWrite.url string
|
||||
Optional URL to Victoria Metrics or VMInsert where to persist alerts state in form of timeseries. E.g. http://127.0.0.1:8428
|
||||
-rule value
|
||||
Path to the file with alert rules.
|
||||
@@ -109,8 +112,30 @@ Usage of vmalert:
|
||||
Pass `-help` to `vmalert` in order to see the full list of supported
|
||||
command-line flags with their descriptions.
|
||||
|
||||
To reload configuration without `vmalert` restart send SIGHUP signal
|
||||
or send GET request to `/-/reload` endpoint.
|
||||
|
||||
### Contributing
|
||||
|
||||
`vmalert` is mostly designed and built by VictoriaMetrics community.
|
||||
Feel free to share your experience and ideas for improving this
|
||||
software. Please keep simplicity as the main priority.
|
||||
|
||||
### How to build from sources
|
||||
|
||||
It is recommended using
|
||||
[binary releases](https://github.com/VictoriaMetrics/VictoriaMetrics/releases)
|
||||
- `vmalert` is located in `vmutils-*` archives there.
|
||||
|
||||
|
||||
#### Development build
|
||||
|
||||
1. [Install Go](https://golang.org/doc/install). The minimum supported version is Go 1.13.
|
||||
2. Run `make vmalert` from the root folder of the repository.
|
||||
It builds `vmalert` binary and puts it into the `bin` folder.
|
||||
|
||||
#### Production build
|
||||
|
||||
1. [Install docker](https://docs.docker.com/install/).
|
||||
2. Run `make vmalert-prod` from the root folder of the repository.
|
||||
It builds `vmalert-prod` binary and puts it into the `bin` folder.
|
||||
|
||||
@@ -19,6 +19,9 @@ Backed up data can be restored with [vmrestore](https://github.com/VictoriaMetri
|
||||
|
||||
See [this article](https://medium.com/@valyala/speeding-up-backups-for-big-time-series-databases-533c1a927883) for more details.
|
||||
|
||||
See also [vmbackuper](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/466) tool built on top of `vmbackup`. This tool simplifies
|
||||
creation of hourly, daily, weekly and monthly backups.
|
||||
|
||||
|
||||
### Use cases
|
||||
|
||||
|
||||
@@ -21,7 +21,8 @@ vmrestore -src=gcs://<bucket>/<path/to/backup> -storageDataPath=<local/path/to/r
|
||||
* `<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.
|
||||
The original `-storageDataPath` directory may contain old files. They will be susbstituted by the files from backup,
|
||||
i.e. the end result would be similar to [rsync --delete](https://askubuntu.com/questions/476041/how-do-i-make-rsync-delete-files-that-have-been-deleted-from-the-source-folder).
|
||||
|
||||
|
||||
### Troubleshooting
|
||||
@@ -52,7 +53,7 @@ Run `vmrestore -help` in order to see all the available options:
|
||||
-envflag.prefix string
|
||||
Prefix for environment variables if -envflag.enable is set
|
||||
-fs.disableMmap
|
||||
Whether to use pread() instead of mmap() for reading data files
|
||||
Whether to use pread() instead of mmap() for reading data files. By default mmap() is used for 64-bit arches and pread() is used for 32-bit arches, since they cannot data files bigger than 2^32 bytes in memory
|
||||
-loggerFormat string
|
||||
Format for logs. Possible values: default, json (default "default")
|
||||
-loggerLevel string
|
||||
@@ -68,8 +69,8 @@ Run `vmrestore -help` in order to see all the available options:
|
||||
-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
|
||||
Destination path where backup must be restored. VictoriaMetrics must be stopped when restoring from backup. -storageDataPath dir can be non-empty. In this case the contents of -storageDataPath dir is synchronized with -src contents, i.e. it works like 'rsync --delete' (default "victoria-metrics-data")
|
||||
-version
|
||||
Show VictoriaMetrics version
|
||||
```
|
||||
|
||||
|
||||
27
go.mod
27
go.mod
@@ -1,32 +1,33 @@
|
||||
module github.com/VictoriaMetrics/VictoriaMetrics
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.57.0 // indirect
|
||||
cloud.google.com/go/storage v1.7.0
|
||||
cloud.google.com/go/storage v1.8.0
|
||||
github.com/VictoriaMetrics/fastcache v1.5.7
|
||||
|
||||
// Do not use the original github.com/valyala/fasthttp because of issues
|
||||
// like https://github.com/valyala/fasthttp/commit/996610f021ff45fdc98c2ce7884d5fa4e7f9199b
|
||||
github.com/VictoriaMetrics/fasthttp v1.0.1
|
||||
github.com/VictoriaMetrics/metrics v1.11.2
|
||||
github.com/VictoriaMetrics/metricsql v0.2.1
|
||||
github.com/aws/aws-sdk-go v1.30.25
|
||||
github.com/VictoriaMetrics/metrics v1.11.3
|
||||
github.com/VictoriaMetrics/metricsql v0.2.3
|
||||
github.com/aws/aws-sdk-go v1.31.5
|
||||
github.com/cespare/xxhash/v2 v2.1.1
|
||||
github.com/golang/protobuf v1.4.1 // indirect
|
||||
github.com/golang/protobuf v1.4.2 // indirect
|
||||
github.com/golang/snappy v0.0.1
|
||||
github.com/klauspost/compress v1.10.5
|
||||
github.com/klauspost/compress v1.10.6
|
||||
github.com/valyala/fastjson v1.5.1
|
||||
github.com/valyala/fastrand v1.0.0
|
||||
github.com/valyala/gozstd v1.7.0
|
||||
github.com/valyala/histogram v1.0.1
|
||||
github.com/valyala/quicktemplate v1.5.0
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f // indirect
|
||||
golang.org/x/mod v0.3.0 // indirect
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25
|
||||
golang.org/x/tools v0.0.0-20200512001501-aaeff5de670a // indirect
|
||||
google.golang.org/api v0.24.0
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121
|
||||
golang.org/x/tools v0.0.0-20200527150044-688b3c5d9fa5 // indirect
|
||||
google.golang.org/api v0.25.0
|
||||
google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece // indirect
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
honnef.co/go/tools v0.0.1-2020.1.4 // indirect
|
||||
)
|
||||
|
||||
go 1.13
|
||||
|
||||
54
go.sum
54
go.sum
@@ -18,8 +18,8 @@ cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNF
|
||||
cloud.google.com/go/bigquery v1.4.0 h1:xE3CPsOgttP4ACBePh79zTKALtXwn/Edhcr16R5hMWU=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.6.0 h1:ajp/DjpiCHO71SyIhwb83YsUGAyWuzVvMko+9xCsJLw=
|
||||
cloud.google.com/go/bigquery v1.6.0/go.mod h1:hyFDG0qSGdHNz8Q6nDN8rYIkld0q/+5uBZaelxiDLfE=
|
||||
cloud.google.com/go/bigquery v1.7.0 h1:a/O/bK/vWrYGOTFtH8di4rBxMZnmkjy+Y5LxpDwo+dA=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
@@ -33,8 +33,8 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0 h1:UDpwYIwla4jHGzZJaEJYx1tOejbgSoNqsAfHAUYe2r8=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.7.0 h1:DzdLPI8Em+DEk7IzA2a10ivq3mxIEASC9GeNJ6FFt5Q=
|
||||
cloud.google.com/go/storage v1.7.0/go.mod h1:jGMIBwF+L/tL6WN/W5InNgYYu4HP0DvGB6rQ1mufWfs=
|
||||
cloud.google.com/go/storage v1.8.0 h1:86K1Gel7BQ9/WmNWn7dTKMvTLFzwtBe5FNqYbi9X35g=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
@@ -45,12 +45,14 @@ github.com/VictoriaMetrics/fasthttp v1.0.1 h1:I7YdbswTIW63WxoFoUOSNxeOEGB46rdKUL
|
||||
github.com/VictoriaMetrics/fasthttp v1.0.1/go.mod h1:BqgsieH90PR7x97c89j+eqZDloKkDhAEQTwhLw6jw/4=
|
||||
github.com/VictoriaMetrics/metrics v1.11.2 h1:t/ceLP6SvagUqypCKU7cI7+tQn54+TIV/tGoxihHvx8=
|
||||
github.com/VictoriaMetrics/metrics v1.11.2/go.mod h1:LU2j9qq7xqZYXz8tF3/RQnB2z2MbZms5TDiIg9/NHiQ=
|
||||
github.com/VictoriaMetrics/metricsql v0.2.1 h1:OI/W2QCFiQiFULVN3ZiC/iCqZFt25rXp/O7P2NiAwYU=
|
||||
github.com/VictoriaMetrics/metricsql v0.2.1/go.mod h1:UIjd9S0W1UnTWlJdM0wLS+2pfuPqjwqKoK8yTos+WyE=
|
||||
github.com/VictoriaMetrics/metrics v1.11.3 h1:eSfXc0CrquKa1VTNUvhP+dhNjLUZHQGTFfp19mYCQWE=
|
||||
github.com/VictoriaMetrics/metrics v1.11.3/go.mod h1:LU2j9qq7xqZYXz8tF3/RQnB2z2MbZms5TDiIg9/NHiQ=
|
||||
github.com/VictoriaMetrics/metricsql v0.2.3 h1:xGscDmLoeIV7+8qX/mdHnOY0vu4m+wHIVGMoy/nBovY=
|
||||
github.com/VictoriaMetrics/metricsql v0.2.3/go.mod h1:UIjd9S0W1UnTWlJdM0wLS+2pfuPqjwqKoK8yTos+WyE=
|
||||
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8=
|
||||
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
|
||||
github.com/aws/aws-sdk-go v1.30.25 h1:89NXJkfpjnMEnsxkP8MVX+LDsoiLCSqevraLb5y4Mjk=
|
||||
github.com/aws/aws-sdk-go v1.30.25/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||
github.com/aws/aws-sdk-go v1.31.5 h1:DFA7BzTydO4etqsTja+x7UfkOKQUv1xzEluLvNk81L0=
|
||||
github.com/aws/aws-sdk-go v1.31.5/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||
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=
|
||||
@@ -95,6 +97,8 @@ github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
@@ -128,6 +132,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
|
||||
github.com/klauspost/compress v1.10.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.10.5 h1:7q6vHIqubShURwQz8cQK6yIe/xC3IF0Vm7TGfqjewrc=
|
||||
github.com/klauspost/compress v1.10.5/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.10.6 h1:SP6zavvTG3YjOosWePXFDlExpKIWMTO4SE/Y8MZB2vI=
|
||||
github.com/klauspost/compress v1.10.6/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
@@ -199,6 +205,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -221,6 +229,8 @@ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5 h1:WQ8q63x+f/zpC8Ac1s9wLElVo
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f h1:QBjCr1Fz5kw158VqdE9JfI9cJnl/ymnJWAdMuinqL7Y=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2 h1:eDrdRpKgkcCqKZQwyZRyeFZgfqt37SL7Kv3tok06cKE=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
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/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -255,10 +265,11 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25 h1:OKbAoGs4fGM5cPLlVQLZGYkFC8OnOfgo6tt0Smf9XhM=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121 h1:rITEj+UZHYC927n8GT97eC3zrpzXdb/voyeOuVKS46o=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
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=
|
||||
@@ -300,10 +311,10 @@ golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapK
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200409170454-77362c5149f0/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512001501-aaeff5de670a h1:vAa2fXRLbiVN3N/xCnodIT36K4QKZQNyQFq3hQJfQ1U=
|
||||
golang.org/x/tools v0.0.0-20200512001501-aaeff5de670a/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200527150044-688b3c5d9fa5 h1:3KBjmg2slvQXATWW9cQJ6tsRc8hj1gsnwWyi1IzYk3o=
|
||||
golang.org/x/tools v0.0.0-20200527150044-688b3c5d9fa5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
@@ -319,10 +330,11 @@ google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.21.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0 h1:cG03eaksBzhfSIk7JRGctfp3lanklcOM/mTGvow7BbQ=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.25.0 h1:LodzhlzZEUfhXzNUMIfVlf9Gr6Ua5MMtoFWh7+f47qA=
|
||||
google.golang.org/api v0.25.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
@@ -351,11 +363,13 @@ google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfG
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200409111301-baae70f3302d/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84 h1:pSLkPbrjnPyLDYUO2VM9mDLqo2V6CFBY84lFSZAfoi4=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380 h1:xriR1EgvKfkKxIoU2uUvrMVl+H26359loFFUleSMXFo=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece h1:1YM0uhfumvoDu9sx8+RyWwTI63zoCQvI23IYFRlvte0=
|
||||
google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
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=
|
||||
@@ -365,7 +379,6 @@ google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.28.1/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
@@ -376,13 +389,18 @@ google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zim
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0 h1:cJv5/xdbk1NnMPR1VP9+HU6gupuG9MLBoH1r6RHZ2MY=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
@@ -390,6 +408,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3 h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3U=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
||||
@@ -113,10 +113,16 @@ func (r *Restore) Run() error {
|
||||
partsToDelete := common.PartsDifference(dstParts, srcParts)
|
||||
deleteSize := uint64(0)
|
||||
if len(partsToDelete) > 0 {
|
||||
// Fully remove local file if certain parts from the remote part are missing.
|
||||
// Remove only files with the missing part at offset 0.
|
||||
// Assume other files are partially downloaded during the previous Restore.Run call,
|
||||
// so only the last part in them may be incomplete.
|
||||
// The last part for partially downloaded files will be re-downloaded later.
|
||||
// This addresses https://github.com/VictoriaMetrics/VictoriaMetrics/issues/487 .
|
||||
pathsToDelete := make(map[string]bool)
|
||||
for _, p := range partsToDelete {
|
||||
pathsToDelete[p.Path] = true
|
||||
if p.Offset == 0 {
|
||||
pathsToDelete[p.Path] = true
|
||||
}
|
||||
}
|
||||
logger.Infof("deleting %d files from %s", len(pathsToDelete), dst)
|
||||
for path := range pathsToDelete {
|
||||
@@ -153,7 +159,8 @@ func (r *Restore) Run() error {
|
||||
logger.Infof("downloading %d parts from %s to %s", len(partsToCopy), src, dst)
|
||||
bytesDownloaded := uint64(0)
|
||||
err = runParallelPerPath(concurrency, perPath, func(parts []common.Part) error {
|
||||
// Sort partsToCopy in order to properly grow file size during downloading.
|
||||
// Sort partsToCopy in order to properly grow file size during downloading
|
||||
// and to properly resume downloading of incomplete files on the next Restore.Run call.
|
||||
common.SortParts(parts)
|
||||
for _, p := range parts {
|
||||
logger.Infof("downloading %s from %s to %s", &p, src, dst)
|
||||
@@ -169,7 +176,7 @@ func (r *Restore) Run() error {
|
||||
return fmt.Errorf("cannot download %s to %s: %s", &p, dst, err)
|
||||
}
|
||||
if err := wc.Close(); err != nil {
|
||||
return fmt.Errorf("cannot close reader fro %s from %s: %s", &p, src, err)
|
||||
return fmt.Errorf("cannot close reader from %s from %s: %s", &p, src, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -230,6 +230,6 @@ func NewRemoteFS(path string) (common.RemoteFS, error) {
|
||||
}
|
||||
return fs, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported scheme %q in `-dst`", scheme)
|
||||
return nil, fmt.Errorf("unsupported scheme %q", scheme)
|
||||
}
|
||||
}
|
||||
|
||||
40
lib/fasttime/fasttime.go
Normal file
40
lib/fasttime/fasttime.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package fasttime
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
go func() {
|
||||
ticker := time.NewTicker(time.Second)
|
||||
defer ticker.Stop()
|
||||
for tm := range ticker.C {
|
||||
t := uint64(tm.Unix())
|
||||
atomic.StoreUint64(¤tTimestamp, t)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
var currentTimestamp = uint64(time.Now().Unix())
|
||||
|
||||
// UnixTimestamp returns the current unix timestamp in seconds.
|
||||
//
|
||||
// It is faster than time.Now().Unix()
|
||||
func UnixTimestamp() uint64 {
|
||||
return atomic.LoadUint64(¤tTimestamp)
|
||||
}
|
||||
|
||||
// UnixDate returns date from the current unix timestamp.
|
||||
//
|
||||
// The date is calculated by dividing unix timestamp by (24*3600)
|
||||
func UnixDate() uint64 {
|
||||
return UnixTimestamp() / (24 * 3600)
|
||||
}
|
||||
|
||||
// UnixHour returns hour from the current unix timestamp.
|
||||
//
|
||||
// The hour is calculated by dividing unix timestamp by 3600
|
||||
func UnixHour() uint64 {
|
||||
return UnixTimestamp() / 3600
|
||||
}
|
||||
30
lib/fasttime/fasttime_test.go
Normal file
30
lib/fasttime/fasttime_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package fasttime
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestUnixTimestamp(t *testing.T) {
|
||||
tsExpected := uint64(time.Now().Unix())
|
||||
ts := UnixTimestamp()
|
||||
if ts-tsExpected > 1 {
|
||||
t.Fatalf("unexpected UnixTimestamp; got %d; want %d", ts, tsExpected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnixDate(t *testing.T) {
|
||||
dateExpected := uint64(time.Now().Unix() / (24 * 3600))
|
||||
date := UnixDate()
|
||||
if date-dateExpected > 1 {
|
||||
t.Fatalf("unexpected UnixDate; got %d; want %d", date, dateExpected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnixHour(t *testing.T) {
|
||||
hourExpected := uint64(time.Now().Unix() / 3600)
|
||||
hour := UnixHour()
|
||||
if hour-hourExpected > 1 {
|
||||
t.Fatalf("unexpected UnixHour; got %d; want %d", hour, hourExpected)
|
||||
}
|
||||
}
|
||||
32
lib/fasttime/fasttime_timing_test.go
Normal file
32
lib/fasttime/fasttime_timing_test.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package fasttime
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func BenchmarkUnixTimestamp(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var ts uint64
|
||||
for pb.Next() {
|
||||
ts += UnixTimestamp()
|
||||
}
|
||||
atomic.StoreUint64(&Sink, ts)
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkTimeNowUnix(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var ts uint64
|
||||
for pb.Next() {
|
||||
ts += uint64(time.Now().Unix())
|
||||
}
|
||||
atomic.StoreUint64(&Sink, ts)
|
||||
})
|
||||
}
|
||||
|
||||
// Sink should prevent from code elimination by optimizing compiler
|
||||
var Sink uint64
|
||||
@@ -283,10 +283,10 @@ func maybeGzipResponseWriter(w http.ResponseWriter, r *http.Request) http.Respon
|
||||
ae = strings.ToLower(ae)
|
||||
n := strings.Index(ae, "gzip")
|
||||
if n < 0 {
|
||||
// Do not apply gzip encoding to the response.
|
||||
return w
|
||||
}
|
||||
h := w.Header()
|
||||
h.Set("Content-Encoding", "gzip")
|
||||
// Apply gzip encoding to the response.
|
||||
zw := getGzipWriter(w)
|
||||
bw := getBufioWriter(zw)
|
||||
zrw := &gzipResponseWriter{
|
||||
@@ -301,7 +301,14 @@ func maybeGzipResponseWriter(w http.ResponseWriter, r *http.Request) http.Respon
|
||||
//
|
||||
// The function must be called before the first w.Write* call.
|
||||
func DisableResponseCompression(w http.ResponseWriter) {
|
||||
w.Header().Del("Content-Encoding")
|
||||
zrw, ok := w.(*gzipResponseWriter)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if zrw.firstWriteDone {
|
||||
logger.Panicf("BUG: DisableResponseCompression must be called before sending the response")
|
||||
}
|
||||
zrw.disableCompression = true
|
||||
}
|
||||
|
||||
// EnableCORS enables https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
||||
@@ -343,20 +350,24 @@ type gzipResponseWriter struct {
|
||||
func (zrw *gzipResponseWriter) Write(p []byte) (int, error) {
|
||||
if !zrw.firstWriteDone {
|
||||
h := zrw.Header()
|
||||
if h.Get("Content-Encoding") != "gzip" {
|
||||
// The request handler disabled gzip encoding.
|
||||
// Send uncompressed response body.
|
||||
if zrw.statusCode == http.StatusNoContent {
|
||||
zrw.disableCompression = true
|
||||
} else if h.Get("Content-Type") == "" {
|
||||
// Disable auto-detection of content-type, since it
|
||||
// is incorrectly detected after the compression.
|
||||
h.Set("Content-Type", "text/html")
|
||||
}
|
||||
if h.Get("Content-Encoding") != "" {
|
||||
zrw.disableCompression = true
|
||||
}
|
||||
if !zrw.disableCompression {
|
||||
h.Set("Content-Encoding", "gzip")
|
||||
h.Del("Content-Length")
|
||||
if h.Get("Content-Type") == "" {
|
||||
// Disable auto-detection of content-type, since it
|
||||
// is incorrectly detected after the compression.
|
||||
h.Set("Content-Type", "text/html")
|
||||
}
|
||||
}
|
||||
zrw.writeHeader()
|
||||
zrw.firstWriteDone = true
|
||||
}
|
||||
if zrw.statusCode == 0 {
|
||||
zrw.WriteHeader(http.StatusOK)
|
||||
}
|
||||
if zrw.disableCompression {
|
||||
return zrw.ResponseWriter.Write(p)
|
||||
}
|
||||
@@ -364,16 +375,16 @@ func (zrw *gzipResponseWriter) Write(p []byte) (int, error) {
|
||||
}
|
||||
|
||||
func (zrw *gzipResponseWriter) WriteHeader(statusCode int) {
|
||||
if zrw.statusCode != 0 {
|
||||
return
|
||||
}
|
||||
if statusCode == http.StatusNoContent {
|
||||
DisableResponseCompression(zrw.ResponseWriter)
|
||||
}
|
||||
zrw.ResponseWriter.WriteHeader(statusCode)
|
||||
zrw.statusCode = statusCode
|
||||
}
|
||||
|
||||
func (zrw *gzipResponseWriter) writeHeader() {
|
||||
if zrw.statusCode == 0 {
|
||||
zrw.statusCode = http.StatusOK
|
||||
}
|
||||
zrw.ResponseWriter.WriteHeader(zrw.statusCode)
|
||||
}
|
||||
|
||||
// Implements http.Flusher
|
||||
func (zrw *gzipResponseWriter) Flush() {
|
||||
if err := zrw.bw.Flush(); err != nil && !isTrivialNetworkError(err) {
|
||||
@@ -389,7 +400,7 @@ func (zrw *gzipResponseWriter) Flush() {
|
||||
|
||||
func (zrw *gzipResponseWriter) Close() error {
|
||||
if !zrw.firstWriteDone {
|
||||
zrw.Header().Del("Content-Encoding")
|
||||
zrw.writeHeader()
|
||||
return nil
|
||||
}
|
||||
zrw.Flush()
|
||||
|
||||
@@ -148,18 +148,18 @@ func unmarshalBlockHeaders(dst []blockHeader, src []byte, blockHeadersCount int)
|
||||
for i := 0; i < blockHeadersCount; i++ {
|
||||
tail, err := dst[dstLen+i].Unmarshal(src)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot unmarshal block header: %s", err)
|
||||
return dst, fmt.Errorf("cannot unmarshal block header: %s", err)
|
||||
}
|
||||
src = tail
|
||||
}
|
||||
if len(src) > 0 {
|
||||
return nil, fmt.Errorf("unexpected non-zero tail left after unmarshaling %d block headers; len(tail)=%d", blockHeadersCount, len(src))
|
||||
return dst, fmt.Errorf("unexpected non-zero tail left after unmarshaling %d block headers; len(tail)=%d", blockHeadersCount, len(src))
|
||||
}
|
||||
newBHS := dst[dstLen:]
|
||||
|
||||
// Verify that block headers are sorted by firstItem.
|
||||
if !sort.SliceIsSorted(newBHS, func(i, j int) bool { return string(newBHS[i].firstItem) < string(newBHS[j].firstItem) }) {
|
||||
return nil, fmt.Errorf("block headers must be sorted by firstItem; unmarshaled unsorted block headers: %#v", newBHS)
|
||||
return dst, fmt.Errorf("block headers must be sorted by firstItem; unmarshaled unsorted block headers: %#v", newBHS)
|
||||
}
|
||||
|
||||
return dst, nil
|
||||
|
||||
@@ -63,9 +63,12 @@ func (bsw *blockStreamWriter) reset() {
|
||||
bsw.mrFirstItemCaught = false
|
||||
}
|
||||
|
||||
func (bsw *blockStreamWriter) InitFromInmemoryPart(ip *inmemoryPart, compressLevel int) {
|
||||
func (bsw *blockStreamWriter) InitFromInmemoryPart(ip *inmemoryPart) {
|
||||
bsw.reset()
|
||||
bsw.compressLevel = compressLevel
|
||||
|
||||
// Use the minimum compression level for in-memory blocks,
|
||||
// since they are going to be re-compressed during the merge into file-based blocks.
|
||||
bsw.compressLevel = -5 // See https://github.com/facebook/zstd/releases/tag/v1.3.4
|
||||
|
||||
bsw.metaindexWriter = &ip.metaindexData
|
||||
bsw.indexWriter = &ip.indexData
|
||||
|
||||
@@ -151,7 +151,7 @@ var isInTest = func() bool {
|
||||
return strings.HasSuffix(os.Args[0], ".test")
|
||||
}()
|
||||
|
||||
// MarshalUnsortedData marshals sorted items from ib to sb.
|
||||
// MarshalSortedData marshals sorted items from ib to sb.
|
||||
//
|
||||
// It also:
|
||||
// - appends first item to firstItemDst and returns the result.
|
||||
|
||||
@@ -29,14 +29,14 @@ func TestMultilevelMerge(t *testing.T) {
|
||||
// First level merge
|
||||
var dstIP1 inmemoryPart
|
||||
var bsw1 blockStreamWriter
|
||||
bsw1.InitFromInmemoryPart(&dstIP1, 0)
|
||||
bsw1.InitFromInmemoryPart(&dstIP1)
|
||||
if err := mergeBlockStreams(&dstIP1.ph, &bsw1, bsrs[:5], nil, nil, &itemsMerged); err != nil {
|
||||
t.Fatalf("cannot merge first level part 1: %s", err)
|
||||
}
|
||||
|
||||
var dstIP2 inmemoryPart
|
||||
var bsw2 blockStreamWriter
|
||||
bsw2.InitFromInmemoryPart(&dstIP2, 0)
|
||||
bsw2.InitFromInmemoryPart(&dstIP2)
|
||||
if err := mergeBlockStreams(&dstIP2.ph, &bsw2, bsrs[5:], nil, nil, &itemsMerged); err != nil {
|
||||
t.Fatalf("cannot merge first level part 2: %s", err)
|
||||
}
|
||||
@@ -53,7 +53,7 @@ func TestMultilevelMerge(t *testing.T) {
|
||||
newTestBlockStreamReader(&dstIP1),
|
||||
newTestBlockStreamReader(&dstIP2),
|
||||
}
|
||||
bsw.InitFromInmemoryPart(&dstIP, 0)
|
||||
bsw.InitFromInmemoryPart(&dstIP)
|
||||
if err := mergeBlockStreams(&dstIP.ph, &bsw, bsrsTop, nil, nil, &itemsMerged); err != nil {
|
||||
t.Fatalf("cannot merge second level: %s", err)
|
||||
}
|
||||
@@ -72,7 +72,7 @@ func TestMergeForciblyStop(t *testing.T) {
|
||||
bsrs, _ := newTestInmemoryBlockStreamReaders(20, 4000)
|
||||
var dstIP inmemoryPart
|
||||
var bsw blockStreamWriter
|
||||
bsw.InitFromInmemoryPart(&dstIP, 0)
|
||||
bsw.InitFromInmemoryPart(&dstIP)
|
||||
ch := make(chan struct{})
|
||||
var itemsMerged uint64
|
||||
close(ch)
|
||||
@@ -119,7 +119,7 @@ func testMergeBlockStreamsSerial(blocksToMerge, maxItemsPerBlock int) error {
|
||||
var itemsMerged uint64
|
||||
var dstIP inmemoryPart
|
||||
var bsw blockStreamWriter
|
||||
bsw.InitFromInmemoryPart(&dstIP, 0)
|
||||
bsw.InitFromInmemoryPart(&dstIP)
|
||||
if err := mergeBlockStreams(&dstIP.ph, &bsw, bsrs, nil, nil, &itemsMerged); err != nil {
|
||||
return fmt.Errorf("cannot merge block streams: %s", err)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/filestream"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/memory"
|
||||
@@ -227,7 +228,7 @@ func (idxbc *indexBlockCache) cleaner() {
|
||||
}
|
||||
|
||||
func (idxbc *indexBlockCache) cleanByTimeout() {
|
||||
currentTime := atomic.LoadUint64(¤tTimestamp)
|
||||
currentTime := fasttime.UnixTimestamp()
|
||||
idxbc.mu.Lock()
|
||||
for k, idxbe := range idxbc.m {
|
||||
// Delete items accessed more than 10 minutes ago.
|
||||
@@ -245,7 +246,7 @@ func (idxbc *indexBlockCache) Get(k uint64) *indexBlock {
|
||||
idxbc.mu.RUnlock()
|
||||
|
||||
if idxbe != nil {
|
||||
currentTime := atomic.LoadUint64(¤tTimestamp)
|
||||
currentTime := fasttime.UnixTimestamp()
|
||||
if atomic.LoadUint64(&idxbe.lastAccessTime) != currentTime {
|
||||
atomic.StoreUint64(&idxbe.lastAccessTime, currentTime)
|
||||
}
|
||||
@@ -256,9 +257,7 @@ func (idxbc *indexBlockCache) Get(k uint64) *indexBlock {
|
||||
}
|
||||
|
||||
// Put puts idxb under the key k into idxbc.
|
||||
//
|
||||
// Returns true if the idxb has been put into idxbc.
|
||||
func (idxbc *indexBlockCache) Put(k uint64, idxb *indexBlock) bool {
|
||||
func (idxbc *indexBlockCache) Put(k uint64, idxb *indexBlock) {
|
||||
idxbc.mu.Lock()
|
||||
|
||||
// Remove superflouos entries.
|
||||
@@ -278,12 +277,11 @@ func (idxbc *indexBlockCache) Put(k uint64, idxb *indexBlock) bool {
|
||||
|
||||
// Store idxb in the cache.
|
||||
idxbe := &indexBlockCacheEntry{
|
||||
lastAccessTime: atomic.LoadUint64(¤tTimestamp),
|
||||
lastAccessTime: fasttime.UnixTimestamp(),
|
||||
idxb: idxb,
|
||||
}
|
||||
idxbc.m[k] = idxbe
|
||||
idxbc.mu.Unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
func (idxbc *indexBlockCache) Len() uint64 {
|
||||
@@ -377,7 +375,7 @@ func (ibc *inmemoryBlockCache) cleaner() {
|
||||
}
|
||||
|
||||
func (ibc *inmemoryBlockCache) cleanByTimeout() {
|
||||
currentTime := atomic.LoadUint64(¤tTimestamp)
|
||||
currentTime := fasttime.UnixTimestamp()
|
||||
ibc.mu.Lock()
|
||||
for k, ibe := range ibc.m {
|
||||
// Delete items accessed more than 10 minutes ago.
|
||||
@@ -396,7 +394,7 @@ func (ibc *inmemoryBlockCache) Get(k inmemoryBlockCacheKey) *inmemoryBlock {
|
||||
ibc.mu.RUnlock()
|
||||
|
||||
if ibe != nil {
|
||||
currentTime := atomic.LoadUint64(¤tTimestamp)
|
||||
currentTime := fasttime.UnixTimestamp()
|
||||
if atomic.LoadUint64(&ibe.lastAccessTime) != currentTime {
|
||||
atomic.StoreUint64(&ibe.lastAccessTime, currentTime)
|
||||
}
|
||||
@@ -407,9 +405,7 @@ func (ibc *inmemoryBlockCache) Get(k inmemoryBlockCacheKey) *inmemoryBlock {
|
||||
}
|
||||
|
||||
// Put puts ib under key k into ibc.
|
||||
//
|
||||
// Returns true if ib was put into ibc.
|
||||
func (ibc *inmemoryBlockCache) Put(k inmemoryBlockCacheKey, ib *inmemoryBlock) bool {
|
||||
func (ibc *inmemoryBlockCache) Put(k inmemoryBlockCacheKey, ib *inmemoryBlock) {
|
||||
ibc.mu.Lock()
|
||||
|
||||
// Clean superflouos entries in cache.
|
||||
@@ -429,12 +425,11 @@ func (ibc *inmemoryBlockCache) Put(k inmemoryBlockCacheKey, ib *inmemoryBlock) b
|
||||
|
||||
// Store ib in the cache.
|
||||
ibe := &inmemoryBlockCacheEntry{
|
||||
lastAccessTime: atomic.LoadUint64(¤tTimestamp),
|
||||
lastAccessTime: fasttime.UnixTimestamp(),
|
||||
ib: ib,
|
||||
}
|
||||
ibc.m[k] = ibe
|
||||
ibc.mu.Unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
func (ibc *inmemoryBlockCache) Len() uint64 {
|
||||
@@ -451,16 +446,3 @@ func (ibc *inmemoryBlockCache) Requests() uint64 {
|
||||
func (ibc *inmemoryBlockCache) Misses() uint64 {
|
||||
return atomic.LoadUint64(&ibc.misses)
|
||||
}
|
||||
|
||||
func init() {
|
||||
go func() {
|
||||
ticker := time.NewTicker(time.Second)
|
||||
defer ticker.Stop()
|
||||
for tm := range ticker.C {
|
||||
t := uint64(tm.Unix())
|
||||
atomic.StoreUint64(¤tTimestamp, t)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
var currentTimestamp uint64
|
||||
|
||||
@@ -25,9 +25,6 @@ type partSearch struct {
|
||||
// The remaining block headers to scan in the current metaindexRow.
|
||||
bhs []blockHeader
|
||||
|
||||
// Pointer to index block, which may be reused.
|
||||
indexBlockReuse *indexBlock
|
||||
|
||||
// Pointer to inmemory block, which may be reused.
|
||||
inmemoryBlockReuse *inmemoryBlock
|
||||
|
||||
@@ -53,10 +50,6 @@ func (ps *partSearch) reset() {
|
||||
ps.p = nil
|
||||
ps.mrs = nil
|
||||
ps.bhs = nil
|
||||
if ps.indexBlockReuse != nil {
|
||||
putIndexBlock(ps.indexBlockReuse)
|
||||
ps.indexBlockReuse = nil
|
||||
}
|
||||
if ps.inmemoryBlockReuse != nil {
|
||||
putInmemoryBlock(ps.inmemoryBlockReuse)
|
||||
ps.inmemoryBlockReuse = nil
|
||||
@@ -275,40 +268,25 @@ func (ps *partSearch) nextBlock() error {
|
||||
}
|
||||
|
||||
func (ps *partSearch) nextBHS() error {
|
||||
if ps.indexBlockReuse != nil {
|
||||
putIndexBlock(ps.indexBlockReuse)
|
||||
ps.indexBlockReuse = nil
|
||||
}
|
||||
if len(ps.mrs) == 0 {
|
||||
return io.EOF
|
||||
}
|
||||
mr := &ps.mrs[0]
|
||||
ps.mrs = ps.mrs[1:]
|
||||
idxb, mayReuseIndexBlock, err := ps.getIndexBlock(mr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot get index block: %s", err)
|
||||
}
|
||||
if mayReuseIndexBlock {
|
||||
ps.indexBlockReuse = idxb
|
||||
idxbKey := mr.indexBlockOffset
|
||||
idxb := ps.idxbCache.Get(idxbKey)
|
||||
if idxb == nil {
|
||||
var err error
|
||||
idxb, err = ps.readIndexBlock(mr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot read index block: %s", err)
|
||||
}
|
||||
ps.idxbCache.Put(idxbKey, idxb)
|
||||
}
|
||||
ps.bhs = idxb.bhs
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ps *partSearch) getIndexBlock(mr *metaindexRow) (*indexBlock, bool, error) {
|
||||
idxbKey := mr.indexBlockOffset
|
||||
idxb := ps.idxbCache.Get(idxbKey)
|
||||
if idxb != nil {
|
||||
return idxb, false, nil
|
||||
}
|
||||
idxb, err := ps.readIndexBlock(mr)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
ok := ps.idxbCache.Put(idxbKey, idxb)
|
||||
return idxb, !ok, nil
|
||||
}
|
||||
|
||||
func (ps *partSearch) readIndexBlock(mr *metaindexRow) (*indexBlock, error) {
|
||||
ps.compressedIndexBuf = bytesutil.Resize(ps.compressedIndexBuf, int(mr.indexBlockSize))
|
||||
ps.p.indexFile.MustReadAt(ps.compressedIndexBuf, int64(mr.indexBlockOffset))
|
||||
@@ -347,8 +325,8 @@ func (ps *partSearch) getInmemoryBlock(bh *blockHeader) (*inmemoryBlock, bool, e
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
ok := ps.ibCache.Put(ibKey, ib)
|
||||
return ib, !ok, nil
|
||||
ps.ibCache.Put(ibKey, ib)
|
||||
return ib, false, nil
|
||||
}
|
||||
|
||||
func (ps *partSearch) readInmemoryBlock(bh *blockHeader) (*inmemoryBlock, error) {
|
||||
|
||||
@@ -149,7 +149,7 @@ func newTestPart(blocksCount, maxItemsPerBlock int) (*part, []string, error) {
|
||||
var itemsMerged uint64
|
||||
var ip inmemoryPart
|
||||
var bsw blockStreamWriter
|
||||
bsw.InitFromInmemoryPart(&ip, 0)
|
||||
bsw.InitFromInmemoryPart(&ip)
|
||||
if err := mergeBlockStreams(&ip.ph, &bsw, bsrs, nil, nil, &itemsMerged); err != nil {
|
||||
return nil, nil, fmt.Errorf("cannot merge blocks: %s", err)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/memory"
|
||||
@@ -102,7 +103,7 @@ type Table struct {
|
||||
|
||||
rawItemsBlocks []*inmemoryBlock
|
||||
rawItemsLock sync.Mutex
|
||||
rawItemsLastFlushTime time.Time
|
||||
rawItemsLastFlushTime uint64
|
||||
|
||||
snapshotLock sync.RWMutex
|
||||
|
||||
@@ -369,7 +370,7 @@ func (tb *Table) AddItems(items [][]byte) error {
|
||||
if len(tb.rawItemsBlocks) >= 1024 {
|
||||
blocksToMerge = tb.rawItemsBlocks
|
||||
tb.rawItemsBlocks = nil
|
||||
tb.rawItemsLastFlushTime = time.Now()
|
||||
tb.rawItemsLastFlushTime = fasttime.UnixTimestamp()
|
||||
}
|
||||
tb.rawItemsLock.Unlock()
|
||||
|
||||
@@ -508,11 +509,15 @@ func (tb *Table) flushRawItems(isFinal bool) {
|
||||
defer tb.rawItemsPendingFlushesWG.Done()
|
||||
|
||||
mustFlush := false
|
||||
currentTime := time.Now()
|
||||
currentTime := fasttime.UnixTimestamp()
|
||||
flushSeconds := int64(rawItemsFlushInterval.Seconds())
|
||||
if flushSeconds <= 0 {
|
||||
flushSeconds = 1
|
||||
}
|
||||
var blocksToMerge []*inmemoryBlock
|
||||
|
||||
tb.rawItemsLock.Lock()
|
||||
if isFinal || currentTime.Sub(tb.rawItemsLastFlushTime) > rawItemsFlushInterval {
|
||||
if isFinal || currentTime-tb.rawItemsLastFlushTime > uint64(flushSeconds) {
|
||||
mustFlush = true
|
||||
blocksToMerge = tb.rawItemsBlocks
|
||||
tb.rawItemsBlocks = nil
|
||||
@@ -619,9 +624,8 @@ func (tb *Table) mergeInmemoryBlocks(blocksToMerge []*inmemoryBlock) *partWrappe
|
||||
|
||||
// Prepare blockStreamWriter for destination part.
|
||||
bsw := getBlockStreamWriter()
|
||||
compressLevel := 1
|
||||
mpDst := getInmemoryPart()
|
||||
bsw.InitFromInmemoryPart(mpDst, compressLevel)
|
||||
bsw.InitFromInmemoryPart(mpDst)
|
||||
|
||||
// Merge parts.
|
||||
// The merge shouldn't be interrupted by stopCh,
|
||||
@@ -674,7 +678,7 @@ const (
|
||||
|
||||
func (tb *Table) partMerger() error {
|
||||
sleepTime := minMergeSleepTime
|
||||
var lastMergeTime time.Time
|
||||
var lastMergeTime uint64
|
||||
isFinal := false
|
||||
t := time.NewTimer(sleepTime)
|
||||
for {
|
||||
@@ -682,7 +686,7 @@ func (tb *Table) partMerger() error {
|
||||
if err == nil {
|
||||
// Try merging additional parts.
|
||||
sleepTime = minMergeSleepTime
|
||||
lastMergeTime = time.Now()
|
||||
lastMergeTime = fasttime.UnixTimestamp()
|
||||
isFinal = false
|
||||
continue
|
||||
}
|
||||
@@ -693,10 +697,10 @@ func (tb *Table) partMerger() error {
|
||||
if err != errNothingToMerge {
|
||||
return err
|
||||
}
|
||||
if time.Since(lastMergeTime) > 30*time.Second {
|
||||
if fasttime.UnixTimestamp()-lastMergeTime > 30 {
|
||||
// We have free time for merging into bigger parts.
|
||||
// This should improve select performance.
|
||||
lastMergeTime = time.Now()
|
||||
lastMergeTime = fasttime.UnixTimestamp()
|
||||
isFinal = true
|
||||
continue
|
||||
}
|
||||
@@ -764,8 +768,10 @@ func (tb *Table) mergeParts(pws []*partWrapper, stopCh <-chan struct{}, isOuterP
|
||||
}
|
||||
|
||||
outItemsCount := uint64(0)
|
||||
outBlocksCount := uint64(0)
|
||||
for _, pw := range pws {
|
||||
outItemsCount += pw.p.ph.itemsCount
|
||||
outBlocksCount += pw.p.ph.blocksCount
|
||||
}
|
||||
nocache := true
|
||||
if outItemsCount < maxItemsPerCachedPart() {
|
||||
@@ -778,7 +784,7 @@ func (tb *Table) mergeParts(pws []*partWrapper, stopCh <-chan struct{}, isOuterP
|
||||
mergeIdx := tb.nextMergeIdx()
|
||||
tmpPartPath := fmt.Sprintf("%s/tmp/%016X", tb.path, mergeIdx)
|
||||
bsw := getBlockStreamWriter()
|
||||
compressLevel := getCompressLevelForPartItems(outItemsCount)
|
||||
compressLevel := getCompressLevelForPartItems(outItemsCount, outBlocksCount)
|
||||
if err := bsw.InitFromFilePart(tmpPartPath, nocache, compressLevel); err != nil {
|
||||
return fmt.Errorf("cannot create destination part %q: %s", tmpPartPath, err)
|
||||
}
|
||||
@@ -863,27 +869,43 @@ func (tb *Table) mergeParts(pws []*partWrapper, stopCh <-chan struct{}, isOuterP
|
||||
|
||||
d := time.Since(startTime)
|
||||
if d > 10*time.Second {
|
||||
logger.Infof("merged %d items in %.3f seconds at %d items/sec to %q; sizeBytes: %d",
|
||||
outItemsCount, d.Seconds(), int(float64(outItemsCount)/d.Seconds()), dstPartPath, newPSize)
|
||||
logger.Infof("merged %d items across %d blocks in %.3f seconds at %d items/sec to %q; sizeBytes: %d",
|
||||
outItemsCount, outBlocksCount, d.Seconds(), int(float64(outItemsCount)/d.Seconds()), dstPartPath, newPSize)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getCompressLevelForPartItems(itemsCount uint64) int {
|
||||
if itemsCount < 1<<19 {
|
||||
func getCompressLevelForPartItems(itemsCount, blocksCount uint64) int {
|
||||
// There is no need in using blocksCount here, since mergeset blocks are usually full.
|
||||
|
||||
if itemsCount <= 1<<16 {
|
||||
// -5 is the minimum supported compression for zstd.
|
||||
// See https://github.com/facebook/zstd/releases/tag/v1.3.4
|
||||
return -5
|
||||
}
|
||||
if itemsCount <= 1<<17 {
|
||||
return -4
|
||||
}
|
||||
if itemsCount <= 1<<18 {
|
||||
return -3
|
||||
}
|
||||
if itemsCount <= 1<<19 {
|
||||
return -2
|
||||
}
|
||||
if itemsCount <= 1<<20 {
|
||||
return -1
|
||||
}
|
||||
if itemsCount <= 1<<22 {
|
||||
return 1
|
||||
}
|
||||
if itemsCount < 1<<22 {
|
||||
if itemsCount <= 1<<25 {
|
||||
return 2
|
||||
}
|
||||
if itemsCount < 1<<25 {
|
||||
if itemsCount <= 1<<28 {
|
||||
return 3
|
||||
}
|
||||
if itemsCount < 1<<28 {
|
||||
return 4
|
||||
}
|
||||
return 5
|
||||
return 4
|
||||
}
|
||||
|
||||
func (tb *Table) nextMergeIdx() uint64 {
|
||||
@@ -892,15 +914,15 @@ func (tb *Table) nextMergeIdx() uint64 {
|
||||
|
||||
var (
|
||||
maxOutPartItemsLock sync.Mutex
|
||||
maxOutPartItemsDeadline time.Time
|
||||
maxOutPartItemsDeadline uint64
|
||||
lastMaxOutPartItems uint64
|
||||
)
|
||||
|
||||
func (tb *Table) maxOutPartItems() uint64 {
|
||||
maxOutPartItemsLock.Lock()
|
||||
if time.Until(maxOutPartItemsDeadline) < 0 {
|
||||
if maxOutPartItemsDeadline < fasttime.UnixTimestamp() {
|
||||
lastMaxOutPartItems = tb.maxOutPartItemsSlow()
|
||||
maxOutPartItemsDeadline = time.Now().Add(time.Second)
|
||||
maxOutPartItemsDeadline = fasttime.UnixTimestamp() + 2
|
||||
}
|
||||
n := lastMaxOutPartItems
|
||||
maxOutPartItemsLock.Unlock()
|
||||
|
||||
@@ -75,6 +75,8 @@ func (fq *FastQueue) flushInmemoryBlocksToFileLocked() {
|
||||
fq.pendingInmemoryBytes -= uint64(len(bb.B))
|
||||
blockBufPool.Put(bb)
|
||||
}
|
||||
// Unblock all the potentially blocked readers, so they could proceed with reading file-based queue.
|
||||
fq.cond.Broadcast()
|
||||
}
|
||||
|
||||
// GetPendingBytes returns the number of pending bytes in the fq.
|
||||
@@ -120,10 +122,10 @@ func (fq *FastQueue) MustWriteBlock(block []byte) {
|
||||
bb.B = append(bb.B[:0], block...)
|
||||
fq.ch <- bb
|
||||
fq.pendingInmemoryBytes += uint64(len(block))
|
||||
if len(fq.ch) == 1 {
|
||||
// Notify potentially blocked reader
|
||||
fq.cond.Signal()
|
||||
}
|
||||
|
||||
// Notify potentially blocked reader.
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/pull/484 for the context.
|
||||
fq.cond.Signal()
|
||||
}
|
||||
|
||||
// MustReadBlock reads the next block from fq to dst and returns it.
|
||||
@@ -137,7 +139,7 @@ func (fq *FastQueue) MustReadBlock(dst []byte) ([]byte, bool) {
|
||||
}
|
||||
if len(fq.ch) > 0 {
|
||||
if n := fq.pq.GetPendingBytes(); n > 0 {
|
||||
logger.Panicf("BUG: the file-based queue must be empty when the inmemory queue is empty; it contains %d pending bytes", n)
|
||||
logger.Panicf("BUG: the file-based queue must be empty when the inmemory queue is non-empty; it contains %d pending bytes", n)
|
||||
}
|
||||
bb := <-fq.ch
|
||||
fq.pendingInmemoryBytes -= uint64(len(bb.B))
|
||||
|
||||
@@ -383,13 +383,13 @@ func (q *Queue) MustWriteBlock(block []byte) {
|
||||
return
|
||||
}
|
||||
}
|
||||
mustNotifyReader := q.readerOffset == q.writerOffset
|
||||
if err := q.writeBlockLocked(block); err != nil {
|
||||
logger.Panicf("FATAL: %s", err)
|
||||
}
|
||||
if mustNotifyReader {
|
||||
q.cond.Signal()
|
||||
}
|
||||
|
||||
// Notify blocked reader if any.
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/pull/484 for details.
|
||||
q.cond.Signal()
|
||||
}
|
||||
|
||||
var blockBufPool bytesutil.ByteBufferPool
|
||||
|
||||
@@ -96,6 +96,10 @@ func (c *client) ReadData(dst []byte) ([]byte, error) {
|
||||
scrapesTimedout.Inc()
|
||||
return dst, fmt.Errorf("error when scraping %q with timeout %s: %s", c.scrapeURL, c.hc.ReadTimeout, err)
|
||||
}
|
||||
if err == fasthttp.ErrBodyTooLarge {
|
||||
return dst, fmt.Errorf("the response from %q exceeds -promscrape.maxScrapeSize=%d; "+
|
||||
"either reduce the response size for the target or increase -promscrape.maxScrapeSize", c.scrapeURL, *maxScrapeSize)
|
||||
}
|
||||
return dst, fmt.Errorf("error when scraping %q: %s", c.scrapeURL, err)
|
||||
}
|
||||
dstLen := len(dst)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
@@ -25,6 +26,9 @@ import (
|
||||
var (
|
||||
strictParse = flag.Bool("promscrape.config.strictParse", false, "Whether to allow only supported fields in '-promscrape.config'. "+
|
||||
"This option may be used for errors detection in '-promscrape.config' file")
|
||||
dryRun = flag.Bool("promscrape.config.dryRun", false, "Checks -promscrape.config file for errors and unsupported fields and then exits. "+
|
||||
"Returns non-zero exit code on parsing errors and emits these errors to stderr. "+
|
||||
"Pass -loggerLevel=ERROR if you don't need to see info messages in the output")
|
||||
)
|
||||
|
||||
// Config represents essential parts from Prometheus config defined at https://prometheus.io/docs/prometheus/latest/configuration/configuration/
|
||||
@@ -114,6 +118,13 @@ func loadConfig(path string) (cfg *Config, data []byte, err error) {
|
||||
if err := cfgObj.parse(data, path); err != nil {
|
||||
return nil, nil, fmt.Errorf("cannot parse Prometheus config from %q: %s", path, err)
|
||||
}
|
||||
if *dryRun {
|
||||
// This is a dirty hack for checking Prometheus config only.
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/362
|
||||
// and https://github.com/VictoriaMetrics/VictoriaMetrics/issues/508 for details.
|
||||
logger.Infof("Success: the config at %q has no errors; exitting with 0 status code", path)
|
||||
os.Exit(0)
|
||||
}
|
||||
return &cfgObj, data, nil
|
||||
}
|
||||
|
||||
@@ -139,7 +150,7 @@ func (cfg *Config) parse(data []byte, path string) error {
|
||||
|
||||
func unmarshalMaybeStrict(data []byte, dst interface{}) error {
|
||||
var err error
|
||||
if *strictParse {
|
||||
if *strictParse || *dryRun {
|
||||
err = yaml.UnmarshalStrict(data, dst)
|
||||
} else {
|
||||
err = yaml.Unmarshal(data, dst)
|
||||
|
||||
@@ -2,17 +2,26 @@ package discoveryutils
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/netutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timerpool"
|
||||
"github.com/VictoriaMetrics/fasthttp"
|
||||
)
|
||||
|
||||
var (
|
||||
maxConcurrency = flag.Int("promscrape.discovery.concurrency", 500, "The maximum number of concurrent requests to Prometheus autodiscovery API (Consul, Kubernetes, etc.)")
|
||||
maxWaitTime = flag.Duration("promscrape.discovery.concurrentWaitTime", time.Minute, "The maximum duration for waiting to perform API requests "+
|
||||
"if more than -promscrape.discovery.concurrency requests are simultaneously performed")
|
||||
)
|
||||
|
||||
var defaultClient = &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
@@ -56,6 +65,7 @@ func NewClient(apiServer string, ac *promauth.Config) (*Client, error) {
|
||||
ReadTimeout: time.Minute,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
MaxResponseBodySize: 300 * 1024 * 1024,
|
||||
MaxConns: *maxConcurrency,
|
||||
}
|
||||
return &Client{
|
||||
hc: hc,
|
||||
@@ -65,8 +75,30 @@ func NewClient(apiServer string, ac *promauth.Config) (*Client, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
concurrencyLimitCh chan struct{}
|
||||
concurrencyLimitChOnce sync.Once
|
||||
)
|
||||
|
||||
func concurrencyLimitChInit() {
|
||||
concurrencyLimitCh = make(chan struct{}, *maxConcurrency)
|
||||
}
|
||||
|
||||
// GetAPIResponse returns response for the given absolute path.
|
||||
func (c *Client) GetAPIResponse(path string) ([]byte, error) {
|
||||
// Limit the number of concurrent API requests.
|
||||
concurrencyLimitChOnce.Do(concurrencyLimitChInit)
|
||||
t := timerpool.Get(*maxWaitTime)
|
||||
select {
|
||||
case concurrencyLimitCh <- struct{}{}:
|
||||
timerpool.Put(t)
|
||||
case <-t.C:
|
||||
timerpool.Put(t)
|
||||
return nil, fmt.Errorf("too many outstanding requests to %q; try increasing -promscrape.discovery.concurrentWaitTime=%s or -promscrape.discovery.concurrency=%d",
|
||||
c.apiServer, *maxWaitTime, *maxConcurrency)
|
||||
}
|
||||
defer func() { <-concurrencyLimitCh }()
|
||||
|
||||
requestURL := c.apiServer + path
|
||||
var u fasthttp.URI
|
||||
u.Update(requestURL)
|
||||
|
||||
@@ -3,6 +3,8 @@ package discoveryutils
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
||||
)
|
||||
|
||||
// ConfigMap is a map for storing discovery api configs.
|
||||
@@ -37,7 +39,7 @@ func (cm *ConfigMap) Get(key interface{}, newConfig func() (interface{}, error))
|
||||
|
||||
e := cm.m[key]
|
||||
if e != nil {
|
||||
e.lastAccessTime = time.Now()
|
||||
e.lastAccessTime = fasttime.UnixTimestamp()
|
||||
return e.cfg, nil
|
||||
}
|
||||
cfg, err := newConfig()
|
||||
@@ -46,17 +48,18 @@ func (cm *ConfigMap) Get(key interface{}, newConfig func() (interface{}, error))
|
||||
}
|
||||
cm.m[key] = &configMapEntry{
|
||||
cfg: cfg,
|
||||
lastAccessTime: time.Now(),
|
||||
lastAccessTime: fasttime.UnixTimestamp(),
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func (cm *ConfigMap) cleaner() {
|
||||
tc := time.NewTicker(15 * time.Minute)
|
||||
for currentTime := range tc.C {
|
||||
for range tc.C {
|
||||
currentTime := fasttime.UnixTimestamp()
|
||||
cm.mu.Lock()
|
||||
for k, e := range cm.m {
|
||||
if currentTime.Sub(e.lastAccessTime) > 10*time.Minute {
|
||||
if currentTime-e.lastAccessTime > 10*60 {
|
||||
delete(cm.m, k)
|
||||
}
|
||||
}
|
||||
@@ -66,5 +69,5 @@ func (cm *ConfigMap) cleaner() {
|
||||
|
||||
type configMapEntry struct {
|
||||
cfg interface{}
|
||||
lastAccessTime time.Time
|
||||
lastAccessTime uint64
|
||||
}
|
||||
|
||||
@@ -37,6 +37,15 @@ var (
|
||||
"See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config for details")
|
||||
)
|
||||
|
||||
// CheckConfig checks -promscrape.config for errors and unsupported options.
|
||||
func CheckConfig() error {
|
||||
if *promscrapeConfigFile == "" {
|
||||
return fmt.Errorf("missing -promscrape.config option")
|
||||
}
|
||||
_, _, err := loadConfig(*promscrapeConfigFile)
|
||||
return err
|
||||
}
|
||||
|
||||
// Init initializes Prometheus scraper with config from the `-promscrape.config`.
|
||||
//
|
||||
// Scraped data is passed to pushData.
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/common"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
)
|
||||
@@ -69,7 +70,7 @@ func (ctx *streamContext) Read(r io.Reader) bool {
|
||||
rows := ctx.Rows.Rows
|
||||
|
||||
// Fill missing timestamps with the current timestamp rounded to seconds.
|
||||
currentTimestamp := time.Now().Unix()
|
||||
currentTimestamp := int64(fasttime.UnixTimestamp())
|
||||
for i := range rows {
|
||||
r := &rows[i]
|
||||
if r.Timestamp == 0 {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/common"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
)
|
||||
@@ -68,7 +69,7 @@ func (ctx *streamContext) Read(r io.Reader) bool {
|
||||
rows := ctx.Rows.Rows
|
||||
|
||||
// Fill in missing timestamps
|
||||
currentTimestamp := time.Now().Unix()
|
||||
currentTimestamp := int64(fasttime.UnixTimestamp())
|
||||
for i := range rows {
|
||||
r := &rows[i]
|
||||
if r.Timestamp == 0 {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/common"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
)
|
||||
@@ -67,7 +68,7 @@ func ParseStream(req *http.Request, callback func(rows []Row) error) error {
|
||||
rows := ctx.Rows.Rows
|
||||
|
||||
// Fill in missing timestamps
|
||||
currentTimestamp := time.Now().Unix()
|
||||
currentTimestamp := int64(fasttime.UnixTimestamp())
|
||||
for i := range rows {
|
||||
r := &rows[i]
|
||||
if r.Timestamp == 0 {
|
||||
|
||||
@@ -189,7 +189,7 @@ func unmarshalBlockHeaders(dst []blockHeader, src []byte, blockHeadersCount int)
|
||||
for len(src) > 0 {
|
||||
tmp, err := bh.Unmarshal(src)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot unmarshal block header: %s", err)
|
||||
return dst, fmt.Errorf("cannot unmarshal block header: %s", err)
|
||||
}
|
||||
src = tmp
|
||||
dst = append(dst, bh)
|
||||
@@ -199,12 +199,12 @@ func unmarshalBlockHeaders(dst []blockHeader, src []byte, blockHeadersCount int)
|
||||
|
||||
// Verify the number of read block headers.
|
||||
if len(newBHS) != blockHeadersCount {
|
||||
return nil, fmt.Errorf("invalid number of block headers found: %d; want %d block headers", len(newBHS), blockHeadersCount)
|
||||
return dst, fmt.Errorf("invalid number of block headers found: %d; want %d block headers", len(newBHS), blockHeadersCount)
|
||||
}
|
||||
|
||||
// Verify that block headers are sorted by tsid.
|
||||
if !sort.SliceIsSorted(newBHS, func(i, j int) bool { return newBHS[i].TSID.Less(&newBHS[j].TSID) }) {
|
||||
return nil, fmt.Errorf("block headers must be sorted by tsid; unmarshaled unsorted block headers: %+v", newBHS)
|
||||
return dst, fmt.Errorf("block headers must be sorted by tsid; unmarshaled unsorted block headers: %+v", newBHS)
|
||||
}
|
||||
|
||||
return dst, nil
|
||||
|
||||
@@ -71,7 +71,10 @@ func (bsw *blockStreamWriter) reset() {
|
||||
// InitFromInmemoryPart initialzes bsw from inmemory part.
|
||||
func (bsw *blockStreamWriter) InitFromInmemoryPart(mp *inmemoryPart) {
|
||||
bsw.reset()
|
||||
bsw.compressLevel = 0
|
||||
|
||||
// Use the minimum compression level for in-memory blocks,
|
||||
// since they are going to be re-compressed during the merge into file-based blocks.
|
||||
bsw.compressLevel = -5 // See https://github.com/facebook/zstd/releases/tag/v1.3.4
|
||||
|
||||
bsw.timestampsWriter = &mp.timestampsData
|
||||
bsw.valuesWriter = &mp.valuesData
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/memory"
|
||||
@@ -633,7 +634,7 @@ func (db *indexDB) generateTSID(dst *TSID, metricName []byte, mn *MetricName) er
|
||||
if len(mn.Tags) > 1 {
|
||||
dst.InstanceID = uint32(xxhash.Sum64(mn.Tags[1].Value))
|
||||
}
|
||||
dst.MetricID = getUniqueUint64()
|
||||
dst.MetricID = generateUniqueMetricID()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -671,6 +672,7 @@ func (db *indexDB) createIndexes(tsid *TSID, mn *MetricName) error {
|
||||
items.B = marshalTagValue(items.B, mn.MetricGroup)
|
||||
items.B = encoding.MarshalUint64(items.B, tsid.MetricID)
|
||||
items.Next()
|
||||
addReverseMetricGroupIfNeeded(items, commonPrefix.B, mn, tsid.MetricID)
|
||||
|
||||
// For each tag create tag -> MetricID index.
|
||||
for i := range mn.Tags {
|
||||
@@ -1233,7 +1235,7 @@ func (db *indexDB) updateDeletedMetricIDs(metricIDs *uint64set.Set) {
|
||||
}
|
||||
|
||||
func (is *indexSearch) getStartDateForPerDayInvertedIndex() (uint64, error) {
|
||||
minDate := uint64(timestampFromTime(time.Now())) / msecPerDay
|
||||
minDate := fasttime.UnixDate()
|
||||
kb := &is.kb
|
||||
ts := &is.ts
|
||||
kb.B = append(kb.B[:0], nsPrefixDateTagToMetricIDs)
|
||||
@@ -2529,19 +2531,7 @@ func (is *indexSearch) getMetricIDsForRecentHours(tr TimeRange, maxMetrics int)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (db *indexDB) storeDateMetricID(date, metricID uint64) error {
|
||||
is := db.getIndexSearch()
|
||||
ok, err := is.hasDateMetricID(date, metricID)
|
||||
db.putIndexSearch(is)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ok {
|
||||
// Fast path: the (date, metricID) entry already exists in the db.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Slow path: create (date, metricID) entries.
|
||||
func (is *indexSearch) storeDateMetricID(date, metricID uint64) error {
|
||||
items := getIndexItems()
|
||||
defer putIndexItems(items)
|
||||
|
||||
@@ -2555,12 +2545,16 @@ func (db *indexDB) storeDateMetricID(date, metricID uint64) error {
|
||||
defer kbPool.Put(kb)
|
||||
mn := GetMetricName()
|
||||
defer PutMetricName(mn)
|
||||
kb.B, err = db.searchMetricName(kb.B[:0], metricID)
|
||||
var err error
|
||||
// There is no need in searching for metric name in is.db.extDB,
|
||||
// Since the storeDateMetricID function is called only after the metricID->metricName
|
||||
// is added into the current is.db.
|
||||
kb.B, err = is.searchMetricName(kb.B[:0], metricID)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
logger.Errorf("missing metricName by metricID %d; this could be the case after unclean shutdown; "+
|
||||
"deleting the metricID, so it could be re-created next time", metricID)
|
||||
if err := db.deleteMetricIDs([]uint64{metricID}); err != nil {
|
||||
if err := is.db.deleteMetricIDs([]uint64{metricID}); err != nil {
|
||||
return fmt.Errorf("cannot delete metricID %d after unclean shutdown: %s", metricID, err)
|
||||
}
|
||||
return nil
|
||||
@@ -2578,6 +2572,7 @@ func (db *indexDB) storeDateMetricID(date, metricID uint64) error {
|
||||
items.B = marshalTagValue(items.B, mn.MetricGroup)
|
||||
items.B = encoding.MarshalUint64(items.B, metricID)
|
||||
items.Next()
|
||||
addReverseMetricGroupIfNeeded(items, kb.B, mn, metricID)
|
||||
for i := range mn.Tags {
|
||||
tag := &mn.Tags[i]
|
||||
items.B = append(items.B, kb.B...)
|
||||
@@ -2585,13 +2580,44 @@ func (db *indexDB) storeDateMetricID(date, metricID uint64) error {
|
||||
items.B = encoding.MarshalUint64(items.B, metricID)
|
||||
items.Next()
|
||||
}
|
||||
|
||||
if err = db.tb.AddItems(items.Items); err != nil {
|
||||
if err = is.db.tb.AddItems(items.Items); err != nil {
|
||||
return fmt.Errorf("cannot add per-day entires for metricID %d: %s", metricID, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func addReverseMetricGroupIfNeeded(items *indexItems, prefix []byte, mn *MetricName, metricID uint64) {
|
||||
if bytes.IndexByte(mn.MetricGroup, '.') < 0 {
|
||||
// The reverse metric group is needed only for Graphite-like metrics with points.
|
||||
return
|
||||
}
|
||||
// This is most likely a Graphite metric like 'foo.bar.baz'.
|
||||
// Store reverse metric name 'zab.rab.oof' in order to speed up search for '*.bar.baz'
|
||||
// when the Graphite wildcard has a suffix matching small number of time series.
|
||||
items.B = append(items.B, prefix...)
|
||||
items.B = marshalTagValue(items.B, graphiteReverseTagKey)
|
||||
revBuf := kbPool.Get()
|
||||
revBuf.B = reverseBytes(revBuf.B[:0], mn.MetricGroup)
|
||||
items.B = marshalTagValue(items.B, revBuf.B)
|
||||
kbPool.Put(revBuf)
|
||||
items.B = encoding.MarshalUint64(items.B, metricID)
|
||||
items.Next()
|
||||
}
|
||||
|
||||
// The tag key for reverse metric name used for speeding up searching
|
||||
// for Graphite wildcards with suffix matching small number of time series,
|
||||
// i.e. '*.bar.baz'.
|
||||
//
|
||||
// It is expected that the given key isn't be used by users.
|
||||
var graphiteReverseTagKey = []byte("\xff")
|
||||
|
||||
func reverseBytes(dst, src []byte) []byte {
|
||||
for i := len(src) - 1; i >= 0; i-- {
|
||||
dst = append(dst, src[i])
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func (is *indexSearch) hasDateMetricID(date, metricID uint64) (bool, error) {
|
||||
ts := &is.ts
|
||||
kb := &is.kb
|
||||
@@ -2785,14 +2811,17 @@ func (is *indexSearch) intersectMetricIDsWithTagFilterNocache(tf *tagFilter, fil
|
||||
var kbPool bytesutil.ByteBufferPool
|
||||
|
||||
// Returns local unique MetricID.
|
||||
func getUniqueUint64() uint64 {
|
||||
return atomic.AddUint64(&uniqueUint64, 1)
|
||||
func generateUniqueMetricID() uint64 {
|
||||
// It is expected that metricIDs returned from this function must be dense.
|
||||
// If they will be sparse, then this may hurt metric_ids intersection
|
||||
// performance with uint64set.Set.
|
||||
return atomic.AddUint64(&nextUniqueMetricID, 1)
|
||||
}
|
||||
|
||||
// This number mustn't go backwards on restarts, otherwise metricID
|
||||
// collisions are possible. So don't change time on the server
|
||||
// between VictoriaMetrics restarts.
|
||||
var uniqueUint64 = uint64(time.Now().UnixNano())
|
||||
var nextUniqueMetricID = uint64(time.Now().UnixNano())
|
||||
|
||||
func marshalCommonPrefix(dst []byte, nsPrefix byte) []byte {
|
||||
dst = append(dst, nsPrefix)
|
||||
|
||||
@@ -17,6 +17,20 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/workingsetcache"
|
||||
)
|
||||
|
||||
func TestReverseBytes(t *testing.T) {
|
||||
f := func(s, resultExpected string) {
|
||||
t.Helper()
|
||||
result := reverseBytes(nil, []byte(s))
|
||||
if string(result) != resultExpected {
|
||||
t.Fatalf("unexpected result for reverseBytes(%q); got %q; want %q", s, result, resultExpected)
|
||||
}
|
||||
}
|
||||
f("", "")
|
||||
f("a", "a")
|
||||
f("av", "va")
|
||||
f("foo.bar", "rab.oof")
|
||||
}
|
||||
|
||||
func TestMergeTagToMetricIDsRows(t *testing.T) {
|
||||
f := func(items []string, expectedItems []string) {
|
||||
t.Helper()
|
||||
@@ -659,7 +673,7 @@ func testIndexDBGetOrCreateTSIDByName(db *indexDB, metricGroups int) ([]MetricNa
|
||||
var mn MetricName
|
||||
|
||||
// Init MetricGroup.
|
||||
mn.MetricGroup = []byte(fmt.Sprintf("metricGroup_%d\x00\x01\x02", i%metricGroups))
|
||||
mn.MetricGroup = []byte(fmt.Sprintf("metricGroup.%d\x00\x01\x02", i%metricGroups))
|
||||
|
||||
// Init other tags.
|
||||
tagsCount := rand.Intn(10) + 1
|
||||
@@ -685,7 +699,7 @@ func testIndexDBGetOrCreateTSIDByName(db *indexDB, metricGroups int) ([]MetricNa
|
||||
date := uint64(timestampFromTime(time.Now())) / msecPerDay
|
||||
for i := range tsids {
|
||||
tsid := &tsids[i]
|
||||
if err := db.storeDateMetricID(date, tsid.MetricID); err != nil {
|
||||
if err := is.storeDateMetricID(date, tsid.MetricID); err != nil {
|
||||
return nil, nil, fmt.Errorf("error in storeDateMetricID(%d, %d): %s", date, tsid.MetricID, err)
|
||||
}
|
||||
}
|
||||
@@ -1476,7 +1490,7 @@ func TestSearchTSIDWithTimeRange(t *testing.T) {
|
||||
date := baseDate - uint64(day*msecPerDay)
|
||||
for i := range tsids {
|
||||
tsid := &tsids[i]
|
||||
if err := db.storeDateMetricID(date, tsid.MetricID); err != nil {
|
||||
if err := is.storeDateMetricID(date, tsid.MetricID); err != nil {
|
||||
t.Fatalf("error in storeDateMetricID(%d, %d): %s", date, tsid.MetricID, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ package storage
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
)
|
||||
|
||||
@@ -17,7 +17,7 @@ type inmemoryPart struct {
|
||||
indexData bytesutil.ByteBuffer
|
||||
metaindexData bytesutil.ByteBuffer
|
||||
|
||||
creationTime time.Time
|
||||
creationTime uint64
|
||||
}
|
||||
|
||||
// Reset resets mp.
|
||||
@@ -29,7 +29,7 @@ func (mp *inmemoryPart) Reset() {
|
||||
mp.indexData.Reset()
|
||||
mp.metaindexData.Reset()
|
||||
|
||||
mp.creationTime = time.Time{}
|
||||
mp.creationTime = 0
|
||||
}
|
||||
|
||||
// InitFromRows initializes mp from the given rows.
|
||||
@@ -42,7 +42,7 @@ func (mp *inmemoryPart) InitFromRows(rows []rawRow) {
|
||||
rrm := getRawRowsMarshaler()
|
||||
rrm.marshalToInmemoryPart(mp, rows)
|
||||
putRawRowsMarshaler(rrm)
|
||||
mp.creationTime = time.Now()
|
||||
mp.creationTime = fasttime.UnixTimestamp()
|
||||
}
|
||||
|
||||
// NewPart creates new part from mp.
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/filestream"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/memory"
|
||||
@@ -228,7 +229,7 @@ func (ibc *indexBlockCache) cleaner() {
|
||||
}
|
||||
|
||||
func (ibc *indexBlockCache) cleanByTimeout() {
|
||||
currentTime := atomic.LoadUint64(¤tTimestamp)
|
||||
currentTime := fasttime.UnixTimestamp()
|
||||
ibc.mu.Lock()
|
||||
for k, ibe := range ibc.m {
|
||||
// Delete items accessed more than 10 minutes ago.
|
||||
@@ -247,7 +248,7 @@ func (ibc *indexBlockCache) Get(k uint64) *indexBlock {
|
||||
ibc.mu.RUnlock()
|
||||
|
||||
if ibe != nil {
|
||||
currentTime := atomic.LoadUint64(¤tTimestamp)
|
||||
currentTime := fasttime.UnixTimestamp()
|
||||
if atomic.LoadUint64(&ibe.lastAccessTime) != currentTime {
|
||||
atomic.StoreUint64(&ibe.lastAccessTime, currentTime)
|
||||
}
|
||||
@@ -257,7 +258,7 @@ func (ibc *indexBlockCache) Get(k uint64) *indexBlock {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ibc *indexBlockCache) Put(k uint64, ib *indexBlock) bool {
|
||||
func (ibc *indexBlockCache) Put(k uint64, ib *indexBlock) {
|
||||
ibc.mu.Lock()
|
||||
|
||||
// Clean superflouos cache entries.
|
||||
@@ -276,12 +277,11 @@ func (ibc *indexBlockCache) Put(k uint64, ib *indexBlock) bool {
|
||||
|
||||
// Store frequently requested ib in the cache.
|
||||
ibe := &indexBlockCacheEntry{
|
||||
lastAccessTime: atomic.LoadUint64(¤tTimestamp),
|
||||
lastAccessTime: fasttime.UnixTimestamp(),
|
||||
ib: ib,
|
||||
}
|
||||
ibc.m[k] = ibe
|
||||
ibc.mu.Unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
func (ibc *indexBlockCache) Requests() uint64 {
|
||||
@@ -298,16 +298,3 @@ func (ibc *indexBlockCache) Len() uint64 {
|
||||
ibc.mu.Unlock()
|
||||
return n
|
||||
}
|
||||
|
||||
func init() {
|
||||
go func() {
|
||||
ticker := time.NewTicker(time.Second)
|
||||
defer ticker.Stop()
|
||||
for tm := range ticker.C {
|
||||
t := uint64(tm.Unix())
|
||||
atomic.StoreUint64(¤tTimestamp, t)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
var currentTimestamp uint64
|
||||
|
||||
@@ -36,9 +36,6 @@ type partSearch struct {
|
||||
|
||||
bhs []blockHeader
|
||||
|
||||
// Pointer to index block, which may be reused
|
||||
indexBlockReuse *indexBlock
|
||||
|
||||
compressedIndexBuf []byte
|
||||
indexBuf []byte
|
||||
|
||||
@@ -53,10 +50,6 @@ func (ps *partSearch) reset() {
|
||||
ps.metaindex = nil
|
||||
ps.ibCache = nil
|
||||
ps.bhs = nil
|
||||
if ps.indexBlockReuse != nil {
|
||||
putIndexBlock(ps.indexBlockReuse)
|
||||
ps.indexBlockReuse = nil
|
||||
}
|
||||
ps.compressedIndexBuf = ps.compressedIndexBuf[:0]
|
||||
ps.indexBuf = ps.indexBuf[:0]
|
||||
ps.err = nil
|
||||
@@ -161,10 +154,6 @@ func (ps *partSearch) nextBHS() bool {
|
||||
|
||||
// Found the index block which may contain the required data
|
||||
// for the ps.BlockRef.bh.TSID and the given timestamp range.
|
||||
if ps.indexBlockReuse != nil {
|
||||
putIndexBlock(ps.indexBlockReuse)
|
||||
ps.indexBlockReuse = nil
|
||||
}
|
||||
indexBlockKey := mr.IndexBlockOffset
|
||||
ib := ps.ibCache.Get(indexBlockKey)
|
||||
if ib == nil {
|
||||
@@ -176,9 +165,7 @@ func (ps *partSearch) nextBHS() bool {
|
||||
&ps.p.ph, mr.IndexBlockOffset, mr.IndexBlockSize, err)
|
||||
return false
|
||||
}
|
||||
if ok := ps.ibCache.Put(indexBlockKey, ib); !ok {
|
||||
ps.indexBlockReuse = ib
|
||||
}
|
||||
ps.ibCache.Put(indexBlockKey, ib)
|
||||
}
|
||||
ps.bhs = ib.bhs
|
||||
return true
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/memory"
|
||||
@@ -445,7 +446,7 @@ func (rrs *rawRowsShards) Len() int {
|
||||
type rawRowsShard struct {
|
||||
lock sync.Mutex
|
||||
rows []rawRow
|
||||
lastFlushTime time.Time
|
||||
lastFlushTime uint64
|
||||
}
|
||||
|
||||
func (rrs *rawRowsShard) Len() int {
|
||||
@@ -478,7 +479,7 @@ func (rrs *rawRowsShard) addRows(pt *partition, rows []rawRow) {
|
||||
rr := getRawRowsMaxSize()
|
||||
rrs.rows, rr.rows = rr.rows, rrs.rows
|
||||
rrss = append(rrss, rr)
|
||||
rrs.lastFlushTime = time.Now()
|
||||
rrs.lastFlushTime = fasttime.UnixTimestamp()
|
||||
}
|
||||
rrs.lock.Unlock()
|
||||
|
||||
@@ -722,10 +723,14 @@ func (rrs *rawRowsShards) flush(pt *partition, isFinal bool) {
|
||||
|
||||
func (rrs *rawRowsShard) flush(pt *partition, isFinal bool) {
|
||||
var rr *rawRows
|
||||
currentTime := time.Now()
|
||||
currentTime := fasttime.UnixTimestamp()
|
||||
flushSeconds := int64(rawRowsFlushInterval.Seconds())
|
||||
if flushSeconds <= 0 {
|
||||
flushSeconds = 1
|
||||
}
|
||||
|
||||
rrs.lock.Lock()
|
||||
if isFinal || currentTime.Sub(rrs.lastFlushTime) > rawRowsFlushInterval {
|
||||
if isFinal || currentTime-rrs.lastFlushTime > uint64(flushSeconds) {
|
||||
rr = getRawRowsMaxSize()
|
||||
rrs.rows, rr.rows = rr.rows, rrs.rows
|
||||
}
|
||||
@@ -764,7 +769,11 @@ func (pt *partition) inmemoryPartsFlusher() {
|
||||
}
|
||||
|
||||
func (pt *partition) flushInmemoryParts(dstPws []*partWrapper, force bool) ([]*partWrapper, error) {
|
||||
currentTime := time.Now()
|
||||
currentTime := fasttime.UnixTimestamp()
|
||||
flushSeconds := int64(inmemoryPartsFlushInterval.Seconds())
|
||||
if flushSeconds <= 0 {
|
||||
flushSeconds = 1
|
||||
}
|
||||
|
||||
// Inmemory parts may present only in small parts.
|
||||
pt.partsLock.Lock()
|
||||
@@ -772,7 +781,7 @@ func (pt *partition) flushInmemoryParts(dstPws []*partWrapper, force bool) ([]*p
|
||||
if pw.mp == nil || pw.isInMerge {
|
||||
continue
|
||||
}
|
||||
if force || currentTime.Sub(pw.mp.creationTime) >= inmemoryPartsFlushInterval {
|
||||
if force || currentTime-pw.mp.creationTime >= uint64(flushSeconds) {
|
||||
pw.isInMerge = true
|
||||
dstPws = append(dstPws, pw)
|
||||
}
|
||||
@@ -876,7 +885,7 @@ const (
|
||||
|
||||
func (pt *partition) partsMerger(mergerFunc func(isFinal bool) error) error {
|
||||
sleepTime := minMergeSleepTime
|
||||
var lastMergeTime time.Time
|
||||
var lastMergeTime uint64
|
||||
isFinal := false
|
||||
t := time.NewTimer(sleepTime)
|
||||
for {
|
||||
@@ -884,7 +893,7 @@ func (pt *partition) partsMerger(mergerFunc func(isFinal bool) error) error {
|
||||
if err == nil {
|
||||
// Try merging additional parts.
|
||||
sleepTime = minMergeSleepTime
|
||||
lastMergeTime = time.Now()
|
||||
lastMergeTime = fasttime.UnixTimestamp()
|
||||
isFinal = false
|
||||
continue
|
||||
}
|
||||
@@ -895,10 +904,10 @@ func (pt *partition) partsMerger(mergerFunc func(isFinal bool) error) error {
|
||||
if err != errNothingToMerge {
|
||||
return err
|
||||
}
|
||||
if time.Since(lastMergeTime) > 30*time.Second {
|
||||
if fasttime.UnixTimestamp()-lastMergeTime > 30 {
|
||||
// We have free time for merging into bigger parts.
|
||||
// This should improve select performance.
|
||||
lastMergeTime = time.Now()
|
||||
lastMergeTime = fasttime.UnixTimestamp()
|
||||
isFinal = true
|
||||
continue
|
||||
}
|
||||
@@ -939,7 +948,7 @@ func mustGetFreeDiskSpace(path string) uint64 {
|
||||
defer freeSpaceMapLock.Unlock()
|
||||
|
||||
e, ok := freeSpaceMap[path]
|
||||
if ok && time.Since(e.updateTime) < time.Second {
|
||||
if ok && fasttime.UnixTimestamp()-e.updateTime < 2 {
|
||||
// Fast path - the entry is fresh.
|
||||
return e.freeSpace
|
||||
}
|
||||
@@ -947,7 +956,7 @@ func mustGetFreeDiskSpace(path string) uint64 {
|
||||
// Slow path.
|
||||
// Determine the amount of free space on bigPartsPath.
|
||||
e.freeSpace = fs.MustGetFreeSpace(path)
|
||||
e.updateTime = time.Now()
|
||||
e.updateTime = fasttime.UnixTimestamp()
|
||||
freeSpaceMap[path] = e
|
||||
return e.freeSpace
|
||||
}
|
||||
@@ -958,7 +967,7 @@ var (
|
||||
)
|
||||
|
||||
type freeSpaceEntry struct {
|
||||
updateTime time.Time
|
||||
updateTime uint64
|
||||
freeSpace uint64
|
||||
}
|
||||
|
||||
@@ -1060,8 +1069,10 @@ func (pt *partition) mergeParts(pws []*partWrapper, stopCh <-chan struct{}) erro
|
||||
}
|
||||
|
||||
outRowsCount := uint64(0)
|
||||
outBlocksCount := uint64(0)
|
||||
for _, pw := range pws {
|
||||
outRowsCount += pw.p.ph.RowsCount
|
||||
outBlocksCount += pw.p.ph.BlocksCount
|
||||
}
|
||||
isBigPart := outRowsCount > maxRowsPerSmallPart()
|
||||
nocache := isBigPart
|
||||
@@ -1075,7 +1086,7 @@ func (pt *partition) mergeParts(pws []*partWrapper, stopCh <-chan struct{}) erro
|
||||
mergeIdx := pt.nextMergeIdx()
|
||||
tmpPartPath := fmt.Sprintf("%s/tmp/%016X", ptPath, mergeIdx)
|
||||
bsw := getBlockStreamWriter()
|
||||
compressLevel := getCompressLevelForRowsCount(outRowsCount)
|
||||
compressLevel := getCompressLevelForRowsCount(outRowsCount, outBlocksCount)
|
||||
if err := bsw.InitFromFilePart(tmpPartPath, nocache, compressLevel); err != nil {
|
||||
return fmt.Errorf("cannot create destination part %q: %s", tmpPartPath, err)
|
||||
}
|
||||
@@ -1176,24 +1187,28 @@ func (pt *partition) mergeParts(pws []*partWrapper, stopCh <-chan struct{}) erro
|
||||
|
||||
d := time.Since(startTime)
|
||||
if d > 10*time.Second {
|
||||
logger.Infof("merged %d rows in %.3f seconds at %d rows/sec to %q; sizeBytes: %d",
|
||||
outRowsCount, d.Seconds(), int(float64(outRowsCount)/d.Seconds()), dstPartPath, newPSize)
|
||||
logger.Infof("merged %d rows across %d blocks in %.3f seconds at %d rows/sec to %q; sizeBytes: %d",
|
||||
outRowsCount, outBlocksCount, d.Seconds(), int(float64(outRowsCount)/d.Seconds()), dstPartPath, newPSize)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getCompressLevelForRowsCount(rowsCount uint64) int {
|
||||
if rowsCount <= 1<<19 {
|
||||
func getCompressLevelForRowsCount(rowsCount, blocksCount uint64) int {
|
||||
avgRowsPerBlock := rowsCount / blocksCount
|
||||
if avgRowsPerBlock <= 200 {
|
||||
return -1
|
||||
}
|
||||
if avgRowsPerBlock <= 500 {
|
||||
return 1
|
||||
}
|
||||
if rowsCount <= 1<<22 {
|
||||
if avgRowsPerBlock <= 1000 {
|
||||
return 2
|
||||
}
|
||||
if rowsCount <= 1<<25 {
|
||||
if avgRowsPerBlock <= 2000 {
|
||||
return 3
|
||||
}
|
||||
if rowsCount <= 1<<28 {
|
||||
if avgRowsPerBlock <= 4000 {
|
||||
return 4
|
||||
}
|
||||
return 5
|
||||
|
||||
@@ -161,16 +161,20 @@ func testSearchGeneric(t *testing.T, forcePerDayInvertedIndex bool) {
|
||||
ch <- testSearchInternal(st, tr, mrs, accountsCount)
|
||||
}()
|
||||
}
|
||||
var firstError error
|
||||
for i := 0; i < cap(ch); i++ {
|
||||
select {
|
||||
case err := <-ch:
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
if err != nil && firstError == nil {
|
||||
firstError = err
|
||||
}
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatalf("timeout")
|
||||
}
|
||||
}
|
||||
if firstError != nil {
|
||||
t.Fatalf("unexpected error: %s", firstError)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@ import (
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/memory"
|
||||
@@ -39,6 +39,10 @@ type Storage struct {
|
||||
addRowsConcurrencyLimitTimeout uint64
|
||||
addRowsConcurrencyDroppedRows uint64
|
||||
|
||||
slowRowInserts uint64
|
||||
slowPerDayIndexInserts uint64
|
||||
slowMetricNameLoads uint64
|
||||
|
||||
path string
|
||||
cachePath string
|
||||
retentionMonths int
|
||||
@@ -142,14 +146,14 @@ func OpenStorage(path string, retentionMonths int) (*Storage, error) {
|
||||
s.metricNameCache = s.mustLoadCache("MetricID->MetricName", "metricID_metricName", mem/8)
|
||||
s.dateMetricIDCache = newDateMetricIDCache()
|
||||
|
||||
hour := uint64(timestampFromTime(time.Now())) / msecPerHour
|
||||
hour := fasttime.UnixHour()
|
||||
hmCurr := s.mustLoadHourMetricIDs(hour, "curr_hour_metric_ids")
|
||||
hmPrev := s.mustLoadHourMetricIDs(hour-1, "prev_hour_metric_ids")
|
||||
s.currHourMetricIDs.Store(hmCurr)
|
||||
s.prevHourMetricIDs.Store(hmPrev)
|
||||
s.pendingHourEntries = &uint64set.Set{}
|
||||
|
||||
date := uint64(timestampFromTime(time.Now())) / msecPerDay
|
||||
date := fasttime.UnixDate()
|
||||
nextDayMetricIDs := s.mustLoadNextDayMetricIDs(date)
|
||||
s.nextDayMetricIDs.Store(nextDayMetricIDs)
|
||||
s.pendingNextDayMetricIDs = &uint64set.Set{}
|
||||
@@ -323,6 +327,10 @@ type Metrics struct {
|
||||
AddRowsConcurrencyCapacity uint64
|
||||
AddRowsConcurrencyCurrent uint64
|
||||
|
||||
SlowRowInserts uint64
|
||||
SlowPerDayIndexInserts uint64
|
||||
SlowMetricNameLoads uint64
|
||||
|
||||
TSIDCacheSize uint64
|
||||
TSIDCacheSizeBytes uint64
|
||||
TSIDCacheRequests uint64
|
||||
@@ -377,6 +385,10 @@ func (s *Storage) UpdateMetrics(m *Metrics) {
|
||||
m.AddRowsConcurrencyCapacity = uint64(cap(addRowsConcurrencyCh))
|
||||
m.AddRowsConcurrencyCurrent = uint64(len(addRowsConcurrencyCh))
|
||||
|
||||
m.SlowRowInserts += atomic.LoadUint64(&s.slowRowInserts)
|
||||
m.SlowPerDayIndexInserts += atomic.LoadUint64(&s.slowPerDayIndexInserts)
|
||||
m.SlowMetricNameLoads += atomic.LoadUint64(&s.slowMetricNameLoads)
|
||||
|
||||
var cs fastcache.Stats
|
||||
s.tsidCache.UpdateStats(&cs)
|
||||
m.TSIDCacheSize += cs.EntriesCount
|
||||
@@ -548,6 +560,8 @@ func (s *Storage) mustRotateIndexDB() {
|
||||
|
||||
// Do not flush metricIDCache and metricNameCache, since all the metricIDs
|
||||
// from prev idb remain valid after the rotation.
|
||||
|
||||
// There is no need in resetting nextDayMetricIDs, since it should be automatically reset every day.
|
||||
}
|
||||
|
||||
// MustClose closes the storage.
|
||||
@@ -807,6 +821,7 @@ func (s *Storage) prefetchMetricNames(tsids []TSID) error {
|
||||
// It is cheaper to skip pre-fetching and obtain metricNames inline.
|
||||
return nil
|
||||
}
|
||||
atomic.AddUint64(&s.slowMetricNameLoads, uint64(len(metricIDs)))
|
||||
|
||||
// Pre-fetch metricIDs.
|
||||
sort.Sort(metricIDs)
|
||||
@@ -1016,10 +1031,6 @@ var (
|
||||
)
|
||||
|
||||
func (s *Storage) add(rows []rawRow, mrs []MetricRow, precisionBits uint8) ([]rawRow, error) {
|
||||
var is *indexSearch
|
||||
var mn *MetricName
|
||||
var kb *bytesutil.ByteBuffer
|
||||
|
||||
idb := s.idb()
|
||||
dmis := idb.getDeletedMetricIDs()
|
||||
rowsLen := len(rows)
|
||||
@@ -1033,9 +1044,10 @@ func (s *Storage) add(rows []rawRow, mrs []MetricRow, precisionBits uint8) ([]ra
|
||||
prevTSID TSID
|
||||
prevMetricNameRaw []byte
|
||||
)
|
||||
var pmrs *pendingMetricRows
|
||||
minTimestamp, maxTimestamp := s.tb.getMinMaxTimestamps()
|
||||
// Return only the last error, since it has no sense in returning all errors.
|
||||
var lastWarn error
|
||||
// Return only the first error, since it has no sense in returning all errors.
|
||||
var firstWarn error
|
||||
for i := range mrs {
|
||||
mr := &mrs[i]
|
||||
if math.IsNaN(mr.Value) {
|
||||
@@ -1045,13 +1057,19 @@ func (s *Storage) add(rows []rawRow, mrs []MetricRow, precisionBits uint8) ([]ra
|
||||
}
|
||||
if mr.Timestamp < minTimestamp {
|
||||
// Skip rows with too small timestamps outside the retention.
|
||||
lastWarn = fmt.Errorf("cannot insert row with too small timestamp %d outside the retention; minimum allowed timestamp is %d", mr.Timestamp, minTimestamp)
|
||||
if firstWarn == nil {
|
||||
firstWarn = fmt.Errorf("cannot insert row with too small timestamp %d outside the retention; minimum allowed timestamp is %d",
|
||||
mr.Timestamp, minTimestamp)
|
||||
}
|
||||
atomic.AddUint64(&s.tooSmallTimestampRows, 1)
|
||||
continue
|
||||
}
|
||||
if mr.Timestamp > maxTimestamp {
|
||||
// Skip rows with too big timestamps significantly exceeding the current time.
|
||||
lastWarn = fmt.Errorf("cannot insert row with too big timestamp %d exceeding the current time; maximum allowd timestamp is %d", mr.Timestamp, maxTimestamp)
|
||||
if firstWarn == nil {
|
||||
firstWarn = fmt.Errorf("cannot insert row with too big timestamp %d exceeding the current time; maximum allowd timestamp is %d",
|
||||
mr.Timestamp, maxTimestamp)
|
||||
}
|
||||
atomic.AddUint64(&s.tooBigTimestampRows, 1)
|
||||
continue
|
||||
}
|
||||
@@ -1066,79 +1084,170 @@ func (s *Storage) add(rows []rawRow, mrs []MetricRow, precisionBits uint8) ([]ra
|
||||
r.TSID = prevTSID
|
||||
continue
|
||||
}
|
||||
if s.getTSIDFromCache(&r.TSID, mr.MetricNameRaw) {
|
||||
if !dmis.Has(r.TSID.MetricID) {
|
||||
if s.getTSIDFromCache(&r.TSID, mr.MetricNameRaw) && !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
|
||||
}
|
||||
|
||||
// Slow path - the TSID is missing in the cache.
|
||||
// Postpone its search in the loop below.
|
||||
j--
|
||||
if pmrs == nil {
|
||||
pmrs = getPendingMetricRows()
|
||||
}
|
||||
if err := pmrs.addRow(mr); err != nil {
|
||||
// Do not stop adding rows on error - just skip invalid row.
|
||||
// This guarantees that invalid rows don't prevent
|
||||
// from adding valid rows into the storage.
|
||||
if firstWarn == nil {
|
||||
firstWarn = err
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
if pmrs != nil {
|
||||
atomic.AddUint64(&s.slowRowInserts, uint64(len(pmrs.pmrs)))
|
||||
// Sort pendingMetricRows by canonical metric name in order to speed up search via `is` in the loop below.
|
||||
pendingMetricRows := pmrs.pmrs
|
||||
sort.Slice(pendingMetricRows, func(i, j int) bool {
|
||||
return string(pendingMetricRows[i].MetricName) < string(pendingMetricRows[j].MetricName)
|
||||
})
|
||||
is := idb.getIndexSearch()
|
||||
prevMetricNameRaw = nil
|
||||
for i := range pendingMetricRows {
|
||||
pmr := &pendingMetricRows[i]
|
||||
mr := &pmr.mr
|
||||
r := &rows[rowsLen+j]
|
||||
j++
|
||||
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) && !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
|
||||
}
|
||||
if err := is.GetOrCreateTSIDByName(&r.TSID, pmr.MetricName); err != nil {
|
||||
// Do not stop adding rows on error - just skip invalid row.
|
||||
// This guarantees that invalid rows don't prevent
|
||||
// from adding valid rows into the storage.
|
||||
if firstWarn == nil {
|
||||
firstWarn = fmt.Errorf("cannot obtain or create TSID for MetricName %q: %s", pmr.MetricName, err)
|
||||
}
|
||||
j--
|
||||
continue
|
||||
}
|
||||
s.putTSIDToCache(&r.TSID, mr.MetricNameRaw)
|
||||
}
|
||||
|
||||
// Slow path - the TSID is missing in the cache. Search for it in the index.
|
||||
if is == nil {
|
||||
is = idb.getIndexSearch()
|
||||
mn = GetMetricName()
|
||||
kb = kbPool.Get()
|
||||
}
|
||||
if err := mn.unmarshalRaw(mr.MetricNameRaw); err != nil {
|
||||
// Do not stop adding rows on error - just skip invalid row.
|
||||
// This guarantees that invalid rows don't prevent
|
||||
// from adding valid rows into the storage.
|
||||
lastWarn = fmt.Errorf("cannot unmarshal MetricNameRaw %q: %s", mr.MetricNameRaw, err)
|
||||
j--
|
||||
continue
|
||||
}
|
||||
mn.sortTags()
|
||||
kb.B = mn.Marshal(kb.B[:0])
|
||||
if err := is.GetOrCreateTSIDByName(&r.TSID, kb.B); err != nil {
|
||||
// Do not stop adding rows on error - just skip invalid row.
|
||||
// This guarantees that invalid rows don't prevent
|
||||
// from adding valid rows into the storage.
|
||||
lastWarn = fmt.Errorf("cannot obtain TSID for MetricName %q: %s", kb.B, err)
|
||||
j--
|
||||
continue
|
||||
}
|
||||
s.putTSIDToCache(&r.TSID, mr.MetricNameRaw)
|
||||
}
|
||||
if lastWarn != nil {
|
||||
logger.Errorf("warn occurred during rows addition: %s", lastWarn)
|
||||
}
|
||||
if is != nil {
|
||||
kbPool.Put(kb)
|
||||
PutMetricName(mn)
|
||||
idb.putIndexSearch(is)
|
||||
putPendingMetricRows(pmrs)
|
||||
}
|
||||
if firstWarn != nil {
|
||||
logger.Errorf("warn occurred during rows addition: %s", firstWarn)
|
||||
}
|
||||
rows = rows[:rowsLen+j]
|
||||
|
||||
var lastError error
|
||||
var firstError error
|
||||
if err := s.tb.AddRows(rows); err != nil {
|
||||
lastError = fmt.Errorf("cannot add rows to table: %s", err)
|
||||
firstError = fmt.Errorf("cannot add rows to table: %s", err)
|
||||
}
|
||||
if err := s.updatePerDateData(rows); err != nil && lastError == nil {
|
||||
lastError = fmt.Errorf("cannot update per-date data: %s", err)
|
||||
if err := s.updatePerDateData(rows); err != nil && firstError == nil {
|
||||
firstError = fmt.Errorf("cannot update per-date data: %s", err)
|
||||
}
|
||||
if lastError != nil {
|
||||
return rows, fmt.Errorf("error occurred during rows addition: %s", lastError)
|
||||
if firstError != nil {
|
||||
return rows, fmt.Errorf("error occurred during rows addition: %s", firstError)
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
type pendingMetricRow struct {
|
||||
MetricName []byte
|
||||
mr MetricRow
|
||||
}
|
||||
|
||||
type pendingMetricRows struct {
|
||||
pmrs []pendingMetricRow
|
||||
metricNamesBuf []byte
|
||||
|
||||
lastMetricNameRaw []byte
|
||||
lastMetricName []byte
|
||||
mn MetricName
|
||||
}
|
||||
|
||||
func (pmrs *pendingMetricRows) reset() {
|
||||
for _, pmr := range pmrs.pmrs {
|
||||
pmr.MetricName = nil
|
||||
pmr.mr.MetricNameRaw = nil
|
||||
}
|
||||
pmrs.pmrs = pmrs.pmrs[:0]
|
||||
pmrs.metricNamesBuf = pmrs.metricNamesBuf[:0]
|
||||
pmrs.lastMetricNameRaw = nil
|
||||
pmrs.lastMetricName = nil
|
||||
pmrs.mn.Reset()
|
||||
}
|
||||
|
||||
func (pmrs *pendingMetricRows) addRow(mr *MetricRow) error {
|
||||
// Do not spend CPU time on re-calculating canonical metricName during bulk import
|
||||
// of many rows for the same metric.
|
||||
if string(mr.MetricNameRaw) != string(pmrs.lastMetricNameRaw) {
|
||||
if err := pmrs.mn.unmarshalRaw(mr.MetricNameRaw); err != nil {
|
||||
return fmt.Errorf("cannot unmarshal MetricNameRaw %q: %s", mr.MetricNameRaw, err)
|
||||
}
|
||||
pmrs.mn.sortTags()
|
||||
metricNamesBufLen := len(pmrs.metricNamesBuf)
|
||||
pmrs.metricNamesBuf = pmrs.mn.Marshal(pmrs.metricNamesBuf)
|
||||
pmrs.lastMetricName = pmrs.metricNamesBuf[metricNamesBufLen:]
|
||||
pmrs.lastMetricNameRaw = mr.MetricNameRaw
|
||||
}
|
||||
pmrs.pmrs = append(pmrs.pmrs, pendingMetricRow{
|
||||
MetricName: pmrs.lastMetricName,
|
||||
mr: *mr,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func getPendingMetricRows() *pendingMetricRows {
|
||||
v := pendingMetricRowsPool.Get()
|
||||
if v == nil {
|
||||
v = &pendingMetricRows{}
|
||||
}
|
||||
return v.(*pendingMetricRows)
|
||||
}
|
||||
|
||||
func putPendingMetricRows(pmrs *pendingMetricRows) {
|
||||
pmrs.reset()
|
||||
pendingMetricRowsPool.Put(pmrs)
|
||||
}
|
||||
|
||||
var pendingMetricRowsPool sync.Pool
|
||||
|
||||
func (s *Storage) updatePerDateData(rows []rawRow) error {
|
||||
var lastError 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
|
||||
prevDate uint64
|
||||
prevMetricID uint64
|
||||
)
|
||||
idb := s.idb()
|
||||
hm := s.currHourMetricIDs.Load().(*hourMetricIDs)
|
||||
nextDayMetricIDs := &s.nextDayMetricIDs.Load().(*byDateMetricIDEntry).v
|
||||
todayShare16bit := uint64((float64(uint64(time.Now().UnixNano()/1e9)%(3600*24)) / (3600 * 24)) * (1 << 16))
|
||||
todayShare16bit := uint64((float64(fasttime.UnixTimestamp()%(3600*24)) / (3600 * 24)) * (1 << 16))
|
||||
type pendingDateMetricID struct {
|
||||
date uint64
|
||||
metricID uint64
|
||||
}
|
||||
var pendingDateMetricIDs []pendingDateMetricID
|
||||
for i := range rows {
|
||||
r := &rows[i]
|
||||
if r.Timestamp != prevTimestamp {
|
||||
@@ -1159,10 +1268,10 @@ func (s *Storage) updatePerDateData(rows []rawRow) error {
|
||||
// when entries for all the active time series must be added to the index.
|
||||
// This should address https://github.com/VictoriaMetrics/VictoriaMetrics/issues/430 .
|
||||
if todayShare16bit > (metricID&(1<<16-1)) && !nextDayMetricIDs.Has(metricID) {
|
||||
if err := idb.storeDateMetricID(date+1, metricID); err != nil && lastError == nil {
|
||||
lastError = err
|
||||
continue
|
||||
}
|
||||
pendingDateMetricIDs = append(pendingDateMetricIDs, pendingDateMetricID{
|
||||
date: date + 1,
|
||||
metricID: metricID,
|
||||
})
|
||||
s.pendingNextDayMetricIDsLock.Lock()
|
||||
s.pendingNextDayMetricIDs.Add(metricID)
|
||||
s.pendingNextDayMetricIDsLock.Unlock()
|
||||
@@ -1175,29 +1284,80 @@ func (s *Storage) updatePerDateData(rows []rawRow) error {
|
||||
}
|
||||
|
||||
// Slower path: check global cache for (date, metricID) entry.
|
||||
if metricID == prevMatchedMetricID && date == prevMatchedDate {
|
||||
if metricID == prevMetricID && date == prevDate {
|
||||
// Fast path for bulk import of multiple rows with the same (date, metricID) pairs.
|
||||
continue
|
||||
}
|
||||
prevDate = date
|
||||
prevMetricID = metricID
|
||||
|
||||
if !s.dateMetricIDCache.Has(date, metricID) {
|
||||
// Slow path: store the (date, metricID) entry in the indexDB.
|
||||
// It is OK if the (date, metricID) entry is added multiple times to db
|
||||
// by concurrent goroutines.
|
||||
pendingDateMetricIDs = append(pendingDateMetricIDs, pendingDateMetricID{
|
||||
date: date,
|
||||
metricID: metricID,
|
||||
})
|
||||
}
|
||||
}
|
||||
if len(pendingDateMetricIDs) == 0 {
|
||||
// Fast path - there are no new (date, metricID) entires in rows.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Slow path - add new (date, metricID) entries to indexDB.
|
||||
|
||||
atomic.AddUint64(&s.slowPerDayIndexInserts, uint64(len(pendingDateMetricIDs)))
|
||||
// Sort pendingDateMetricIDs by (date, metricID) in order to speed up `is` search in the loop below.
|
||||
sort.Slice(pendingDateMetricIDs, func(i, j int) bool {
|
||||
a := pendingDateMetricIDs[i]
|
||||
b := pendingDateMetricIDs[j]
|
||||
if a.date != b.date {
|
||||
return a.date < b.date
|
||||
}
|
||||
return a.metricID < b.metricID
|
||||
})
|
||||
idb := s.idb()
|
||||
is := idb.getIndexSearch()
|
||||
defer idb.putIndexSearch(is)
|
||||
var firstError error
|
||||
prevMetricID = 0
|
||||
prevDate = 0
|
||||
for _, dateMetricID := range pendingDateMetricIDs {
|
||||
date := dateMetricID.date
|
||||
metricID := dateMetricID.metricID
|
||||
if metricID == prevMetricID && date == prevDate {
|
||||
// Fast path for bulk import of multiple rows with the same (date, metricID) pairs.
|
||||
continue
|
||||
}
|
||||
prevDate = date
|
||||
prevMetricID = metricID
|
||||
|
||||
if s.dateMetricIDCache.Has(date, metricID) {
|
||||
// The metricID has been already added to per-day inverted index.
|
||||
prevMatchedDate = date
|
||||
prevMatchedMetricID = metricID
|
||||
continue
|
||||
}
|
||||
|
||||
// Slow path: store the (date, metricID) entry in the indexDB.
|
||||
// It is OK if the (date, metricID) entry is added multiple times to db
|
||||
// by concurrent goroutines.
|
||||
if err := idb.storeDateMetricID(date, metricID); err != nil && lastError == nil {
|
||||
lastError = err
|
||||
ok, err := is.hasDateMetricID(date, metricID)
|
||||
if err != nil {
|
||||
if firstError == nil {
|
||||
firstError = fmt.Errorf("error when locating (date=%d, metricID=%d) in database: %s", date, metricID, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if !ok {
|
||||
// The (date, metricID) entry is missing in the indexDB. Add it there.
|
||||
if err := is.storeDateMetricID(date, metricID); err != nil {
|
||||
if firstError == nil {
|
||||
firstError = fmt.Errorf("error when storing (date=%d, metricID=%d) in database: %s", date, metricID, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
// The metric must be added to cache only after it has been successfully added to indexDB.
|
||||
s.dateMetricIDCache.Set(date, metricID)
|
||||
}
|
||||
return lastError
|
||||
return firstError
|
||||
}
|
||||
|
||||
// dateMetricIDCache is fast cache for holding (date, metricID) entries.
|
||||
@@ -1213,7 +1373,7 @@ type dateMetricIDCache struct {
|
||||
|
||||
// Contains mutable map protected by mu
|
||||
byDateMutable *byDateMetricIDMap
|
||||
lastSyncTime time.Time
|
||||
lastSyncTime uint64
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
@@ -1228,7 +1388,7 @@ func (dmc *dateMetricIDCache) Reset() {
|
||||
// Do not reset syncsCount and resetsCount
|
||||
dmc.byDate.Store(newByDateMetricIDMap())
|
||||
dmc.byDateMutable = newByDateMetricIDMap()
|
||||
dmc.lastSyncTime = time.Now()
|
||||
dmc.lastSyncTime = fasttime.UnixTimestamp()
|
||||
dmc.mu.Unlock()
|
||||
|
||||
atomic.AddUint64(&dmc.resetsCount, 1)
|
||||
@@ -1262,13 +1422,12 @@ func (dmc *dateMetricIDCache) Has(date, metricID uint64) bool {
|
||||
}
|
||||
|
||||
// Slow path. Check mutable map.
|
||||
currentTime := time.Now()
|
||||
|
||||
currentTime := fasttime.UnixTimestamp()
|
||||
dmc.mu.Lock()
|
||||
v = dmc.byDateMutable.get(date)
|
||||
ok := v.Has(metricID)
|
||||
mustSync := false
|
||||
if currentTime.Sub(dmc.lastSyncTime) > 10*time.Second {
|
||||
if currentTime-dmc.lastSyncTime > 10 {
|
||||
mustSync = true
|
||||
dmc.lastSyncTime = currentTime
|
||||
}
|
||||
@@ -1351,8 +1510,7 @@ type byDateMetricIDEntry struct {
|
||||
}
|
||||
|
||||
func (s *Storage) updateNextDayMetricIDs() {
|
||||
date := uint64(timestampFromTime(time.Now())) / msecPerDay
|
||||
|
||||
date := fasttime.UnixDate()
|
||||
e := s.nextDayMetricIDs.Load().(*byDateMetricIDEntry)
|
||||
s.pendingNextDayMetricIDsLock.Lock()
|
||||
pendingMetricIDs := s.pendingNextDayMetricIDs
|
||||
@@ -1380,7 +1538,7 @@ func (s *Storage) updateCurrHourMetricIDs() {
|
||||
newMetricIDs := s.pendingHourEntries
|
||||
s.pendingHourEntries = &uint64set.Set{}
|
||||
s.pendingHourEntriesLock.Unlock()
|
||||
hour := uint64(timestampFromTime(time.Now())) / msecPerHour
|
||||
hour := fasttime.UnixHour()
|
||||
if newMetricIDs.Len() == 0 && hm.hour == hour {
|
||||
// Fast path: nothing to update.
|
||||
return
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/uint64set"
|
||||
@@ -353,7 +354,7 @@ func (tb *table) AddRows(rows []rawRow) error {
|
||||
}
|
||||
|
||||
func (tb *table) getMinMaxTimestamps() (int64, int64) {
|
||||
now := timestampFromTime(time.Now())
|
||||
now := int64(fasttime.UnixTimestamp() * 1000)
|
||||
minTimestamp := now - tb.retentionMilliseconds
|
||||
maxTimestamp := now + 2*24*3600*1000 // allow max +2 days from now due to timezones shit :)
|
||||
if minTimestamp < 0 {
|
||||
@@ -384,7 +385,7 @@ func (tb *table) retentionWatcher() {
|
||||
case <-ticker.C:
|
||||
}
|
||||
|
||||
minTimestamp := timestampFromTime(time.Now()) - tb.retentionMilliseconds
|
||||
minTimestamp := int64(fasttime.UnixTimestamp()*1000) - tb.retentionMilliseconds
|
||||
var ptwsDrop []*partitionWrapper
|
||||
tb.ptwsLock.Lock()
|
||||
dst := tb.ptws[:0]
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"container/heap"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
)
|
||||
|
||||
@@ -65,7 +65,7 @@ func (ts *tableSearch) Init(tb *table, tsids []TSID, tr TimeRange) {
|
||||
|
||||
// Adjust tr.MinTimestamp, so it doesn't obtain data older
|
||||
// than the tb retention.
|
||||
now := timestampFromTime(time.Now())
|
||||
now := int64(fasttime.UnixTimestamp() * 1000)
|
||||
minTimestamp := now - tb.retentionMilliseconds
|
||||
if tr.MinTimestamp < minTimestamp {
|
||||
tr.MinTimestamp = minTimestamp
|
||||
|
||||
@@ -55,17 +55,26 @@ func (tfs *TagFilters) Add(key, value []byte, isNegative, isRegexp bool) error {
|
||||
// since it must filter out all the time series with the given key.
|
||||
}
|
||||
|
||||
tf := tfs.addTagFilter()
|
||||
if err := tf.Init(tfs.commonPrefix, key, value, isNegative, isRegexp); err != nil {
|
||||
return fmt.Errorf("cannot initialize tagFilter: %s", err)
|
||||
}
|
||||
if len(tf.graphiteReverseSuffix) > 0 {
|
||||
tf = tfs.addTagFilter()
|
||||
if err := tf.Init(tfs.commonPrefix, graphiteReverseTagKey, tf.graphiteReverseSuffix, false, false); err != nil {
|
||||
return fmt.Errorf("cannot initialize reverse tag filter for Graphite wildcard: %s", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tfs *TagFilters) addTagFilter() *tagFilter {
|
||||
if cap(tfs.tfs) > len(tfs.tfs) {
|
||||
tfs.tfs = tfs.tfs[:len(tfs.tfs)+1]
|
||||
} else {
|
||||
tfs.tfs = append(tfs.tfs, tagFilter{})
|
||||
}
|
||||
tf := &tfs.tfs[len(tfs.tfs)-1]
|
||||
err := tf.Init(tfs.commonPrefix, key, value, isNegative, isRegexp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot initialize tagFilter: %s", err)
|
||||
}
|
||||
return nil
|
||||
return &tfs.tfs[len(tfs.tfs)-1]
|
||||
}
|
||||
|
||||
// Finalize finalizes tfs and may return complementary TagFilters,
|
||||
@@ -150,6 +159,10 @@ type tagFilter struct {
|
||||
//
|
||||
// Such a filter must be applied directly to metricNames.
|
||||
matchesEmptyValue bool
|
||||
|
||||
// Contains reverse suffix for Graphite wildcard.
|
||||
// I.e. for `{__name__=~"foo\\.[^.]*\\.bar\\.baz"}` the value will be `zab.rab.`
|
||||
graphiteReverseSuffix []byte
|
||||
}
|
||||
|
||||
func (tf *tagFilter) Less(other *tagFilter) bool {
|
||||
@@ -225,6 +238,7 @@ func (tf *tagFilter) Init(commonPrefix, key, value []byte, isNegative, isRegexp
|
||||
tf.orSuffixes = tf.orSuffixes[:0]
|
||||
tf.reSuffixMatch = nil
|
||||
tf.matchesEmptyValue = false
|
||||
tf.graphiteReverseSuffix = tf.graphiteReverseSuffix[:0]
|
||||
|
||||
tf.prefix = append(tf.prefix, commonPrefix...)
|
||||
tf.prefix = marshalTagValue(tf.prefix, key)
|
||||
@@ -254,6 +268,10 @@ func (tf *tagFilter) Init(commonPrefix, key, value []byte, isNegative, isRegexp
|
||||
if len(prefix) == 0 && !tf.isNegative && tf.reSuffixMatch(nil) {
|
||||
tf.matchesEmptyValue = true
|
||||
}
|
||||
if !tf.isNegative && len(key) == 0 && strings.IndexByte(rcv.literalSuffix, '.') >= 0 {
|
||||
// Reverse suffix is needed only for non-negative regexp filters on __name__ that contains dots.
|
||||
tf.graphiteReverseSuffix = reverseBytes(tf.graphiteReverseSuffix[:0], []byte(rcv.literalSuffix))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -313,6 +331,7 @@ func getRegexpFromCache(expr []byte) (regexpCacheValue, error) {
|
||||
sExpr := string(expr)
|
||||
orValues := getOrValues(sExpr)
|
||||
var reMatch func(b []byte) bool
|
||||
var literalSuffix string
|
||||
if len(orValues) > 0 {
|
||||
if len(orValues) == 1 {
|
||||
v := orValues[0]
|
||||
@@ -330,12 +349,13 @@ func getRegexpFromCache(expr []byte) (regexpCacheValue, error) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reMatch = getOptimizedReMatchFunc(re.Match, sExpr)
|
||||
reMatch, literalSuffix = getOptimizedReMatchFunc(re.Match, sExpr)
|
||||
}
|
||||
|
||||
// Put the reMatch in the cache.
|
||||
rcv.orValues = orValues
|
||||
rcv.reMatch = reMatch
|
||||
rcv.literalSuffix = literalSuffix
|
||||
|
||||
regexpCacheLock.Lock()
|
||||
if overflow := len(regexpCacheMap) - getMaxRegexpCacheSize(); overflow > 0 {
|
||||
@@ -367,31 +387,33 @@ func getRegexpFromCache(expr []byte) (regexpCacheValue, error) {
|
||||
// '.+literal.+'
|
||||
//
|
||||
// It returns reMatch if it cannot find optimized function.
|
||||
func getOptimizedReMatchFunc(reMatch func(b []byte) bool, expr string) func(b []byte) bool {
|
||||
//
|
||||
// It also returns literal suffix from the expr.
|
||||
func getOptimizedReMatchFunc(reMatch func(b []byte) bool, expr string) (func(b []byte) bool, string) {
|
||||
sre, err := syntax.Parse(expr, syntax.Perl)
|
||||
if err != nil {
|
||||
logger.Panicf("BUG: unexpected error when parsing verified expr=%q: %s", expr, err)
|
||||
}
|
||||
if matchFunc := getOptimizedReMatchFuncExt(reMatch, sre); matchFunc != nil {
|
||||
if matchFunc, literalSuffix := getOptimizedReMatchFuncExt(reMatch, sre); matchFunc != nil {
|
||||
// Found optimized function for matching the expr.
|
||||
return matchFunc
|
||||
return matchFunc, literalSuffix
|
||||
}
|
||||
// Fall back to un-optimized reMatch.
|
||||
return reMatch
|
||||
return reMatch, ""
|
||||
}
|
||||
|
||||
func getOptimizedReMatchFuncExt(reMatch func(b []byte) bool, sre *syntax.Regexp) func(b []byte) bool {
|
||||
func getOptimizedReMatchFuncExt(reMatch func(b []byte) bool, sre *syntax.Regexp) (func(b []byte) bool, string) {
|
||||
if isDotStar(sre) {
|
||||
// '.*'
|
||||
return func(b []byte) bool {
|
||||
return true
|
||||
}
|
||||
}, ""
|
||||
}
|
||||
if isDotPlus(sre) {
|
||||
// '.+'
|
||||
return func(b []byte) bool {
|
||||
return len(b) > 0
|
||||
}
|
||||
}, ""
|
||||
}
|
||||
switch sre.Op {
|
||||
case syntax.OpCapture:
|
||||
@@ -399,13 +421,13 @@ func getOptimizedReMatchFuncExt(reMatch func(b []byte) bool, sre *syntax.Regexp)
|
||||
return getOptimizedReMatchFuncExt(reMatch, sre.Sub[0])
|
||||
case syntax.OpLiteral:
|
||||
if !isLiteral(sre) {
|
||||
return nil
|
||||
return nil, ""
|
||||
}
|
||||
s := string(sre.Rune)
|
||||
// Literal match
|
||||
return func(b []byte) bool {
|
||||
return string(b) == s
|
||||
}
|
||||
}, s
|
||||
case syntax.OpConcat:
|
||||
if len(sre.Sub) == 2 {
|
||||
if isLiteral(sre.Sub[0]) {
|
||||
@@ -414,13 +436,13 @@ func getOptimizedReMatchFuncExt(reMatch func(b []byte) bool, sre *syntax.Regexp)
|
||||
// 'prefix.*'
|
||||
return func(b []byte) bool {
|
||||
return bytes.HasPrefix(b, prefix)
|
||||
}
|
||||
}, ""
|
||||
}
|
||||
if isDotPlus(sre.Sub[1]) {
|
||||
// 'prefix.+'
|
||||
return func(b []byte) bool {
|
||||
return len(b) > len(prefix) && bytes.HasPrefix(b, prefix)
|
||||
}
|
||||
}, ""
|
||||
}
|
||||
}
|
||||
if isLiteral(sre.Sub[1]) {
|
||||
@@ -429,13 +451,13 @@ func getOptimizedReMatchFuncExt(reMatch func(b []byte) bool, sre *syntax.Regexp)
|
||||
// '.*suffix'
|
||||
return func(b []byte) bool {
|
||||
return bytes.HasSuffix(b, suffix)
|
||||
}
|
||||
}, string(suffix)
|
||||
}
|
||||
if isDotPlus(sre.Sub[0]) {
|
||||
// '.+suffix'
|
||||
return func(b []byte) bool {
|
||||
return len(b) > len(suffix) && bytes.HasSuffix(b[1:], suffix)
|
||||
}
|
||||
}, string(suffix)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -446,13 +468,13 @@ func getOptimizedReMatchFuncExt(reMatch func(b []byte) bool, sre *syntax.Regexp)
|
||||
// '.*middle.*'
|
||||
return func(b []byte) bool {
|
||||
return bytes.Contains(b, middle)
|
||||
}
|
||||
}, ""
|
||||
}
|
||||
if isDotPlus(sre.Sub[2]) {
|
||||
// '.*middle.+'
|
||||
return func(b []byte) bool {
|
||||
return len(b) > len(middle) && bytes.Contains(b[:len(b)-1], middle)
|
||||
}
|
||||
}, ""
|
||||
}
|
||||
}
|
||||
if isDotPlus(sre.Sub[0]) {
|
||||
@@ -460,13 +482,13 @@ func getOptimizedReMatchFuncExt(reMatch func(b []byte) bool, sre *syntax.Regexp)
|
||||
// '.+middle.*'
|
||||
return func(b []byte) bool {
|
||||
return len(b) > len(middle) && bytes.Contains(b[1:], middle)
|
||||
}
|
||||
}, ""
|
||||
}
|
||||
if isDotPlus(sre.Sub[2]) {
|
||||
// '.+middle.+'
|
||||
return func(b []byte) bool {
|
||||
return len(b) > len(middle)+1 && bytes.Contains(b[1:len(b)-1], middle)
|
||||
}
|
||||
}, ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -479,7 +501,16 @@ func getOptimizedReMatchFuncExt(reMatch func(b []byte) bool, sre *syntax.Regexp)
|
||||
literals = append(literals, []byte(string(sub.Rune)))
|
||||
}
|
||||
}
|
||||
var suffix []byte
|
||||
if isLiteral(sre.Sub[len(sre.Sub)-1]) {
|
||||
suffix = literals[len(literals)-1]
|
||||
literals = literals[:len(literals)-1]
|
||||
}
|
||||
return func(b []byte) bool {
|
||||
if len(suffix) > 0 && !bytes.HasSuffix(b, suffix) {
|
||||
// Fast path - b has no the given suffix
|
||||
return false
|
||||
}
|
||||
bOrig := b
|
||||
for _, literal := range literals {
|
||||
n := bytes.Index(b, literal)
|
||||
@@ -491,9 +522,9 @@ func getOptimizedReMatchFuncExt(reMatch func(b []byte) bool, sre *syntax.Regexp)
|
||||
}
|
||||
// Fall back to slow path.
|
||||
return reMatch(bOrig)
|
||||
}
|
||||
}, string(suffix)
|
||||
default:
|
||||
return nil
|
||||
return nil, ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -669,8 +700,9 @@ var (
|
||||
)
|
||||
|
||||
type regexpCacheValue struct {
|
||||
orValues []string
|
||||
reMatch func(b []byte) bool
|
||||
orValues []string
|
||||
reMatch func(b []byte) bool
|
||||
literalSuffix string
|
||||
}
|
||||
|
||||
func getRegexpPrefix(b []byte) ([]byte, []byte) {
|
||||
|
||||
@@ -21,7 +21,7 @@ func TestExtractRegexpPrefix(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetRegexpFromCache(t *testing.T) {
|
||||
f := func(s string, orValuesExpected, expectedMatches, expectedMismatches []string) {
|
||||
f := func(s string, orValuesExpected, expectedMatches, expectedMismatches []string, suffixExpected string) {
|
||||
t.Helper()
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
@@ -32,6 +32,9 @@ func TestGetRegexpFromCache(t *testing.T) {
|
||||
if !reflect.DeepEqual(rcv.orValues, orValuesExpected) {
|
||||
t.Fatalf("unexpected orValues for s=%q; got %q; want %q", s, rcv.orValues, orValuesExpected)
|
||||
}
|
||||
if rcv.literalSuffix != suffixExpected {
|
||||
t.Fatalf("unexpected literal suffix for s=%q; got %q; want %q", s, rcv.literalSuffix, suffixExpected)
|
||||
}
|
||||
for _, expectedMatch := range expectedMatches {
|
||||
if !rcv.reMatch([]byte(expectedMatch)) {
|
||||
t.Fatalf("s=%q must match %q", s, expectedMatch)
|
||||
@@ -45,43 +48,45 @@ func TestGetRegexpFromCache(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
f("", []string{""}, []string{""}, []string{"foo", "x"})
|
||||
f("foo", []string{"foo"}, []string{"foo"}, []string{"", "bar"})
|
||||
f("(?s)(foo)?", nil, []string{"foo", ""}, []string{"s", "bar"})
|
||||
f("foo.*", nil, []string{"foo", "foobar"}, []string{"xfoo", "xfoobar", "", "a"})
|
||||
f("foo(a|b)?", nil, []string{"fooa", "foob", "foo"}, []string{"xfoo", "xfoobar", "", "fooc", "fooba"})
|
||||
f(".*foo", nil, []string{"foo", "xfoo"}, []string{"foox", "xfoobar", "", "a"})
|
||||
f("(a|b)?foo", nil, []string{"foo", "afoo", "bfoo"}, []string{"foox", "xfoobar", "", "a"})
|
||||
f(".*foo.*", nil, []string{"foo", "xfoo", "foox", "xfoobar"}, []string{"", "bar", "foxx"})
|
||||
f(".*foo.+", nil, []string{"foo1", "xfoodff", "foox", "xfoobar"}, []string{"", "bar", "foo", "fox"})
|
||||
f(".+foo.+", nil, []string{"xfoo1", "xfoodff", "xfoox", "xfoobar"}, []string{"", "bar", "foo", "foox", "xfoo"})
|
||||
f(".+foo.*", nil, []string{"xfoo", "xfoox", "xfoobar"}, []string{"", "bar", "foo", "fox"})
|
||||
f(".+foo(a|b)?", nil, []string{"xfoo", "xfooa", "xafoob"}, []string{"", "bar", "foo", "foob"})
|
||||
f(".*foo(a|b)?", nil, []string{"foo", "foob", "xafoo", "xfooa"}, []string{"", "bar", "fooba"})
|
||||
f("(a|b)?foo(a|b)?", nil, []string{"foo", "foob", "afoo", "afooa"}, []string{"", "bar", "fooba", "xfoo"})
|
||||
f("((.*)foo(.*))", nil, []string{"foo", "xfoo", "foox", "xfoobar"}, []string{"", "bar", "foxx"})
|
||||
f(".+foo", nil, []string{"afoo", "bbfoo"}, []string{"foo", "foobar", "afoox", ""})
|
||||
f("a|b", []string{"a", "b"}, []string{"a", "b"}, []string{"xa", "bx", "xab", ""})
|
||||
f("(a|b)", []string{"a", "b"}, []string{"a", "b"}, []string{"xa", "bx", "xab", ""})
|
||||
f("(a|b)foo(c|d)", []string{"afooc", "afood", "bfooc", "bfood"}, []string{"afooc", "bfood"}, []string{"foo", "", "afoo", "fooc", "xfood"})
|
||||
f("foo.+", nil, []string{"foox", "foobar"}, []string{"foo", "afoox", "afoo", ""})
|
||||
f(".*foo.*bar", nil, []string{"foobar", "xfoobar", "xfooxbar", "fooxbar"}, []string{"", "foobarx", "afoobarx", "aaa"})
|
||||
f("foo.*bar", nil, []string{"foobar", "fooxbar"}, []string{"xfoobar", "", "foobarx", "aaa"})
|
||||
f("foo.*bar.*", nil, []string{"foobar", "fooxbar", "foobarx", "fooxbarx"}, []string{"", "afoobarx", "aaa", "afoobar"})
|
||||
f(".+foo.+(b|c).+", nil, []string{"xfooxbar", "xfooxca"}, []string{"", "foo", "foob", "xfooc", "xfoodc"})
|
||||
f("", []string{""}, []string{""}, []string{"foo", "x"}, "")
|
||||
f("foo", []string{"foo"}, []string{"foo"}, []string{"", "bar"}, "")
|
||||
f("(?s)(foo)?", nil, []string{"foo", ""}, []string{"s", "bar"}, "")
|
||||
f("foo.*", nil, []string{"foo", "foobar"}, []string{"xfoo", "xfoobar", "", "a"}, "")
|
||||
f("foo(a|b)?", nil, []string{"fooa", "foob", "foo"}, []string{"xfoo", "xfoobar", "", "fooc", "fooba"}, "")
|
||||
f(".*foo", nil, []string{"foo", "xfoo"}, []string{"foox", "xfoobar", "", "a"}, "foo")
|
||||
f("(a|b)?foo", nil, []string{"foo", "afoo", "bfoo"}, []string{"foox", "xfoobar", "", "a"}, "foo")
|
||||
f(".*foo.*", nil, []string{"foo", "xfoo", "foox", "xfoobar"}, []string{"", "bar", "foxx"}, "")
|
||||
f(".*foo.+", nil, []string{"foo1", "xfoodff", "foox", "xfoobar"}, []string{"", "bar", "foo", "fox"}, "")
|
||||
f(".+foo.+", nil, []string{"xfoo1", "xfoodff", "xfoox", "xfoobar"}, []string{"", "bar", "foo", "foox", "xfoo"}, "")
|
||||
f(".+foo.*", nil, []string{"xfoo", "xfoox", "xfoobar"}, []string{"", "bar", "foo", "fox"}, "")
|
||||
f(".+foo(a|b)?", nil, []string{"xfoo", "xfooa", "xafoob"}, []string{"", "bar", "foo", "foob"}, "")
|
||||
f(".*foo(a|b)?", nil, []string{"foo", "foob", "xafoo", "xfooa"}, []string{"", "bar", "fooba"}, "")
|
||||
f("(a|b)?foo(a|b)?", nil, []string{"foo", "foob", "afoo", "afooa"}, []string{"", "bar", "fooba", "xfoo"}, "")
|
||||
f("((.*)foo(.*))", nil, []string{"foo", "xfoo", "foox", "xfoobar"}, []string{"", "bar", "foxx"}, "")
|
||||
f(".+foo", nil, []string{"afoo", "bbfoo"}, []string{"foo", "foobar", "afoox", ""}, "foo")
|
||||
f("a|b", []string{"a", "b"}, []string{"a", "b"}, []string{"xa", "bx", "xab", ""}, "")
|
||||
f("(a|b)", []string{"a", "b"}, []string{"a", "b"}, []string{"xa", "bx", "xab", ""}, "")
|
||||
f("(a|b)foo(c|d)", []string{"afooc", "afood", "bfooc", "bfood"}, []string{"afooc", "bfood"}, []string{"foo", "", "afoo", "fooc", "xfood"}, "")
|
||||
f("foo.+", nil, []string{"foox", "foobar"}, []string{"foo", "afoox", "afoo", ""}, "")
|
||||
f(".*foo.*bar", nil, []string{"foobar", "xfoobar", "xfooxbar", "fooxbar"}, []string{"", "foobarx", "afoobarx", "aaa"}, "bar")
|
||||
f("foo.*bar", nil, []string{"foobar", "fooxbar"}, []string{"xfoobar", "", "foobarx", "aaa"}, "bar")
|
||||
f("foo.*bar.*", nil, []string{"foobar", "fooxbar", "foobarx", "fooxbarx"}, []string{"", "afoobarx", "aaa", "afoobar"}, "")
|
||||
f("foo.*bar.*baz", nil, []string{"foobarbaz", "fooxbarxbaz", "foobarxbaz", "fooxbarbaz"}, []string{"", "afoobarx", "aaa", "afoobar", "foobarzaz"}, "baz")
|
||||
f(".+foo.+(b|c).+", nil, []string{"xfooxbar", "xfooxca"}, []string{"", "foo", "foob", "xfooc", "xfoodc"}, "")
|
||||
|
||||
f("(?i)foo", nil, []string{"foo", "Foo", "FOO"}, []string{"xfoo", "foobar", "xFOObar"})
|
||||
f("(?i).+foo", nil, []string{"xfoo", "aaFoo", "bArFOO"}, []string{"foosdf", "xFOObar"})
|
||||
f("(?i)(foo|bar)", nil, []string{"foo", "Foo", "BAR", "bAR"}, []string{"foobar", "xfoo", "xFOObAR"})
|
||||
f("(?i)foo.*bar", nil, []string{"foobar", "FooBAR", "FOOxxbaR"}, []string{"xfoobar", "foobarx", "xFOObarx"})
|
||||
f("(?i)foo", nil, []string{"foo", "Foo", "FOO"}, []string{"xfoo", "foobar", "xFOObar"}, "")
|
||||
f("(?i).+foo", nil, []string{"xfoo", "aaFoo", "bArFOO"}, []string{"foosdf", "xFOObar"}, "")
|
||||
f("(?i)(foo|bar)", nil, []string{"foo", "Foo", "BAR", "bAR"}, []string{"foobar", "xfoo", "xFOObAR"}, "")
|
||||
f("(?i)foo.*bar", nil, []string{"foobar", "FooBAR", "FOOxxbaR"}, []string{"xfoobar", "foobarx", "xFOObarx"}, "")
|
||||
|
||||
f(".*", nil, []string{"", "a", "foo", "foobar"}, nil)
|
||||
f("foo|.*", nil, []string{"", "a", "foo", "foobar"}, nil)
|
||||
f(".+", nil, []string{"a", "foo"}, []string{""})
|
||||
f("(.+)*(foo)?", nil, []string{"a", "foo", ""}, nil)
|
||||
f(".*", nil, []string{"", "a", "foo", "foobar"}, nil, "")
|
||||
f("foo|.*", nil, []string{"", "a", "foo", "foobar"}, nil, "")
|
||||
f(".+", nil, []string{"a", "foo"}, []string{""}, "")
|
||||
f("(.+)*(foo)?", nil, []string{"a", "foo", ""}, nil, "")
|
||||
|
||||
// Graphite-like regexps
|
||||
f(`foo\.[^.]*\.bar\.ba(xx|zz)[^.]*\.a`, nil, []string{"foo.ss.bar.baxx.a", "foo.s.bar.bazzasd.a"}, []string{"", "foo", "foo.ss.xar.baxx.a"})
|
||||
f(`foo\.[^.]*\.bar\.ba(xx|zz)[^.]*\.a`, nil, []string{"foo.ss.bar.baxx.a", "foo.s.bar.bazzasd.a"}, []string{"", "foo", "foo.ss.xar.baxx.a"}, ".a")
|
||||
f(`foo\.[^.]*?\.bar\.baz\.aaa`, nil, []string{"foo.aa.bar.baz.aaa"}, []string{"", "foo"}, ".bar.baz.aaa")
|
||||
}
|
||||
|
||||
func TestTagFilterMatchSuffix(t *testing.T) {
|
||||
|
||||
3
vendor/cloud.google.com/go/storage/CHANGES.md
generated
vendored
3
vendor/cloud.google.com/go/storage/CHANGES.md
generated
vendored
@@ -1,5 +1,8 @@
|
||||
# Changes
|
||||
|
||||
## v1.8.0
|
||||
- Add support for V4 signed post policies.
|
||||
|
||||
## v1.7.0
|
||||
- V4 signed URL support:
|
||||
- Add support for bucket-bound domains and virtual hosted style URLs.
|
||||
|
||||
15
vendor/cloud.google.com/go/storage/doc.go
generated
vendored
15
vendor/cloud.google.com/go/storage/doc.go
generated
vendored
@@ -191,6 +191,21 @@ SignedURL for details.
|
||||
}
|
||||
fmt.Println(url)
|
||||
|
||||
Post Policy V4 Signed Request
|
||||
|
||||
A type of signed request that allows uploads through HTML forms directly to Cloud Storage with
|
||||
temporary permission. Conditions can be applied to restrict how the HTML form is used and exercised
|
||||
by a user.
|
||||
|
||||
For more information, please see https://cloud.google.com/storage/docs/xml-api/post-object as well
|
||||
as the documentation of GenerateSignedPostPolicyV4.
|
||||
|
||||
pv4, err := storage.GenerateSignedPostPolicyV4(bucketName, objectName, opts)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
fmt.Printf("URL: %s\nFields; %v\n", pv4.URL, pv4.Fields)
|
||||
|
||||
Errors
|
||||
|
||||
Errors returned by this client are often of the type [`googleapi.Error`](https://godoc.org/google.golang.org/api/googleapi#Error).
|
||||
|
||||
17
vendor/cloud.google.com/go/storage/go.mod
generated
vendored
17
vendor/cloud.google.com/go/storage/go.mod
generated
vendored
@@ -3,17 +3,16 @@ module cloud.google.com/go/storage
|
||||
go 1.11
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.56.0
|
||||
cloud.google.com/go/bigquery v1.6.0 // indirect
|
||||
github.com/golang/protobuf v1.4.0
|
||||
cloud.google.com/go v0.57.0
|
||||
cloud.google.com/go/bigquery v1.7.0 // indirect
|
||||
github.com/golang/protobuf v1.4.1
|
||||
github.com/google/go-cmp v0.4.0
|
||||
github.com/googleapis/gax-go/v2 v2.0.5
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5 // indirect
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e // indirect
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d // indirect
|
||||
google.golang.org/api v0.22.0
|
||||
google.golang.org/appengine v1.6.6 // indirect
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25 // indirect
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88 // indirect
|
||||
google.golang.org/api v0.24.0
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380
|
||||
google.golang.org/grpc v1.29.1
|
||||
)
|
||||
|
||||
31
vendor/cloud.google.com/go/storage/go.sum
generated
vendored
31
vendor/cloud.google.com/go/storage/go.sum
generated
vendored
@@ -15,6 +15,8 @@ cloud.google.com/go v0.54.0 h1:3ithwDMr7/3vpAMXiH+ZQnYbuIsh+OPhUPMFC9enmn0=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0 h1:WRz29PgAsVEyPSDHyk+0fpEkwEFyfhHn+JbksT6gIL4=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0 h1:EpMNVUorLiZIELdMZbCYX/ByTFCdoYopYAGxaGVz9ms=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go/bigquery v1.0.1 h1:hL+ycaJpVE9M7nLoiXb/Pn10ENE2u+oddxbD8uu0ZVU=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0 h1:sAbMqjY1PEQKZBWfbu6Y6bsupJ9c4QdHnzg/VvYTLcE=
|
||||
@@ -23,8 +25,8 @@ cloud.google.com/go/bigquery v1.4.0 h1:xE3CPsOgttP4ACBePh79zTKALtXwn/Edhcr16R5hM
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0 h1:K2NyuHRuv15ku6eUpe0DQk5ZykPMnSOnvuVf6IHcjaE=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.6.0 h1:ajp/DjpiCHO71SyIhwb83YsUGAyWuzVvMko+9xCsJLw=
|
||||
cloud.google.com/go/bigquery v1.6.0/go.mod h1:hyFDG0qSGdHNz8Q6nDN8rYIkld0q/+5uBZaelxiDLfE=
|
||||
cloud.google.com/go/bigquery v1.7.0 h1:a/O/bK/vWrYGOTFtH8di4rBxMZnmkjy+Y5LxpDwo+dA=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/datastore v1.0.0 h1:Kt+gOPPp2LEPWp8CSfxhsM8ik9CcyE/gYu+0r+RnZvM=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ=
|
||||
@@ -87,6 +89,8 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
@@ -102,6 +106,7 @@ github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OI
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
|
||||
@@ -202,6 +207,8 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5 h1:WQ8q63x+f/zpC8Ac1s9wLElVoHhm32p6tudrU72n1QA=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f h1:QBjCr1Fz5kw158VqdE9JfI9cJnl/ymnJWAdMuinqL7Y=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
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=
|
||||
@@ -248,10 +255,10 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20u
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d h1:nc5K6ox/4lTFbMVSL9WRR81ixkcwXThoiF6yf+R9scA=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa h1:mQTN3ECqfsViCNBgq+A40vdwhkGykrrQlYe3mPj6BoU=
|
||||
golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e h1:hq86ru83GdWTlfQFZGO4nZJTU4Bs2wfHl8oFHRaXsfc=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25 h1:OKbAoGs4fGM5cPLlVQLZGYkFC8OnOfgo6tt0Smf9XhM=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
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=
|
||||
@@ -302,10 +309,10 @@ golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d h1:3K34ovZAOnVaUPxanr0j4gh
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4 h1:kDtqNkeBrZb8B+atrj50B5XLHpzXXqcCdZPP/ApQ5NY=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200409170454-77362c5149f0 h1:Vj4uPv+FWfJqeeBexROGL+6fhy0yL5JgwKU5B54Cu7Y=
|
||||
golang.org/x/tools v0.0.0-20200409170454-77362c5149f0/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d h1:lzLdP95xJmMpwQ6LUHwrc5V7js93hTiY7gkznu0BgmY=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88 h1:4j84u0sokprDu3IdSYHJMmou+YSLflMz8p7yAx/QI4g=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
@@ -326,10 +333,10 @@ google.golang.org/api v0.19.0 h1:GwFK8+l5/gdsOYKz5p6M4UK+QT8OvmHWZPJCnf+5DjA=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0 h1:jz2KixHX7EcCPiQrySzPdnYT7DbINAypCqKZ1Z7GM40=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.21.0 h1:zS+Q/CJJnVlXpXQVIz+lH0ZT2lBuT2ac7XD8Y/3w6hY=
|
||||
google.golang.org/api v0.21.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0 h1:J1Pl9P2lnmYFSJvgs70DKELqHNh8CNWXPbud4njEE2s=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0 h1:cG03eaksBzhfSIk7JRGctfp3lanklcOM/mTGvow7BbQ=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
@@ -369,10 +376,10 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672 h1:jiDSspVssiikoRP
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940 h1:MRHtG0U6SnaUb+s+LhNE1qt1FQ1wlhqr5E4usBKC0uA=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200409111301-baae70f3302d h1:I7Vuu5Ejagca+VcgfBINHke3xwjCTYnIG4Q57fv0wYY=
|
||||
google.golang.org/genproto v0.0.0-20200409111301-baae70f3302d/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84 h1:pSLkPbrjnPyLDYUO2VM9mDLqo2V6CFBY84lFSZAfoi4=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380 h1:xriR1EgvKfkKxIoU2uUvrMVl+H26359loFFUleSMXFo=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
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 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8=
|
||||
@@ -387,8 +394,6 @@ google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0 h1:bO/TA4OxCOummhSf10siHuG7vJOiwh7SpRpFZDkOgl4=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.28.1 h1:C1QC6KzgSiLyBabDi87BbjaGreoRgGUF5nOyvfrAZ1k=
|
||||
google.golang.org/grpc v1.28.1/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
@@ -397,6 +402,8 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0 h1:cJv5/xdbk1NnMPR1VP9+HU6gupuG9MLBoH1r6RHZ2MY=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
|
||||
377
vendor/cloud.google.com/go/storage/post_policy_v4.go
generated
vendored
Normal file
377
vendor/cloud.google.com/go/storage/post_policy_v4.go
generated
vendored
Normal file
@@ -0,0 +1,377 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// PostPolicyV4Options are used to construct a signed post policy.
|
||||
// Please see https://cloud.google.com/storage/docs/xml-api/post-object
|
||||
// for reference about the fields.
|
||||
type PostPolicyV4Options struct {
|
||||
// GoogleAccessID represents the authorizer of the signed URL generation.
|
||||
// It is typically the Google service account client email address from
|
||||
// the Google Developers Console in the form of "xxx@developer.gserviceaccount.com".
|
||||
// Required.
|
||||
GoogleAccessID string
|
||||
|
||||
// PrivateKey is the Google service account private key. It is obtainable
|
||||
// from the Google Developers Console.
|
||||
// At https://console.developers.google.com/project/<your-project-id>/apiui/credential,
|
||||
// create a service account client ID or reuse one of your existing service account
|
||||
// credentials. Click on the "Generate new P12 key" to generate and download
|
||||
// a new private key. Once you download the P12 file, use the following command
|
||||
// to convert it into a PEM file.
|
||||
//
|
||||
// $ openssl pkcs12 -in key.p12 -passin pass:notasecret -out key.pem -nodes
|
||||
//
|
||||
// Provide the contents of the PEM file as a byte slice.
|
||||
// Exactly one of PrivateKey or SignBytes must be non-nil.
|
||||
PrivateKey []byte
|
||||
|
||||
// SignBytes is a function for implementing custom signing. For example, if
|
||||
// your application is running on Google App Engine, you can use
|
||||
// appengine's internal signing function:
|
||||
// ctx := appengine.NewContext(request)
|
||||
// acc, _ := appengine.ServiceAccount(ctx)
|
||||
// url, err := SignedURL("bucket", "object", &SignedURLOptions{
|
||||
// GoogleAccessID: acc,
|
||||
// SignBytes: func(b []byte) ([]byte, error) {
|
||||
// _, signedBytes, err := appengine.SignBytes(ctx, b)
|
||||
// return signedBytes, err
|
||||
// },
|
||||
// // etc.
|
||||
// })
|
||||
//
|
||||
// Exactly one of PrivateKey or SignBytes must be non-nil.
|
||||
SignBytes func(hashBytes []byte) (signature []byte, err error)
|
||||
|
||||
// Expires is the expiration time on the signed URL.
|
||||
// It must be a time in the future.
|
||||
// Required.
|
||||
Expires time.Time
|
||||
|
||||
// Style provides options for the type of URL to use. Options are
|
||||
// PathStyle (default), BucketBoundHostname, and VirtualHostedStyle. See
|
||||
// https://cloud.google.com/storage/docs/request-endpoints for details.
|
||||
// Optional.
|
||||
Style URLStyle
|
||||
|
||||
// Insecure when set indicates that the generated URL's scheme
|
||||
// will use "http" instead of "https" (default).
|
||||
// Optional.
|
||||
Insecure bool
|
||||
|
||||
// Fields specifies the attributes of a PostPolicyV4 request.
|
||||
// When Fields is non-nil, its attributes must match those that will
|
||||
// passed into field Conditions.
|
||||
// Optional.
|
||||
Fields *PolicyV4Fields
|
||||
|
||||
// The conditions that the uploaded file will be expected to conform to.
|
||||
// When used, the failure of an upload to satisfy a condition will result in
|
||||
// a 4XX status code, back with the message describing the problem.
|
||||
// Optional.
|
||||
Conditions []PostPolicyV4Condition
|
||||
}
|
||||
|
||||
// PolicyV4Fields describes the attributes for a PostPolicyV4 request.
|
||||
type PolicyV4Fields struct {
|
||||
// ACL specifies the access control permissions for the object.
|
||||
// Optional.
|
||||
ACL string
|
||||
// CacheControl specifies the caching directives for the object.
|
||||
// Optional.
|
||||
CacheControl string
|
||||
// ContentType specifies the media type of the object.
|
||||
// Optional.
|
||||
ContentType string
|
||||
// ContentDisposition specifies how the file will be served back to requesters.
|
||||
// Optional.
|
||||
ContentDisposition string
|
||||
// ContentEncoding specifies the decompressive transcoding that the object.
|
||||
// This field is complementary to ContentType in that the file could be
|
||||
// compressed but ContentType specifies the file's original media type.
|
||||
// Optional.
|
||||
ContentEncoding string
|
||||
// Metadata specifies custom metadata for the object.
|
||||
// If any key doesn't begin with "x-goog-meta-", an error will be returned.
|
||||
// Optional.
|
||||
Metadata map[string]string
|
||||
// StatusCodeOnSuccess when set, specifies the status code that Cloud Storage
|
||||
// will serve back on successful upload of the object.
|
||||
// Optional.
|
||||
StatusCodeOnSuccess int
|
||||
// RedirectToURLOnSuccess when set, specifies the URL that Cloud Storage
|
||||
// will serve back on successful upload of the object.
|
||||
// Optional.
|
||||
RedirectToURLOnSuccess string
|
||||
}
|
||||
|
||||
// PostPolicyV4 describes the URL and respective form fields for a generated PostPolicyV4 request.
|
||||
type PostPolicyV4 struct {
|
||||
// URL is the generated URL that the file upload will be made to.
|
||||
URL string
|
||||
// Fields specifies the generated key-values that the file uploader
|
||||
// must include in their multipart upload form.
|
||||
Fields map[string]string
|
||||
}
|
||||
|
||||
// PostPolicyV4Condition describes the constraints that the subsequent
|
||||
// object upload's multipart form fields will be expected to conform to.
|
||||
type PostPolicyV4Condition interface {
|
||||
isEmpty() bool
|
||||
json.Marshaler
|
||||
}
|
||||
|
||||
type startsWith struct {
|
||||
key, value string
|
||||
}
|
||||
|
||||
func (sw *startsWith) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal([]string{"starts-with", sw.key, sw.value})
|
||||
}
|
||||
func (sw *startsWith) isEmpty() bool {
|
||||
return sw.value == ""
|
||||
}
|
||||
|
||||
// ConditionStartsWith checks that an attributes starts with value.
|
||||
// An empty value will cause this condition to be ignored.
|
||||
func ConditionStartsWith(key, value string) PostPolicyV4Condition {
|
||||
return &startsWith{key, value}
|
||||
}
|
||||
|
||||
type contentLengthRangeCondition struct {
|
||||
start, end uint64
|
||||
}
|
||||
|
||||
func (clr *contentLengthRangeCondition) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal([]interface{}{"content-length-range", clr.start, clr.end})
|
||||
}
|
||||
func (clr *contentLengthRangeCondition) isEmpty() bool {
|
||||
return clr.start == 0 && clr.end == 0
|
||||
}
|
||||
|
||||
type singleValueCondition struct {
|
||||
name, value string
|
||||
}
|
||||
|
||||
func (svc *singleValueCondition) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]string{svc.name: svc.value})
|
||||
}
|
||||
func (svc *singleValueCondition) isEmpty() bool {
|
||||
return svc.value == ""
|
||||
}
|
||||
|
||||
// ConditionContentLengthRange constraints the limits that the
|
||||
// multipart upload's range header will be expected to be within.
|
||||
func ConditionContentLengthRange(start, end uint64) PostPolicyV4Condition {
|
||||
return &contentLengthRangeCondition{start, end}
|
||||
}
|
||||
|
||||
func conditionRedirectToURLOnSuccess(redirectURL string) PostPolicyV4Condition {
|
||||
return &singleValueCondition{"success_action_redirect", redirectURL}
|
||||
}
|
||||
|
||||
func conditionStatusCodeOnSuccess(statusCode int) PostPolicyV4Condition {
|
||||
svc := &singleValueCondition{name: "success_action_status"}
|
||||
if statusCode > 0 {
|
||||
svc.value = fmt.Sprintf("%d", statusCode)
|
||||
}
|
||||
return svc
|
||||
}
|
||||
|
||||
// GenerateSignedPostPolicyV4 generates a PostPolicyV4 value from bucket, object and opts.
|
||||
// The generated URL and fields will then allow an unauthenticated client to perform multipart uploads.
|
||||
func GenerateSignedPostPolicyV4(bucket, object string, opts *PostPolicyV4Options) (*PostPolicyV4, error) {
|
||||
if bucket == "" {
|
||||
return nil, errors.New("storage: bucket must be non-empty")
|
||||
}
|
||||
if object == "" {
|
||||
return nil, errors.New("storage: object must be non-empty")
|
||||
}
|
||||
now := utcNow()
|
||||
if err := validatePostPolicyV4Options(opts, now); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var signingFn func(hashedBytes []byte) ([]byte, error)
|
||||
switch {
|
||||
case opts.SignBytes != nil:
|
||||
signingFn = opts.SignBytes
|
||||
|
||||
case len(opts.PrivateKey) != 0:
|
||||
parsedRSAPrivKey, err := parseKey(opts.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signingFn = func(hashedBytes []byte) ([]byte, error) {
|
||||
return rsa.SignPKCS1v15(rand.Reader, parsedRSAPrivKey, crypto.SHA256, hashedBytes)
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, errors.New("storage: exactly one of PrivateKey or SignedBytes must be set")
|
||||
}
|
||||
|
||||
var descFields PolicyV4Fields
|
||||
if opts.Fields != nil {
|
||||
descFields = *opts.Fields
|
||||
}
|
||||
|
||||
if err := validateMetadata(descFields.Metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Build the policy.
|
||||
conds := make([]PostPolicyV4Condition, len(opts.Conditions))
|
||||
copy(conds, opts.Conditions)
|
||||
conds = append(conds,
|
||||
conditionRedirectToURLOnSuccess(descFields.RedirectToURLOnSuccess),
|
||||
conditionStatusCodeOnSuccess(descFields.StatusCodeOnSuccess),
|
||||
&singleValueCondition{"acl", descFields.ACL},
|
||||
&singleValueCondition{"cache-control", descFields.CacheControl},
|
||||
)
|
||||
|
||||
YYYYMMDD := now.Format(yearMonthDay)
|
||||
policyFields := map[string]string{
|
||||
"key": object,
|
||||
"x-goog-date": now.Format(iso8601),
|
||||
"x-goog-credential": opts.GoogleAccessID + "/" + YYYYMMDD + "/auto/storage/goog4_request",
|
||||
"x-goog-algorithm": "GOOG4-RSA-SHA256",
|
||||
"success_action_redirect": descFields.RedirectToURLOnSuccess,
|
||||
"acl": descFields.ACL,
|
||||
}
|
||||
for key, value := range descFields.Metadata {
|
||||
conds = append(conds, &singleValueCondition{key, value})
|
||||
policyFields[key] = value
|
||||
}
|
||||
|
||||
// Following from the order expected by the conformance test cases,
|
||||
// hence manually inserting these fields in a specific order.
|
||||
conds = append(conds,
|
||||
&singleValueCondition{"bucket", bucket},
|
||||
&singleValueCondition{"key", object},
|
||||
&singleValueCondition{"x-goog-date", now.Format(iso8601)},
|
||||
&singleValueCondition{
|
||||
name: "x-goog-credential",
|
||||
value: opts.GoogleAccessID + "/" + YYYYMMDD + "/auto/storage/goog4_request",
|
||||
},
|
||||
&singleValueCondition{"x-goog-algorithm", "GOOG4-RSA-SHA256"},
|
||||
)
|
||||
|
||||
nonEmptyConds := make([]PostPolicyV4Condition, 0, len(opts.Conditions))
|
||||
for _, cond := range conds {
|
||||
if cond == nil || !cond.isEmpty() {
|
||||
nonEmptyConds = append(nonEmptyConds, cond)
|
||||
}
|
||||
}
|
||||
condsAsJSON, err := json.Marshal(map[string]interface{}{
|
||||
"conditions": nonEmptyConds,
|
||||
"expiration": opts.Expires.Format(time.RFC3339),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("storage: PostPolicyV4 JSON serialization failed: %v", err)
|
||||
}
|
||||
|
||||
b64Policy := base64.StdEncoding.EncodeToString(condsAsJSON)
|
||||
shaSum := sha256.Sum256([]byte(b64Policy))
|
||||
signature, err := signingFn(shaSum[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
policyFields["policy"] = b64Policy
|
||||
policyFields["x-goog-signature"] = fmt.Sprintf("%x", signature)
|
||||
|
||||
// Construct the URL.
|
||||
scheme := "https"
|
||||
if opts.Insecure {
|
||||
scheme = "http"
|
||||
}
|
||||
path := opts.Style.path(bucket, "") + "/"
|
||||
u := &url.URL{
|
||||
Path: path,
|
||||
RawPath: pathEncodeV4(path),
|
||||
Host: opts.Style.host(bucket),
|
||||
Scheme: scheme,
|
||||
}
|
||||
|
||||
if descFields.StatusCodeOnSuccess > 0 {
|
||||
policyFields["success_action_status"] = fmt.Sprintf("%d", descFields.StatusCodeOnSuccess)
|
||||
}
|
||||
|
||||
// Clear out fields with blanks values.
|
||||
for key, value := range policyFields {
|
||||
if value == "" {
|
||||
delete(policyFields, key)
|
||||
}
|
||||
}
|
||||
pp4 := &PostPolicyV4{
|
||||
Fields: policyFields,
|
||||
URL: u.String(),
|
||||
}
|
||||
return pp4, nil
|
||||
}
|
||||
|
||||
// validatePostPolicyV4Options checks that:
|
||||
// * GoogleAccessID is set
|
||||
// * either but not both PrivateKey and SignBytes are set or nil, but not both
|
||||
// * Expires, the deadline is not in the past
|
||||
// * if Style is not set, it'll use PathStyle
|
||||
func validatePostPolicyV4Options(opts *PostPolicyV4Options, now time.Time) error {
|
||||
if opts == nil || opts.GoogleAccessID == "" {
|
||||
return errors.New("storage: missing required GoogleAccessID")
|
||||
}
|
||||
if privBlank, signBlank := len(opts.PrivateKey) == 0, opts.SignBytes == nil; privBlank == signBlank {
|
||||
return errors.New("storage: exactly one of PrivateKey or SignedBytes must be set")
|
||||
}
|
||||
if opts.Expires.Before(now) {
|
||||
return errors.New("storage: expecting Expires to be in the future")
|
||||
}
|
||||
if opts.Style == nil {
|
||||
opts.Style = PathStyle()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateMetadata ensures that all keys passed in have a prefix of "x-goog-meta-",
|
||||
// otherwise it will return an error.
|
||||
func validateMetadata(hdrs map[string]string) (err error) {
|
||||
if len(hdrs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
badKeys := make([]string, 0, len(hdrs))
|
||||
for key := range hdrs {
|
||||
if !strings.HasPrefix(key, "x-goog-meta-") {
|
||||
badKeys = append(badKeys, key)
|
||||
}
|
||||
}
|
||||
if len(badKeys) != 0 {
|
||||
err = errors.New("storage: expected metadata to begin with x-goog-meta-, got " + strings.Join(badKeys, ", "))
|
||||
}
|
||||
return
|
||||
}
|
||||
2
vendor/github.com/VictoriaMetrics/metrics/histogram.go
generated
vendored
2
vendor/github.com/VictoriaMetrics/metrics/histogram.go
generated
vendored
@@ -14,7 +14,7 @@ const (
|
||||
decimalMultiplier = 2
|
||||
bucketSize = 9 * decimalMultiplier
|
||||
bucketsCount = e10Max - e10Min
|
||||
decimalPrecision = 0.01 / decimalMultiplier
|
||||
decimalPrecision = 1e-12
|
||||
)
|
||||
|
||||
// Histogram is a histogram for non-negative values with automatically created buckets.
|
||||
|
||||
1
vendor/github.com/VictoriaMetrics/metricsql/aggr.go
generated
vendored
1
vendor/github.com/VictoriaMetrics/metricsql/aggr.go
generated
vendored
@@ -34,6 +34,7 @@ var aggrFuncs = map[string]bool{
|
||||
"bottomk_avg": true,
|
||||
"bottomk_median": true,
|
||||
"any": true,
|
||||
"outliersk": true,
|
||||
}
|
||||
|
||||
func isAggrFunc(s string) bool {
|
||||
|
||||
2
vendor/github.com/VictoriaMetrics/metricsql/rollup.go
generated
vendored
2
vendor/github.com/VictoriaMetrics/metricsql/rollup.go
generated
vendored
@@ -57,6 +57,8 @@ var rollupFuncs = map[string]bool{
|
||||
"aggr_over_time": true,
|
||||
"hoeffding_bound_upper": true,
|
||||
"hoeffding_bound_lower": true,
|
||||
"ascent_over_time": true,
|
||||
"descent_over_time": true,
|
||||
|
||||
// `timestamp` func has been moved here because it must work properly with offsets and samples unaligned to the current step.
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/415 for details.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user