mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-06-07 10:56:50 +03:00
Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c0a262a2e | ||
|
|
3685fc18d5 | ||
|
|
ede7ad3703 | ||
|
|
9196c085a7 | ||
|
|
3802ae9269 | ||
|
|
b0090dbd86 | ||
|
|
603a79b357 | ||
|
|
2655220c58 | ||
|
|
bf915fc0db | ||
|
|
2fc157ff7a | ||
|
|
0dc0006f34 | ||
|
|
4b688fffee | ||
|
|
1402a6b981 | ||
|
|
3308279c4e | ||
|
|
fb909cf710 | ||
|
|
c4e75f09dc | ||
|
|
fb8840ac38 | ||
|
|
9c9221d1b2 | ||
|
|
70ca018a57 | ||
|
|
4266091e4f | ||
|
|
8001d29b6e | ||
|
|
9d3f1fcbb9 | ||
|
|
ba7b3806be | ||
|
|
7fa88c6efc | ||
|
|
4da34b11f8 | ||
|
|
a18317adbc | ||
|
|
44d7fc599d | ||
|
|
dce6079379 | ||
|
|
98419c00ef | ||
|
|
ac004665b5 | ||
|
|
8c03a8c4b4 | ||
|
|
8a126c2865 | ||
|
|
380cae23a0 | ||
|
|
1272e407b2 | ||
|
|
5f33fc8e46 | ||
|
|
ec8125606d | ||
|
|
f4a38f7fb1 | ||
|
|
ab740afd0d | ||
|
|
7b5168adfb | ||
|
|
a0d480fbf3 | ||
|
|
0dfc1ace53 | ||
|
|
d3fd113a80 | ||
|
|
4f738c8a15 | ||
|
|
dd86e6130c | ||
|
|
6a27657d73 | ||
|
|
c23b66a1ad |
42
.github/workflows/main.yml
vendored
Normal file
42
.github/workflows/main.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
name: main
|
||||
on:
|
||||
- push
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: 1.12
|
||||
id: go
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v1
|
||||
- name: Dependencies
|
||||
env:
|
||||
GO111MODULE: off
|
||||
run: |
|
||||
go get -v golang.org/x/lint/golint
|
||||
go get -u github.com/kisielk/errcheck
|
||||
- name: Build
|
||||
env:
|
||||
GO111MODULE: on
|
||||
run: |
|
||||
export PATH=$PATH:$(go env GOPATH)/bin # temporary fix. See https://github.com/actions/setup-go/issues/14
|
||||
make check-all
|
||||
git diff --exit-code
|
||||
make test-full
|
||||
make test-pure
|
||||
make victoria-metrics
|
||||
make victoria-metrics-pure
|
||||
make victoria-metrics-arm
|
||||
make victoria-metrics-arm64
|
||||
GOOS=freebsd go build -mod=vendor ./app/victoria-metrics
|
||||
GOOS=darwin go build -mod=vendor ./app/victoria-metrics
|
||||
- name: Publish coverage
|
||||
uses: codecov/codecov-action@v1.0.0
|
||||
with:
|
||||
token: ${{secrets.CODECOV_TOKEN}}
|
||||
file: ./coverage.txt
|
||||
|
||||
26
.travis.yml
26
.travis.yml
@@ -1,26 +0,0 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.12.x
|
||||
|
||||
install: make
|
||||
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
|
||||
before_install:
|
||||
- GO111MODULE=off go get -v golang.org/x/lint/golint
|
||||
- GO111MODULE=off go get -u github.com/kisielk/errcheck
|
||||
|
||||
script:
|
||||
- make check-all
|
||||
- git diff --exit-code
|
||||
- make test-full
|
||||
- make test-pure
|
||||
- make victoria-metrics
|
||||
- make victoria-metrics-pure
|
||||
- make victoria-metrics-arm
|
||||
- make victoria-metrics-arm64
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
8
Makefile
8
Makefile
@@ -1,7 +1,7 @@
|
||||
PKG_PREFIX := github.com/VictoriaMetrics/VictoriaMetrics
|
||||
|
||||
BUILDINFO_TAG ?= $(shell echo $$(git describe --long --all | tr '/' '-')$$( \
|
||||
git diff-index --quiet HEAD -- || echo '-dirty-'$$(git diff-index -u HEAD | sha1sum | grep -oP '^.{8}')))
|
||||
git diff-index --quiet HEAD -- || echo '-dirty-'$$(git diff-index -u HEAD | openssl sha1 | cut -c 10-17)))
|
||||
|
||||
PKG_TAG ?= $(shell git tag -l --points-at HEAD)
|
||||
ifeq ($(PKG_TAG),)
|
||||
@@ -75,6 +75,12 @@ vendor-update:
|
||||
GO111MODULE=on go mod tidy
|
||||
GO111MODULE=on go mod vendor
|
||||
|
||||
app-local:
|
||||
CGO_ENABLED=1 GO111MODULE=on go build $(RACE) -mod=vendor -ldflags "$(GO_BUILDINFO)" -o bin/$(APP_NAME)$(RACE) $(PKG_PREFIX)/app/$(APP_NAME)
|
||||
|
||||
app-local-pure:
|
||||
CGO_ENABLED=0 GO111MODULE=on go build $(RACE) -mod=vendor -ldflags "$(GO_BUILDINFO)" -o bin/$(APP_NAME)-pure$(RACE) $(PKG_PREFIX)/app/$(APP_NAME)
|
||||
|
||||
quicktemplate-gen: install-qtc
|
||||
qtc
|
||||
|
||||
|
||||
100
README.md
100
README.md
@@ -2,7 +2,7 @@
|
||||
[](http://slack.victoriametrics.com/)
|
||||
[](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/LICENSE)
|
||||
[](https://goreportcard.com/report/github.com/VictoriaMetrics/VictoriaMetrics)
|
||||
[](https://travis-ci.org/VictoriaMetrics/VictoriaMetrics)
|
||||
[](https://github.com/VictoriaMetrics/VictoriaMetrics/actions)
|
||||
[](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics)
|
||||
|
||||
<img alt="Victoria Metrics" src="logo.png">
|
||||
@@ -21,7 +21,7 @@ Cluster version is available [here](https://github.com/VictoriaMetrics/VictoriaM
|
||||
|
||||
* Supports [Prometheus querying API](https://prometheus.io/docs/prometheus/latest/querying/api/), so it can be used as Prometheus drop-in replacement in Grafana.
|
||||
Additionally, VictoriaMetrics extends PromQL with opt-in [useful features](https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/ExtendedPromQL).
|
||||
* Global query view. Multiple Prometheus instances may write data into VictoriaMetrics. Later this data may be used in a single query.
|
||||
* Supports global query view. Multiple Prometheus instances may write data into VictoriaMetrics. Later this data may be used in a single query.
|
||||
* High performance and good scalability for both [inserts](https://medium.com/@valyala/high-cardinality-tsdb-benchmarks-victoriametrics-vs-timescaledb-vs-influxdb-13e6ee64dd6b)
|
||||
and [selects](https://medium.com/@valyala/when-size-matters-benchmarking-victoriametrics-vs-timescale-and-influxdb-6035811952d4).
|
||||
[Outperforms InfluxDB and TimescaleDB by up to 20x](https://medium.com/@valyala/measuring-vertical-scalability-for-time-series-databases-in-google-cloud-92550d78d8ae).
|
||||
@@ -38,13 +38,14 @@ Cluster version is available [here](https://github.com/VictoriaMetrics/VictoriaM
|
||||
* All the data is stored in a single directory pointed by `-storageDataPath` flag.
|
||||
* Easy backups from [instant snapshots](https://medium.com/@valyala/how-victoriametrics-makes-instant-snapshots-for-multi-terabyte-time-series-data-e1f3fb0e0282).
|
||||
* Storage is protected from corruption on unclean shutdown (i.e. hardware reset or `kill -9`) thanks to [the storage architecture](https://medium.com/@valyala/how-victoriametrics-makes-instant-snapshots-for-multi-terabyte-time-series-data-e1f3fb0e0282).
|
||||
* Supports metrics' ingestion and backfilling via the following protocols:
|
||||
* Supports metrics' ingestion and [backfilling](#backfilling) via the following protocols:
|
||||
* [Prometheus remote write API](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_write)
|
||||
* [InfluxDB line protocol](https://docs.influxdata.com/influxdb/v1.7/write_protocols/line_protocol_tutorial/)
|
||||
* [Graphite plaintext protocol](https://graphite.readthedocs.io/en/latest/feeding-carbon.html) with [tags](https://graphite.readthedocs.io/en/latest/tags.html#carbon)
|
||||
if `-graphiteListenAddr` is set.
|
||||
* [OpenTSDB put message](http://opentsdb.net/docs/build/html/api_telnet/put.html) if `-opentsdbListenAddr` is set.
|
||||
* Ideally works with big amounts of time series data from Kubernetes, IoT sensors, connected cars and industrial telemetry.
|
||||
* [HTTP OpenTSDB /api/put requests](http://opentsdb.net/docs/build/html/api_http/put.html) if `-opentsdbHTTPListenAddr` is set.
|
||||
* Ideally works with big amounts of time series data from Kubernetes, IoT sensors, connected cars, industrial telemetry and various Enterprise workloads.
|
||||
* Has open source [cluster version](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/cluster).
|
||||
|
||||
|
||||
@@ -86,6 +87,8 @@ Cluster version is available [here](https://github.com/VictoriaMetrics/VictoriaM
|
||||
- [Tuning](#tuning)
|
||||
- [Monitoring](#monitoring)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Backfilling](#backfilling)
|
||||
- [Profiling](#profiling)
|
||||
- [Roadmap](#roadmap)
|
||||
- [Contacts](#contacts)
|
||||
- [Community and contributions](#community-and-contributions)
|
||||
@@ -108,7 +111,8 @@ The following command-line flags are used the most:
|
||||
* `-retentionPeriod` - retention period in months for the data. Older data is automatically deleted.
|
||||
* `-httpListenAddr` - TCP address to listen to for http requests. By default, it listens port `8428` on all the network interfaces.
|
||||
* `-graphiteListenAddr` - TCP and UDP address to listen to for Graphite data. By default, it is disabled.
|
||||
* `-opentsdbListenAddr` - TCP and UDP address to listen to for OpenTSDB data. By default, it is disabled.
|
||||
* `-opentsdbListenAddr` - TCP and UDP address to listen to for OpenTSDB data over telnet protocol. By default, it is disabled.
|
||||
* `-opentsdbHTTPListenAddr` - TCP address to listen to for HTTP OpenTSDB data over `/api/put`. By default, it is disabled.
|
||||
|
||||
Pass `-help` to see all the available flags with description and default values.
|
||||
|
||||
@@ -205,7 +209,8 @@ For instance, put the following lines into `Telegraf` config, so it sends data t
|
||||
Do not forget substituting `<victoriametrics-addr>` with the real address where VictoriaMetrics runs.
|
||||
|
||||
VictoriaMetrics maps Influx data using the following rules:
|
||||
* [`db` query arg](https://docs.influxdata.com/influxdb/v1.7/tools/api/#write-http-endpoint) is mapped into `db` label value.
|
||||
* [`db` query arg](https://docs.influxdata.com/influxdb/v1.7/tools/api/#write-http-endpoint) is mapped into `db` label value
|
||||
unless `db` tag exists in the Influx line.
|
||||
* Field names are mapped to time series names prefixed with `{measurement}{separator}` value,
|
||||
where `{separator}` equals to `_` by default. It can be changed with `-influxMeasurementFieldSeparator` command-line flag.
|
||||
See also `-influxSkipSingleField` command-line flag.
|
||||
@@ -236,7 +241,7 @@ An arbitrary number of lines delimited by '\n' may be sent in a single request.
|
||||
After that the data may be read via [/api/v1/export](#how-to-export-time-series) endpoint:
|
||||
|
||||
```
|
||||
curl -G 'http://localhost:8428/api/v1/export' --data-urlencode 'match={__name__!=""}'
|
||||
curl -G 'http://localhost:8428/api/v1/export' -d 'match={__name__!=""}'
|
||||
```
|
||||
|
||||
The `/api/v1/export` endpoint should return the following response:
|
||||
@@ -274,7 +279,7 @@ An arbitrary number of lines delimited by `\n` may be sent in one go.
|
||||
After that the data may be read via [/api/v1/export](#how-to-export-time-series) endpoint:
|
||||
|
||||
```
|
||||
curl -G 'http://localhost:8428/api/v1/export' --data-urlencode 'match={__name__!=""}'
|
||||
curl -G 'http://localhost:8428/api/v1/export' -d 'match={__name__!=""}'
|
||||
```
|
||||
|
||||
The `/api/v1/export` endpoint should return the following response:
|
||||
@@ -294,8 +299,13 @@ or via [go-graphite/carbonapi](https://github.com/go-graphite/carbonapi/blob/mas
|
||||
|
||||
### How to send data from OpenTSDB-compatible agents?
|
||||
|
||||
VictoriaMetrics supports [telnet put protocol](http://opentsdb.net/docs/build/html/api_telnet/put.html)
|
||||
and [HTTP /api/put requests](http://opentsdb.net/docs/build/html/api_http/put.html) for ingesting OpenTSDB data.
|
||||
|
||||
#### Sending data via `telnet put` protocol
|
||||
|
||||
1) Enable OpenTSDB receiver in VictoriaMetrics by setting `-opentsdbListenAddr` command line flag. For instance,
|
||||
the following command will enable OpenTSDB receiver in VictoriaMetrics on TCP and UDP port `4242`:
|
||||
the following command enables OpenTSDB receiver in VictoriaMetrics on TCP and UDP port `4242`:
|
||||
|
||||
```
|
||||
/path/to/victoria-metrics-prod -opentsdbListenAddr=:4242
|
||||
@@ -314,7 +324,7 @@ An arbitrary number of lines delimited by `\n` may be sent in one go.
|
||||
After that the data may be read via [/api/v1/export](#how-to-export-time-series) endpoint:
|
||||
|
||||
```
|
||||
curl -G 'http://localhost:8428/api/v1/export' --data-urlencode 'match={__name__!=""}'
|
||||
curl -G 'http://localhost:8428/api/v1/export' -d 'match={__name__!=""}'
|
||||
```
|
||||
|
||||
The `/api/v1/export` endpoint should return the following response:
|
||||
@@ -324,6 +334,44 @@ The `/api/v1/export` endpoint should return the following response:
|
||||
```
|
||||
|
||||
|
||||
#### Sending OpenTSDB data via HTTP `/api/put` requests
|
||||
|
||||
1) Enable HTTP server for OpenTSDB `/api/put` requests by setting `-opentsdbHTTPListenAddr` command line flag. For instance,
|
||||
the following command enables OpenTSDB HTTP server on port `4242`:
|
||||
|
||||
```
|
||||
/path/to/victoria-metrics-prod -opentsdbHTTPListenAddr=:4242
|
||||
```
|
||||
|
||||
2) Send data to the given address from OpenTSDB-compatible agents.
|
||||
|
||||
Example for writing a single data point:
|
||||
|
||||
```
|
||||
curl -H 'Content-Type: application/json' -d '{"metric":"x.y.z","value":45.34,"tags":{"t1":"v1","t2":"v2"}}' http://localhost:4242/api/put
|
||||
```
|
||||
|
||||
Example for writing multiple data points in a single request:
|
||||
|
||||
```
|
||||
curl -H 'Content-Type: application/json' -d '[{"metric":"foo","value":45.34},{"metric":"bar","value":43}]' http://localhost:4242/api/put
|
||||
```
|
||||
|
||||
After that the data may be read via [/api/v1/export](#how-to-export-time-series) endpoint:
|
||||
|
||||
```
|
||||
curl -G 'http://localhost:8428/api/v1/export' -d 'match[]=x.y.z' -d 'match[]=foo' -d 'match[]=bar'
|
||||
```
|
||||
|
||||
The `/api/v1/export` endpoint should return the following response:
|
||||
|
||||
```
|
||||
{"metric":{"__name__":"foo"},"values":[45.34],"timestamps":[1566464846000]}
|
||||
{"metric":{"__name__":"bar"},"values":[43],"timestamps":[1566464846000]}
|
||||
{"metric":{"__name__":"x.y.z","t1":"v1","t2":"v2"},"values":[45.34],"timestamps":[1566464763000]}
|
||||
```
|
||||
|
||||
|
||||
### How to build from sources
|
||||
|
||||
We recommend using either [binary releases](https://github.com/VictoriaMetrics/VictoriaMetrics/releases) or
|
||||
@@ -430,6 +478,9 @@ where `<timeseries_selector_for_delete>` may contain any [time series selector](
|
||||
for metrics to delete. After that all the time series matching the given selector are deleted. Storage space for
|
||||
the deleted time series isn't freed instantly - it is freed during subsequent merges of data files.
|
||||
|
||||
It is recommended verifying which metrics will be deleted with the call to `http://<victoria-metrics-addr>:8428/api/v1/series?match[]=<timeseries_selector_for_delete>`
|
||||
before actually deleting the metrics.
|
||||
|
||||
|
||||
### How to export time series?
|
||||
|
||||
@@ -637,6 +688,35 @@ The most interesting metrics are:
|
||||
of data loss stored in the broken parts. In the future, `vmrecover` tool will be created
|
||||
for automatic recovering from such errors.
|
||||
|
||||
|
||||
### Backfilling
|
||||
|
||||
Make sure that configured `-retentionPeriod` covers timestamps for the backfilled data.
|
||||
|
||||
It is recommended disabling query cache with `-search.disableCache` command-line flag when writing
|
||||
historical data with timestamps from the past, since the cache assumes that the data is written with
|
||||
the current timestamps. Query cache can be enabled after the backfilling is complete.
|
||||
|
||||
|
||||
### Profiling
|
||||
|
||||
VictoriaMetrics provides handlers for collecting the following [Go profiles](https://blog.golang.org/profiling-go-programs):
|
||||
|
||||
- Memory profile. It can be collected with the following command:
|
||||
```
|
||||
curl -s http://<victoria-metrics-host>:8428/debug/pprof/heap > mem.pprof
|
||||
```
|
||||
|
||||
- CPU profile. It can be collected with the following command:
|
||||
```
|
||||
curl -s http://<victoria-metrics-host>:8428/debug/pprof/profile > cpu.pprof
|
||||
```
|
||||
|
||||
The command for collecting CPU profile waits for 30 seconds before returning.
|
||||
|
||||
The collected profiles may be analyzed with [go tool pprof](https://github.com/google/pprof).
|
||||
|
||||
|
||||
## Roadmap
|
||||
|
||||
- [ ] Replication [#118](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/118)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# All these commands must run from repository root.
|
||||
|
||||
victoria-metrics:
|
||||
GO111MODULE=on go build -mod=vendor -ldflags "$(GO_BUILDINFO)" -o bin/victoria-metrics ./app/victoria-metrics
|
||||
APP_NAME=victoria-metrics $(MAKE) app-local
|
||||
|
||||
victoria-metrics-prod:
|
||||
APP_NAME=victoria-metrics $(MAKE) app-via-docker
|
||||
@@ -33,7 +33,7 @@ victoria-metrics-arm64-prod:
|
||||
APP_NAME=victoria-metrics APP_SUFFIX='-arm64' DOCKER_OPTS='--env CGO_ENABLED=0 --env GOARCH=arm64' $(MAKE) app-via-docker
|
||||
|
||||
victoria-metrics-pure:
|
||||
GO111MODULE=on CGO_ENABLED=0 go build -mod=vendor -ldflags "$(GO_BUILDINFO)" -o bin/victoria-metrics-pure ./app/victoria-metrics
|
||||
APP_NAME=victoria-metrics $(MAKE) app-local-pure
|
||||
|
||||
victoria-metrics-pure-prod:
|
||||
APP_NAME=victoria-metrics APP_SUFFIX='-pure' DOCKER_OPTS='--env CGO_ENABLED=0' $(MAKE) app-via-docker
|
||||
|
||||
30
app/vminsert/common/gzip_reader.go
Normal file
30
app/vminsert/common/gzip_reader.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// GetGzipReader returns new gzip reader from the pool.
|
||||
//
|
||||
// Return back the gzip reader when it no longer needed with PutGzipReader.
|
||||
func GetGzipReader(r io.Reader) (*gzip.Reader, error) {
|
||||
v := gzipReaderPool.Get()
|
||||
if v == nil {
|
||||
return gzip.NewReader(r)
|
||||
}
|
||||
zr := v.(*gzip.Reader)
|
||||
if err := zr.Reset(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return zr, nil
|
||||
}
|
||||
|
||||
// PutGzipReader returns back gzip reader obtained via GetGzipReader.
|
||||
func PutGzipReader(zr *gzip.Reader) {
|
||||
_ = zr.Close()
|
||||
gzipReaderPool.Put(zr)
|
||||
}
|
||||
|
||||
var gzipReaderPool sync.Pool
|
||||
@@ -2,9 +2,11 @@ package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmstorage"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
|
||||
)
|
||||
@@ -99,7 +101,10 @@ func (ctx *InsertCtx) AddLabel(name, value string) {
|
||||
// FlushBufs flushes buffered rows to the underlying storage.
|
||||
func (ctx *InsertCtx) FlushBufs() error {
|
||||
if err := vmstorage.AddRows(ctx.mrs); err != nil {
|
||||
return fmt.Errorf("cannot store metrics: %s", err)
|
||||
return &httpserver.ErrorWithStatusCode{
|
||||
Err: fmt.Errorf("cannot store metrics: %s", err),
|
||||
StatusCode: http.StatusServiceUnavailable,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,9 +3,11 @@ package concurrencylimiter
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timerpool"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
)
|
||||
@@ -53,7 +55,10 @@ func Do(f func() error) error {
|
||||
case <-t.C:
|
||||
timerpool.Put(t)
|
||||
concurrencyLimitTimeout.Inc()
|
||||
return fmt.Errorf("the server is overloaded with %d concurrent inserts; either increase -maxConcurrentInserts or reduce the load", cap(ch))
|
||||
return &httpserver.ErrorWithStatusCode{
|
||||
Err: fmt.Errorf("the server is overloaded with %d concurrent inserts; either increase -maxConcurrentInserts or reduce the load", cap(ch)),
|
||||
StatusCode: http.StatusServiceUnavailable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
"github.com/valyala/fastjson/fastfloat"
|
||||
)
|
||||
|
||||
@@ -34,13 +36,8 @@ func (rs *Rows) Reset() {
|
||||
// See https://graphite.readthedocs.io/en/latest/feeding-carbon.html#the-plaintext-protocol
|
||||
//
|
||||
// s must be unchanged until rs is in use.
|
||||
func (rs *Rows) Unmarshal(s string) error {
|
||||
var err error
|
||||
rs.Rows, rs.tagsPool, err = unmarshalRows(rs.Rows[:0], s, rs.tagsPool[:0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
func (rs *Rows) Unmarshal(s string) {
|
||||
rs.Rows, rs.tagsPool = unmarshalRows(rs.Rows[:0], s, rs.tagsPool[:0])
|
||||
}
|
||||
|
||||
// Row is a single graphite row.
|
||||
@@ -83,6 +80,9 @@ func (r *Row) unmarshal(s string, tagsPool []Tag) ([]Tag, error) {
|
||||
tags := tagsPool[tagsStart:]
|
||||
r.Tags = tags[:len(tags):len(tags)]
|
||||
}
|
||||
if len(r.Metric) == 0 {
|
||||
return tagsPool, fmt.Errorf("metric cannot be empty")
|
||||
}
|
||||
|
||||
n = strings.IndexByte(tail, ' ')
|
||||
if n < 0 {
|
||||
@@ -95,41 +95,46 @@ func (r *Row) unmarshal(s string, tagsPool []Tag) ([]Tag, error) {
|
||||
return tagsPool, nil
|
||||
}
|
||||
|
||||
func unmarshalRows(dst []Row, s string, tagsPool []Tag) ([]Row, []Tag, error) {
|
||||
func unmarshalRows(dst []Row, s string, tagsPool []Tag) ([]Row, []Tag) {
|
||||
for len(s) > 0 {
|
||||
n := strings.IndexByte(s, '\n')
|
||||
if n == 0 {
|
||||
// Skip empty line
|
||||
s = s[1:]
|
||||
continue
|
||||
}
|
||||
if cap(dst) > len(dst) {
|
||||
dst = dst[:len(dst)+1]
|
||||
} else {
|
||||
dst = append(dst, Row{})
|
||||
}
|
||||
r := &dst[len(dst)-1]
|
||||
if n < 0 {
|
||||
// The last line.
|
||||
var err error
|
||||
tagsPool, err = r.unmarshal(s, tagsPool)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("cannot unmarshal Graphite line %q: %s", s, err)
|
||||
return dst, tagsPool, err
|
||||
}
|
||||
return dst, tagsPool, nil
|
||||
}
|
||||
var err error
|
||||
tagsPool, err = r.unmarshal(s[:n], tagsPool)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("cannot unmarshal Graphite line %q: %s", s[:n], err)
|
||||
return dst, tagsPool, err
|
||||
return unmarshalRow(dst, s, tagsPool)
|
||||
}
|
||||
dst, tagsPool = unmarshalRow(dst, s[:n], tagsPool)
|
||||
s = s[n+1:]
|
||||
}
|
||||
return dst, tagsPool, nil
|
||||
return dst, tagsPool
|
||||
}
|
||||
|
||||
func unmarshalRow(dst []Row, s string, tagsPool []Tag) ([]Row, []Tag) {
|
||||
if len(s) > 0 && s[len(s)-1] == '\r' {
|
||||
s = s[:len(s)-1]
|
||||
}
|
||||
if len(s) == 0 {
|
||||
// Skip empty line
|
||||
return dst, tagsPool
|
||||
}
|
||||
|
||||
if cap(dst) > len(dst) {
|
||||
dst = dst[:len(dst)+1]
|
||||
} else {
|
||||
dst = append(dst, Row{})
|
||||
}
|
||||
r := &dst[len(dst)-1]
|
||||
var err error
|
||||
tagsPool, err = r.unmarshal(s, tagsPool)
|
||||
if err != nil {
|
||||
dst = dst[:len(dst)-1]
|
||||
logger.Errorf("cannot unmarshal Graphite line %q: %s", s, err)
|
||||
invalidLines.Inc()
|
||||
}
|
||||
return dst, tagsPool
|
||||
}
|
||||
|
||||
var invalidLines = metrics.NewCounter(`vm_rows_invalid_total{type="graphite"}`)
|
||||
|
||||
func unmarshalTags(dst []Tag, s string) ([]Tag, error) {
|
||||
for {
|
||||
if cap(dst) > len(dst) {
|
||||
@@ -145,12 +150,20 @@ func unmarshalTags(dst []Tag, s string) ([]Tag, error) {
|
||||
if err := tag.unmarshal(s); err != nil {
|
||||
return dst[:len(dst)-1], err
|
||||
}
|
||||
if len(tag.Key) == 0 || len(tag.Value) == 0 {
|
||||
// Skip empty tag
|
||||
dst = dst[:len(dst)-1]
|
||||
}
|
||||
return dst, nil
|
||||
}
|
||||
if err := tag.unmarshal(s[:n]); err != nil {
|
||||
return dst[:len(dst)-1], err
|
||||
}
|
||||
s = s[n+1:]
|
||||
if len(tag.Key) == 0 || len(tag.Value) == 0 {
|
||||
// Skip empty tag
|
||||
dst = dst[:len(dst)-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,9 +185,6 @@ func (t *Tag) unmarshal(s string) error {
|
||||
return fmt.Errorf("missing tag value for %q", s)
|
||||
}
|
||||
t.Key = s[:n]
|
||||
if len(t.Key) == 0 {
|
||||
return fmt.Errorf("tag key cannot be empty for %q", s)
|
||||
}
|
||||
t.Value = s[n+1:]
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,45 +9,42 @@ func TestRowsUnmarshalFailure(t *testing.T) {
|
||||
f := func(s string) {
|
||||
t.Helper()
|
||||
var rows Rows
|
||||
if err := rows.Unmarshal(s); err == nil {
|
||||
t.Fatalf("expecting non-nil error when parsing %q", s)
|
||||
rows.Unmarshal(s)
|
||||
if len(rows.Rows) != 0 {
|
||||
t.Fatalf("unexpected number of rows parsed; got %d; want 0", len(rows.Rows))
|
||||
}
|
||||
|
||||
// Try again
|
||||
if err := rows.Unmarshal(s); err == nil {
|
||||
t.Fatalf("expecting non-nil error when parsing %q", s)
|
||||
rows.Unmarshal(s)
|
||||
if len(rows.Rows) != 0 {
|
||||
t.Fatalf("unexpected number of rows parsed; got %d; want 0", len(rows.Rows))
|
||||
}
|
||||
}
|
||||
|
||||
// Missing metric
|
||||
f(" 123 455")
|
||||
|
||||
// Missing value
|
||||
f("aaa")
|
||||
|
||||
// Invalid multiline
|
||||
f("aaa\nbbb 123 34")
|
||||
|
||||
// missing tag
|
||||
f("aa; 12 34")
|
||||
|
||||
// missing tag value
|
||||
f("aa;bb 23 34")
|
||||
f("aa;=dsd 234 45")
|
||||
}
|
||||
|
||||
func TestRowsUnmarshalSuccess(t *testing.T) {
|
||||
f := func(s string, rowsExpected *Rows) {
|
||||
t.Helper()
|
||||
var rows Rows
|
||||
if err := rows.Unmarshal(s); err != nil {
|
||||
t.Fatalf("cannot unmarshal %q: %s", s, err)
|
||||
}
|
||||
rows.Unmarshal(s)
|
||||
if !reflect.DeepEqual(rows.Rows, rowsExpected.Rows) {
|
||||
t.Fatalf("unexpected rows;\ngot\n%+v;\nwant\n%+v", rows.Rows, rowsExpected.Rows)
|
||||
}
|
||||
|
||||
// Try unmarshaling again
|
||||
if err := rows.Unmarshal(s); err != nil {
|
||||
t.Fatalf("cannot unmarshal %q: %s", s, err)
|
||||
}
|
||||
rows.Unmarshal(s)
|
||||
if !reflect.DeepEqual(rows.Rows, rowsExpected.Rows) {
|
||||
t.Fatalf("unexpected rows;\ngot\n%+v;\nwant\n%+v", rows.Rows, rowsExpected.Rows)
|
||||
}
|
||||
@@ -60,7 +57,9 @@ func TestRowsUnmarshalSuccess(t *testing.T) {
|
||||
|
||||
// Empty line
|
||||
f("", &Rows{})
|
||||
f("\r", &Rows{})
|
||||
f("\n\n", &Rows{})
|
||||
f("\n\r\n", &Rows{})
|
||||
|
||||
// Single line
|
||||
f("foobar -123.456 789", &Rows{
|
||||
@@ -98,7 +97,8 @@ func TestRowsUnmarshalSuccess(t *testing.T) {
|
||||
Timestamp: 2,
|
||||
}},
|
||||
})
|
||||
f("foo;bar=baz;aa=;x=y 1 2", &Rows{
|
||||
// Empty tags
|
||||
f("foo;bar=baz;aa=;x=y;=z 1 2", &Rows{
|
||||
Rows: []Row{{
|
||||
Metric: "foo",
|
||||
Tags: []Tag{
|
||||
@@ -106,10 +106,6 @@ func TestRowsUnmarshalSuccess(t *testing.T) {
|
||||
Key: "bar",
|
||||
Value: "baz",
|
||||
},
|
||||
{
|
||||
Key: "aa",
|
||||
Value: "",
|
||||
},
|
||||
{
|
||||
Key: "x",
|
||||
Value: "y",
|
||||
@@ -139,4 +135,20 @@ func TestRowsUnmarshalSuccess(t *testing.T) {
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// Multi lines with invalid line
|
||||
f("foo 0.3 2\naaa\nbar.baz 0.34 43\n", &Rows{
|
||||
Rows: []Row{
|
||||
{
|
||||
Metric: "foo",
|
||||
Value: 0.3,
|
||||
Timestamp: 2,
|
||||
},
|
||||
{
|
||||
Metric: "bar.baz",
|
||||
Value: 0.34,
|
||||
Timestamp: 43,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -16,8 +16,9 @@ cpu.usage_irq 0.34432 1234556768
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var rows Rows
|
||||
for pb.Next() {
|
||||
if err := rows.Unmarshal(s); err != nil {
|
||||
panic(fmt.Errorf("cannot unmarshal %q: %s", s, err))
|
||||
rows.Unmarshal(s)
|
||||
if len(rows.Rows) != 4 {
|
||||
panic(fmt.Errorf("unexpected number of rows unmarshaled: got %d; want 4", len(rows.Rows)))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -85,11 +85,7 @@ func (ctx *pushCtx) Read(r io.Reader) bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if err := ctx.Rows.Unmarshal(bytesutil.ToUnsafeString(ctx.reqBuf)); err != nil {
|
||||
graphiteUnmarshalErrors.Inc()
|
||||
ctx.err = fmt.Errorf("cannot unmarshal graphite plaintext protocol data with size %d: %s", len(ctx.reqBuf), err)
|
||||
return false
|
||||
}
|
||||
ctx.Rows.Unmarshal(bytesutil.ToUnsafeString(ctx.reqBuf))
|
||||
|
||||
// Fill missing timestamps with the current timestamp rounded to seconds.
|
||||
currentTimestamp := time.Now().Unix()
|
||||
@@ -136,9 +132,8 @@ func (ctx *pushCtx) reset() {
|
||||
}
|
||||
|
||||
var (
|
||||
graphiteReadCalls = metrics.NewCounter(`vm_read_calls_total{name="graphite"}`)
|
||||
graphiteReadErrors = metrics.NewCounter(`vm_read_errors_total{name="graphite"}`)
|
||||
graphiteUnmarshalErrors = metrics.NewCounter(`vm_unmarshal_errors_total{name="graphite"}`)
|
||||
graphiteReadCalls = metrics.NewCounter(`vm_read_calls_total{name="graphite"}`)
|
||||
graphiteReadErrors = metrics.NewCounter(`vm_read_errors_total{name="graphite"}`)
|
||||
)
|
||||
|
||||
func getPushCtx() *pushCtx {
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
"github.com/valyala/fastjson/fastfloat"
|
||||
)
|
||||
|
||||
@@ -41,13 +43,8 @@ func (rs *Rows) Reset() {
|
||||
// See https://docs.influxdata.com/influxdb/v1.7/write_protocols/line_protocol_tutorial/
|
||||
//
|
||||
// s must be unchanged until rs is in use.
|
||||
func (rs *Rows) Unmarshal(s string) error {
|
||||
var err error
|
||||
rs.Rows, rs.tagsPool, rs.fieldsPool, err = unmarshalRows(rs.Rows[:0], s, rs.tagsPool[:0], rs.fieldsPool[:0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
func (rs *Rows) Unmarshal(s string) {
|
||||
rs.Rows, rs.tagsPool, rs.fieldsPool = unmarshalRows(rs.Rows[:0], s, rs.tagsPool[:0], rs.fieldsPool[:0])
|
||||
}
|
||||
|
||||
// Row is a single influx row.
|
||||
@@ -65,9 +62,8 @@ func (r *Row) reset() {
|
||||
r.Timestamp = 0
|
||||
}
|
||||
|
||||
func (r *Row) unmarshal(s string, tagsPool []Tag, fieldsPool []Field) ([]Tag, []Field, error) {
|
||||
func (r *Row) unmarshal(s string, tagsPool []Tag, fieldsPool []Field, noEscapeChars bool) ([]Tag, []Field, error) {
|
||||
r.reset()
|
||||
noEscapeChars := strings.IndexByte(s, '\\') < 0
|
||||
n := nextUnescapedChar(s, ' ', noEscapeChars)
|
||||
if n < 0 {
|
||||
return tagsPool, fieldsPool, fmt.Errorf("cannot find Whitespace I in %q", s)
|
||||
@@ -141,9 +137,6 @@ func (tag *Tag) unmarshal(s string, noEscapeChars bool) error {
|
||||
return fmt.Errorf("missing tag value for %q", s)
|
||||
}
|
||||
tag.Key = unescapeTagValue(s[:n], noEscapeChars)
|
||||
if len(tag.Key) == 0 {
|
||||
return fmt.Errorf("tag key cannot be empty")
|
||||
}
|
||||
tag.Value = unescapeTagValue(s[n+1:], noEscapeChars)
|
||||
return nil
|
||||
}
|
||||
@@ -177,41 +170,51 @@ func (f *Field) unmarshal(s string, noEscapeChars, hasQuotedFields bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshalRows(dst []Row, s string, tagsPool []Tag, fieldsPool []Field) ([]Row, []Tag, []Field, error) {
|
||||
func unmarshalRows(dst []Row, s string, tagsPool []Tag, fieldsPool []Field) ([]Row, []Tag, []Field) {
|
||||
noEscapeChars := strings.IndexByte(s, '\\') < 0
|
||||
for len(s) > 0 {
|
||||
n := strings.IndexByte(s, '\n')
|
||||
if n == 0 {
|
||||
// Skip empty line
|
||||
s = s[1:]
|
||||
continue
|
||||
}
|
||||
if cap(dst) > len(dst) {
|
||||
dst = dst[:len(dst)+1]
|
||||
} else {
|
||||
dst = append(dst, Row{})
|
||||
}
|
||||
r := &dst[len(dst)-1]
|
||||
if n < 0 {
|
||||
// The last line.
|
||||
var err error
|
||||
tagsPool, fieldsPool, err = r.unmarshal(s, tagsPool, fieldsPool)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("cannot unmarshal Influx line %q: %s", s, err)
|
||||
return dst, tagsPool, fieldsPool, err
|
||||
}
|
||||
return dst, tagsPool, fieldsPool, nil
|
||||
}
|
||||
var err error
|
||||
tagsPool, fieldsPool, err = r.unmarshal(s[:n], tagsPool, fieldsPool)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("cannot unmarshal Influx line %q: %s", s[:n], err)
|
||||
return dst, tagsPool, fieldsPool, err
|
||||
return unmarshalRow(dst, s, tagsPool, fieldsPool, noEscapeChars)
|
||||
}
|
||||
dst, tagsPool, fieldsPool = unmarshalRow(dst, s[:n], tagsPool, fieldsPool, noEscapeChars)
|
||||
s = s[n+1:]
|
||||
}
|
||||
return dst, tagsPool, fieldsPool, nil
|
||||
return dst, tagsPool, fieldsPool
|
||||
}
|
||||
|
||||
func unmarshalRow(dst []Row, s string, tagsPool []Tag, fieldsPool []Field, noEscapeChars bool) ([]Row, []Tag, []Field) {
|
||||
if len(s) > 0 && s[len(s)-1] == '\r' {
|
||||
s = s[:len(s)-1]
|
||||
}
|
||||
if len(s) == 0 {
|
||||
// Skip empty line
|
||||
return dst, tagsPool, fieldsPool
|
||||
}
|
||||
if s[0] == '#' {
|
||||
// Skip comment
|
||||
return dst, tagsPool, fieldsPool
|
||||
}
|
||||
|
||||
if cap(dst) > len(dst) {
|
||||
dst = dst[:len(dst)+1]
|
||||
} else {
|
||||
dst = append(dst, Row{})
|
||||
}
|
||||
r := &dst[len(dst)-1]
|
||||
var err error
|
||||
tagsPool, fieldsPool, err = r.unmarshal(s, tagsPool, fieldsPool, noEscapeChars)
|
||||
if err != nil {
|
||||
dst = dst[:len(dst)-1]
|
||||
logger.Errorf("cannot unmarshal Influx line %q: %s; skipping it", s, err)
|
||||
invalidLines.Inc()
|
||||
}
|
||||
return dst, tagsPool, fieldsPool
|
||||
}
|
||||
|
||||
var invalidLines = metrics.NewCounter(`vm_rows_invalid_total{type="influx"}`)
|
||||
|
||||
func unmarshalTags(dst []Tag, s string, noEscapeChars bool) ([]Tag, error) {
|
||||
for {
|
||||
if cap(dst) > len(dst) {
|
||||
@@ -223,14 +226,22 @@ func unmarshalTags(dst []Tag, s string, noEscapeChars bool) ([]Tag, error) {
|
||||
n := nextUnescapedChar(s, ',', noEscapeChars)
|
||||
if n < 0 {
|
||||
if err := tag.unmarshal(s, noEscapeChars); err != nil {
|
||||
return dst, err
|
||||
return dst[:len(dst)-1], err
|
||||
}
|
||||
if len(tag.Key) == 0 || len(tag.Value) == 0 {
|
||||
// Skip empty tag
|
||||
dst = dst[:len(dst)-1]
|
||||
}
|
||||
return dst, nil
|
||||
}
|
||||
if err := tag.unmarshal(s[:n], noEscapeChars); err != nil {
|
||||
return dst, err
|
||||
return dst[:len(dst)-1], err
|
||||
}
|
||||
s = s[n+1:]
|
||||
if len(tag.Key) == 0 || len(tag.Value) == 0 {
|
||||
// Skip empty tag
|
||||
dst = dst[:len(dst)-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -74,13 +74,15 @@ func TestRowsUnmarshalFailure(t *testing.T) {
|
||||
f := func(s string) {
|
||||
t.Helper()
|
||||
var rows Rows
|
||||
if err := rows.Unmarshal(s); err == nil {
|
||||
t.Fatalf("expecting non-nil error when parsing %q", s)
|
||||
rows.Unmarshal(s)
|
||||
if len(rows.Rows) != 0 {
|
||||
t.Fatalf("expecting zero rows; got %d rows", len(rows.Rows))
|
||||
}
|
||||
|
||||
// Try again
|
||||
if err := rows.Unmarshal(s); err == nil {
|
||||
t.Fatalf("expecting non-nil error when parsing %q", s)
|
||||
rows.Unmarshal(s)
|
||||
if len(rows.Rows) != 0 {
|
||||
t.Fatalf("expecting zero rows; got %d rows", len(rows.Rows))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,12 +96,8 @@ func TestRowsUnmarshalFailure(t *testing.T) {
|
||||
// Missing tag value
|
||||
f("foo,bar")
|
||||
f("foo,bar baz")
|
||||
f("foo,bar= baz")
|
||||
f("foo,bar=123, 123")
|
||||
|
||||
// Missing tag name
|
||||
f("foo,=bar baz=234")
|
||||
|
||||
// Missing field value
|
||||
f("foo bar")
|
||||
f("foo bar=")
|
||||
@@ -122,17 +120,13 @@ func TestRowsUnmarshalSuccess(t *testing.T) {
|
||||
f := func(s string, rowsExpected *Rows) {
|
||||
t.Helper()
|
||||
var rows Rows
|
||||
if err := rows.Unmarshal(s); err != nil {
|
||||
t.Fatalf("cannot unmarshal %q: %s", s, err)
|
||||
}
|
||||
rows.Unmarshal(s)
|
||||
if !reflect.DeepEqual(rows.Rows, rowsExpected.Rows) {
|
||||
t.Fatalf("unexpected rows;\ngot\n%+v;\nwant\n%+v", rows.Rows, rowsExpected.Rows)
|
||||
}
|
||||
|
||||
// Try unmarshaling again
|
||||
if err := rows.Unmarshal(s); err != nil {
|
||||
t.Fatalf("cannot unmarshal %q: %s", s, err)
|
||||
}
|
||||
rows.Unmarshal(s)
|
||||
if !reflect.DeepEqual(rows.Rows, rowsExpected.Rows) {
|
||||
t.Fatalf("unexpected rows;\ngot\n%+v;\nwant\n%+v", rows.Rows, rowsExpected.Rows)
|
||||
}
|
||||
@@ -146,6 +140,12 @@ func TestRowsUnmarshalSuccess(t *testing.T) {
|
||||
// Empty line
|
||||
f("", &Rows{})
|
||||
f("\n\n", &Rows{})
|
||||
f("\n\r\n", &Rows{})
|
||||
|
||||
// Comment
|
||||
f("\n# foobar\n", &Rows{})
|
||||
f("#foobar baz", &Rows{})
|
||||
f("#foobar baz\n#sss", &Rows{})
|
||||
|
||||
// Minimal line without tags and timestamp
|
||||
f("foo bar=123", &Rows{
|
||||
@@ -157,6 +157,15 @@ func TestRowsUnmarshalSuccess(t *testing.T) {
|
||||
}},
|
||||
}},
|
||||
})
|
||||
f("# comment\nfoo bar=123\r\n#comment2 sdsf dsf", &Rows{
|
||||
Rows: []Row{{
|
||||
Measurement: "foo",
|
||||
Fields: []Field{{
|
||||
Key: "bar",
|
||||
Value: 123,
|
||||
}},
|
||||
}},
|
||||
})
|
||||
f("foo bar=123\n", &Rows{
|
||||
Rows: []Row{{
|
||||
Measurement: "foo",
|
||||
@@ -216,7 +225,7 @@ func TestRowsUnmarshalSuccess(t *testing.T) {
|
||||
})
|
||||
|
||||
// Line with empty tag values
|
||||
f("foo,tag1=xyz,tagN=,tag2=43as bar=123", &Rows{
|
||||
f("foo,tag1=xyz,tagN=,tag2=43as,=xxx bar=123", &Rows{
|
||||
Rows: []Row{{
|
||||
Measurement: "foo",
|
||||
Tags: []Tag{
|
||||
@@ -224,10 +233,6 @@ func TestRowsUnmarshalSuccess(t *testing.T) {
|
||||
Key: "tag1",
|
||||
Value: "xyz",
|
||||
},
|
||||
{
|
||||
Key: "tagN",
|
||||
Value: "",
|
||||
},
|
||||
{
|
||||
Key: "tag2",
|
||||
Value: "43as",
|
||||
@@ -309,11 +314,11 @@ func TestRowsUnmarshalSuccess(t *testing.T) {
|
||||
})
|
||||
|
||||
// Escape chars
|
||||
f(`fo\,bar\=baz,x\==\\a\,\=\q\ \\\a\=\,=4.34`, &Rows{
|
||||
f(`fo\,bar\=baz,x\=\b=\\a\,\=\q\ \\\a\=\,=4.34`, &Rows{
|
||||
Rows: []Row{{
|
||||
Measurement: `fo,bar=baz`,
|
||||
Tags: []Tag{{
|
||||
Key: `x=`,
|
||||
Key: `x=\b`,
|
||||
Value: `\a,=\q `,
|
||||
}},
|
||||
Fields: []Field{{
|
||||
@@ -348,6 +353,34 @@ func TestRowsUnmarshalSuccess(t *testing.T) {
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// Multiple lines with invalid line in the middle.
|
||||
f("foo,tag=xyz field=1.23 48934\n"+
|
||||
"invalid line\n"+
|
||||
"bar x=-1i\n\n", &Rows{
|
||||
Rows: []Row{
|
||||
{
|
||||
Measurement: "foo",
|
||||
Tags: []Tag{{
|
||||
Key: "tag",
|
||||
Value: "xyz",
|
||||
}},
|
||||
Fields: []Field{{
|
||||
Key: "field",
|
||||
Value: 1.23,
|
||||
}},
|
||||
Timestamp: 48934,
|
||||
},
|
||||
{
|
||||
Measurement: "bar",
|
||||
Fields: []Field{{
|
||||
Key: "x",
|
||||
Value: -1,
|
||||
}},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// No newline after the second line.
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/82
|
||||
f("foo,tag=xyz field=1.23 48934\n"+
|
||||
@@ -374,4 +407,24 @@ func TestRowsUnmarshalSuccess(t *testing.T) {
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
f("x,y=z,g=p:\\ \\ 5432\\,\\ gp\\ mon\\ [lol]\\ con10\\ cmd5\\ SELECT f=1", &Rows{
|
||||
Rows: []Row{{
|
||||
Measurement: "x",
|
||||
Tags: []Tag{
|
||||
{
|
||||
Key: "y",
|
||||
Value: "z",
|
||||
},
|
||||
{
|
||||
Key: "g",
|
||||
Value: "p: 5432, gp mon [lol] con10 cmd5 SELECT",
|
||||
},
|
||||
},
|
||||
Fields: []Field{{
|
||||
Key: "f",
|
||||
Value: 1,
|
||||
}},
|
||||
}},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,14 +6,19 @@ import (
|
||||
)
|
||||
|
||||
func BenchmarkRowsUnmarshal(b *testing.B) {
|
||||
s := `cpu usage_user=1.23,usage_system=4.34,usage_iowait=0.1112 1234556768`
|
||||
s := `cpu usage_user=1.23,usage_system=4.34,usage_iowait=0.1112 1234556768
|
||||
cpu usage_user=1.23,usage_system=4.34,usage_iowait=0.1112 123455676344
|
||||
aaa usage_user=1.23,usage_system=4.34,usage_iowait=0.1112 123455676344
|
||||
bbb usage_user=1.23,usage_system=4.34,usage_iowait=0.1112 123455676344
|
||||
`
|
||||
b.SetBytes(int64(len(s)))
|
||||
b.ReportAllocs()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var rows Rows
|
||||
for pb.Next() {
|
||||
if err := rows.Unmarshal(s); err != nil {
|
||||
panic(fmt.Errorf("cannot unmarshal %q: %s", s, err))
|
||||
rows.Unmarshal(s)
|
||||
if len(rows.Rows) != 4 {
|
||||
panic(fmt.Errorf("unexpected number of rows parsed; got %d; want 4", len(rows.Rows)))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package influx
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -41,11 +40,11 @@ func insertHandlerInternal(req *http.Request) error {
|
||||
|
||||
r := req.Body
|
||||
if req.Header.Get("Content-Encoding") == "gzip" {
|
||||
zr, err := getGzipReader(r)
|
||||
zr, err := common.GetGzipReader(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot read gzipped influx line protocol data: %s", err)
|
||||
}
|
||||
defer putGzipReader(zr)
|
||||
defer common.PutGzipReader(zr)
|
||||
r = zr
|
||||
}
|
||||
|
||||
@@ -91,11 +90,17 @@ func (ctx *pushCtx) InsertRows(db string) error {
|
||||
for i := range rows {
|
||||
r := &rows[i]
|
||||
ic.Labels = ic.Labels[:0]
|
||||
ic.AddLabel("db", db)
|
||||
hasDBLabel := false
|
||||
for j := range r.Tags {
|
||||
tag := &r.Tags[j]
|
||||
if tag.Key == "db" {
|
||||
hasDBLabel = true
|
||||
}
|
||||
ic.AddLabel(tag.Key, tag.Value)
|
||||
}
|
||||
if len(db) > 0 && !hasDBLabel {
|
||||
ic.AddLabel("db", db)
|
||||
}
|
||||
ctx.metricNameBuf = storage.MarshalMetricNameRaw(ctx.metricNameBuf[:0], ic.Labels)
|
||||
ctx.metricGroupBuf = append(ctx.metricGroupBuf[:0], r.Measurement...)
|
||||
skipFieldKey := len(r.Fields) == 1 && *skipSingleField
|
||||
@@ -120,25 +125,6 @@ func (ctx *pushCtx) InsertRows(db string) error {
|
||||
return ic.FlushBufs()
|
||||
}
|
||||
|
||||
func getGzipReader(r io.Reader) (*gzip.Reader, error) {
|
||||
v := gzipReaderPool.Get()
|
||||
if v == nil {
|
||||
return gzip.NewReader(r)
|
||||
}
|
||||
zr := v.(*gzip.Reader)
|
||||
if err := zr.Reset(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return zr, nil
|
||||
}
|
||||
|
||||
func putGzipReader(zr *gzip.Reader) {
|
||||
_ = zr.Close()
|
||||
gzipReaderPool.Put(zr)
|
||||
}
|
||||
|
||||
var gzipReaderPool sync.Pool
|
||||
|
||||
func (ctx *pushCtx) Read(r io.Reader, tsMultiplier int64) bool {
|
||||
if ctx.err != nil {
|
||||
return false
|
||||
@@ -151,11 +137,7 @@ func (ctx *pushCtx) Read(r io.Reader, tsMultiplier int64) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
if err := ctx.Rows.Unmarshal(bytesutil.ToUnsafeString(ctx.reqBuf)); err != nil {
|
||||
influxUnmarshalErrors.Inc()
|
||||
ctx.err = fmt.Errorf("cannot unmarshal influx line protocol data with size %d: %s", len(ctx.reqBuf), err)
|
||||
return false
|
||||
}
|
||||
ctx.Rows.Unmarshal(bytesutil.ToUnsafeString(ctx.reqBuf))
|
||||
|
||||
// Adjust timestamps according to tsMultiplier
|
||||
currentTs := time.Now().UnixNano() / 1e6
|
||||
@@ -184,9 +166,8 @@ func (ctx *pushCtx) Read(r io.Reader, tsMultiplier int64) bool {
|
||||
}
|
||||
|
||||
var (
|
||||
influxReadCalls = metrics.NewCounter(`vm_read_calls_total{name="influx"}`)
|
||||
influxReadErrors = metrics.NewCounter(`vm_read_errors_total{name="influx"}`)
|
||||
influxUnmarshalErrors = metrics.NewCounter(`vm_unmarshal_errors_total{name="influx"}`)
|
||||
influxReadCalls = metrics.NewCounter(`vm_read_calls_total{name="influx"}`)
|
||||
influxReadErrors = metrics.NewCounter(`vm_read_errors_total{name="influx"}`)
|
||||
)
|
||||
|
||||
type pushCtx struct {
|
||||
|
||||
@@ -10,19 +10,25 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/graphite"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/influx"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/opentsdb"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/opentsdbhttp"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/prometheus"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
)
|
||||
|
||||
var (
|
||||
graphiteListenAddr = flag.String("graphiteListenAddr", "", "TCP and UDP address to listen for Graphite plaintext data. Usually :2003 must be set. Doesn't work if empty")
|
||||
opentsdbListenAddr = flag.String("opentsdbListenAddr", "", "TCP and UDP address to listen for OpentTSDB put messages. Usually :4242 must be set. Doesn't work if empty")
|
||||
maxInsertRequestSize = flag.Int("maxInsertRequestSize", 32*1024*1024, "The maximum size of a single insert request in bytes")
|
||||
graphiteListenAddr = flag.String("graphiteListenAddr", "", "TCP and UDP address to listen for Graphite plaintext data. Usually :2003 must be set. Doesn't work if empty")
|
||||
opentsdbListenAddr = flag.String("opentsdbListenAddr", "", "TCP and UDP address to listen for OpentTSDB put messages. Usually :4242 must be set. Doesn't work if empty")
|
||||
opentsdbHTTPListenAddr = flag.String("opentsdbHTTPListenAddr", "", "TCP address to listen for OpentTSDB HTTP put requests. Usually :4242 must be set. Doesn't work if empty")
|
||||
maxInsertRequestSize = flag.Int("maxInsertRequestSize", 32*1024*1024, "The maximum size of a single insert request in bytes")
|
||||
maxLabelsPerTimeseries = flag.Int("maxLabelsPerTimeseries", 30, "The maximum number of labels accepted per time series. Superflouos labels are dropped")
|
||||
)
|
||||
|
||||
// Init initializes vminsert.
|
||||
func Init() {
|
||||
storage.SetMaxLabelsPerTimeseries(*maxLabelsPerTimeseries)
|
||||
|
||||
concurrencylimiter.Init()
|
||||
if len(*graphiteListenAddr) > 0 {
|
||||
go graphite.Serve(*graphiteListenAddr)
|
||||
@@ -30,6 +36,9 @@ func Init() {
|
||||
if len(*opentsdbListenAddr) > 0 {
|
||||
go opentsdb.Serve(*opentsdbListenAddr)
|
||||
}
|
||||
if len(*opentsdbHTTPListenAddr) > 0 {
|
||||
go opentsdbhttp.Serve(*opentsdbHTTPListenAddr, int64(*maxInsertRequestSize))
|
||||
}
|
||||
}
|
||||
|
||||
// Stop stops vminsert.
|
||||
@@ -40,6 +49,9 @@ func Stop() {
|
||||
if len(*opentsdbListenAddr) > 0 {
|
||||
opentsdb.Stop()
|
||||
}
|
||||
if len(*opentsdbHTTPListenAddr) > 0 {
|
||||
opentsdbhttp.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
// RequestHandler is a handler for Prometheus remote storage write API
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
"github.com/valyala/fastjson/fastfloat"
|
||||
)
|
||||
|
||||
@@ -34,13 +36,8 @@ func (rs *Rows) Reset() {
|
||||
// See http://opentsdb.net/docs/build/html/api_telnet/put.html
|
||||
//
|
||||
// s must be unchanged until rs is in use.
|
||||
func (rs *Rows) Unmarshal(s string) error {
|
||||
var err error
|
||||
rs.Rows, rs.tagsPool, err = unmarshalRows(rs.Rows[:0], s, rs.tagsPool[:0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
func (rs *Rows) Unmarshal(s string) {
|
||||
rs.Rows, rs.tagsPool = unmarshalRows(rs.Rows[:0], s, rs.tagsPool[:0])
|
||||
}
|
||||
|
||||
// Row is a single OpenTSDB row.
|
||||
@@ -69,6 +66,9 @@ func (r *Row) unmarshal(s string, tagsPool []Tag) ([]Tag, error) {
|
||||
return tagsPool, fmt.Errorf("cannot find whitespace between metric and timestamp in %q", s)
|
||||
}
|
||||
r.Metric = s[:n]
|
||||
if len(r.Metric) == 0 {
|
||||
return tagsPool, fmt.Errorf("metric cannot be empty")
|
||||
}
|
||||
tail := s[n+1:]
|
||||
n = strings.IndexByte(tail, ' ')
|
||||
if n < 0 {
|
||||
@@ -92,41 +92,46 @@ func (r *Row) unmarshal(s string, tagsPool []Tag) ([]Tag, error) {
|
||||
return tagsPool, nil
|
||||
}
|
||||
|
||||
func unmarshalRows(dst []Row, s string, tagsPool []Tag) ([]Row, []Tag, error) {
|
||||
func unmarshalRows(dst []Row, s string, tagsPool []Tag) ([]Row, []Tag) {
|
||||
for len(s) > 0 {
|
||||
n := strings.IndexByte(s, '\n')
|
||||
if n == 0 {
|
||||
// Skip empty line
|
||||
s = s[1:]
|
||||
continue
|
||||
}
|
||||
if cap(dst) > len(dst) {
|
||||
dst = dst[:len(dst)+1]
|
||||
} else {
|
||||
dst = append(dst, Row{})
|
||||
}
|
||||
r := &dst[len(dst)-1]
|
||||
if n < 0 {
|
||||
// The last line.
|
||||
var err error
|
||||
tagsPool, err = r.unmarshal(s, tagsPool)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("cannot unmarshal OpenTSDB line %q: %s", s, err)
|
||||
return dst, tagsPool, err
|
||||
}
|
||||
return dst, tagsPool, nil
|
||||
}
|
||||
var err error
|
||||
tagsPool, err = r.unmarshal(s[:n], tagsPool)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("cannot unmarshal OpenTSDB line %q: %s", s[:n], err)
|
||||
return dst, tagsPool, err
|
||||
return unmarshalRow(dst, s, tagsPool)
|
||||
}
|
||||
dst, tagsPool = unmarshalRow(dst, s[:n], tagsPool)
|
||||
s = s[n+1:]
|
||||
}
|
||||
return dst, tagsPool, nil
|
||||
return dst, tagsPool
|
||||
}
|
||||
|
||||
func unmarshalRow(dst []Row, s string, tagsPool []Tag) ([]Row, []Tag) {
|
||||
if len(s) > 0 && s[len(s)-1] == '\r' {
|
||||
s = s[:len(s)-1]
|
||||
}
|
||||
if len(s) == 0 {
|
||||
// Skip empty line
|
||||
return dst, tagsPool
|
||||
}
|
||||
|
||||
if cap(dst) > len(dst) {
|
||||
dst = dst[:len(dst)+1]
|
||||
} else {
|
||||
dst = append(dst, Row{})
|
||||
}
|
||||
r := &dst[len(dst)-1]
|
||||
var err error
|
||||
tagsPool, err = r.unmarshal(s, tagsPool)
|
||||
if err != nil {
|
||||
dst = dst[:len(dst)-1]
|
||||
logger.Errorf("cannot unmarshal OpenTSDB line %q: %s", s, err)
|
||||
invalidLines.Inc()
|
||||
}
|
||||
return dst, tagsPool
|
||||
}
|
||||
|
||||
var invalidLines = metrics.NewCounter(`vm_rows_invalid_total{type="opentsdb"}`)
|
||||
|
||||
func unmarshalTags(dst []Tag, s string) ([]Tag, error) {
|
||||
for {
|
||||
if cap(dst) > len(dst) {
|
||||
@@ -142,12 +147,20 @@ func unmarshalTags(dst []Tag, s string) ([]Tag, error) {
|
||||
if err := tag.unmarshal(s); err != nil {
|
||||
return dst[:len(dst)-1], err
|
||||
}
|
||||
if len(tag.Key) == 0 || len(tag.Value) == 0 {
|
||||
// Skip empty tag
|
||||
dst = dst[:len(dst)-1]
|
||||
}
|
||||
return dst, nil
|
||||
}
|
||||
if err := tag.unmarshal(s[:n]); err != nil {
|
||||
return dst[:len(dst)-1], err
|
||||
}
|
||||
s = s[n+1:]
|
||||
if len(tag.Key) == 0 || len(tag.Value) == 0 {
|
||||
// Skip empty tag
|
||||
dst = dst[:len(dst)-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,9 +182,6 @@ func (t *Tag) unmarshal(s string) error {
|
||||
return fmt.Errorf("missing tag value for %q", s)
|
||||
}
|
||||
t.Key = s[:n]
|
||||
if len(t.Key) == 0 {
|
||||
return fmt.Errorf("tag key cannot be empty for %q", s)
|
||||
}
|
||||
t.Value = s[n+1:]
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,19 +9,24 @@ func TestRowsUnmarshalFailure(t *testing.T) {
|
||||
f := func(s string) {
|
||||
t.Helper()
|
||||
var rows Rows
|
||||
if err := rows.Unmarshal(s); err == nil {
|
||||
t.Fatalf("expecting non-nil error when parsing %q", s)
|
||||
rows.Unmarshal(s)
|
||||
if len(rows.Rows) != 0 {
|
||||
t.Fatalf("unexpected number of rows parsed; got %d; want 0", len(rows.Rows))
|
||||
}
|
||||
|
||||
// Try again
|
||||
if err := rows.Unmarshal(s); err == nil {
|
||||
t.Fatalf("expecting non-nil error when parsing %q", s)
|
||||
rows.Unmarshal(s)
|
||||
if len(rows.Rows) != 0 {
|
||||
t.Fatalf("unexpected number of rows parsed; got %d; want 0", len(rows.Rows))
|
||||
}
|
||||
}
|
||||
|
||||
// Missing put prefix
|
||||
f("xx")
|
||||
|
||||
// Missing metric
|
||||
f("put 111 34")
|
||||
|
||||
// Missing timestamp
|
||||
f("put aaa")
|
||||
|
||||
@@ -42,26 +47,19 @@ func TestRowsUnmarshalFailure(t *testing.T) {
|
||||
|
||||
// Invalid tag
|
||||
f("put aaa 123 4.5 foo")
|
||||
f("put aaa 123 4.5 =")
|
||||
f("put aaa 123 4.5 =foo")
|
||||
f("put aaa 123 4.5 =foo a=b")
|
||||
}
|
||||
|
||||
func TestRowsUnmarshalSuccess(t *testing.T) {
|
||||
f := func(s string, rowsExpected *Rows) {
|
||||
t.Helper()
|
||||
var rows Rows
|
||||
if err := rows.Unmarshal(s); err != nil {
|
||||
t.Fatalf("cannot unmarshal %q: %s", s, err)
|
||||
}
|
||||
rows.Unmarshal(s)
|
||||
if !reflect.DeepEqual(rows.Rows, rowsExpected.Rows) {
|
||||
t.Fatalf("unexpected rows;\ngot\n%+v;\nwant\n%+v", rows.Rows, rowsExpected.Rows)
|
||||
}
|
||||
|
||||
// Try unmarshaling again
|
||||
if err := rows.Unmarshal(s); err != nil {
|
||||
t.Fatalf("cannot unmarshal %q: %s", s, err)
|
||||
}
|
||||
rows.Unmarshal(s)
|
||||
if !reflect.DeepEqual(rows.Rows, rowsExpected.Rows) {
|
||||
t.Fatalf("unexpected rows;\ngot\n%+v;\nwant\n%+v", rows.Rows, rowsExpected.Rows)
|
||||
}
|
||||
@@ -74,7 +72,9 @@ func TestRowsUnmarshalSuccess(t *testing.T) {
|
||||
|
||||
// Empty line
|
||||
f("", &Rows{})
|
||||
f("\r", &Rows{})
|
||||
f("\n\n", &Rows{})
|
||||
f("\n\r\n", &Rows{})
|
||||
|
||||
// Single line
|
||||
f("put foobar 789 -123.456 a=b", &Rows{
|
||||
@@ -88,17 +88,13 @@ func TestRowsUnmarshalSuccess(t *testing.T) {
|
||||
}},
|
||||
}},
|
||||
})
|
||||
// Empty tag value
|
||||
f("put foobar 789 -123.456 a= b=c", &Rows{
|
||||
// Empty tag
|
||||
f("put foobar 789 -123.456 a= b=c =d", &Rows{
|
||||
Rows: []Row{{
|
||||
Metric: "foobar",
|
||||
Value: -123.456,
|
||||
Timestamp: 789,
|
||||
Tags: []Tag{
|
||||
{
|
||||
Key: "a",
|
||||
Value: "",
|
||||
},
|
||||
{
|
||||
Key: "b",
|
||||
Value: "c",
|
||||
@@ -200,4 +196,27 @@ func TestRowsUnmarshalSuccess(t *testing.T) {
|
||||
},
|
||||
},
|
||||
})
|
||||
// Multi lines with invalid line
|
||||
f("put foo 2 0.3 a=b\naaa bbb\nput bar.baz 43 0.34 a=b\n", &Rows{
|
||||
Rows: []Row{
|
||||
{
|
||||
Metric: "foo",
|
||||
Value: 0.3,
|
||||
Timestamp: 2,
|
||||
Tags: []Tag{{
|
||||
Key: "a",
|
||||
Value: "b",
|
||||
}},
|
||||
},
|
||||
{
|
||||
Metric: "bar.baz",
|
||||
Value: 0.34,
|
||||
Timestamp: 43,
|
||||
Tags: []Tag{{
|
||||
Key: "a",
|
||||
Value: "b",
|
||||
}},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,18 +6,19 @@ import (
|
||||
)
|
||||
|
||||
func BenchmarkRowsUnmarshal(b *testing.B) {
|
||||
s := `cpu.usage_user 1234556768 1.23 a=b
|
||||
cpu.usage_system 1234556768 23.344 a=b
|
||||
cpu.usage_iowait 1234556769 3.3443 a=b
|
||||
cpu.usage_irq 1234556768 0.34432 a=b
|
||||
s := `put cpu.usage_user 1234556768 1.23 a=b
|
||||
put cpu.usage_system 1234556768 23.344 a=b
|
||||
put cpu.usage_iowait 1234556769 3.3443 a=b
|
||||
put cpu.usage_irq 1234556768 0.34432 a=b
|
||||
`
|
||||
b.SetBytes(int64(len(s)))
|
||||
b.ReportAllocs()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var rows Rows
|
||||
for pb.Next() {
|
||||
if err := rows.Unmarshal(s); err != nil {
|
||||
panic(fmt.Errorf("cannot unmarshal %q: %s", s, err))
|
||||
rows.Unmarshal(s)
|
||||
if len(rows.Rows) != 4 {
|
||||
panic(fmt.Errorf("unexpected number of parsed rows; got %d; want 4", len(rows.Rows)))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -85,15 +85,21 @@ func (ctx *pushCtx) Read(r io.Reader) bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if err := ctx.Rows.Unmarshal(bytesutil.ToUnsafeString(ctx.reqBuf)); err != nil {
|
||||
opentsdbUnmarshalErrors.Inc()
|
||||
ctx.err = fmt.Errorf("cannot unmarshal OpenTSDB put protocol data with size %d: %s", len(ctx.reqBuf), err)
|
||||
return false
|
||||
ctx.Rows.Unmarshal(bytesutil.ToUnsafeString(ctx.reqBuf))
|
||||
|
||||
// Fill in missing timestamps
|
||||
currentTimestamp := time.Now().Unix()
|
||||
rows := ctx.Rows.Rows
|
||||
for i := range rows {
|
||||
r := &rows[i]
|
||||
if r.Timestamp == 0 {
|
||||
r.Timestamp = currentTimestamp
|
||||
}
|
||||
}
|
||||
|
||||
// Convert timestamps from seconds to milliseconds
|
||||
for i := range ctx.Rows.Rows {
|
||||
ctx.Rows.Rows[i].Timestamp *= 1e3
|
||||
for i := range rows {
|
||||
rows[i].Timestamp *= 1e3
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -125,9 +131,8 @@ func (ctx *pushCtx) reset() {
|
||||
}
|
||||
|
||||
var (
|
||||
opentsdbReadCalls = metrics.NewCounter(`vm_read_calls_total{name="opentsdb"}`)
|
||||
opentsdbReadErrors = metrics.NewCounter(`vm_read_errors_total{name="opentsdb"}`)
|
||||
opentsdbUnmarshalErrors = metrics.NewCounter(`vm_unmarshal_errors_total{name="opentsdb"}`)
|
||||
opentsdbReadCalls = metrics.NewCounter(`vm_read_calls_total{name="opentsdb"}`)
|
||||
opentsdbReadErrors = metrics.NewCounter(`vm_read_errors_total{name="opentsdb"}`)
|
||||
)
|
||||
|
||||
func getPushCtx() *pushCtx {
|
||||
|
||||
198
app/vminsert/opentsdbhttp/parser.go
Normal file
198
app/vminsert/opentsdbhttp/parser.go
Normal file
@@ -0,0 +1,198 @@
|
||||
package opentsdbhttp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
"github.com/valyala/fastjson"
|
||||
"github.com/valyala/fastjson/fastfloat"
|
||||
)
|
||||
|
||||
// Rows contains parsed OpenTSDB rows.
|
||||
type Rows struct {
|
||||
Rows []Row
|
||||
|
||||
tagsPool []Tag
|
||||
}
|
||||
|
||||
// Reset resets rs.
|
||||
func (rs *Rows) Reset() {
|
||||
// Release references to objects, so they can be GC'ed.
|
||||
for i := range rs.Rows {
|
||||
rs.Rows[i].reset()
|
||||
}
|
||||
rs.Rows = rs.Rows[:0]
|
||||
|
||||
for i := range rs.tagsPool {
|
||||
rs.tagsPool[i].reset()
|
||||
}
|
||||
rs.tagsPool = rs.tagsPool[:0]
|
||||
}
|
||||
|
||||
// Unmarshal unmarshals OpenTSDB rows from av.
|
||||
//
|
||||
// See http://opentsdb.net/docs/build/html/api_http/put.html
|
||||
//
|
||||
// s must be unchanged until rs is in use.
|
||||
func (rs *Rows) Unmarshal(av *fastjson.Value) {
|
||||
rs.Rows, rs.tagsPool = unmarshalRows(rs.Rows[:0], av, rs.tagsPool[:0])
|
||||
}
|
||||
|
||||
// Row is a single OpenTSDB row.
|
||||
type Row struct {
|
||||
Metric string
|
||||
Tags []Tag
|
||||
Value float64
|
||||
Timestamp int64
|
||||
}
|
||||
|
||||
func (r *Row) reset() {
|
||||
r.Metric = ""
|
||||
r.Tags = nil
|
||||
r.Value = 0
|
||||
r.Timestamp = 0
|
||||
}
|
||||
|
||||
func (r *Row) unmarshal(o *fastjson.Value, tagsPool []Tag) ([]Tag, error) {
|
||||
r.reset()
|
||||
m := o.GetStringBytes("metric")
|
||||
if len(m) == 0 {
|
||||
return tagsPool, fmt.Errorf("missing `metric` in %s", o)
|
||||
}
|
||||
r.Metric = bytesutil.ToUnsafeString(m)
|
||||
|
||||
rawTs := o.Get("timestamp")
|
||||
if rawTs != nil {
|
||||
ts, err := getFloat64(rawTs)
|
||||
if err != nil {
|
||||
return tagsPool, fmt.Errorf("invalid `timestamp` in %s: %s", o, err)
|
||||
}
|
||||
r.Timestamp = int64(ts)
|
||||
} else {
|
||||
// Allow missing timestamp. It is automatically populated
|
||||
// with the current time in this case.
|
||||
r.Timestamp = 0
|
||||
}
|
||||
|
||||
rawV := o.Get("value")
|
||||
if rawV == nil {
|
||||
return tagsPool, fmt.Errorf("missing `value` in %s", o)
|
||||
}
|
||||
v, err := getFloat64(rawV)
|
||||
if err != nil {
|
||||
return tagsPool, fmt.Errorf("invalid `value` in %s: %s", o, err)
|
||||
}
|
||||
r.Value = v
|
||||
|
||||
vt := o.Get("tags")
|
||||
if vt == nil {
|
||||
// Allow empty tags.
|
||||
return tagsPool, nil
|
||||
}
|
||||
rawTags, err := vt.Object()
|
||||
if err != nil {
|
||||
return tagsPool, fmt.Errorf("invalid `tags` in %s: %s", o, err)
|
||||
}
|
||||
|
||||
tagsStart := len(tagsPool)
|
||||
tagsPool, err = unmarshalTags(tagsPool, rawTags)
|
||||
if err != nil {
|
||||
return tagsPool, fmt.Errorf("cannot parse tags %s: %s", rawTags, err)
|
||||
}
|
||||
tags := tagsPool[tagsStart:]
|
||||
r.Tags = tags[:len(tags):len(tags)]
|
||||
return tagsPool, nil
|
||||
}
|
||||
|
||||
func getFloat64(v *fastjson.Value) (float64, error) {
|
||||
switch v.Type() {
|
||||
case fastjson.TypeNumber:
|
||||
return v.Float64()
|
||||
case fastjson.TypeString:
|
||||
vStr, _ := v.StringBytes()
|
||||
vFloat := fastfloat.ParseBestEffort(bytesutil.ToUnsafeString(vStr))
|
||||
if vFloat == 0 && string(vStr) != "0" && string(vStr) != "0.0" {
|
||||
return 0, fmt.Errorf("invalid float64 value: %q", vStr)
|
||||
}
|
||||
return vFloat, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("value doesn't contain float64; it contains %s", v.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshalRows(dst []Row, av *fastjson.Value, tagsPool []Tag) ([]Row, []Tag) {
|
||||
switch av.Type() {
|
||||
case fastjson.TypeObject:
|
||||
return unmarshalRow(dst, av, tagsPool)
|
||||
case fastjson.TypeArray:
|
||||
a, _ := av.Array()
|
||||
for _, o := range a {
|
||||
dst, tagsPool = unmarshalRow(dst, o, tagsPool)
|
||||
}
|
||||
return dst, tagsPool
|
||||
default:
|
||||
logger.Errorf("OpenTSDB JSON must be either object or array; got %s; body=%s", av.Type(), av)
|
||||
invalidLines.Inc()
|
||||
return dst, tagsPool
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshalRow(dst []Row, o *fastjson.Value, tagsPool []Tag) ([]Row, []Tag) {
|
||||
if cap(dst) > len(dst) {
|
||||
dst = dst[:len(dst)+1]
|
||||
} else {
|
||||
dst = append(dst, Row{})
|
||||
}
|
||||
r := &dst[len(dst)-1]
|
||||
var err error
|
||||
tagsPool, err = r.unmarshal(o, tagsPool)
|
||||
if err != nil {
|
||||
dst = dst[:len(dst)-1]
|
||||
logger.Errorf("cannot unmarshal OpenTSDB object %s: %s", o, err)
|
||||
invalidLines.Inc()
|
||||
}
|
||||
return dst, tagsPool
|
||||
}
|
||||
|
||||
var invalidLines = metrics.NewCounter(`vm_rows_invalid_total{type="opentsdb-http"}`)
|
||||
|
||||
func unmarshalTags(dst []Tag, o *fastjson.Object) ([]Tag, error) {
|
||||
var err error
|
||||
o.Visit(func(k []byte, v *fastjson.Value) {
|
||||
if v.Type() != fastjson.TypeString {
|
||||
err = fmt.Errorf("tag value must be string; got %s; value=%s", v.Type(), v)
|
||||
return
|
||||
}
|
||||
if len(k) == 0 {
|
||||
// Skip empty tags
|
||||
return
|
||||
}
|
||||
vStr, _ := v.StringBytes()
|
||||
if len(vStr) == 0 {
|
||||
// Skip empty tags
|
||||
return
|
||||
}
|
||||
if cap(dst) > len(dst) {
|
||||
dst = dst[:len(dst)+1]
|
||||
} else {
|
||||
dst = append(dst, Tag{})
|
||||
}
|
||||
tag := &dst[len(dst)-1]
|
||||
tag.Key = bytesutil.ToUnsafeString(k)
|
||||
tag.Value = bytesutil.ToUnsafeString(vStr)
|
||||
})
|
||||
return dst, err
|
||||
}
|
||||
|
||||
// Tag is an OpenTSDB tag.
|
||||
type Tag struct {
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
func (t *Tag) reset() {
|
||||
t.Key = ""
|
||||
t.Value = ""
|
||||
}
|
||||
246
app/vminsert/opentsdbhttp/parser_test.go
Normal file
246
app/vminsert/opentsdbhttp/parser_test.go
Normal file
@@ -0,0 +1,246 @@
|
||||
package opentsdbhttp
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRowsUnmarshalFailure(t *testing.T) {
|
||||
f := func(s string) {
|
||||
t.Helper()
|
||||
var rows Rows
|
||||
p := parserPool.Get()
|
||||
defer parserPool.Put(p)
|
||||
v, err := p.Parse(s)
|
||||
if err != nil {
|
||||
// Expected JSON parser error
|
||||
return
|
||||
}
|
||||
// Verify OpenTSDB body parsing error
|
||||
rows.Unmarshal(v)
|
||||
if len(rows.Rows) != 0 {
|
||||
t.Fatalf("unexpected number of rows parsed; got %d; want 0", len(rows.Rows))
|
||||
}
|
||||
// Try again
|
||||
rows.Unmarshal(v)
|
||||
if len(rows.Rows) != 0 {
|
||||
t.Fatalf("unexpected number of rows parsed; got %d; want 0", len(rows.Rows))
|
||||
}
|
||||
}
|
||||
|
||||
// invalid json
|
||||
f("{g")
|
||||
|
||||
// Invalid json type
|
||||
f(`1`)
|
||||
f(`"foo"`)
|
||||
f(`[1,2]`)
|
||||
f(`null`)
|
||||
|
||||
// Incomplete object
|
||||
f(`{}`)
|
||||
f(`{"metric": "aaa"}`)
|
||||
f(`{"metric": "aaa", "timestamp": 1122}`)
|
||||
f(`{"metric": "aaa", "timestamp": "tststs"}`)
|
||||
f(`{"timestamp": 1122, "value": 33}`)
|
||||
f(`{"value": 33}`)
|
||||
f(`{"value": 33, "tags": {"fooo":"bar"}}`)
|
||||
|
||||
// Invalid value
|
||||
f(`{"metric": "aaa", "timestamp": 1122, "value": "0.0.0"}`)
|
||||
|
||||
// Invalid metric type
|
||||
f(`{"metric": "", "timestamp": 1122, "value": 0.45, "tags": {"foo": "bar"}}`)
|
||||
f(`{"metric": ["aaa"], "timestamp": 1122, "value": 0.45, "tags": {"foo": "bar"}}`)
|
||||
f(`{"metric": {"aaa":1}, "timestamp": 1122, "value": 0.45, "tags": {"foo": "bar"}}`)
|
||||
f(`{"metric": 1, "timestamp": 1122, "value": 0.45, "tags": {"foo": "bar"}}`)
|
||||
|
||||
// Invalid timestamp type
|
||||
f(`{"metric": "aaa", "timestamp": "foobar", "value": 0.45, "tags": {"foo": "bar"}}`)
|
||||
f(`{"metric": "aaa", "timestamp": [1,2], "value": 0.45, "tags": {"foo": "bar"}}`)
|
||||
f(`{"metric": "aaa", "timestamp": {"a":1}, "value": 0.45, "tags": {"foo": "bar"}}`)
|
||||
|
||||
// Invalid value type
|
||||
f(`{"metric": "aaa", "timestamp": 1122, "value": [0,1], "tags": {"foo":"bar"}}`)
|
||||
f(`{"metric": "aaa", "timestamp": 1122, "value": {"a":1}, "tags": {"foo":"bar"}}`)
|
||||
f(`{"metric": "aaa", "timestamp": 1122, "value": "foobar", "tags": {"foo":"bar"}}`)
|
||||
|
||||
// Invalid tags type
|
||||
f(`{"metric": "aaa", "timestamp": 1122, "value": 0.45, "tags": 1}`)
|
||||
f(`{"metric": "aaa", "timestamp": 1122, "value": 0.45, "tags": [1,2]}`)
|
||||
f(`{"metric": "aaa", "timestamp": 1122, "value": 0.45, "tags": "foo"}`)
|
||||
|
||||
// Invalid tag value type
|
||||
f(`{"metric": "aaa", "timestamp": 1122, "value": 0.45, "tags": {"foo": ["bar"]}}`)
|
||||
f(`{"metric": "aaa", "timestamp": 1122, "value": 0.45, "tags": {"foo": {"bar":"baz"}}}`)
|
||||
f(`{"metric": "aaa", "timestamp": 1122, "value": 0.45, "tags": {"foo": 1}}`)
|
||||
|
||||
// Invalid multiline
|
||||
f(`[{"metric": "aaa", "timestamp": 1122, "value": "trt", "tags":{"foo":"bar"}}, {"metric": "aaa", "timestamp": [1122], "value": 111}]`)
|
||||
}
|
||||
|
||||
func TestRowsUnmarshalSuccess(t *testing.T) {
|
||||
f := func(s string, rowsExpected *Rows) {
|
||||
t.Helper()
|
||||
var rows Rows
|
||||
|
||||
p := parserPool.Get()
|
||||
defer parserPool.Put(p)
|
||||
v, err := p.Parse(s)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot parse json %s: %s", s, err)
|
||||
}
|
||||
rows.Unmarshal(v)
|
||||
if !reflect.DeepEqual(rows.Rows, rowsExpected.Rows) {
|
||||
t.Fatalf("unexpected rows;\ngot\n%+v;\nwant\n%+v", rows.Rows, rowsExpected.Rows)
|
||||
}
|
||||
|
||||
// Try unmarshaling again
|
||||
rows.Unmarshal(v)
|
||||
if !reflect.DeepEqual(rows.Rows, rowsExpected.Rows) {
|
||||
t.Fatalf("unexpected rows;\ngot\n%+v;\nwant\n%+v", rows.Rows, rowsExpected.Rows)
|
||||
}
|
||||
|
||||
rows.Reset()
|
||||
if len(rows.Rows) != 0 {
|
||||
t.Fatalf("non-empty rows after reset: %+v", rows.Rows)
|
||||
}
|
||||
}
|
||||
|
||||
// Normal line
|
||||
f(`{"metric": "foobar", "timestamp": 789, "value": -123.456, "tags": {"a":"b"}}`, &Rows{
|
||||
Rows: []Row{{
|
||||
Metric: "foobar",
|
||||
Value: -123.456,
|
||||
Timestamp: 789,
|
||||
Tags: []Tag{{
|
||||
Key: "a",
|
||||
Value: "b",
|
||||
}},
|
||||
}},
|
||||
})
|
||||
// Timestamp as string
|
||||
f(`{"metric": "foobar", "timestamp": "1789", "value": -123.456, "tags": {"a":"b"}}`, &Rows{
|
||||
Rows: []Row{{
|
||||
Metric: "foobar",
|
||||
Value: -123.456,
|
||||
Timestamp: 1789,
|
||||
Tags: []Tag{{
|
||||
Key: "a",
|
||||
Value: "b",
|
||||
}},
|
||||
}},
|
||||
})
|
||||
// Timestamp as float64 (it is truncated to integer)
|
||||
f(`{"metric": "foobar", "timestamp": 17.89, "value": -123.456, "tags": {"a":"b"}}`, &Rows{
|
||||
Rows: []Row{{
|
||||
Metric: "foobar",
|
||||
Value: -123.456,
|
||||
Timestamp: 17,
|
||||
Tags: []Tag{{
|
||||
Key: "a",
|
||||
Value: "b",
|
||||
}},
|
||||
}},
|
||||
})
|
||||
// Empty tags
|
||||
f(`{"metric": "foobar", "timestamp": 789, "value": -123.456, "tags": {}}`, &Rows{
|
||||
Rows: []Row{{
|
||||
Metric: "foobar",
|
||||
Value: -123.456,
|
||||
Timestamp: 789,
|
||||
Tags: nil,
|
||||
}},
|
||||
})
|
||||
// Missing tags
|
||||
f(`{"metric": "foobar", "timestamp": 789, "value": -123.456}`, &Rows{
|
||||
Rows: []Row{{
|
||||
Metric: "foobar",
|
||||
Value: -123.456,
|
||||
Timestamp: 789,
|
||||
Tags: nil,
|
||||
}},
|
||||
})
|
||||
// Empty tag value
|
||||
f(`{"metric": "foobar", "timestamp": 123, "value": -123.456, "tags": {"a":"", "b":"c", "": "d"}}`, &Rows{
|
||||
Rows: []Row{{
|
||||
Metric: "foobar",
|
||||
Value: -123.456,
|
||||
Timestamp: 123,
|
||||
Tags: []Tag{
|
||||
{
|
||||
Key: "b",
|
||||
Value: "c",
|
||||
},
|
||||
},
|
||||
}},
|
||||
})
|
||||
// Value as string
|
||||
f(`{"metric": "foobar", "timestamp": 789, "value": "-12.456", "tags": {"a":"b"}}`, &Rows{
|
||||
Rows: []Row{{
|
||||
Metric: "foobar",
|
||||
Value: -12.456,
|
||||
Timestamp: 789,
|
||||
Tags: []Tag{{
|
||||
Key: "a",
|
||||
Value: "b",
|
||||
}},
|
||||
}},
|
||||
})
|
||||
// Missing timestamp
|
||||
f(`{"metric": "foobar", "value": "-12.456", "tags": {"a":"b"}}`, &Rows{
|
||||
Rows: []Row{{
|
||||
Metric: "foobar",
|
||||
Value: -12.456,
|
||||
Timestamp: 0,
|
||||
Tags: []Tag{{
|
||||
Key: "a",
|
||||
Value: "b",
|
||||
}},
|
||||
}},
|
||||
})
|
||||
|
||||
// Multiple tags
|
||||
f(`{"metric": "foo", "value": 1, "timestamp": 2, "tags": {"bar":"baz", "x": "y"}}`, &Rows{
|
||||
Rows: []Row{{
|
||||
Metric: "foo",
|
||||
Tags: []Tag{
|
||||
{
|
||||
Key: "bar",
|
||||
Value: "baz",
|
||||
},
|
||||
{
|
||||
Key: "x",
|
||||
Value: "y",
|
||||
},
|
||||
},
|
||||
Value: 1,
|
||||
Timestamp: 2,
|
||||
}},
|
||||
})
|
||||
|
||||
// Multi lines
|
||||
f(`[{"metric": "foo", "value": "0.3", "timestamp": 2, "tags": {"a":"b"}},
|
||||
{"metric": "bar.baz", "value": 0.34, "timestamp": 43, "tags": {"a":"b"}}]`, &Rows{
|
||||
Rows: []Row{
|
||||
{
|
||||
Metric: "foo",
|
||||
Value: 0.3,
|
||||
Timestamp: 2,
|
||||
Tags: []Tag{{
|
||||
Key: "a",
|
||||
Value: "b",
|
||||
}},
|
||||
},
|
||||
{
|
||||
Metric: "bar.baz",
|
||||
Value: 0.34,
|
||||
Timestamp: 43,
|
||||
Tags: []Tag{{
|
||||
Key: "a",
|
||||
Value: "b",
|
||||
}},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
33
app/vminsert/opentsdbhttp/parser_timing_test.go
Normal file
33
app/vminsert/opentsdbhttp/parser_timing_test.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package opentsdbhttp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/valyala/fastjson"
|
||||
)
|
||||
|
||||
func BenchmarkRowsUnmarshal(b *testing.B) {
|
||||
s := `[{"metric": "cpu.usage_user", "timestamp": 1234556768, "value": 1.23, "tags": {"a":"b", "x": "y"}},
|
||||
{"metric": "cpu.usage_system", "timestamp": 1234556768, "value": 23.344, "tags": {"a":"b"}},
|
||||
{"metric": "cpu.usage_iowait", "timestamp": 1234556769, "value":3.3443, "tags": {"a":"b"}},
|
||||
{"metric": "cpu.usage_irq", "timestamp": 1234556768, "value": 0.34432, "tags": {"a":"b"}}
|
||||
]
|
||||
`
|
||||
b.SetBytes(int64(len(s)))
|
||||
b.ReportAllocs()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var rows Rows
|
||||
var p fastjson.Parser
|
||||
for pb.Next() {
|
||||
v, err := p.Parse(s)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot parse %q: %s", s, err))
|
||||
}
|
||||
rows.Unmarshal(v)
|
||||
if len(rows.Rows) != 4 {
|
||||
panic(fmt.Errorf("unexpected number of rows unmarshaled; got %d; want 4", len(rows.Rows)))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
150
app/vminsert/opentsdbhttp/request_handler.go
Normal file
150
app/vminsert/opentsdbhttp/request_handler.go
Normal file
@@ -0,0 +1,150 @@
|
||||
package opentsdbhttp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/common"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/concurrencylimiter"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
"github.com/valyala/fastjson"
|
||||
)
|
||||
|
||||
var (
|
||||
rowsInserted = metrics.NewCounter(`vm_rows_inserted_total{type="opentsdb-http"}`)
|
||||
rowsPerInsert = metrics.NewSummary(`vm_rows_per_insert{type="opentsdb-http"}`)
|
||||
|
||||
opentsdbReadCalls = metrics.NewCounter(`vm_read_calls_total{name="opentsdb-http"}`)
|
||||
opentsdbReadErrors = metrics.NewCounter(`vm_read_errors_total{name="opentsdb-http"}`)
|
||||
opentsdbUnmarshalErrors = metrics.NewCounter(`vm_unmarshal_errors_total{name="opentsdb-http"}`)
|
||||
)
|
||||
|
||||
// insertHandler processes HTTP OpenTSDB put requests.
|
||||
// See http://opentsdb.net/docs/build/html/api_http/put.html
|
||||
func insertHandler(req *http.Request, maxSize int64) error {
|
||||
return concurrencylimiter.Do(func() error {
|
||||
return insertHandlerInternal(req, maxSize)
|
||||
})
|
||||
}
|
||||
|
||||
func insertHandlerInternal(req *http.Request, maxSize int64) error {
|
||||
opentsdbReadCalls.Inc()
|
||||
|
||||
r := req.Body
|
||||
if req.Header.Get("Content-Encoding") == "gzip" {
|
||||
zr, err := common.GetGzipReader(r)
|
||||
if err != nil {
|
||||
opentsdbReadErrors.Inc()
|
||||
return fmt.Errorf("cannot read gzipped http protocol data: %s", err)
|
||||
}
|
||||
defer common.PutGzipReader(zr)
|
||||
r = zr
|
||||
}
|
||||
|
||||
ctx := getPushCtx()
|
||||
defer putPushCtx(ctx)
|
||||
|
||||
// Read the request in ctx.reqBuf
|
||||
lr := io.LimitReader(r, maxSize+1)
|
||||
reqLen, err := ctx.reqBuf.ReadFrom(lr)
|
||||
if err != nil {
|
||||
opentsdbReadErrors.Inc()
|
||||
return fmt.Errorf("cannot read HTTP OpenTSDB request: %s", err)
|
||||
}
|
||||
if reqLen > maxSize {
|
||||
opentsdbReadErrors.Inc()
|
||||
return fmt.Errorf("too big HTTP OpenTSDB request; mustn't exceed %d bytes", maxSize)
|
||||
}
|
||||
|
||||
// Unmarshal the request to ctx.Rows
|
||||
p := parserPool.Get()
|
||||
defer parserPool.Put(p)
|
||||
v, err := p.ParseBytes(ctx.reqBuf.B)
|
||||
if err != nil {
|
||||
opentsdbUnmarshalErrors.Inc()
|
||||
return fmt.Errorf("cannot parse HTTP OpenTSDB json: %s", err)
|
||||
}
|
||||
ctx.Rows.Unmarshal(v)
|
||||
|
||||
// Fill in missing timestamps
|
||||
currentTimestamp := time.Now().Unix()
|
||||
rows := ctx.Rows.Rows
|
||||
for i := range rows {
|
||||
r := &rows[i]
|
||||
if r.Timestamp == 0 {
|
||||
r.Timestamp = currentTimestamp
|
||||
}
|
||||
}
|
||||
|
||||
// Convert timestamps in seconds to milliseconds if needed.
|
||||
// See http://opentsdb.net/docs/javadoc/net/opentsdb/core/Const.html#SECOND_MASK
|
||||
for i := range rows {
|
||||
r := &rows[i]
|
||||
if r.Timestamp&secondMask == 0 {
|
||||
r.Timestamp *= 1e3
|
||||
}
|
||||
}
|
||||
|
||||
// Insert ctx.Rows to db.
|
||||
ic := &ctx.Common
|
||||
ic.Reset(len(rows))
|
||||
for i := range rows {
|
||||
r := &rows[i]
|
||||
ic.Labels = ic.Labels[:0]
|
||||
ic.AddLabel("", r.Metric)
|
||||
for j := range r.Tags {
|
||||
tag := &r.Tags[j]
|
||||
ic.AddLabel(tag.Key, tag.Value)
|
||||
}
|
||||
ic.WriteDataPoint(nil, ic.Labels, r.Timestamp, r.Value)
|
||||
}
|
||||
rowsInserted.Add(len(rows))
|
||||
rowsPerInsert.Update(float64(len(rows)))
|
||||
return ic.FlushBufs()
|
||||
}
|
||||
|
||||
const secondMask int64 = 0x7FFFFFFF00000000
|
||||
|
||||
var parserPool fastjson.ParserPool
|
||||
|
||||
type pushCtx struct {
|
||||
Rows Rows
|
||||
Common common.InsertCtx
|
||||
|
||||
reqBuf bytesutil.ByteBuffer
|
||||
}
|
||||
|
||||
func (ctx *pushCtx) reset() {
|
||||
ctx.Rows.Reset()
|
||||
ctx.Common.Reset(0)
|
||||
ctx.reqBuf.Reset()
|
||||
}
|
||||
|
||||
func getPushCtx() *pushCtx {
|
||||
select {
|
||||
case ctx := <-pushCtxPoolCh:
|
||||
return ctx
|
||||
default:
|
||||
if v := pushCtxPool.Get(); v != nil {
|
||||
return v.(*pushCtx)
|
||||
}
|
||||
return &pushCtx{}
|
||||
}
|
||||
}
|
||||
|
||||
func putPushCtx(ctx *pushCtx) {
|
||||
ctx.reset()
|
||||
select {
|
||||
case pushCtxPoolCh <- ctx:
|
||||
default:
|
||||
pushCtxPool.Put(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
var pushCtxPool sync.Pool
|
||||
var pushCtxPoolCh = make(chan *pushCtx, runtime.GOMAXPROCS(-1))
|
||||
70
app/vminsert/opentsdbhttp/server.go
Normal file
70
app/vminsert/opentsdbhttp/server.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package opentsdbhttp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
)
|
||||
|
||||
var (
|
||||
writeRequests = metrics.NewCounter(`vm_http_requests_total{path="/api/put", protocol="opentsdb-http"}`)
|
||||
writeErrors = metrics.NewCounter(`vm_http_request_errors_total{path="/api/put", protocol="opentsdb-http"}`)
|
||||
)
|
||||
|
||||
var (
|
||||
httpServer *http.Server
|
||||
httpAddr string
|
||||
maxRequestSize int64
|
||||
)
|
||||
|
||||
// Serve starts HTTP OpenTSDB server on the given addr.
|
||||
func Serve(addr string, maxReqSize int64) {
|
||||
logger.Infof("starting HTTP OpenTSDB server at %q", addr)
|
||||
httpAddr = addr
|
||||
maxRequestSize = maxReqSize
|
||||
httpServer = &http.Server{
|
||||
Addr: addr,
|
||||
Handler: http.HandlerFunc(requestHandler),
|
||||
ReadTimeout: 30 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
}
|
||||
go func() {
|
||||
err := httpServer.ListenAndServe()
|
||||
if err == http.ErrServerClosed {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
logger.Fatalf("FATAL: error serving HTTP OpenTSDB: %s", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// requestHandler handles HTTP OpenTSDB insert request.
|
||||
func requestHandler(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/api/put":
|
||||
writeRequests.Inc()
|
||||
if err := insertHandler(r, maxRequestSize); err != nil {
|
||||
writeErrors.Inc()
|
||||
httpserver.Errorf(w, "error in %q: %s", r.URL.Path, err)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
default:
|
||||
httpserver.Errorf(w, "unexpected path requested on HTTP OpenTSDB server: %q", r.URL.Path)
|
||||
}
|
||||
}
|
||||
|
||||
// Stop stops HTTP OpenTSDB server.
|
||||
func Stop() {
|
||||
logger.Infof("stopping HTTP OpenTSDB server at %q...", httpAddr)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
if err := httpServer.Shutdown(ctx); err != nil {
|
||||
logger.Fatalf("FATAL: cannot close HTTP OpenTSDB server: %s", err)
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package vmselect
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strings"
|
||||
@@ -70,7 +71,11 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
|
||||
case <-t.C:
|
||||
timerpool.Put(t)
|
||||
concurrencyLimitTimeout.Inc()
|
||||
httpserver.Errorf(w, "cannot handle more than %d concurrent requests", cap(concurrencyCh))
|
||||
err := &httpserver.ErrorWithStatusCode{
|
||||
Err: fmt.Errorf("cannot handle more than %d concurrent requests", cap(concurrencyCh)),
|
||||
StatusCode: http.StatusServiceUnavailable,
|
||||
}
|
||||
httpserver.Errorf(w, "%s", err)
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -185,7 +190,10 @@ func sendPrometheusError(w http.ResponseWriter, r *http.Request, err error) {
|
||||
logger.Errorf("error in %q: %s", r.URL.Path, err)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
statusCode := 422
|
||||
statusCode := http.StatusUnprocessableEntity
|
||||
if esc, ok := err.(*httpserver.ErrorWithStatusCode); ok {
|
||||
statusCode = esc.StatusCode
|
||||
}
|
||||
w.WriteHeader(statusCode)
|
||||
prometheus.WriteErrorResponse(w, statusCode, err)
|
||||
}
|
||||
|
||||
15
app/vmselect/netstorage/fadvise_freebsd.go
Normal file
15
app/vmselect/netstorage/fadvise_freebsd.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package netstorage
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func mustFadviseRandomRead(f *os.File) {
|
||||
fd := int(f.Fd())
|
||||
if err := unix.Fadvise(int(fd), 0, 0, unix.FADV_RANDOM|unix.FADV_WILLNEED); err != nil {
|
||||
logger.Panicf("FATAL: error returned from unix.Fadvise(RANDOM|WILLNEED): %s", err)
|
||||
}
|
||||
}
|
||||
@@ -19,9 +19,9 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
maxTagKeysPerSearch = flag.Int("search.maxTagKeys", 10e3, "The maximum number of tag keys returned per search")
|
||||
maxTagValuesPerSearch = flag.Int("search.maxTagValues", 10e3, "The maximum number of tag values returned per search")
|
||||
maxMetricsPerSearch = flag.Int("search.maxUniqueTimeseries", 100e3, "The maximum number of unique time series each search can scan")
|
||||
maxTagKeysPerSearch = flag.Int("search.maxTagKeys", 100e3, "The maximum number of tag keys returned per search")
|
||||
maxTagValuesPerSearch = flag.Int("search.maxTagValues", 100e3, "The maximum number of tag values returned per search")
|
||||
maxMetricsPerSearch = flag.Int("search.maxUniqueTimeseries", 300e3, "The maximum number of unique time series each search can scan")
|
||||
)
|
||||
|
||||
// Result is a single timeseries result.
|
||||
|
||||
@@ -574,12 +574,47 @@ func QueryRangeHandler(w http.ResponseWriter, r *http.Request) error {
|
||||
result = adjustLastPoints(result)
|
||||
}
|
||||
|
||||
// Remove NaN values as Prometheus does.
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/153
|
||||
removeNaNValuesInplace(result)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
WriteQueryRangeResponse(w, result)
|
||||
queryRangeDuration.UpdateDuration(startTime)
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeNaNValuesInplace(tss []netstorage.Result) {
|
||||
for i := range tss {
|
||||
ts := &tss[i]
|
||||
hasNaNs := false
|
||||
for _, v := range ts.Values {
|
||||
if math.IsNaN(v) {
|
||||
hasNaNs = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasNaNs {
|
||||
// Fast path: nothing to remove.
|
||||
continue
|
||||
}
|
||||
|
||||
// Slow path: remove NaNs.
|
||||
srcTimestamps := ts.Timestamps
|
||||
dstValues := ts.Values[:0]
|
||||
dstTimestamps := ts.Timestamps[:0]
|
||||
for j, v := range ts.Values {
|
||||
if math.IsNaN(v) {
|
||||
continue
|
||||
}
|
||||
dstValues = append(dstValues, v)
|
||||
dstTimestamps = append(dstTimestamps, srcTimestamps[j])
|
||||
}
|
||||
ts.Values = dstValues
|
||||
ts.Timestamps = dstTimestamps
|
||||
}
|
||||
}
|
||||
|
||||
var queryRangeDuration = metrics.NewSummary(`vm_request_duration_seconds{path="/api/v1/query_range"}`)
|
||||
|
||||
// adjustLastPoints substitutes the last point values with the previous
|
||||
|
||||
@@ -2,11 +2,48 @@ package prometheus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/netstorage"
|
||||
)
|
||||
|
||||
func TestRemoveNaNValuesInplace(t *testing.T) {
|
||||
f := func(tss []netstorage.Result, tssExpected []netstorage.Result) {
|
||||
t.Helper()
|
||||
removeNaNValuesInplace(tss)
|
||||
if !reflect.DeepEqual(tss, tssExpected) {
|
||||
t.Fatalf("unexpected result; got %v; want %v", tss, tssExpected)
|
||||
}
|
||||
}
|
||||
|
||||
nan := math.NaN()
|
||||
|
||||
f(nil, nil)
|
||||
f([]netstorage.Result{
|
||||
{
|
||||
Timestamps: []int64{100, 200, 300},
|
||||
Values: []float64{1, 2, 3},
|
||||
},
|
||||
{
|
||||
Timestamps: []int64{100, 200, 300, 400},
|
||||
Values: []float64{nan, nan, 3, nan},
|
||||
},
|
||||
}, []netstorage.Result{
|
||||
{
|
||||
Timestamps: []int64{100, 200, 300},
|
||||
Values: []float64{1, 2, 3},
|
||||
},
|
||||
{
|
||||
Timestamps: []int64{300},
|
||||
Values: []float64{3},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetTimeSuccess(t *testing.T) {
|
||||
f := func(s string, timestampExpected int64) {
|
||||
t.Helper()
|
||||
|
||||
@@ -179,7 +179,8 @@ func compareValues(vs1, vs2 []float64) error {
|
||||
}
|
||||
continue
|
||||
}
|
||||
if v1 != v2 {
|
||||
eps := math.Abs(v1 - v2)
|
||||
if eps > 1e-14 {
|
||||
return fmt.Errorf("unexpected value; got %v; want %v", v1, v2)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,14 +105,14 @@ func maySortResults(e expr, tss []*timeseries) bool {
|
||||
func timeseriesToResult(tss []*timeseries, maySort bool) ([]netstorage.Result, error) {
|
||||
tss = removeNaNs(tss)
|
||||
result := make([]netstorage.Result, len(tss))
|
||||
m := make(map[string]bool)
|
||||
m := make(map[string]struct{}, len(tss))
|
||||
bb := bbPool.Get()
|
||||
for i, ts := range tss {
|
||||
bb.B = marshalMetricNameSorted(bb.B[:0], &ts.MetricName)
|
||||
if m[string(bb.B)] {
|
||||
if _, ok := m[string(bb.B)]; ok {
|
||||
return nil, fmt.Errorf(`duplicate output timeseries: %s%s`, ts.MetricName.MetricGroup, stringMetricName(&ts.MetricName))
|
||||
}
|
||||
m[string(bb.B)] = true
|
||||
m[string(bb.B)] = struct{}{}
|
||||
|
||||
rs := &result[i]
|
||||
rs.MetricNameMarshaled = append(rs.MetricNameMarshaled[:0], bb.B...)
|
||||
|
||||
@@ -1302,6 +1302,44 @@ func TestExecSuccess(t *testing.T) {
|
||||
resultExpected := []netstorage.Result{r}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`label_value()`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `with (
|
||||
x = (
|
||||
label_set(time(), "foo", "123.456", "__name__", "aaa"),
|
||||
label_set(-time(), "foo", "bar", "__name__", "bbb"),
|
||||
label_set(-time(), "__name__", "bxs"),
|
||||
label_set(-time(), "foo", "45", "bar", "xs"),
|
||||
)
|
||||
)
|
||||
sort(x + label_value(x, "foo"))`
|
||||
r1 := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{-955, -1155, -1355, -1555, -1755, -1955},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r1.MetricName.Tags = []storage.Tag{
|
||||
{
|
||||
Key: []byte("bar"),
|
||||
Value: []byte("xs"),
|
||||
},
|
||||
{
|
||||
Key: []byte("foo"),
|
||||
Value: []byte("45"),
|
||||
},
|
||||
}
|
||||
r2 := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{1123.456, 1323.456, 1523.456, 1723.456, 1923.456, 2123.456},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r2.MetricName.Tags = []storage.Tag{{
|
||||
Key: []byte("foo"),
|
||||
Value: []byte("123.456"),
|
||||
}}
|
||||
resultExpected := []netstorage.Result{r1, r2}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`label_transform(mismatch)`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `label_transform(time(), "__name__", "foobar", "xx")`
|
||||
@@ -2160,21 +2198,78 @@ func TestExecSuccess(t *testing.T) {
|
||||
})
|
||||
t.Run(`histogram_quantile(negative-bucket-count)`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `sort(histogram_quantile(0.6,
|
||||
q := `histogram_quantile(0.6,
|
||||
label_set(90, "foo", "bar", "le", "10")
|
||||
or label_set(-100, "foo", "bar", "le", "30")
|
||||
or label_set(300, "foo", "bar", "le", "+Inf")
|
||||
))`
|
||||
)`
|
||||
r := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{30, 30, 30, 30, 30, 30},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r.MetricName.Tags = []storage.Tag{{
|
||||
Key: []byte("foo"),
|
||||
Value: []byte("bar"),
|
||||
}}
|
||||
resultExpected := []netstorage.Result{r}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`histogram_quantile(nan-bucket-count)`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `histogram_quantile(0.6,
|
||||
label_set(90, "foo", "bar", "le", "10")
|
||||
or label_set(NaN, "foo", "bar", "le", "30")
|
||||
or label_set(300, "foo", "bar", "le", "+Inf")
|
||||
)`
|
||||
r := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{30, 30, 30, 30, 30, 30},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r.MetricName.Tags = []storage.Tag{{
|
||||
Key: []byte("foo"),
|
||||
Value: []byte("bar"),
|
||||
}}
|
||||
resultExpected := []netstorage.Result{r}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`histogram_quantile(nan-bucket-count)`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `histogram_quantile(0.2,
|
||||
label_set(0, "foo", "bar", "le", "10")
|
||||
or label_set(100, "foo", "bar", "le", "30")
|
||||
or label_set(300, "foo", "bar", "le", "+Inf")
|
||||
)`
|
||||
r := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{22, 22, 22, 22, 22, 22},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r.MetricName.Tags = []storage.Tag{{
|
||||
Key: []byte("foo"),
|
||||
Value: []byte("bar"),
|
||||
}}
|
||||
resultExpected := []netstorage.Result{r}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`histogram_quantile(zero-bucket-count)`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `histogram_quantile(0.6,
|
||||
label_set(0, "foo", "bar", "le", "10")
|
||||
or label_set(0, "foo", "bar", "le", "30")
|
||||
or label_set(0, "foo", "bar", "le", "+Inf")
|
||||
)`
|
||||
resultExpected := []netstorage.Result{}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`histogram_quantile(nan-bucket-count)`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `sort(histogram_quantile(0.6,
|
||||
label_set(90, "foo", "bar", "le", "10")
|
||||
or label_set(NaN, "foo", "bar", "le", "30")
|
||||
or label_set(300, "foo", "bar", "le", "+Inf")
|
||||
))`
|
||||
q := `histogram_quantile(0.6,
|
||||
label_set(nan, "foo", "bar", "le", "10")
|
||||
or label_set(nan, "foo", "bar", "le", "30")
|
||||
or label_set(nan, "foo", "bar", "le", "+Inf")
|
||||
)`
|
||||
resultExpected := []netstorage.Result{}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
|
||||
@@ -197,19 +197,17 @@ func (rc *rollupConfig) Do(dstValues []float64, values []float64, timestamps []i
|
||||
|
||||
i := 0
|
||||
j := 0
|
||||
ni := 0
|
||||
nj := 0
|
||||
for _, tEnd := range rc.Timestamps {
|
||||
tStart := tEnd - window
|
||||
n := sort.Search(len(timestamps)-i, func(n int) bool {
|
||||
return timestamps[i+n] > tStart
|
||||
})
|
||||
i += n
|
||||
ni = seekFirstTimestampIdxAfter(timestamps[i:], tStart, ni)
|
||||
i += ni
|
||||
if j < i {
|
||||
j = i
|
||||
}
|
||||
n = sort.Search(len(timestamps)-j, func(n int) bool {
|
||||
return timestamps[j+n] > tEnd
|
||||
})
|
||||
j += n
|
||||
nj = seekFirstTimestampIdxAfter(timestamps[j:], tEnd, nj)
|
||||
j += nj
|
||||
|
||||
rfa.prevValue = nan
|
||||
rfa.prevTimestamp = tStart - maxPrevInterval
|
||||
@@ -229,6 +227,46 @@ func (rc *rollupConfig) Do(dstValues []float64, values []float64, timestamps []i
|
||||
return dstValues
|
||||
}
|
||||
|
||||
func seekFirstTimestampIdxAfter(timestamps []int64, seekTimestamp int64, nHint int) int {
|
||||
if len(timestamps) == 0 || timestamps[0] > seekTimestamp {
|
||||
return 0
|
||||
}
|
||||
startIdx := nHint - 2
|
||||
if startIdx < 0 {
|
||||
startIdx = 0
|
||||
}
|
||||
if startIdx >= len(timestamps) {
|
||||
startIdx = len(timestamps) - 1
|
||||
}
|
||||
endIdx := nHint + 2
|
||||
if endIdx > len(timestamps) {
|
||||
endIdx = len(timestamps)
|
||||
}
|
||||
if startIdx > 0 && timestamps[startIdx] <= seekTimestamp {
|
||||
timestamps = timestamps[startIdx:]
|
||||
endIdx -= startIdx
|
||||
} else {
|
||||
startIdx = 0
|
||||
}
|
||||
if endIdx < len(timestamps) && timestamps[endIdx] > seekTimestamp {
|
||||
timestamps = timestamps[:endIdx]
|
||||
}
|
||||
if len(timestamps) < 16 {
|
||||
// Fast path: the number of timestamps to search is small, so scan them all.
|
||||
for i, timestamp := range timestamps {
|
||||
if timestamp > seekTimestamp {
|
||||
return startIdx + i
|
||||
}
|
||||
}
|
||||
return startIdx + len(timestamps)
|
||||
}
|
||||
// Slow path: too big len(timestamps), so use binary search.
|
||||
i := sort.Search(len(timestamps), func(n int) bool {
|
||||
return n >= 0 && n < len(timestamps) && timestamps[n] > seekTimestamp
|
||||
})
|
||||
return startIdx + i
|
||||
}
|
||||
|
||||
func getMaxPrevInterval(timestamps []int64) int64 {
|
||||
if len(timestamps) < 2 {
|
||||
return int64(maxSilenceInterval)
|
||||
|
||||
@@ -53,8 +53,7 @@ func InitRollupResultCache(cachePath string) {
|
||||
c = workingsetcache.New(cacheSize, time.Hour)
|
||||
}
|
||||
if *disableCache {
|
||||
c.Stop()
|
||||
c = nil
|
||||
c.Reset()
|
||||
}
|
||||
|
||||
stats := &fastcache.Stats{}
|
||||
@@ -99,10 +98,8 @@ func InitRollupResultCache(cachePath string) {
|
||||
// StopRollupResultCache closes the rollupResult cache.
|
||||
func StopRollupResultCache() {
|
||||
if len(rollupResultCachePath) == 0 {
|
||||
if !*disableCache {
|
||||
rollupResultCacheV.c.Stop()
|
||||
rollupResultCacheV.c = nil
|
||||
}
|
||||
rollupResultCacheV.c.Stop()
|
||||
rollupResultCacheV.c = nil
|
||||
return
|
||||
}
|
||||
logger.Infof("saving rollupResult cache to %q...", rollupResultCachePath)
|
||||
|
||||
@@ -182,7 +182,8 @@ func testRollupFunc(t *testing.T, funcName string, args []interface{}, meExpecte
|
||||
t.Fatalf("unexpected value; got %v; want %v", v, vExpected)
|
||||
}
|
||||
} else {
|
||||
if v != vExpected {
|
||||
eps := math.Abs(v - vExpected)
|
||||
if eps > 1e-14 {
|
||||
t.Fatalf("unexpected value; got %v; want %v", v, vExpected)
|
||||
}
|
||||
}
|
||||
@@ -584,7 +585,7 @@ func TestRollupFuncsNoWindow(t *testing.T) {
|
||||
timestampsExpected := []int64{10, 50, 90, 130}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
t.Run("lifetime", func(t *testing.T) {
|
||||
t.Run("lifetime_1", func(t *testing.T) {
|
||||
rc := rollupConfig{
|
||||
Func: rollupLifetime,
|
||||
Start: 0,
|
||||
@@ -598,7 +599,7 @@ func TestRollupFuncsNoWindow(t *testing.T) {
|
||||
timestampsExpected := []int64{0, 40, 80, 120, 160}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
t.Run("lifetime", func(t *testing.T) {
|
||||
t.Run("lifetime_2", func(t *testing.T) {
|
||||
rc := rollupConfig{
|
||||
Func: rollupLifetime,
|
||||
Start: 0,
|
||||
@@ -612,7 +613,7 @@ func TestRollupFuncsNoWindow(t *testing.T) {
|
||||
timestampsExpected := []int64{0, 40, 80, 120, 160}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
t.Run("scrape_interval", func(t *testing.T) {
|
||||
t.Run("scrape_interval_1", func(t *testing.T) {
|
||||
rc := rollupConfig{
|
||||
Func: rollupScrapeInterval,
|
||||
Start: 0,
|
||||
@@ -626,7 +627,7 @@ func TestRollupFuncsNoWindow(t *testing.T) {
|
||||
timestampsExpected := []int64{0, 40, 80, 120, 160}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
t.Run("scrape_interval", func(t *testing.T) {
|
||||
t.Run("scrape_interval_2", func(t *testing.T) {
|
||||
rc := rollupConfig{
|
||||
Func: rollupScrapeInterval,
|
||||
Start: 0,
|
||||
@@ -752,7 +753,7 @@ func TestRollupFuncsNoWindow(t *testing.T) {
|
||||
timestampsExpected := []int64{0, 40, 80, 120, 160}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
t.Run("distinct", func(t *testing.T) {
|
||||
t.Run("distinct_over_time_1", func(t *testing.T) {
|
||||
rc := rollupConfig{
|
||||
Func: rollupDistinct,
|
||||
Start: 0,
|
||||
@@ -766,6 +767,20 @@ func TestRollupFuncsNoWindow(t *testing.T) {
|
||||
timestampsExpected := []int64{0, 40, 80, 120, 160}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
t.Run("distinct_over_time_2", func(t *testing.T) {
|
||||
rc := rollupConfig{
|
||||
Func: rollupDistinct,
|
||||
Start: 0,
|
||||
End: 160,
|
||||
Step: 40,
|
||||
Window: 80,
|
||||
}
|
||||
rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step)
|
||||
values := rc.Do(nil, testValues, testTimestamps)
|
||||
valuesExpected := []float64{nan, 4, 7, 6, 3}
|
||||
timestampsExpected := []int64{0, 40, 80, 120, 160}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
}
|
||||
|
||||
func testRowsEqual(t *testing.T, values []float64, timestamps []int64, valuesExpected []float64, timestampsExpected []int64) {
|
||||
|
||||
@@ -63,6 +63,7 @@ var transformFuncs = map[string]transformFunc{
|
||||
"label_copy": transformLabelCopy,
|
||||
"label_move": transformLabelMove,
|
||||
"label_transform": transformLabelTransform,
|
||||
"label_value": transformLabelValue,
|
||||
"union": transformUnion,
|
||||
"": transformUnion, // empty func is a synonim to union
|
||||
"keep_last_value": transformKeepLastValue,
|
||||
@@ -308,8 +309,16 @@ func transformHistogramQuantile(tfa *transformFuncArg) ([]*timeseries, error) {
|
||||
bbPool.Put(bb)
|
||||
|
||||
// Calculate quantile for each group in m
|
||||
lastNonInf := func(xss []x) float64 {
|
||||
for len(xss) > 0 && math.IsInf(xss[len(xss)-1].le, 0) {
|
||||
|
||||
lastNonInf := func(i int, xss []x) float64 {
|
||||
for len(xss) > 0 {
|
||||
xsLast := xss[len(xss)-1]
|
||||
if xsLast.ts.Values[i] == 0 {
|
||||
return nan
|
||||
}
|
||||
if !math.IsInf(xsLast.le, 0) {
|
||||
break
|
||||
}
|
||||
xss = xss[:len(xss)-1]
|
||||
}
|
||||
if len(xss) == 0 {
|
||||
@@ -318,27 +327,38 @@ func transformHistogramQuantile(tfa *transformFuncArg) ([]*timeseries, error) {
|
||||
return xss[len(xss)-1].le
|
||||
}
|
||||
quantile := func(i int, phis []float64, xss []x) float64 {
|
||||
vPrev := float64(0)
|
||||
lePrev := float64(0)
|
||||
phi := phis[i]
|
||||
if math.IsNaN(phi) {
|
||||
return nan
|
||||
}
|
||||
// Verify for broken buckets with NaN or negative values.
|
||||
// Fix broken buckets.
|
||||
// They are already sorted by le, so their values must be in ascending order,
|
||||
// since the next bucket value includes all the previous buckets.
|
||||
vPrev := float64(0)
|
||||
for _, xs := range xss {
|
||||
v := xs.ts.Values[i]
|
||||
if math.IsNaN(v) || v < 0 {
|
||||
// Broken bucket.
|
||||
return nan
|
||||
if math.IsNaN(v) || v < vPrev {
|
||||
xs.ts.Values[i] = vPrev
|
||||
} else {
|
||||
vPrev = v
|
||||
}
|
||||
}
|
||||
if len(xss) == 0 {
|
||||
return nan
|
||||
}
|
||||
if phi < 0 {
|
||||
return -inf
|
||||
}
|
||||
if phi > 1 {
|
||||
return inf
|
||||
}
|
||||
vReq := xss[len(xss)-1].ts.Values[i] * phi
|
||||
vLast := xss[len(xss)-1].ts.Values[i]
|
||||
if vLast == 0 {
|
||||
return nan
|
||||
}
|
||||
vReq := vLast * phi
|
||||
vPrev = 0
|
||||
lePrev := float64(0)
|
||||
for _, xs := range xss {
|
||||
v := xs.ts.Values[i]
|
||||
le := xs.le
|
||||
@@ -348,16 +368,16 @@ func transformHistogramQuantile(tfa *transformFuncArg) ([]*timeseries, error) {
|
||||
continue
|
||||
}
|
||||
if math.IsInf(le, 0) {
|
||||
return lastNonInf(xss)
|
||||
return lastNonInf(i, xss)
|
||||
}
|
||||
if v == vPrev {
|
||||
return lePrev
|
||||
}
|
||||
return lePrev + (le-lePrev)*(vReq-vPrev)/(v-vPrev)
|
||||
}
|
||||
return lastNonInf(xss)
|
||||
return lastNonInf(i, xss)
|
||||
}
|
||||
var rvs []*timeseries
|
||||
rvs := make([]*timeseries, 0, len(m))
|
||||
for _, xss := range m {
|
||||
sort.Slice(xss, func(i, j int) bool {
|
||||
return xss[i].le < xss[j].le
|
||||
@@ -881,6 +901,33 @@ func labelReplace(tss []*timeseries, srcLabel string, r *regexp.Regexp, dstLabel
|
||||
return tss, nil
|
||||
}
|
||||
|
||||
func transformLabelValue(tfa *transformFuncArg) ([]*timeseries, error) {
|
||||
args := tfa.args
|
||||
if err := expectTransformArgsNum(args, 2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
labelName, err := getString(args[1], 1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot get label name: %s", err)
|
||||
}
|
||||
rvs := args[0]
|
||||
for _, ts := range rvs {
|
||||
ts.MetricName.ResetMetricGroup()
|
||||
labelValue := ts.MetricName.GetTagValue(labelName)
|
||||
v, err := strconv.ParseFloat(string(labelValue), 64)
|
||||
if err != nil {
|
||||
v = nan
|
||||
}
|
||||
values := ts.Values
|
||||
for i := range values {
|
||||
values[i] = v
|
||||
}
|
||||
}
|
||||
// Do not remove timeseries with only NaN values, so `default` could be applied to them:
|
||||
// label_value(q, "label") default 123
|
||||
return rvs, nil
|
||||
}
|
||||
|
||||
func transformLn(v float64) float64 {
|
||||
return math.Log(v)
|
||||
}
|
||||
|
||||
8
go.mod
8
go.mod
@@ -6,13 +6,13 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.0.1-0.20190104013014-3767db7a7e18
|
||||
github.com/golang/snappy v0.0.1
|
||||
github.com/google/go-cmp v0.3.0 // indirect
|
||||
github.com/klauspost/compress v1.7.5
|
||||
github.com/klauspost/compress v1.7.6
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
github.com/valyala/fastjson v1.4.1
|
||||
github.com/valyala/gozstd v1.6.0
|
||||
github.com/valyala/gozstd v1.6.1
|
||||
github.com/valyala/histogram v1.0.1
|
||||
github.com/valyala/quicktemplate v1.1.1
|
||||
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa
|
||||
github.com/valyala/quicktemplate v1.2.0
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a
|
||||
)
|
||||
|
||||
go 1.12
|
||||
|
||||
16
go.sum
16
go.sum
@@ -20,8 +20,8 @@ github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.7.5 h1:NMapGoDIKPKpk2hpcgAU6XHfsREHG2p8PIg7C3f/jpI=
|
||||
github.com/klauspost/compress v1.7.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.7.6 h1:GH2karLOcuZtA5a3+KuzSU33A2cvcHGbtEWM6K4t7oU=
|
||||
github.com/klauspost/compress v1.7.6/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
@@ -41,13 +41,13 @@ github.com/valyala/fastjson v1.4.1 h1:hrltpHpIpkaxll8QltMU8c3QZ5+qIiCL8yKqPFJI/y
|
||||
github.com/valyala/fastjson v1.4.1/go.mod h1:nV6MsjxL2IMJQUoHDIrjEI7oLyeqK6aBD7EFWPsvP8o=
|
||||
github.com/valyala/fastrand v1.0.0 h1:LUKT9aKer2dVQNUi3waewTbKV+7H17kvWFNKs2ObdkI=
|
||||
github.com/valyala/fastrand v1.0.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
|
||||
github.com/valyala/gozstd v1.6.0 h1:34qKK75C6Dx9zof2JqUiunfJQ87Up6vTHXABWDyCH+g=
|
||||
github.com/valyala/gozstd v1.6.0/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ=
|
||||
github.com/valyala/gozstd v1.6.1 h1:oFN2mNW0kOr1fEKJuLpDwakNb6Y9fElVEBZmPEsFTUw=
|
||||
github.com/valyala/gozstd v1.6.1/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ=
|
||||
github.com/valyala/histogram v1.0.1 h1:FzA7n2Tz/wKRMejgu3PV1vw3htAklTjjuoI6z3d4KDg=
|
||||
github.com/valyala/histogram v1.0.1/go.mod h1:lQy0xA4wUz2+IUnf97SivorsJIp8FxsnRd6x25q7Mto=
|
||||
github.com/valyala/quicktemplate v1.1.1 h1:C58y/wN0FMTi2PR0n3onltemfFabany53j7M6SDDB8k=
|
||||
github.com/valyala/quicktemplate v1.1.1/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4=
|
||||
github.com/valyala/quicktemplate v1.2.0 h1:BaO1nHTkspYzmAjPXj0QiDJxai96tlcZyKcI9dyEGvM=
|
||||
github.com/valyala/quicktemplate v1.2.0/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4=
|
||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
||||
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa h1:KIDDMLT1O0Nr7TSxp8xM5tJcdn8tgyAONntO829og1M=
|
||||
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
||||
64
lib/filestream/filestream_freebsd.go
Normal file
64
lib/filestream/filestream_freebsd.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package filestream
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func (st *streamTracker) adviseDontNeed(n int, fdatasync bool) error {
|
||||
st.length += uint64(n)
|
||||
if st.fd == 0 {
|
||||
return nil
|
||||
}
|
||||
if st.length < dontNeedBlockSize {
|
||||
return nil
|
||||
}
|
||||
blockSize := st.length - (st.length % dontNeedBlockSize)
|
||||
if fdatasync {
|
||||
if err := unixFdatasync(int(st.fd)); err != nil {
|
||||
return fmt.Errorf("unix.Fdatasync error: %s", err)
|
||||
}
|
||||
}
|
||||
if err := unix.Fadvise(int(st.fd), int64(st.offset), int64(blockSize), unix.FADV_DONTNEED); err != nil {
|
||||
return fmt.Errorf("unix.Fadvise(FADV_DONTNEEDED, %d, %d) error: %s", st.offset, blockSize, err)
|
||||
}
|
||||
st.offset += blockSize
|
||||
st.length -= blockSize
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *streamTracker) close() error {
|
||||
if st.fd == 0 {
|
||||
return nil
|
||||
}
|
||||
// Advise the whole file as it shouldn't be cached.
|
||||
if err := unix.Fadvise(int(st.fd), 0, 0, unix.FADV_DONTNEED); err != nil {
|
||||
return fmt.Errorf("unix.Fadvise(FADV_DONTNEEDED, 0, 0) error: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// unix.Fdatasync is missing, so put it here
|
||||
func unixFdatasync(fd int) (err error) {
|
||||
_, _, e1 := unix.Syscall(unix.SYS_FDATASYNC, uintptr(fd), 0, 0)
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func errnoErr(e syscall.Errno) error {
|
||||
switch e {
|
||||
case 0:
|
||||
return nil
|
||||
case unix.EAGAIN:
|
||||
return syscall.EAGAIN
|
||||
case unix.EINVAL:
|
||||
return syscall.EINVAL
|
||||
case unix.ENOENT:
|
||||
return syscall.ENOENT
|
||||
}
|
||||
return e
|
||||
}
|
||||
17
lib/fs/fs.go
17
lib/fs/fs.go
@@ -400,3 +400,20 @@ func CreateFlockFile(dir string) (*os.File, error) {
|
||||
}
|
||||
return flockF, nil
|
||||
}
|
||||
|
||||
// MustGetFreeSpace returns free space for the given directory path.
|
||||
func MustGetFreeSpace(path string) uint64 {
|
||||
d, err := os.Open(path)
|
||||
if err != nil {
|
||||
logger.Panicf("FATAL: cannot determine free disk space on %q: %s", path, err)
|
||||
}
|
||||
defer MustClose(d)
|
||||
|
||||
fd := d.Fd()
|
||||
var stat unix.Statfs_t
|
||||
if err := unix.Fstatfs(int(fd), &stat); err != nil {
|
||||
logger.Panicf("FATAL: cannot determine free disk space on %q: %s", path, err)
|
||||
}
|
||||
freeSpace := uint64(stat.Bavail) * uint64(stat.Bsize)
|
||||
return freeSpace
|
||||
}
|
||||
|
||||
@@ -423,7 +423,29 @@ var (
|
||||
func Errorf(w http.ResponseWriter, format string, args ...interface{}) {
|
||||
errStr := fmt.Sprintf(format, args...)
|
||||
logger.Errorf("%s", errStr)
|
||||
http.Error(w, errStr, http.StatusBadRequest)
|
||||
|
||||
// Extract statusCode from args
|
||||
statusCode := http.StatusBadRequest
|
||||
for _, arg := range args {
|
||||
if esc, ok := arg.(*ErrorWithStatusCode); ok {
|
||||
statusCode = esc.StatusCode
|
||||
break
|
||||
}
|
||||
}
|
||||
http.Error(w, errStr, statusCode)
|
||||
}
|
||||
|
||||
// ErrorWithStatusCode is error with HTTP status code.
|
||||
//
|
||||
// The given StatusCode is sent to client when the error is passed to Errorf.
|
||||
type ErrorWithStatusCode struct {
|
||||
Err error
|
||||
StatusCode int
|
||||
}
|
||||
|
||||
// Error implements error interface.
|
||||
func (e *ErrorWithStatusCode) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
func isTrivialNetworkError(err error) bool {
|
||||
|
||||
@@ -10,27 +10,41 @@ import (
|
||||
|
||||
var allowedMemPercent = flag.Float64("memory.allowedPercent", 60, "Allowed percent of system memory VictoriaMetrics caches may occupy")
|
||||
|
||||
var allowedMemory int
|
||||
var (
|
||||
allowedMemory int
|
||||
remainingMemory int
|
||||
)
|
||||
|
||||
var once sync.Once
|
||||
|
||||
func initOnce() {
|
||||
if !flag.Parsed() {
|
||||
// Do not use logger.Panicf here, since logger may be uninitialized yet.
|
||||
panic(fmt.Errorf("BUG: memory.Allowed must be called only after flag.Parse call"))
|
||||
}
|
||||
if *allowedMemPercent < 10 || *allowedMemPercent > 200 {
|
||||
logger.Panicf("FATAL: -memory.allowedPercent must be in the range [10...200]; got %f", *allowedMemPercent)
|
||||
}
|
||||
percent := *allowedMemPercent / 100
|
||||
|
||||
mem := sysTotalMemory()
|
||||
allowedMemory = int(float64(mem) * percent)
|
||||
remainingMemory = mem - allowedMemory
|
||||
logger.Infof("limiting caches to %d bytes, leaving %d bytes to the OS according to -memory.allowedPercent=%g", allowedMemory, remainingMemory, *allowedMemPercent)
|
||||
}
|
||||
|
||||
// Allowed returns the amount of system memory allowed to use by the app.
|
||||
//
|
||||
// The function must be called only after flag.Parse is called.
|
||||
func Allowed() int {
|
||||
once.Do(func() {
|
||||
if !flag.Parsed() {
|
||||
// Do not use logger.Panicf here, since logger may be uninitialized yet.
|
||||
panic(fmt.Errorf("BUG: memory.Allowed must be called only after flag.Parse call"))
|
||||
}
|
||||
if *allowedMemPercent < 10 || *allowedMemPercent > 200 {
|
||||
logger.Panicf("FATAL: -memory.allowedPercent must be in the range [10...200]; got %f", *allowedMemPercent)
|
||||
}
|
||||
percent := *allowedMemPercent / 100
|
||||
|
||||
mem := sysTotalMemory()
|
||||
allowedMemory = int(float64(mem) * percent)
|
||||
logger.Infof("limiting caches to %d bytes of RAM according to -memory.allowedPercent=%g", allowedMemory, *allowedMemPercent)
|
||||
})
|
||||
once.Do(initOnce)
|
||||
return allowedMemory
|
||||
}
|
||||
|
||||
// Remaining returns the amount of memory remaining to the OS.
|
||||
//
|
||||
// This function must be called only after flag.Parse is called.
|
||||
func Remaining() int {
|
||||
once.Do(initOnce)
|
||||
return remainingMemory
|
||||
}
|
||||
|
||||
17
lib/memory/memory_bsd.go
Normal file
17
lib/memory/memory_bsd.go
Normal file
@@ -0,0 +1,17 @@
|
||||
// +build freebsd openbsd dragonfly netbsd
|
||||
|
||||
package memory
|
||||
|
||||
import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
)
|
||||
|
||||
// This code has been adopted from https://github.com/pbnjay/memory
|
||||
|
||||
func sysTotalMemory() int {
|
||||
s, err := sysctlUint64("hw.physmem")
|
||||
if err != nil {
|
||||
logger.Panicf("FATAL: cannot determine system memory: %s", err)
|
||||
}
|
||||
return int(s)
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
package memory
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
)
|
||||
|
||||
@@ -15,16 +12,3 @@ func sysTotalMemory() int {
|
||||
}
|
||||
return int(s)
|
||||
}
|
||||
|
||||
func sysctlUint64(name string) (uint64, error) {
|
||||
s, err := syscall.Sysctl(name)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// hack because the string conversion above drops a \0
|
||||
b := []byte(s)
|
||||
if len(b) < 8 {
|
||||
b = append(b, 0)
|
||||
}
|
||||
return *(*uint64)(unsafe.Pointer(&b[0])), nil
|
||||
}
|
||||
|
||||
22
lib/memory/sysctl.go
Normal file
22
lib/memory/sysctl.go
Normal file
@@ -0,0 +1,22 @@
|
||||
// +build darwin freebsd openbsd dragonfly netbsd
|
||||
|
||||
package memory
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// This has been adapted from github.com/pbnjay/memory.
|
||||
func sysctlUint64(name string) (uint64, error) {
|
||||
s, err := syscall.Sysctl(name)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// hack because the string conversion above drops a \0
|
||||
b := []byte(s)
|
||||
if len(b) < 8 {
|
||||
b = append(b, 0)
|
||||
}
|
||||
return *(*uint64)(unsafe.Pointer(&b[0])), nil
|
||||
}
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/syncwg"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// maxParts is the maximum number of parts in the table.
|
||||
@@ -804,19 +803,7 @@ func (tb *Table) maxOutPartItems() uint64 {
|
||||
}
|
||||
|
||||
func (tb *Table) maxOutPartItemsSlow() uint64 {
|
||||
// Determine the amount of free space on tb.path.
|
||||
d, err := os.Open(tb.path)
|
||||
if err != nil {
|
||||
logger.Panicf("FATAL: cannot determine free disk space on %q: %s", tb.path, err)
|
||||
}
|
||||
defer fs.MustClose(d)
|
||||
|
||||
fd := d.Fd()
|
||||
var stat unix.Statfs_t
|
||||
if err := unix.Fstatfs(int(fd), &stat); err != nil {
|
||||
logger.Panicf("FATAL: cannot determine free disk space on %q: %s", tb.path, err)
|
||||
}
|
||||
freeSpace := stat.Bavail * uint64(stat.Bsize)
|
||||
freeSpace := fs.MustGetFreeSpace(tb.path)
|
||||
|
||||
// Calculate the maximum number of items in the output merge part
|
||||
// by dividing the freeSpace by 4 and by the number of concurrent
|
||||
|
||||
@@ -181,6 +181,10 @@ func unmarshalBlockHeaders(dst []blockHeader, src []byte, blockHeadersCount int)
|
||||
logger.Panicf("BUG: blockHeadersCount must be greater than zero; got %d", blockHeadersCount)
|
||||
}
|
||||
dstLen := len(dst)
|
||||
if n := dstLen + blockHeadersCount - cap(dst); n > 0 {
|
||||
dst = append(dst[:cap(dst)], make([]blockHeader, n)...)
|
||||
dst = dst[:dstLen]
|
||||
}
|
||||
var bh blockHeader
|
||||
for len(src) > 0 {
|
||||
tmp, err := bh.Unmarshal(src)
|
||||
|
||||
@@ -892,6 +892,9 @@ func (db *indexDB) DeleteTSIDs(tfss []*TagFilters) (int, error) {
|
||||
// Reset TagFilters -> TSIDS cache, since it may contain deleted TSIDs.
|
||||
db.invalidateTagCache()
|
||||
|
||||
// Do not reset uselessTagFiltersCache, since the found metricIDs
|
||||
// on cache miss are filtered out later with deletedMetricIDs.
|
||||
|
||||
// Delete TSIDs in the extDB.
|
||||
if db.doExtDB(func(extDB *indexDB) {
|
||||
var n int
|
||||
@@ -1236,6 +1239,82 @@ func (is *indexSearch) updateMetricIDsByMetricNameMatch(metricIDs, srcMetricIDs
|
||||
return nil
|
||||
}
|
||||
|
||||
func (is *indexSearch) getTagFilterWithMinMetricIDsCountOptimized(tfs *TagFilters, tr TimeRange, maxMetrics int) (*tagFilter, map[uint64]struct{}, error) {
|
||||
// Try fast path with the minimized number of maxMetrics.
|
||||
maxMetricsAdjusted := is.adjustMaxMetricsAdaptive(tr, maxMetrics)
|
||||
minTf, minMetricIDs, err := is.getTagFilterWithMinMetricIDsCountAdaptive(tfs, maxMetricsAdjusted)
|
||||
if err == nil {
|
||||
return minTf, minMetricIDs, nil
|
||||
}
|
||||
if err != errTooManyMetrics {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// All the tag filters match too many metrics.
|
||||
|
||||
// Slow path: try filtering the matching metrics by time range.
|
||||
// This should work well for cases when old metrics are constantly substituted
|
||||
// by big number of new metrics. For example, prometheus-operator creates many new
|
||||
// metrics for each new deployment.
|
||||
//
|
||||
// Allow fetching up to 20*maxMetrics metrics for the given time range
|
||||
// in the hope these metricIDs will be filtered out by other filters later.
|
||||
maxTimeRangeMetrics := 20 * maxMetrics
|
||||
metricIDsForTimeRange, err := is.getMetricIDsForTimeRange(tr, maxTimeRangeMetrics+1)
|
||||
if err == errMissingMetricIDsForDate {
|
||||
// Slow path: try to select find the tag filter without maxMetrics adjustement.
|
||||
minTf, minMetricIDs, err = is.getTagFilterWithMinMetricIDsCountAdaptive(tfs, maxMetrics)
|
||||
if err == nil {
|
||||
return minTf, minMetricIDs, nil
|
||||
}
|
||||
if err != errTooManyMetrics {
|
||||
return nil, nil, err
|
||||
}
|
||||
return nil, nil, fmt.Errorf("cannot find tag filter matching less than %d time series; "+
|
||||
"either increase -search.maxUniqueTimeseries or use more specific tag filters", maxMetrics)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(metricIDsForTimeRange) <= maxTimeRangeMetrics {
|
||||
return nil, metricIDsForTimeRange, nil
|
||||
}
|
||||
|
||||
// Slow path: try to select the tag filter without maxMetrics adjustement.
|
||||
minTf, minMetricIDs, err = is.getTagFilterWithMinMetricIDsCountAdaptive(tfs, maxMetrics)
|
||||
if err == nil {
|
||||
return minTf, minMetricIDs, nil
|
||||
}
|
||||
if err != errTooManyMetrics {
|
||||
return nil, nil, err
|
||||
}
|
||||
return nil, nil, fmt.Errorf("more than %d time series found on the time range %s; either increase -search.maxUniqueTimeseries or shrink the time range",
|
||||
maxMetrics, tr.String())
|
||||
}
|
||||
|
||||
const maxDaysForDateMetricIDs = 40
|
||||
|
||||
func (is *indexSearch) adjustMaxMetricsAdaptive(tr TimeRange, maxMetrics int) int {
|
||||
minDate := uint64(tr.MinTimestamp) / msecPerDay
|
||||
maxDate := uint64(tr.MaxTimestamp) / msecPerDay
|
||||
if maxDate-minDate > maxDaysForDateMetricIDs {
|
||||
// Cannot reduce maxMetrics for the given time range,
|
||||
// since it is expensive extracting metricIDs for the given tr.
|
||||
return maxMetrics
|
||||
}
|
||||
hmPrev := is.db.prevHourMetricIDs.Load().(*hourMetricIDs)
|
||||
if !hmPrev.isFull {
|
||||
return maxMetrics
|
||||
}
|
||||
hourMetrics := len(hmPrev.m)
|
||||
if hourMetrics >= 256 && maxMetrics > hourMetrics/4 {
|
||||
// It is cheaper to filter on the hour or day metrics if the minimum
|
||||
// number of matching metrics across tfs exceeds hourMetrics / 4.
|
||||
return hourMetrics / 4
|
||||
}
|
||||
return maxMetrics
|
||||
}
|
||||
|
||||
func (is *indexSearch) getTagFilterWithMinMetricIDsCountAdaptive(tfs *TagFilters, maxMetrics int) (*tagFilter, map[uint64]struct{}, error) {
|
||||
kb := &is.kb
|
||||
kb.B = append(kb.B[:0], uselessMultiTagFiltersKeyPrefix)
|
||||
@@ -1284,29 +1363,6 @@ func (is *indexSearch) getTagFilterWithMinMetricIDsCountAdaptive(tfs *TagFilters
|
||||
|
||||
var errTooManyMetrics = errors.New("all the tag filters match too many metrics")
|
||||
|
||||
const maxDaysForDateMetricIDs = 40
|
||||
|
||||
func (is *indexSearch) adjustMaxMetricsAdaptive(tr TimeRange, maxMetrics int) int {
|
||||
minDate := uint64(tr.MinTimestamp) / msecPerDay
|
||||
maxDate := uint64(tr.MaxTimestamp) / msecPerDay
|
||||
if maxDate-minDate > maxDaysForDateMetricIDs {
|
||||
// Cannot reduce maxMetrics for the given time range,
|
||||
// since the it is expensive extracting metricIDs for the given tr.
|
||||
return maxMetrics
|
||||
}
|
||||
hmPrev := is.db.prevHourMetricIDs.Load().(*hourMetricIDs)
|
||||
if !hmPrev.isFull {
|
||||
return maxMetrics
|
||||
}
|
||||
hourMetrics := len(hmPrev.m)
|
||||
if hourMetrics >= 256 && maxMetrics > hourMetrics/4 {
|
||||
// It is cheaper to filter on the hour or day metrics if the minimum
|
||||
// number of matching metrics across tfs exceeds hourMetrics / 4.
|
||||
return hourMetrics / 4
|
||||
}
|
||||
return maxMetrics
|
||||
}
|
||||
|
||||
func (is *indexSearch) getTagFilterWithMinMetricIDsCount(tfs *TagFilters, maxMetrics int) (*tagFilter, map[uint64]struct{}, error) {
|
||||
var minMetricIDs map[uint64]struct{}
|
||||
var minTf *tagFilter
|
||||
@@ -1481,37 +1537,9 @@ func (is *indexSearch) updateMetricIDsForTagFilters(metricIDs map[uint64]struct{
|
||||
// Sort tag filters for faster ts.Seek below.
|
||||
sort.Slice(tfs.tfs, func(i, j int) bool { return bytes.Compare(tfs.tfs[i].prefix, tfs.tfs[j].prefix) < 0 })
|
||||
|
||||
maxMetricsAdjusted := is.adjustMaxMetricsAdaptive(tr, maxMetrics)
|
||||
minTf, minMetricIDs, err := is.getTagFilterWithMinMetricIDsCountAdaptive(tfs, maxMetricsAdjusted)
|
||||
minTf, minMetricIDs, err := is.getTagFilterWithMinMetricIDsCountOptimized(tfs, tr, maxMetrics)
|
||||
if err != nil {
|
||||
if err != errTooManyMetrics {
|
||||
return err
|
||||
}
|
||||
|
||||
// All the tag filters match too many metrics.
|
||||
|
||||
// Slow path: try filtering the matching metrics by time range.
|
||||
// This should work well for cases when old metrics are constantly substituted
|
||||
// by big number of new metrics. For example, prometheus-operator creates many new
|
||||
// metrics for each new deployment.
|
||||
//
|
||||
// Allow fetching up to 20*maxMetrics metrics for the given time range
|
||||
// in the hope these metricIDs will be filtered out by other filters below.
|
||||
maxTimeRangeMetrics := 20 * maxMetrics
|
||||
metricIDsForTimeRange, err := is.getMetricIDsForTimeRange(tr, maxTimeRangeMetrics+1)
|
||||
if err == errMissingMetricIDsForDate {
|
||||
return fmt.Errorf("cannot find tag filter matching less than %d time series; either increase -search.maxUniqueTimeseries or use more specific tag filters",
|
||||
maxMetrics)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(metricIDsForTimeRange) > maxTimeRangeMetrics {
|
||||
return fmt.Errorf("more than %d time series found on the time range %s; either increase -search.maxUniqueTimeseries or shrink the time range",
|
||||
maxTimeRangeMetrics, tr.String())
|
||||
}
|
||||
minMetricIDs = metricIDsForTimeRange
|
||||
minTf = nil
|
||||
return err
|
||||
}
|
||||
|
||||
// Find intersection of minTf with other tfs.
|
||||
|
||||
@@ -3,6 +3,7 @@ package storage
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
@@ -11,6 +12,34 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/workingsetcache"
|
||||
)
|
||||
|
||||
func BenchmarkRegexpFilterMatch(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
re := regexp.MustCompile(`.*foo-bar-baz.*`)
|
||||
b := []byte("fdsffd foo-bar-baz assd fdsfad dasf dsa")
|
||||
for pb.Next() {
|
||||
if !re.Match(b) {
|
||||
panic("BUG: regexp must match!")
|
||||
}
|
||||
b[0]++
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkRegexpFilterMismatch(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
re := regexp.MustCompile(`.*foo-bar-baz.*`)
|
||||
b := []byte("fdsffd foo-bar sfddsf assd nmn,mfdsdsakj")
|
||||
for pb.Next() {
|
||||
if re.Match(b) {
|
||||
panic("BUG: regexp mustn't match!")
|
||||
}
|
||||
b[0]++
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkIndexDBAddTSIDs(b *testing.B) {
|
||||
const recordsPerLoop = 1e3
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
|
||||
)
|
||||
|
||||
@@ -392,9 +393,18 @@ const maxLabelNameLen = 256
|
||||
const maxLabelValueLen = 16 * 1024
|
||||
|
||||
// The maximum number of labels per each timeseries.
|
||||
var maxLabelsPerTimeseries = 30
|
||||
|
||||
// SetMaxLabelsPerTimeseries sets the limit on the number of labels
|
||||
// per each time series.
|
||||
//
|
||||
// Superflouos lables are dropped.
|
||||
const maxLabelsPerTimeseries = 30
|
||||
// Superfouos labels are dropped.
|
||||
func SetMaxLabelsPerTimeseries(maxLabels int) {
|
||||
if maxLabels <= 0 {
|
||||
logger.Panicf("BUG: maxLabels must be positive; got %d", maxLabels)
|
||||
}
|
||||
maxLabelsPerTimeseries = maxLabels
|
||||
}
|
||||
|
||||
// MarshalMetricNameRaw marshals labels to dst and returns the result.
|
||||
//
|
||||
|
||||
@@ -144,7 +144,7 @@ func (p *part) MustClose() {
|
||||
p.valuesFile.MustClose()
|
||||
p.indexFile.MustClose()
|
||||
|
||||
isBig := p.ph.RowsCount > maxRowsPerSmallPart
|
||||
isBig := p.ph.RowsCount > maxRowsPerSmallPart()
|
||||
p.ibCache.Reset(isBig)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,23 +19,18 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/memory"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// The maximum number of rows in a small part.
|
||||
//
|
||||
// Small part merges cannot be interrupted during server stop, so this value
|
||||
// must be small enough to complete a merge
|
||||
// of `maxRowsPerSmallPart * defaultPartsToMerge` rows in a reasonable amount
|
||||
// of time (up to a a minute).
|
||||
//
|
||||
// Additionally, this number limits the maximum size of small parts storage.
|
||||
// Production simultation shows that the required size of the storage
|
||||
// may be estimated as:
|
||||
//
|
||||
// maxRowsPerSmallPart * 2 * defaultPartsToMerge * mergeWorkers
|
||||
//
|
||||
const maxRowsPerSmallPart = 300e6
|
||||
func maxRowsPerSmallPart() uint64 {
|
||||
// Small parts are cached in the OS page cache,
|
||||
// so limit the number of rows for small part
|
||||
// by the remaining free RAM.
|
||||
mem := memory.Remaining()
|
||||
if mem <= 0 {
|
||||
return 100e6
|
||||
}
|
||||
return uint64(mem) / defaultPartsToMerge
|
||||
}
|
||||
|
||||
// The maximum number of rows per big part.
|
||||
//
|
||||
@@ -813,8 +808,8 @@ func (pt *partition) partsMerger(mergerFunc func(isFinal bool) error) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (pt *partition) maxOutPartRows() uint64 {
|
||||
freeSpace := mustGetFreeDiskSpace(pt.bigPartsPath)
|
||||
func maxRowsByPath(path string) uint64 {
|
||||
freeSpace := mustGetFreeDiskSpace(path)
|
||||
|
||||
// Calculate the maximum number of rows in the output merge part
|
||||
// by dividing the freeSpace by the number of concurrent
|
||||
@@ -822,7 +817,11 @@ func (pt *partition) maxOutPartRows() uint64 {
|
||||
// This assumes each row is compressed into 1 byte. Production
|
||||
// simulation shows that each row usually occupies up to 0.5 bytes,
|
||||
// so this is quite safe assumption.
|
||||
return freeSpace / uint64(mergeWorkers)
|
||||
maxRows := freeSpace / uint64(mergeWorkers)
|
||||
if maxRows > maxRowsPerBigPart {
|
||||
maxRows = maxRowsPerBigPart
|
||||
}
|
||||
return maxRows
|
||||
}
|
||||
|
||||
func mustGetFreeDiskSpace(path string) uint64 {
|
||||
@@ -838,18 +837,7 @@ func mustGetFreeDiskSpace(path string) uint64 {
|
||||
|
||||
// Slow path.
|
||||
// Determine the amount of free space on bigPartsPath.
|
||||
d, err := os.Open(path)
|
||||
if err != nil {
|
||||
logger.Panicf("FATAL: cannot determine free disk space on %q: %s", path, err)
|
||||
}
|
||||
defer fs.MustClose(d)
|
||||
|
||||
fd := d.Fd()
|
||||
var stat unix.Statfs_t
|
||||
if err := unix.Fstatfs(int(fd), &stat); err != nil {
|
||||
logger.Panicf("FATAL: cannot determine free disk space on %q: %s", path, err)
|
||||
}
|
||||
e.freeSpace = stat.Bavail * uint64(stat.Bsize)
|
||||
e.freeSpace = fs.MustGetFreeSpace(path)
|
||||
e.updateTime = time.Now()
|
||||
freeSpaceMap[path] = e
|
||||
return e.freeSpace
|
||||
@@ -866,10 +854,7 @@ type freeSpaceEntry struct {
|
||||
}
|
||||
|
||||
func (pt *partition) mergeBigParts(isFinal bool) error {
|
||||
maxRows := pt.maxOutPartRows()
|
||||
if maxRows > maxRowsPerBigPart {
|
||||
maxRows = maxRowsPerBigPart
|
||||
}
|
||||
maxRows := maxRowsByPath(pt.bigPartsPath)
|
||||
|
||||
pt.partsLock.Lock()
|
||||
pws := getPartsToMerge(pt.bigParts, maxRows, isFinal)
|
||||
@@ -888,7 +873,15 @@ func (pt *partition) mergeBigParts(isFinal bool) error {
|
||||
}
|
||||
|
||||
func (pt *partition) mergeSmallParts(isFinal bool) error {
|
||||
maxRows := uint64(maxRowsPerSmallPart * defaultPartsToMerge)
|
||||
maxRows := maxRowsByPath(pt.smallPartsPath)
|
||||
if maxRows > maxRowsPerSmallPart() {
|
||||
// The output part may go to big part,
|
||||
// so make sure it as enough space.
|
||||
maxBigPartRows := maxRowsByPath(pt.bigPartsPath)
|
||||
if maxRows > maxBigPartRows {
|
||||
maxRows = maxBigPartRows
|
||||
}
|
||||
}
|
||||
|
||||
pt.partsLock.Lock()
|
||||
pws := getPartsToMerge(pt.smallParts, maxRows, isFinal)
|
||||
@@ -951,7 +944,7 @@ func (pt *partition) mergeParts(pws []*partWrapper, stopCh <-chan struct{}) erro
|
||||
for _, pw := range pws {
|
||||
outRowsCount += pw.p.ph.RowsCount
|
||||
}
|
||||
isBigPart := outRowsCount > maxRowsPerSmallPart
|
||||
isBigPart := outRowsCount > maxRowsPerSmallPart()
|
||||
nocache := isBigPart
|
||||
|
||||
// Prepare BlockStreamWriter for destination part.
|
||||
|
||||
@@ -6,11 +6,8 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPartitionMaxOutPartRows(t *testing.T) {
|
||||
pt := &partition{
|
||||
bigPartsPath: ".",
|
||||
}
|
||||
n := pt.maxOutPartRows()
|
||||
func TestPartitionMaxRowsByPath(t *testing.T) {
|
||||
n := maxRowsByPath(".")
|
||||
if n < 1e3 {
|
||||
t.Fatalf("too small number of rows can be created in the current directory: %d", n)
|
||||
}
|
||||
|
||||
@@ -769,7 +769,9 @@ var (
|
||||
)
|
||||
|
||||
func (s *Storage) add(rows []rawRow, mrs []MetricRow, precisionBits uint8) ([]rawRow, error) {
|
||||
var errors []error
|
||||
// Return only the last error, since it has no sense in returning all errors.
|
||||
var lastError error
|
||||
|
||||
var is *indexSearch
|
||||
var mn *MetricName
|
||||
var kb *bytesutil.ByteBuffer
|
||||
@@ -792,11 +794,13 @@ func (s *Storage) add(rows []rawRow, mrs []MetricRow, precisionBits uint8) ([]ra
|
||||
}
|
||||
if mr.Timestamp < minTimestamp {
|
||||
// Skip rows with too small timestamps outside the retention.
|
||||
lastError = 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.
|
||||
lastError = 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
|
||||
}
|
||||
@@ -826,8 +830,7 @@ func (s *Storage) add(rows []rawRow, mrs []MetricRow, precisionBits uint8) ([]ra
|
||||
// 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.
|
||||
err = fmt.Errorf("cannot unmarshal MetricNameRaw %q: %s", mr.MetricNameRaw, err)
|
||||
errors = append(errors, err)
|
||||
lastError = fmt.Errorf("cannot unmarshal MetricNameRaw %q: %s", mr.MetricNameRaw, err)
|
||||
j--
|
||||
continue
|
||||
}
|
||||
@@ -837,8 +840,7 @@ func (s *Storage) add(rows []rawRow, mrs []MetricRow, precisionBits uint8) ([]ra
|
||||
// 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.
|
||||
err = fmt.Errorf("cannot obtain TSID for MetricName %q: %s", kb.B, err)
|
||||
errors = append(errors, err)
|
||||
lastError = fmt.Errorf("cannot obtain TSID for MetricName %q: %s", kb.B, err)
|
||||
j--
|
||||
continue
|
||||
}
|
||||
@@ -852,18 +854,16 @@ func (s *Storage) add(rows []rawRow, mrs []MetricRow, precisionBits uint8) ([]ra
|
||||
rows = rows[:rowsLen+j]
|
||||
|
||||
if err := s.tb.AddRows(rows); err != nil {
|
||||
err = fmt.Errorf("cannot add rows to table: %s", err)
|
||||
errors = append(errors, err)
|
||||
lastError = fmt.Errorf("cannot add rows to table: %s", err)
|
||||
}
|
||||
errors = s.updateDateMetricIDCache(rows, errors)
|
||||
if len(errors) > 0 {
|
||||
// Return only the first error, since it has no sense in returning all errors.
|
||||
return rows, fmt.Errorf("errors occurred during rows addition: %s", errors[0])
|
||||
lastError = s.updateDateMetricIDCache(rows, lastError)
|
||||
if lastError != nil {
|
||||
return rows, fmt.Errorf("errors occurred during rows addition: %s", lastError)
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func (s *Storage) updateDateMetricIDCache(rows []rawRow, errors []error) []error {
|
||||
func (s *Storage) updateDateMetricIDCache(rows []rawRow, lastError error) error {
|
||||
var date uint64
|
||||
var hour uint64
|
||||
var prevTimestamp int64
|
||||
@@ -905,11 +905,11 @@ func (s *Storage) updateDateMetricIDCache(rows []rawRow, errors []error) []error
|
||||
// by concurrent goroutines.
|
||||
s.dateMetricIDCache.Set(keyBuf, nil)
|
||||
if err := idb.storeDateMetricID(date, metricID); err != nil {
|
||||
errors = append(errors, err)
|
||||
lastError = err
|
||||
continue
|
||||
}
|
||||
}
|
||||
return errors
|
||||
return lastError
|
||||
}
|
||||
|
||||
func (s *Storage) updateCurrHourMetricIDs() {
|
||||
|
||||
@@ -349,7 +349,8 @@ func testStorageRandTimestamps(s *Storage) error {
|
||||
mrs = append(mrs, mr)
|
||||
}
|
||||
if err := s.AddRows(mrs, defaultPrecisionBits); err != nil {
|
||||
if !strings.Contains(err.Error(), "too big timestamp") {
|
||||
errStr := err.Error()
|
||||
if !strings.Contains(errStr, "too big timestamp") && !strings.Contains(errStr, "too small timestamp") {
|
||||
return fmt.Errorf("unexpected error when adding mrs: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -592,15 +592,18 @@ func extractRegexpPrefix(b []byte) ([]byte, []byte) {
|
||||
if re == emptyRegexp {
|
||||
return nil, nil
|
||||
}
|
||||
if re.Op == syntax.OpLiteral {
|
||||
if re.Op == syntax.OpLiteral && re.Flags&syntax.FoldCase == 0 {
|
||||
return []byte(string(re.Rune)), nil
|
||||
}
|
||||
var prefix []byte
|
||||
if re.Op == syntax.OpConcat && re.Sub[0].Op == syntax.OpLiteral {
|
||||
prefix = []byte(string(re.Sub[0].Rune))
|
||||
re.Sub = re.Sub[1:]
|
||||
if len(re.Sub) == 0 {
|
||||
return nil, nil
|
||||
if re.Op == syntax.OpConcat {
|
||||
sub0 := re.Sub[0]
|
||||
if sub0.Op == syntax.OpLiteral && sub0.Flags&syntax.FoldCase == 0 {
|
||||
prefix = []byte(string(sub0.Rune))
|
||||
re.Sub = re.Sub[1:]
|
||||
if len(re.Sub) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if _, err := syntax.Compile(re); err != nil {
|
||||
|
||||
@@ -5,6 +5,21 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestExtractRegexpPrefix(t *testing.T) {
|
||||
f := func(s string, expectedPrefix, expectedSuffix string) {
|
||||
t.Helper()
|
||||
prefix, suffix := extractRegexpPrefix([]byte(s))
|
||||
if string(prefix) != expectedPrefix {
|
||||
t.Fatalf("unexpected prefix for %q; got %q; want %q", s, prefix, expectedPrefix)
|
||||
}
|
||||
if string(suffix) != expectedSuffix {
|
||||
t.Fatalf("unexpected suffix for %q; got %q; want %q", s, suffix, expectedSuffix)
|
||||
}
|
||||
}
|
||||
f("", "", "")
|
||||
f("foobar", "foobar", "")
|
||||
}
|
||||
|
||||
func TestGetRegexpFromCache(t *testing.T) {
|
||||
f := func(s string, orValuesExpected, expectedMatches, expectedMismatches []string) {
|
||||
t.Helper()
|
||||
@@ -397,67 +412,73 @@ func TestGetOrValues(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetRegexpPrefix(t *testing.T) {
|
||||
testGetRegexpPrefix(t, "", "", "")
|
||||
testGetRegexpPrefix(t, "^", "", "")
|
||||
testGetRegexpPrefix(t, "$", "", "")
|
||||
testGetRegexpPrefix(t, "^()$", "", "")
|
||||
testGetRegexpPrefix(t, "^(?:)$", "", "")
|
||||
testGetRegexpPrefix(t, "foobar", "foobar", "")
|
||||
testGetRegexpPrefix(t, "foo$|^foobar", "foo", "(?:(?:)|bar)")
|
||||
testGetRegexpPrefix(t, "^(foo$|^foobar)$", "foo", "(?:(?:)|bar)")
|
||||
testGetRegexpPrefix(t, "foobar|foobaz", "fooba", "[rz]")
|
||||
testGetRegexpPrefix(t, "(fo|(zar|bazz)|x)", "", "fo|zar|bazz|x")
|
||||
testGetRegexpPrefix(t, "(тестЧЧ|тест)", "тест", "(?:ЧЧ|(?:))")
|
||||
testGetRegexpPrefix(t, "foo(bar|baz|bana)", "fooba", "(?:[rz]|na)")
|
||||
testGetRegexpPrefix(t, "^foobar|foobaz", "fooba", "[rz]")
|
||||
testGetRegexpPrefix(t, "^foobar|^foobaz$", "fooba", "[rz]")
|
||||
testGetRegexpPrefix(t, "foobar|foobaz", "fooba", "[rz]")
|
||||
testGetRegexpPrefix(t, "(?:^foobar|^foobaz)aa.*", "fooba", "[rz]aa(?-s:.)*")
|
||||
testGetRegexpPrefix(t, "foo[bar]+", "foo", "[a-br]+")
|
||||
testGetRegexpPrefix(t, "foo[a-z]+", "foo", "[a-z]+")
|
||||
testGetRegexpPrefix(t, "foo[bar]*", "foo", "[a-br]*")
|
||||
testGetRegexpPrefix(t, "foo[a-z]*", "foo", "[a-z]*")
|
||||
testGetRegexpPrefix(t, "foo[x]+", "foo", "x+")
|
||||
testGetRegexpPrefix(t, "foo[^x]+", "foo", "[^x]+")
|
||||
testGetRegexpPrefix(t, "foo[x]*", "foo", "x*")
|
||||
testGetRegexpPrefix(t, "foo[^x]*", "foo", "[^x]*")
|
||||
testGetRegexpPrefix(t, "foo[x]*bar", "foo", "x*bar")
|
||||
testGetRegexpPrefix(t, "fo\\Bo[x]*bar?", "fo", "\\Box*bar?")
|
||||
f := func(t *testing.T, s, expectedPrefix, expectedSuffix string) {
|
||||
t.Helper()
|
||||
|
||||
prefix, suffix := getRegexpPrefix([]byte(s))
|
||||
if string(prefix) != expectedPrefix {
|
||||
t.Fatalf("unexpected prefix for s=%q; got %q; want %q", s, prefix, expectedPrefix)
|
||||
}
|
||||
if string(suffix) != expectedSuffix {
|
||||
t.Fatalf("unexpected suffix for s=%q; got %q; want %q", s, suffix, expectedSuffix)
|
||||
}
|
||||
|
||||
// Get the prefix from cache.
|
||||
prefix, suffix = getRegexpPrefix([]byte(s))
|
||||
if string(prefix) != expectedPrefix {
|
||||
t.Fatalf("unexpected prefix for s=%q; got %q; want %q", s, prefix, expectedPrefix)
|
||||
}
|
||||
if string(suffix) != expectedSuffix {
|
||||
t.Fatalf("unexpected suffix for s=%q; got %q; want %q", s, suffix, expectedSuffix)
|
||||
}
|
||||
}
|
||||
|
||||
f(t, "", "", "")
|
||||
f(t, "^", "", "")
|
||||
f(t, "$", "", "")
|
||||
f(t, "^()$", "", "")
|
||||
f(t, "^(?:)$", "", "")
|
||||
f(t, "foobar", "foobar", "")
|
||||
f(t, "foo$|^foobar", "foo", "(?:(?:)|bar)")
|
||||
f(t, "^(foo$|^foobar)$", "foo", "(?:(?:)|bar)")
|
||||
f(t, "foobar|foobaz", "fooba", "[rz]")
|
||||
f(t, "(fo|(zar|bazz)|x)", "", "fo|zar|bazz|x")
|
||||
f(t, "(тестЧЧ|тест)", "тест", "(?:ЧЧ|(?:))")
|
||||
f(t, "foo(bar|baz|bana)", "fooba", "(?:[rz]|na)")
|
||||
f(t, "^foobar|foobaz", "fooba", "[rz]")
|
||||
f(t, "^foobar|^foobaz$", "fooba", "[rz]")
|
||||
f(t, "foobar|foobaz", "fooba", "[rz]")
|
||||
f(t, "(?:^foobar|^foobaz)aa.*", "fooba", "[rz]aa(?-s:.)*")
|
||||
f(t, "foo[bar]+", "foo", "[a-br]+")
|
||||
f(t, "foo[a-z]+", "foo", "[a-z]+")
|
||||
f(t, "foo[bar]*", "foo", "[a-br]*")
|
||||
f(t, "foo[a-z]*", "foo", "[a-z]*")
|
||||
f(t, "foo[x]+", "foo", "x+")
|
||||
f(t, "foo[^x]+", "foo", "[^x]+")
|
||||
f(t, "foo[x]*", "foo", "x*")
|
||||
f(t, "foo[^x]*", "foo", "[^x]*")
|
||||
f(t, "foo[x]*bar", "foo", "x*bar")
|
||||
f(t, "fo\\Bo[x]*bar?", "fo", "\\Box*bar?")
|
||||
f(t, "foo.+bar", "foo", "(?-s:.)+bar")
|
||||
f(t, "a(b|c.*).+", "a", "(?:b|c(?-s:.)*)(?-s:.)+")
|
||||
f(t, "ab|ac", "a", "[b-c]")
|
||||
f(t, "(?i)xyz", "", "(?i:XYZ)")
|
||||
f(t, "(?i)up.+x", "", "(?i:UP)(?-s:.)+(?i:X)")
|
||||
f(t, "(?smi)xy.*z$", "", "(?i:XY)(?s:.)*(?i:Z)(?m:$)")
|
||||
|
||||
// test invalid regexps
|
||||
testGetRegexpPrefix(t, "a(", "a(", "")
|
||||
testGetRegexpPrefix(t, "a[", "a[", "")
|
||||
testGetRegexpPrefix(t, "a[]", "a[]", "")
|
||||
testGetRegexpPrefix(t, "a{", "a{", "")
|
||||
testGetRegexpPrefix(t, "a{}", "a{}", "")
|
||||
testGetRegexpPrefix(t, "invalid(regexp", "invalid(regexp", "")
|
||||
f(t, "a(", "a(", "")
|
||||
f(t, "a[", "a[", "")
|
||||
f(t, "a[]", "a[]", "")
|
||||
f(t, "a{", "a{", "")
|
||||
f(t, "a{}", "a{}", "")
|
||||
f(t, "invalid(regexp", "invalid(regexp", "")
|
||||
|
||||
// The transformed regexp mustn't match aba
|
||||
testGetRegexpPrefix(t, "a?(^ba|c)", "", "a?(?:\\Aba|c)")
|
||||
f(t, "a?(^ba|c)", "", "a?(?:\\Aba|c)")
|
||||
|
||||
// The transformed regexp mustn't match barx
|
||||
testGetRegexpPrefix(t, "(foo|bar$)x*", "", "(?:foo|bar(?-m:$))x*")
|
||||
}
|
||||
|
||||
func testGetRegexpPrefix(t *testing.T, s, expectedPrefix, expectedSuffix string) {
|
||||
t.Helper()
|
||||
|
||||
prefix, suffix := getRegexpPrefix([]byte(s))
|
||||
if string(prefix) != expectedPrefix {
|
||||
t.Fatalf("unexpected prefix for s=%q; got %q; want %q", s, prefix, expectedPrefix)
|
||||
}
|
||||
if string(suffix) != expectedSuffix {
|
||||
t.Fatalf("unexpected suffix for s=%q; got %q; want %q", s, suffix, expectedSuffix)
|
||||
}
|
||||
|
||||
// Get the prefix from cache.
|
||||
prefix, suffix = getRegexpPrefix([]byte(s))
|
||||
if string(prefix) != expectedPrefix {
|
||||
t.Fatalf("unexpected prefix for s=%q; got %q; want %q", s, prefix, expectedPrefix)
|
||||
}
|
||||
if string(suffix) != expectedSuffix {
|
||||
t.Fatalf("unexpected suffix for s=%q; got %q; want %q", s, suffix, expectedSuffix)
|
||||
}
|
||||
f(t, "(foo|bar$)x*", "", "(?:foo|bar(?-m:$))x*")
|
||||
}
|
||||
|
||||
func TestTagFiltersAddEmpty(t *testing.T) {
|
||||
|
||||
8
vendor/github.com/klauspost/compress/fse/decompress.go
generated
vendored
8
vendor/github.com/klauspost/compress/fse/decompress.go
generated
vendored
@@ -243,7 +243,7 @@ func (s *Scratch) buildDtable() error {
|
||||
nBits := s.actualTableLog - byte(highBits(uint32(nextState)))
|
||||
s.decTable[u].nbBits = nBits
|
||||
newState := (nextState << nBits) - tableSize
|
||||
if newState > tableSize {
|
||||
if newState >= tableSize {
|
||||
return fmt.Errorf("newState (%d) outside table size (%d)", newState, tableSize)
|
||||
}
|
||||
if newState == uint16(u) && nBits == 0 {
|
||||
@@ -281,8 +281,12 @@ func (s *Scratch) decompress() error {
|
||||
tmp[off+2] = s1.nextFast()
|
||||
tmp[off+3] = s2.nextFast()
|
||||
off += 4
|
||||
// When off is 0, we have overflowed and should write.
|
||||
if off == 0 {
|
||||
s.Out = append(s.Out, tmp...)
|
||||
if len(s.Out) >= s.DecompressLimit {
|
||||
return fmt.Errorf("output size (%d) > DecompressLimit (%d)", len(s.Out), s.DecompressLimit)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -296,7 +300,7 @@ func (s *Scratch) decompress() error {
|
||||
off += 4
|
||||
if off == 0 {
|
||||
s.Out = append(s.Out, tmp...)
|
||||
off = 0
|
||||
// When off is 0, we have overflowed and should write.
|
||||
if len(s.Out) >= s.DecompressLimit {
|
||||
return fmt.Errorf("output size (%d) > DecompressLimit (%d)", len(s.Out), s.DecompressLimit)
|
||||
}
|
||||
|
||||
10
vendor/github.com/klauspost/compress/huff0/decompress.go
generated
vendored
10
vendor/github.com/klauspost/compress/huff0/decompress.go
generated
vendored
@@ -283,7 +283,7 @@ bigloop:
|
||||
off += 2
|
||||
if off == bufoff {
|
||||
if bufoff > dstEvery {
|
||||
return nil, errors.New("corruption detected: stream overrun")
|
||||
return nil, errors.New("corruption detected: stream overrun 1")
|
||||
}
|
||||
copy(dstOut, tmp[:bufoff])
|
||||
copy(dstOut[dstEvery:], tmp[bufoff:bufoff*2])
|
||||
@@ -292,15 +292,15 @@ bigloop:
|
||||
off = 0
|
||||
dstOut = dstOut[bufoff:]
|
||||
// There must at least be 3 buffers left.
|
||||
if len(dstOut) < dstEvery*3+3 {
|
||||
return nil, errors.New("corruption detected: stream overrun")
|
||||
if len(dstOut) < dstEvery*3 {
|
||||
return nil, errors.New("corruption detected: stream overrun 2")
|
||||
}
|
||||
}
|
||||
}
|
||||
if off > 0 {
|
||||
ioff := int(off)
|
||||
if len(dstOut) < dstEvery*3+ioff {
|
||||
return nil, errors.New("corruption detected: stream overrun")
|
||||
return nil, errors.New("corruption detected: stream overrun 3")
|
||||
}
|
||||
copy(dstOut, tmp[:off])
|
||||
copy(dstOut[dstEvery:dstEvery+ioff], tmp[bufoff:bufoff*2])
|
||||
@@ -315,7 +315,7 @@ bigloop:
|
||||
for !br.finished() {
|
||||
br.fill()
|
||||
if offset >= len(dstOut) {
|
||||
return nil, errors.New("corruption detected: stream overrun")
|
||||
return nil, errors.New("corruption detected: stream overrun 4")
|
||||
}
|
||||
dstOut[offset] = decode(br)
|
||||
offset++
|
||||
|
||||
5
vendor/github.com/klauspost/compress/zstd/bytebuf.go
generated
vendored
5
vendor/github.com/klauspost/compress/zstd/bytebuf.go
generated
vendored
@@ -116,6 +116,9 @@ func (r *readerWrapper) readByte() (byte, error) {
|
||||
}
|
||||
|
||||
func (r *readerWrapper) skipN(n int) error {
|
||||
_, err := io.CopyN(ioutil.Discard, r.r, int64(n))
|
||||
n2, err := io.CopyN(ioutil.Discard, r.r, int64(n))
|
||||
if n2 != int64(n) {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
15
vendor/github.com/klauspost/compress/zstd/encoder.go
generated
vendored
15
vendor/github.com/klauspost/compress/zstd/encoder.go
generated
vendored
@@ -257,7 +257,12 @@ func (e *Encoder) nextBlock(final bool) error {
|
||||
}
|
||||
s.wWg.Done()
|
||||
}()
|
||||
err := blk.encode()
|
||||
err := errIncompressible
|
||||
// If we got the exact same number of literals as input,
|
||||
// assume the literals cannot be compressed.
|
||||
if len(src) != len(blk.literals) || len(src) != e.o.blockSize {
|
||||
err = blk.encode()
|
||||
}
|
||||
switch err {
|
||||
case errIncompressible:
|
||||
if debug {
|
||||
@@ -444,7 +449,13 @@ func (e *Encoder) EncodeAll(src, dst []byte) []byte {
|
||||
if len(src) == 0 {
|
||||
blk.last = true
|
||||
}
|
||||
err := blk.encode()
|
||||
err := errIncompressible
|
||||
// If we got the exact same number of literals as input,
|
||||
// assume the literals cannot be compressed.
|
||||
if len(blk.literals) != len(todo) || len(todo) != e.o.blockSize {
|
||||
err = blk.encode()
|
||||
}
|
||||
|
||||
switch err {
|
||||
case errIncompressible:
|
||||
if debug {
|
||||
|
||||
1
vendor/github.com/valyala/fastjson/.gitignore
generated
vendored
Normal file
1
vendor/github.com/valyala/fastjson/.gitignore
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
tags
|
||||
19
vendor/github.com/valyala/fastjson/.travis.yml
generated
vendored
Normal file
19
vendor/github.com/valyala/fastjson/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.10.x
|
||||
|
||||
script:
|
||||
# build test for supported platforms
|
||||
- GOOS=linux go build
|
||||
- GOOS=darwin go build
|
||||
- GOOS=freebsd go build
|
||||
- GOOS=windows go build
|
||||
|
||||
# run tests on a standard platform
|
||||
- go test -v ./... -coverprofile=coverage.txt -covermode=atomic
|
||||
- go test -v ./... -race
|
||||
|
||||
after_success:
|
||||
# Upload coverage results to codecov.io
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
212
vendor/github.com/valyala/fastjson/README.md
generated
vendored
Normal file
212
vendor/github.com/valyala/fastjson/README.md
generated
vendored
Normal file
@@ -0,0 +1,212 @@
|
||||
[](https://travis-ci.org/valyala/fastjson)
|
||||
[](http://godoc.org/github.com/valyala/fastjson)
|
||||
[](https://goreportcard.com/report/github.com/valyala/fastjson)
|
||||
[](https://codecov.io/gh/valyala/fastjson)
|
||||
|
||||
# fastjson - fast JSON parser and validator for Go
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
* Fast. As usual, up to 15x faster than the standard [encoding/json](https://golang.org/pkg/encoding/json/).
|
||||
See [benchmarks](#benchmarks).
|
||||
* Parses arbitrary JSON without schema, reflection, struct magic and code generation
|
||||
contrary to [easyjson](https://github.com/mailru/easyjson).
|
||||
* Provides simple [API](http://godoc.org/github.com/valyala/fastjson).
|
||||
* Outperforms [jsonparser](https://github.com/buger/jsonparser) and [gjson](https://github.com/tidwall/gjson)
|
||||
when accessing multiple unrelated fields, since `fastjson` parses the input JSON only once.
|
||||
* Validates the parsed JSON unlike [jsonparser](https://github.com/buger/jsonparser)
|
||||
and [gjson](https://github.com/tidwall/gjson).
|
||||
* May quickly extract a part of the original JSON with `Value.Get(...).MarshalTo` and modify it
|
||||
with [Del](https://godoc.org/github.com/valyala/fastjson#Value.Del)
|
||||
and [Set](https://godoc.org/github.com/valyala/fastjson#Value.Set) functions.
|
||||
* May parse array containing values with distinct types (aka non-homogenous types).
|
||||
For instance, `fastjson` easily parses the following JSON array `[123, "foo", [456], {"k": "v"}, null]`.
|
||||
* `fastjson` preserves the original order of object items when calling
|
||||
[Object.Visit](https://godoc.org/github.com/valyala/fastjson#Object.Visit).
|
||||
|
||||
|
||||
## Known limitations
|
||||
|
||||
* Requies extra care to work with - references to certain objects recursively
|
||||
returned by [Parser](https://godoc.org/github.com/valyala/fastjson#Parser)
|
||||
must be released before the next call to [Parse](https://godoc.org/github.com/valyala/fastjson#Parser.Parse).
|
||||
Otherwise the program may work improperly. The same applies to objects returned by [Arena](https://godoc.org/github.com/valyala/fastjson#Arena).
|
||||
Adhere recommendations from [docs](https://godoc.org/github.com/valyala/fastjson).
|
||||
* Cannot parse JSON from `io.Reader`. There is [Scanner](https://godoc.org/github.com/valyala/fastjson#Scanner)
|
||||
for parsing stream of JSON values from a string.
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
One-liner accessing a single field:
|
||||
```go
|
||||
s := []byte(`{"foo": [123, "bar"]}`)
|
||||
fmt.Printf("foo.0=%d\n", fastjson.GetInt(s, "foo", "0"))
|
||||
|
||||
// Output:
|
||||
// foo.0=123
|
||||
```
|
||||
|
||||
Accessing multiple fields with error handling:
|
||||
```go
|
||||
var p fastjson.Parser
|
||||
v, err := p.Parse(`{
|
||||
"str": "bar",
|
||||
"int": 123,
|
||||
"float": 1.23,
|
||||
"bool": true,
|
||||
"arr": [1, "foo", {}]
|
||||
}`)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("foo=%s\n", v.GetStringBytes("str"))
|
||||
fmt.Printf("int=%d\n", v.GetInt("int"))
|
||||
fmt.Printf("float=%f\n", v.GetFloat64("float"))
|
||||
fmt.Printf("bool=%v\n", v.GetBool("bool"))
|
||||
fmt.Printf("arr.1=%s\n", v.GetStringBytes("arr", "1"))
|
||||
|
||||
// Output:
|
||||
// foo=bar
|
||||
// int=123
|
||||
// float=1.230000
|
||||
// bool=true
|
||||
// arr.1=foo
|
||||
```
|
||||
|
||||
See also [examples](https://godoc.org/github.com/valyala/fastjson#pkg-examples).
|
||||
|
||||
|
||||
## Security
|
||||
|
||||
* `fastjson` shouldn't crash or panic when parsing input strings specially crafted
|
||||
by an attacker. It must return error on invalid input JSON.
|
||||
* `fastjson` requires up to `sizeof(Value) * len(inputJSON)` bytes of memory
|
||||
for parsing `inputJSON` string. Limit the maximum size of the `inputJSON`
|
||||
before parsing it in order to limit the maximum memory usage.
|
||||
|
||||
|
||||
## Performance optimization tips
|
||||
|
||||
* Re-use [Parser](https://godoc.org/github.com/valyala/fastjson#Parser) and [Scanner](https://godoc.org/github.com/valyala/fastjson#Scanner)
|
||||
for parsing many JSONs. This reduces memory allocations overhead.
|
||||
[ParserPool](https://godoc.org/github.com/valyala/fastjson#ParserPool) may be useful in this case.
|
||||
* Prefer calling `Value.Get*` on the value returned from [Parser](https://godoc.org/github.com/valyala/fastjson#Parser)
|
||||
instead of calling `Get*` one-liners when multiple fields
|
||||
must be obtained from JSON, since each `Get*` one-liner re-parses
|
||||
the input JSON again.
|
||||
* Prefer calling once [Value.Get](https://godoc.org/github.com/valyala/fastjson#Value.Get)
|
||||
for common prefix paths and then calling `Value.Get*` on the returned value
|
||||
for distinct suffix paths.
|
||||
* Prefer iterating over array returned from [Value.GetArray](https://godoc.org/github.com/valyala/fastjson#Object.Visit)
|
||||
with a range loop instead of calling `Value.Get*` for each array item.
|
||||
|
||||
|
||||
## Benchmarks
|
||||
|
||||
Go 1.12 has been used for benchmarking.
|
||||
|
||||
Legend:
|
||||
|
||||
* `small` - parse [small.json](testdata/small.json) (190 bytes).
|
||||
* `medium` - parse [medium.json](testdata/medium.json) (2.3KB).
|
||||
* `large` - parse [large.json](testdata/large.json) (28KB).
|
||||
* `canada` - parse [canada.json](testdata/canada.json) (2.2MB).
|
||||
* `citm` - parse [citm_catalog.json](testdata/citm_catalog.json) (1.7MB).
|
||||
* `twitter` - parse [twitter.json](testdata/twitter.json) (617KB).
|
||||
|
||||
* `stdjson-map` - parse into a `map[string]interface{}` using `encoding/json`.
|
||||
* `stdjson-struct` - parse into a struct containing
|
||||
a subset of fields of the parsed JSON, using `encoding/json`.
|
||||
* `stdjson-empty-struct` - parse into an empty struct using `encoding/json`.
|
||||
This is the fastest possible solution for `encoding/json`, may be used
|
||||
for json validation. See also benchmark results for json validation.
|
||||
* `fastjson` - parse using `fastjson` without fields access.
|
||||
* `fastjson-get` - parse using `fastjson` with fields access similar to `stdjson-struct`.
|
||||
|
||||
```
|
||||
$ GOMAXPROCS=1 go test github.com/valyala/fastjson -bench='Parse$'
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
pkg: github.com/valyala/fastjson
|
||||
BenchmarkParse/small/stdjson-map 200000 7305 ns/op 26.01 MB/s 960 B/op 51 allocs/op
|
||||
BenchmarkParse/small/stdjson-struct 500000 3431 ns/op 55.37 MB/s 224 B/op 4 allocs/op
|
||||
BenchmarkParse/small/stdjson-empty-struct 500000 2273 ns/op 83.58 MB/s 168 B/op 2 allocs/op
|
||||
BenchmarkParse/small/fastjson 5000000 347 ns/op 547.53 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkParse/small/fastjson-get 2000000 620 ns/op 306.39 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkParse/medium/stdjson-map 30000 40672 ns/op 57.26 MB/s 10196 B/op 208 allocs/op
|
||||
BenchmarkParse/medium/stdjson-struct 30000 47792 ns/op 48.73 MB/s 9174 B/op 258 allocs/op
|
||||
BenchmarkParse/medium/stdjson-empty-struct 100000 22096 ns/op 105.40 MB/s 280 B/op 5 allocs/op
|
||||
BenchmarkParse/medium/fastjson 500000 3025 ns/op 769.90 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkParse/medium/fastjson-get 500000 3211 ns/op 725.20 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkParse/large/stdjson-map 2000 614079 ns/op 45.79 MB/s 210734 B/op 2785 allocs/op
|
||||
BenchmarkParse/large/stdjson-struct 5000 298554 ns/op 94.18 MB/s 15616 B/op 353 allocs/op
|
||||
BenchmarkParse/large/stdjson-empty-struct 5000 268577 ns/op 104.69 MB/s 280 B/op 5 allocs/op
|
||||
BenchmarkParse/large/fastjson 50000 35210 ns/op 798.56 MB/s 5 B/op 0 allocs/op
|
||||
BenchmarkParse/large/fastjson-get 50000 35171 ns/op 799.46 MB/s 5 B/op 0 allocs/op
|
||||
BenchmarkParse/canada/stdjson-map 20 68147307 ns/op 33.03 MB/s 12260502 B/op 392539 allocs/op
|
||||
BenchmarkParse/canada/stdjson-struct 20 68044518 ns/op 33.08 MB/s 12260123 B/op 392534 allocs/op
|
||||
BenchmarkParse/canada/stdjson-empty-struct 100 17709250 ns/op 127.11 MB/s 280 B/op 5 allocs/op
|
||||
BenchmarkParse/canada/fastjson 300 4182404 ns/op 538.22 MB/s 254902 B/op 381 allocs/op
|
||||
BenchmarkParse/canada/fastjson-get 300 4274744 ns/op 526.60 MB/s 254902 B/op 381 allocs/op
|
||||
BenchmarkParse/citm/stdjson-map 50 27772612 ns/op 62.19 MB/s 5214163 B/op 95402 allocs/op
|
||||
BenchmarkParse/citm/stdjson-struct 100 14936191 ns/op 115.64 MB/s 1989 B/op 75 allocs/op
|
||||
BenchmarkParse/citm/stdjson-empty-struct 100 14946034 ns/op 115.56 MB/s 280 B/op 5 allocs/op
|
||||
BenchmarkParse/citm/fastjson 1000 1879714 ns/op 918.87 MB/s 17628 B/op 30 allocs/op
|
||||
BenchmarkParse/citm/fastjson-get 1000 1881598 ns/op 917.94 MB/s 17628 B/op 30 allocs/op
|
||||
BenchmarkParse/twitter/stdjson-map 100 11289146 ns/op 55.94 MB/s 2187878 B/op 31266 allocs/op
|
||||
BenchmarkParse/twitter/stdjson-struct 300 5779442 ns/op 109.27 MB/s 408 B/op 6 allocs/op
|
||||
BenchmarkParse/twitter/stdjson-empty-struct 300 5738504 ns/op 110.05 MB/s 408 B/op 6 allocs/op
|
||||
BenchmarkParse/twitter/fastjson 2000 774042 ns/op 815.86 MB/s 2541 B/op 2 allocs/op
|
||||
BenchmarkParse/twitter/fastjson-get 2000 777833 ns/op 811.89 MB/s 2541 B/op 2 allocs/op
|
||||
```
|
||||
|
||||
Benchmark results for json validation:
|
||||
|
||||
```
|
||||
$ GOMAXPROCS=1 go test github.com/valyala/fastjson -bench='Validate$'
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
pkg: github.com/valyala/fastjson
|
||||
BenchmarkValidate/small/stdjson 2000000 955 ns/op 198.83 MB/s 72 B/op 2 allocs/op
|
||||
BenchmarkValidate/small/fastjson 5000000 384 ns/op 493.60 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkValidate/medium/stdjson 200000 10799 ns/op 215.66 MB/s 184 B/op 5 allocs/op
|
||||
BenchmarkValidate/medium/fastjson 300000 3809 ns/op 611.30 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkValidate/large/stdjson 10000 133064 ns/op 211.31 MB/s 184 B/op 5 allocs/op
|
||||
BenchmarkValidate/large/fastjson 30000 45268 ns/op 621.14 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkValidate/canada/stdjson 200 8470904 ns/op 265.74 MB/s 184 B/op 5 allocs/op
|
||||
BenchmarkValidate/canada/fastjson 500 2973377 ns/op 757.07 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkValidate/citm/stdjson 200 7273172 ns/op 237.48 MB/s 184 B/op 5 allocs/op
|
||||
BenchmarkValidate/citm/fastjson 1000 1684430 ns/op 1025.39 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkValidate/twitter/stdjson 500 2849439 ns/op 221.63 MB/s 312 B/op 6 allocs/op
|
||||
BenchmarkValidate/twitter/fastjson 2000 1036796 ns/op 609.10 MB/s 0 B/op 0 allocs/op
|
||||
```
|
||||
|
||||
## FAQ
|
||||
|
||||
* Q: _There are a ton of other high-perf packages for JSON parsing in Go. Why creating yet another package?_
|
||||
A: Because other packages require either rigid JSON schema via struct magic
|
||||
and code generation or perform poorly when multiple unrelated fields
|
||||
must be obtained from the parsed JSON.
|
||||
Additionally, `fastjson` provides nicer [API](http://godoc.org/github.com/valyala/fastjson).
|
||||
|
||||
* Q: _What is the main purpose for `fastjson`?_
|
||||
A: High-perf JSON parsing for [RTB](https://www.iab.com/wp-content/uploads/2015/05/OpenRTB_API_Specification_Version_2_3_1.pdf)
|
||||
and other [JSON-RPC](https://en.wikipedia.org/wiki/JSON-RPC) services.
|
||||
|
||||
* Q: _Why fastjson doesn't provide fast marshaling (serialization)?_
|
||||
A: Actually it provides some sort of marshaling - see [Value.MarshalTo](https://godoc.org/github.com/valyala/fastjson#Value.MarshalTo).
|
||||
But I'd recommend using [quicktemplate](https://github.com/valyala/quicktemplate#use-cases)
|
||||
for high-performance JSON marshaling :)
|
||||
|
||||
* Q: _`fastjson` crashes my program!_
|
||||
A: There is high probability of improper use.
|
||||
* Make sure you don't hold references to objects recursively returned by `Parser` / `Scanner`
|
||||
beyond the next `Parser.Parse` / `Scanner.Next` call
|
||||
if such restriction is mentioned in [docs](https://github.com/valyala/fastjson/issues/new).
|
||||
* Make sure you don't access `fastjson` objects from concurrently running goroutines
|
||||
if such restriction is mentioned in [docs](https://github.com/valyala/fastjson/issues/new).
|
||||
* Build and run your program with [-race](https://golang.org/doc/articles/race_detector.html) flag.
|
||||
Make sure the race detector detects zero races.
|
||||
* If your program continue crashing after fixing issues mentioned above, [file a bug](https://github.com/valyala/fastjson/issues/new).
|
||||
126
vendor/github.com/valyala/fastjson/arena.go
generated
vendored
Normal file
126
vendor/github.com/valyala/fastjson/arena.go
generated
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
package fastjson
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Arena may be used for fast creation and re-use of Values.
|
||||
//
|
||||
// Typical Arena lifecycle:
|
||||
//
|
||||
// 1) Construct Values via the Arena and Value.Set* calls.
|
||||
// 2) Marshal the constructed Values with Value.MarshalTo call.
|
||||
// 3) Reset all the constructed Values at once by Arena.Reset call.
|
||||
// 4) Go to 1 and re-use the Arena.
|
||||
//
|
||||
// It is unsafe calling Arena methods from concurrent goroutines.
|
||||
// Use per-goroutine Arenas or ArenaPool instead.
|
||||
type Arena struct {
|
||||
b []byte
|
||||
c cache
|
||||
}
|
||||
|
||||
// Reset resets all the Values allocated by a.
|
||||
//
|
||||
// Values previously allocated by a cannot be used after the Reset call.
|
||||
func (a *Arena) Reset() {
|
||||
a.b = a.b[:0]
|
||||
a.c.reset()
|
||||
}
|
||||
|
||||
// NewObject returns new empty object value.
|
||||
//
|
||||
// New entries may be added to the returned object via Set call.
|
||||
//
|
||||
// The returned object is valid until Reset is called on a.
|
||||
func (a *Arena) NewObject() *Value {
|
||||
v := a.c.getValue()
|
||||
v.t = TypeObject
|
||||
v.o.reset()
|
||||
return v
|
||||
}
|
||||
|
||||
// NewArray returns new empty array value.
|
||||
//
|
||||
// New entries may be added to the returned array via Set* calls.
|
||||
//
|
||||
// The returned array is valid until Reset is called on a.
|
||||
func (a *Arena) NewArray() *Value {
|
||||
v := a.c.getValue()
|
||||
v.t = TypeArray
|
||||
v.a = v.a[:0]
|
||||
return v
|
||||
}
|
||||
|
||||
// NewString returns new string value containing s.
|
||||
//
|
||||
// The returned string is valid until Reset is called on a.
|
||||
func (a *Arena) NewString(s string) *Value {
|
||||
v := a.c.getValue()
|
||||
v.t = typeRawString
|
||||
bLen := len(a.b)
|
||||
a.b = escapeString(a.b, s)
|
||||
v.s = b2s(a.b[bLen+1 : len(a.b)-1])
|
||||
return v
|
||||
}
|
||||
|
||||
// NewStringBytes returns new string value containing b.
|
||||
//
|
||||
// The returned string is valid until Reset is called on a.
|
||||
func (a *Arena) NewStringBytes(b []byte) *Value {
|
||||
v := a.c.getValue()
|
||||
v.t = typeRawString
|
||||
bLen := len(a.b)
|
||||
a.b = escapeString(a.b, b2s(b))
|
||||
v.s = b2s(a.b[bLen+1 : len(a.b)-1])
|
||||
return v
|
||||
}
|
||||
|
||||
// NewNumberFloat64 returns new number value containing f.
|
||||
//
|
||||
// The returned number is valid until Reset is called on a.
|
||||
func (a *Arena) NewNumberFloat64(f float64) *Value {
|
||||
v := a.c.getValue()
|
||||
v.t = TypeNumber
|
||||
bLen := len(a.b)
|
||||
a.b = strconv.AppendFloat(a.b, f, 'g', -1, 64)
|
||||
v.s = b2s(a.b[bLen:])
|
||||
return v
|
||||
}
|
||||
|
||||
// NewNumberInt returns new number value containing n.
|
||||
//
|
||||
// The returned number is valid until Reset is called on a.
|
||||
func (a *Arena) NewNumberInt(n int) *Value {
|
||||
v := a.c.getValue()
|
||||
v.t = TypeNumber
|
||||
bLen := len(a.b)
|
||||
a.b = strconv.AppendInt(a.b, int64(n), 10)
|
||||
v.s = b2s(a.b[bLen:])
|
||||
return v
|
||||
}
|
||||
|
||||
// NewNumberString returns new number value containing s.
|
||||
//
|
||||
// The returned number is valid until Reset is called on a.
|
||||
func (a *Arena) NewNumberString(s string) *Value {
|
||||
v := a.c.getValue()
|
||||
v.t = TypeNumber
|
||||
v.s = s
|
||||
return v
|
||||
}
|
||||
|
||||
// NewNull returns null value.
|
||||
func (a *Arena) NewNull() *Value {
|
||||
return valueNull
|
||||
}
|
||||
|
||||
// NewTrue returns true value.
|
||||
func (a *Arena) NewTrue() *Value {
|
||||
return valueTrue
|
||||
}
|
||||
|
||||
// NewFalse return false value.
|
||||
func (a *Arena) NewFalse() *Value {
|
||||
return valueFalse
|
||||
}
|
||||
9
vendor/github.com/valyala/fastjson/doc.go
generated
vendored
Normal file
9
vendor/github.com/valyala/fastjson/doc.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
Package fastjson provides fast JSON parsing.
|
||||
|
||||
Arbitrary JSON may be parsed by fastjson without the need for creating structs
|
||||
or for generating go code. Just parse JSON and get the required fields with
|
||||
Get* functions.
|
||||
|
||||
*/
|
||||
package fastjson
|
||||
1
vendor/github.com/valyala/fastjson/go.mod
generated
vendored
Normal file
1
vendor/github.com/valyala/fastjson/go.mod
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
module github.com/valyala/fastjson
|
||||
170
vendor/github.com/valyala/fastjson/handy.go
generated
vendored
Normal file
170
vendor/github.com/valyala/fastjson/handy.go
generated
vendored
Normal file
@@ -0,0 +1,170 @@
|
||||
package fastjson
|
||||
|
||||
var handyPool ParserPool
|
||||
|
||||
// GetString returns string value for the field identified by keys path
|
||||
// in JSON data.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
//
|
||||
// An empty string is returned on error. Use Parser for proper error handling.
|
||||
//
|
||||
// Parser is faster for obtaining multiple fields from JSON.
|
||||
func GetString(data []byte, keys ...string) string {
|
||||
p := handyPool.Get()
|
||||
v, err := p.ParseBytes(data)
|
||||
if err != nil {
|
||||
handyPool.Put(p)
|
||||
return ""
|
||||
}
|
||||
sb := v.GetStringBytes(keys...)
|
||||
str := string(sb)
|
||||
handyPool.Put(p)
|
||||
return str
|
||||
}
|
||||
|
||||
// GetBytes returns string value for the field identified by keys path
|
||||
// in JSON data.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
//
|
||||
// nil is returned on error. Use Parser for proper error handling.
|
||||
//
|
||||
// Parser is faster for obtaining multiple fields from JSON.
|
||||
func GetBytes(data []byte, keys ...string) []byte {
|
||||
p := handyPool.Get()
|
||||
v, err := p.ParseBytes(data)
|
||||
if err != nil {
|
||||
handyPool.Put(p)
|
||||
return nil
|
||||
}
|
||||
sb := v.GetStringBytes(keys...)
|
||||
|
||||
// Make a copy of sb, since sb belongs to p.
|
||||
var b []byte
|
||||
if sb != nil {
|
||||
b = append(b, sb...)
|
||||
}
|
||||
|
||||
handyPool.Put(p)
|
||||
return b
|
||||
}
|
||||
|
||||
// GetInt returns int value for the field identified by keys path
|
||||
// in JSON data.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
//
|
||||
// 0 is returned on error. Use Parser for proper error handling.
|
||||
//
|
||||
// Parser is faster for obtaining multiple fields from JSON.
|
||||
func GetInt(data []byte, keys ...string) int {
|
||||
p := handyPool.Get()
|
||||
v, err := p.ParseBytes(data)
|
||||
if err != nil {
|
||||
handyPool.Put(p)
|
||||
return 0
|
||||
}
|
||||
n := v.GetInt(keys...)
|
||||
handyPool.Put(p)
|
||||
return n
|
||||
}
|
||||
|
||||
// GetFloat64 returns float64 value for the field identified by keys path
|
||||
// in JSON data.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
//
|
||||
// 0 is returned on error. Use Parser for proper error handling.
|
||||
//
|
||||
// Parser is faster for obtaining multiple fields from JSON.
|
||||
func GetFloat64(data []byte, keys ...string) float64 {
|
||||
p := handyPool.Get()
|
||||
v, err := p.ParseBytes(data)
|
||||
if err != nil {
|
||||
handyPool.Put(p)
|
||||
return 0
|
||||
}
|
||||
f := v.GetFloat64(keys...)
|
||||
handyPool.Put(p)
|
||||
return f
|
||||
}
|
||||
|
||||
// GetBool returns boolean value for the field identified by keys path
|
||||
// in JSON data.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
//
|
||||
// False is returned on error. Use Parser for proper error handling.
|
||||
//
|
||||
// Parser is faster for obtaining multiple fields from JSON.
|
||||
func GetBool(data []byte, keys ...string) bool {
|
||||
p := handyPool.Get()
|
||||
v, err := p.ParseBytes(data)
|
||||
if err != nil {
|
||||
handyPool.Put(p)
|
||||
return false
|
||||
}
|
||||
b := v.GetBool(keys...)
|
||||
handyPool.Put(p)
|
||||
return b
|
||||
}
|
||||
|
||||
// Exists returns true if the field identified by keys path exists in JSON data.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
//
|
||||
// False is returned on error. Use Parser for proper error handling.
|
||||
//
|
||||
// Parser is faster when multiple fields must be checked in the JSON.
|
||||
func Exists(data []byte, keys ...string) bool {
|
||||
p := handyPool.Get()
|
||||
v, err := p.ParseBytes(data)
|
||||
if err != nil {
|
||||
handyPool.Put(p)
|
||||
return false
|
||||
}
|
||||
ok := v.Exists(keys...)
|
||||
handyPool.Put(p)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Parse parses json string s.
|
||||
//
|
||||
// The function is slower than the Parser.Parse for re-used Parser.
|
||||
func Parse(s string) (*Value, error) {
|
||||
var p Parser
|
||||
return p.Parse(s)
|
||||
}
|
||||
|
||||
// MustParse parses json string s.
|
||||
//
|
||||
// The function panics if s cannot be parsed.
|
||||
// The function is slower than the Parser.Parse for re-used Parser.
|
||||
func MustParse(s string) *Value {
|
||||
v, err := Parse(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// ParseBytes parses b containing json.
|
||||
//
|
||||
// The function is slower than the Parser.ParseBytes for re-used Parser.
|
||||
func ParseBytes(b []byte) (*Value, error) {
|
||||
var p Parser
|
||||
return p.ParseBytes(b)
|
||||
}
|
||||
|
||||
// MustParseBytes parses b containing json.
|
||||
//
|
||||
// The function banics if b cannot be parsed.
|
||||
// The function is slower than the Parser.ParseBytes for re-used Parser.
|
||||
func MustParseBytes(b []byte) *Value {
|
||||
v, err := ParseBytes(b)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
964
vendor/github.com/valyala/fastjson/parser.go
generated
vendored
Normal file
964
vendor/github.com/valyala/fastjson/parser.go
generated
vendored
Normal file
@@ -0,0 +1,964 @@
|
||||
package fastjson
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/valyala/fastjson/fastfloat"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf16"
|
||||
)
|
||||
|
||||
// Parser parses JSON.
|
||||
//
|
||||
// Parser may be re-used for subsequent parsing.
|
||||
//
|
||||
// Parser cannot be used from concurrent goroutines.
|
||||
// Use per-goroutine parsers or ParserPool instead.
|
||||
type Parser struct {
|
||||
// b contains working copy of the string to be parsed.
|
||||
b []byte
|
||||
|
||||
// c is a cache for json values.
|
||||
c cache
|
||||
}
|
||||
|
||||
// Parse parses s containing JSON.
|
||||
//
|
||||
// The returned value is valid until the next call to Parse*.
|
||||
//
|
||||
// Use Scanner if a stream of JSON values must be parsed.
|
||||
func (p *Parser) Parse(s string) (*Value, error) {
|
||||
s = skipWS(s)
|
||||
p.b = append(p.b[:0], s...)
|
||||
p.c.reset()
|
||||
|
||||
v, tail, err := parseValue(b2s(p.b), &p.c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse JSON: %s; unparsed tail: %q", err, startEndString(tail))
|
||||
}
|
||||
tail = skipWS(tail)
|
||||
if len(tail) > 0 {
|
||||
return nil, fmt.Errorf("unexpected tail: %q", startEndString(tail))
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// ParseBytes parses b containing JSON.
|
||||
//
|
||||
// The returned Value is valid until the next call to Parse*.
|
||||
//
|
||||
// Use Scanner if a stream of JSON values must be parsed.
|
||||
func (p *Parser) ParseBytes(b []byte) (*Value, error) {
|
||||
return p.Parse(b2s(b))
|
||||
}
|
||||
|
||||
type cache struct {
|
||||
vs []Value
|
||||
}
|
||||
|
||||
func (c *cache) reset() {
|
||||
c.vs = c.vs[:0]
|
||||
}
|
||||
|
||||
func (c *cache) getValue() *Value {
|
||||
if cap(c.vs) > len(c.vs) {
|
||||
c.vs = c.vs[:len(c.vs)+1]
|
||||
} else {
|
||||
c.vs = append(c.vs, Value{})
|
||||
}
|
||||
// Do not reset the value, since the caller must properly init it.
|
||||
return &c.vs[len(c.vs)-1]
|
||||
}
|
||||
|
||||
func skipWS(s string) string {
|
||||
if len(s) == 0 || s[0] > 0x20 {
|
||||
// Fast path.
|
||||
return s
|
||||
}
|
||||
return skipWSSlow(s)
|
||||
}
|
||||
|
||||
func skipWSSlow(s string) string {
|
||||
if len(s) == 0 || s[0] != 0x20 && s[0] != 0x0A && s[0] != 0x09 && s[0] != 0x0D {
|
||||
return s
|
||||
}
|
||||
for i := 1; i < len(s); i++ {
|
||||
if s[i] != 0x20 && s[i] != 0x0A && s[i] != 0x09 && s[i] != 0x0D {
|
||||
return s[i:]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type kv struct {
|
||||
k string
|
||||
v *Value
|
||||
}
|
||||
|
||||
func parseValue(s string, c *cache) (*Value, string, error) {
|
||||
if len(s) == 0 {
|
||||
return nil, s, fmt.Errorf("cannot parse empty string")
|
||||
}
|
||||
|
||||
if s[0] == '{' {
|
||||
v, tail, err := parseObject(s[1:], c)
|
||||
if err != nil {
|
||||
return nil, tail, fmt.Errorf("cannot parse object: %s", err)
|
||||
}
|
||||
return v, tail, nil
|
||||
}
|
||||
if s[0] == '[' {
|
||||
v, tail, err := parseArray(s[1:], c)
|
||||
if err != nil {
|
||||
return nil, tail, fmt.Errorf("cannot parse array: %s", err)
|
||||
}
|
||||
return v, tail, nil
|
||||
}
|
||||
if s[0] == '"' {
|
||||
ss, tail, err := parseRawString(s[1:])
|
||||
if err != nil {
|
||||
return nil, tail, fmt.Errorf("cannot parse string: %s", err)
|
||||
}
|
||||
v := c.getValue()
|
||||
v.t = typeRawString
|
||||
v.s = ss
|
||||
return v, tail, nil
|
||||
}
|
||||
if s[0] == 't' {
|
||||
if len(s) < len("true") || s[:len("true")] != "true" {
|
||||
return nil, s, fmt.Errorf("unexpected value found: %q", s)
|
||||
}
|
||||
return valueTrue, s[len("true"):], nil
|
||||
}
|
||||
if s[0] == 'f' {
|
||||
if len(s) < len("false") || s[:len("false")] != "false" {
|
||||
return nil, s, fmt.Errorf("unexpected value found: %q", s)
|
||||
}
|
||||
return valueFalse, s[len("false"):], nil
|
||||
}
|
||||
if s[0] == 'n' {
|
||||
if len(s) < len("null") || s[:len("null")] != "null" {
|
||||
return nil, s, fmt.Errorf("unexpected value found: %q", s)
|
||||
}
|
||||
return valueNull, s[len("null"):], nil
|
||||
}
|
||||
|
||||
ns, tail, err := parseRawNumber(s)
|
||||
if err != nil {
|
||||
return nil, tail, fmt.Errorf("cannot parse number: %s", err)
|
||||
}
|
||||
v := c.getValue()
|
||||
v.t = TypeNumber
|
||||
v.s = ns
|
||||
return v, tail, nil
|
||||
}
|
||||
|
||||
func parseArray(s string, c *cache) (*Value, string, error) {
|
||||
s = skipWS(s)
|
||||
if len(s) == 0 {
|
||||
return nil, s, fmt.Errorf("missing ']'")
|
||||
}
|
||||
|
||||
if s[0] == ']' {
|
||||
v := c.getValue()
|
||||
v.t = TypeArray
|
||||
v.a = v.a[:0]
|
||||
return v, s[1:], nil
|
||||
}
|
||||
|
||||
a := c.getValue()
|
||||
a.t = TypeArray
|
||||
a.a = a.a[:0]
|
||||
for {
|
||||
var v *Value
|
||||
var err error
|
||||
|
||||
s = skipWS(s)
|
||||
v, s, err = parseValue(s, c)
|
||||
if err != nil {
|
||||
return nil, s, fmt.Errorf("cannot parse array value: %s", err)
|
||||
}
|
||||
a.a = append(a.a, v)
|
||||
|
||||
s = skipWS(s)
|
||||
if len(s) == 0 {
|
||||
return nil, s, fmt.Errorf("unexpected end of array")
|
||||
}
|
||||
if s[0] == ',' {
|
||||
s = s[1:]
|
||||
continue
|
||||
}
|
||||
if s[0] == ']' {
|
||||
s = s[1:]
|
||||
return a, s, nil
|
||||
}
|
||||
return nil, s, fmt.Errorf("missing ',' after array value")
|
||||
}
|
||||
}
|
||||
|
||||
func parseObject(s string, c *cache) (*Value, string, error) {
|
||||
s = skipWS(s)
|
||||
if len(s) == 0 {
|
||||
return nil, s, fmt.Errorf("missing '}'")
|
||||
}
|
||||
|
||||
if s[0] == '}' {
|
||||
v := c.getValue()
|
||||
v.t = TypeObject
|
||||
v.o.reset()
|
||||
return v, s[1:], nil
|
||||
}
|
||||
|
||||
o := c.getValue()
|
||||
o.t = TypeObject
|
||||
o.o.reset()
|
||||
for {
|
||||
var err error
|
||||
kv := o.o.getKV()
|
||||
|
||||
// Parse key.
|
||||
s = skipWS(s)
|
||||
if len(s) == 0 || s[0] != '"' {
|
||||
return nil, s, fmt.Errorf(`cannot find opening '"" for object key`)
|
||||
}
|
||||
kv.k, s, err = parseRawKey(s[1:])
|
||||
if err != nil {
|
||||
return nil, s, fmt.Errorf("cannot parse object key: %s", err)
|
||||
}
|
||||
s = skipWS(s)
|
||||
if len(s) == 0 || s[0] != ':' {
|
||||
return nil, s, fmt.Errorf("missing ':' after object key")
|
||||
}
|
||||
s = s[1:]
|
||||
|
||||
// Parse value
|
||||
s = skipWS(s)
|
||||
kv.v, s, err = parseValue(s, c)
|
||||
if err != nil {
|
||||
return nil, s, fmt.Errorf("cannot parse object value: %s", err)
|
||||
}
|
||||
s = skipWS(s)
|
||||
if len(s) == 0 {
|
||||
return nil, s, fmt.Errorf("unexpected end of object")
|
||||
}
|
||||
if s[0] == ',' {
|
||||
s = s[1:]
|
||||
continue
|
||||
}
|
||||
if s[0] == '}' {
|
||||
return o, s[1:], nil
|
||||
}
|
||||
return nil, s, fmt.Errorf("missing ',' after object value")
|
||||
}
|
||||
}
|
||||
|
||||
func escapeString(dst []byte, s string) []byte {
|
||||
if !hasSpecialChars(s) {
|
||||
// Fast path - nothing to escape.
|
||||
dst = append(dst, '"')
|
||||
dst = append(dst, s...)
|
||||
dst = append(dst, '"')
|
||||
return dst
|
||||
}
|
||||
|
||||
// Slow path.
|
||||
return strconv.AppendQuote(dst, s)
|
||||
}
|
||||
|
||||
func hasSpecialChars(s string) bool {
|
||||
if strings.IndexByte(s, '"') >= 0 || strings.IndexByte(s, '\\') >= 0 {
|
||||
return true
|
||||
}
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] < 0x20 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func unescapeStringBestEffort(s string) string {
|
||||
n := strings.IndexByte(s, '\\')
|
||||
if n < 0 {
|
||||
// Fast path - nothing to unescape.
|
||||
return s
|
||||
}
|
||||
|
||||
// Slow path - unescape string.
|
||||
b := s2b(s) // It is safe to do, since s points to a byte slice in Parser.b.
|
||||
b = b[:n]
|
||||
s = s[n+1:]
|
||||
for len(s) > 0 {
|
||||
ch := s[0]
|
||||
s = s[1:]
|
||||
switch ch {
|
||||
case '"':
|
||||
b = append(b, '"')
|
||||
case '\\':
|
||||
b = append(b, '\\')
|
||||
case '/':
|
||||
b = append(b, '/')
|
||||
case 'b':
|
||||
b = append(b, '\b')
|
||||
case 'f':
|
||||
b = append(b, '\f')
|
||||
case 'n':
|
||||
b = append(b, '\n')
|
||||
case 'r':
|
||||
b = append(b, '\r')
|
||||
case 't':
|
||||
b = append(b, '\t')
|
||||
case 'u':
|
||||
if len(s) < 4 {
|
||||
// Too short escape sequence. Just store it unchanged.
|
||||
b = append(b, "\\u"...)
|
||||
break
|
||||
}
|
||||
xs := s[:4]
|
||||
x, err := strconv.ParseUint(xs, 16, 16)
|
||||
if err != nil {
|
||||
// Invalid escape sequence. Just store it unchanged.
|
||||
b = append(b, "\\u"...)
|
||||
break
|
||||
}
|
||||
s = s[4:]
|
||||
if !utf16.IsSurrogate(rune(x)) {
|
||||
b = append(b, string(rune(x))...)
|
||||
break
|
||||
}
|
||||
|
||||
// Surrogate.
|
||||
// See https://en.wikipedia.org/wiki/Universal_Character_Set_characters#Surrogates
|
||||
if len(s) < 6 || s[0] != '\\' || s[1] != 'u' {
|
||||
b = append(b, "\\u"...)
|
||||
b = append(b, xs...)
|
||||
break
|
||||
}
|
||||
x1, err := strconv.ParseUint(s[2:6], 16, 16)
|
||||
if err != nil {
|
||||
b = append(b, "\\u"...)
|
||||
b = append(b, xs...)
|
||||
break
|
||||
}
|
||||
r := utf16.DecodeRune(rune(x), rune(x1))
|
||||
b = append(b, string(r)...)
|
||||
s = s[6:]
|
||||
default:
|
||||
// Unknown escape sequence. Just store it unchanged.
|
||||
b = append(b, '\\', ch)
|
||||
}
|
||||
n = strings.IndexByte(s, '\\')
|
||||
if n < 0 {
|
||||
b = append(b, s...)
|
||||
break
|
||||
}
|
||||
b = append(b, s[:n]...)
|
||||
s = s[n+1:]
|
||||
}
|
||||
return b2s(b)
|
||||
}
|
||||
|
||||
// parseRawKey is similar to parseRawString, but is optimized
|
||||
// for small-sized keys without escape sequences.
|
||||
func parseRawKey(s string) (string, string, error) {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] == '"' {
|
||||
// Fast path.
|
||||
return s[:i], s[i+1:], nil
|
||||
}
|
||||
if s[i] == '\\' {
|
||||
// Slow path.
|
||||
return parseRawString(s)
|
||||
}
|
||||
}
|
||||
return s, "", fmt.Errorf(`missing closing '"'`)
|
||||
}
|
||||
|
||||
func parseRawString(s string) (string, string, error) {
|
||||
n := strings.IndexByte(s, '"')
|
||||
if n < 0 {
|
||||
return s, "", fmt.Errorf(`missing closing '"'`)
|
||||
}
|
||||
if n == 0 || s[n-1] != '\\' {
|
||||
// Fast path. No escaped ".
|
||||
return s[:n], s[n+1:], nil
|
||||
}
|
||||
|
||||
// Slow path - possible escaped " found.
|
||||
ss := s
|
||||
for {
|
||||
i := n - 1
|
||||
for i > 0 && s[i-1] == '\\' {
|
||||
i--
|
||||
}
|
||||
if uint(n-i)%2 == 0 {
|
||||
return ss[:len(ss)-len(s)+n], s[n+1:], nil
|
||||
}
|
||||
s = s[n+1:]
|
||||
|
||||
n = strings.IndexByte(s, '"')
|
||||
if n < 0 {
|
||||
return ss, "", fmt.Errorf(`missing closing '"'`)
|
||||
}
|
||||
if n == 0 || s[n-1] != '\\' {
|
||||
return ss[:len(ss)-len(s)+n], s[n+1:], nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseRawNumber(s string) (string, string, error) {
|
||||
// The caller must ensure len(s) > 0
|
||||
|
||||
// Find the end of the number.
|
||||
for i := 0; i < len(s); i++ {
|
||||
ch := s[i]
|
||||
if (ch >= '0' && ch <= '9') || ch == '.' || ch == '-' || ch == 'e' || ch == 'E' || ch == '+' {
|
||||
continue
|
||||
}
|
||||
if i == 0 {
|
||||
return "", s, fmt.Errorf("unexpected char: %q", s[:1])
|
||||
}
|
||||
ns := s[:i]
|
||||
s = s[i:]
|
||||
return ns, s, nil
|
||||
}
|
||||
return s, "", nil
|
||||
}
|
||||
|
||||
// Object represents JSON object.
|
||||
//
|
||||
// Object cannot be used from concurrent goroutines.
|
||||
// Use per-goroutine parsers or ParserPool instead.
|
||||
type Object struct {
|
||||
kvs []kv
|
||||
keysUnescaped bool
|
||||
}
|
||||
|
||||
func (o *Object) reset() {
|
||||
o.kvs = o.kvs[:0]
|
||||
o.keysUnescaped = false
|
||||
}
|
||||
|
||||
// MarshalTo appends marshaled o to dst and returns the result.
|
||||
func (o *Object) MarshalTo(dst []byte) []byte {
|
||||
dst = append(dst, '{')
|
||||
for i, kv := range o.kvs {
|
||||
if o.keysUnescaped {
|
||||
dst = escapeString(dst, kv.k)
|
||||
} else {
|
||||
dst = append(dst, '"')
|
||||
dst = append(dst, kv.k...)
|
||||
dst = append(dst, '"')
|
||||
}
|
||||
dst = append(dst, ':')
|
||||
dst = kv.v.MarshalTo(dst)
|
||||
if i != len(o.kvs)-1 {
|
||||
dst = append(dst, ',')
|
||||
}
|
||||
}
|
||||
dst = append(dst, '}')
|
||||
return dst
|
||||
}
|
||||
|
||||
// String returns string representation for the o.
|
||||
//
|
||||
// This function is for debugging purposes only. It isn't optimized for speed.
|
||||
// See MarshalTo instead.
|
||||
func (o *Object) String() string {
|
||||
b := o.MarshalTo(nil)
|
||||
// It is safe converting b to string without allocation, since b is no longer
|
||||
// reachable after this line.
|
||||
return b2s(b)
|
||||
}
|
||||
|
||||
func (o *Object) getKV() *kv {
|
||||
if cap(o.kvs) > len(o.kvs) {
|
||||
o.kvs = o.kvs[:len(o.kvs)+1]
|
||||
} else {
|
||||
o.kvs = append(o.kvs, kv{})
|
||||
}
|
||||
return &o.kvs[len(o.kvs)-1]
|
||||
}
|
||||
|
||||
func (o *Object) unescapeKeys() {
|
||||
if o.keysUnescaped {
|
||||
return
|
||||
}
|
||||
for i := range o.kvs {
|
||||
kv := &o.kvs[i]
|
||||
kv.k = unescapeStringBestEffort(kv.k)
|
||||
}
|
||||
o.keysUnescaped = true
|
||||
}
|
||||
|
||||
// Len returns the number of items in the o.
|
||||
func (o *Object) Len() int {
|
||||
return len(o.kvs)
|
||||
}
|
||||
|
||||
// Get returns the value for the given key in the o.
|
||||
//
|
||||
// Returns nil if the value for the given key isn't found.
|
||||
//
|
||||
// The returned value is valid until Parse is called on the Parser returned o.
|
||||
func (o *Object) Get(key string) *Value {
|
||||
if !o.keysUnescaped && strings.IndexByte(key, '\\') < 0 {
|
||||
// Fast path - try searching for the key without object keys unescaping.
|
||||
for _, kv := range o.kvs {
|
||||
if kv.k == key {
|
||||
return kv.v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Slow path - unescape object keys.
|
||||
o.unescapeKeys()
|
||||
|
||||
for _, kv := range o.kvs {
|
||||
if kv.k == key {
|
||||
return kv.v
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Visit calls f for each item in the o in the original order
|
||||
// of the parsed JSON.
|
||||
//
|
||||
// f cannot hold key and/or v after returning.
|
||||
func (o *Object) Visit(f func(key []byte, v *Value)) {
|
||||
if o == nil {
|
||||
return
|
||||
}
|
||||
|
||||
o.unescapeKeys()
|
||||
|
||||
for _, kv := range o.kvs {
|
||||
f(s2b(kv.k), kv.v)
|
||||
}
|
||||
}
|
||||
|
||||
// Value represents any JSON value.
|
||||
//
|
||||
// Call Type in order to determine the actual type of the JSON value.
|
||||
//
|
||||
// Value cannot be used from concurrent goroutines.
|
||||
// Use per-goroutine parsers or ParserPool instead.
|
||||
type Value struct {
|
||||
o Object
|
||||
a []*Value
|
||||
s string
|
||||
t Type
|
||||
}
|
||||
|
||||
// MarshalTo appends marshaled v to dst and returns the result.
|
||||
func (v *Value) MarshalTo(dst []byte) []byte {
|
||||
switch v.t {
|
||||
case typeRawString:
|
||||
dst = append(dst, '"')
|
||||
dst = append(dst, v.s...)
|
||||
dst = append(dst, '"')
|
||||
return dst
|
||||
case TypeObject:
|
||||
return v.o.MarshalTo(dst)
|
||||
case TypeArray:
|
||||
dst = append(dst, '[')
|
||||
for i, vv := range v.a {
|
||||
dst = vv.MarshalTo(dst)
|
||||
if i != len(v.a)-1 {
|
||||
dst = append(dst, ',')
|
||||
}
|
||||
}
|
||||
dst = append(dst, ']')
|
||||
return dst
|
||||
case TypeString:
|
||||
return escapeString(dst, v.s)
|
||||
case TypeNumber:
|
||||
return append(dst, v.s...)
|
||||
case TypeTrue:
|
||||
return append(dst, "true"...)
|
||||
case TypeFalse:
|
||||
return append(dst, "false"...)
|
||||
case TypeNull:
|
||||
return append(dst, "null"...)
|
||||
default:
|
||||
panic(fmt.Errorf("BUG: unexpected Value type: %d", v.t))
|
||||
}
|
||||
}
|
||||
|
||||
// String returns string representation of the v.
|
||||
//
|
||||
// The function is for debugging purposes only. It isn't optimized for speed.
|
||||
// See MarshalTo instead.
|
||||
//
|
||||
// Don't confuse this function with StringBytes, which must be called
|
||||
// for obtaining the underlying JSON string for the v.
|
||||
func (v *Value) String() string {
|
||||
b := v.MarshalTo(nil)
|
||||
// It is safe converting b to string without allocation, since b is no longer
|
||||
// reachable after this line.
|
||||
return b2s(b)
|
||||
}
|
||||
|
||||
// Type represents JSON type.
|
||||
type Type int
|
||||
|
||||
const (
|
||||
// TypeNull is JSON null.
|
||||
TypeNull Type = 0
|
||||
|
||||
// TypeObject is JSON object type.
|
||||
TypeObject Type = 1
|
||||
|
||||
// TypeArray is JSON array type.
|
||||
TypeArray Type = 2
|
||||
|
||||
// TypeString is JSON string type.
|
||||
TypeString Type = 3
|
||||
|
||||
// TypeNumber is JSON number type.
|
||||
TypeNumber Type = 4
|
||||
|
||||
// TypeTrue is JSON true.
|
||||
TypeTrue Type = 5
|
||||
|
||||
// TypeFalse is JSON false.
|
||||
TypeFalse Type = 6
|
||||
|
||||
typeRawString Type = 7
|
||||
)
|
||||
|
||||
// String returns string representation of t.
|
||||
func (t Type) String() string {
|
||||
switch t {
|
||||
case TypeObject:
|
||||
return "object"
|
||||
case TypeArray:
|
||||
return "array"
|
||||
case TypeString:
|
||||
return "string"
|
||||
case TypeNumber:
|
||||
return "number"
|
||||
case TypeTrue:
|
||||
return "true"
|
||||
case TypeFalse:
|
||||
return "false"
|
||||
case TypeNull:
|
||||
return "null"
|
||||
|
||||
// typeRawString is skipped intentionally,
|
||||
// since it shouldn't be visible to user.
|
||||
default:
|
||||
panic(fmt.Errorf("BUG: unknown Value type: %d", t))
|
||||
}
|
||||
}
|
||||
|
||||
// Type returns the type of the v.
|
||||
func (v *Value) Type() Type {
|
||||
if v.t == typeRawString {
|
||||
v.s = unescapeStringBestEffort(v.s)
|
||||
v.t = TypeString
|
||||
}
|
||||
return v.t
|
||||
}
|
||||
|
||||
// Exists returns true if the field exists for the given keys path.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
func (v *Value) Exists(keys ...string) bool {
|
||||
v = v.Get(keys...)
|
||||
return v != nil
|
||||
}
|
||||
|
||||
// Get returns value by the given keys path.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
//
|
||||
// nil is returned for non-existing keys path.
|
||||
//
|
||||
// The returned value is valid until Parse is called on the Parser returned v.
|
||||
func (v *Value) Get(keys ...string) *Value {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
for _, key := range keys {
|
||||
if v.t == TypeObject {
|
||||
v = v.o.Get(key)
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
} else if v.t == TypeArray {
|
||||
n, err := strconv.Atoi(key)
|
||||
if err != nil || n < 0 || n >= len(v.a) {
|
||||
return nil
|
||||
}
|
||||
v = v.a[n]
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// GetObject returns object value by the given keys path.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
//
|
||||
// nil is returned for non-existing keys path or for invalid value type.
|
||||
//
|
||||
// The returned object is valid until Parse is called on the Parser returned v.
|
||||
func (v *Value) GetObject(keys ...string) *Object {
|
||||
v = v.Get(keys...)
|
||||
if v == nil || v.t != TypeObject {
|
||||
return nil
|
||||
}
|
||||
return &v.o
|
||||
}
|
||||
|
||||
// GetArray returns array value by the given keys path.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
//
|
||||
// nil is returned for non-existing keys path or for invalid value type.
|
||||
//
|
||||
// The returned array is valid until Parse is called on the Parser returned v.
|
||||
func (v *Value) GetArray(keys ...string) []*Value {
|
||||
v = v.Get(keys...)
|
||||
if v == nil || v.t != TypeArray {
|
||||
return nil
|
||||
}
|
||||
return v.a
|
||||
}
|
||||
|
||||
// GetFloat64 returns float64 value by the given keys path.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
//
|
||||
// 0 is returned for non-existing keys path or for invalid value type.
|
||||
func (v *Value) GetFloat64(keys ...string) float64 {
|
||||
v = v.Get(keys...)
|
||||
if v == nil || v.Type() != TypeNumber {
|
||||
return 0
|
||||
}
|
||||
return fastfloat.ParseBestEffort(v.s)
|
||||
}
|
||||
|
||||
// GetInt returns int value by the given keys path.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
//
|
||||
// 0 is returned for non-existing keys path or for invalid value type.
|
||||
func (v *Value) GetInt(keys ...string) int {
|
||||
v = v.Get(keys...)
|
||||
if v == nil || v.Type() != TypeNumber {
|
||||
return 0
|
||||
}
|
||||
n := fastfloat.ParseInt64BestEffort(v.s)
|
||||
nn := int(n)
|
||||
if int64(nn) != n {
|
||||
return 0
|
||||
}
|
||||
return nn
|
||||
}
|
||||
|
||||
// GetUint returns uint value by the given keys path.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
//
|
||||
// 0 is returned for non-existing keys path or for invalid value type.
|
||||
func (v *Value) GetUint(keys ...string) uint {
|
||||
v = v.Get(keys...)
|
||||
if v == nil || v.Type() != TypeNumber {
|
||||
return 0
|
||||
}
|
||||
n := fastfloat.ParseUint64BestEffort(v.s)
|
||||
nn := uint(n)
|
||||
if uint64(nn) != n {
|
||||
return 0
|
||||
}
|
||||
return nn
|
||||
}
|
||||
|
||||
// GetInt64 returns int64 value by the given keys path.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
//
|
||||
// 0 is returned for non-existing keys path or for invalid value type.
|
||||
func (v *Value) GetInt64(keys ...string) int64 {
|
||||
v = v.Get(keys...)
|
||||
if v == nil || v.Type() != TypeNumber {
|
||||
return 0
|
||||
}
|
||||
return fastfloat.ParseInt64BestEffort(v.s)
|
||||
}
|
||||
|
||||
// GetUint64 returns uint64 value by the given keys path.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
//
|
||||
// 0 is returned for non-existing keys path or for invalid value type.
|
||||
func (v *Value) GetUint64(keys ...string) uint64 {
|
||||
v = v.Get(keys...)
|
||||
if v == nil || v.Type() != TypeNumber {
|
||||
return 0
|
||||
}
|
||||
return fastfloat.ParseUint64BestEffort(v.s)
|
||||
}
|
||||
|
||||
// GetStringBytes returns string value by the given keys path.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
//
|
||||
// nil is returned for non-existing keys path or for invalid value type.
|
||||
//
|
||||
// The returned string is valid until Parse is called on the Parser returned v.
|
||||
func (v *Value) GetStringBytes(keys ...string) []byte {
|
||||
v = v.Get(keys...)
|
||||
if v == nil || v.Type() != TypeString {
|
||||
return nil
|
||||
}
|
||||
return s2b(v.s)
|
||||
}
|
||||
|
||||
// GetBool returns bool value by the given keys path.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
//
|
||||
// false is returned for non-existing keys path or for invalid value type.
|
||||
func (v *Value) GetBool(keys ...string) bool {
|
||||
v = v.Get(keys...)
|
||||
if v != nil && v.t == TypeTrue {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Object returns the underlying JSON object for the v.
|
||||
//
|
||||
// The returned object is valid until Parse is called on the Parser returned v.
|
||||
//
|
||||
// Use GetObject if you don't need error handling.
|
||||
func (v *Value) Object() (*Object, error) {
|
||||
if v.t != TypeObject {
|
||||
return nil, fmt.Errorf("value doesn't contain object; it contains %s", v.Type())
|
||||
}
|
||||
return &v.o, nil
|
||||
}
|
||||
|
||||
// Array returns the underlying JSON array for the v.
|
||||
//
|
||||
// The returned array is valid until Parse is called on the Parser returned v.
|
||||
//
|
||||
// Use GetArray if you don't need error handling.
|
||||
func (v *Value) Array() ([]*Value, error) {
|
||||
if v.t != TypeArray {
|
||||
return nil, fmt.Errorf("value doesn't contain array; it contains %s", v.Type())
|
||||
}
|
||||
return v.a, nil
|
||||
}
|
||||
|
||||
// StringBytes returns the underlying JSON string for the v.
|
||||
//
|
||||
// The returned string is valid until Parse is called on the Parser returned v.
|
||||
//
|
||||
// Use GetStringBytes if you don't need error handling.
|
||||
func (v *Value) StringBytes() ([]byte, error) {
|
||||
if v.Type() != TypeString {
|
||||
return nil, fmt.Errorf("value doesn't contain string; it contains %s", v.Type())
|
||||
}
|
||||
return s2b(v.s), nil
|
||||
}
|
||||
|
||||
// Float64 returns the underlying JSON number for the v.
|
||||
//
|
||||
// Use GetFloat64 if you don't need error handling.
|
||||
func (v *Value) Float64() (float64, error) {
|
||||
if v.Type() != TypeNumber {
|
||||
return 0, fmt.Errorf("value doesn't contain number; it contains %s", v.Type())
|
||||
}
|
||||
f := fastfloat.ParseBestEffort(v.s)
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// Int returns the underlying JSON int for the v.
|
||||
//
|
||||
// Use GetInt if you don't need error handling.
|
||||
func (v *Value) Int() (int, error) {
|
||||
if v.Type() != TypeNumber {
|
||||
return 0, fmt.Errorf("value doesn't contain number; it contains %s", v.Type())
|
||||
}
|
||||
n := fastfloat.ParseInt64BestEffort(v.s)
|
||||
if n == 0 && v.s != "0" {
|
||||
return 0, fmt.Errorf("cannot parse int %q", v.s)
|
||||
}
|
||||
nn := int(n)
|
||||
if int64(nn) != n {
|
||||
return 0, fmt.Errorf("number %q doesn't fit int", v.s)
|
||||
}
|
||||
return nn, nil
|
||||
}
|
||||
|
||||
// Uint returns the underlying JSON uint for the v.
|
||||
//
|
||||
// Use GetInt if you don't need error handling.
|
||||
func (v *Value) Uint() (uint, error) {
|
||||
if v.Type() != TypeNumber {
|
||||
return 0, fmt.Errorf("value doesn't contain number; it contains %s", v.Type())
|
||||
}
|
||||
n := fastfloat.ParseUint64BestEffort(v.s)
|
||||
if n == 0 && v.s != "0" {
|
||||
return 0, fmt.Errorf("cannot parse uint %q", v.s)
|
||||
}
|
||||
nn := uint(n)
|
||||
if uint64(nn) != n {
|
||||
return 0, fmt.Errorf("number %q doesn't fit uint", v.s)
|
||||
}
|
||||
return nn, nil
|
||||
}
|
||||
|
||||
// Int64 returns the underlying JSON int64 for the v.
|
||||
//
|
||||
// Use GetInt64 if you don't need error handling.
|
||||
func (v *Value) Int64() (int64, error) {
|
||||
if v.Type() != TypeNumber {
|
||||
return 0, fmt.Errorf("value doesn't contain number; it contains %s", v.Type())
|
||||
}
|
||||
n := fastfloat.ParseInt64BestEffort(v.s)
|
||||
if n == 0 && v.s != "0" {
|
||||
return 0, fmt.Errorf("cannot parse int64 %q", v.s)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Uint64 returns the underlying JSON uint64 for the v.
|
||||
//
|
||||
// Use GetInt64 if you don't need error handling.
|
||||
func (v *Value) Uint64() (uint64, error) {
|
||||
if v.Type() != TypeNumber {
|
||||
return 0, fmt.Errorf("value doesn't contain number; it contains %s", v.Type())
|
||||
}
|
||||
n := fastfloat.ParseUint64BestEffort(v.s)
|
||||
if n == 0 && v.s != "0" {
|
||||
return 0, fmt.Errorf("cannot parse uint64 %q", v.s)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Bool returns the underlying JSON bool for the v.
|
||||
//
|
||||
// Use GetBool if you don't need error handling.
|
||||
func (v *Value) Bool() (bool, error) {
|
||||
if v.t == TypeTrue {
|
||||
return true, nil
|
||||
}
|
||||
if v.t == TypeFalse {
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("value doesn't contain bool; it contains %s", v.Type())
|
||||
}
|
||||
|
||||
var (
|
||||
valueTrue = &Value{t: TypeTrue}
|
||||
valueFalse = &Value{t: TypeFalse}
|
||||
valueNull = &Value{t: TypeNull}
|
||||
)
|
||||
52
vendor/github.com/valyala/fastjson/pool.go
generated
vendored
Normal file
52
vendor/github.com/valyala/fastjson/pool.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
package fastjson
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ParserPool may be used for pooling Parsers for similarly typed JSONs.
|
||||
type ParserPool struct {
|
||||
pool sync.Pool
|
||||
}
|
||||
|
||||
// Get returns a Parser from pp.
|
||||
//
|
||||
// The Parser must be Put to pp after use.
|
||||
func (pp *ParserPool) Get() *Parser {
|
||||
v := pp.pool.Get()
|
||||
if v == nil {
|
||||
return &Parser{}
|
||||
}
|
||||
return v.(*Parser)
|
||||
}
|
||||
|
||||
// Put returns p to pp.
|
||||
//
|
||||
// p and objects recursively returned from p cannot be used after p
|
||||
// is put into pp.
|
||||
func (pp *ParserPool) Put(p *Parser) {
|
||||
pp.pool.Put(p)
|
||||
}
|
||||
|
||||
// ArenaPool may be used for pooling Arenas for similarly typed JSONs.
|
||||
type ArenaPool struct {
|
||||
pool sync.Pool
|
||||
}
|
||||
|
||||
// Get returns an Arena from ap.
|
||||
//
|
||||
// The Arena must be Put to ap after use.
|
||||
func (ap *ArenaPool) Get() *Arena {
|
||||
v := ap.pool.Get()
|
||||
if v == nil {
|
||||
return &Arena{}
|
||||
}
|
||||
return v.(*Arena)
|
||||
}
|
||||
|
||||
// Put returns a to ap.
|
||||
//
|
||||
// a and objects created by a cannot be used after a is put into ap.
|
||||
func (ap *ArenaPool) Put(a *Arena) {
|
||||
ap.pool.Put(a)
|
||||
}
|
||||
94
vendor/github.com/valyala/fastjson/scanner.go
generated
vendored
Normal file
94
vendor/github.com/valyala/fastjson/scanner.go
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
package fastjson
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Scanner scans a series of JSON values. Values may be delimited by whitespace.
|
||||
//
|
||||
// Scanner may parse JSON lines ( http://jsonlines.org/ ).
|
||||
//
|
||||
// Scanner may be re-used for subsequent parsing.
|
||||
//
|
||||
// Scanner cannot be used from concurrent goroutines.
|
||||
//
|
||||
// Use Parser for parsing only a single JSON value.
|
||||
type Scanner struct {
|
||||
// b contains a working copy of json value passed to Init.
|
||||
b []byte
|
||||
|
||||
// s points to the next JSON value to parse.
|
||||
s string
|
||||
|
||||
// err contains the last error.
|
||||
err error
|
||||
|
||||
// v contains the last parsed JSON value.
|
||||
v *Value
|
||||
|
||||
// c is used for caching JSON values.
|
||||
c cache
|
||||
}
|
||||
|
||||
// Init initializes sc with the given s.
|
||||
//
|
||||
// s may contain multiple JSON values, which may be delimited by whitespace.
|
||||
func (sc *Scanner) Init(s string) {
|
||||
sc.b = append(sc.b[:0], s...)
|
||||
sc.s = b2s(sc.b)
|
||||
sc.err = nil
|
||||
sc.v = nil
|
||||
}
|
||||
|
||||
// InitBytes initializes sc with the given b.
|
||||
//
|
||||
// b may contain multiple JSON values, which may be delimited by whitespace.
|
||||
func (sc *Scanner) InitBytes(b []byte) {
|
||||
sc.Init(b2s(b))
|
||||
}
|
||||
|
||||
// Next parses the next JSON value from s passed to Init.
|
||||
//
|
||||
// Returns true on success. The parsed value is available via Value call.
|
||||
//
|
||||
// Returns false either on error or on the end of s.
|
||||
// Call Error in order to determine the cause of the returned false.
|
||||
func (sc *Scanner) Next() bool {
|
||||
if sc.err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
sc.s = skipWS(sc.s)
|
||||
if len(sc.s) == 0 {
|
||||
sc.err = errEOF
|
||||
return false
|
||||
}
|
||||
|
||||
sc.c.reset()
|
||||
v, tail, err := parseValue(sc.s, &sc.c)
|
||||
if err != nil {
|
||||
sc.err = err
|
||||
return false
|
||||
}
|
||||
|
||||
sc.s = tail
|
||||
sc.v = v
|
||||
return true
|
||||
}
|
||||
|
||||
// Error returns the last error.
|
||||
func (sc *Scanner) Error() error {
|
||||
if sc.err == errEOF {
|
||||
return nil
|
||||
}
|
||||
return sc.err
|
||||
}
|
||||
|
||||
// Value returns the last parsed value.
|
||||
//
|
||||
// The value is valid until the Next call.
|
||||
func (sc *Scanner) Value() *Value {
|
||||
return sc.v
|
||||
}
|
||||
|
||||
var errEOF = errors.New("end of s")
|
||||
110
vendor/github.com/valyala/fastjson/update.go
generated
vendored
Normal file
110
vendor/github.com/valyala/fastjson/update.go
generated
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
package fastjson
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Del deletes the entry with the given key from o.
|
||||
func (o *Object) Del(key string) {
|
||||
if o == nil {
|
||||
return
|
||||
}
|
||||
if !o.keysUnescaped && strings.IndexByte(key, '\\') < 0 {
|
||||
// Fast path - try searching for the key without object keys unescaping.
|
||||
for i, kv := range o.kvs {
|
||||
if kv.k == key {
|
||||
o.kvs = append(o.kvs[:i], o.kvs[i+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Slow path - unescape object keys before item search.
|
||||
o.unescapeKeys()
|
||||
|
||||
for i, kv := range o.kvs {
|
||||
if kv.k == key {
|
||||
o.kvs = append(o.kvs[:i], o.kvs[i+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Del deletes the entry with the given key from array or object v.
|
||||
func (v *Value) Del(key string) {
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
if v.t == TypeObject {
|
||||
v.o.Del(key)
|
||||
return
|
||||
}
|
||||
if v.t == TypeArray {
|
||||
n, err := strconv.Atoi(key)
|
||||
if err != nil || n < 0 || n >= len(v.a) {
|
||||
return
|
||||
}
|
||||
v.a = append(v.a[:n], v.a[n+1:]...)
|
||||
}
|
||||
}
|
||||
|
||||
// Set sets (key, value) entry in the o.
|
||||
//
|
||||
// The value must be unchanged during o lifetime.
|
||||
func (o *Object) Set(key string, value *Value) {
|
||||
if o == nil {
|
||||
return
|
||||
}
|
||||
if value == nil {
|
||||
value = valueNull
|
||||
}
|
||||
o.unescapeKeys()
|
||||
|
||||
// Try substituting already existing entry with the given key.
|
||||
for i := range o.kvs {
|
||||
kv := &o.kvs[i]
|
||||
if kv.k == key {
|
||||
kv.v = value
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Add new entry.
|
||||
kv := o.getKV()
|
||||
kv.k = key
|
||||
kv.v = value
|
||||
}
|
||||
|
||||
// Set sets (key, value) entry in the array or object v.
|
||||
//
|
||||
// The value must be unchanged during v lifetime.
|
||||
func (v *Value) Set(key string, value *Value) {
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
if v.t == TypeObject {
|
||||
v.o.Set(key, value)
|
||||
return
|
||||
}
|
||||
if v.t == TypeArray {
|
||||
idx, err := strconv.Atoi(key)
|
||||
if err != nil || idx < 0 {
|
||||
return
|
||||
}
|
||||
v.SetArrayItem(idx, value)
|
||||
}
|
||||
}
|
||||
|
||||
// SetArrayItem sets the value in the array v at idx position.
|
||||
//
|
||||
// The value must be unchanged during v lifetime.
|
||||
func (v *Value) SetArrayItem(idx int, value *Value) {
|
||||
if v == nil || v.t != TypeArray {
|
||||
return
|
||||
}
|
||||
for idx >= len(v.a) {
|
||||
v.a = append(v.a, valueNull)
|
||||
}
|
||||
v.a[idx] = value
|
||||
}
|
||||
30
vendor/github.com/valyala/fastjson/util.go
generated
vendored
Normal file
30
vendor/github.com/valyala/fastjson/util.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
package fastjson
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func b2s(b []byte) string {
|
||||
return *(*string)(unsafe.Pointer(&b))
|
||||
}
|
||||
|
||||
func s2b(s string) []byte {
|
||||
strh := (*reflect.StringHeader)(unsafe.Pointer(&s))
|
||||
var sh reflect.SliceHeader
|
||||
sh.Data = strh.Data
|
||||
sh.Len = strh.Len
|
||||
sh.Cap = strh.Len
|
||||
return *(*[]byte)(unsafe.Pointer(&sh))
|
||||
}
|
||||
|
||||
const maxStartEndStringLen = 80
|
||||
|
||||
func startEndString(s string) string {
|
||||
if len(s) <= maxStartEndStringLen {
|
||||
return s
|
||||
}
|
||||
start := s[:40]
|
||||
end := s[len(s)-40:]
|
||||
return start + "..." + end
|
||||
}
|
||||
308
vendor/github.com/valyala/fastjson/validate.go
generated
vendored
Normal file
308
vendor/github.com/valyala/fastjson/validate.go
generated
vendored
Normal file
@@ -0,0 +1,308 @@
|
||||
package fastjson
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Validate validates JSON s.
|
||||
func Validate(s string) error {
|
||||
s = skipWS(s)
|
||||
|
||||
tail, err := validateValue(s)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse JSON: %s; unparsed tail: %q", err, startEndString(tail))
|
||||
}
|
||||
tail = skipWS(tail)
|
||||
if len(tail) > 0 {
|
||||
return fmt.Errorf("unexpected tail: %q", startEndString(tail))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateBytes validates JSON b.
|
||||
func ValidateBytes(b []byte) error {
|
||||
return Validate(b2s(b))
|
||||
}
|
||||
|
||||
func validateValue(s string) (string, error) {
|
||||
if len(s) == 0 {
|
||||
return s, fmt.Errorf("cannot parse empty string")
|
||||
}
|
||||
|
||||
if s[0] == '{' {
|
||||
tail, err := validateObject(s[1:])
|
||||
if err != nil {
|
||||
return tail, fmt.Errorf("cannot parse object: %s", err)
|
||||
}
|
||||
return tail, nil
|
||||
}
|
||||
if s[0] == '[' {
|
||||
tail, err := validateArray(s[1:])
|
||||
if err != nil {
|
||||
return tail, fmt.Errorf("cannot parse array: %s", err)
|
||||
}
|
||||
return tail, nil
|
||||
}
|
||||
if s[0] == '"' {
|
||||
sv, tail, err := validateString(s[1:])
|
||||
if err != nil {
|
||||
return tail, fmt.Errorf("cannot parse string: %s", err)
|
||||
}
|
||||
// Scan the string for control chars.
|
||||
for i := 0; i < len(sv); i++ {
|
||||
if sv[i] < 0x20 {
|
||||
return tail, fmt.Errorf("string cannot contain control char 0x%02X", sv[i])
|
||||
}
|
||||
}
|
||||
return tail, nil
|
||||
}
|
||||
if s[0] == 't' {
|
||||
if len(s) < len("true") || s[:len("true")] != "true" {
|
||||
return s, fmt.Errorf("unexpected value found: %q", s)
|
||||
}
|
||||
return s[len("true"):], nil
|
||||
}
|
||||
if s[0] == 'f' {
|
||||
if len(s) < len("false") || s[:len("false")] != "false" {
|
||||
return s, fmt.Errorf("unexpected value found: %q", s)
|
||||
}
|
||||
return s[len("false"):], nil
|
||||
}
|
||||
if s[0] == 'n' {
|
||||
if len(s) < len("null") || s[:len("null")] != "null" {
|
||||
return s, fmt.Errorf("unexpected value found: %q", s)
|
||||
}
|
||||
return s[len("null"):], nil
|
||||
}
|
||||
|
||||
tail, err := validateNumber(s)
|
||||
if err != nil {
|
||||
return tail, fmt.Errorf("cannot parse number: %s", err)
|
||||
}
|
||||
return tail, nil
|
||||
}
|
||||
|
||||
func validateArray(s string) (string, error) {
|
||||
s = skipWS(s)
|
||||
if len(s) == 0 {
|
||||
return s, fmt.Errorf("missing ']'")
|
||||
}
|
||||
if s[0] == ']' {
|
||||
return s[1:], nil
|
||||
}
|
||||
|
||||
for {
|
||||
var err error
|
||||
|
||||
s = skipWS(s)
|
||||
s, err = validateValue(s)
|
||||
if err != nil {
|
||||
return s, fmt.Errorf("cannot parse array value: %s", err)
|
||||
}
|
||||
|
||||
s = skipWS(s)
|
||||
if len(s) == 0 {
|
||||
return s, fmt.Errorf("unexpected end of array")
|
||||
}
|
||||
if s[0] == ',' {
|
||||
s = s[1:]
|
||||
continue
|
||||
}
|
||||
if s[0] == ']' {
|
||||
s = s[1:]
|
||||
return s, nil
|
||||
}
|
||||
return s, fmt.Errorf("missing ',' after array value")
|
||||
}
|
||||
}
|
||||
|
||||
func validateObject(s string) (string, error) {
|
||||
s = skipWS(s)
|
||||
if len(s) == 0 {
|
||||
return s, fmt.Errorf("missing '}'")
|
||||
}
|
||||
if s[0] == '}' {
|
||||
return s[1:], nil
|
||||
}
|
||||
|
||||
for {
|
||||
var err error
|
||||
|
||||
// Parse key.
|
||||
s = skipWS(s)
|
||||
if len(s) == 0 || s[0] != '"' {
|
||||
return s, fmt.Errorf(`cannot find opening '"" for object key`)
|
||||
}
|
||||
|
||||
var key string
|
||||
key, s, err = validateKey(s[1:])
|
||||
if err != nil {
|
||||
return s, fmt.Errorf("cannot parse object key: %s", err)
|
||||
}
|
||||
// Scan the key for control chars.
|
||||
for i := 0; i < len(key); i++ {
|
||||
if key[i] < 0x20 {
|
||||
return s, fmt.Errorf("object key cannot contain control char 0x%02X", key[i])
|
||||
}
|
||||
}
|
||||
s = skipWS(s)
|
||||
if len(s) == 0 || s[0] != ':' {
|
||||
return s, fmt.Errorf("missing ':' after object key")
|
||||
}
|
||||
s = s[1:]
|
||||
|
||||
// Parse value
|
||||
s = skipWS(s)
|
||||
s, err = validateValue(s)
|
||||
if err != nil {
|
||||
return s, fmt.Errorf("cannot parse object value: %s", err)
|
||||
}
|
||||
s = skipWS(s)
|
||||
if len(s) == 0 {
|
||||
return s, fmt.Errorf("unexpected end of object")
|
||||
}
|
||||
if s[0] == ',' {
|
||||
s = s[1:]
|
||||
continue
|
||||
}
|
||||
if s[0] == '}' {
|
||||
return s[1:], nil
|
||||
}
|
||||
return s, fmt.Errorf("missing ',' after object value")
|
||||
}
|
||||
}
|
||||
|
||||
// validateKey is similar to validateString, but is optimized
|
||||
// for typical object keys, which are quite small and have no escape sequences.
|
||||
func validateKey(s string) (string, string, error) {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] == '"' {
|
||||
// Fast path - the key doesn't contain escape sequences.
|
||||
return s[:i], s[i+1:], nil
|
||||
}
|
||||
if s[i] == '\\' {
|
||||
// Slow path - the key contains escape sequences.
|
||||
return validateString(s)
|
||||
}
|
||||
}
|
||||
return "", s, fmt.Errorf(`missing closing '"'`)
|
||||
}
|
||||
|
||||
func validateString(s string) (string, string, error) {
|
||||
// Try fast path - a string without escape sequences.
|
||||
if n := strings.IndexByte(s, '"'); n >= 0 && strings.IndexByte(s[:n], '\\') < 0 {
|
||||
return s[:n], s[n+1:], nil
|
||||
}
|
||||
|
||||
// Slow path - escape sequences are present.
|
||||
rs, tail, err := parseRawString(s)
|
||||
if err != nil {
|
||||
return rs, tail, err
|
||||
}
|
||||
for {
|
||||
n := strings.IndexByte(rs, '\\')
|
||||
if n < 0 {
|
||||
return rs, tail, nil
|
||||
}
|
||||
n++
|
||||
if n >= len(rs) {
|
||||
return rs, tail, fmt.Errorf("BUG: parseRawString returned invalid string with trailing backslash: %q", rs)
|
||||
}
|
||||
ch := rs[n]
|
||||
rs = rs[n+1:]
|
||||
switch ch {
|
||||
case '"', '\\', '/', 'b', 'f', 'n', 'r', 't':
|
||||
// Valid escape sequences - see http://json.org/
|
||||
break
|
||||
case 'u':
|
||||
if len(rs) < 4 {
|
||||
return rs, tail, fmt.Errorf(`too short escape sequence: \u%s`, rs)
|
||||
}
|
||||
xs := rs[:4]
|
||||
_, err := strconv.ParseUint(xs, 16, 16)
|
||||
if err != nil {
|
||||
return rs, tail, fmt.Errorf(`invalid escape sequence \u%s: %s`, xs, err)
|
||||
}
|
||||
rs = rs[4:]
|
||||
default:
|
||||
return rs, tail, fmt.Errorf(`unknown escape sequence \%c`, ch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func validateNumber(s string) (string, error) {
|
||||
if len(s) == 0 {
|
||||
return s, fmt.Errorf("zero-length number")
|
||||
}
|
||||
if s[0] == '-' {
|
||||
s = s[1:]
|
||||
if len(s) == 0 {
|
||||
return s, fmt.Errorf("missing number after minus")
|
||||
}
|
||||
}
|
||||
i := 0
|
||||
for i < len(s) {
|
||||
if s[i] < '0' || s[i] > '9' {
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
if i <= 0 {
|
||||
return s, fmt.Errorf("expecting 0..9 digit, got %c", s[0])
|
||||
}
|
||||
if s[0] == '0' && i != 1 {
|
||||
return s, fmt.Errorf("unexpected number starting from 0")
|
||||
}
|
||||
if i >= len(s) {
|
||||
return "", nil
|
||||
}
|
||||
if s[i] == '.' {
|
||||
// Validate fractional part
|
||||
s = s[i+1:]
|
||||
if len(s) == 0 {
|
||||
return s, fmt.Errorf("missing fractional part")
|
||||
}
|
||||
i = 0
|
||||
for i < len(s) {
|
||||
if s[i] < '0' || s[i] > '9' {
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
if i == 0 {
|
||||
return s, fmt.Errorf("expecting 0..9 digit in fractional part, got %c", s[0])
|
||||
}
|
||||
if i >= len(s) {
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
if s[i] == 'e' || s[i] == 'E' {
|
||||
// Validate exponent part
|
||||
s = s[i+1:]
|
||||
if len(s) == 0 {
|
||||
return s, fmt.Errorf("missing exponent part")
|
||||
}
|
||||
if s[0] == '-' || s[0] == '+' {
|
||||
s = s[1:]
|
||||
if len(s) == 0 {
|
||||
return s, fmt.Errorf("missing exponent part")
|
||||
}
|
||||
}
|
||||
i = 0
|
||||
for i < len(s) {
|
||||
if s[i] < '0' || s[i] > '9' {
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
if i == 0 {
|
||||
return s, fmt.Errorf("expecting 0..9 digit in exponent part, got %c", s[0])
|
||||
}
|
||||
if i >= len(s) {
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
return s[i:], nil
|
||||
}
|
||||
2
vendor/github.com/valyala/gozstd/Makefile
generated
vendored
2
vendor/github.com/valyala/gozstd/Makefile
generated
vendored
@@ -26,7 +26,7 @@ endif
|
||||
|
||||
clean:
|
||||
rm -f $(LIBZSTD_NAME)
|
||||
cd zstd && make clean
|
||||
cd zstd && $(MAKE) clean
|
||||
|
||||
update-zstd:
|
||||
rm -rf zstd-tmp
|
||||
|
||||
BIN
vendor/github.com/valyala/gozstd/libzstd_freebsd_amd64.a
generated
vendored
Normal file
BIN
vendor/github.com/valyala/gozstd/libzstd_freebsd_amd64.a
generated
vendored
Normal file
Binary file not shown.
6
vendor/github.com/valyala/gozstd/libzstd_freebsd_amd64.go
generated
vendored
Normal file
6
vendor/github.com/valyala/gozstd/libzstd_freebsd_amd64.go
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
package gozstd
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: ${SRCDIR}/libzstd_freebsd_amd64.a
|
||||
*/
|
||||
import "C"
|
||||
1
vendor/github.com/valyala/quicktemplate/README.md
generated
vendored
1
vendor/github.com/valyala/quicktemplate/README.md
generated
vendored
@@ -605,7 +605,6 @@ BenchmarkMarshalXMLQuickTemplate1000-4 30000 53000 ns/op 32 B/
|
||||
* Clear syntax insead of hard-to-understand magic stuff related
|
||||
to template arguments, template inheritance and embedding function
|
||||
templates into other templates.
|
||||
* Performance optimizations.
|
||||
|
||||
* *Is there a syntax highlighting for qtpl files?*
|
||||
|
||||
|
||||
1
vendor/golang.org/x/sys/unix/syscall_darwin.go
generated
vendored
1
vendor/golang.org/x/sys/unix/syscall_darwin.go
generated
vendored
@@ -89,7 +89,6 @@ func direntNamlen(buf []byte) (uint64, bool) {
|
||||
return readInt(buf, unsafe.Offsetof(Dirent{}.Namlen), unsafe.Sizeof(Dirent{}.Namlen))
|
||||
}
|
||||
|
||||
//sys ptrace(request int, pid int, addr uintptr, data uintptr) (err error)
|
||||
func PtraceAttach(pid int) (err error) { return ptrace(PT_ATTACH, pid, 0, 0) }
|
||||
func PtraceDetach(pid int) (err error) { return ptrace(PT_DETACH, pid, 0, 0) }
|
||||
|
||||
|
||||
2
vendor/golang.org/x/sys/unix/syscall_darwin_386.go
generated
vendored
2
vendor/golang.org/x/sys/unix/syscall_darwin_386.go
generated
vendored
@@ -10,6 +10,8 @@ import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
//sys ptrace(request int, pid int, addr uintptr, data uintptr) (err error)
|
||||
|
||||
func setTimespec(sec, nsec int64) Timespec {
|
||||
return Timespec{Sec: int32(sec), Nsec: int32(nsec)}
|
||||
}
|
||||
|
||||
2
vendor/golang.org/x/sys/unix/syscall_darwin_amd64.go
generated
vendored
2
vendor/golang.org/x/sys/unix/syscall_darwin_amd64.go
generated
vendored
@@ -10,6 +10,8 @@ import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
//sys ptrace(request int, pid int, addr uintptr, data uintptr) (err error)
|
||||
|
||||
func setTimespec(sec, nsec int64) Timespec {
|
||||
return Timespec{Sec: sec, Nsec: nsec}
|
||||
}
|
||||
|
||||
4
vendor/golang.org/x/sys/unix/syscall_darwin_arm.go
generated
vendored
4
vendor/golang.org/x/sys/unix/syscall_darwin_arm.go
generated
vendored
@@ -8,6 +8,10 @@ import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func ptrace(request int, pid int, addr uintptr, data uintptr) error {
|
||||
return ENOTSUP
|
||||
}
|
||||
|
||||
func setTimespec(sec, nsec int64) Timespec {
|
||||
return Timespec{Sec: int32(sec), Nsec: int32(nsec)}
|
||||
}
|
||||
|
||||
4
vendor/golang.org/x/sys/unix/syscall_darwin_arm64.go
generated
vendored
4
vendor/golang.org/x/sys/unix/syscall_darwin_arm64.go
generated
vendored
@@ -10,6 +10,10 @@ import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func ptrace(request int, pid int, addr uintptr, data uintptr) error {
|
||||
return ENOTSUP
|
||||
}
|
||||
|
||||
func setTimespec(sec, nsec int64) Timespec {
|
||||
return Timespec{Sec: sec, Nsec: nsec}
|
||||
}
|
||||
|
||||
2
vendor/golang.org/x/sys/unix/zerrors_linux_386.go
generated
vendored
2
vendor/golang.org/x/sys/unix/zerrors_linux_386.go
generated
vendored
@@ -722,6 +722,7 @@ const (
|
||||
F_OFD_SETLKW = 0x26
|
||||
F_OK = 0x0
|
||||
F_RDLCK = 0x0
|
||||
F_SEAL_FUTURE_WRITE = 0x10
|
||||
F_SEAL_GROW = 0x4
|
||||
F_SEAL_SEAL = 0x1
|
||||
F_SEAL_SHRINK = 0x2
|
||||
@@ -987,6 +988,7 @@ const (
|
||||
IPV6_RECVRTHDR = 0x38
|
||||
IPV6_RECVTCLASS = 0x42
|
||||
IPV6_ROUTER_ALERT = 0x16
|
||||
IPV6_ROUTER_ALERT_ISOLATE = 0x1e
|
||||
IPV6_RTHDR = 0x39
|
||||
IPV6_RTHDRDSTOPTS = 0x37
|
||||
IPV6_RTHDR_LOOSE = 0x0
|
||||
|
||||
2
vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go
generated
vendored
2
vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go
generated
vendored
@@ -722,6 +722,7 @@ const (
|
||||
F_OFD_SETLKW = 0x26
|
||||
F_OK = 0x0
|
||||
F_RDLCK = 0x0
|
||||
F_SEAL_FUTURE_WRITE = 0x10
|
||||
F_SEAL_GROW = 0x4
|
||||
F_SEAL_SEAL = 0x1
|
||||
F_SEAL_SHRINK = 0x2
|
||||
@@ -987,6 +988,7 @@ const (
|
||||
IPV6_RECVRTHDR = 0x38
|
||||
IPV6_RECVTCLASS = 0x42
|
||||
IPV6_ROUTER_ALERT = 0x16
|
||||
IPV6_ROUTER_ALERT_ISOLATE = 0x1e
|
||||
IPV6_RTHDR = 0x39
|
||||
IPV6_RTHDRDSTOPTS = 0x37
|
||||
IPV6_RTHDR_LOOSE = 0x0
|
||||
|
||||
2
vendor/golang.org/x/sys/unix/zerrors_linux_arm.go
generated
vendored
2
vendor/golang.org/x/sys/unix/zerrors_linux_arm.go
generated
vendored
@@ -721,6 +721,7 @@ const (
|
||||
F_OFD_SETLKW = 0x26
|
||||
F_OK = 0x0
|
||||
F_RDLCK = 0x0
|
||||
F_SEAL_FUTURE_WRITE = 0x10
|
||||
F_SEAL_GROW = 0x4
|
||||
F_SEAL_SEAL = 0x1
|
||||
F_SEAL_SHRINK = 0x2
|
||||
@@ -986,6 +987,7 @@ const (
|
||||
IPV6_RECVRTHDR = 0x38
|
||||
IPV6_RECVTCLASS = 0x42
|
||||
IPV6_ROUTER_ALERT = 0x16
|
||||
IPV6_ROUTER_ALERT_ISOLATE = 0x1e
|
||||
IPV6_RTHDR = 0x39
|
||||
IPV6_RTHDRDSTOPTS = 0x37
|
||||
IPV6_RTHDR_LOOSE = 0x0
|
||||
|
||||
2
vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go
generated
vendored
2
vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go
generated
vendored
@@ -724,6 +724,7 @@ const (
|
||||
F_OFD_SETLKW = 0x26
|
||||
F_OK = 0x0
|
||||
F_RDLCK = 0x0
|
||||
F_SEAL_FUTURE_WRITE = 0x10
|
||||
F_SEAL_GROW = 0x4
|
||||
F_SEAL_SEAL = 0x1
|
||||
F_SEAL_SHRINK = 0x2
|
||||
@@ -989,6 +990,7 @@ const (
|
||||
IPV6_RECVRTHDR = 0x38
|
||||
IPV6_RECVTCLASS = 0x42
|
||||
IPV6_ROUTER_ALERT = 0x16
|
||||
IPV6_ROUTER_ALERT_ISOLATE = 0x1e
|
||||
IPV6_RTHDR = 0x39
|
||||
IPV6_RTHDRDSTOPTS = 0x37
|
||||
IPV6_RTHDR_LOOSE = 0x0
|
||||
|
||||
2
vendor/golang.org/x/sys/unix/zerrors_linux_mips.go
generated
vendored
2
vendor/golang.org/x/sys/unix/zerrors_linux_mips.go
generated
vendored
@@ -721,6 +721,7 @@ const (
|
||||
F_OFD_SETLKW = 0x26
|
||||
F_OK = 0x0
|
||||
F_RDLCK = 0x0
|
||||
F_SEAL_FUTURE_WRITE = 0x10
|
||||
F_SEAL_GROW = 0x4
|
||||
F_SEAL_SEAL = 0x1
|
||||
F_SEAL_SHRINK = 0x2
|
||||
@@ -986,6 +987,7 @@ const (
|
||||
IPV6_RECVRTHDR = 0x38
|
||||
IPV6_RECVTCLASS = 0x42
|
||||
IPV6_ROUTER_ALERT = 0x16
|
||||
IPV6_ROUTER_ALERT_ISOLATE = 0x1e
|
||||
IPV6_RTHDR = 0x39
|
||||
IPV6_RTHDRDSTOPTS = 0x37
|
||||
IPV6_RTHDR_LOOSE = 0x0
|
||||
|
||||
2
vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go
generated
vendored
2
vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go
generated
vendored
@@ -721,6 +721,7 @@ const (
|
||||
F_OFD_SETLKW = 0x26
|
||||
F_OK = 0x0
|
||||
F_RDLCK = 0x0
|
||||
F_SEAL_FUTURE_WRITE = 0x10
|
||||
F_SEAL_GROW = 0x4
|
||||
F_SEAL_SEAL = 0x1
|
||||
F_SEAL_SHRINK = 0x2
|
||||
@@ -986,6 +987,7 @@ const (
|
||||
IPV6_RECVRTHDR = 0x38
|
||||
IPV6_RECVTCLASS = 0x42
|
||||
IPV6_ROUTER_ALERT = 0x16
|
||||
IPV6_ROUTER_ALERT_ISOLATE = 0x1e
|
||||
IPV6_RTHDR = 0x39
|
||||
IPV6_RTHDRDSTOPTS = 0x37
|
||||
IPV6_RTHDR_LOOSE = 0x0
|
||||
|
||||
2
vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go
generated
vendored
2
vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go
generated
vendored
@@ -721,6 +721,7 @@ const (
|
||||
F_OFD_SETLKW = 0x26
|
||||
F_OK = 0x0
|
||||
F_RDLCK = 0x0
|
||||
F_SEAL_FUTURE_WRITE = 0x10
|
||||
F_SEAL_GROW = 0x4
|
||||
F_SEAL_SEAL = 0x1
|
||||
F_SEAL_SHRINK = 0x2
|
||||
@@ -986,6 +987,7 @@ const (
|
||||
IPV6_RECVRTHDR = 0x38
|
||||
IPV6_RECVTCLASS = 0x42
|
||||
IPV6_ROUTER_ALERT = 0x16
|
||||
IPV6_ROUTER_ALERT_ISOLATE = 0x1e
|
||||
IPV6_RTHDR = 0x39
|
||||
IPV6_RTHDRDSTOPTS = 0x37
|
||||
IPV6_RTHDR_LOOSE = 0x0
|
||||
|
||||
2
vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go
generated
vendored
2
vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go
generated
vendored
@@ -721,6 +721,7 @@ const (
|
||||
F_OFD_SETLKW = 0x26
|
||||
F_OK = 0x0
|
||||
F_RDLCK = 0x0
|
||||
F_SEAL_FUTURE_WRITE = 0x10
|
||||
F_SEAL_GROW = 0x4
|
||||
F_SEAL_SEAL = 0x1
|
||||
F_SEAL_SHRINK = 0x2
|
||||
@@ -986,6 +987,7 @@ const (
|
||||
IPV6_RECVRTHDR = 0x38
|
||||
IPV6_RECVTCLASS = 0x42
|
||||
IPV6_ROUTER_ALERT = 0x16
|
||||
IPV6_ROUTER_ALERT_ISOLATE = 0x1e
|
||||
IPV6_RTHDR = 0x39
|
||||
IPV6_RTHDRDSTOPTS = 0x37
|
||||
IPV6_RTHDR_LOOSE = 0x0
|
||||
|
||||
2
vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go
generated
vendored
2
vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go
generated
vendored
@@ -721,6 +721,7 @@ const (
|
||||
F_OFD_SETLKW = 0x26
|
||||
F_OK = 0x0
|
||||
F_RDLCK = 0x0
|
||||
F_SEAL_FUTURE_WRITE = 0x10
|
||||
F_SEAL_GROW = 0x4
|
||||
F_SEAL_SEAL = 0x1
|
||||
F_SEAL_SHRINK = 0x2
|
||||
@@ -986,6 +987,7 @@ const (
|
||||
IPV6_RECVRTHDR = 0x38
|
||||
IPV6_RECVTCLASS = 0x42
|
||||
IPV6_ROUTER_ALERT = 0x16
|
||||
IPV6_ROUTER_ALERT_ISOLATE = 0x1e
|
||||
IPV6_RTHDR = 0x39
|
||||
IPV6_RTHDRDSTOPTS = 0x37
|
||||
IPV6_RTHDR_LOOSE = 0x0
|
||||
|
||||
2
vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go
generated
vendored
2
vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go
generated
vendored
@@ -721,6 +721,7 @@ const (
|
||||
F_OFD_SETLKW = 0x26
|
||||
F_OK = 0x0
|
||||
F_RDLCK = 0x0
|
||||
F_SEAL_FUTURE_WRITE = 0x10
|
||||
F_SEAL_GROW = 0x4
|
||||
F_SEAL_SEAL = 0x1
|
||||
F_SEAL_SHRINK = 0x2
|
||||
@@ -986,6 +987,7 @@ const (
|
||||
IPV6_RECVRTHDR = 0x38
|
||||
IPV6_RECVTCLASS = 0x42
|
||||
IPV6_ROUTER_ALERT = 0x16
|
||||
IPV6_ROUTER_ALERT_ISOLATE = 0x1e
|
||||
IPV6_RTHDR = 0x39
|
||||
IPV6_RTHDRDSTOPTS = 0x37
|
||||
IPV6_RTHDR_LOOSE = 0x0
|
||||
|
||||
2
vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go
generated
vendored
2
vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go
generated
vendored
@@ -721,6 +721,7 @@ const (
|
||||
F_OFD_SETLKW = 0x26
|
||||
F_OK = 0x0
|
||||
F_RDLCK = 0x0
|
||||
F_SEAL_FUTURE_WRITE = 0x10
|
||||
F_SEAL_GROW = 0x4
|
||||
F_SEAL_SEAL = 0x1
|
||||
F_SEAL_SHRINK = 0x2
|
||||
@@ -986,6 +987,7 @@ const (
|
||||
IPV6_RECVRTHDR = 0x38
|
||||
IPV6_RECVTCLASS = 0x42
|
||||
IPV6_ROUTER_ALERT = 0x16
|
||||
IPV6_ROUTER_ALERT_ISOLATE = 0x1e
|
||||
IPV6_RTHDR = 0x39
|
||||
IPV6_RTHDRDSTOPTS = 0x37
|
||||
IPV6_RTHDR_LOOSE = 0x0
|
||||
|
||||
2
vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go
generated
vendored
2
vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go
generated
vendored
@@ -721,6 +721,7 @@ const (
|
||||
F_OFD_SETLKW = 0x26
|
||||
F_OK = 0x0
|
||||
F_RDLCK = 0x0
|
||||
F_SEAL_FUTURE_WRITE = 0x10
|
||||
F_SEAL_GROW = 0x4
|
||||
F_SEAL_SEAL = 0x1
|
||||
F_SEAL_SHRINK = 0x2
|
||||
@@ -986,6 +987,7 @@ const (
|
||||
IPV6_RECVRTHDR = 0x38
|
||||
IPV6_RECVTCLASS = 0x42
|
||||
IPV6_ROUTER_ALERT = 0x16
|
||||
IPV6_ROUTER_ALERT_ISOLATE = 0x1e
|
||||
IPV6_RTHDR = 0x39
|
||||
IPV6_RTHDRDSTOPTS = 0x37
|
||||
IPV6_RTHDR_LOOSE = 0x0
|
||||
|
||||
2
vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go
generated
vendored
2
vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go
generated
vendored
@@ -725,6 +725,7 @@ const (
|
||||
F_OFD_SETLKW = 0x26
|
||||
F_OK = 0x0
|
||||
F_RDLCK = 0x1
|
||||
F_SEAL_FUTURE_WRITE = 0x10
|
||||
F_SEAL_GROW = 0x4
|
||||
F_SEAL_SEAL = 0x1
|
||||
F_SEAL_SHRINK = 0x2
|
||||
@@ -990,6 +991,7 @@ const (
|
||||
IPV6_RECVRTHDR = 0x38
|
||||
IPV6_RECVTCLASS = 0x42
|
||||
IPV6_ROUTER_ALERT = 0x16
|
||||
IPV6_ROUTER_ALERT_ISOLATE = 0x1e
|
||||
IPV6_RTHDR = 0x39
|
||||
IPV6_RTHDRDSTOPTS = 0x37
|
||||
IPV6_RTHDR_LOOSE = 0x0
|
||||
|
||||
20
vendor/golang.org/x/sys/unix/zsyscall_darwin_386.1_11.go
generated
vendored
20
vendor/golang.org/x/sys/unix/zsyscall_darwin_386.1_11.go
generated
vendored
@@ -377,16 +377,6 @@ func Munlockall() (err error) {
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func ptrace(request int, pid int, addr uintptr, data uintptr) (err error) {
|
||||
_, _, e1 := Syscall6(SYS_PTRACE, uintptr(request), uintptr(pid), uintptr(addr), uintptr(data), 0, 0)
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func getattrlist(path *byte, list unsafe.Pointer, buf unsafe.Pointer, size uintptr, options int) (err error) {
|
||||
_, _, e1 := Syscall6(SYS_GETATTRLIST, uintptr(unsafe.Pointer(path)), uintptr(list), uintptr(buf), uintptr(size), uintptr(options), 0)
|
||||
if e1 != 0 {
|
||||
@@ -1691,6 +1681,16 @@ func writelen(fd int, buf *byte, nbuf int) (n int, err error) {
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func ptrace(request int, pid int, addr uintptr, data uintptr) (err error) {
|
||||
_, _, e1 := Syscall6(SYS_PTRACE, uintptr(request), uintptr(pid), uintptr(addr), uintptr(data), 0, 0)
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func gettimeofday(tp *Timeval) (sec int32, usec int32, err error) {
|
||||
r0, r1, e1 := RawSyscall(SYS_GETTIMEOFDAY, uintptr(unsafe.Pointer(tp)), 0, 0)
|
||||
sec = int32(r0)
|
||||
|
||||
30
vendor/golang.org/x/sys/unix/zsyscall_darwin_386.go
generated
vendored
30
vendor/golang.org/x/sys/unix/zsyscall_darwin_386.go
generated
vendored
@@ -527,21 +527,6 @@ func libc_munlockall_trampoline()
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func ptrace(request int, pid int, addr uintptr, data uintptr) (err error) {
|
||||
_, _, e1 := syscall_syscall6(funcPC(libc_ptrace_trampoline), uintptr(request), uintptr(pid), uintptr(addr), uintptr(data), 0, 0)
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func libc_ptrace_trampoline()
|
||||
|
||||
//go:linkname libc_ptrace libc_ptrace
|
||||
//go:cgo_import_dynamic libc_ptrace ptrace "/usr/lib/libSystem.B.dylib"
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func getattrlist(path *byte, list unsafe.Pointer, buf unsafe.Pointer, size uintptr, options int) (err error) {
|
||||
_, _, e1 := syscall_syscall6(funcPC(libc_getattrlist_trampoline), uintptr(unsafe.Pointer(path)), uintptr(list), uintptr(buf), uintptr(size), uintptr(options), 0)
|
||||
if e1 != 0 {
|
||||
@@ -2341,6 +2326,21 @@ func writelen(fd int, buf *byte, nbuf int) (n int, err error) {
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func ptrace(request int, pid int, addr uintptr, data uintptr) (err error) {
|
||||
_, _, e1 := syscall_syscall6(funcPC(libc_ptrace_trampoline), uintptr(request), uintptr(pid), uintptr(addr), uintptr(data), 0, 0)
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func libc_ptrace_trampoline()
|
||||
|
||||
//go:linkname libc_ptrace libc_ptrace
|
||||
//go:cgo_import_dynamic libc_ptrace ptrace "/usr/lib/libSystem.B.dylib"
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func gettimeofday(tp *Timeval) (sec int32, usec int32, err error) {
|
||||
r0, r1, e1 := syscall_rawSyscall(funcPC(libc_gettimeofday_trampoline), uintptr(unsafe.Pointer(tp)), 0, 0)
|
||||
sec = int32(r0)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user