mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-06-09 11:54:31 +03:00
Compare commits
97 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c2099ecfe | ||
|
|
885ba17905 | ||
|
|
b9a06e8e74 | ||
|
|
30c8301b11 | ||
|
|
e53f9e553d | ||
|
|
d6ade02fd3 | ||
|
|
3c90d77858 | ||
|
|
478767d0ed | ||
|
|
02e0b19a62 | ||
|
|
6be4456d88 | ||
|
|
9becc26f4b | ||
|
|
c62399eb3e | ||
|
|
55d728c849 | ||
|
|
808fc0971f | ||
|
|
370cfbb365 | ||
|
|
2f58f37f07 | ||
|
|
d18ea0c95b | ||
|
|
e0b292c6de | ||
|
|
86f6be40db | ||
|
|
e76e21e4c7 | ||
|
|
cfa5e279c2 | ||
|
|
fa7c3ab93a | ||
|
|
26d570bb3a | ||
|
|
62ed508546 | ||
|
|
2e2eff90d5 | ||
|
|
855e5c8963 | ||
|
|
04e48ef064 | ||
|
|
971206b514 | ||
|
|
d063bfaf83 | ||
|
|
6ab48838bf | ||
|
|
a42b5db39f | ||
|
|
b0295dbf2e | ||
|
|
3cea200309 | ||
|
|
32600ba4fc | ||
|
|
b3c946e35a | ||
|
|
e83fe938c8 | ||
|
|
f708aa7003 | ||
|
|
97ce4e03a5 | ||
|
|
a398343bb6 | ||
|
|
6ebf537153 | ||
|
|
f752479cb8 | ||
|
|
61e956e175 | ||
|
|
c66a691593 | ||
|
|
cc21b31502 | ||
|
|
195cefd81a | ||
|
|
c1581c3810 | ||
|
|
16cae15c45 | ||
|
|
f6334bffa1 | ||
|
|
2abd5154e0 | ||
|
|
c1cf7d9f93 | ||
|
|
956fdd89d3 | ||
|
|
1bc6377863 | ||
|
|
1e2c511747 | ||
|
|
0eeffb910f | ||
|
|
4ba86f501a | ||
|
|
fdc5cfd838 | ||
|
|
a116f5e7c1 | ||
|
|
4e9e1ca0f7 | ||
|
|
c1d3705be0 | ||
|
|
b7ee2e7af2 | ||
|
|
67d44b0845 | ||
|
|
1e6ae9eff4 | ||
|
|
fa81f82714 | ||
|
|
0fa6df94a2 | ||
|
|
c39355921e | ||
|
|
cf4786f34a | ||
|
|
3e67862676 | ||
|
|
0db9fcedd5 | ||
|
|
391530bb74 | ||
|
|
60c5b368bc | ||
|
|
26dc21cf64 | ||
|
|
2444433d83 | ||
|
|
ea4c828bae | ||
|
|
aebc45ad26 | ||
|
|
2cb811b42f | ||
|
|
b986516fbe | ||
|
|
ef2296e420 | ||
|
|
a6086cde78 | ||
|
|
c9063ece66 | ||
|
|
4e26ad869b | ||
|
|
0772191975 | ||
|
|
48999e5396 | ||
|
|
0adebae1f8 | ||
|
|
267efde5ae | ||
|
|
0686ac52c3 | ||
|
|
68722c3c74 | ||
|
|
a544f49c2b | ||
|
|
d32f88c378 | ||
|
|
00cfb2d2b9 | ||
|
|
37dc223e25 | ||
|
|
a84fe76677 | ||
|
|
3a697a935a | ||
|
|
51a21c7d4b | ||
|
|
3d83f5d334 | ||
|
|
6f3b2fd600 | ||
|
|
8d35718dc6 | ||
|
|
33975513d0 |
1
.github/workflows/main.yml
vendored
1
.github/workflows/main.yml
vendored
@@ -29,6 +29,7 @@ jobs:
|
||||
git diff --exit-code
|
||||
make test-full
|
||||
make test-pure
|
||||
make test-full-386
|
||||
make victoria-metrics
|
||||
make victoria-metrics-pure
|
||||
make victoria-metrics-arm
|
||||
|
||||
8
Makefile
8
Makefile
@@ -26,6 +26,9 @@ package: package-victoria-metrics
|
||||
release: victoria-metrics-prod
|
||||
cd bin && tar czf victoria-metrics-$(PKG_TAG).tar.gz victoria-metrics-prod
|
||||
|
||||
pprof-cpu:
|
||||
go tool pprof -trim_path=github.com/VictoriaMetrics/VictoriaMetrics@ $(PPROF_FILE)
|
||||
|
||||
fmt:
|
||||
GO111MODULE=on gofmt -l -w -s ./lib
|
||||
GO111MODULE=on gofmt -l -w -s ./app
|
||||
@@ -61,6 +64,9 @@ test-pure:
|
||||
test-full:
|
||||
GO111MODULE=on go test -tags=integration -mod=vendor -coverprofile=coverage.txt -covermode=atomic ./lib/... ./app/...
|
||||
|
||||
test-full-386:
|
||||
GO111MODULE=on GOARCH=386 go test -tags=integration -mod=vendor -coverprofile=coverage.txt -covermode=atomic ./lib/... ./app/...
|
||||
|
||||
benchmark:
|
||||
GO111MODULE=on go test -mod=vendor -bench=. ./lib/...
|
||||
GO111MODULE=on go test -mod=vendor -bench=. ./app/...
|
||||
@@ -89,7 +95,7 @@ install-qtc:
|
||||
|
||||
|
||||
golangci-lint: install-golangci-lint
|
||||
golangci-lint run --exclude '(SA4003|SA1019):' -D errcheck
|
||||
golangci-lint run --exclude '(SA4003|SA1019):' -D errcheck -D structcheck
|
||||
|
||||
install-golangci-lint:
|
||||
which golangci-lint || GO111MODULE=off go get -u github.com/golangci/golangci-lint/cmd/golangci-lint
|
||||
|
||||
43
README.md
43
README.md
@@ -89,6 +89,7 @@ Cluster version is available [here](https://github.com/VictoriaMetrics/VictoriaM
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Backfilling](#backfilling)
|
||||
- [Profiling](#profiling)
|
||||
- [Integrations](#integrations)
|
||||
- [Roadmap](#roadmap)
|
||||
- [Contacts](#contacts)
|
||||
- [Community and contributions](#community-and-contributions)
|
||||
@@ -107,8 +108,8 @@ or [docker image](https://hub.docker.com/r/victoriametrics/victoria-metrics/) wi
|
||||
|
||||
The following command-line flags are used the most:
|
||||
|
||||
* `-storageDataPath` - path to data directory. VictoriaMetrics stores all the data in this directory.
|
||||
* `-retentionPeriod` - retention period in months for the data. Older data is automatically deleted.
|
||||
* `-storageDataPath` - path to data directory. VictoriaMetrics stores all the data in this directory. Default path is `victoria-metrics-data` in current working directory.
|
||||
* `-retentionPeriod` - retention period in months for the data. Older data is automatically deleted. Default period is 1 month.
|
||||
* `-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 over telnet protocol. By default, it is disabled.
|
||||
@@ -156,7 +157,7 @@ The label name may be arbitrary - `datacenter` is just an example. The label val
|
||||
across Prometheus instances, so those time series may be filtered and grouped by this label.
|
||||
|
||||
|
||||
It is recommended upgrading Prometheus to [v2.10.0](https://github.com/prometheus/prometheus/releases) or newer,
|
||||
It is recommended upgrading Prometheus to [v2.12.0](https://github.com/prometheus/prometheus/releases) or newer,
|
||||
since the previous versions may have issues with `remote_write`.
|
||||
|
||||
|
||||
@@ -253,8 +254,8 @@ curl -G 'http://localhost:8428/api/v1/export' -d 'match={__name__!=""}'
|
||||
The `/api/v1/export` endpoint should return the following response:
|
||||
|
||||
```
|
||||
{"metric":{"__name__":"measurement.field1","tag1":"value1","tag2":"value2"},"values":[123],"timestamps":[1560272508147]}
|
||||
{"metric":{"__name__":"measurement.field2","tag1":"value1","tag2":"value2"},"values":[1.23],"timestamps":[1560272508147]}
|
||||
{"metric":{"__name__":"measurement_field1","tag1":"value1","tag2":"value2"},"values":[123],"timestamps":[1560272508147]}
|
||||
{"metric":{"__name__":"measurement_field2","tag1":"value1","tag2":"value2"},"values":[1.23],"timestamps":[1560272508147]}
|
||||
```
|
||||
|
||||
Note that Influx line protocol expects [timestamps in *nanoseconds* by default](https://docs.influxdata.com/influxdb/v1.7/write_protocols/line_protocol_tutorial/#timestamp),
|
||||
@@ -511,7 +512,7 @@ at `http://<victoriametrics-addr>:8428/federate?match[]=<timeseries_selector_for
|
||||
|
||||
Optional `start` and `end` args may be added to the request in order to scrape the last point for each selected time series on the `[start ... end]` interval.
|
||||
`start` and `end` may contain either unix timestamp in seconds or [RFC3339](https://www.ietf.org/rfc/rfc3339.txt) values. By default, the last point
|
||||
on the interval `[now - max_lookback ... now]` is scraped for each time series. The default value for `max_lookback` is `5m` (5 minutes), but can be overridden.
|
||||
on the interval `[now - max_lookback ... now]` is scraped for each time series. The default value for `max_lookback` is `5m` (5 minutes), but it can be overridden.
|
||||
For instance, `/federate?match[]=up&max_lookback=1h` would return last points on the `[now - 1h ... now]` interval. This may be useful for time series federation
|
||||
with scrape intervals exceeding `5m`.
|
||||
|
||||
@@ -527,7 +528,7 @@ A rough estimation of the required resources for ingestion path:
|
||||
VictoriaMetrics stores various caches in RAM. Memory size for these caches may be limited by `-memory.allowedPercent` flag.
|
||||
|
||||
* CPU cores: a CPU core per 300K inserted data points per second. So, ~4 CPU cores are required for processing
|
||||
the insert stream of 1M data points per second. The ingestion rate may be lower for high cardinality data.
|
||||
the insert stream of 1M data points per second. The ingestion rate may be lower for high cardinality data or for time series with high number of labels.
|
||||
See [this article](https://medium.com/@valyala/insert-benchmarks-with-inch-influxdb-vs-victoriametrics-e31a41ae2893) for details.
|
||||
If you see lower numbers per CPU core, then it is likely active time series info doesn't fit caches,
|
||||
so you need more RAM for lowering CPU usage.
|
||||
@@ -652,6 +653,14 @@ For example, substitute `-graphiteListenAddr=:2003` with `-graphiteListenAddr=<i
|
||||
* There is no need in Operating System tuning since VictoriaMetrics is optimized for default OS settings.
|
||||
The only option is increasing the limit on [the number of open files in the OS](https://medium.com/@muhammadtriwibowo/set-permanently-ulimit-n-open-files-in-ubuntu-4d61064429a),
|
||||
so Prometheus instances could establish more connections to VictoriaMetrics.
|
||||
* The recommended filesystem is `ext4`, the recommended persistent storage is [persistent HDD-based disk on GCP](https://cloud.google.com/compute/docs/disks/#pdspecs),
|
||||
since it is protected from hardware failures via internal replication and it can be [resized on the fly](https://cloud.google.com/compute/docs/disks/add-persistent-disk#resize_pd).
|
||||
If you plan storing more than 1TB of data on `ext4` partition or plan extending it to more than 16TB,
|
||||
then the following options are recommended to pass to `mkfs.ext4`:
|
||||
|
||||
```
|
||||
mkfs.ext4 ... -O 64bit,huge_file,extent -T huge
|
||||
```
|
||||
|
||||
|
||||
### Monitoring
|
||||
@@ -664,10 +673,7 @@ The most interesting metrics are:
|
||||
|
||||
* `vm_cache_entries{type="storage/hour_metric_ids"}` - the number of time series with new data points during the last hour
|
||||
aka active time series.
|
||||
* `vm_rows{type="indexdb"}` - the number of rows in inverted index. Each label in each unique time series adds a single
|
||||
row into the inverted index. An approximate number of time series in the database may be calculated as
|
||||
`vm_rows{type="indexdb"} / (avg_labels_per_series + 1)`, where `avg_labels_per_series` is the average number of labels
|
||||
per each time series.
|
||||
* `vm_rows{type="indexdb"}` - the number of rows in inverted index. High value for this number usually mean high churn rate for time series.
|
||||
* Sum of `vm_rows{type="storage/big"}` and `vm_rows{type="storage/small"}` - total number of `(timestamp, value)` data points
|
||||
in the database.
|
||||
* Sum of all the `vm_cache_size_bytes` metrics - the total size of all the caches in the database.
|
||||
@@ -678,6 +684,9 @@ The most interesting metrics are:
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
* It is recommended to use default command-line flag values (i.e. don't set them explicitly) until the need
|
||||
in tweaking these flag values arises.
|
||||
|
||||
* If VictoriaMetrics works slowly and eats more than a CPU core per 100K ingested data points per second,
|
||||
then it is likely you have too many active time series for the current amount of RAM.
|
||||
It is recommended increasing the amount of RAM on the node with VictoriaMetrics in order to improve
|
||||
@@ -723,6 +732,14 @@ 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).
|
||||
|
||||
|
||||
## Integrations
|
||||
|
||||
* [netdata](https://github.com/netdata/netdata) can push data into VictoriaMetrics via `Prometheus remote_write API`.
|
||||
See [these docs](https://github.com/netdata/netdata#integrations).
|
||||
* [go-graphite/carbonapi](https://github.com/go-graphite/carbonapi) can use VictoriaMetrics as time series backend.
|
||||
See [this example](/blob/master/cmd/carbonapi/carbonapi.example.prometheus.yaml).
|
||||
|
||||
|
||||
## Roadmap
|
||||
|
||||
- [ ] Replication [#118](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/118)
|
||||
@@ -745,8 +762,8 @@ Contact us with any questions regarding VictoriaMetrics at [info@victoriametrics
|
||||
Feel free asking any questions regarding VictoriaMetrics:
|
||||
|
||||
- [slack](http://slack.victoriametrics.com/)
|
||||
- [telergam-en](https://t.me/VictoriaMetrics_en)
|
||||
- [telergam-ru](https://t.me/VictoriaMetrics_ru1)
|
||||
- [telegram-en](https://t.me/VictoriaMetrics_en)
|
||||
- [telegram-ru](https://t.me/VictoriaMetrics_ru1)
|
||||
- [google groups](https://groups.google.com/forum/#!forum/victorametrics-users)
|
||||
|
||||
|
||||
|
||||
@@ -32,6 +32,12 @@ victoria-metrics-arm64:
|
||||
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-386:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=386 GO111MODULE=on go build -mod=vendor -ldflags "$(GO_BUILDINFO)" -o bin/victoria-metrics-386 ./app/victoria-metrics
|
||||
|
||||
victoria-metrics-386-prod:
|
||||
APP_NAME=victoria-metrics APP_SUFFIX='-386' DOCKER_OPTS='--env CGO_ENABLED=0 --env GOARCH=386' $(MAKE) app-via-docker
|
||||
|
||||
victoria-metrics-pure:
|
||||
APP_NAME=victoria-metrics $(MAKE) app-local-pure
|
||||
|
||||
|
||||
@@ -50,52 +50,73 @@ const (
|
||||
testStorageInitTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
const (
|
||||
tplWordTime = "{TIME}"
|
||||
tplQuotedWordTimeSeconds = `"{TIME_S}"`
|
||||
tplQuotedWordTimeMillis = `"{TIME_MS}"`
|
||||
)
|
||||
|
||||
var (
|
||||
storagePath string
|
||||
insertionTime = time.Now().UTC()
|
||||
)
|
||||
|
||||
type test struct {
|
||||
Name string `json:"name"`
|
||||
Data string `json:"data"`
|
||||
Query string `json:"query"`
|
||||
Result []Row `json:"result"`
|
||||
Name string `json:"name"`
|
||||
Data []string `json:"data"`
|
||||
Query []string `json:"query"`
|
||||
ResultMetrics []Metric `json:"result_metrics"`
|
||||
ResultSeries Series `json:"result_series"`
|
||||
ResultQuery Query `json:"result_query"`
|
||||
ResultQueryRange QueryRange `json:"result_query_range"`
|
||||
Issue string `json:"issue"`
|
||||
}
|
||||
|
||||
type Row struct {
|
||||
type Metric struct {
|
||||
Metric map[string]string `json:"metric"`
|
||||
Values []float64 `json:"values"`
|
||||
Timestamps []int64 `json:"timestamps"`
|
||||
}
|
||||
|
||||
func (r *Row) UnmarshalJSON(b []byte) error {
|
||||
type withoutInterface Row
|
||||
var to withoutInterface
|
||||
if err := json.Unmarshal(populateTimeTpl(b), &to); err != nil {
|
||||
return err
|
||||
}
|
||||
*r = Row(to)
|
||||
return nil
|
||||
func (r *Metric) UnmarshalJSON(b []byte) error {
|
||||
type plain Metric
|
||||
return json.Unmarshal(testutil.PopulateTimeTpl(b, insertionTime), (*plain)(r))
|
||||
}
|
||||
|
||||
func populateTimeTpl(b []byte) []byte {
|
||||
var (
|
||||
tplTimeToQuotedMS = [2][]byte{[]byte(tplQuotedWordTimeMillis), []byte(fmt.Sprintf("%d", timeToMillis(insertionTime)))}
|
||||
tpsTimeToQuotedS = [2][]byte{[]byte(tplQuotedWordTimeSeconds), []byte(fmt.Sprintf("%d", insertionTime.Unix()*1e3))}
|
||||
)
|
||||
tpls := [][2][]byte{
|
||||
tplTimeToQuotedMS, tpsTimeToQuotedS,
|
||||
}
|
||||
for i := range tpls {
|
||||
b = bytes.ReplaceAll(b, tpls[i][0], tpls[i][1])
|
||||
}
|
||||
return b
|
||||
type Series struct {
|
||||
Status string `json:"status"`
|
||||
Data []map[string]string `json:"data"`
|
||||
}
|
||||
type Query struct {
|
||||
Status string `json:"status"`
|
||||
Data QueryData `json:"data"`
|
||||
}
|
||||
type QueryData struct {
|
||||
ResultType string `json:"resultType"`
|
||||
Result []QueryDataResult `json:"result"`
|
||||
}
|
||||
|
||||
type QueryDataResult struct {
|
||||
Metric map[string]string `json:"metric"`
|
||||
Value []interface{} `json:"value"`
|
||||
}
|
||||
|
||||
func (r *QueryDataResult) UnmarshalJSON(b []byte) error {
|
||||
type plain QueryDataResult
|
||||
return json.Unmarshal(testutil.PopulateTimeTpl(b, insertionTime), (*plain)(r))
|
||||
}
|
||||
|
||||
type QueryRange struct {
|
||||
Status string `json:"status"`
|
||||
Data QueryRangeData `json:"data"`
|
||||
}
|
||||
type QueryRangeData struct {
|
||||
ResultType string `json:"resultType"`
|
||||
Result []QueryRangeDataResult `json:"result"`
|
||||
}
|
||||
|
||||
type QueryRangeDataResult struct {
|
||||
Metric map[string]string `json:"metric"`
|
||||
Values [][]interface{} `json:"values"`
|
||||
}
|
||||
|
||||
func (r *QueryRangeDataResult) UnmarshalJSON(b []byte) error {
|
||||
type plain QueryRangeDataResult
|
||||
return json.Unmarshal(testutil.PopulateTimeTpl(b, insertionTime), (*plain)(r))
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@@ -179,10 +200,10 @@ func TestWriteRead(t *testing.T) {
|
||||
|
||||
func testWrite(t *testing.T) {
|
||||
t.Run("prometheus", func(t *testing.T) {
|
||||
for _, test := range readIn("prometheus", t, fmt.Sprintf("%d", timeToMillis(insertionTime))) {
|
||||
for _, test := range readIn("prometheus", t, insertionTime) {
|
||||
s := newSuite(t)
|
||||
r := testutil.WriteRequest{}
|
||||
s.noError(json.Unmarshal([]byte(test.Data), &r.Timeseries))
|
||||
s.noError(json.Unmarshal([]byte(strings.Join(test.Data, "\n")), &r.Timeseries))
|
||||
data, err := testutil.Compress(r)
|
||||
s.greaterThan(len(r.Timeseries), 0)
|
||||
if err != nil {
|
||||
@@ -194,39 +215,39 @@ func testWrite(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("influxdb", func(t *testing.T) {
|
||||
for _, x := range readIn("influxdb", t, fmt.Sprintf("%d", insertionTime.UnixNano())) {
|
||||
for _, x := range readIn("influxdb", t, insertionTime) {
|
||||
test := x
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
httpWrite(t, testWriteHTTPPath, bytes.NewBufferString(test.Data))
|
||||
httpWrite(t, testWriteHTTPPath, bytes.NewBufferString(strings.Join(test.Data, "\n")))
|
||||
})
|
||||
}
|
||||
})
|
||||
t.Run("graphite", func(t *testing.T) {
|
||||
for _, x := range readIn("graphite", t, fmt.Sprintf("%d", insertionTime.Unix())) {
|
||||
for _, x := range readIn("graphite", t, insertionTime) {
|
||||
test := x
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
tcpWrite(t, "127.0.0.1"+testStatsDListenAddr, test.Data)
|
||||
tcpWrite(t, "127.0.0.1"+testStatsDListenAddr, strings.Join(test.Data, "\n"))
|
||||
})
|
||||
}
|
||||
})
|
||||
t.Run("opentsdb", func(t *testing.T) {
|
||||
for _, x := range readIn("opentsdb", t, fmt.Sprintf("%d", insertionTime.Unix())) {
|
||||
for _, x := range readIn("opentsdb", t, insertionTime) {
|
||||
test := x
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
tcpWrite(t, "127.0.0.1"+testOpenTSDBListenAddr, test.Data)
|
||||
tcpWrite(t, "127.0.0.1"+testOpenTSDBListenAddr, strings.Join(test.Data, "\n"))
|
||||
})
|
||||
}
|
||||
})
|
||||
t.Run("opentsdbhttp", func(t *testing.T) {
|
||||
for _, x := range readIn("opentsdbhttp", t, fmt.Sprintf("%d", insertionTime.Unix())) {
|
||||
for _, x := range readIn("opentsdbhttp", t, insertionTime) {
|
||||
test := x
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
logger.Infof("writing %s", test.Data)
|
||||
httpWrite(t, testOpenTSDBWriteHTTPPath, bytes.NewBufferString(test.Data))
|
||||
httpWrite(t, testOpenTSDBWriteHTTPPath, bytes.NewBufferString(strings.Join(test.Data, "\n")))
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -235,18 +256,49 @@ func testWrite(t *testing.T) {
|
||||
func testRead(t *testing.T) {
|
||||
for _, engine := range []string{"prometheus", "graphite", "opentsdb", "influxdb", "opentsdbhttp"} {
|
||||
t.Run(engine, func(t *testing.T) {
|
||||
for _, x := range readIn(engine, t, fmt.Sprintf("%d", insertionTime.UnixNano())) {
|
||||
for _, x := range readIn(engine, t, insertionTime) {
|
||||
test := x
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
rowContains(t, httpRead(t, testReadHTTPPath, test.Query), test.Result)
|
||||
for _, q := range test.Query {
|
||||
q = testutil.PopulateTimeTplString(q, insertionTime)
|
||||
if test.Issue != "" {
|
||||
test.Issue = "Regression in " + test.Issue
|
||||
}
|
||||
switch true {
|
||||
case strings.HasPrefix(q, "/api/v1/export"):
|
||||
if err := checkMetricsResult(httpReadMetrics(t, testReadHTTPPath, q), test.ResultMetrics); err != nil {
|
||||
t.Fatalf("Export. %s fails with error %s.%s", q, err, test.Issue)
|
||||
}
|
||||
case strings.HasPrefix(q, "/api/v1/series"):
|
||||
s := Series{}
|
||||
httpReadStruct(t, testReadHTTPPath, q, &s)
|
||||
if err := checkSeriesResult(s, test.ResultSeries); err != nil {
|
||||
t.Fatalf("Series. %s fails with error %s.%s", q, err, test.Issue)
|
||||
}
|
||||
case strings.HasPrefix(q, "/api/v1/query_range"):
|
||||
queryResult := QueryRange{}
|
||||
httpReadStruct(t, testReadHTTPPath, q, &queryResult)
|
||||
if err := checkQueryRangeResult(queryResult, test.ResultQueryRange); err != nil {
|
||||
t.Fatalf("Query Range. %s fails with error %s.%s", q, err, test.Issue)
|
||||
}
|
||||
case strings.HasPrefix(q, "/api/v1/query"):
|
||||
queryResult := Query{}
|
||||
httpReadStruct(t, testReadHTTPPath, q, &queryResult)
|
||||
if err := checkQueryResult(queryResult, test.ResultQuery); err != nil {
|
||||
t.Fatalf("Query. %s fails with error %s.%s", q, err, test.Issue)
|
||||
}
|
||||
default:
|
||||
t.Fatalf("unsupported read query %s", q)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func readIn(readFor string, t *testing.T, timeStr string) []test {
|
||||
func readIn(readFor string, t *testing.T, insertTime time.Time) []test {
|
||||
t.Helper()
|
||||
s := newSuite(t)
|
||||
var tt []test
|
||||
@@ -258,7 +310,9 @@ func readIn(readFor string, t *testing.T, timeStr string) []test {
|
||||
s.noError(err)
|
||||
item := test{}
|
||||
s.noError(json.Unmarshal(b, &item))
|
||||
item.Data = strings.Replace(item.Data, tplWordTime, timeStr, -1)
|
||||
for i := range item.Data {
|
||||
item.Data[i] = testutil.PopulateTimeTplString(item.Data[i], insertTime)
|
||||
}
|
||||
tt = append(tt, item)
|
||||
return nil
|
||||
}))
|
||||
@@ -288,33 +342,42 @@ func tcpWrite(t *testing.T, address string, data string) {
|
||||
s.equalInt(n, len(data))
|
||||
}
|
||||
|
||||
func httpRead(t *testing.T, address, query string) []Row {
|
||||
func httpReadMetrics(t *testing.T, address, query string) []Metric {
|
||||
t.Helper()
|
||||
s := newSuite(t)
|
||||
resp, err := http.Get(address + query)
|
||||
s.noError(err)
|
||||
defer resp.Body.Close()
|
||||
s.equalInt(resp.StatusCode, 200)
|
||||
var rows []Row
|
||||
var rows []Metric
|
||||
for dec := json.NewDecoder(resp.Body); dec.More(); {
|
||||
var row Row
|
||||
var row Metric
|
||||
s.noError(dec.Decode(&row))
|
||||
rows = append(rows, row)
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
func rowContains(t *testing.T, rows, contains []Row) {
|
||||
func httpReadStruct(t *testing.T, address, query string, dst interface{}) {
|
||||
t.Helper()
|
||||
for _, r := range rows {
|
||||
contains = removeIfFound(r, contains)
|
||||
}
|
||||
if len(contains) > 0 {
|
||||
t.Fatalf("result rows %+v not found in %+v", contains, rows)
|
||||
}
|
||||
s := newSuite(t)
|
||||
resp, err := http.Get(address + query)
|
||||
s.noError(err)
|
||||
defer resp.Body.Close()
|
||||
s.equalInt(resp.StatusCode, 200)
|
||||
s.noError(json.NewDecoder(resp.Body).Decode(dst))
|
||||
}
|
||||
|
||||
func removeIfFound(r Row, contains []Row) []Row {
|
||||
func checkMetricsResult(got, want []Metric) error {
|
||||
for _, r := range append([]Metric(nil), got...) {
|
||||
want = removeIfFoundMetrics(r, want)
|
||||
}
|
||||
if len(want) > 0 {
|
||||
return fmt.Errorf("exptected metrics %+v not found in %+v", want, got)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeIfFoundMetrics(r Metric, contains []Metric) []Metric {
|
||||
for i, item := range contains {
|
||||
if reflect.DeepEqual(r.Metric, item.Metric) && reflect.DeepEqual(r.Values, item.Values) &&
|
||||
reflect.DeepEqual(r.Timestamps, item.Timestamps) {
|
||||
@@ -325,6 +388,84 @@ func removeIfFound(r Row, contains []Row) []Row {
|
||||
return contains
|
||||
}
|
||||
|
||||
func checkSeriesResult(got, want Series) error {
|
||||
if got.Status != want.Status {
|
||||
return fmt.Errorf("status mismatch %q - %q", want.Status, got.Status)
|
||||
}
|
||||
wantData := append([]map[string]string(nil), want.Data...)
|
||||
for _, r := range got.Data {
|
||||
wantData = removeIfFoundSeries(r, wantData)
|
||||
}
|
||||
if len(wantData) > 0 {
|
||||
return fmt.Errorf("expected seria(s) %+v not found in %+v", wantData, got.Data)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeIfFoundSeries(r map[string]string, contains []map[string]string) []map[string]string {
|
||||
for i, item := range contains {
|
||||
if reflect.DeepEqual(r, item) {
|
||||
contains[i] = contains[len(contains)-1]
|
||||
return contains[:len(contains)-1]
|
||||
}
|
||||
}
|
||||
return contains
|
||||
}
|
||||
|
||||
func checkQueryResult(got, want Query) error {
|
||||
if got.Status != want.Status {
|
||||
return fmt.Errorf("status mismatch %q - %q", want.Status, got.Status)
|
||||
}
|
||||
if got.Data.ResultType != want.Data.ResultType {
|
||||
return fmt.Errorf("result type mismatch %q - %q", want.Data.ResultType, got.Data.ResultType)
|
||||
}
|
||||
wantData := append([]QueryDataResult(nil), want.Data.Result...)
|
||||
for _, r := range got.Data.Result {
|
||||
wantData = removeIfFoundQueryData(r, wantData)
|
||||
}
|
||||
if len(wantData) > 0 {
|
||||
return fmt.Errorf("expected query result %+v not found in %+v", wantData, got.Data.Result)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeIfFoundQueryData(r QueryDataResult, contains []QueryDataResult) []QueryDataResult {
|
||||
for i, item := range contains {
|
||||
if reflect.DeepEqual(r.Metric, item.Metric) && reflect.DeepEqual(r.Value[0], item.Value[0]) && reflect.DeepEqual(r.Value[1], item.Value[1]) {
|
||||
contains[i] = contains[len(contains)-1]
|
||||
return contains[:len(contains)-1]
|
||||
}
|
||||
}
|
||||
return contains
|
||||
}
|
||||
|
||||
func checkQueryRangeResult(got, want QueryRange) error {
|
||||
if got.Status != want.Status {
|
||||
return fmt.Errorf("status mismatch %q - %q", want.Status, got.Status)
|
||||
}
|
||||
if got.Data.ResultType != want.Data.ResultType {
|
||||
return fmt.Errorf("result type mismatch %q - %q", want.Data.ResultType, got.Data.ResultType)
|
||||
}
|
||||
wantData := append([]QueryRangeDataResult(nil), want.Data.Result...)
|
||||
for _, r := range got.Data.Result {
|
||||
wantData = removeIfFoundQueryRangeData(r, wantData)
|
||||
}
|
||||
if len(wantData) > 0 {
|
||||
return fmt.Errorf("expected query range result %+v not found in %+v", wantData, got.Data.Result)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeIfFoundQueryRangeData(r QueryRangeDataResult, contains []QueryRangeDataResult) []QueryRangeDataResult {
|
||||
for i, item := range contains {
|
||||
if reflect.DeepEqual(r.Metric, item.Metric) && reflect.DeepEqual(r.Values, item.Values) {
|
||||
contains[i] = contains[len(contains)-1]
|
||||
return contains[:len(contains)-1]
|
||||
}
|
||||
}
|
||||
return contains
|
||||
}
|
||||
|
||||
type suite struct{ t *testing.T }
|
||||
|
||||
func newSuite(t *testing.T) *suite { return &suite{t: t} }
|
||||
@@ -352,7 +493,3 @@ func (s *suite) greaterThan(a, b int) {
|
||||
s.t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func timeToMillis(t time.Time) int64 {
|
||||
return t.UnixNano() / 1e6
|
||||
}
|
||||
|
||||
52
app/victoria-metrics/test/parser.go
Normal file
52
app/victoria-metrics/test/parser.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
parseTimeExpRegex = regexp.MustCompile(`"?{TIME[^}]*}"?`)
|
||||
extractRegex = regexp.MustCompile(`"?{([^}]*)}"?`)
|
||||
)
|
||||
|
||||
// PopulateTimeTplString substitutes {TIME_*} with t in s and returns the result.
|
||||
func PopulateTimeTplString(s string, t time.Time) string {
|
||||
return string(PopulateTimeTpl([]byte(s), t))
|
||||
}
|
||||
|
||||
// PopulateTimeTpl substitutes {TIME_*} with tGlobal in b and returns the result.
|
||||
func PopulateTimeTpl(b []byte, tGlobal time.Time) []byte {
|
||||
return parseTimeExpRegex.ReplaceAllFunc(b, func(repl []byte) []byte {
|
||||
t := tGlobal
|
||||
repl = extractRegex.FindSubmatch(repl)[1]
|
||||
parts := strings.SplitN(string(repl), "-", 2)
|
||||
if len(parts) == 2 {
|
||||
duration, err := time.ParseDuration(strings.TrimSpace(parts[1]))
|
||||
if err != nil {
|
||||
log.Fatalf("error %s parsing duration %s in %s", err, parts[1], repl)
|
||||
}
|
||||
t = t.Add(-duration)
|
||||
}
|
||||
switch strings.TrimSpace(parts[0]) {
|
||||
case `TIME_S`:
|
||||
return []byte(fmt.Sprintf("%d", t.Unix()))
|
||||
case `TIME_MSZ`:
|
||||
return []byte(fmt.Sprintf("%d", t.Unix()*1e3))
|
||||
case `TIME_MS`:
|
||||
return []byte(fmt.Sprintf("%d", timeToMillis(t)))
|
||||
case `TIME_NS`:
|
||||
return []byte(fmt.Sprintf("%d", t.UnixNano()))
|
||||
default:
|
||||
log.Fatalf("unkown time pattern %s in %s", parts[0], repl)
|
||||
}
|
||||
return repl
|
||||
})
|
||||
}
|
||||
|
||||
func timeToMillis(t time.Time) int64 {
|
||||
return t.UnixNano() / 1e6
|
||||
}
|
||||
24
app/victoria-metrics/test/parser_test.go
Normal file
24
app/victoria-metrics/test/parser_test.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestPopulateTimeTplString(t *testing.T) {
|
||||
now, err := time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error when parsing time: %s", err)
|
||||
}
|
||||
f := func(s, resultExpected string) {
|
||||
t.Helper()
|
||||
result := PopulateTimeTplString(s, now)
|
||||
if result != resultExpected {
|
||||
t.Fatalf("unexpected result; got %q; want %q", result, resultExpected)
|
||||
}
|
||||
}
|
||||
f("", "")
|
||||
f("{TIME_S}", "1136214245")
|
||||
f("now: {TIME_S}, past 30s: {TIME_MS-30s}, now: {TIME_S}", "now: 1136214245, past 30s: 1136214215000, now: 1136214245")
|
||||
f("now: {TIME_MS}, past 30m: {TIME_MSZ-30m}, past 2h: {TIME_NS-2h}", "now: 1136214245000, past 30m: 1136212445000, past 2h: 1136207045000000000")
|
||||
}
|
||||
@@ -5,7 +5,6 @@ package test
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"log"
|
||||
"math"
|
||||
"math/bits"
|
||||
)
|
||||
@@ -124,7 +123,6 @@ func (m *Sample) MarshalTo(dAtA []byte) (int, error) {
|
||||
func (m *Sample) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
if m.Timestamp != 0 {
|
||||
log.Printf("prom types %d", m.Timestamp)
|
||||
i = encodeVarintTypes(dAtA, i, uint64(m.Timestamp))
|
||||
i--
|
||||
dAtA[i] = 0x10
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "basic_insertion",
|
||||
"data": "graphite.foo.bar.baz;tag1=value1;tag2=value2 123 {TIME}",
|
||||
"query": "/api/v1/export?match={__name__!=\"\"}",
|
||||
"result": [
|
||||
{"metric":{"__name__":"graphite.foo.bar.baz","tag1":"value1","tag2":"value2"},"values":[123], "timestamps": ["{TIME_S}"]}
|
||||
"data": ["graphite.foo.bar.baz;tag1=value1;tag2=value2 123 {TIME_S}"],
|
||||
"query": ["/api/v1/export?match={__name__!=''}"],
|
||||
"result_metrics": [
|
||||
{"metric":{"__name__":"graphite.foo.bar.baz","tag1":"value1","tag2":"value2"},"values":[123], "timestamps": ["{TIME_MSZ}"]}
|
||||
]
|
||||
}
|
||||
|
||||
16
app/victoria-metrics/testdata/graphite/comparison-not-inf-not-nan.json
vendored
Normal file
16
app/victoria-metrics/testdata/graphite/comparison-not-inf-not-nan.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "comparison-not-inf-not-nan",
|
||||
"issue": "https://github.com/VictoriaMetrics/VictoriaMetrics/issues/150",
|
||||
"data": [
|
||||
"not_nan_not_inf;item=x 1 {TIME_S-1m}",
|
||||
"not_nan_not_inf;item=x 1 {TIME_S-2m}",
|
||||
"not_nan_not_inf;item=y 3 {TIME_S-1m}",
|
||||
"not_nan_not_inf;item=y 1 {TIME_S-2m}"],
|
||||
"query": ["/api/v1/query_range?query=1/(not_nan_not_inf-1)!=inf!=nan&start={TIME_S-3m}&end={TIME_S}&step=60"],
|
||||
"result_query_range": {
|
||||
"status":"success",
|
||||
"data":{"resultType":"matrix",
|
||||
"result":[
|
||||
{"metric":{"item":"y"},"values":[["{TIME_S-1m}","0.5"],["{TIME_S}","0.5"]]}
|
||||
]}}
|
||||
}
|
||||
24
app/victoria-metrics/testdata/graphite/max_lookback_set.json
vendored
Normal file
24
app/victoria-metrics/testdata/graphite/max_lookback_set.json
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "max_lookback_set",
|
||||
"issue": "https://github.com/VictoriaMetrics/VictoriaMetrics/issues/209",
|
||||
"data": [
|
||||
"max_lookback_set 1 {TIME_S-30s}",
|
||||
"max_lookback_set 2 {TIME_S-60s}",
|
||||
"max_lookback_set 3 {TIME_S-120s}",
|
||||
"max_lookback_set 4 {TIME_S-150s}"
|
||||
],
|
||||
"query": ["/api/v1/query_range?query=max_lookback_set&start={TIME_S-150s}&end={TIME_S}&step=10s&max_lookback=1s"],
|
||||
"result_query_range": {
|
||||
"status":"success",
|
||||
"data":{"resultType":"matrix",
|
||||
"result":[{"metric":{"__name__":"max_lookback_set"},"values":[
|
||||
["{TIME_S-150s}","4"],
|
||||
["{TIME_S-140s}","4"],
|
||||
["{TIME_S-120s}","3"],
|
||||
["{TIME_S-110s}","3"],
|
||||
["{TIME_S-60s}","2"],
|
||||
["{TIME_S-50s}","2"],
|
||||
["{TIME_S-30s}","1"],
|
||||
["{TIME_S-20s}","1"]
|
||||
]}]}}
|
||||
}
|
||||
32
app/victoria-metrics/testdata/graphite/max_lookback_unset.json
vendored
Normal file
32
app/victoria-metrics/testdata/graphite/max_lookback_unset.json
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "max_lookback_unset",
|
||||
"issue": "https://github.com/VictoriaMetrics/VictoriaMetrics/issues/209",
|
||||
"data": [
|
||||
"max_lookback_unset 1 {TIME_S-30s}",
|
||||
"max_lookback_unset 2 {TIME_S-60s}",
|
||||
"max_lookback_unset 3 {TIME_S-120s}",
|
||||
"max_lookback_unset 4 {TIME_S-150s}"
|
||||
],
|
||||
"query": ["/api/v1/query_range?query=max_lookback_unset&start={TIME_S-150s}&end={TIME_S}&step=10s"],
|
||||
"result_query_range": {
|
||||
"status":"success",
|
||||
"data":{"resultType":"matrix",
|
||||
"result":[{"metric":{"__name__":"max_lookback_unset"},"values":[
|
||||
["{TIME_S-150s}","4"],
|
||||
["{TIME_S-140s}","4"],
|
||||
["{TIME_S-130s}","4"],
|
||||
["{TIME_S-120s}","3"],
|
||||
["{TIME_S-110s}","3"],
|
||||
["{TIME_S-100s}","3"],
|
||||
["{TIME_S-90s}","3"],
|
||||
["{TIME_S-80s}","3"],
|
||||
["{TIME_S-70s}","3"],
|
||||
["{TIME_S-60s}","2"],
|
||||
["{TIME_S-50s}","2"],
|
||||
["{TIME_S-40s}","2"],
|
||||
["{TIME_S-30s}","1"],
|
||||
["{TIME_S-20s}","1"],
|
||||
["{TIME_S-10s}","1"],
|
||||
["{TIME_S}","1"]
|
||||
]}]}}
|
||||
}
|
||||
18
app/victoria-metrics/testdata/graphite/not-nan-as-missing-data.json
vendored
Normal file
18
app/victoria-metrics/testdata/graphite/not-nan-as-missing-data.json
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "not-nan-as-missing-data",
|
||||
"issue": "https://github.com/VictoriaMetrics/VictoriaMetrics/issues/153",
|
||||
"data": [
|
||||
"not_nan_as_missing_data;item=x 2 {TIME_S-2m}",
|
||||
"not_nan_as_missing_data;item=x 1 {TIME_S-1m}",
|
||||
"not_nan_as_missing_data;item=y 4 {TIME_S-2m}",
|
||||
"not_nan_as_missing_data;item=y 3 {TIME_S-1m}"
|
||||
],
|
||||
"query": ["/api/v1/query_range?query=not_nan_as_missing_data>1&start={TIME_S-2m}&end={TIME_S}&step=60"],
|
||||
"result_query_range": {
|
||||
"status":"success",
|
||||
"data":{"resultType":"matrix",
|
||||
"result":[
|
||||
{"metric":{"__name__":"not_nan_as_missing_data","item":"x"},"values":[["{TIME_S-2m}","2"]]},
|
||||
{"metric":{"__name__":"not_nan_as_missing_data","item":"y"},"values":[["{TIME_S-2m}","4"],["{TIME_S-1m}","3"],["{TIME_S}","3"]]}
|
||||
]}}
|
||||
}
|
||||
14
app/victoria-metrics/testdata/graphite/subquery-aggregation.json
vendored
Normal file
14
app/victoria-metrics/testdata/graphite/subquery-aggregation.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "subquery-aggregation",
|
||||
"issue": "https://github.com/VictoriaMetrics/VictoriaMetrics/issues/184",
|
||||
"data": [
|
||||
"forms_daily_count;item=x 1 {TIME_S-1m}",
|
||||
"forms_daily_count;item=x 2 {TIME_S-2m}",
|
||||
"forms_daily_count;item=y 3 {TIME_S-1m}",
|
||||
"forms_daily_count;item=y 4 {TIME_S-2m}"],
|
||||
"query": ["/api/v1/query?query=min%20by%20(item)%20(min_over_time(forms_daily_count[10m:1m]))&time={TIME_S-1m}"],
|
||||
"result_query": {
|
||||
"status":"success",
|
||||
"data":{"resultType":"vector","result":[{"metric":{"item":"x"},"value":["{TIME_S-1m}","1"]},{"metric":{"item":"y"},"value":["{TIME_S-1m}","3"]}]}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "basic_insertion",
|
||||
"data": "measurement,tag1=value1,tag2=value2 field1=1.23,field2=123 {TIME}",
|
||||
"query": "/api/v1/export?match={__name__!=\"\"}",
|
||||
"result": [
|
||||
"data": ["measurement,tag1=value1,tag2=value2 field1=1.23,field2=123 {TIME_NS}"],
|
||||
"query": ["/api/v1/export?match={__name__!=''}"],
|
||||
"result_metrics": [
|
||||
{"metric":{"__name__":"measurement_field2","tag1":"value1","tag2":"value2"},"values":[123], "timestamps": ["{TIME_MS}"]},
|
||||
{"metric":{"__name__":"measurement_field1","tag1":"value1","tag2":"value2"},"values":[1.23], "timestamps": ["{TIME_MS}"]}
|
||||
]
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "basic_insertion",
|
||||
"data": "put openstdb.foo.bar.baz {TIME} 123 tag1=value1 tag2=value2",
|
||||
"query": "/api/v1/export?match={__name__!=\"\"}",
|
||||
"result": [
|
||||
{"metric":{"__name__":"openstdb.foo.bar.baz","tag1":"value1","tag2":"value2"},"values":[123], "timestamps": ["{TIME_S}"]}
|
||||
"data": ["put openstdb.foo.bar.baz {TIME_S} 123 tag1=value1 tag2=value2"],
|
||||
"query": ["/api/v1/export?match={__name__!=''}"],
|
||||
"result_metrics": [
|
||||
{"metric":{"__name__":"openstdb.foo.bar.baz","tag1":"value1","tag2":"value2"},"values":[123], "timestamps": ["{TIME_MSZ}"]}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "basic_insertion",
|
||||
"data": "{\"metric\": \"opentsdbhttp.foo\", \"value\": 1001, \"timestamp\": {TIME}, \"tags\": {\"bar\":\"baz\", \"x\": \"y\"}}",
|
||||
"query": "/api/v1/export?match={__name__!=\"\"}",
|
||||
"result": [
|
||||
{"metric":{"__name__":"opentsdbhttp.foo","bar":"baz","x":"y"},"values":[1001], "timestamps": ["{TIME_S}"]}
|
||||
"data": ["{\"metric\": \"opentsdbhttp.foo\", \"value\": 1001, \"timestamp\": {TIME_S}, \"tags\": {\"bar\":\"baz\", \"x\": \"y\"}}"],
|
||||
"query": ["/api/v1/export?match={__name__!=''}"],
|
||||
"result_metrics": [
|
||||
{"metric":{"__name__":"opentsdbhttp.foo","bar":"baz","x":"y"},"values":[1001], "timestamps": ["{TIME_MSZ}"]}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "multiline",
|
||||
"data": "[{\"metric\": \"opentsdbhttp.multiline1\", \"value\": 1001, \"timestamp\": \"{TIME}\", \"tags\": {\"bar\":\"baz\", \"x\": \"y\"}}, {\"metric\": \"opentsdbhttp.multiline2\", \"value\": 1002, \"timestamp\": {TIME}}]",
|
||||
"query": "/api/v1/export?match={__name__!=\"\"}",
|
||||
"result": [
|
||||
{"metric":{"__name__":"opentsdbhttp.multiline1","bar":"baz","x":"y"},"values":[1001], "timestamps": ["{TIME_S}"]},
|
||||
{"metric":{"__name__":"opentsdbhttp.multiline2"},"values":[1002], "timestamps": ["{TIME_S}"]}
|
||||
"data": ["[{\"metric\": \"opentsdbhttp.multiline1\", \"value\": 1001, \"timestamp\": \"{TIME_S}\", \"tags\": {\"bar\":\"baz\", \"x\": \"y\"}}, {\"metric\": \"opentsdbhttp.multiline2\", \"value\": 1002, \"timestamp\": {TIME_S}}]"],
|
||||
"query": ["/api/v1/export?match={__name__!=''}"],
|
||||
"result_metrics": [
|
||||
{"metric":{"__name__":"opentsdbhttp.multiline1","bar":"baz","x":"y"},"values":[1001], "timestamps": ["{TIME_MSZ}"]},
|
||||
{"metric":{"__name__":"opentsdbhttp.multiline2"},"values":[1002], "timestamps": ["{TIME_MSZ}"]}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "basic_insertion",
|
||||
"data": "[{\"labels\":[{\"name\":\"__name__\",\"value\":\"prometheus.bar\"},{\"name\":\"baz\",\"value\":\"qux\"}],\"samples\":[{\"value\":100000,\"timestamp\":{TIME}}]}]",
|
||||
"query": "/api/v1/export?match={__name__!=\"\"}",
|
||||
"result": [
|
||||
"data": ["[{\"labels\":[{\"name\":\"__name__\",\"value\":\"prometheus.bar\"},{\"name\":\"baz\",\"value\":\"qux\"}],\"samples\":[{\"value\":100000,\"timestamp\":\"{TIME_MS}\"}]}]"],
|
||||
"query": ["/api/v1/export?match={__name__!=''}"],
|
||||
"result_metrics": [
|
||||
{"metric":{"__name__":"prometheus.bar","baz":"qux"},"values":[100000], "timestamps": ["{TIME_MS}"]}
|
||||
]
|
||||
}
|
||||
|
||||
10
app/victoria-metrics/testdata/prometheus/case-sensitive-regex.json
vendored
Normal file
10
app/victoria-metrics/testdata/prometheus/case-sensitive-regex.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "case-sensitive-regex",
|
||||
"issue": "https://github.com/VictoriaMetrics/VictoriaMetrics/issues/161",
|
||||
"data": ["[{\"labels\":[{\"name\":\"__name__\",\"value\":\"prometheus.sensitiveRegex\"},{\"name\":\"label\",\"value\":\"sensitiveRegex\"}],\"samples\":[{\"value\":2,\"timestamp\":\"{TIME_MS}\"}]},{\"labels\":[{\"name\":\"__name__\",\"value\":\"prometheus.sensitiveRegex\"},{\"name\":\"label\",\"value\":\"SensitiveRegex\"}],\"samples\":[{\"value\":1,\"timestamp\":\"{TIME_MS}\"}]}]"],
|
||||
"query": ["/api/v1/export?match={label=~'(?i)sensitiveregex'}"],
|
||||
"result_metrics": [
|
||||
{"metric":{"__name__":"prometheus.sensitiveRegex","label":"sensitiveRegex"},"values":[2], "timestamps": ["{TIME_MS}"]},
|
||||
{"metric":{"__name__":"prometheus.sensitiveRegex","label":"SensitiveRegex"},"values":[1], "timestamps": ["{TIME_MS}"]}
|
||||
]
|
||||
}
|
||||
9
app/victoria-metrics/testdata/prometheus/duplicate-label.json
vendored
Normal file
9
app/victoria-metrics/testdata/prometheus/duplicate-label.json
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "duplicate_label",
|
||||
"issue": "https://github.com/VictoriaMetrics/VictoriaMetrics/issues/172",
|
||||
"data": ["[{\"labels\":[{\"name\":\"__name__\",\"value\":\"prometheus.duplicate_label\"},{\"name\":\"duplicate\",\"value\":\"label\"},{\"name\":\"duplicate\",\"value\":\"label\"}],\"samples\":[{\"value\":1,\"timestamp\":\"{TIME_MS}\"}]}]"],
|
||||
"query": ["/api/v1/export?match={__name__!=''}"],
|
||||
"result_metrics": [
|
||||
{"metric":{"__name__":"prometheus.duplicate_label","duplicate":"label"},"values":[1], "timestamps": ["{TIME_MS}"]}
|
||||
]
|
||||
}
|
||||
15
app/victoria-metrics/testdata/prometheus/match-series.json
vendored
Normal file
15
app/victoria-metrics/testdata/prometheus/match-series.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "match_series",
|
||||
"issue": "https://github.com/VictoriaMetrics/VictoriaMetrics/issues/155",
|
||||
"data": ["[{\"labels\":[{\"name\":\"__name__\",\"value\":\"MatchSeries\"},{\"name\":\"db\",\"value\":\"TenMinute\"},{\"name\":\"TurbineType\",\"value\":\"V112\"},{\"name\":\"Park\",\"value\":\"1\"}],\"samples\":[{\"value\":1,\"timestamp\":\"{TIME_MS}\"}]},{\"labels\":[{\"name\":\"__name__\",\"value\":\"MatchSeries\"},{\"name\":\"db\",\"value\":\"TenMinute\"},{\"name\":\"TurbineType\",\"value\":\"V112\"},{\"name\":\"Park\",\"value\":\"2\"}],\"samples\":[{\"value\":1,\"timestamp\":\"{TIME_MS}\"}]},{\"labels\":[{\"name\":\"__name__\",\"value\":\"MatchSeries\"},{\"name\":\"db\",\"value\":\"TenMinute\"},{\"name\":\"TurbineType\",\"value\":\"V112\"},{\"name\":\"Park\",\"value\":\"3\"}],\"samples\":[{\"value\":1,\"timestamp\":\"{TIME_MS}\"}]},{\"labels\":[{\"name\":\"__name__\",\"value\":\"MatchSeries\"},{\"name\":\"db\",\"value\":\"TenMinute\"},{\"name\":\"TurbineType\",\"value\":\"V112\"},{\"name\":\"Park\",\"value\":\"4\"}],\"samples\":[{\"value\":1,\"timestamp\":\"{TIME_MS}\"}]}]"],
|
||||
"query": ["/api/v1/series?match[]={__name__='MatchSeries'}", "/api/v1/series?match[]={__name__=~'MatchSeries.*'}"],
|
||||
"result_series": {
|
||||
"status": "success",
|
||||
"data": [
|
||||
{"__name__":"MatchSeries","db":"TenMinute","Park":"1","TurbineType":"V112"},
|
||||
{"__name__":"MatchSeries","db":"TenMinute","Park":"2","TurbineType":"V112"},
|
||||
{"__name__":"MatchSeries","db":"TenMinute","Park":"3","TurbineType":"V112"},
|
||||
{"__name__":"MatchSeries","db":"TenMinute","Park":"4","TurbineType":"V112"}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -85,6 +85,15 @@ func TestRowsUnmarshalSuccess(t *testing.T) {
|
||||
}},
|
||||
})
|
||||
|
||||
// Timestamp bigger than 1<<31
|
||||
f("aaa 1123 429496729600", &Rows{
|
||||
Rows: []Row{{
|
||||
Metric: "aaa",
|
||||
Value: 1123,
|
||||
Timestamp: 429496729600,
|
||||
}},
|
||||
})
|
||||
|
||||
// Tags
|
||||
f("foo;bar=baz 1 2", &Rows{
|
||||
Rows: []Row{{
|
||||
|
||||
@@ -38,7 +38,7 @@ func Serve(addr string, maxReqSize int64) {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
logger.Fatalf("FATAL: error serving HTTP OpenTSDB: %s", err)
|
||||
logger.Fatalf("error serving HTTP OpenTSDB: %s", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -65,6 +65,6 @@ func Stop() {
|
||||
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)
|
||||
logger.Fatalf("cannot close HTTP OpenTSDB server: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,6 @@ import (
|
||||
"os"
|
||||
)
|
||||
|
||||
func mustFadviseRandomRead(f *os.File) {
|
||||
func mustFadviseSequentialRead(f *os.File) {
|
||||
// Do nothing :)
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func mustFadviseRandomRead(f *os.File) {
|
||||
func mustFadviseSequentialRead(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)
|
||||
if err := unix.Fadvise(int(fd), 0, 0, unix.FADV_SEQUENTIAL|unix.FADV_WILLNEED); err != nil {
|
||||
logger.Panicf("FATAL: error returned from unix.Fadvise(SEQUENTIAL|WILLNEED): %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func mustFadviseRandomRead(f *os.File) {
|
||||
func mustFadviseSequentialRead(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)
|
||||
if err := unix.Fadvise(int(fd), 0, 0, unix.FADV_SEQUENTIAL|unix.FADV_WILLNEED); err != nil {
|
||||
logger.Panicf("FATAL: error returned from unix.Fadvise(SEQUENTIAL|WILLNEED): %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,9 +484,12 @@ func ProcessSearchQuery(sq *storage.SearchQuery, fetchData bool, deadline Deadli
|
||||
tbf := getTmpBlocksFile()
|
||||
m := make(map[string][]tmpBlockAddr)
|
||||
blocksRead := 0
|
||||
bb := tmpBufPool.Get()
|
||||
defer tmpBufPool.Put(bb)
|
||||
for sr.NextMetricBlock() {
|
||||
blocksRead++
|
||||
addr, err := tbf.WriteBlock(sr.MetricBlock.Block)
|
||||
bb.B = storage.MarshalBlock(bb.B[:0], sr.MetricBlock.Block)
|
||||
addr, err := tbf.WriteBlockData(bb.B)
|
||||
if err != nil {
|
||||
putTmpBlocksFile(tbf)
|
||||
return nil, fmt.Errorf("cannot write data block #%d to temporary blocks file: %s", blocksRead, err)
|
||||
@@ -520,6 +523,15 @@ func ProcessSearchQuery(sq *storage.SearchQuery, fetchData bool, deadline Deadli
|
||||
pts.metricName = metricName
|
||||
pts.addrs = addrs
|
||||
}
|
||||
|
||||
// Sort rss.packedTimeseries by the first addr offset in order
|
||||
// to reduce the number of disk seeks during unpacking in RunParallel.
|
||||
// In this case tmpBlocksFile must be read almost sequentially.
|
||||
sort.Slice(rss.packedTimeseries, func(i, j int) bool {
|
||||
pts := rss.packedTimeseries
|
||||
return pts[i].addrs[0].offset < pts[j].addrs[0].offset
|
||||
})
|
||||
|
||||
return &rss, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -82,22 +82,18 @@ func (addr tmpBlockAddr) String() string {
|
||||
|
||||
var tmpBlocksFilesCreated = metrics.NewCounter(`vm_tmp_blocks_files_created_total`)
|
||||
|
||||
// WriteBlock writes b to tbf.
|
||||
// WriteBlockData writes b to tbf.
|
||||
//
|
||||
// It returns errors since the operation may fail on space shortage
|
||||
// and this must be handled.
|
||||
func (tbf *tmpBlocksFile) WriteBlock(b *storage.Block) (tmpBlockAddr, error) {
|
||||
bb := tmpBufPool.Get()
|
||||
defer tmpBufPool.Put(bb)
|
||||
bb.B = storage.MarshalBlock(bb.B[:0], b)
|
||||
|
||||
func (tbf *tmpBlocksFile) WriteBlockData(b []byte) (tmpBlockAddr, error) {
|
||||
var addr tmpBlockAddr
|
||||
addr.offset = tbf.offset
|
||||
addr.size = len(bb.B)
|
||||
addr.size = len(b)
|
||||
tbf.offset += uint64(addr.size)
|
||||
if len(tbf.buf)+len(bb.B) <= cap(tbf.buf) {
|
||||
if len(tbf.buf)+len(b) <= cap(tbf.buf) {
|
||||
// Fast path - the data fits tbf.buf
|
||||
tbf.buf = append(tbf.buf, bb.B...)
|
||||
tbf.buf = append(tbf.buf, b...)
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
@@ -111,7 +107,7 @@ func (tbf *tmpBlocksFile) WriteBlock(b *storage.Block) (tmpBlockAddr, error) {
|
||||
tmpBlocksFilesCreated.Inc()
|
||||
}
|
||||
_, err := tbf.f.Write(tbf.buf)
|
||||
tbf.buf = append(tbf.buf[:0], bb.B...)
|
||||
tbf.buf = append(tbf.buf[:0], b...)
|
||||
if err != nil {
|
||||
return addr, fmt.Errorf("cannot write block to %q: %s", tbf.f.Name(), err)
|
||||
}
|
||||
@@ -129,7 +125,10 @@ func (tbf *tmpBlocksFile) Finalize() error {
|
||||
if _, err := tbf.f.Seek(0, 0); err != nil {
|
||||
logger.Panicf("FATAL: cannot seek to the start of file: %s", err)
|
||||
}
|
||||
mustFadviseRandomRead(tbf.f)
|
||||
// Hint the OS that the file is read almost sequentiallly.
|
||||
// This should reduce the number of disk seeks, which is important
|
||||
// for HDDs.
|
||||
mustFadviseSequentialRead(tbf.f)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -77,9 +77,12 @@ func testTmpBlocksFile() error {
|
||||
// Write blocks until their summary size exceeds `size`.
|
||||
var addrs []tmpBlockAddr
|
||||
var blocks []*storage.Block
|
||||
bb := tmpBufPool.Get()
|
||||
defer tmpBufPool.Put(bb)
|
||||
for tbf.offset < uint64(size) {
|
||||
b := createBlock()
|
||||
addr, err := tbf.WriteBlock(b)
|
||||
bb.B = storage.MarshalBlock(bb.B[:0], b)
|
||||
addr, err := tbf.WriteBlockData(bb.B)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot write block at offset %d: %s", tbf.offset, err)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
{% for i, ts := range rs.Timestamps %}
|
||||
{%z= bb.B %}{% space %}
|
||||
{%f= rs.Values[i] %}{% space %}
|
||||
{%d= int(ts) %}{% newline %}
|
||||
{%dl= ts %}{% newline %}
|
||||
{% endfor %}
|
||||
{% code quicktemplate.ReleaseByteBuffer(bb) %}
|
||||
{% endfunc %}
|
||||
@@ -35,10 +35,10 @@
|
||||
"timestamps":[
|
||||
{% if len(rs.Timestamps) > 0 %}
|
||||
{% code timestamps := rs.Timestamps %}
|
||||
{%d= int(timestamps[0]) %}
|
||||
{%dl= timestamps[0] %}
|
||||
{% code timestamps = timestamps[1:] %}
|
||||
{% for _, ts := range timestamps %}
|
||||
,{%d= int(ts) %}
|
||||
,{%dl= ts %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
]
|
||||
|
||||
@@ -49,7 +49,7 @@ func StreamExportPrometheusLine(qw422016 *qt422016.Writer, rs *netstorage.Result
|
||||
//line app/vmselect/prometheus/export.qtpl:15
|
||||
qw422016.N().S(` `)
|
||||
//line app/vmselect/prometheus/export.qtpl:16
|
||||
qw422016.N().D(int(ts))
|
||||
qw422016.N().DL(ts)
|
||||
//line app/vmselect/prometheus/export.qtpl:16
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
@@ -129,7 +129,7 @@ func StreamExportJSONLine(qw422016 *qt422016.Writer, rs *netstorage.Result) {
|
||||
timestamps := rs.Timestamps
|
||||
|
||||
//line app/vmselect/prometheus/export.qtpl:38
|
||||
qw422016.N().D(int(timestamps[0]))
|
||||
qw422016.N().DL(timestamps[0])
|
||||
//line app/vmselect/prometheus/export.qtpl:39
|
||||
timestamps = timestamps[1:]
|
||||
|
||||
@@ -138,7 +138,7 @@ func StreamExportJSONLine(qw422016 *qt422016.Writer, rs *netstorage.Result) {
|
||||
//line app/vmselect/prometheus/export.qtpl:40
|
||||
qw422016.N().S(`,`)
|
||||
//line app/vmselect/prometheus/export.qtpl:41
|
||||
qw422016.N().D(int(ts))
|
||||
qw422016.N().DL(ts)
|
||||
//line app/vmselect/prometheus/export.qtpl:42
|
||||
}
|
||||
//line app/vmselect/prometheus/export.qtpl:43
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
{% if len(rs.Timestamps) == 0 || len(rs.Values) == 0 %}{% return %}{% endif %}
|
||||
{%= prometheusMetricName(&rs.MetricName) %}{% space %}
|
||||
{%f= rs.Values[len(rs.Values)-1] %}{% space %}
|
||||
{%d= int(rs.Timestamps[len(rs.Timestamps)-1]) %}{% newline %}
|
||||
{%dl= rs.Timestamps[len(rs.Timestamps)-1] %}{% newline %}
|
||||
{% endfunc %}
|
||||
|
||||
{% endstripspace %}
|
||||
|
||||
@@ -41,7 +41,7 @@ func StreamFederate(qw422016 *qt422016.Writer, rs *netstorage.Result) {
|
||||
//line app/vmselect/prometheus/federate.qtpl:12
|
||||
qw422016.N().S(` `)
|
||||
//line app/vmselect/prometheus/federate.qtpl:13
|
||||
qw422016.N().D(int(rs.Timestamps[len(rs.Timestamps)-1]))
|
||||
qw422016.N().DL(rs.Timestamps[len(rs.Timestamps)-1])
|
||||
//line app/vmselect/prometheus/federate.qtpl:13
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
|
||||
@@ -21,17 +21,17 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
latencyOffset = flag.Duration("search.latencyOffset", time.Second*60, "The time when data points become visible in query results after the colection. "+
|
||||
"Too small value can result in incomplete last points for query results")
|
||||
maxQueryDuration = flag.Duration("search.maxQueryDuration", time.Second*30, "The maximum time for search query execution")
|
||||
maxQueryLen = flag.Int("search.maxQueryLen", 16*1024, "The maximum search query length in bytes")
|
||||
maxLookback = flag.Duration("search.maxLookback", 0, "Synonim to `-search.lookback-delta` from Prometheus. "+
|
||||
"The value is dynamically detected from interval between time series datapoints if not set. It can be overriden on per-query basis via `max_lookback` arg")
|
||||
)
|
||||
|
||||
// Default step used if not set.
|
||||
const defaultStep = 5 * 60 * 1000
|
||||
|
||||
// Latency for data processing pipeline, i.e. the time between data is ignested
|
||||
// into the system and the time it becomes visible to search.
|
||||
const latencyOffset = 60 * 1000
|
||||
|
||||
// FederateHandler implements /federate . See https://prometheus.io/docs/prometheus/latest/federation/
|
||||
func FederateHandler(w http.ResponseWriter, r *http.Request) error {
|
||||
startTime := time.Now()
|
||||
@@ -43,11 +43,14 @@ func FederateHandler(w http.ResponseWriter, r *http.Request) error {
|
||||
if len(matches) == 0 {
|
||||
return fmt.Errorf("missing `match[]` arg")
|
||||
}
|
||||
maxLookback, err := getDuration(r, "max_lookback", defaultStep)
|
||||
lookbackDelta, err := getMaxLookback(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
start, err := getTime(r, "start", ct-maxLookback)
|
||||
if lookbackDelta <= 0 {
|
||||
lookbackDelta = defaultStep
|
||||
}
|
||||
start, err := getTime(r, "start", ct-lookbackDelta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -463,17 +466,22 @@ func QueryHandler(w http.ResponseWriter, r *http.Request) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
step, err := getDuration(r, "step", latencyOffset)
|
||||
queryOffset := getLatencyOffsetMilliseconds()
|
||||
step, err := getDuration(r, "step", queryOffset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deadline := getDeadline(r)
|
||||
lookbackDelta, err := getMaxLookback(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(query) > *maxQueryLen {
|
||||
return fmt.Errorf(`too long query; got %d bytes; mustn't exceed %d bytes`, len(query), *maxQueryLen)
|
||||
}
|
||||
if ct-start < latencyOffset {
|
||||
start -= latencyOffset
|
||||
if ct-start < queryOffset {
|
||||
start -= queryOffset
|
||||
}
|
||||
if childQuery, windowStr, offsetStr := promql.IsMetricSelectorWithRollup(query); childQuery != "" {
|
||||
var window int64
|
||||
@@ -503,10 +511,11 @@ func QueryHandler(w http.ResponseWriter, r *http.Request) error {
|
||||
}
|
||||
|
||||
ec := promql.EvalConfig{
|
||||
Start: start,
|
||||
End: start,
|
||||
Step: step,
|
||||
Deadline: deadline,
|
||||
Start: start,
|
||||
End: start,
|
||||
Step: step,
|
||||
Deadline: deadline,
|
||||
LookbackDelta: lookbackDelta,
|
||||
}
|
||||
result, err := promql.Exec(&ec, query, true)
|
||||
if err != nil {
|
||||
@@ -546,6 +555,10 @@ func QueryRangeHandler(w http.ResponseWriter, r *http.Request) error {
|
||||
}
|
||||
deadline := getDeadline(r)
|
||||
mayCache := !getBool(r, "nocache")
|
||||
lookbackDelta, err := getMaxLookback(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate input args.
|
||||
if len(query) > *maxQueryLen {
|
||||
@@ -562,17 +575,19 @@ func QueryRangeHandler(w http.ResponseWriter, r *http.Request) error {
|
||||
}
|
||||
|
||||
ec := promql.EvalConfig{
|
||||
Start: start,
|
||||
End: end,
|
||||
Step: step,
|
||||
Deadline: deadline,
|
||||
MayCache: mayCache,
|
||||
Start: start,
|
||||
End: end,
|
||||
Step: step,
|
||||
Deadline: deadline,
|
||||
MayCache: mayCache,
|
||||
LookbackDelta: lookbackDelta,
|
||||
}
|
||||
result, err := promql.Exec(&ec, query, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot execute %q: %s", query, err)
|
||||
}
|
||||
if ct-end < latencyOffset {
|
||||
queryOffset := getLatencyOffsetMilliseconds()
|
||||
if ct-end < queryOffset {
|
||||
result = adjustLastPoints(result)
|
||||
}
|
||||
|
||||
@@ -726,6 +741,11 @@ func getDuration(r *http.Request, argKey string, defaultValue int64) (int64, err
|
||||
|
||||
const maxDurationMsecs = 100 * 365 * 24 * 3600 * 1000
|
||||
|
||||
func getMaxLookback(r *http.Request) (int64, error) {
|
||||
d := int64(*maxLookback / time.Millisecond)
|
||||
return getDuration(r, "max_lookback", d)
|
||||
}
|
||||
|
||||
func getDeadline(r *http.Request) netstorage.Deadline {
|
||||
d, err := getDuration(r, "timeout", 0)
|
||||
if err != nil {
|
||||
@@ -764,3 +784,11 @@ func getTagFilterssFromMatches(matches []string) ([][]storage.TagFilter, error)
|
||||
}
|
||||
return tagFilterss, nil
|
||||
}
|
||||
|
||||
func getLatencyOffsetMilliseconds() int64 {
|
||||
d := int64(*latencyOffset / time.Millisecond)
|
||||
if d <= 1000 {
|
||||
d = 1000
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ SeriesCountResponse generates response for /api/v1/series/count .
|
||||
{% func SeriesCountResponse(n uint64) %}
|
||||
{
|
||||
"status":"success",
|
||||
"data":[{%d int(n) %}]
|
||||
"data":[{%dl int64(n) %}]
|
||||
}
|
||||
{% endfunc %}
|
||||
{% endstripspace %}
|
||||
|
||||
@@ -24,7 +24,7 @@ func StreamSeriesCountResponse(qw422016 *qt422016.Writer, n uint64) {
|
||||
//line app/vmselect/prometheus/series_count_response.qtpl:3
|
||||
qw422016.N().S(`{"status":"success","data":[`)
|
||||
//line app/vmselect/prometheus/series_count_response.qtpl:6
|
||||
qw422016.N().D(int(n))
|
||||
qw422016.N().DL(int64(n))
|
||||
//line app/vmselect/prometheus/series_count_response.qtpl:6
|
||||
qw422016.N().S(`]}`)
|
||||
//line app/vmselect/prometheus/series_count_response.qtpl:8
|
||||
|
||||
3
app/vmselect/promql/arch_386.go
Normal file
3
app/vmselect/promql/arch_386.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package promql
|
||||
|
||||
const maxByteSliceLen = 1<<31 - 1
|
||||
@@ -70,6 +70,9 @@ type EvalConfig struct {
|
||||
|
||||
MayCache bool
|
||||
|
||||
// LookbackDelta is analog to `-query.lookback-delta` from Prometheus.
|
||||
LookbackDelta int64
|
||||
|
||||
timestamps []int64
|
||||
timestampsOnce sync.Once
|
||||
}
|
||||
@@ -82,6 +85,7 @@ func newEvalConfig(src *EvalConfig) *EvalConfig {
|
||||
ec.Step = src.Step
|
||||
ec.Deadline = src.Deadline
|
||||
ec.MayCache = src.MayCache
|
||||
ec.LookbackDelta = src.LookbackDelta
|
||||
|
||||
// do not copy src.timestamps - they must be generated again.
|
||||
return &ec
|
||||
@@ -465,7 +469,7 @@ func evalRollupFuncWithSubquery(ec *EvalConfig, name string, rf rollupFunc, re *
|
||||
}
|
||||
|
||||
sharedTimestamps := getTimestamps(ec.Start, ec.End, ec.Step)
|
||||
preFunc, rcs := getRollupConfigs(name, rf, ec.Start, ec.End, ec.Step, window, sharedTimestamps)
|
||||
preFunc, rcs := getRollupConfigs(name, rf, ec.Start, ec.End, ec.Step, window, ec.LookbackDelta, sharedTimestamps)
|
||||
tss := make([]*timeseries, 0, len(tssSQ)*len(rcs))
|
||||
var tssLock sync.Mutex
|
||||
removeMetricGroup := !rollupFuncsKeepMetricGroup[name]
|
||||
@@ -586,7 +590,7 @@ func evalRollupFuncWithMetricExpr(ec *EvalConfig, name string, rf rollupFunc, me
|
||||
return tss, nil
|
||||
}
|
||||
sharedTimestamps := getTimestamps(start, ec.End, ec.Step)
|
||||
preFunc, rcs := getRollupConfigs(name, rf, start, ec.End, ec.Step, window, sharedTimestamps)
|
||||
preFunc, rcs := getRollupConfigs(name, rf, start, ec.End, ec.Step, window, ec.LookbackDelta, sharedTimestamps)
|
||||
|
||||
// Verify timeseries fit available memory after the rollup.
|
||||
// Take into account points from tssCached.
|
||||
@@ -689,7 +693,8 @@ func doRollupForTimeseries(rc *rollupConfig, tsDst *timeseries, mnSrc *storage.M
|
||||
tsDst.denyReuse = true
|
||||
}
|
||||
|
||||
func getRollupConfigs(name string, rf rollupFunc, start, end, step, window int64, sharedTimestamps []int64) (func(values []float64, timestamps []int64), []*rollupConfig) {
|
||||
func getRollupConfigs(name string, rf rollupFunc, start, end, step, window int64, lookbackDelta int64, sharedTimestamps []int64) (
|
||||
func(values []float64, timestamps []int64), []*rollupConfig) {
|
||||
preFunc := func(values []float64, timestamps []int64) {}
|
||||
if rollupFuncsRemoveCounterResets[name] {
|
||||
preFunc = func(values []float64, timestamps []int64) {
|
||||
@@ -705,6 +710,7 @@ func getRollupConfigs(name string, rf rollupFunc, start, end, step, window int64
|
||||
Step: step,
|
||||
Window: window,
|
||||
MayAdjustWindow: rollupFuncsMayAdjustWindow[name],
|
||||
LookbackDelta: lookbackDelta,
|
||||
Timestamps: sharedTimestamps,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,11 +194,14 @@ type parseCacheValue struct {
|
||||
}
|
||||
|
||||
type parseCache struct {
|
||||
m map[string]*parseCacheValue
|
||||
mu sync.RWMutex
|
||||
// Move atomic counters to the top of struct for 8-byte alignment on 32-bit arch.
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/212
|
||||
|
||||
requests uint64
|
||||
misses uint64
|
||||
|
||||
m map[string]*parseCacheValue
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func (pc *parseCache) Requests() uint64 {
|
||||
|
||||
@@ -369,6 +369,17 @@ func TestExecSuccess(t *testing.T) {
|
||||
resultExpected := []netstorage.Result{r}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run("timestamp(time()>=1600)", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `timestamp(time()>=1600)`
|
||||
r := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{nan, nan, nan, 1600, 1800, 2000},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
resultExpected := []netstorage.Result{r}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run("time()/100", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `time()/100`
|
||||
@@ -2671,6 +2682,28 @@ func TestExecSuccess(t *testing.T) {
|
||||
resultExpected := []netstorage.Result{r}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`increases_over_time`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `increases_over_time(rand(0)[200s:10s])`
|
||||
r := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{11, 9, 9, 12, 9, 8},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
resultExpected := []netstorage.Result{r}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`decreases_over_time`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `decreases_over_time(rand(0)[200s:10s])`
|
||||
r := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{9, 11, 11, 8, 11, 12},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
resultExpected := []netstorage.Result{r}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`limitk(-1)`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `limitk(-1, label_set(10, "foo", "bar") or label_set(time()/150, "baz", "sss"))`
|
||||
@@ -3523,7 +3556,7 @@ func TestExecSuccess(t *testing.T) {
|
||||
}}
|
||||
r4 := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{0.85, 0.94, 0.97, 0.93, 0.98, 0.92},
|
||||
Values: []float64{0.9, 0.94, 0.97, 0.93, 0.98, 0.92},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r4.MetricName.Tags = []storage.Tag{{
|
||||
@@ -3571,7 +3604,7 @@ func TestExecSuccess(t *testing.T) {
|
||||
q := `sort(rollup(time()[:50s]))`
|
||||
r1 := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{850, 1050, 1250, 1450, 1650, 1850},
|
||||
Values: []float64{800, 1000, 1200, 1400, 1600, 1800},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r1.MetricName.Tags = []storage.Tag{{
|
||||
@@ -3677,6 +3710,17 @@ func TestExecSuccess(t *testing.T) {
|
||||
resultExpected := []netstorage.Result{r}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`lag()`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `lag(time()[60s:17s])`
|
||||
r := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{14, 10, 6, 2, 15, 11},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
resultExpected := []netstorage.Result{r}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`()`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `()`
|
||||
@@ -4142,6 +4186,8 @@ func TestExecError(t *testing.T) {
|
||||
f(`alias()`)
|
||||
f(`alias(1)`)
|
||||
f(`alias(1, "foo", "bar")`)
|
||||
f(`lifetime()`)
|
||||
f(`lag()`)
|
||||
|
||||
// Invalid argument type
|
||||
f(`median_over_time({}, 2)`)
|
||||
|
||||
@@ -51,11 +51,14 @@ type regexpCacheValue struct {
|
||||
}
|
||||
|
||||
type regexpCache struct {
|
||||
m map[string]*regexpCacheValue
|
||||
mu sync.RWMutex
|
||||
// Move atomic counters to the top of struct for 8-byte alignment on 32-bit arch.
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/212
|
||||
|
||||
requests uint64
|
||||
misses uint64
|
||||
|
||||
m map[string]*regexpCacheValue
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func (rc *regexpCache) Requests() uint64 {
|
||||
|
||||
@@ -38,21 +38,24 @@ var rollupFuncs = map[string]newRollupFunc{
|
||||
"stdvar_over_time": newRollupFuncOneArg(rollupStdvar),
|
||||
|
||||
// Additional rollup funcs.
|
||||
"sum2_over_time": newRollupFuncOneArg(rollupSum2),
|
||||
"geomean_over_time": newRollupFuncOneArg(rollupGeomean),
|
||||
"first_over_time": newRollupFuncOneArg(rollupFirst),
|
||||
"last_over_time": newRollupFuncOneArg(rollupLast),
|
||||
"distinct_over_time": newRollupFuncOneArg(rollupDistinct),
|
||||
"integrate": newRollupFuncOneArg(rollupIntegrate),
|
||||
"ideriv": newRollupFuncOneArg(rollupIderiv),
|
||||
"lifetime": newRollupFuncOneArg(rollupLifetime),
|
||||
"scrape_interval": newRollupFuncOneArg(rollupScrapeInterval),
|
||||
"rollup": newRollupFuncOneArg(rollupFake),
|
||||
"rollup_rate": newRollupFuncOneArg(rollupFake), // + rollupFuncsRemoveCounterResets
|
||||
"rollup_deriv": newRollupFuncOneArg(rollupFake),
|
||||
"rollup_delta": newRollupFuncOneArg(rollupFake),
|
||||
"rollup_increase": newRollupFuncOneArg(rollupFake), // + rollupFuncsRemoveCounterResets
|
||||
"rollup_candlestick": newRollupFuncOneArg(rollupFake),
|
||||
"sum2_over_time": newRollupFuncOneArg(rollupSum2),
|
||||
"geomean_over_time": newRollupFuncOneArg(rollupGeomean),
|
||||
"first_over_time": newRollupFuncOneArg(rollupFirst),
|
||||
"last_over_time": newRollupFuncOneArg(rollupLast),
|
||||
"distinct_over_time": newRollupFuncOneArg(rollupDistinct),
|
||||
"increases_over_time": newRollupFuncOneArg(rollupIncreases),
|
||||
"decreases_over_time": newRollupFuncOneArg(rollupDecreases),
|
||||
"integrate": newRollupFuncOneArg(rollupIntegrate),
|
||||
"ideriv": newRollupFuncOneArg(rollupIderiv),
|
||||
"lifetime": newRollupFuncOneArg(rollupLifetime),
|
||||
"lag": newRollupFuncOneArg(rollupLag),
|
||||
"scrape_interval": newRollupFuncOneArg(rollupScrapeInterval),
|
||||
"rollup": newRollupFuncOneArg(rollupFake),
|
||||
"rollup_rate": newRollupFuncOneArg(rollupFake), // + rollupFuncsRemoveCounterResets
|
||||
"rollup_deriv": newRollupFuncOneArg(rollupFake),
|
||||
"rollup_delta": newRollupFuncOneArg(rollupFake),
|
||||
"rollup_increase": newRollupFuncOneArg(rollupFake), // + rollupFuncsRemoveCounterResets
|
||||
"rollup_candlestick": newRollupFuncOneArg(rollupFake),
|
||||
}
|
||||
|
||||
var rollupFuncsMayAdjustWindow = map[string]bool{
|
||||
@@ -111,8 +114,9 @@ type rollupFuncArg struct {
|
||||
values []float64
|
||||
timestamps []int64
|
||||
|
||||
idx int
|
||||
step int64
|
||||
currTimestamp int64
|
||||
idx int
|
||||
step int64
|
||||
}
|
||||
|
||||
func (rfa *rollupFuncArg) reset() {
|
||||
@@ -120,6 +124,7 @@ func (rfa *rollupFuncArg) reset() {
|
||||
rfa.prevTimestamp = 0
|
||||
rfa.values = nil
|
||||
rfa.timestamps = nil
|
||||
rfa.currTimestamp = 0
|
||||
rfa.idx = 0
|
||||
rfa.step = 0
|
||||
}
|
||||
@@ -147,6 +152,9 @@ type rollupConfig struct {
|
||||
MayAdjustWindow bool
|
||||
|
||||
Timestamps []int64
|
||||
|
||||
// LoookbackDelta is the analog to `-query.lookback-delta` from Prometheus world.
|
||||
LookbackDelta int64
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -184,6 +192,9 @@ func (rc *rollupConfig) Do(dstValues []float64, values []float64, timestamps []i
|
||||
dstValues = decimal.ExtendFloat64sCapacity(dstValues, len(rc.Timestamps))
|
||||
|
||||
maxPrevInterval := getMaxPrevInterval(timestamps)
|
||||
if rc.LookbackDelta > 0 && maxPrevInterval > rc.LookbackDelta {
|
||||
maxPrevInterval = rc.LookbackDelta
|
||||
}
|
||||
window := rc.Window
|
||||
if window <= 0 {
|
||||
window = rc.Step
|
||||
@@ -218,6 +229,7 @@ func (rc *rollupConfig) Do(dstValues []float64, values []float64, timestamps []i
|
||||
|
||||
rfa.values = values[i:j]
|
||||
rfa.timestamps = timestamps[i:j]
|
||||
rfa.currTimestamp = tEnd
|
||||
value := rc.Func(rfa)
|
||||
rfa.idx++
|
||||
dstValues = append(dstValues, value)
|
||||
@@ -531,11 +543,14 @@ func rollupAvg(rfa *rollupFuncArg) float64 {
|
||||
func rollupMin(rfa *rollupFuncArg) float64 {
|
||||
// There is no need in handling NaNs here, since they must be cleaned up
|
||||
// before calling rollup funcs.
|
||||
minValue := rfa.prevValue
|
||||
values := rfa.values
|
||||
if len(values) == 0 {
|
||||
return rfa.prevValue
|
||||
if math.IsNaN(minValue) {
|
||||
if len(values) == 0 {
|
||||
return nan
|
||||
}
|
||||
minValue = values[0]
|
||||
}
|
||||
minValue := values[0]
|
||||
for _, v := range values {
|
||||
if v < minValue {
|
||||
minValue = v
|
||||
@@ -547,11 +562,14 @@ func rollupMin(rfa *rollupFuncArg) float64 {
|
||||
func rollupMax(rfa *rollupFuncArg) float64 {
|
||||
// There is no need in handling NaNs here, since they must be cleaned up
|
||||
// before calling rollup funcs.
|
||||
maxValue := rfa.prevValue
|
||||
values := rfa.values
|
||||
if len(values) == 0 {
|
||||
return rfa.prevValue
|
||||
if math.IsNaN(maxValue) {
|
||||
if len(values) == 0 {
|
||||
return nan
|
||||
}
|
||||
maxValue = values[0]
|
||||
}
|
||||
maxValue := values[0]
|
||||
for _, v := range values {
|
||||
if v > maxValue {
|
||||
maxValue = v
|
||||
@@ -565,7 +583,10 @@ func rollupSum(rfa *rollupFuncArg) float64 {
|
||||
// before calling rollup funcs.
|
||||
values := rfa.values
|
||||
if len(values) == 0 {
|
||||
return rfa.prevValue
|
||||
if math.IsNaN(rfa.prevValue) {
|
||||
return nan
|
||||
}
|
||||
return 0
|
||||
}
|
||||
var sum float64
|
||||
for _, v := range values {
|
||||
@@ -782,6 +803,18 @@ func rollupLifetime(rfa *rollupFuncArg) float64 {
|
||||
return float64(timestamps[len(timestamps)-1]-rfa.prevTimestamp) * 1e-3
|
||||
}
|
||||
|
||||
func rollupLag(rfa *rollupFuncArg) float64 {
|
||||
// Calculate the duration between the current timestamp and the last data point.
|
||||
timestamps := rfa.timestamps
|
||||
if len(timestamps) == 0 {
|
||||
if math.IsNaN(rfa.prevValue) {
|
||||
return nan
|
||||
}
|
||||
return float64(rfa.currTimestamp-rfa.prevTimestamp) * 1e-3
|
||||
}
|
||||
return float64(rfa.currTimestamp-timestamps[len(timestamps)-1]) * 1e-3
|
||||
}
|
||||
|
||||
func rollupScrapeInterval(rfa *rollupFuncArg) float64 {
|
||||
// Calculate the average interval between data points.
|
||||
timestamps := rfa.timestamps
|
||||
@@ -820,6 +853,37 @@ func rollupChanges(rfa *rollupFuncArg) float64 {
|
||||
return float64(n)
|
||||
}
|
||||
|
||||
func rollupIncreases(rfa *rollupFuncArg) float64 {
|
||||
// There is no need in handling NaNs here, since they must be cleaned up
|
||||
// before calling rollup funcs.
|
||||
values := rfa.values
|
||||
if len(values) == 0 {
|
||||
if math.IsNaN(rfa.prevValue) {
|
||||
return nan
|
||||
}
|
||||
return 0
|
||||
}
|
||||
prevValue := rfa.prevValue
|
||||
if math.IsNaN(prevValue) {
|
||||
prevValue = values[0]
|
||||
values = values[1:]
|
||||
}
|
||||
if len(values) == 0 {
|
||||
return 0
|
||||
}
|
||||
n := 0
|
||||
for _, v := range values {
|
||||
if v > prevValue {
|
||||
n++
|
||||
}
|
||||
prevValue = v
|
||||
}
|
||||
return float64(n)
|
||||
}
|
||||
|
||||
// `decreases_over_time` logic is the same as `resets` logic.
|
||||
var rollupDecreases = rollupResets
|
||||
|
||||
func rollupResets(rfa *rollupFuncArg) float64 {
|
||||
// There is no need in handling NaNs here, since they must be cleaned up
|
||||
// before calling rollup funcs.
|
||||
|
||||
@@ -294,6 +294,8 @@ func TestRollupNewRollupFuncSuccess(t *testing.T) {
|
||||
f("integrate", 61.0275)
|
||||
f("distinct_over_time", 8)
|
||||
f("ideriv", 0)
|
||||
f("decreases_over_time", 5)
|
||||
f("increases_over_time", 5)
|
||||
}
|
||||
|
||||
func TestRollupNewRollupFuncError(t *testing.T) {
|
||||
@@ -486,6 +488,51 @@ func TestRollupWindowPartialPoints(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestRollupFuncsLookbackDelta(t *testing.T) {
|
||||
t.Run("1", func(t *testing.T) {
|
||||
rc := rollupConfig{
|
||||
Func: rollupFirst,
|
||||
Start: 80,
|
||||
End: 140,
|
||||
Step: 10,
|
||||
LookbackDelta: 1,
|
||||
}
|
||||
rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step)
|
||||
values := rc.Do(nil, testValues, testTimestamps)
|
||||
valuesExpected := []float64{99, 12, 44, nan, 32, 34, nan}
|
||||
timestampsExpected := []int64{80, 90, 100, 110, 120, 130, 140}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
t.Run("7", func(t *testing.T) {
|
||||
rc := rollupConfig{
|
||||
Func: rollupFirst,
|
||||
Start: 80,
|
||||
End: 140,
|
||||
Step: 10,
|
||||
LookbackDelta: 7,
|
||||
}
|
||||
rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step)
|
||||
values := rc.Do(nil, testValues, testTimestamps)
|
||||
valuesExpected := []float64{99, 12, 44, 44, 32, 34, nan}
|
||||
timestampsExpected := []int64{80, 90, 100, 110, 120, 130, 140}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
t.Run("0", func(t *testing.T) {
|
||||
rc := rollupConfig{
|
||||
Func: rollupFirst,
|
||||
Start: 80,
|
||||
End: 140,
|
||||
Step: 10,
|
||||
LookbackDelta: 0,
|
||||
}
|
||||
rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step)
|
||||
values := rc.Do(nil, testValues, testTimestamps)
|
||||
valuesExpected := []float64{34, 12, 12, 44, 44, 34, nan}
|
||||
timestampsExpected := []int64{80, 90, 100, 110, 120, 130, 140}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRollupFuncsNoWindow(t *testing.T) {
|
||||
t.Run("first", func(t *testing.T) {
|
||||
rc := rollupConfig{
|
||||
@@ -525,7 +572,7 @@ func TestRollupFuncsNoWindow(t *testing.T) {
|
||||
}
|
||||
rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step)
|
||||
values := rc.Do(nil, testValues, testTimestamps)
|
||||
valuesExpected := []float64{nan, 21, 12, 32, 34}
|
||||
valuesExpected := []float64{nan, 21, 12, 12, 34}
|
||||
timestampsExpected := []int64{0, 40, 80, 120, 160}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
@@ -585,6 +632,20 @@ func TestRollupFuncsNoWindow(t *testing.T) {
|
||||
timestampsExpected := []int64{10, 50, 90, 130}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
t.Run("lag", func(t *testing.T) {
|
||||
rc := rollupConfig{
|
||||
Func: rollupLag,
|
||||
Start: 0,
|
||||
End: 160,
|
||||
Step: 40,
|
||||
Window: 0,
|
||||
}
|
||||
rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step)
|
||||
values := rc.Do(nil, testValues, testTimestamps)
|
||||
valuesExpected := []float64{nan, 0.004, 0, 0, 0.03}
|
||||
timestampsExpected := []int64{0, 40, 80, 120, 160}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
t.Run("lifetime_1", func(t *testing.T) {
|
||||
rc := rollupConfig{
|
||||
Func: rollupLifetime,
|
||||
@@ -811,7 +872,7 @@ func testRowsEqual(t *testing.T, values []float64, timestamps []int64, valuesExp
|
||||
}
|
||||
continue
|
||||
}
|
||||
if v != vExpected {
|
||||
if math.Abs(v-vExpected) > 1e-15 {
|
||||
t.Fatalf("unexpected value at values[%d]; got %f; want %f\nvalues=\n%v\nvaluesExpected=\n%v",
|
||||
i, v, vExpected, values, valuesExpected)
|
||||
}
|
||||
|
||||
@@ -1121,7 +1121,10 @@ func transformTimestamp(tfa *transformFuncArg) ([]*timeseries, error) {
|
||||
ts.MetricName.ResetMetricGroup()
|
||||
values := ts.Values
|
||||
for i, t := range ts.Timestamps {
|
||||
values[i] = float64(t) / 1e3
|
||||
v := values[i]
|
||||
if !math.IsNaN(v) {
|
||||
values[i] = float64(t) / 1e3
|
||||
}
|
||||
}
|
||||
}
|
||||
return rvs, nil
|
||||
|
||||
@@ -24,6 +24,9 @@ var (
|
||||
|
||||
// DataPath is a path to storage data.
|
||||
DataPath = flag.String("storageDataPath", "victoria-metrics-data", "Path to storage data")
|
||||
|
||||
bigMergeConcurrency = flag.Int("bigMergeConcurrency", 0, "The maximum number of CPU cores to use for big merges. Default value is used if set to 0")
|
||||
smallMergeConcurrency = flag.Int("smallMergeConcurrency", 0, "The maximum number of CPU cores to use for small merges. Default value is used if set to 0")
|
||||
)
|
||||
|
||||
// Init initializes vmstorage.
|
||||
@@ -39,6 +42,10 @@ func InitWithoutMetrics() {
|
||||
if err := encoding.CheckPrecisionBits(uint8(*precisionBits)); err != nil {
|
||||
logger.Fatalf("invalid `-precisionBits`: %s", err)
|
||||
}
|
||||
|
||||
storage.SetBigMergeWorkersCount(*bigMergeConcurrency)
|
||||
storage.SetSmallMergeWorkersCount(*smallMergeConcurrency)
|
||||
|
||||
logger.Infof("opening storage at %q with retention period %d months", *DataPath, *retentionPeriod)
|
||||
startTime := time.Now()
|
||||
WG = syncwg.WaitGroup{}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"type": "grafana",
|
||||
"id": "grafana",
|
||||
"name": "Grafana",
|
||||
"version": "6.2.1"
|
||||
"version": "6.3.5"
|
||||
},
|
||||
{
|
||||
"type": "panel",
|
||||
@@ -60,12 +60,12 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": "Overview for single node VictoriaMetrics v1.22.2 or higher",
|
||||
"description": "Overview for single node VictoriaMetrics v1.28.0 or higher",
|
||||
"editable": true,
|
||||
"gnetId": 10229,
|
||||
"graphTooltip": 0,
|
||||
"id": null,
|
||||
"iteration": 1563651131627,
|
||||
"iteration": 1572208904768,
|
||||
"links": [
|
||||
{
|
||||
"icon": "doc",
|
||||
@@ -121,7 +121,6 @@
|
||||
{
|
||||
"targetBlank": true,
|
||||
"title": "VictoriaMetrics releases",
|
||||
"type": "absolute",
|
||||
"url": "https://github.com/VictoriaMetrics/VictoriaMetrics/releases"
|
||||
}
|
||||
],
|
||||
@@ -490,6 +489,7 @@
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"description": "* `*` - unsupported query path\n* `/write` - insert into VM\n* `/metrics` - query VM system metrics\n* `/query` - query instant values\n* `/query_range` - query over a range of time\n* `/series` - match a certain label set\n* `/label/{}/values` - query a list of label values (variables mostly)",
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
@@ -513,7 +513,9 @@
|
||||
"linewidth": 1,
|
||||
"links": [],
|
||||
"nullPointMode": "null as zero",
|
||||
"options": {},
|
||||
"options": {
|
||||
"dataLinks": []
|
||||
},
|
||||
"percentage": false,
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
@@ -580,6 +582,7 @@
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"description": "The less time it takes is better.\n* `*` - unsupported query path\n* `/write` - insert into VM\n* `/metrics` - query VM system metrics\n* `/query` - query instant values\n* `/query_range` - query over a range of time\n* `/series` - match a certain label set\n* `/label/{}/values` - query a list of label values (variables mostly)",
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
@@ -603,7 +606,9 @@
|
||||
"linewidth": 1,
|
||||
"links": [],
|
||||
"nullPointMode": "null as zero",
|
||||
"options": {},
|
||||
"options": {
|
||||
"dataLinks": []
|
||||
},
|
||||
"percentage": false,
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
@@ -670,6 +675,7 @@
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"description": "Shows the number of active time series with new data points inserted during the last hour. High value may result in ingestion slowdown. \n\nSee following link for details:",
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
@@ -692,12 +698,13 @@
|
||||
{
|
||||
"targetBlank": true,
|
||||
"title": "troubleshooting",
|
||||
"type": "absolute",
|
||||
"url": "https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/README.md#troubleshooting"
|
||||
}
|
||||
],
|
||||
"nullPointMode": "null",
|
||||
"options": {},
|
||||
"options": {
|
||||
"dataLinks": []
|
||||
},
|
||||
"percentage": false,
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
@@ -764,6 +771,7 @@
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"description": "VictoriaMetrics stores various caches in RAM. Memory size for these caches may be limited with -`memory.allowedPercent` flag. Line `max allowed` shows max allowed memory size for cache.",
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
@@ -784,7 +792,9 @@
|
||||
"linewidth": 1,
|
||||
"links": [],
|
||||
"nullPointMode": "null",
|
||||
"options": {},
|
||||
"options": {
|
||||
"dataLinks": []
|
||||
},
|
||||
"percentage": false,
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
@@ -865,6 +875,7 @@
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"description": "* `*` - unsupported query path\n* `/write` - insert into VM\n* `/metrics` - query VM system metrics\n* `/query` - query instant values\n* `/query_range` - query over a range of time\n* `/series` - match a certain label set\n* `/label/{}/values` - query a list of label values (variables mostly)",
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
@@ -888,7 +899,9 @@
|
||||
"linewidth": 1,
|
||||
"links": [],
|
||||
"nullPointMode": "null as zero",
|
||||
"options": {},
|
||||
"options": {
|
||||
"dataLinks": []
|
||||
},
|
||||
"percentage": false,
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
@@ -953,55 +966,74 @@
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"description": "",
|
||||
"description": "Shows how many ongoing insertions are taking place.\n* `max` - equal to number of CPU * 2\n* `current` - current number of goroutines busy with inserting rows into storage\n\nWhen `current` hits `max` constantly, it means storage is overloaded and require more CPU.",
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 27
|
||||
},
|
||||
"id": 49,
|
||||
"id": 59,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
"current": false,
|
||||
"alignAsTable": true,
|
||||
"avg": true,
|
||||
"current": true,
|
||||
"hideEmpty": false,
|
||||
"hideZero": false,
|
||||
"max": false,
|
||||
"min": false,
|
||||
"show": false,
|
||||
"show": true,
|
||||
"sort": "current",
|
||||
"sortDesc": true,
|
||||
"total": false,
|
||||
"values": false
|
||||
"values": true
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"links": [],
|
||||
"nullPointMode": "null",
|
||||
"options": {},
|
||||
"options": {
|
||||
"dataLinks": []
|
||||
},
|
||||
"percentage": false,
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"seriesOverrides": [
|
||||
{
|
||||
"alias": "max",
|
||||
"color": "#C4162A"
|
||||
}
|
||||
],
|
||||
"spaceLength": 10,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(rate(vm_tcplistener_accepts_total{job=\"$job\"}[$__interval]))",
|
||||
"expr": "sum(vm_concurrent_addrows_capacity{job=\"$job\"})",
|
||||
"format": "time_series",
|
||||
"hide": false,
|
||||
"intervalFactor": 1,
|
||||
"legendFormat": "connections",
|
||||
"legendFormat": "max",
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"expr": "sum(vm_concurrent_addrows_current{job=\"$job\"})",
|
||||
"format": "time_series",
|
||||
"intervalFactor": 1,
|
||||
"legendFormat": "current",
|
||||
"refId": "B"
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeRegions": [],
|
||||
"timeShift": null,
|
||||
"title": "TCP connections rate",
|
||||
"title": "Concurrent inserts",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"sort": 2,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
@@ -1014,7 +1046,7 @@
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"decimals": null,
|
||||
"decimals": 0,
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
@@ -1023,6 +1055,7 @@
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"decimals": 0,
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
@@ -1044,6 +1077,7 @@
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"description": "",
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
@@ -1064,7 +1098,9 @@
|
||||
"linewidth": 1,
|
||||
"links": [],
|
||||
"nullPointMode": "null",
|
||||
"options": {},
|
||||
"options": {
|
||||
"dataLinks": []
|
||||
},
|
||||
"percentage": false,
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
@@ -1125,6 +1161,98 @@
|
||||
"alignLevel": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"description": "",
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 35
|
||||
},
|
||||
"id": 49,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
"current": false,
|
||||
"max": false,
|
||||
"min": false,
|
||||
"show": false,
|
||||
"total": false,
|
||||
"values": false
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"links": [],
|
||||
"nullPointMode": "null",
|
||||
"options": {
|
||||
"dataLinks": []
|
||||
},
|
||||
"percentage": false,
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"spaceLength": 10,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(rate(vm_tcplistener_accepts_total{job=\"$job\"}[$__interval]))",
|
||||
"format": "time_series",
|
||||
"hide": false,
|
||||
"intervalFactor": 1,
|
||||
"legendFormat": "connections",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeRegions": [],
|
||||
"timeShift": null,
|
||||
"title": "TCP connections rate",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"buckets": null,
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"decimals": null,
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"yaxis": {
|
||||
"align": false,
|
||||
"alignLevel": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {
|
||||
@@ -1146,6 +1274,7 @@
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"description": "How many datapoints are inserted into storage per second",
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
@@ -1168,7 +1297,9 @@
|
||||
"linewidth": 1,
|
||||
"links": [],
|
||||
"nullPointMode": "null as zero",
|
||||
"options": {},
|
||||
"options": {
|
||||
"dataLinks": []
|
||||
},
|
||||
"percentage": false,
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
@@ -1242,6 +1373,7 @@
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"description": "How many datapoints are in RAM queue waiting to be written into storage. The less is better.",
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
@@ -1262,7 +1394,9 @@
|
||||
"linewidth": 1,
|
||||
"links": [],
|
||||
"nullPointMode": "null",
|
||||
"options": {},
|
||||
"options": {
|
||||
"dataLinks": []
|
||||
},
|
||||
"percentage": false,
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
@@ -1344,6 +1478,7 @@
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"description": "How many datapoints are in the storage.",
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
@@ -1364,7 +1499,9 @@
|
||||
"linewidth": 1,
|
||||
"links": [],
|
||||
"nullPointMode": "null",
|
||||
"options": {},
|
||||
"options": {
|
||||
"dataLinks": []
|
||||
},
|
||||
"percentage": false,
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
@@ -1431,6 +1568,7 @@
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"description": "Data parts of LSM tree.\nHigh number of parts could be an evidence of slow merge performance - check the resource utilization.\n* `indexdb` - inverted index\n* `storage/small` - recently added parts of data ingested into storage(hot data)\n* `storage/big` - small parts gradually merged into big parts (cold data)",
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
@@ -1451,7 +1589,9 @@
|
||||
"linewidth": 1,
|
||||
"links": [],
|
||||
"nullPointMode": "null",
|
||||
"options": {},
|
||||
"options": {
|
||||
"dataLinks": []
|
||||
},
|
||||
"percentage": false,
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
@@ -1518,6 +1658,7 @@
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"description": "Shows amount of on-disk space occupied by data points.",
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
@@ -1538,7 +1679,9 @@
|
||||
"linewidth": 1,
|
||||
"links": [],
|
||||
"nullPointMode": "null",
|
||||
"options": {},
|
||||
"options": {
|
||||
"dataLinks": []
|
||||
},
|
||||
"percentage": false,
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
@@ -1605,6 +1748,7 @@
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"description": "Shows amount of on-disk space occupied by inverted index.",
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
@@ -1625,7 +1769,9 @@
|
||||
"linewidth": 1,
|
||||
"links": [],
|
||||
"nullPointMode": "null",
|
||||
"options": {},
|
||||
"options": {
|
||||
"dataLinks": []
|
||||
},
|
||||
"percentage": false,
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
@@ -1683,13 +1829,105 @@
|
||||
"alignLevel": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"description": "Shows how many rows were ignored on insertion due to corrupted or out of retention timestamps.",
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 68
|
||||
},
|
||||
"id": 58,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
"current": false,
|
||||
"max": false,
|
||||
"min": false,
|
||||
"show": false,
|
||||
"total": false,
|
||||
"values": false
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"links": [],
|
||||
"nullPointMode": "null",
|
||||
"options": {
|
||||
"dataLinks": []
|
||||
},
|
||||
"percentage": false,
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"spaceLength": 10,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(vm_rows_ignored_total{job=\"$job\"}) by (reason) > 0",
|
||||
"format": "time_series",
|
||||
"hide": false,
|
||||
"intervalFactor": 1,
|
||||
"legendFormat": "{{reason}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeRegions": [],
|
||||
"timeShift": null,
|
||||
"title": "Rows ignored",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"buckets": null,
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"decimals": null,
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"yaxis": {
|
||||
"align": false,
|
||||
"alignLevel": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 68
|
||||
"y": 76
|
||||
},
|
||||
"id": 46,
|
||||
"panels": [],
|
||||
@@ -1704,11 +1942,12 @@
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"description": "",
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 69
|
||||
"y": 77
|
||||
},
|
||||
"id": 44,
|
||||
"legend": {
|
||||
@@ -1724,7 +1963,9 @@
|
||||
"linewidth": 1,
|
||||
"links": [],
|
||||
"nullPointMode": "null",
|
||||
"options": {},
|
||||
"options": {
|
||||
"dataLinks": []
|
||||
},
|
||||
"percentage": false,
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
@@ -1806,11 +2047,12 @@
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 69
|
||||
"y": 77
|
||||
},
|
||||
"id": 57,
|
||||
"legend": {
|
||||
@@ -1826,7 +2068,9 @@
|
||||
"linewidth": 1,
|
||||
"links": [],
|
||||
"nullPointMode": "null",
|
||||
"options": {},
|
||||
"options": {
|
||||
"dataLinks": []
|
||||
},
|
||||
"percentage": false,
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
@@ -1892,11 +2136,12 @@
|
||||
"dashes": false,
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 77
|
||||
"y": 85
|
||||
},
|
||||
"id": 47,
|
||||
"legend": {
|
||||
@@ -1912,7 +2157,9 @@
|
||||
"linewidth": 1,
|
||||
"links": [],
|
||||
"nullPointMode": "null",
|
||||
"options": {},
|
||||
"options": {
|
||||
"dataLinks": []
|
||||
},
|
||||
"percentage": false,
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
@@ -1979,11 +2226,12 @@
|
||||
"dashes": false,
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 77
|
||||
"y": 85
|
||||
},
|
||||
"id": 42,
|
||||
"legend": {
|
||||
@@ -1999,7 +2247,9 @@
|
||||
"linewidth": 1,
|
||||
"links": [],
|
||||
"nullPointMode": "null",
|
||||
"options": {},
|
||||
"options": {
|
||||
"dataLinks": []
|
||||
},
|
||||
"percentage": false,
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
@@ -2065,11 +2315,12 @@
|
||||
"dashes": false,
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 85
|
||||
"y": 93
|
||||
},
|
||||
"id": 48,
|
||||
"legend": {
|
||||
@@ -2085,7 +2336,9 @@
|
||||
"linewidth": 1,
|
||||
"links": [],
|
||||
"nullPointMode": "null",
|
||||
"options": {},
|
||||
"options": {
|
||||
"dataLinks": []
|
||||
},
|
||||
"percentage": false,
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
@@ -2147,7 +2400,7 @@
|
||||
}
|
||||
],
|
||||
"refresh": "30s",
|
||||
"schemaVersion": 18,
|
||||
"schemaVersion": 19,
|
||||
"style": "dark",
|
||||
"tags": [],
|
||||
"templating": {
|
||||
@@ -2230,5 +2483,5 @@
|
||||
"timezone": "",
|
||||
"title": "VictoriaMetrics",
|
||||
"uid": "wNf0q_kZk",
|
||||
"version": 2
|
||||
}
|
||||
"version": 3
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
DOCKER_NAMESPACE := victoriametrics
|
||||
BUILDER_IMAGE := local/builder:go1.13.0
|
||||
BUILDER_IMAGE := local/builder:go1.13.3
|
||||
CERTS_IMAGE := local/certs:1.0.2
|
||||
|
||||
package-certs:
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
FROM golang:1.13.0
|
||||
FROM golang:1.13.3
|
||||
STOPSIGNAL SIGINT
|
||||
|
||||
@@ -2,7 +2,7 @@ version: '3.5'
|
||||
services:
|
||||
prometheus:
|
||||
container_name: prometheus
|
||||
image: prom/prometheus:v2.10.0
|
||||
image: prom/prometheus:v2.12.0
|
||||
depends_on:
|
||||
- "victoriametrics"
|
||||
ports:
|
||||
@@ -35,7 +35,7 @@ services:
|
||||
restart: always
|
||||
grafana:
|
||||
container_name: grafana
|
||||
image: grafana/grafana:6.2.1
|
||||
image: grafana/grafana:6.3.5
|
||||
entrypoint: >
|
||||
/bin/sh -c "
|
||||
cd /var/lib/grafana &&
|
||||
|
||||
13
go.mod
13
go.mod
@@ -1,17 +1,18 @@
|
||||
module github.com/VictoriaMetrics/VictoriaMetrics
|
||||
|
||||
require (
|
||||
github.com/VictoriaMetrics/fastcache v1.5.1
|
||||
github.com/VictoriaMetrics/metrics v1.7.1
|
||||
github.com/VictoriaMetrics/fastcache v1.5.2
|
||||
github.com/VictoriaMetrics/metrics v1.7.2
|
||||
github.com/cespare/xxhash/v2 v2.1.0
|
||||
github.com/golang/snappy v0.0.1
|
||||
github.com/google/go-cmp v0.3.0 // indirect
|
||||
github.com/klauspost/compress v1.8.3
|
||||
github.com/klauspost/compress v1.9.1
|
||||
github.com/valyala/fastjson v1.4.1
|
||||
github.com/valyala/gozstd v1.6.1
|
||||
github.com/valyala/fastrand v1.0.0
|
||||
github.com/valyala/gozstd v1.6.2
|
||||
github.com/valyala/histogram v1.0.1
|
||||
github.com/valyala/quicktemplate v1.2.0
|
||||
golang.org/x/sys v0.0.0-20190913121621-c3b328c6e5a7
|
||||
github.com/valyala/quicktemplate v1.3.1
|
||||
golang.org/x/sys v0.0.0-20191027211539-f8518d3b3627
|
||||
)
|
||||
|
||||
go 1.12
|
||||
|
||||
24
go.sum
24
go.sum
@@ -1,10 +1,10 @@
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI=
|
||||
github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
|
||||
github.com/VictoriaMetrics/fastcache v1.5.1 h1:qHgHjyoNFV7jgucU8QZUuU4gcdhfs8QW1kw68OD2Lag=
|
||||
github.com/VictoriaMetrics/fastcache v1.5.1/go.mod h1:+jv9Ckb+za/P1ZRg/sulP5Ni1v49daAVERr0H3CuscE=
|
||||
github.com/VictoriaMetrics/metrics v1.7.1 h1:g2qrY6Upn8rvlvR40cGHFY0crwi4hpqF0n9vJMNsCSg=
|
||||
github.com/VictoriaMetrics/metrics v1.7.1/go.mod h1:LU2j9qq7xqZYXz8tF3/RQnB2z2MbZms5TDiIg9/NHiQ=
|
||||
github.com/VictoriaMetrics/fastcache v1.5.2 h1:Erd8iIuBAL9kke8JzM4+WxkKuFkHh3ktwLanJvDgR44=
|
||||
github.com/VictoriaMetrics/fastcache v1.5.2/go.mod h1:+jv9Ckb+za/P1ZRg/sulP5Ni1v49daAVERr0H3CuscE=
|
||||
github.com/VictoriaMetrics/metrics v1.7.2 h1:PzC0SEo5lbbNK7xaYwclCCdoaIGRmXOfflIMF3LpSW4=
|
||||
github.com/VictoriaMetrics/metrics v1.7.2/go.mod h1:LU2j9qq7xqZYXz8tF3/RQnB2z2MbZms5TDiIg9/NHiQ=
|
||||
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8=
|
||||
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
@@ -22,8 +22,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.8.3 h1:CkLseiEYMM/fRb0RIg9mXB+Iwgmle+U9KGFu+JCO4Ec=
|
||||
github.com/klauspost/compress v1.8.3/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.9.1 h1:TWy0o9J9c6LK9C8t7Msh6IAJNXbsU/nvKLTQUU5HdaY=
|
||||
github.com/klauspost/compress v1.9.1/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.1 h1:oFN2mNW0kOr1fEKJuLpDwakNb6Y9fElVEBZmPEsFTUw=
|
||||
github.com/valyala/gozstd v1.6.1/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ=
|
||||
github.com/valyala/gozstd v1.6.2 h1:MgBfNm0I8IKm51LUTTKfO9vi4BtmoH7kBXeUvgaiZVU=
|
||||
github.com/valyala/gozstd v1.6.2/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.2.0 h1:BaO1nHTkspYzmAjPXj0QiDJxai96tlcZyKcI9dyEGvM=
|
||||
github.com/valyala/quicktemplate v1.2.0/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4=
|
||||
github.com/valyala/quicktemplate v1.3.1 h1:V9Ixd/ONuoT6C1ipx8XR2dNGSDgIVnvT4ezZ38ZWllU=
|
||||
github.com/valyala/quicktemplate v1.3.1/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4=
|
||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
||||
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sys v0.0.0-20190913121621-c3b328c6e5a7 h1:wYqz/tQaWUgGKyx+B/rssSE6wkIKdY5Ee6ryOmzarIg=
|
||||
golang.org/x/sys v0.0.0-20190913121621-c3b328c6e5a7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191027211539-f8518d3b3627 h1:/FZUR3d/QsXe4AcJyJFCc40TOj3y6Hs23Y3YJlvVkWo=
|
||||
golang.org/x/sys v0.0.0-20191027211539-f8518d3b3627/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package bytesutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -20,3 +21,10 @@ func TestResize(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestToUnsafeString(t *testing.T) {
|
||||
s := "str"
|
||||
if !bytes.Equal([]byte("str"), ToUnsafeBytes(s)) {
|
||||
t.Fatalf(`[]bytes(%s) doesnt equal to %s `, s, s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package decimal
|
||||
import (
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fastnum"
|
||||
)
|
||||
|
||||
// CalibrateScale calibrates a and b with the corresponding exponents ae, be
|
||||
@@ -81,6 +83,13 @@ func ExtendInt64sCapacity(dst []int64, additionalItems int) []int64 {
|
||||
// AppendDecimalToFloat converts each item in va to f=v*10^e, appends it
|
||||
// to dst and returns the resulting dst.
|
||||
func AppendDecimalToFloat(dst []float64, va []int64, e int16) []float64 {
|
||||
if fastnum.IsInt64Zeros(va) {
|
||||
return fastnum.AppendFloat64Zeros(dst, len(va))
|
||||
}
|
||||
if e == 0 && fastnum.IsInt64Ones(va) {
|
||||
return fastnum.AppendFloat64Ones(dst, len(va))
|
||||
}
|
||||
|
||||
// Extend dst capacity in order to eliminate memory allocations below.
|
||||
dst = ExtendFloat64sCapacity(dst, len(va))
|
||||
|
||||
@@ -108,6 +117,14 @@ func AppendFloatToDecimal(dst []int64, src []float64) (va []int64, e int16) {
|
||||
if len(src) == 0 {
|
||||
return dst, 0
|
||||
}
|
||||
if fastnum.IsFloat64Zeros(src) {
|
||||
dst = fastnum.AppendInt64Zeros(dst, len(src))
|
||||
return dst, 0
|
||||
}
|
||||
if fastnum.IsFloat64Ones(src) {
|
||||
dst = fastnum.AppendInt64Ones(dst, len(src))
|
||||
return dst, 0
|
||||
}
|
||||
|
||||
// Extend dst capacity in order to eliminate memory allocations below.
|
||||
dst = ExtendInt64sCapacity(dst, len(src))
|
||||
@@ -265,61 +282,83 @@ var (
|
||||
// For instance, for f = -1.234 it returns v = -1234, e = -3.
|
||||
//
|
||||
// FromFloat doesn't work properly with NaN values, so don't pass them here.
|
||||
func FromFloat(f float64) (v int64, e int16) {
|
||||
if math.IsInf(f, 0) {
|
||||
// Special case for Inf
|
||||
if math.IsInf(f, 1) {
|
||||
return vInfPos, 0
|
||||
}
|
||||
return vInfNeg, 0
|
||||
}
|
||||
|
||||
minus := false
|
||||
if f < 0 {
|
||||
f = -f
|
||||
minus = true
|
||||
}
|
||||
func FromFloat(f float64) (int64, int16) {
|
||||
if f == 0 {
|
||||
// Special case for 0.0 and -0.0
|
||||
return 0, 0
|
||||
}
|
||||
v, e = positiveFloatToDecimal(f)
|
||||
if minus {
|
||||
v = -v
|
||||
if math.IsInf(f, 0) {
|
||||
return fromFloatInf(f)
|
||||
}
|
||||
if v == 0 {
|
||||
e = 0
|
||||
} else if v > vMax {
|
||||
v = vMax
|
||||
} else if v < vMin {
|
||||
if f > 0 {
|
||||
v, e := positiveFloatToDecimal(f)
|
||||
if v > vMax {
|
||||
v = vMax
|
||||
}
|
||||
return v, e
|
||||
}
|
||||
v, e := positiveFloatToDecimal(-f)
|
||||
v = -v
|
||||
if v < vMin {
|
||||
v = vMin
|
||||
}
|
||||
return v, e
|
||||
}
|
||||
|
||||
func fromFloatInf(f float64) (int64, int16) {
|
||||
// Special case for Inf
|
||||
if math.IsInf(f, 1) {
|
||||
return vInfPos, 0
|
||||
}
|
||||
return vInfNeg, 0
|
||||
}
|
||||
|
||||
func positiveFloatToDecimal(f float64) (int64, int16) {
|
||||
// There is no need in checking for f == 0, since it should be already checked by the caller.
|
||||
u := uint64(f)
|
||||
if float64(u) != f {
|
||||
return positiveFloatToDecimalSlow(f)
|
||||
}
|
||||
// Fast path for integers.
|
||||
if u < 1<<55 && u%10 != 0 {
|
||||
return int64(u), 0
|
||||
}
|
||||
return getDecimalAndScale(u)
|
||||
}
|
||||
|
||||
func getDecimalAndScale(u uint64) (int64, int16) {
|
||||
var scale int16
|
||||
v := int64(f)
|
||||
if f == float64(v) {
|
||||
// Fast path for integers.
|
||||
u := uint64(v)
|
||||
if u%10 != 0 {
|
||||
return v, 0
|
||||
}
|
||||
// Minimize v by converting trailing zeros to scale.
|
||||
for u >= 1<<55 {
|
||||
// Remove trailing garbage bits left after float64->uint64 conversion,
|
||||
// since float64 contains only 53 significant bits.
|
||||
// See https://en.wikipedia.org/wiki/Double-precision_floating-point_format
|
||||
u /= 10
|
||||
scale++
|
||||
for u != 0 && u%10 == 0 {
|
||||
u /= 10
|
||||
scale++
|
||||
}
|
||||
}
|
||||
if u%10 != 0 {
|
||||
return int64(u), scale
|
||||
}
|
||||
// Minimize v by converting trailing zeros to scale.
|
||||
u /= 10
|
||||
scale++
|
||||
for u != 0 && u%10 == 0 {
|
||||
u /= 10
|
||||
scale++
|
||||
}
|
||||
return int64(u), scale
|
||||
}
|
||||
|
||||
func positiveFloatToDecimalSlow(f float64) (int64, int16) {
|
||||
// Slow path for floating point numbers.
|
||||
var scale int16
|
||||
prec := conversionPrecision
|
||||
if f > 1e6 || f < 1e-6 {
|
||||
// Normalize f, so it is in the small range suitable
|
||||
// for the next loop.
|
||||
if f > 1e6 {
|
||||
// Increase conversion precision for big numbers.
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/213
|
||||
prec = 1e15
|
||||
}
|
||||
_, exp := math.Frexp(f)
|
||||
scale = int16(float64(exp) * math.Ln2 / math.Ln10)
|
||||
f *= math.Pow10(-int(scale))
|
||||
@@ -327,13 +366,13 @@ func positiveFloatToDecimal(f float64) (int64, int16) {
|
||||
|
||||
// Multiply f by 100 until the fractional part becomes
|
||||
// too small comparing to integer part.
|
||||
for f < conversionPrecision {
|
||||
for f < prec {
|
||||
x, frac := math.Modf(f)
|
||||
if frac*conversionPrecision < x {
|
||||
if frac*prec < x {
|
||||
f = x
|
||||
break
|
||||
}
|
||||
if (1-frac)*conversionPrecision < x {
|
||||
if (1-frac)*prec < x {
|
||||
f = x + 1
|
||||
break
|
||||
}
|
||||
|
||||
@@ -7,6 +7,44 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPositiveFloatToDecimal(t *testing.T) {
|
||||
f := func(f float64, decimalExpected int64, exponentExpected int16) {
|
||||
t.Helper()
|
||||
decimal, exponent := positiveFloatToDecimal(f)
|
||||
if decimal != decimalExpected {
|
||||
t.Fatalf("unexpected decimal for positiveFloatToDecimal(%f); got %d; want %d", f, decimal, decimalExpected)
|
||||
}
|
||||
if exponent != exponentExpected {
|
||||
t.Fatalf("unexpected exponent for positiveFloatToDecimal(%f); got %d; want %d", f, exponent, exponentExpected)
|
||||
}
|
||||
}
|
||||
f(0, 0, 1) // The exponent is 1 is OK here. See comment in positiveFloatToDecimal.
|
||||
f(1, 1, 0)
|
||||
f(30, 3, 1)
|
||||
f(12345678900000000, 123456789, 8)
|
||||
f(12345678901234567, 12345678901234568, 0)
|
||||
f(1234567890123456789, 12345678901234567, 2)
|
||||
f(12345678901234567890, 12345678901234567, 3)
|
||||
f(18446744073670737131, 18446744073670737, 3)
|
||||
f(123456789012345678901, 12345678901234568, 4)
|
||||
f(1<<53, 1<<53, 0)
|
||||
f(1<<54, 18014398509481984, 0)
|
||||
f(1<<55, 3602879701896396, 1)
|
||||
f(1<<62, 4611686018427387, 3)
|
||||
f(1<<63, 9223372036854775, 3)
|
||||
f(1<<64, 18446744073709548, 3)
|
||||
f(1<<65, 368934881474191, 5)
|
||||
f(1<<66, 737869762948382, 5)
|
||||
f(1<<67, 1475739525896764, 5)
|
||||
|
||||
f(0.1, 1, -1)
|
||||
f(123456789012345678e-5, 12345678901234568, -4)
|
||||
f(1234567890123456789e-10, 12345678901234568, -8)
|
||||
f(1234567890123456789e-14, 1234567890123, -8)
|
||||
f(1234567890123456789e-17, 12345678901234, -12)
|
||||
f(1234567890123456789e-20, 1234567890123, -14)
|
||||
}
|
||||
|
||||
func TestAppendDecimalToFloat(t *testing.T) {
|
||||
testAppendDecimalToFloat(t, []int64{}, 0, nil)
|
||||
testAppendDecimalToFloat(t, []int64{0}, 0, []float64{0})
|
||||
@@ -168,7 +206,7 @@ func TestAppendFloatToDecimal(t *testing.T) {
|
||||
// no-op
|
||||
testAppendFloatToDecimal(t, []float64{}, nil, 0)
|
||||
testAppendFloatToDecimal(t, []float64{0}, []int64{0}, 0)
|
||||
testAppendFloatToDecimal(t, []float64{0, 1, -1, 12345678, -123456789}, []int64{0, 1, -1, 12345678, -123456789}, 0)
|
||||
testAppendFloatToDecimal(t, []float64{0, -0, 1, -1, 12345678, -123456789}, []int64{0, 0, 1, -1, 12345678, -123456789}, 0)
|
||||
|
||||
// upExp
|
||||
testAppendFloatToDecimal(t, []float64{-24, 0, 4.123, 0.3}, []int64{-24000, 0, 4123, 300}, -3)
|
||||
@@ -248,8 +286,8 @@ func TestFloatToDecimal(t *testing.T) {
|
||||
|
||||
f(math.Inf(1), vInfPos, 0)
|
||||
f(math.Inf(-1), vInfNeg, 0)
|
||||
f(1<<63-1, 922337203685, 7)
|
||||
f(-1<<63, -922337203685, 7)
|
||||
f(1<<63-1, 9223372036854775, 3)
|
||||
f(-1<<63, -9223372036854775, 3)
|
||||
|
||||
// Test precision loss due to conversionPrecision.
|
||||
f(0.1234567890123456, 12345678901234, -14)
|
||||
|
||||
@@ -8,17 +8,38 @@ import (
|
||||
)
|
||||
|
||||
func BenchmarkAppendDecimalToFloat(b *testing.B) {
|
||||
b.Run("VarNums", func(b *testing.B) {
|
||||
benchmarkAppendDecimalToFloat(b, testVA)
|
||||
})
|
||||
b.Run("Zeros", func(b *testing.B) {
|
||||
benchmarkAppendDecimalToFloat(b, testZeros)
|
||||
})
|
||||
b.Run("Ones", func(b *testing.B) {
|
||||
benchmarkAppendDecimalToFloat(b, testOnes)
|
||||
})
|
||||
}
|
||||
|
||||
func benchmarkAppendDecimalToFloat(b *testing.B, a []int64) {
|
||||
b.ReportAllocs()
|
||||
b.SetBytes(int64(len(testVA)))
|
||||
b.SetBytes(int64(len(a)))
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var fa []float64
|
||||
for pb.Next() {
|
||||
fa = AppendDecimalToFloat(fa[:0], testVA, 0)
|
||||
fa = AppendDecimalToFloat(fa[:0], a, 0)
|
||||
atomic.AddUint64(&Sink, uint64(len(fa)))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var testZeros = make([]int64, 8*1024)
|
||||
var testOnes = func() []int64 {
|
||||
a := make([]int64, 8*1024)
|
||||
for i := 0; i < len(a); i++ {
|
||||
a[i] = 1
|
||||
}
|
||||
return a
|
||||
}()
|
||||
|
||||
func BenchmarkAppendFloatToDecimal(b *testing.B) {
|
||||
b.Run("RealFloat", func(b *testing.B) {
|
||||
benchmarkAppendFloatToDecimal(b, testFAReal)
|
||||
@@ -26,8 +47,23 @@ func BenchmarkAppendFloatToDecimal(b *testing.B) {
|
||||
b.Run("Integers", func(b *testing.B) {
|
||||
benchmarkAppendFloatToDecimal(b, testFAInteger)
|
||||
})
|
||||
b.Run("Zeros", func(b *testing.B) {
|
||||
benchmarkAppendFloatToDecimal(b, testFZeros)
|
||||
})
|
||||
b.Run("Ones", func(b *testing.B) {
|
||||
benchmarkAppendFloatToDecimal(b, testFOnes)
|
||||
})
|
||||
}
|
||||
|
||||
var testFZeros = make([]float64, 8*1024)
|
||||
var testFOnes = func() []float64 {
|
||||
a := make([]float64, 8*1024)
|
||||
for i := 0; i < len(a); i++ {
|
||||
a[i] = 1
|
||||
}
|
||||
return a
|
||||
}()
|
||||
|
||||
func benchmarkAppendFloatToDecimal(b *testing.B, fa []float64) {
|
||||
b.ReportAllocs()
|
||||
b.SetBytes(int64(len(fa)))
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/decimal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fastnum"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
)
|
||||
|
||||
@@ -201,6 +202,14 @@ func unmarshalInt64Array(dst []int64, src []byte, mt MarshalType, firstValue int
|
||||
if len(src) > 0 {
|
||||
return nil, fmt.Errorf("unexpected data left in const encoding: %d bytes", len(src))
|
||||
}
|
||||
if firstValue == 0 {
|
||||
dst = fastnum.AppendInt64Zeros(dst, itemsCount)
|
||||
return dst, nil
|
||||
}
|
||||
if firstValue == 1 {
|
||||
dst = fastnum.AppendInt64Ones(dst, itemsCount)
|
||||
return dst, nil
|
||||
}
|
||||
for itemsCount > 0 {
|
||||
dst = append(dst, firstValue)
|
||||
itemsCount--
|
||||
@@ -267,6 +276,14 @@ func isConst(a []int64) bool {
|
||||
if len(a) == 0 {
|
||||
return false
|
||||
}
|
||||
if fastnum.IsInt64Zeros(a) {
|
||||
// Fast path for array containing only zeros.
|
||||
return true
|
||||
}
|
||||
if fastnum.IsInt64Ones(a) {
|
||||
// Fast path for array containing only ones.
|
||||
return true
|
||||
}
|
||||
v1 := a[0]
|
||||
for _, v := range a {
|
||||
if v != v1 {
|
||||
|
||||
@@ -32,7 +32,7 @@ func BenchmarkUnmarshalGaugeArray(b *testing.B) {
|
||||
var dst []int64
|
||||
var err error
|
||||
for pb.Next() {
|
||||
dst, err = unmarshalInt64Array(dst[:0], benchMarshaledGaugeArray, MarshalTypeZSTDNearestDelta, 0, len(benchGaugeArray))
|
||||
dst, err = unmarshalInt64Array(dst[:0], benchMarshaledGaugeArray, MarshalTypeZSTDNearestDelta, benchGaugeArray[0], len(benchGaugeArray))
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot unmarshal gauge array: %s", err))
|
||||
}
|
||||
@@ -79,7 +79,7 @@ func BenchmarkUnmarshalDeltaConstArray(b *testing.B) {
|
||||
var dst []int64
|
||||
var err error
|
||||
for pb.Next() {
|
||||
dst, err = unmarshalInt64Array(dst[:0], benchMarshaledDeltaConstArray, MarshalTypeDeltaConst, 0, len(benchDeltaConstArray))
|
||||
dst, err = unmarshalInt64Array(dst[:0], benchMarshaledDeltaConstArray, MarshalTypeDeltaConst, benchDeltaConstArray[0], len(benchDeltaConstArray))
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot unmarshal delta const array: %s", err))
|
||||
}
|
||||
@@ -126,7 +126,7 @@ func BenchmarkUnmarshalConstArray(b *testing.B) {
|
||||
var dst []int64
|
||||
var err error
|
||||
for pb.Next() {
|
||||
dst, err = unmarshalInt64Array(dst[:0], benchMarshaledConstArray, MarshalTypeConst, 0, len(benchConstArray))
|
||||
dst, err = unmarshalInt64Array(dst[:0], benchMarshaledConstArray, MarshalTypeConst, benchConstArray[0], len(benchConstArray))
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot unmarshal const array: %s", err))
|
||||
}
|
||||
@@ -171,7 +171,7 @@ func BenchmarkUnmarshalZeroConstArray(b *testing.B) {
|
||||
var dst []int64
|
||||
var err error
|
||||
for pb.Next() {
|
||||
dst, err = unmarshalInt64Array(dst[:0], benchMarshaledZeroConstArray, MarshalTypeConst, 0, len(benchZeroConstArray))
|
||||
dst, err = unmarshalInt64Array(dst[:0], benchMarshaledZeroConstArray, MarshalTypeConst, benchZeroConstArray[0], len(benchZeroConstArray))
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot unmarshal zero const array: %s", err))
|
||||
}
|
||||
@@ -210,7 +210,7 @@ func BenchmarkUnmarshalInt64Array(b *testing.B) {
|
||||
var dst []int64
|
||||
var err error
|
||||
for pb.Next() {
|
||||
dst, err = unmarshalInt64Array(dst[:0], benchMarshaledInt64Array, benchMarshalType, 0, len(benchInt64Array))
|
||||
dst, err = unmarshalInt64Array(dst[:0], benchMarshaledInt64Array, benchMarshalType, benchInt64Array[0], len(benchInt64Array))
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot unmarshal int64 array: %s", err))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package encoding
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
@@ -12,8 +13,8 @@ func MarshalUint16(dst []byte, u uint16) []byte {
|
||||
|
||||
// UnmarshalUint16 returns unmarshaled uint32 from src.
|
||||
func UnmarshalUint16(src []byte) uint16 {
|
||||
_ = src[1]
|
||||
return uint16(src[0])<<8 | uint16(src[1])
|
||||
// This is faster than the manual conversion.
|
||||
return binary.BigEndian.Uint16(src)
|
||||
}
|
||||
|
||||
// MarshalUint32 appends marshaled v to dst and returns the result.
|
||||
@@ -23,8 +24,8 @@ func MarshalUint32(dst []byte, u uint32) []byte {
|
||||
|
||||
// UnmarshalUint32 returns unmarshaled uint32 from src.
|
||||
func UnmarshalUint32(src []byte) uint32 {
|
||||
_ = src[3]
|
||||
return uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3])
|
||||
// This is faster than the manual conversion.
|
||||
return binary.BigEndian.Uint32(src)
|
||||
}
|
||||
|
||||
// MarshalUint64 appends marshaled v to dst and returns the result.
|
||||
@@ -34,8 +35,8 @@ func MarshalUint64(dst []byte, u uint64) []byte {
|
||||
|
||||
// UnmarshalUint64 returns unmarshaled uint64 from src.
|
||||
func UnmarshalUint64(src []byte) uint64 {
|
||||
_ = src[7]
|
||||
return uint64(src[0])<<56 | uint64(src[1])<<48 | uint64(src[2])<<40 | uint64(src[3])<<32 | uint64(src[4])<<24 | uint64(src[5])<<16 | uint64(src[6])<<8 | uint64(src[7])
|
||||
// This is faster than the manual conversion.
|
||||
return binary.BigEndian.Uint64(src)
|
||||
}
|
||||
|
||||
// MarshalInt16 appends marshaled v to dst and returns the result.
|
||||
@@ -48,8 +49,8 @@ func MarshalInt16(dst []byte, v int16) []byte {
|
||||
|
||||
// UnmarshalInt16 returns unmarshaled int16 from src.
|
||||
func UnmarshalInt16(src []byte) int16 {
|
||||
_ = src[1]
|
||||
u := uint16(src[0])<<8 | uint16(src[1])
|
||||
// This is faster than the manual conversion.
|
||||
u := binary.BigEndian.Uint16(src)
|
||||
v := int16(u>>1) ^ (int16(u<<15) >> 15) // zig-zag decoding without branching.
|
||||
return v
|
||||
}
|
||||
@@ -64,8 +65,8 @@ func MarshalInt64(dst []byte, v int64) []byte {
|
||||
|
||||
// UnmarshalInt64 returns unmarshaled int64 from src.
|
||||
func UnmarshalInt64(src []byte) int64 {
|
||||
_ = src[7]
|
||||
u := uint64(src[0])<<56 | uint64(src[1])<<48 | uint64(src[2])<<40 | uint64(src[3])<<32 | uint64(src[4])<<24 | uint64(src[5])<<16 | uint64(src[6])<<8 | uint64(src[7])
|
||||
// This is faster than the manual conversion.
|
||||
u := binary.BigEndian.Uint64(src)
|
||||
v := int64(u>>1) ^ (int64(u<<63) >> 63) // zig-zag decoding without branching.
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -6,6 +6,33 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkMarshalUint64(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.SetBytes(1)
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var dst []byte
|
||||
var sink uint64
|
||||
for pb.Next() {
|
||||
dst = MarshalUint64(dst[:0], sink)
|
||||
sink += uint64(len(dst))
|
||||
}
|
||||
atomic.AddUint64(&Sink, sink)
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshalUint64(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.SetBytes(1)
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var sink uint64
|
||||
for pb.Next() {
|
||||
v := UnmarshalUint64(testMarshaledUint64Data)
|
||||
sink += v
|
||||
}
|
||||
atomic.AddUint64(&Sink, sink)
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkMarshalInt64(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.SetBytes(1)
|
||||
@@ -120,3 +147,4 @@ func benchmarkUnmarshalVarInt64s(b *testing.B, maxValue int64) {
|
||||
}
|
||||
|
||||
var testMarshaledInt64Data = MarshalInt64(nil, 1234567890)
|
||||
var testMarshaledUint64Data = MarshalUint64(nil, 1234567890)
|
||||
|
||||
144
lib/fastnum/fastnum.go
Normal file
144
lib/fastnum/fastnum.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package fastnum
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// AppendInt64Zeros appends items zeros to dst and returns the result.
|
||||
//
|
||||
// It is faster than the corresponding loop.
|
||||
func AppendInt64Zeros(dst []int64, items int) []int64 {
|
||||
return appendInt64Data(dst, items, int64Zeros[:])
|
||||
}
|
||||
|
||||
// AppendInt64Ones appends items ones to dst and returns the result.
|
||||
//
|
||||
// It is faster than the correponding loop.
|
||||
func AppendInt64Ones(dst []int64, items int) []int64 {
|
||||
return appendInt64Data(dst, items, int64Ones[:])
|
||||
}
|
||||
|
||||
// AppendFloat64Zeros appends items zeros to dst and returns the result.
|
||||
//
|
||||
// It is faster than the corresponding loop.
|
||||
func AppendFloat64Zeros(dst []float64, items int) []float64 {
|
||||
return appendFloat64Data(dst, items, float64Zeros[:])
|
||||
}
|
||||
|
||||
// AppendFloat64Ones appends items ones to dst and returns the result.
|
||||
//
|
||||
// It is faster than the corresponding loop.
|
||||
func AppendFloat64Ones(dst []float64, items int) []float64 {
|
||||
return appendFloat64Data(dst, items, float64Ones[:])
|
||||
}
|
||||
|
||||
// IsInt64Zeros checks whether a contains only zeros.
|
||||
func IsInt64Zeros(a []int64) bool {
|
||||
return isInt64Data(a, int64Zeros[:])
|
||||
}
|
||||
|
||||
// IsInt64Ones checks whether a contains only ones.
|
||||
func IsInt64Ones(a []int64) bool {
|
||||
return isInt64Data(a, int64Ones[:])
|
||||
}
|
||||
|
||||
// IsFloat64Zeros checks whether a contains only zeros.
|
||||
func IsFloat64Zeros(a []float64) bool {
|
||||
return isFloat64Data(a, float64Zeros[:])
|
||||
}
|
||||
|
||||
// IsFloat64Ones checks whether a contains only ones.
|
||||
func IsFloat64Ones(a []float64) bool {
|
||||
return isFloat64Data(a, float64Ones[:])
|
||||
}
|
||||
|
||||
func appendInt64Data(dst []int64, items int, src []int64) []int64 {
|
||||
for items > 0 {
|
||||
n := len(src)
|
||||
if n > items {
|
||||
n = items
|
||||
}
|
||||
dst = append(dst, src[:n]...)
|
||||
items -= n
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func appendFloat64Data(dst []float64, items int, src []float64) []float64 {
|
||||
for items > 0 {
|
||||
n := len(src)
|
||||
if n > items {
|
||||
n = items
|
||||
}
|
||||
dst = append(dst, src[:n]...)
|
||||
items -= n
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func isInt64Data(a, data []int64) bool {
|
||||
if len(a) == 0 {
|
||||
return true
|
||||
}
|
||||
if len(data) != 8*1024 {
|
||||
panic("len(data) must equal to 8*1024")
|
||||
}
|
||||
b := (*[64 * 1024]byte)(unsafe.Pointer(&data[0]))
|
||||
for len(a) > 0 {
|
||||
n := len(data)
|
||||
if n > len(a) {
|
||||
n = len(a)
|
||||
}
|
||||
x := a[:n]
|
||||
a = a[n:]
|
||||
xb := (*[64 * 1024]byte)(unsafe.Pointer(&x[0]))
|
||||
xbLen := len(x) * 8
|
||||
if !bytes.Equal(xb[:xbLen], b[:xbLen]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isFloat64Data(a, data []float64) bool {
|
||||
if len(a) == 0 {
|
||||
return true
|
||||
}
|
||||
if len(data) != 8*1024 {
|
||||
panic("len(data) must equal to 8*1024")
|
||||
}
|
||||
b := (*[64 * 1024]byte)(unsafe.Pointer(&data[0]))
|
||||
for len(a) > 0 {
|
||||
n := len(data)
|
||||
if n > len(a) {
|
||||
n = len(a)
|
||||
}
|
||||
x := a[:n]
|
||||
a = a[n:]
|
||||
xb := (*[64 * 1024]byte)(unsafe.Pointer(&x[0]))
|
||||
xbLen := len(x) * 8
|
||||
if !bytes.Equal(xb[:xbLen], b[:xbLen]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var (
|
||||
int64Zeros [8 * 1024]int64
|
||||
int64Ones = func() (a [8 * 1024]int64) {
|
||||
for i := 0; i < len(a); i++ {
|
||||
a[i] = 1
|
||||
}
|
||||
return a
|
||||
}()
|
||||
|
||||
float64Zeros [8 * 1024]float64
|
||||
float64Ones = func() (a [8 * 1024]float64) {
|
||||
for i := 0; i < len(a); i++ {
|
||||
a[i] = 1
|
||||
}
|
||||
return a
|
||||
}()
|
||||
)
|
||||
192
lib/fastnum/fastnum_test.go
Normal file
192
lib/fastnum/fastnum_test.go
Normal file
@@ -0,0 +1,192 @@
|
||||
package fastnum
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIsInt64Zeros(t *testing.T) {
|
||||
for _, n := range []int{0, 1, 10, 100, 1000, 1e4, 1e5, 8*1024 + 1} {
|
||||
t.Run(fmt.Sprintf("%d_items", n), func(t *testing.T) {
|
||||
a := make([]int64, n)
|
||||
if !IsInt64Zeros(a) {
|
||||
t.Fatalf("IsInt64Zeros must return true")
|
||||
}
|
||||
if len(a) > 0 {
|
||||
a[len(a)-1] = 1
|
||||
if IsInt64Zeros(a) {
|
||||
t.Fatalf("IsInt64Zeros must return false")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsInt64Ones(t *testing.T) {
|
||||
for _, n := range []int{0, 1, 10, 100, 1000, 1e4, 1e5, 8*1024 + 1} {
|
||||
t.Run(fmt.Sprintf("%d_items", n), func(t *testing.T) {
|
||||
a := make([]int64, n)
|
||||
for i := 0; i < n; i++ {
|
||||
a[i] = 1
|
||||
}
|
||||
if !IsInt64Ones(a) {
|
||||
t.Fatalf("IsInt64Ones must return true")
|
||||
}
|
||||
if len(a) > 0 {
|
||||
a[len(a)-1] = 0
|
||||
if IsInt64Ones(a) {
|
||||
t.Fatalf("IsInt64Ones must return false")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsFloat64Zeros(t *testing.T) {
|
||||
for _, n := range []int{0, 1, 10, 100, 1000, 1e4, 1e5, 8*1024 + 1} {
|
||||
t.Run(fmt.Sprintf("%d_items", n), func(t *testing.T) {
|
||||
a := make([]float64, n)
|
||||
if !IsFloat64Zeros(a) {
|
||||
t.Fatalf("IsInt64Zeros must return true")
|
||||
}
|
||||
if len(a) > 0 {
|
||||
a[len(a)-1] = 1
|
||||
if IsFloat64Zeros(a) {
|
||||
t.Fatalf("IsInt64Zeros must return false")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsFloat64Ones(t *testing.T) {
|
||||
for _, n := range []int{0, 1, 10, 100, 1000, 1e4, 1e5, 8*1024 + 1} {
|
||||
t.Run(fmt.Sprintf("%d_items", n), func(t *testing.T) {
|
||||
a := make([]float64, n)
|
||||
for i := 0; i < n; i++ {
|
||||
a[i] = 1
|
||||
}
|
||||
if !IsFloat64Ones(a) {
|
||||
t.Fatalf("IsInt64Ones must return true")
|
||||
}
|
||||
if len(a) > 0 {
|
||||
a[len(a)-1] = 0
|
||||
if IsFloat64Ones(a) {
|
||||
t.Fatalf("IsInt64Ones must return false")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendInt64Zeros(t *testing.T) {
|
||||
for _, n := range []int{0, 1, 10, 100, 1000, 1e4, 1e5, 8*1024 + 1} {
|
||||
t.Run(fmt.Sprintf("%d_items", n), func(t *testing.T) {
|
||||
a := AppendInt64Zeros(nil, n)
|
||||
if len(a) != n {
|
||||
t.Fatalf("unexpected len(a); got %d; want %d", len(a), n)
|
||||
}
|
||||
if !IsInt64Zeros(a) {
|
||||
t.Fatalf("IsInt64Zeros must return true")
|
||||
}
|
||||
|
||||
prefix := []int64{1, 2, 3}
|
||||
a = AppendInt64Zeros(prefix, n)
|
||||
if len(a) != len(prefix)+n {
|
||||
t.Fatalf("unexpected len(a) with prefix; got %d; want %d", len(a), len(prefix)+n)
|
||||
}
|
||||
for i := 0; i < len(prefix); i++ {
|
||||
if a[i] != prefix[i] {
|
||||
t.Fatalf("unexpected prefix[%d]; got %d; want %d", i, a[i], prefix[i])
|
||||
}
|
||||
}
|
||||
if !IsInt64Zeros(a[len(prefix):]) {
|
||||
t.Fatalf("IsInt64Zeros for prefixed a must return true")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendInt64Ones(t *testing.T) {
|
||||
for _, n := range []int{0, 1, 10, 100, 1000, 1e4, 1e5, 8*1024 + 1} {
|
||||
t.Run(fmt.Sprintf("%d_items", n), func(t *testing.T) {
|
||||
a := AppendInt64Ones(nil, n)
|
||||
if len(a) != n {
|
||||
t.Fatalf("unexpected len(a); got %d; want %d", len(a), n)
|
||||
}
|
||||
if !IsInt64Ones(a) {
|
||||
t.Fatalf("IsInt64Ones must return true")
|
||||
}
|
||||
|
||||
prefix := []int64{1, 2, 3}
|
||||
a = AppendInt64Ones(prefix, n)
|
||||
if len(a) != len(prefix)+n {
|
||||
t.Fatalf("unexpected len(a) with prefix; got %d; want %d", len(a), len(prefix)+n)
|
||||
}
|
||||
for i := 0; i < len(prefix); i++ {
|
||||
if a[i] != prefix[i] {
|
||||
t.Fatalf("unexpected prefix[%d]; got %d; want %d", i, a[i], prefix[i])
|
||||
}
|
||||
}
|
||||
if !IsInt64Ones(a[len(prefix):]) {
|
||||
t.Fatalf("IsInt64Ones for prefixed a must return true")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendFloat64Zeros(t *testing.T) {
|
||||
for _, n := range []int{0, 1, 10, 100, 1000, 1e4, 1e5, 8*1024 + 1} {
|
||||
t.Run(fmt.Sprintf("%d_items", n), func(t *testing.T) {
|
||||
a := AppendFloat64Zeros(nil, n)
|
||||
if len(a) != n {
|
||||
t.Fatalf("unexpected len(a); got %d; want %d", len(a), n)
|
||||
}
|
||||
if !IsFloat64Zeros(a) {
|
||||
t.Fatalf("IsFloat64Zeros must return true")
|
||||
}
|
||||
|
||||
prefix := []float64{1, 2, 3}
|
||||
a = AppendFloat64Zeros(prefix, n)
|
||||
if len(a) != len(prefix)+n {
|
||||
t.Fatalf("unexpected len(a) with prefix; got %d; want %d", len(a), len(prefix)+n)
|
||||
}
|
||||
for i := 0; i < len(prefix); i++ {
|
||||
if a[i] != prefix[i] {
|
||||
t.Fatalf("unexpected prefix[%d]; got %f; want %f", i, a[i], prefix[i])
|
||||
}
|
||||
}
|
||||
if !IsFloat64Zeros(a[len(prefix):]) {
|
||||
t.Fatalf("IsFloat64Zeros for prefixed a must return true")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendFloat64Ones(t *testing.T) {
|
||||
for _, n := range []int{0, 1, 10, 100, 1000, 1e4, 1e5, 8*1024 + 1} {
|
||||
t.Run(fmt.Sprintf("%d_items", n), func(t *testing.T) {
|
||||
a := AppendFloat64Ones(nil, n)
|
||||
if len(a) != n {
|
||||
t.Fatalf("unexpected len(a); got %d; want %d", len(a), n)
|
||||
}
|
||||
if !IsFloat64Ones(a) {
|
||||
t.Fatalf("IsFloat64Ones must return true")
|
||||
}
|
||||
|
||||
prefix := []float64{1, 2, 3}
|
||||
a = AppendFloat64Ones(prefix, n)
|
||||
if len(a) != len(prefix)+n {
|
||||
t.Fatalf("unexpected len(a) with prefix; got %d; want %d", len(a), len(prefix)+n)
|
||||
}
|
||||
for i := 0; i < len(prefix); i++ {
|
||||
if a[i] != prefix[i] {
|
||||
t.Fatalf("unexpected prefix[%d]; got %f; want %f", i, a[i], prefix[i])
|
||||
}
|
||||
}
|
||||
if !IsFloat64Ones(a[len(prefix):]) {
|
||||
t.Fatalf("IsFloat64Ones for prefixed a must return true")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -92,7 +92,7 @@ var tmpFileNum uint64
|
||||
|
||||
// WriteFileAtomically atomically writes data to the given file path.
|
||||
//
|
||||
// WriteFile returns only after the file is fully written and synced
|
||||
// WriteFileAtomically returns only after the file is fully written and synced
|
||||
// to the underlying storage.
|
||||
func WriteFileAtomically(path string, data []byte) error {
|
||||
// Check for the existing file. It is expected that
|
||||
|
||||
@@ -9,12 +9,17 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
)
|
||||
|
||||
const maxInt = int(^uint(0) >> 1)
|
||||
|
||||
func sysTotalMemory() int {
|
||||
var si syscall.Sysinfo_t
|
||||
if err := syscall.Sysinfo(&si); err != nil {
|
||||
logger.Panicf("FATAL: error in syscall.Sysinfo: %s", err)
|
||||
}
|
||||
totalMem := int(si.Totalram) * int(si.Unit)
|
||||
totalMem := maxInt
|
||||
if uint64(maxInt)/uint64(si.Totalram) > uint64(si.Unit) {
|
||||
totalMem = int(uint64(si.Totalram) * uint64(si.Unit))
|
||||
}
|
||||
|
||||
// Try determining the amount of memory inside docker container.
|
||||
// See https://stackoverflow.com/questions/42187085/check-mem-limit-within-a-docker-container .
|
||||
|
||||
@@ -2,6 +2,7 @@ package mergeset
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -11,10 +12,20 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
)
|
||||
|
||||
type byteSliceSorter [][]byte
|
||||
|
||||
func (s byteSliceSorter) Len() int { return len(s) }
|
||||
func (s byteSliceSorter) Less(i, j int) bool {
|
||||
return string(s[i]) < string(s[j])
|
||||
}
|
||||
func (s byteSliceSorter) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
type inmemoryBlock struct {
|
||||
commonPrefix []byte
|
||||
data []byte
|
||||
items [][]byte
|
||||
items byteSliceSorter
|
||||
}
|
||||
|
||||
func (ib *inmemoryBlock) Reset() {
|
||||
@@ -77,12 +88,9 @@ func (ib *inmemoryBlock) Add(x []byte) bool {
|
||||
// It must fit CPU cache size, i.e. 64KB for the current CPUs.
|
||||
const maxInmemoryBlockSize = 64 * 1024
|
||||
|
||||
func (ib *inmemoryBlock) itemsLess(i, j int) bool {
|
||||
return string(ib.items[i]) < string(ib.items[j])
|
||||
}
|
||||
|
||||
func (ib *inmemoryBlock) sort() {
|
||||
sort.Slice(ib.items, ib.itemsLess)
|
||||
// Use sort.Sort instead of sort.Slice in order to eliminate memory allocation.
|
||||
sort.Sort(&ib.items)
|
||||
bb := bbPool.Get()
|
||||
b := bytesutil.Resize(bb.B, len(ib.data))
|
||||
b = b[:0]
|
||||
@@ -120,7 +128,8 @@ func checkMarshalType(mt marshalType) error {
|
||||
}
|
||||
|
||||
func (ib *inmemoryBlock) isSorted() bool {
|
||||
return sort.SliceIsSorted(ib.items, ib.itemsLess)
|
||||
// Use sort.IsSorted instead of sort.SliceIsSorted in order to eliminate memory allocation.
|
||||
return sort.IsSorted(&ib.items)
|
||||
}
|
||||
|
||||
// MarshalUnsortedData marshals unsorted items from ib to sb.
|
||||
@@ -138,6 +147,10 @@ func (ib *inmemoryBlock) MarshalUnsortedData(sb *storageBlock, firstItemDst, com
|
||||
return ib.marshalData(sb, firstItemDst, commonPrefixDst, compressLevel)
|
||||
}
|
||||
|
||||
var isInTest = func() bool {
|
||||
return strings.HasSuffix(os.Args[0], ".test")
|
||||
}()
|
||||
|
||||
// MarshalUnsortedData marshals sorted items from ib to sb.
|
||||
//
|
||||
// It also:
|
||||
@@ -146,17 +159,22 @@ func (ib *inmemoryBlock) MarshalUnsortedData(sb *storageBlock, firstItemDst, com
|
||||
// - returns the number of items encoded including the first item.
|
||||
// - returns the marshal type used for the encoding.
|
||||
func (ib *inmemoryBlock) MarshalSortedData(sb *storageBlock, firstItemDst, commonPrefixDst []byte, compressLevel int) ([]byte, []byte, uint32, marshalType) {
|
||||
// if !ib.isSorted() {
|
||||
// logger.Panicf("BUG: %d items must be sorted; items:\n%s", len(ib.items), ib.debugItemsString())
|
||||
// }
|
||||
if isInTest && !ib.isSorted() {
|
||||
logger.Panicf("BUG: %d items must be sorted; items:\n%s", len(ib.items), ib.debugItemsString())
|
||||
}
|
||||
ib.updateCommonPrefix()
|
||||
return ib.marshalData(sb, firstItemDst, commonPrefixDst, compressLevel)
|
||||
}
|
||||
|
||||
func (ib *inmemoryBlock) debugItemsString() string {
|
||||
var sb strings.Builder
|
||||
var prevItem []byte
|
||||
for i, item := range ib.items {
|
||||
if string(item) < string(prevItem) {
|
||||
fmt.Fprintf(&sb, "!!! the next item is smaller than the previous item !!!\n")
|
||||
}
|
||||
fmt.Fprintf(&sb, "%05d %X\n", i, item)
|
||||
prevItem = item
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
@@ -175,7 +193,7 @@ func (ib *inmemoryBlock) marshalData(sb *storageBlock, firstItemDst, commonPrefi
|
||||
firstItemDst = append(firstItemDst, ib.items[0]...)
|
||||
commonPrefixDst = append(commonPrefixDst, ib.commonPrefix...)
|
||||
|
||||
if len(ib.data)-len(ib.commonPrefix)*len(ib.items) < 64 || len(ib.items) < 10 {
|
||||
if len(ib.data)-len(ib.commonPrefix)*len(ib.items) < 64 || len(ib.items) < 2 {
|
||||
// Use plain encoding form small block, since it is cheaper.
|
||||
ib.marshalDataPlain(sb)
|
||||
return firstItemDst, commonPrefixDst, uint32(len(ib.items)), marshalTypePlain
|
||||
|
||||
@@ -5,18 +5,31 @@ import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
)
|
||||
|
||||
// PrepareBlockCallback can transform the passed items allocated at the given data.
|
||||
//
|
||||
// The callback is called during merge before flushing full block of the given items
|
||||
// to persistent storage.
|
||||
//
|
||||
// The callback must return sorted items. The first and the last item must be unchanged.
|
||||
// The callback can re-use data and items for storing the result.
|
||||
type PrepareBlockCallback func(data []byte, items [][]byte) ([]byte, [][]byte)
|
||||
|
||||
// mergeBlockStreams merges bsrs and writes result to bsw.
|
||||
//
|
||||
// It also fills ph.
|
||||
//
|
||||
// prepareBlock is optional.
|
||||
//
|
||||
// The function immediately returns when stopCh is closed.
|
||||
//
|
||||
// It also atomically adds the number of items merged to itemsMerged.
|
||||
func mergeBlockStreams(ph *partHeader, bsw *blockStreamWriter, bsrs []*blockStreamReader, stopCh <-chan struct{}, itemsMerged *uint64) error {
|
||||
func mergeBlockStreams(ph *partHeader, bsw *blockStreamWriter, bsrs []*blockStreamReader, prepareBlock PrepareBlockCallback, stopCh <-chan struct{}, itemsMerged *uint64) error {
|
||||
bsm := bsmPool.Get().(*blockStreamMerger)
|
||||
if err := bsm.Init(bsrs); err != nil {
|
||||
if err := bsm.Init(bsrs, prepareBlock); err != nil {
|
||||
return fmt.Errorf("cannot initialize blockStreamMerger: %s", err)
|
||||
}
|
||||
err := bsm.Merge(bsw, ph, stopCh, itemsMerged)
|
||||
@@ -39,15 +52,24 @@ var bsmPool = &sync.Pool{
|
||||
}
|
||||
|
||||
type blockStreamMerger struct {
|
||||
prepareBlock PrepareBlockCallback
|
||||
|
||||
bsrHeap bsrHeap
|
||||
|
||||
// ib is a scratch block with pending items.
|
||||
ib inmemoryBlock
|
||||
|
||||
phFirstItemCaught bool
|
||||
|
||||
// This are auxiliary buffers used in flushIB
|
||||
// for consistency checks after prepareBlock call.
|
||||
firstItem []byte
|
||||
lastItem []byte
|
||||
}
|
||||
|
||||
func (bsm *blockStreamMerger) reset() {
|
||||
bsm.prepareBlock = nil
|
||||
|
||||
for i := range bsm.bsrHeap {
|
||||
bsm.bsrHeap[i] = nil
|
||||
}
|
||||
@@ -57,8 +79,9 @@ func (bsm *blockStreamMerger) reset() {
|
||||
bsm.phFirstItemCaught = false
|
||||
}
|
||||
|
||||
func (bsm *blockStreamMerger) Init(bsrs []*blockStreamReader) error {
|
||||
func (bsm *blockStreamMerger) Init(bsrs []*blockStreamReader, prepareBlock PrepareBlockCallback) error {
|
||||
bsm.reset()
|
||||
bsm.prepareBlock = prepareBlock
|
||||
for _, bsr := range bsrs {
|
||||
if bsr.Next() {
|
||||
bsm.bsrHeap = append(bsm.bsrHeap, bsr)
|
||||
@@ -95,25 +118,23 @@ again:
|
||||
|
||||
bsr := heap.Pop(&bsm.bsrHeap).(*blockStreamReader)
|
||||
|
||||
if !bsm.phFirstItemCaught {
|
||||
ph.firstItem = append(ph.firstItem[:0], bsr.Block.items[0]...)
|
||||
bsm.phFirstItemCaught = true
|
||||
}
|
||||
|
||||
var nextItem []byte
|
||||
hasNextItem := false
|
||||
if len(bsm.bsrHeap) > 0 {
|
||||
nextItem = bsm.bsrHeap[0].bh.firstItem
|
||||
hasNextItem = true
|
||||
}
|
||||
for bsr.blockItemIdx < len(bsr.Block.items) && (!hasNextItem || string(bsr.Block.items[bsr.blockItemIdx]) <= string(nextItem)) {
|
||||
if bsm.ib.Add(bsr.Block.items[bsr.blockItemIdx]) {
|
||||
bsr.blockItemIdx++
|
||||
for bsr.blockItemIdx < len(bsr.Block.items) {
|
||||
item := bsr.Block.items[bsr.blockItemIdx]
|
||||
if hasNextItem && string(item) > string(nextItem) {
|
||||
break
|
||||
}
|
||||
if !bsm.ib.Add(item) {
|
||||
// The bsm.ib is full. Flush it to bsw and continue.
|
||||
bsm.flushIB(bsw, ph, itemsMerged)
|
||||
continue
|
||||
}
|
||||
|
||||
// The bsm.ib is full. Flush it to bsw and continue.
|
||||
bsm.flushIB(bsw, ph, itemsMerged)
|
||||
bsr.blockItemIdx++
|
||||
}
|
||||
if bsr.blockItemIdx == len(bsr.Block.items) {
|
||||
// bsr.Block is fully read. Proceed to the next block.
|
||||
@@ -139,9 +160,35 @@ func (bsm *blockStreamMerger) flushIB(bsw *blockStreamWriter, ph *partHeader, it
|
||||
// Nothing to flush.
|
||||
return
|
||||
}
|
||||
itemsCount := uint64(len(bsm.ib.items))
|
||||
ph.itemsCount += itemsCount
|
||||
atomic.AddUint64(itemsMerged, itemsCount)
|
||||
atomic.AddUint64(itemsMerged, uint64(len(bsm.ib.items)))
|
||||
if bsm.prepareBlock != nil {
|
||||
bsm.firstItem = append(bsm.firstItem[:0], bsm.ib.items[0]...)
|
||||
bsm.lastItem = append(bsm.lastItem[:0], bsm.ib.items[len(bsm.ib.items)-1]...)
|
||||
bsm.ib.data, bsm.ib.items = bsm.prepareBlock(bsm.ib.data, bsm.ib.items)
|
||||
if len(bsm.ib.items) == 0 {
|
||||
// Nothing to flush
|
||||
return
|
||||
}
|
||||
// Consistency checks after prepareBlock call.
|
||||
firstItem := bsm.ib.items[0]
|
||||
if string(firstItem) != string(bsm.firstItem) {
|
||||
logger.Panicf("BUG: prepareBlock must return first item equal to the original first item;\ngot\n%X\nwant\n%X", firstItem, bsm.firstItem)
|
||||
}
|
||||
lastItem := bsm.ib.items[len(bsm.ib.items)-1]
|
||||
if string(lastItem) != string(bsm.lastItem) {
|
||||
logger.Panicf("BUG: prepareBlock must return last item equal to the original last item;\ngot\n%X\nwant\n%X", lastItem, bsm.lastItem)
|
||||
}
|
||||
// Verify whether the bsm.ib.items are sorted only in tests, since this
|
||||
// can be expensive check in prod for items with long common prefix.
|
||||
if isInTest && !bsm.ib.isSorted() {
|
||||
logger.Panicf("BUG: prepareBlock must return sorted items;\ngot\n%s", bsm.ib.debugItemsString())
|
||||
}
|
||||
}
|
||||
ph.itemsCount += uint64(len(bsm.ib.items))
|
||||
if !bsm.phFirstItemCaught {
|
||||
ph.firstItem = append(ph.firstItem[:0], bsm.ib.items[0]...)
|
||||
bsm.phFirstItemCaught = true
|
||||
}
|
||||
ph.lastItem = append(ph.lastItem[:0], bsm.ib.items[len(bsm.ib.items)-1]...)
|
||||
bsw.WriteBlock(&bsm.ib)
|
||||
bsm.ib.Reset()
|
||||
|
||||
@@ -30,14 +30,14 @@ func TestMultilevelMerge(t *testing.T) {
|
||||
var dstIP1 inmemoryPart
|
||||
var bsw1 blockStreamWriter
|
||||
bsw1.InitFromInmemoryPart(&dstIP1, 0)
|
||||
if err := mergeBlockStreams(&dstIP1.ph, &bsw1, bsrs[:5], nil, &itemsMerged); err != nil {
|
||||
if err := mergeBlockStreams(&dstIP1.ph, &bsw1, bsrs[:5], nil, nil, &itemsMerged); err != nil {
|
||||
t.Fatalf("cannot merge first level part 1: %s", err)
|
||||
}
|
||||
|
||||
var dstIP2 inmemoryPart
|
||||
var bsw2 blockStreamWriter
|
||||
bsw2.InitFromInmemoryPart(&dstIP2, 0)
|
||||
if err := mergeBlockStreams(&dstIP2.ph, &bsw2, bsrs[5:], nil, &itemsMerged); err != nil {
|
||||
if err := mergeBlockStreams(&dstIP2.ph, &bsw2, bsrs[5:], nil, nil, &itemsMerged); err != nil {
|
||||
t.Fatalf("cannot merge first level part 2: %s", err)
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ func TestMultilevelMerge(t *testing.T) {
|
||||
newTestBlockStreamReader(&dstIP2),
|
||||
}
|
||||
bsw.InitFromInmemoryPart(&dstIP, 0)
|
||||
if err := mergeBlockStreams(&dstIP.ph, &bsw, bsrsTop, nil, &itemsMerged); err != nil {
|
||||
if err := mergeBlockStreams(&dstIP.ph, &bsw, bsrsTop, nil, nil, &itemsMerged); err != nil {
|
||||
t.Fatalf("cannot merge second level: %s", err)
|
||||
}
|
||||
if itemsMerged != uint64(len(items)) {
|
||||
@@ -76,7 +76,7 @@ func TestMergeForciblyStop(t *testing.T) {
|
||||
ch := make(chan struct{})
|
||||
var itemsMerged uint64
|
||||
close(ch)
|
||||
if err := mergeBlockStreams(&dstIP.ph, &bsw, bsrs, ch, &itemsMerged); err != errForciblyStopped {
|
||||
if err := mergeBlockStreams(&dstIP.ph, &bsw, bsrs, nil, ch, &itemsMerged); err != errForciblyStopped {
|
||||
t.Fatalf("unexpected error during merge: got %v; want %v", err, errForciblyStopped)
|
||||
}
|
||||
if itemsMerged != 0 {
|
||||
@@ -120,7 +120,7 @@ func testMergeBlockStreamsSerial(blocksToMerge, maxItemsPerBlock int) error {
|
||||
var dstIP inmemoryPart
|
||||
var bsw blockStreamWriter
|
||||
bsw.InitFromInmemoryPart(&dstIP, 0)
|
||||
if err := mergeBlockStreams(&dstIP.ph, &bsw, bsrs, nil, &itemsMerged); err != nil {
|
||||
if err := mergeBlockStreams(&dstIP.ph, &bsw, bsrs, nil, nil, &itemsMerged); err != nil {
|
||||
return fmt.Errorf("cannot merge block streams: %s", err)
|
||||
}
|
||||
if itemsMerged != uint64(len(items)) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/filestream"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
|
||||
@@ -13,7 +14,7 @@ import (
|
||||
|
||||
func getMaxCachedIndexBlocksPerPart() int {
|
||||
maxCachedIndexBlocksPerPartOnce.Do(func() {
|
||||
n := memory.Allowed() / 1024 / 1024 / 2
|
||||
n := memory.Allowed() / 1024 / 1024 / 4
|
||||
if n == 0 {
|
||||
n = 10
|
||||
}
|
||||
@@ -29,7 +30,7 @@ var (
|
||||
|
||||
func getMaxCachedInmemoryBlocksPerPart() int {
|
||||
maxCachedInmemoryBlocksPerPartOnce.Do(func() {
|
||||
n := memory.Allowed() / 1024 / 1024 / 2
|
||||
n := memory.Allowed() / 1024 / 1024 / 4
|
||||
if n == 0 {
|
||||
n = 10
|
||||
}
|
||||
@@ -43,7 +44,7 @@ var (
|
||||
maxCachedInmemoryBlocksPerPartOnce sync.Once
|
||||
)
|
||||
|
||||
type part struct {
|
||||
type partInternals struct {
|
||||
ph partHeader
|
||||
|
||||
path string
|
||||
@@ -55,7 +56,14 @@ type part struct {
|
||||
indexFile fs.ReadAtCloser
|
||||
itemsFile fs.ReadAtCloser
|
||||
lensFile fs.ReadAtCloser
|
||||
}
|
||||
|
||||
type part struct {
|
||||
partInternals
|
||||
|
||||
// Align atomic counters inside caches by 8 bytes on 32-bit architectures.
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/212 .
|
||||
_ [(8 - (unsafe.Sizeof(partInternals{}) % 8)) % 8]byte
|
||||
idxbCache indexBlockCache
|
||||
ibCache inmemoryBlockCache
|
||||
}
|
||||
@@ -114,15 +122,15 @@ func newPart(ph *partHeader, path string, size uint64, metaindexReader filestrea
|
||||
}
|
||||
metaindexReader.MustClose()
|
||||
|
||||
p := &part{
|
||||
path: path,
|
||||
size: size,
|
||||
mrs: mrs,
|
||||
var p part
|
||||
p.path = path
|
||||
p.size = size
|
||||
p.mrs = mrs
|
||||
|
||||
p.indexFile = indexFile
|
||||
p.itemsFile = itemsFile
|
||||
p.lensFile = lensFile
|
||||
|
||||
indexFile: indexFile,
|
||||
itemsFile: itemsFile,
|
||||
lensFile: lensFile,
|
||||
}
|
||||
p.ph.CopyFrom(ph)
|
||||
p.idxbCache.Init()
|
||||
p.ibCache.Init()
|
||||
@@ -133,7 +141,7 @@ func newPart(ph *partHeader, path string, size uint64, metaindexReader filestrea
|
||||
p.MustClose()
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
func (p *part) MustClose() {
|
||||
@@ -165,12 +173,15 @@ func putIndexBlock(idxb *indexBlock) {
|
||||
var indexBlockPool sync.Pool
|
||||
|
||||
type indexBlockCache struct {
|
||||
// Atomically updated counters must go first in the struct, so they are properly
|
||||
// aligned to 8 bytes on 32-bit architectures.
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/212
|
||||
requests uint64
|
||||
misses uint64
|
||||
|
||||
m map[uint64]*indexBlock
|
||||
missesMap map[uint64]uint64
|
||||
mu sync.RWMutex
|
||||
|
||||
requests uint64
|
||||
misses uint64
|
||||
}
|
||||
|
||||
func (idxbc *indexBlockCache) Init() {
|
||||
@@ -274,12 +285,15 @@ func (idxbc *indexBlockCache) Misses() uint64 {
|
||||
}
|
||||
|
||||
type inmemoryBlockCache struct {
|
||||
// Atomically updated counters must go first in the struct, so they are properly
|
||||
// aligned to 8 bytes on 32-bit architectures.
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/212
|
||||
requests uint64
|
||||
misses uint64
|
||||
|
||||
m map[inmemoryBlockCacheKey]*inmemoryBlock
|
||||
missesMap map[inmemoryBlockCacheKey]uint64
|
||||
mu sync.RWMutex
|
||||
|
||||
requests uint64
|
||||
misses uint64
|
||||
}
|
||||
|
||||
type inmemoryBlockCacheKey struct {
|
||||
|
||||
@@ -31,6 +31,8 @@ type partSearch struct {
|
||||
// Pointer to inmemory block, which may be reused.
|
||||
inmemoryBlockReuse *inmemoryBlock
|
||||
|
||||
shouldCacheBlock func(item []byte) bool
|
||||
|
||||
idxbCache *indexBlockCache
|
||||
ibCache *inmemoryBlockCache
|
||||
|
||||
@@ -59,6 +61,7 @@ func (ps *partSearch) reset() {
|
||||
putInmemoryBlock(ps.inmemoryBlockReuse)
|
||||
ps.inmemoryBlockReuse = nil
|
||||
}
|
||||
ps.shouldCacheBlock = nil
|
||||
ps.idxbCache = nil
|
||||
ps.ibCache = nil
|
||||
ps.err = nil
|
||||
@@ -75,7 +78,7 @@ func (ps *partSearch) reset() {
|
||||
// Init initializes ps for search in the p.
|
||||
//
|
||||
// Use Seek for search in p.
|
||||
func (ps *partSearch) Init(p *part) {
|
||||
func (ps *partSearch) Init(p *part, shouldCacheBlock func(item []byte) bool) {
|
||||
ps.reset()
|
||||
|
||||
ps.p = p
|
||||
@@ -324,6 +327,16 @@ func (ps *partSearch) readIndexBlock(mr *metaindexRow) (*indexBlock, error) {
|
||||
}
|
||||
|
||||
func (ps *partSearch) getInmemoryBlock(bh *blockHeader) (*inmemoryBlock, bool, error) {
|
||||
if ps.shouldCacheBlock != nil {
|
||||
if !ps.shouldCacheBlock(bh.firstItem) {
|
||||
ib, err := ps.readInmemoryBlock(bh)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return ib, true, nil
|
||||
}
|
||||
}
|
||||
|
||||
var ibKey inmemoryBlockCacheKey
|
||||
ibKey.Init(bh)
|
||||
ib := ps.ibCache.Get(ibKey)
|
||||
@@ -371,7 +384,7 @@ func binarySearchKey(items [][]byte, key []byte) int {
|
||||
i, j := uint(0), n
|
||||
for i < j {
|
||||
h := uint(i+j) >> 1
|
||||
if string(key) > string(items[h]) {
|
||||
if h >= 0 && h < uint(len(items)) && string(key) > string(items[h]) {
|
||||
i = h + 1
|
||||
} else {
|
||||
j = h
|
||||
|
||||
@@ -51,7 +51,7 @@ func testPartSearchConcurrent(p *part, items []string) error {
|
||||
func testPartSearchSerial(p *part, items []string) error {
|
||||
var ps partSearch
|
||||
|
||||
ps.Init(p)
|
||||
ps.Init(p, nil)
|
||||
var k []byte
|
||||
|
||||
// Search for the item smaller than the items[0]
|
||||
@@ -150,7 +150,7 @@ func newTestPart(blocksCount, maxItemsPerBlock int) (*part, []string, error) {
|
||||
var ip inmemoryPart
|
||||
var bsw blockStreamWriter
|
||||
bsw.InitFromInmemoryPart(&ip, 0)
|
||||
if err := mergeBlockStreams(&ip.ph, &bsw, bsrs, nil, &itemsMerged); err != nil {
|
||||
if err := mergeBlockStreams(&ip.ph, &bsw, bsrs, nil, nil, &itemsMerged); err != nil {
|
||||
return nil, nil, fmt.Errorf("cannot merge blocks: %s", err)
|
||||
}
|
||||
if itemsMerged != uint64(len(items)) {
|
||||
|
||||
@@ -70,10 +70,23 @@ const rawItemsFlushInterval = time.Second
|
||||
|
||||
// Table represents mergeset table.
|
||||
type Table struct {
|
||||
// Atomically updated counters must go first in the struct, so they are properly
|
||||
// aligned to 8 bytes on 32-bit architectures.
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/212
|
||||
|
||||
activeMerges uint64
|
||||
mergesCount uint64
|
||||
itemsMerged uint64
|
||||
assistedMerges uint64
|
||||
|
||||
mergeIdx uint64
|
||||
|
||||
path string
|
||||
|
||||
flushCallback func()
|
||||
|
||||
prepareBlock PrepareBlockCallback
|
||||
|
||||
partsLock sync.Mutex
|
||||
parts []*partWrapper
|
||||
|
||||
@@ -81,8 +94,6 @@ type Table struct {
|
||||
rawItemsLock sync.Mutex
|
||||
rawItemsLastFlushTime time.Time
|
||||
|
||||
mergeIdx uint64
|
||||
|
||||
snapshotLock sync.RWMutex
|
||||
|
||||
flockF *os.File
|
||||
@@ -94,13 +105,10 @@ type Table struct {
|
||||
|
||||
rawItemsFlusherWG sync.WaitGroup
|
||||
|
||||
convertersWG sync.WaitGroup
|
||||
|
||||
// Use syncwg instead of sync, since Add/Wait may be called from concurrent goroutines.
|
||||
rawItemsPendingFlushesWG syncwg.WaitGroup
|
||||
|
||||
activeMerges uint64
|
||||
mergesCount uint64
|
||||
itemsMerged uint64
|
||||
assistedMerges uint64
|
||||
}
|
||||
|
||||
type partWrapper struct {
|
||||
@@ -139,8 +147,11 @@ func (pw *partWrapper) decRef() {
|
||||
// Optional flushCallback is called every time new data batch is flushed
|
||||
// to the underlying storage and becomes visible to search.
|
||||
//
|
||||
// Optional prepareBlock is called during merge before flushing the prepared block
|
||||
// to persistent storage.
|
||||
//
|
||||
// The table is created if it doesn't exist yet.
|
||||
func OpenTable(path string, flushCallback func()) (*Table, error) {
|
||||
func OpenTable(path string, flushCallback func(), prepareBlock PrepareBlockCallback) (*Table, error) {
|
||||
path = filepath.Clean(path)
|
||||
logger.Infof("opening table %q...", path)
|
||||
startTime := time.Now()
|
||||
@@ -165,6 +176,7 @@ func OpenTable(path string, flushCallback func()) (*Table, error) {
|
||||
tb := &Table{
|
||||
path: path,
|
||||
flushCallback: flushCallback,
|
||||
prepareBlock: prepareBlock,
|
||||
parts: pws,
|
||||
mergeIdx: uint64(time.Now().UnixNano()),
|
||||
flockF: flockF,
|
||||
@@ -178,6 +190,12 @@ func OpenTable(path string, flushCallback func()) (*Table, error) {
|
||||
logger.Infof("table %q has been opened in %s; partsCount: %d; blocksCount: %d, itemsCount: %d; sizeBytes: %d",
|
||||
path, time.Since(startTime), m.PartsCount, m.BlocksCount, m.ItemsCount, m.SizeBytes)
|
||||
|
||||
tb.convertersWG.Add(1)
|
||||
go func() {
|
||||
tb.convertToV1280()
|
||||
tb.convertersWG.Done()
|
||||
}()
|
||||
|
||||
return tb, nil
|
||||
}
|
||||
|
||||
@@ -190,6 +208,11 @@ func (tb *Table) MustClose() {
|
||||
tb.rawItemsFlusherWG.Wait()
|
||||
logger.Infof("raw items flusher stopped in %s on %q", time.Since(startTime), tb.path)
|
||||
|
||||
logger.Infof("waiting for converters to stop on %q...", tb.path)
|
||||
startTime = time.Now()
|
||||
tb.convertersWG.Wait()
|
||||
logger.Infof("converters stopped in %s on %q", time.Since(startTime), tb.path)
|
||||
|
||||
logger.Infof("waiting for part mergers to stop on %q...", tb.path)
|
||||
startTime = time.Now()
|
||||
tb.partMergersWG.Wait()
|
||||
@@ -216,7 +239,7 @@ func (tb *Table) MustClose() {
|
||||
}
|
||||
tb.partsLock.Unlock()
|
||||
|
||||
if err := tb.mergePartsOptimal(pws); err != nil {
|
||||
if err := tb.mergePartsOptimal(pws, nil); err != nil {
|
||||
logger.Panicf("FATAL: cannot flush inmemory parts to files in %q: %s", tb.path, err)
|
||||
}
|
||||
logger.Infof("%d inmemory parts have been flushed to files in %s on %q", len(pws), time.Since(startTime), tb.path)
|
||||
@@ -393,15 +416,67 @@ func (tb *Table) rawItemsFlusher() {
|
||||
}
|
||||
}
|
||||
|
||||
func (tb *Table) mergePartsOptimal(pws []*partWrapper) error {
|
||||
const convertToV1280FileName = "converted-to-v1.28.0"
|
||||
|
||||
func (tb *Table) convertToV1280() {
|
||||
// Convert tag->metricID rows into tag->metricIDs rows when upgrading to v1.28.0+.
|
||||
flagFilePath := tb.path + "/" + convertToV1280FileName
|
||||
if fs.IsPathExist(flagFilePath) {
|
||||
// The conversion has been already performed.
|
||||
return
|
||||
}
|
||||
|
||||
getAllPartsForMerge := func() []*partWrapper {
|
||||
var pws []*partWrapper
|
||||
tb.partsLock.Lock()
|
||||
for _, pw := range tb.parts {
|
||||
if pw.isInMerge {
|
||||
continue
|
||||
}
|
||||
pw.isInMerge = true
|
||||
pws = append(pws, pw)
|
||||
}
|
||||
tb.partsLock.Unlock()
|
||||
return pws
|
||||
}
|
||||
pws := getAllPartsForMerge()
|
||||
if len(pws) > 0 {
|
||||
logger.Infof("started round 1 of background conversion of %q to v1.28.0 format; merge %d parts", tb.path, len(pws))
|
||||
startTime := time.Now()
|
||||
if err := tb.mergePartsOptimal(pws, tb.stopCh); err != nil {
|
||||
logger.Errorf("failed round 1 of background conversion of %q to v1.28.0 format: %s", tb.path, err)
|
||||
return
|
||||
}
|
||||
logger.Infof("finished round 1 of background conversion of %q to v1.28.0 format in %s", tb.path, time.Since(startTime))
|
||||
|
||||
// The second round is needed in order to merge small blocks
|
||||
// with tag->metricIDs rows left after the first round.
|
||||
pws = getAllPartsForMerge()
|
||||
logger.Infof("started round 2 of background conversion of %q to v1.28.0 format; merge %d parts", tb.path, len(pws))
|
||||
startTime = time.Now()
|
||||
if len(pws) > 0 {
|
||||
if err := tb.mergePartsOptimal(pws, tb.stopCh); err != nil {
|
||||
logger.Errorf("failed round 2 of background conversion of %q to v1.28.0 format: %s", tb.path, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
logger.Infof("finished round 2 of background conversion of %q to v1.28.0 format in %s", tb.path, time.Since(startTime))
|
||||
}
|
||||
|
||||
if err := fs.WriteFileAtomically(flagFilePath, []byte("ok")); err != nil {
|
||||
logger.Panicf("FATAL: cannot create %q: %s", flagFilePath, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (tb *Table) mergePartsOptimal(pws []*partWrapper, stopCh <-chan struct{}) error {
|
||||
for len(pws) > defaultPartsToMerge {
|
||||
if err := tb.mergeParts(pws[:defaultPartsToMerge], nil, false); err != nil {
|
||||
if err := tb.mergeParts(pws[:defaultPartsToMerge], stopCh, false); err != nil {
|
||||
return fmt.Errorf("cannot merge %d parts: %s", defaultPartsToMerge, err)
|
||||
}
|
||||
pws = pws[defaultPartsToMerge:]
|
||||
}
|
||||
if len(pws) > 0 {
|
||||
if err := tb.mergeParts(pws, nil, false); err != nil {
|
||||
if err := tb.mergeParts(pws, stopCh, false); err != nil {
|
||||
return fmt.Errorf("cannot merge %d parts: %s", len(pws), err)
|
||||
}
|
||||
}
|
||||
@@ -477,7 +552,7 @@ func (tb *Table) mergeRawItemsBlocks(blocksToMerge []*inmemoryBlock) {
|
||||
}
|
||||
|
||||
// The added part exceeds maxParts count. Assist with merging other parts.
|
||||
err := tb.mergeSmallParts(false)
|
||||
err := tb.mergeExistingParts(false)
|
||||
if err == nil {
|
||||
atomic.AddUint64(&tb.assistedMerges, 1)
|
||||
continue
|
||||
@@ -541,7 +616,7 @@ func (tb *Table) mergeInmemoryBlocks(blocksToMerge []*inmemoryBlock) *partWrappe
|
||||
// Merge parts.
|
||||
// The merge shouldn't be interrupted by stopCh,
|
||||
// since it may be final after stopCh is closed.
|
||||
if err := mergeBlockStreams(&mpDst.ph, bsw, bsrs, nil, &tb.itemsMerged); err != nil {
|
||||
if err := mergeBlockStreams(&mpDst.ph, bsw, bsrs, tb.prepareBlock, nil, &tb.itemsMerged); err != nil {
|
||||
logger.Panicf("FATAL: cannot merge inmemoryBlocks: %s", err)
|
||||
}
|
||||
putBlockStreamWriter(bsw)
|
||||
@@ -558,7 +633,7 @@ func (tb *Table) mergeInmemoryBlocks(blocksToMerge []*inmemoryBlock) *partWrappe
|
||||
}
|
||||
|
||||
func (tb *Table) startPartMergers() {
|
||||
for i := 0; i < mergeWorkers; i++ {
|
||||
for i := 0; i < mergeWorkersCount; i++ {
|
||||
tb.partMergersWG.Add(1)
|
||||
go func() {
|
||||
if err := tb.partMerger(); err != nil {
|
||||
@@ -569,7 +644,7 @@ func (tb *Table) startPartMergers() {
|
||||
}
|
||||
}
|
||||
|
||||
func (tb *Table) mergeSmallParts(isFinal bool) error {
|
||||
func (tb *Table) mergeExistingParts(isFinal bool) error {
|
||||
maxItems := tb.maxOutPartItems()
|
||||
if maxItems > maxItemsPerPart {
|
||||
maxItems = maxItemsPerPart
|
||||
@@ -593,7 +668,7 @@ func (tb *Table) partMerger() error {
|
||||
isFinal := false
|
||||
t := time.NewTimer(sleepTime)
|
||||
for {
|
||||
err := tb.mergeSmallParts(isFinal)
|
||||
err := tb.mergeExistingParts(isFinal)
|
||||
if err == nil {
|
||||
// Try merging additional parts.
|
||||
sleepTime = minMergeSleepTime
|
||||
@@ -608,7 +683,7 @@ func (tb *Table) partMerger() error {
|
||||
if err != errNothingToMerge {
|
||||
return err
|
||||
}
|
||||
if time.Since(lastMergeTime) > 10*time.Second {
|
||||
if time.Since(lastMergeTime) > 30*time.Second {
|
||||
// We have free time for merging into bigger parts.
|
||||
// This should improve select performance.
|
||||
lastMergeTime = time.Now()
|
||||
@@ -700,7 +775,7 @@ func (tb *Table) mergeParts(pws []*partWrapper, stopCh <-chan struct{}, isOuterP
|
||||
|
||||
// Merge parts into a temporary location.
|
||||
var ph partHeader
|
||||
err := mergeBlockStreams(&ph, bsw, bsrs, stopCh, &tb.itemsMerged)
|
||||
err := mergeBlockStreams(&ph, bsw, bsrs, tb.prepareBlock, stopCh, &tb.itemsMerged)
|
||||
putBlockStreamWriter(bsw)
|
||||
if err != nil {
|
||||
if err == errForciblyStopped {
|
||||
@@ -826,17 +901,20 @@ func (tb *Table) maxOutPartItemsSlow() uint64 {
|
||||
|
||||
// Calculate the maximum number of items in the output merge part
|
||||
// by dividing the freeSpace by 4 and by the number of concurrent
|
||||
// mergeWorkers.
|
||||
// mergeWorkersCount.
|
||||
// This assumes each item is compressed into 4 bytes.
|
||||
return freeSpace / uint64(mergeWorkers) / 4
|
||||
return freeSpace / uint64(mergeWorkersCount) / 4
|
||||
}
|
||||
|
||||
var mergeWorkers = func() int {
|
||||
var mergeWorkersCount = func() int {
|
||||
return runtime.GOMAXPROCS(-1)
|
||||
}()
|
||||
|
||||
func openParts(path string) ([]*partWrapper, error) {
|
||||
// Verify that the directory for the parts exists.
|
||||
// The path can be missing after restoring from backup, so create it if needed.
|
||||
if err := fs.MkdirAllIfNotExist(path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot open difrectory: %s", err)
|
||||
@@ -950,11 +1028,20 @@ func (tb *Table) CreateSnapshotAt(dstDir string) error {
|
||||
return fmt.Errorf("cannot read directory: %s", err)
|
||||
}
|
||||
for _, fi := range fis {
|
||||
fn := fi.Name()
|
||||
if !fs.IsDirOrSymlink(fi) {
|
||||
// Skip non-directories.
|
||||
switch fn {
|
||||
case convertToV1280FileName:
|
||||
srcPath := srcDir + "/" + fn
|
||||
dstPath := dstDir + "/" + fn
|
||||
if err := os.Link(srcPath, dstPath); err != nil {
|
||||
return fmt.Errorf("cannot hard link from %q to %q: %s", srcPath, dstPath, err)
|
||||
}
|
||||
default:
|
||||
// Skip other non-directories.
|
||||
}
|
||||
continue
|
||||
}
|
||||
fn := fi.Name()
|
||||
if isSpecialDir(fn) {
|
||||
// Skip special dirs.
|
||||
continue
|
||||
@@ -1162,30 +1249,31 @@ func appendPartsToMerge(dst, src []*partWrapper, maxPartsToMerge int, maxItems u
|
||||
for i := 2; i <= n; i++ {
|
||||
for j := 0; j <= len(src)-i; j++ {
|
||||
itemsSum := uint64(0)
|
||||
for _, pw := range src[j : j+i] {
|
||||
a := src[j : j+i]
|
||||
for _, pw := range a {
|
||||
itemsSum += pw.p.ph.itemsCount
|
||||
}
|
||||
if itemsSum > maxItems {
|
||||
continue
|
||||
// There is no sense in checking the remaining bigger parts.
|
||||
break
|
||||
}
|
||||
m := float64(itemsSum) / float64(src[j+i-1].p.ph.itemsCount)
|
||||
m := float64(itemsSum) / float64(a[len(a)-1].p.ph.itemsCount)
|
||||
if m < maxM {
|
||||
continue
|
||||
}
|
||||
maxM = m
|
||||
pws = src[j : j+i]
|
||||
pws = a
|
||||
}
|
||||
}
|
||||
|
||||
minM := float64(maxPartsToMerge / 2)
|
||||
if minM < 2 {
|
||||
minM = 2
|
||||
minM := float64(maxPartsToMerge) / 2
|
||||
if minM < 1.7 {
|
||||
minM = 1.7
|
||||
}
|
||||
if maxM < minM {
|
||||
// There is no sense in merging parts with too small m.
|
||||
return dst
|
||||
}
|
||||
|
||||
return append(dst, pws...)
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ func (ts *TableSearch) reset() {
|
||||
// Init initializes ts for searching in the tb.
|
||||
//
|
||||
// MustClose must be called when the ts is no longer needed.
|
||||
func (ts *TableSearch) Init(tb *Table) {
|
||||
func (ts *TableSearch) Init(tb *Table, shouldCacheBlock func(item []byte) bool) {
|
||||
if ts.needClosing {
|
||||
logger.Panicf("BUG: missing MustClose call before the next call to Init")
|
||||
}
|
||||
@@ -76,7 +76,7 @@ func (ts *TableSearch) Init(tb *Table) {
|
||||
}
|
||||
ts.psPool = ts.psPool[:len(ts.pws)]
|
||||
for i, pw := range ts.pws {
|
||||
ts.psPool[i].Init(pw.p)
|
||||
ts.psPool[i].Init(pw.p, shouldCacheBlock)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ func TestTableSearchSerial(t *testing.T) {
|
||||
|
||||
func() {
|
||||
// Re-open the table and verify the search works.
|
||||
tb, err := OpenTable(path, nil)
|
||||
tb, err := OpenTable(path, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot open table: %s", err)
|
||||
}
|
||||
@@ -75,7 +75,7 @@ func TestTableSearchConcurrent(t *testing.T) {
|
||||
|
||||
// Re-open the table and verify the search works.
|
||||
func() {
|
||||
tb, err := OpenTable(path, nil)
|
||||
tb, err := OpenTable(path, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot open table: %s", err)
|
||||
}
|
||||
@@ -109,7 +109,7 @@ func testTableSearchConcurrent(tb *Table, items []string) error {
|
||||
|
||||
func testTableSearchSerial(tb *Table, items []string) error {
|
||||
var ts TableSearch
|
||||
ts.Init(tb)
|
||||
ts.Init(tb, nil)
|
||||
for _, key := range []string{
|
||||
"",
|
||||
"123",
|
||||
@@ -151,7 +151,7 @@ func newTestTable(path string, itemsCount int) (*Table, []string, error) {
|
||||
flushCallback := func() {
|
||||
atomic.AddUint64(&flushes, 1)
|
||||
}
|
||||
tb, err := OpenTable(path, flushCallback)
|
||||
tb, err := OpenTable(path, flushCallback, nil)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("cannot open table: %s", err)
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ func benchmarkTableSearch(b *testing.B, itemsCount int) {
|
||||
|
||||
// Force finishing pending merges
|
||||
tb.MustClose()
|
||||
tb, err = OpenTable(path, nil)
|
||||
tb, err = OpenTable(path, nil, nil)
|
||||
if err != nil {
|
||||
b.Fatalf("unexpected error when re-opening table %q: %s", path, err)
|
||||
}
|
||||
@@ -81,7 +81,7 @@ func benchmarkTableSearchKeysExt(b *testing.B, tb *Table, keys [][]byte, stripSu
|
||||
b.SetBytes(int64(searchKeysCount * rowsToScan))
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var ts TableSearch
|
||||
ts.Init(tb)
|
||||
ts.Init(tb, nil)
|
||||
defer ts.MustClose()
|
||||
for pb.Next() {
|
||||
startIdx := rand.Intn(len(keys) - searchKeysCount)
|
||||
|
||||
@@ -21,7 +21,7 @@ func TestTableOpenClose(t *testing.T) {
|
||||
}()
|
||||
|
||||
// Create a new table
|
||||
tb, err := OpenTable(path, nil)
|
||||
tb, err := OpenTable(path, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot create new table: %s", err)
|
||||
}
|
||||
@@ -31,7 +31,7 @@ func TestTableOpenClose(t *testing.T) {
|
||||
|
||||
// Re-open created table multiple times.
|
||||
for i := 0; i < 10; i++ {
|
||||
tb, err := OpenTable(path, nil)
|
||||
tb, err := OpenTable(path, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot open created table: %s", err)
|
||||
}
|
||||
@@ -45,14 +45,14 @@ func TestTableOpenMultipleTimes(t *testing.T) {
|
||||
_ = os.RemoveAll(path)
|
||||
}()
|
||||
|
||||
tb1, err := OpenTable(path, nil)
|
||||
tb1, err := OpenTable(path, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot open table: %s", err)
|
||||
}
|
||||
defer tb1.MustClose()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
tb2, err := OpenTable(path, nil)
|
||||
tb2, err := OpenTable(path, nil, nil)
|
||||
if err == nil {
|
||||
tb2.MustClose()
|
||||
t.Fatalf("expecting non-nil error when opening already opened table")
|
||||
@@ -73,7 +73,7 @@ func TestTableAddItemSerial(t *testing.T) {
|
||||
flushCallback := func() {
|
||||
atomic.AddUint64(&flushes, 1)
|
||||
}
|
||||
tb, err := OpenTable(path, flushCallback)
|
||||
tb, err := OpenTable(path, flushCallback, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot open %q: %s", path, err)
|
||||
}
|
||||
@@ -99,7 +99,7 @@ func TestTableAddItemSerial(t *testing.T) {
|
||||
testReopenTable(t, path, itemsCount)
|
||||
|
||||
// Add more items in order to verify merge between inmemory parts and file-based parts.
|
||||
tb, err = OpenTable(path, nil)
|
||||
tb, err = OpenTable(path, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot open %q: %s", path, err)
|
||||
}
|
||||
@@ -132,7 +132,7 @@ func TestTableCreateSnapshotAt(t *testing.T) {
|
||||
_ = os.RemoveAll(path)
|
||||
}()
|
||||
|
||||
tb, err := OpenTable(path, nil)
|
||||
tb, err := OpenTable(path, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot open %q: %s", path, err)
|
||||
}
|
||||
@@ -163,23 +163,23 @@ func TestTableCreateSnapshotAt(t *testing.T) {
|
||||
}()
|
||||
|
||||
// Verify snapshots contain all the data.
|
||||
tb1, err := OpenTable(snapshot1, nil)
|
||||
tb1, err := OpenTable(snapshot1, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot open %q: %s", path, err)
|
||||
}
|
||||
defer tb1.MustClose()
|
||||
|
||||
tb2, err := OpenTable(snapshot2, nil)
|
||||
tb2, err := OpenTable(snapshot2, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot open %q: %s", path, err)
|
||||
}
|
||||
defer tb2.MustClose()
|
||||
|
||||
var ts, ts1, ts2 TableSearch
|
||||
ts.Init(tb)
|
||||
ts1.Init(tb1)
|
||||
ts.Init(tb, nil)
|
||||
ts1.Init(tb1, nil)
|
||||
defer ts1.MustClose()
|
||||
ts2.Init(tb2)
|
||||
ts2.Init(tb2, nil)
|
||||
defer ts2.MustClose()
|
||||
for i := 0; i < itemsCount; i++ {
|
||||
key := []byte(fmt.Sprintf("item %d", i))
|
||||
@@ -217,7 +217,12 @@ func TestTableAddItemsConcurrent(t *testing.T) {
|
||||
flushCallback := func() {
|
||||
atomic.AddUint64(&flushes, 1)
|
||||
}
|
||||
tb, err := OpenTable(path, flushCallback)
|
||||
var itemsMerged uint64
|
||||
prepareBlock := func(data []byte, items [][]byte) ([]byte, [][]byte) {
|
||||
atomic.AddUint64(&itemsMerged, uint64(len(items)))
|
||||
return data, items
|
||||
}
|
||||
tb, err := OpenTable(path, flushCallback, prepareBlock)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot open %q: %s", path, err)
|
||||
}
|
||||
@@ -230,6 +235,10 @@ func TestTableAddItemsConcurrent(t *testing.T) {
|
||||
if atomic.LoadUint64(&flushes) == 0 {
|
||||
t.Fatalf("unexpected zero flushes")
|
||||
}
|
||||
n := atomic.LoadUint64(&itemsMerged)
|
||||
if n < itemsCount {
|
||||
t.Fatalf("too low number of items merged; got %v; must be at least %v", n, itemsCount)
|
||||
}
|
||||
|
||||
var m TableMetrics
|
||||
tb.UpdateMetrics(&m)
|
||||
@@ -243,7 +252,7 @@ func TestTableAddItemsConcurrent(t *testing.T) {
|
||||
testReopenTable(t, path, itemsCount)
|
||||
|
||||
// Add more items in order to verify merge between inmemory parts and file-based parts.
|
||||
tb, err = OpenTable(path, nil)
|
||||
tb, err = OpenTable(path, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot open %q: %s", path, err)
|
||||
}
|
||||
@@ -285,7 +294,7 @@ func testReopenTable(t *testing.T, path string, itemsCount int) {
|
||||
t.Helper()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
tb, err := OpenTable(path, nil)
|
||||
tb, err := OpenTable(path, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot re-open %q: %s", path, err)
|
||||
}
|
||||
|
||||
@@ -43,6 +43,11 @@ func (cm *connMetrics) init(group, name, addr string) {
|
||||
}
|
||||
|
||||
type statConn struct {
|
||||
// Move atomic counters to the top of struct in order to properly align them on 32-bit arch.
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/212
|
||||
|
||||
closeCalls uint64
|
||||
|
||||
readTimeout time.Duration
|
||||
lastReadTime time.Time
|
||||
|
||||
@@ -52,8 +57,6 @@ type statConn struct {
|
||||
net.Conn
|
||||
|
||||
cm *connMetrics
|
||||
|
||||
closeCalls uint64
|
||||
}
|
||||
|
||||
func (sc *statConn) Read(p []byte) (int, error) {
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
The compiled protobufs are version controlled and you won't normally need to
|
||||
re-compile them when building Prometheus.
|
||||
|
||||
If however you have modified the defs and do need to re-compile, run
|
||||
`./scripts/genproto.sh` from the parent dir.
|
||||
|
||||
In order for the script to run, you'll need `protoc` (version 3.5) in your
|
||||
PATH, and the following Go packages installed:
|
||||
|
||||
- github.com/gogo/protobuf
|
||||
- github.com/gogo/protobuf/protoc-gen-gogofast
|
||||
- github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/
|
||||
- github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
|
||||
- golang.org/x/tools/cmd/goimports
|
||||
@@ -25,9 +25,13 @@ type blockStreamReader struct {
|
||||
|
||||
ph partHeader
|
||||
|
||||
timestampsReader filestream.ReadCloser
|
||||
valuesReader filestream.ReadCloser
|
||||
indexReader filestream.ReadCloser
|
||||
// Use io.Reader type for timestampsReader and valuesReader
|
||||
// in order to remove I2I conversion in readBlock
|
||||
// when passing them to fs.ReadFullData
|
||||
timestampsReader io.Reader
|
||||
valuesReader io.Reader
|
||||
|
||||
indexReader filestream.ReadCloser
|
||||
|
||||
mrs []metaindexRow
|
||||
|
||||
@@ -56,6 +60,11 @@ type blockStreamReader struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (bsr *blockStreamReader) assertWriteClosers() {
|
||||
_ = bsr.timestampsReader.(filestream.ReadCloser)
|
||||
_ = bsr.valuesReader.(filestream.ReadCloser)
|
||||
}
|
||||
|
||||
func (bsr *blockStreamReader) reset() {
|
||||
bsr.Block.Reset()
|
||||
|
||||
@@ -108,6 +117,8 @@ func (bsr *blockStreamReader) InitFromInmemoryPart(mp *inmemoryPart) {
|
||||
if err != nil {
|
||||
logger.Panicf("BUG: cannot unmarshal metaindex rows from inmemoryPart: %s", err)
|
||||
}
|
||||
|
||||
bsr.assertWriteClosers()
|
||||
}
|
||||
|
||||
// InitFromFilePart initializes bsr from a file-based part on the given path.
|
||||
@@ -167,6 +178,8 @@ func (bsr *blockStreamReader) InitFromFilePart(path string) error {
|
||||
bsr.indexReader = indexFile
|
||||
bsr.mrs = mrs
|
||||
|
||||
bsr.assertWriteClosers()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -174,8 +187,8 @@ func (bsr *blockStreamReader) InitFromFilePart(path string) error {
|
||||
//
|
||||
// It closes *Reader files passed to Init.
|
||||
func (bsr *blockStreamReader) MustClose() {
|
||||
bsr.timestampsReader.MustClose()
|
||||
bsr.valuesReader.MustClose()
|
||||
bsr.timestampsReader.(filestream.ReadCloser).MustClose()
|
||||
bsr.valuesReader.(filestream.ReadCloser).MustClose()
|
||||
bsr.indexReader.MustClose()
|
||||
|
||||
bsr.reset()
|
||||
|
||||
@@ -59,7 +59,7 @@ func TestBlockStreamReaderManyTSIDManyRows(t *testing.T) {
|
||||
r.PrecisionBits = defaultPrecisionBits
|
||||
const blocks = 123
|
||||
for i := 0; i < 3210; i++ {
|
||||
r.TSID.MetricID = uint64((1e12 - i) % blocks)
|
||||
r.TSID.MetricID = uint64((1e9 - i) % blocks)
|
||||
r.Value = rand.Float64()
|
||||
r.Timestamp = int64(rand.Float64() * 1e9)
|
||||
rows = append(rows, r)
|
||||
@@ -73,7 +73,7 @@ func TestBlockStreamReaderReadConcurrent(t *testing.T) {
|
||||
r.PrecisionBits = defaultPrecisionBits
|
||||
const blocks = 123
|
||||
for i := 0; i < 3210; i++ {
|
||||
r.TSID.MetricID = uint64((1e12 - i) % blocks)
|
||||
r.TSID.MetricID = uint64((1e9 - i) % blocks)
|
||||
r.Value = rand.Float64()
|
||||
r.Timestamp = int64(rand.Float64() * 1e9)
|
||||
rows = append(rows, r)
|
||||
|
||||
@@ -2,6 +2,7 @@ package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -17,10 +18,14 @@ type blockStreamWriter struct {
|
||||
compressLevel int
|
||||
path string
|
||||
|
||||
timestampsWriter filestream.WriteCloser
|
||||
valuesWriter filestream.WriteCloser
|
||||
indexWriter filestream.WriteCloser
|
||||
metaindexWriter filestream.WriteCloser
|
||||
// Use io.Writer type for timestampsWriter and valuesWriter
|
||||
// in order to remove I2I conversion in WriteExternalBlock
|
||||
// when passing them to fs.MustWriteData
|
||||
timestampsWriter io.Writer
|
||||
valuesWriter io.Writer
|
||||
|
||||
indexWriter filestream.WriteCloser
|
||||
metaindexWriter filestream.WriteCloser
|
||||
|
||||
mr metaindexRow
|
||||
|
||||
@@ -35,6 +40,11 @@ type blockStreamWriter struct {
|
||||
compressedMetaindexData []byte
|
||||
}
|
||||
|
||||
func (bsw *blockStreamWriter) assertWriteClosers() {
|
||||
_ = bsw.timestampsWriter.(filestream.WriteCloser)
|
||||
_ = bsw.valuesWriter.(filestream.WriteCloser)
|
||||
}
|
||||
|
||||
// Init initializes bsw with the given writers.
|
||||
func (bsw *blockStreamWriter) reset() {
|
||||
bsw.compressLevel = 0
|
||||
@@ -67,6 +77,8 @@ func (bsw *blockStreamWriter) InitFromInmemoryPart(mp *inmemoryPart) {
|
||||
bsw.valuesWriter = &mp.valuesData
|
||||
bsw.indexWriter = &mp.indexData
|
||||
bsw.metaindexWriter = &mp.metaindexData
|
||||
|
||||
bsw.assertWriteClosers()
|
||||
}
|
||||
|
||||
// InitFromFilePart initializes bsw from a file-based part on the given path.
|
||||
@@ -126,6 +138,8 @@ func (bsw *blockStreamWriter) InitFromFilePart(path string, nocache bool, compre
|
||||
bsw.indexWriter = indexFile
|
||||
bsw.metaindexWriter = metaindexFile
|
||||
|
||||
bsw.assertWriteClosers()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -141,8 +155,8 @@ func (bsw *blockStreamWriter) MustClose() {
|
||||
fs.MustWriteData(bsw.metaindexWriter, bsw.compressedMetaindexData)
|
||||
|
||||
// Close writers.
|
||||
bsw.timestampsWriter.MustClose()
|
||||
bsw.valuesWriter.MustClose()
|
||||
bsw.timestampsWriter.(filestream.WriteCloser).MustClose()
|
||||
bsw.valuesWriter.(filestream.WriteCloser).MustClose()
|
||||
bsw.indexWriter.MustClose()
|
||||
bsw.metaindexWriter.MustClose()
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,9 +12,331 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/workingsetcache"
|
||||
)
|
||||
|
||||
func TestMergeTagToMetricIDsRows(t *testing.T) {
|
||||
f := func(items []string, expectedItems []string) {
|
||||
t.Helper()
|
||||
var data []byte
|
||||
var itemsB [][]byte
|
||||
for _, item := range items {
|
||||
data = append(data, item...)
|
||||
itemsB = append(itemsB, data[len(data)-len(item):])
|
||||
}
|
||||
if err := checkItemsSorted(itemsB); err != nil {
|
||||
t.Fatalf("source items aren't sorted: %s", err)
|
||||
}
|
||||
resultData, resultItemsB := mergeTagToMetricIDsRows(data, itemsB)
|
||||
if len(resultItemsB) != len(expectedItems) {
|
||||
t.Fatalf("unexpected len(resultItemsB); got %d; want %d", len(resultItemsB), len(expectedItems))
|
||||
}
|
||||
if err := checkItemsSorted(resultItemsB); err != nil {
|
||||
t.Fatalf("result items aren't sorted: %s", err)
|
||||
}
|
||||
for i, item := range resultItemsB {
|
||||
if !bytes.HasPrefix(resultData, item) {
|
||||
t.Fatalf("unexpected prefix for resultData #%d;\ngot\n%X\nwant\n%X", i, resultData, item)
|
||||
}
|
||||
resultData = resultData[len(item):]
|
||||
}
|
||||
if len(resultData) != 0 {
|
||||
t.Fatalf("unexpected tail left in resultData: %X", resultData)
|
||||
}
|
||||
var resultItems []string
|
||||
for _, item := range resultItemsB {
|
||||
resultItems = append(resultItems, string(item))
|
||||
}
|
||||
if !reflect.DeepEqual(expectedItems, resultItems) {
|
||||
t.Fatalf("unexpected items;\ngot\n%X\nwant\n%X", resultItems, expectedItems)
|
||||
}
|
||||
}
|
||||
x := func(key, value string, metricIDs []uint64) string {
|
||||
dst := marshalCommonPrefix(nil, nsPrefixTagToMetricIDs)
|
||||
t := &Tag{
|
||||
Key: []byte(key),
|
||||
Value: []byte(value),
|
||||
}
|
||||
dst = t.Marshal(dst)
|
||||
for _, metricID := range metricIDs {
|
||||
dst = encoding.MarshalUint64(dst, metricID)
|
||||
}
|
||||
return string(dst)
|
||||
}
|
||||
|
||||
f(nil, nil)
|
||||
f([]string{}, nil)
|
||||
f([]string{"foo"}, []string{"foo"})
|
||||
f([]string{"a", "b", "c", "def"}, []string{"a", "b", "c", "def"})
|
||||
f([]string{"\x00", "\x00b", "\x00c", "\x00def"}, []string{"\x00", "\x00b", "\x00c", "\x00def"})
|
||||
f([]string{
|
||||
x("", "", []uint64{0}),
|
||||
x("", "", []uint64{0}),
|
||||
x("", "", []uint64{0}),
|
||||
x("", "", []uint64{0}),
|
||||
}, []string{
|
||||
x("", "", []uint64{0}),
|
||||
x("", "", []uint64{0}),
|
||||
x("", "", []uint64{0}),
|
||||
})
|
||||
f([]string{
|
||||
x("", "", []uint64{0}),
|
||||
x("", "", []uint64{0}),
|
||||
x("", "", []uint64{0}),
|
||||
x("", "", []uint64{0}),
|
||||
"xyz",
|
||||
}, []string{
|
||||
x("", "", []uint64{0}),
|
||||
x("", "", []uint64{0}),
|
||||
"xyz",
|
||||
})
|
||||
f([]string{
|
||||
"\x00asdf",
|
||||
x("", "", []uint64{0}),
|
||||
x("", "", []uint64{0}),
|
||||
x("", "", []uint64{0}),
|
||||
x("", "", []uint64{0}),
|
||||
}, []string{
|
||||
"\x00asdf",
|
||||
x("", "", []uint64{0}),
|
||||
x("", "", []uint64{0}),
|
||||
})
|
||||
f([]string{
|
||||
"\x00asdf",
|
||||
x("", "", []uint64{0}),
|
||||
x("", "", []uint64{0}),
|
||||
x("", "", []uint64{0}),
|
||||
x("", "", []uint64{0}),
|
||||
"xyz",
|
||||
}, []string{
|
||||
"\x00asdf",
|
||||
x("", "", []uint64{0}),
|
||||
"xyz",
|
||||
})
|
||||
f([]string{
|
||||
"\x00asdf",
|
||||
x("", "", []uint64{1}),
|
||||
x("", "", []uint64{2}),
|
||||
x("", "", []uint64{3}),
|
||||
x("", "", []uint64{4}),
|
||||
"xyz",
|
||||
}, []string{
|
||||
"\x00asdf",
|
||||
x("", "", []uint64{1, 2, 3, 4}),
|
||||
"xyz",
|
||||
})
|
||||
f([]string{
|
||||
"\x00asdf",
|
||||
x("", "", []uint64{1}),
|
||||
x("", "", []uint64{2}),
|
||||
x("", "", []uint64{3}),
|
||||
x("", "", []uint64{4}),
|
||||
}, []string{
|
||||
"\x00asdf",
|
||||
x("", "", []uint64{1, 2, 3}),
|
||||
x("", "", []uint64{4}),
|
||||
})
|
||||
f([]string{
|
||||
"\x00asdf",
|
||||
x("", "", []uint64{1}),
|
||||
x("", "", []uint64{2, 3, 4}),
|
||||
x("", "", []uint64{2, 3, 4, 5}),
|
||||
x("", "", []uint64{3, 5}),
|
||||
"foo",
|
||||
}, []string{
|
||||
"\x00asdf",
|
||||
x("", "", []uint64{1, 2, 3, 4, 5}),
|
||||
"foo",
|
||||
})
|
||||
f([]string{
|
||||
"\x00asdf",
|
||||
x("", "", []uint64{1}),
|
||||
x("", "a", []uint64{2, 3, 4}),
|
||||
x("", "a", []uint64{2, 3, 4, 5}),
|
||||
x("", "b", []uint64{3, 5}),
|
||||
"foo",
|
||||
}, []string{
|
||||
"\x00asdf",
|
||||
x("", "", []uint64{1}),
|
||||
x("", "a", []uint64{2, 3, 4, 5}),
|
||||
x("", "b", []uint64{3, 5}),
|
||||
"foo",
|
||||
})
|
||||
f([]string{
|
||||
"\x00asdf",
|
||||
x("", "", []uint64{1}),
|
||||
x("x", "a", []uint64{2, 3, 4}),
|
||||
x("y", "", []uint64{2, 3, 4, 5}),
|
||||
x("y", "x", []uint64{3, 5}),
|
||||
"foo",
|
||||
}, []string{
|
||||
"\x00asdf",
|
||||
x("", "", []uint64{1}),
|
||||
x("x", "a", []uint64{2, 3, 4}),
|
||||
x("y", "", []uint64{2, 3, 4, 5}),
|
||||
x("y", "x", []uint64{3, 5}),
|
||||
"foo",
|
||||
})
|
||||
f([]string{
|
||||
"\x00asdf",
|
||||
x("sdf", "aa", []uint64{1, 1, 3}),
|
||||
x("sdf", "aa", []uint64{1, 2}),
|
||||
"foo",
|
||||
}, []string{
|
||||
"\x00asdf",
|
||||
x("sdf", "aa", []uint64{1, 2, 3}),
|
||||
"foo",
|
||||
})
|
||||
f([]string{
|
||||
"\x00asdf",
|
||||
x("sdf", "aa", []uint64{1, 2, 2, 4}),
|
||||
x("sdf", "aa", []uint64{1, 2, 3}),
|
||||
"foo",
|
||||
}, []string{
|
||||
"\x00asdf",
|
||||
x("sdf", "aa", []uint64{1, 2, 3, 4}),
|
||||
"foo",
|
||||
})
|
||||
|
||||
// Construct big source chunks
|
||||
var metricIDs []uint64
|
||||
|
||||
metricIDs = metricIDs[:0]
|
||||
for i := 0; i < maxMetricIDsPerRow-1; i++ {
|
||||
metricIDs = append(metricIDs, uint64(i))
|
||||
}
|
||||
f([]string{
|
||||
"\x00aa",
|
||||
x("foo", "bar", metricIDs),
|
||||
x("foo", "bar", metricIDs),
|
||||
"x",
|
||||
}, []string{
|
||||
"\x00aa",
|
||||
x("foo", "bar", metricIDs),
|
||||
"x",
|
||||
})
|
||||
|
||||
metricIDs = metricIDs[:0]
|
||||
for i := 0; i < maxMetricIDsPerRow; i++ {
|
||||
metricIDs = append(metricIDs, uint64(i))
|
||||
}
|
||||
f([]string{
|
||||
"\x00aa",
|
||||
x("foo", "bar", metricIDs),
|
||||
x("foo", "bar", metricIDs),
|
||||
"x",
|
||||
}, []string{
|
||||
"\x00aa",
|
||||
x("foo", "bar", metricIDs),
|
||||
x("foo", "bar", metricIDs),
|
||||
"x",
|
||||
})
|
||||
|
||||
metricIDs = metricIDs[:0]
|
||||
for i := 0; i < 3*maxMetricIDsPerRow; i++ {
|
||||
metricIDs = append(metricIDs, uint64(i))
|
||||
}
|
||||
f([]string{
|
||||
"\x00aa",
|
||||
x("foo", "bar", metricIDs),
|
||||
x("foo", "bar", metricIDs),
|
||||
"x",
|
||||
}, []string{
|
||||
"\x00aa",
|
||||
x("foo", "bar", metricIDs),
|
||||
x("foo", "bar", metricIDs),
|
||||
"x",
|
||||
})
|
||||
f([]string{
|
||||
"\x00aa",
|
||||
x("foo", "bar", []uint64{0, 0, 1, 2, 3}),
|
||||
x("foo", "bar", metricIDs),
|
||||
x("foo", "bar", metricIDs),
|
||||
"x",
|
||||
}, []string{
|
||||
"\x00aa",
|
||||
x("foo", "bar", []uint64{0, 1, 2, 3}),
|
||||
x("foo", "bar", metricIDs),
|
||||
x("foo", "bar", metricIDs),
|
||||
"x",
|
||||
})
|
||||
|
||||
// Check for duplicate metricIDs removal
|
||||
metricIDs = metricIDs[:0]
|
||||
for i := 0; i < maxMetricIDsPerRow-1; i++ {
|
||||
metricIDs = append(metricIDs, 123)
|
||||
}
|
||||
f([]string{
|
||||
"\x00aa",
|
||||
x("foo", "bar", metricIDs),
|
||||
x("foo", "bar", metricIDs),
|
||||
"x",
|
||||
}, []string{
|
||||
"\x00aa",
|
||||
x("foo", "bar", []uint64{123}),
|
||||
"x",
|
||||
})
|
||||
|
||||
// Check fallback to the original items after merging, which result in incorrect ordering.
|
||||
metricIDs = metricIDs[:0]
|
||||
for i := 0; i < maxMetricIDsPerRow-3; i++ {
|
||||
metricIDs = append(metricIDs, uint64(123))
|
||||
}
|
||||
f([]string{
|
||||
"\x00aa",
|
||||
x("foo", "bar", metricIDs),
|
||||
x("foo", "bar", []uint64{123, 123, 125}),
|
||||
x("foo", "bar", []uint64{123, 124}),
|
||||
"x",
|
||||
}, []string{
|
||||
"\x00aa",
|
||||
x("foo", "bar", metricIDs),
|
||||
x("foo", "bar", []uint64{123, 123, 125}),
|
||||
x("foo", "bar", []uint64{123, 124}),
|
||||
"x",
|
||||
})
|
||||
f([]string{
|
||||
"\x00aa",
|
||||
x("foo", "bar", metricIDs),
|
||||
x("foo", "bar", []uint64{123, 123, 125}),
|
||||
x("foo", "bar", []uint64{123, 124}),
|
||||
}, []string{
|
||||
"\x00aa",
|
||||
x("foo", "bar", metricIDs),
|
||||
x("foo", "bar", []uint64{123, 123, 125}),
|
||||
x("foo", "bar", []uint64{123, 124}),
|
||||
})
|
||||
f([]string{
|
||||
x("foo", "bar", metricIDs),
|
||||
x("foo", "bar", []uint64{123, 123, 125}),
|
||||
x("foo", "bar", []uint64{123, 124}),
|
||||
}, []string{
|
||||
x("foo", "bar", metricIDs),
|
||||
x("foo", "bar", []uint64{123, 123, 125}),
|
||||
x("foo", "bar", []uint64{123, 124}),
|
||||
})
|
||||
}
|
||||
|
||||
func TestRemoveDuplicateMetricIDs(t *testing.T) {
|
||||
f := func(metricIDs, expectedMetricIDs []uint64) {
|
||||
t.Helper()
|
||||
a := removeDuplicateMetricIDs(metricIDs)
|
||||
if !reflect.DeepEqual(a, expectedMetricIDs) {
|
||||
t.Fatalf("unexpected result from removeDuplicateMetricIDs:\ngot\n%d\nwant\n%d", a, expectedMetricIDs)
|
||||
}
|
||||
}
|
||||
f(nil, nil)
|
||||
f([]uint64{123}, []uint64{123})
|
||||
f([]uint64{123, 123}, []uint64{123})
|
||||
f([]uint64{123, 123, 123}, []uint64{123})
|
||||
f([]uint64{123, 1234, 1235}, []uint64{123, 1234, 1235})
|
||||
f([]uint64{0, 1, 1, 2}, []uint64{0, 1, 2})
|
||||
f([]uint64{0, 0, 0, 1, 1, 2}, []uint64{0, 1, 2})
|
||||
f([]uint64{0, 1, 1, 2, 2}, []uint64{0, 1, 2})
|
||||
f([]uint64{0, 1, 2, 2}, []uint64{0, 1, 2})
|
||||
}
|
||||
|
||||
func TestMarshalUnmarshalTSIDs(t *testing.T) {
|
||||
f := func(tsids []TSID) {
|
||||
t.Helper()
|
||||
@@ -280,6 +602,7 @@ func testIndexDBGetOrCreateTSIDByName(db *indexDB, accountsCount, projectsCount,
|
||||
is := db.getIndexSearch()
|
||||
defer db.putIndexSearch(is)
|
||||
|
||||
var metricNameBuf []byte
|
||||
for i := 0; i < 4e2+1; i++ {
|
||||
var mn MetricName
|
||||
|
||||
@@ -294,11 +617,11 @@ func testIndexDBGetOrCreateTSIDByName(db *indexDB, accountsCount, projectsCount,
|
||||
mn.AddTag(key, value)
|
||||
}
|
||||
mn.sortTags()
|
||||
metricName := mn.Marshal(nil)
|
||||
metricNameBuf = mn.Marshal(metricNameBuf[:0])
|
||||
|
||||
// Create tsid for the metricName.
|
||||
var tsid TSID
|
||||
if err := is.GetOrCreateTSIDByName(&tsid, metricName); err != nil {
|
||||
if err := is.GetOrCreateTSIDByName(&tsid, metricNameBuf); err != nil {
|
||||
return nil, nil, fmt.Errorf("unexpected error when creating tsid for mn:\n%s: %s", &mn, err)
|
||||
}
|
||||
|
||||
@@ -306,22 +629,22 @@ func testIndexDBGetOrCreateTSIDByName(db *indexDB, accountsCount, projectsCount,
|
||||
tsids = append(tsids, tsid)
|
||||
}
|
||||
|
||||
// fill Date -> MetricID cache
|
||||
date := uint64(timestampFromTime(time.Now())) / msecPerDay
|
||||
for i := range tsids {
|
||||
tsid := &tsids[i]
|
||||
if err := db.storeDateMetricID(date, tsid.MetricID); err != nil {
|
||||
return nil, nil, fmt.Errorf("error in storeDateMetricID(%d, %d): %s", date, tsid.MetricID, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Flush index to disk, so it becomes visible for search
|
||||
db.tb.DebugFlush()
|
||||
|
||||
return mns, tsids, nil
|
||||
}
|
||||
|
||||
func testIndexDBCheckTSIDByName(db *indexDB, mns []MetricName, tsids []TSID, isConcurrent bool) error {
|
||||
// fill Date -> MetricID cache
|
||||
date := uint64(timestampFromTime(time.Now())) / msecPerDay
|
||||
for i := range tsids {
|
||||
tsid := &tsids[i]
|
||||
if err := db.storeDateMetricID(date, tsid.MetricID); err != nil {
|
||||
return fmt.Errorf("error in storeDateMetricID(%d, %d): %s", date, tsid.MetricID, err)
|
||||
}
|
||||
}
|
||||
db.tb.DebugFlush()
|
||||
|
||||
hasValue := func(tvs []string, v []byte) bool {
|
||||
for _, tv := range tvs {
|
||||
if string(v) == tv {
|
||||
@@ -361,7 +684,7 @@ func testIndexDBCheckTSIDByName(db *indexDB, mns []MetricName, tsids []TSID, isC
|
||||
var err error
|
||||
metricNameCopy, err = db.searchMetricName(metricNameCopy[:0], tsidCopy.MetricID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in searchMetricName: %s", err)
|
||||
return fmt.Errorf("error in searchMetricName for metricID=%d; i=%d: %s", tsidCopy.MetricID, i, err)
|
||||
}
|
||||
if !bytes.Equal(metricName, metricNameCopy) {
|
||||
return fmt.Errorf("unexpected mn for metricID=%d;\ngot\n%q\nwant\n%q", tsidCopy.MetricID, metricNameCopy, metricName)
|
||||
@@ -451,7 +774,7 @@ func testIndexDBCheckTSIDByName(db *indexDB, mns []MetricName, tsids []TSID, isC
|
||||
return fmt.Errorf("cannot search by exact tag filter: %s", err)
|
||||
}
|
||||
if !testHasTSID(tsidsFound, tsid) {
|
||||
return fmt.Errorf("tsids is missing in exact tsidsFound\ntsid=%+v\ntsidsFound=%+v\ntfs=%s\nmn=%s", tsid, tsidsFound, tfs, mn)
|
||||
return fmt.Errorf("tsids is missing in exact tsidsFound\ntsid=%+v\ntsidsFound=%+v\ntfs=%s\nmn=%s\ni=%d", tsid, tsidsFound, tfs, mn, i)
|
||||
}
|
||||
|
||||
// Verify tag cache.
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/decimal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/uint64set"
|
||||
)
|
||||
|
||||
// mergeBlockStreams merges bsrs into bsw and updates ph.
|
||||
@@ -14,7 +15,7 @@ import (
|
||||
//
|
||||
// rowsMerged is atomically updated with the number of merged rows during the merge.
|
||||
func mergeBlockStreams(ph *partHeader, bsw *blockStreamWriter, bsrs []*blockStreamReader, stopCh <-chan struct{}, rowsMerged *uint64,
|
||||
deletedMetricIDs map[uint64]struct{}, rowsDeleted *uint64) error {
|
||||
deletedMetricIDs *uint64set.Set, rowsDeleted *uint64) error {
|
||||
ph.Reset()
|
||||
|
||||
bsm := bsmPool.Get().(*blockStreamMerger)
|
||||
@@ -41,7 +42,7 @@ var bsmPool = &sync.Pool{
|
||||
var errForciblyStopped = fmt.Errorf("forcibly stopped")
|
||||
|
||||
func mergeBlockStreamsInternal(ph *partHeader, bsw *blockStreamWriter, bsm *blockStreamMerger, stopCh <-chan struct{}, rowsMerged *uint64,
|
||||
deletedMetricIDs map[uint64]struct{}, rowsDeleted *uint64) error {
|
||||
deletedMetricIDs *uint64set.Set, rowsDeleted *uint64) error {
|
||||
// Search for the first block to merge
|
||||
var pendingBlock *Block
|
||||
for bsm.NextBlock() {
|
||||
@@ -50,7 +51,7 @@ func mergeBlockStreamsInternal(ph *partHeader, bsw *blockStreamWriter, bsm *bloc
|
||||
return errForciblyStopped
|
||||
default:
|
||||
}
|
||||
if _, deleted := deletedMetricIDs[bsm.Block.bh.TSID.MetricID]; deleted {
|
||||
if deletedMetricIDs.Has(bsm.Block.bh.TSID.MetricID) {
|
||||
// Skip blocks for deleted metrics.
|
||||
*rowsDeleted += uint64(bsm.Block.bh.RowsCount)
|
||||
continue
|
||||
@@ -72,7 +73,7 @@ func mergeBlockStreamsInternal(ph *partHeader, bsw *blockStreamWriter, bsm *bloc
|
||||
return errForciblyStopped
|
||||
default:
|
||||
}
|
||||
if _, deleted := deletedMetricIDs[bsm.Block.bh.TSID.MetricID]; deleted {
|
||||
if deletedMetricIDs.Has(bsm.Block.bh.TSID.MetricID) {
|
||||
// Skip blocks for deleted metrics.
|
||||
*rowsDeleted += uint64(bsm.Block.bh.RowsCount)
|
||||
continue
|
||||
|
||||
@@ -25,7 +25,7 @@ func TestMergeBlockStreamsOneStreamOneBlockManyRows(t *testing.T) {
|
||||
minTimestamp := int64(1<<63 - 1)
|
||||
maxTimestamp := int64(-1 << 63)
|
||||
for i := 0; i < maxRowsPerBlock; i++ {
|
||||
r.Timestamp = int64(rand.Intn(1e15))
|
||||
r.Timestamp = int64(rand.Intn(1e9))
|
||||
r.Value = rand.NormFloat64() * 2332
|
||||
rows = append(rows, r)
|
||||
|
||||
@@ -51,7 +51,7 @@ func TestMergeBlockStreamsOneStreamManyBlocksOneRow(t *testing.T) {
|
||||
for i := 0; i < blocksCount; i++ {
|
||||
initTestTSID(&r.TSID)
|
||||
r.TSID.MetricID = uint64(i * 123)
|
||||
r.Timestamp = int64(rand.Intn(1e15))
|
||||
r.Timestamp = int64(rand.Intn(1e9))
|
||||
r.Value = rand.NormFloat64() * 2332
|
||||
rows = append(rows, r)
|
||||
|
||||
@@ -78,7 +78,7 @@ func TestMergeBlockStreamsOneStreamManyBlocksManyRows(t *testing.T) {
|
||||
maxTimestamp := int64(-1 << 63)
|
||||
for i := 0; i < rowsCount; i++ {
|
||||
r.TSID.MetricID = uint64(i % blocksCount)
|
||||
r.Timestamp = int64(rand.Intn(1e15))
|
||||
r.Timestamp = int64(rand.Intn(1e9))
|
||||
r.Value = rand.NormFloat64() * 2332
|
||||
rows = append(rows, r)
|
||||
|
||||
@@ -175,7 +175,7 @@ func TestMergeBlockStreamsTwoStreamsManyBlocksManyRows(t *testing.T) {
|
||||
const rowsCount1 = 4938
|
||||
for i := 0; i < rowsCount1; i++ {
|
||||
r.TSID.MetricID = uint64(i % blocksCount)
|
||||
r.Timestamp = int64(rand.Intn(1e15))
|
||||
r.Timestamp = int64(rand.Intn(1e9))
|
||||
r.Value = rand.NormFloat64() * 2332
|
||||
rows = append(rows, r)
|
||||
|
||||
@@ -192,7 +192,7 @@ func TestMergeBlockStreamsTwoStreamsManyBlocksManyRows(t *testing.T) {
|
||||
const rowsCount2 = 3281
|
||||
for i := 0; i < rowsCount2; i++ {
|
||||
r.TSID.MetricID = uint64((i + 17) % blocksCount)
|
||||
r.Timestamp = int64(rand.Intn(1e15))
|
||||
r.Timestamp = int64(rand.Intn(1e9))
|
||||
r.Value = rand.NormFloat64() * 2332
|
||||
rows = append(rows, r)
|
||||
|
||||
@@ -310,7 +310,7 @@ func TestMergeBlockStreamsManyStreamsManyBlocksManyRows(t *testing.T) {
|
||||
var rows []rawRow
|
||||
for j := 0; j < rowsPerStream; j++ {
|
||||
r.TSID.MetricID = uint64(j % blocksCount)
|
||||
r.Timestamp = int64(rand.Intn(1e10))
|
||||
r.Timestamp = int64(rand.Intn(1e9))
|
||||
r.Value = rand.NormFloat64()
|
||||
rows = append(rows, r)
|
||||
|
||||
@@ -343,7 +343,7 @@ func TestMergeForciblyStop(t *testing.T) {
|
||||
var rows []rawRow
|
||||
for j := 0; j < rowsPerStream; j++ {
|
||||
r.TSID.MetricID = uint64(j % blocksCount)
|
||||
r.Timestamp = int64(rand.Intn(1e10))
|
||||
r.Timestamp = int64(rand.Intn(1e9))
|
||||
r.Value = rand.NormFloat64()
|
||||
rows = append(rows, r)
|
||||
|
||||
|
||||
@@ -25,6 +25,17 @@ type Tag struct {
|
||||
Value []byte
|
||||
}
|
||||
|
||||
// Reset resets the tag.
|
||||
func (tag *Tag) Reset() {
|
||||
tag.Key = tag.Key[:0]
|
||||
tag.Value = tag.Value[:0]
|
||||
}
|
||||
|
||||
// Equal returns true if tag equals t
|
||||
func (tag *Tag) Equal(t *Tag) bool {
|
||||
return string(tag.Key) == string(t.Key) && string(tag.Value) == string(t.Value)
|
||||
}
|
||||
|
||||
// Marshal appends marshaled tag to dst and returns the result.
|
||||
func (tag *Tag) Marshal(dst []byte) []byte {
|
||||
dst = marshalTagValue(dst, tag.Key)
|
||||
|
||||
@@ -142,3 +142,82 @@ func TestMetricNameMarshalUnmarshalRaw(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetricNameCopyFrom(t *testing.T) {
|
||||
var from MetricName
|
||||
from.MetricGroup = []byte("group")
|
||||
from.AddTag("key", "value")
|
||||
|
||||
var to MetricName
|
||||
to.CopyFrom(&from)
|
||||
|
||||
var expected MetricName
|
||||
expected.MetricGroup = []byte("group")
|
||||
expected.AddTag("key", "value")
|
||||
|
||||
if !reflect.DeepEqual(expected, to) {
|
||||
t.Fatalf("expecting equal metics exp: %s, got %s", &expected, &to)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetricNameRemoveTagsOn(t *testing.T) {
|
||||
var emptyMN MetricName
|
||||
emptyMN.MetricGroup = []byte("name")
|
||||
emptyMN.AddTag("key", "value")
|
||||
emptyMN.RemoveTagsOn(nil)
|
||||
if len(emptyMN.MetricGroup) != 0 || len(emptyMN.Tags) != 0 {
|
||||
t.Fatalf("expecitng empty metric name got %s", &emptyMN)
|
||||
}
|
||||
|
||||
var asIsMN MetricName
|
||||
asIsMN.MetricGroup = []byte("name")
|
||||
asIsMN.AddTag("key", "value")
|
||||
asIsMN.RemoveTagsOn([]string{"__name__", "key"})
|
||||
var expAsIsMN MetricName
|
||||
expAsIsMN.MetricGroup = []byte("name")
|
||||
expAsIsMN.AddTag("key", "value")
|
||||
if !reflect.DeepEqual(expAsIsMN, asIsMN) {
|
||||
t.Fatalf("expecitng %s got %s", &expAsIsMN, &asIsMN)
|
||||
}
|
||||
|
||||
var mn MetricName
|
||||
mn.MetricGroup = []byte("name")
|
||||
mn.AddTag("foo", "bar")
|
||||
mn.AddTag("baz", "qux")
|
||||
mn.RemoveTagsOn([]string{"baz"})
|
||||
var expMN MetricName
|
||||
expMN.AddTag("baz", "qux")
|
||||
if !reflect.DeepEqual(expMN.Tags, mn.Tags) || len(mn.MetricGroup) != len(expMN.MetricGroup) {
|
||||
t.Fatalf("expecitng %s got %s", &expMN, &mn)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetricNameRemoveTag(t *testing.T) {
|
||||
var mn MetricName
|
||||
mn.MetricGroup = []byte("name")
|
||||
mn.AddTag("foo", "bar")
|
||||
mn.AddTag("baz", "qux")
|
||||
mn.RemoveTag("__name__")
|
||||
if len(mn.MetricGroup) != 0 {
|
||||
t.Fatalf("expecting empty metric group got %s", &mn)
|
||||
}
|
||||
mn.RemoveTag("foo")
|
||||
var expMN MetricName
|
||||
expMN.AddTag("baz", "qux")
|
||||
if !reflect.DeepEqual(expMN.Tags, mn.Tags) || len(mn.MetricGroup) != len(expMN.MetricGroup) {
|
||||
t.Fatalf("expecitng %s got %s", &expMN, &mn)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetricNameRemoveTagsIgnoring(t *testing.T) {
|
||||
var mn MetricName
|
||||
mn.MetricGroup = []byte("name")
|
||||
mn.AddTag("foo", "bar")
|
||||
mn.AddTag("baz", "qux")
|
||||
mn.RemoveTagsIgnoring([]string{"__name__", "foo"})
|
||||
var expMN MetricName
|
||||
expMN.AddTag("baz", "qux")
|
||||
if !reflect.DeepEqual(expMN.Tags, mn.Tags) || len(mn.MetricGroup) != len(expMN.MetricGroup) {
|
||||
t.Fatalf("expecitng %s got %s", &expMN, &mn)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/filestream"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
|
||||
@@ -27,8 +28,7 @@ var (
|
||||
maxCachedIndexBlocksPerPartOnce sync.Once
|
||||
)
|
||||
|
||||
// part represents a searchable part containing time series data.
|
||||
type part struct {
|
||||
type partInternals struct {
|
||||
ph partHeader
|
||||
|
||||
// Filesystem path to the part.
|
||||
@@ -44,7 +44,15 @@ type part struct {
|
||||
indexFile fs.ReadAtCloser
|
||||
|
||||
metaindex []metaindexRow
|
||||
}
|
||||
|
||||
// part represents a searchable part containing time series data.
|
||||
type part struct {
|
||||
partInternals
|
||||
|
||||
// Align ibCache to 8 bytes in order to align internal counters on 32-bit architectures.
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/212
|
||||
_ [(8 - (unsafe.Sizeof(partInternals{}) % 8)) % 8]byte
|
||||
ibCache indexBlockCache
|
||||
}
|
||||
|
||||
@@ -107,27 +115,26 @@ func newPart(ph *partHeader, path string, size uint64, metaindexReader filestrea
|
||||
}
|
||||
metaindexReader.MustClose()
|
||||
|
||||
p := &part{
|
||||
ph: *ph,
|
||||
path: path,
|
||||
size: size,
|
||||
timestampsFile: timestampsFile,
|
||||
valuesFile: valuesFile,
|
||||
indexFile: indexFile,
|
||||
var p part
|
||||
p.ph = *ph
|
||||
p.path = path
|
||||
p.size = size
|
||||
p.timestampsFile = timestampsFile
|
||||
p.valuesFile = valuesFile
|
||||
p.indexFile = indexFile
|
||||
|
||||
metaindex: metaindex,
|
||||
}
|
||||
p.metaindex = metaindex
|
||||
|
||||
if len(errors) > 0 {
|
||||
// Return only the first error, since it has no sense in returning all errors.
|
||||
err = fmt.Errorf("cannot initialize part %q: %s", p, errors[0])
|
||||
err = fmt.Errorf("cannot initialize part %q: %s", &p, errors[0])
|
||||
p.MustClose()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.ibCache.Init()
|
||||
|
||||
return p, nil
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
// String returns human-readable representation of p.
|
||||
@@ -168,12 +175,14 @@ func putIndexBlock(ib *indexBlock) {
|
||||
var indexBlockPool sync.Pool
|
||||
|
||||
type indexBlockCache struct {
|
||||
// Put atomic counters to the top of struct in order to align them to 8 bytes on 32-bit architectures.
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/212
|
||||
requests uint64
|
||||
misses uint64
|
||||
|
||||
m map[uint64]*indexBlock
|
||||
missesMap map[uint64]uint64
|
||||
mu sync.RWMutex
|
||||
|
||||
requests uint64
|
||||
misses uint64
|
||||
}
|
||||
|
||||
func (ibc *indexBlockCache) Init() {
|
||||
|
||||
@@ -3,7 +3,9 @@ package storage
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
|
||||
@@ -49,7 +51,7 @@ type partSearch struct {
|
||||
func (ps *partSearch) reset() {
|
||||
ps.Block.Reset()
|
||||
ps.p = nil
|
||||
ps.tsids = ps.tsids[:0]
|
||||
ps.tsids = nil
|
||||
ps.tsidIdx = 0
|
||||
ps.fetchData = true
|
||||
ps.metaindex = nil
|
||||
@@ -64,16 +66,24 @@ func (ps *partSearch) reset() {
|
||||
ps.err = nil
|
||||
}
|
||||
|
||||
var isInTest = func() bool {
|
||||
return strings.HasSuffix(os.Args[0], ".test")
|
||||
}()
|
||||
|
||||
// Init initializes the ps with the given p, tsids and tr.
|
||||
//
|
||||
// tsids must be sorted.
|
||||
// tsids cannot be modified after the Init call, since it is owned by ps.
|
||||
func (ps *partSearch) Init(p *part, tsids []TSID, tr TimeRange, fetchData bool) {
|
||||
ps.reset()
|
||||
ps.p = p
|
||||
|
||||
if p.ph.MinTimestamp <= tr.MaxTimestamp && p.ph.MaxTimestamp >= tr.MinTimestamp {
|
||||
if !sort.SliceIsSorted(tsids, func(i, j int) bool { return tsids[i].Less(&tsids[j]) }) {
|
||||
if isInTest && !sort.SliceIsSorted(tsids, func(i, j int) bool { return tsids[i].Less(&tsids[j]) }) {
|
||||
logger.Panicf("BUG: tsids must be sorted; got %+v", tsids)
|
||||
}
|
||||
ps.tsids = append(ps.tsids[:0], tsids...)
|
||||
// take ownership of of tsids.
|
||||
ps.tsids = tsids
|
||||
}
|
||||
ps.tr = tr
|
||||
ps.fetchData = fetchData
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/memory"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/uint64set"
|
||||
)
|
||||
|
||||
func maxRowsPerSmallPart() uint64 {
|
||||
@@ -28,7 +29,7 @@ func maxRowsPerSmallPart() uint64 {
|
||||
// Production data shows that each row occupies ~1 byte in the compressed part.
|
||||
// It is expected no more than defaultPartsToMerge/2 parts exist
|
||||
// in the OS page cache before they are merged into bigger part.
|
||||
// Halft of the remaining RAM must be left for lib/mergeset parts,
|
||||
// Half of the remaining RAM must be left for lib/mergeset parts,
|
||||
// so the maxItems is calculated using the below code:
|
||||
maxRows := uint64(mem) / defaultPartsToMerge
|
||||
if maxRows < 10e6 {
|
||||
@@ -89,11 +90,27 @@ const inmemoryPartsFlushInterval = 5 * time.Second
|
||||
|
||||
// partition represents a partition.
|
||||
type partition struct {
|
||||
// Put atomic counters to the top of struct, so they are aligned to 8 bytes on 32-bit arch.
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/212
|
||||
|
||||
activeBigMerges uint64
|
||||
activeSmallMerges uint64
|
||||
bigMergesCount uint64
|
||||
smallMergesCount uint64
|
||||
bigRowsMerged uint64
|
||||
smallRowsMerged uint64
|
||||
bigRowsDeleted uint64
|
||||
smallRowsDeleted uint64
|
||||
|
||||
smallAssistedMerges uint64
|
||||
|
||||
mergeIdx uint64
|
||||
|
||||
smallPartsPath string
|
||||
bigPartsPath string
|
||||
|
||||
// The callack that returns deleted metric ids which must be skipped during merge.
|
||||
getDeletedMetricIDs func() map[uint64]struct{}
|
||||
getDeletedMetricIDs func() *uint64set.Set
|
||||
|
||||
// Name is the name of the partition in the form YYYY_MM.
|
||||
name string
|
||||
@@ -122,8 +139,6 @@ type partition struct {
|
||||
// rawRowsLastFlushTime is the last time rawRows are flushed.
|
||||
rawRowsLastFlushTime time.Time
|
||||
|
||||
mergeIdx uint64
|
||||
|
||||
snapshotLock sync.RWMutex
|
||||
|
||||
stopCh chan struct{}
|
||||
@@ -132,30 +147,22 @@ type partition struct {
|
||||
bigPartsMergerWG sync.WaitGroup
|
||||
rawRowsFlusherWG sync.WaitGroup
|
||||
inmemoryPartsFlusherWG sync.WaitGroup
|
||||
|
||||
activeBigMerges uint64
|
||||
activeSmallMerges uint64
|
||||
bigMergesCount uint64
|
||||
smallMergesCount uint64
|
||||
bigRowsMerged uint64
|
||||
smallRowsMerged uint64
|
||||
bigRowsDeleted uint64
|
||||
smallRowsDeleted uint64
|
||||
|
||||
smallAssistedMerges uint64
|
||||
}
|
||||
|
||||
// partWrapper is a wrapper for the part.
|
||||
type partWrapper struct {
|
||||
// Put atomic counters to the top of struct, so they are aligned to 8 bytes on 32-bit arch.
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/212
|
||||
|
||||
// The number of references to the part.
|
||||
refCount uint64
|
||||
|
||||
// The part itself.
|
||||
p *part
|
||||
|
||||
// non-nil if the part is inmemoryPart.
|
||||
mp *inmemoryPart
|
||||
|
||||
// The number of references to the part.
|
||||
refCount uint64
|
||||
|
||||
// Whether the part is in merge now.
|
||||
isInMerge bool
|
||||
}
|
||||
@@ -183,7 +190,7 @@ func (pw *partWrapper) decRef() {
|
||||
|
||||
// createPartition creates new partition for the given timestamp and the given paths
|
||||
// to small and big partitions.
|
||||
func createPartition(timestamp int64, smallPartitionsPath, bigPartitionsPath string, getDeletedMetricIDs func() map[uint64]struct{}) (*partition, error) {
|
||||
func createPartition(timestamp int64, smallPartitionsPath, bigPartitionsPath string, getDeletedMetricIDs func() *uint64set.Set) (*partition, error) {
|
||||
name := timestampToPartitionName(timestamp)
|
||||
smallPartsPath := filepath.Clean(smallPartitionsPath) + "/" + name
|
||||
bigPartsPath := filepath.Clean(bigPartitionsPath) + "/" + name
|
||||
@@ -218,7 +225,7 @@ func (pt *partition) Drop() {
|
||||
}
|
||||
|
||||
// openPartition opens the existing partition from the given paths.
|
||||
func openPartition(smallPartsPath, bigPartsPath string, getDeletedMetricIDs func() map[uint64]struct{}) (*partition, error) {
|
||||
func openPartition(smallPartsPath, bigPartsPath string, getDeletedMetricIDs func() *uint64set.Set) (*partition, error) {
|
||||
smallPartsPath = filepath.Clean(smallPartsPath)
|
||||
bigPartsPath = filepath.Clean(bigPartsPath)
|
||||
|
||||
@@ -255,7 +262,7 @@ func openPartition(smallPartsPath, bigPartsPath string, getDeletedMetricIDs func
|
||||
return pt, nil
|
||||
}
|
||||
|
||||
func newPartition(name, smallPartsPath, bigPartsPath string, getDeletedMetricIDs func() map[uint64]struct{}) *partition {
|
||||
func newPartition(name, smallPartsPath, bigPartsPath string, getDeletedMetricIDs func() *uint64set.Set) *partition {
|
||||
return &partition{
|
||||
name: name,
|
||||
smallPartsPath: smallPartsPath,
|
||||
@@ -727,7 +734,7 @@ func (pt *partition) mergePartsOptimal(pws []*partWrapper) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var mergeWorkers = func() int {
|
||||
var mergeWorkersCount = func() int {
|
||||
n := runtime.GOMAXPROCS(-1) / 2
|
||||
if n <= 0 {
|
||||
n = 1
|
||||
@@ -735,16 +742,47 @@ var mergeWorkers = func() int {
|
||||
return n
|
||||
}()
|
||||
|
||||
var (
|
||||
bigMergeWorkersCount = uint64(mergeWorkersCount)
|
||||
smallMergeWorkersCount = uint64(mergeWorkersCount)
|
||||
)
|
||||
|
||||
var (
|
||||
bigMergeConcurrencyLimitCh = make(chan struct{}, bigMergeWorkersCount)
|
||||
smallMergeConcurrencyLimitCh = make(chan struct{}, smallMergeWorkersCount)
|
||||
)
|
||||
|
||||
// SetBigMergeWorkersCount sets the maximum number of concurrent mergers for big blocks.
|
||||
//
|
||||
// The function must be called before opening or creating any storage.
|
||||
func SetBigMergeWorkersCount(n int) {
|
||||
if n <= 0 {
|
||||
// Do nothing
|
||||
return
|
||||
}
|
||||
atomic.StoreUint64(&bigMergeWorkersCount, uint64(n))
|
||||
}
|
||||
|
||||
// SetSmallMergeWorkersCount sets the maximum number of concurrent mergers for small blocks.
|
||||
//
|
||||
// The function must be called before opening or creating any storage.
|
||||
func SetSmallMergeWorkersCount(n int) {
|
||||
if n <= 0 {
|
||||
// Do nothing
|
||||
return
|
||||
}
|
||||
atomic.StoreUint64(&smallMergeWorkersCount, uint64(n))
|
||||
}
|
||||
|
||||
func (pt *partition) startMergeWorkers() {
|
||||
for i := 0; i < mergeWorkers; i++ {
|
||||
for i := 0; i < mergeWorkersCount; i++ {
|
||||
pt.smallPartsMergerWG.Add(1)
|
||||
go func() {
|
||||
pt.smallPartsMerger()
|
||||
pt.smallPartsMergerWG.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
for i := 0; i < mergeWorkers; i++ {
|
||||
for i := 0; i < mergeWorkersCount; i++ {
|
||||
pt.bigPartsMergerWG.Add(1)
|
||||
go func() {
|
||||
pt.bigPartsMerger()
|
||||
@@ -791,7 +829,7 @@ func (pt *partition) partsMerger(mergerFunc func(isFinal bool) error) error {
|
||||
if err != errNothingToMerge {
|
||||
return err
|
||||
}
|
||||
if time.Since(lastMergeTime) > 10*time.Second {
|
||||
if time.Since(lastMergeTime) > 30*time.Second {
|
||||
// We have free time for merging into bigger parts.
|
||||
// This should improve select performance.
|
||||
lastMergeTime = time.Now()
|
||||
@@ -818,11 +856,11 @@ func maxRowsByPath(path string) uint64 {
|
||||
|
||||
// Calculate the maximum number of rows in the output merge part
|
||||
// by dividing the freeSpace by the number of concurrent
|
||||
// mergeWorkers for big parts.
|
||||
// mergeWorkersCount for big parts.
|
||||
// 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.
|
||||
maxRows := freeSpace / uint64(mergeWorkers)
|
||||
maxRows := freeSpace / uint64(mergeWorkersCount)
|
||||
if maxRows > maxRowsPerBigPart {
|
||||
maxRows = maxRowsPerBigPart
|
||||
}
|
||||
@@ -859,6 +897,11 @@ type freeSpaceEntry struct {
|
||||
}
|
||||
|
||||
func (pt *partition) mergeBigParts(isFinal bool) error {
|
||||
bigMergeConcurrencyLimitCh <- struct{}{}
|
||||
defer func() {
|
||||
<-bigMergeConcurrencyLimitCh
|
||||
}()
|
||||
|
||||
maxRows := maxRowsByPath(pt.bigPartsPath)
|
||||
|
||||
pt.partsLock.Lock()
|
||||
@@ -878,10 +921,15 @@ func (pt *partition) mergeBigParts(isFinal bool) error {
|
||||
}
|
||||
|
||||
func (pt *partition) mergeSmallParts(isFinal bool) error {
|
||||
smallMergeConcurrencyLimitCh <- struct{}{}
|
||||
defer func() {
|
||||
<-smallMergeConcurrencyLimitCh
|
||||
}()
|
||||
|
||||
maxRows := maxRowsByPath(pt.smallPartsPath)
|
||||
if maxRows > maxRowsPerSmallPart() {
|
||||
// The output part may go to big part,
|
||||
// so make sure it as enough space.
|
||||
// so make sure it has enough space.
|
||||
maxBigPartRows := maxRowsByPath(pt.bigPartsPath)
|
||||
if maxRows > maxBigPartRows {
|
||||
maxRows = maxBigPartRows
|
||||
@@ -1158,13 +1206,10 @@ func appendPartsToMerge(dst, src []*partWrapper, maxPartsToMerge int, maxRows ui
|
||||
sort.Slice(src, func(i, j int) bool {
|
||||
a := &src[i].p.ph
|
||||
b := &src[j].p.ph
|
||||
if a.RowsCount < b.RowsCount {
|
||||
return true
|
||||
if a.RowsCount == b.RowsCount {
|
||||
return a.MinTimestamp > b.MinTimestamp
|
||||
}
|
||||
if a.RowsCount > b.RowsCount {
|
||||
return false
|
||||
}
|
||||
return a.MinTimestamp > b.MinTimestamp
|
||||
return a.RowsCount < b.RowsCount
|
||||
})
|
||||
|
||||
n := maxPartsToMerge
|
||||
@@ -1178,36 +1223,40 @@ func appendPartsToMerge(dst, src []*partWrapper, maxPartsToMerge int, maxRows ui
|
||||
maxM := float64(0)
|
||||
for i := 2; i <= n; i++ {
|
||||
for j := 0; j <= len(src)-i; j++ {
|
||||
a := src[j : j+i]
|
||||
rowsSum := uint64(0)
|
||||
for _, pw := range src[j : j+i] {
|
||||
for _, pw := range a {
|
||||
rowsSum += pw.p.ph.RowsCount
|
||||
}
|
||||
if rowsSum > maxRows {
|
||||
continue
|
||||
// There is no need in verifying remaining parts with higher number of rows
|
||||
break
|
||||
}
|
||||
m := float64(rowsSum) / float64(src[j+i-1].p.ph.RowsCount)
|
||||
m := float64(rowsSum) / float64(a[len(a)-1].p.ph.RowsCount)
|
||||
if m < maxM {
|
||||
continue
|
||||
}
|
||||
maxM = m
|
||||
pws = src[j : j+i]
|
||||
pws = a
|
||||
}
|
||||
}
|
||||
|
||||
minM := float64(maxPartsToMerge / 2)
|
||||
if minM < 2 {
|
||||
minM = 2
|
||||
minM := float64(maxPartsToMerge) / 2
|
||||
if minM < 1.7 {
|
||||
minM = 1.7
|
||||
}
|
||||
if maxM < minM {
|
||||
// There is no sense in merging parts with too small m.
|
||||
return dst
|
||||
}
|
||||
|
||||
return append(dst, pws...)
|
||||
}
|
||||
|
||||
func openParts(pathPrefix1, pathPrefix2, path string) ([]*partWrapper, error) {
|
||||
// Verify that the directory for the parts exists.
|
||||
// The path can be missing after restoring from backup, so create it if needed.
|
||||
if err := fs.MkdirAllIfNotExist(path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot open directory %q: %s", path, err)
|
||||
|
||||
@@ -55,7 +55,10 @@ func (pts *partitionSearch) reset() {
|
||||
|
||||
// Init initializes the search in the given partition for the given tsid and tr.
|
||||
//
|
||||
// MustClose must be called when partition search is done.
|
||||
// tsids must be sorted.
|
||||
// tsids cannot be modified after the Init call, since it is owned by pts.
|
||||
//
|
||||
/// MustClose must be called when partition search is done.
|
||||
func (pts *partitionSearch) Init(pt *partition, tsids []TSID, tr TimeRange, fetchData bool) {
|
||||
if pts.needClosing {
|
||||
logger.Panicf("BUG: missing partitionSearch.MustClose call before the next call to Init")
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/uint64set"
|
||||
)
|
||||
|
||||
func TestPartitionSearch(t *testing.T) {
|
||||
@@ -284,6 +286,6 @@ func testPartitionSearchSerial(pt *partition, tsids []TSID, tr TimeRange, rbsExp
|
||||
return nil
|
||||
}
|
||||
|
||||
func nilGetDeletedMetricIDs() map[uint64]struct{} {
|
||||
func nilGetDeletedMetricIDs() *uint64set.Set {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -14,34 +14,34 @@ func TestPartitionMaxRowsByPath(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAppendPartsToMerge(t *testing.T) {
|
||||
testAppendPartsToMerge(t, 2, []int{}, nil)
|
||||
testAppendPartsToMerge(t, 2, []int{123}, nil)
|
||||
testAppendPartsToMerge(t, 2, []int{4, 2}, nil)
|
||||
testAppendPartsToMerge(t, 2, []int{128, 64, 32, 16, 8, 4, 2, 1}, nil)
|
||||
testAppendPartsToMerge(t, 4, []int{128, 64, 32, 10, 9, 7, 2, 1}, []int{2, 7, 9, 10})
|
||||
testAppendPartsToMerge(t, 2, []int{128, 64, 32, 16, 8, 4, 2, 2}, []int{2, 2})
|
||||
testAppendPartsToMerge(t, 4, []int{128, 64, 32, 16, 8, 4, 2, 2}, []int{2, 2, 4, 8})
|
||||
testAppendPartsToMerge(t, 2, []int{1, 1}, []int{1, 1})
|
||||
testAppendPartsToMerge(t, 2, []int{2, 2, 2}, []int{2, 2})
|
||||
testAppendPartsToMerge(t, 2, []int{4, 2, 4}, []int{4, 4})
|
||||
testAppendPartsToMerge(t, 2, []int{1, 3, 7, 2}, nil)
|
||||
testAppendPartsToMerge(t, 3, []int{1, 3, 7, 2}, []int{1, 2, 3})
|
||||
testAppendPartsToMerge(t, 4, []int{1, 3, 7, 2}, []int{1, 2, 3})
|
||||
testAppendPartsToMerge(t, 3, []int{11, 1, 10, 100, 10}, []int{10, 10, 11})
|
||||
testAppendPartsToMerge(t, 2, []uint64{}, nil)
|
||||
testAppendPartsToMerge(t, 2, []uint64{123}, nil)
|
||||
testAppendPartsToMerge(t, 2, []uint64{4, 2}, nil)
|
||||
testAppendPartsToMerge(t, 2, []uint64{128, 64, 32, 16, 8, 4, 2, 1}, nil)
|
||||
testAppendPartsToMerge(t, 4, []uint64{128, 64, 32, 10, 9, 7, 2, 1}, []uint64{2, 7, 9, 10})
|
||||
testAppendPartsToMerge(t, 2, []uint64{128, 64, 32, 16, 8, 4, 2, 2}, []uint64{2, 2})
|
||||
testAppendPartsToMerge(t, 4, []uint64{128, 64, 32, 16, 8, 4, 2, 2}, []uint64{2, 2, 4, 8})
|
||||
testAppendPartsToMerge(t, 2, []uint64{1, 1}, []uint64{1, 1})
|
||||
testAppendPartsToMerge(t, 2, []uint64{2, 2, 2}, []uint64{2, 2})
|
||||
testAppendPartsToMerge(t, 2, []uint64{4, 2, 4}, []uint64{4, 4})
|
||||
testAppendPartsToMerge(t, 2, []uint64{1, 3, 7, 2}, nil)
|
||||
testAppendPartsToMerge(t, 3, []uint64{1, 3, 7, 2}, []uint64{1, 2, 3})
|
||||
testAppendPartsToMerge(t, 4, []uint64{1, 3, 7, 2}, []uint64{1, 2, 3})
|
||||
testAppendPartsToMerge(t, 3, []uint64{11, 1, 10, 100, 10}, []uint64{10, 10, 11})
|
||||
}
|
||||
|
||||
func TestAppendPartsToMergeManyParts(t *testing.T) {
|
||||
// Verify that big number of parts are merged into minimal number of parts
|
||||
// using minimum merges.
|
||||
var a []int
|
||||
var a []uint64
|
||||
maxOutPartRows := uint64(0)
|
||||
for i := 0; i < 1024; i++ {
|
||||
n := int(rand.NormFloat64() * 1e9)
|
||||
n := uint64(uint32(rand.NormFloat64() * 1e9))
|
||||
if n < 0 {
|
||||
n = -n
|
||||
}
|
||||
n++
|
||||
maxOutPartRows += uint64(n)
|
||||
maxOutPartRows += n
|
||||
a = append(a, n)
|
||||
}
|
||||
pws := newTestPartWrappersForRowsCount(a)
|
||||
@@ -67,11 +67,10 @@ func TestAppendPartsToMergeManyParts(t *testing.T) {
|
||||
}
|
||||
}
|
||||
pw := &partWrapper{
|
||||
p: &part{
|
||||
ph: partHeader{
|
||||
RowsCount: rowsCount,
|
||||
},
|
||||
},
|
||||
p: &part{},
|
||||
}
|
||||
pw.p.ph = partHeader{
|
||||
RowsCount: rowsCount,
|
||||
}
|
||||
rowsMerged += rowsCount
|
||||
pwsNew = append(pwsNew, pw)
|
||||
@@ -94,7 +93,7 @@ func TestAppendPartsToMergeManyParts(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func testAppendPartsToMerge(t *testing.T, maxPartsToMerge int, initialRowsCount, expectedRowsCount []int) {
|
||||
func testAppendPartsToMerge(t *testing.T, maxPartsToMerge int, initialRowsCount, expectedRowsCount []uint64) {
|
||||
t.Helper()
|
||||
|
||||
pws := newTestPartWrappersForRowsCount(initialRowsCount)
|
||||
@@ -111,8 +110,10 @@ func testAppendPartsToMerge(t *testing.T, maxPartsToMerge int, initialRowsCount,
|
||||
prefix := []*partWrapper{
|
||||
{
|
||||
p: &part{
|
||||
ph: partHeader{
|
||||
RowsCount: 1234,
|
||||
partInternals: partInternals{
|
||||
ph: partHeader{
|
||||
RowsCount: 1234,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -132,21 +133,23 @@ func testAppendPartsToMerge(t *testing.T, maxPartsToMerge int, initialRowsCount,
|
||||
}
|
||||
}
|
||||
|
||||
func newTestRowsCountFromPartWrappers(pws []*partWrapper) []int {
|
||||
var rowsCount []int
|
||||
func newTestRowsCountFromPartWrappers(pws []*partWrapper) []uint64 {
|
||||
var rowsCount []uint64
|
||||
for _, pw := range pws {
|
||||
rowsCount = append(rowsCount, int(pw.p.ph.RowsCount))
|
||||
rowsCount = append(rowsCount, pw.p.ph.RowsCount)
|
||||
}
|
||||
return rowsCount
|
||||
}
|
||||
|
||||
func newTestPartWrappersForRowsCount(rowsCount []int) []*partWrapper {
|
||||
func newTestPartWrappersForRowsCount(rowsCount []uint64) []*partWrapper {
|
||||
var pws []*partWrapper
|
||||
for _, rc := range rowsCount {
|
||||
pw := &partWrapper{
|
||||
p: &part{
|
||||
ph: partHeader{
|
||||
RowsCount: uint64(rc),
|
||||
partInternals: partInternals{
|
||||
ph: partHeader{
|
||||
RowsCount: rc,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -48,42 +48,30 @@ type rawRowsSort []rawRow
|
||||
func (rrs *rawRowsSort) Len() int { return len(*rrs) }
|
||||
func (rrs *rawRowsSort) Less(i, j int) bool {
|
||||
x := *rrs
|
||||
if i < 0 || j < 0 || i >= len(x) || j >= len(x) {
|
||||
// This is no-op for compiler, so it doesn't generate panic code
|
||||
// for out of range access on x[i], x[j] below
|
||||
return false
|
||||
}
|
||||
a := &x[i]
|
||||
b := &x[j]
|
||||
ta := &a.TSID
|
||||
tb := &b.TSID
|
||||
if ta.MetricID == tb.MetricID {
|
||||
// Fast path - identical TSID values.
|
||||
return a.Timestamp < b.Timestamp
|
||||
}
|
||||
|
||||
// Slow path - compare TSIDs.
|
||||
// Manually inline TSID.Less here, since the compiler doesn't inline it yet :(
|
||||
if ta.MetricGroupID < tb.MetricGroupID {
|
||||
return true
|
||||
if ta.MetricGroupID != tb.MetricGroupID {
|
||||
return ta.MetricGroupID < tb.MetricGroupID
|
||||
}
|
||||
if ta.MetricGroupID > tb.MetricGroupID {
|
||||
return false
|
||||
if ta.JobID != tb.JobID {
|
||||
return ta.JobID < tb.JobID
|
||||
}
|
||||
if ta.JobID < tb.JobID {
|
||||
return true
|
||||
if ta.InstanceID != tb.InstanceID {
|
||||
return ta.InstanceID < tb.InstanceID
|
||||
}
|
||||
if ta.JobID > tb.JobID {
|
||||
return false
|
||||
if ta.MetricID != tb.MetricID {
|
||||
return ta.MetricID < tb.MetricID
|
||||
}
|
||||
if ta.InstanceID < tb.InstanceID {
|
||||
return true
|
||||
}
|
||||
if ta.InstanceID > tb.InstanceID {
|
||||
return false
|
||||
}
|
||||
if ta.MetricID < tb.MetricID {
|
||||
return true
|
||||
}
|
||||
if ta.MetricID > tb.MetricID {
|
||||
return false
|
||||
}
|
||||
return false
|
||||
return a.Timestamp < b.Timestamp
|
||||
}
|
||||
func (rrs *rawRowsSort) Swap(i, j int) {
|
||||
x := *rrs
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/memory"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timerpool"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/uint64set"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/workingsetcache"
|
||||
"github.com/VictoriaMetrics/fastcache"
|
||||
)
|
||||
@@ -28,6 +29,15 @@ const maxRetentionMonths = 12 * 100
|
||||
|
||||
// Storage represents TSDB storage.
|
||||
type Storage struct {
|
||||
// Atomic counters must go at the top of the structure in order to properly align by 8 bytes on 32-bit archs.
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/212 .
|
||||
tooSmallTimestampRows uint64
|
||||
tooBigTimestampRows uint64
|
||||
|
||||
addRowsConcurrencyLimitReached uint64
|
||||
addRowsConcurrencyLimitTimeout uint64
|
||||
addRowsConcurrencyDroppedRows uint64
|
||||
|
||||
path string
|
||||
cachePath string
|
||||
retentionMonths int
|
||||
@@ -59,19 +69,12 @@ type Storage struct {
|
||||
|
||||
// Pending MetricID values to be added to currHourMetricIDs.
|
||||
pendingHourMetricIDsLock sync.Mutex
|
||||
pendingHourMetricIDs map[uint64]struct{}
|
||||
pendingHourMetricIDs *uint64set.Set
|
||||
|
||||
stop chan struct{}
|
||||
|
||||
currHourMetricIDsUpdaterWG sync.WaitGroup
|
||||
retentionWatcherWG sync.WaitGroup
|
||||
|
||||
tooSmallTimestampRows uint64
|
||||
tooBigTimestampRows uint64
|
||||
|
||||
addRowsConcurrencyLimitReached uint64
|
||||
addRowsConcurrencyLimitTimeout uint64
|
||||
addRowsConcurrencyDroppedRows uint64
|
||||
}
|
||||
|
||||
// OpenStorage opens storage on the given path with the given number of retention months.
|
||||
@@ -122,7 +125,7 @@ func OpenStorage(path string, retentionMonths int) (*Storage, error) {
|
||||
hmPrev := s.mustLoadHourMetricIDs(hour-1, "prev_hour_metric_ids")
|
||||
s.currHourMetricIDs.Store(hmCurr)
|
||||
s.prevHourMetricIDs.Store(hmPrev)
|
||||
s.pendingHourMetricIDs = make(map[uint64]struct{})
|
||||
s.pendingHourMetricIDs = &uint64set.Set{}
|
||||
|
||||
// Load indexdb
|
||||
idbPath := path + "/indexdb"
|
||||
@@ -158,7 +161,7 @@ func (s *Storage) debugFlush() {
|
||||
s.idb().tb.DebugFlush()
|
||||
}
|
||||
|
||||
func (s *Storage) getDeletedMetricIDs() map[uint64]struct{} {
|
||||
func (s *Storage) getDeletedMetricIDs() *uint64set.Set {
|
||||
return s.idb().getDeletedMetricIDs()
|
||||
}
|
||||
|
||||
@@ -364,9 +367,9 @@ func (s *Storage) UpdateMetrics(m *Metrics) {
|
||||
|
||||
hmCurr := s.currHourMetricIDs.Load().(*hourMetricIDs)
|
||||
hmPrev := s.prevHourMetricIDs.Load().(*hourMetricIDs)
|
||||
hourMetricIDsLen := len(hmPrev.m)
|
||||
if len(hmCurr.m) > hourMetricIDsLen {
|
||||
hourMetricIDsLen = len(hmCurr.m)
|
||||
hourMetricIDsLen := hmPrev.m.Len()
|
||||
if hmCurr.m.Len() > hourMetricIDsLen {
|
||||
hourMetricIDsLen = hmCurr.m.Len()
|
||||
}
|
||||
m.HourMetricIDCacheSize += uint64(hourMetricIDsLen)
|
||||
|
||||
@@ -508,11 +511,11 @@ func (s *Storage) mustLoadHourMetricIDs(hour uint64, name string) *hourMetricIDs
|
||||
logger.Errorf("discarding %s, since it has broken body; got %d bytes; want %d bytes", path, len(src), 8*hmLen)
|
||||
return &hourMetricIDs{}
|
||||
}
|
||||
m := make(map[uint64]struct{}, hmLen)
|
||||
m := &uint64set.Set{}
|
||||
for i := uint64(0); i < hmLen; i++ {
|
||||
metricID := encoding.UnmarshalUint64(src)
|
||||
src = src[8:]
|
||||
m[metricID] = struct{}{}
|
||||
m.Add(metricID)
|
||||
}
|
||||
logger.Infof("loaded %s from %q in %s; entriesCount: %d; sizeBytes: %d", name, path, time.Since(startTime), hmLen, srcOrigLen)
|
||||
return &hourMetricIDs{
|
||||
@@ -526,21 +529,21 @@ func (s *Storage) mustSaveHourMetricIDs(hm *hourMetricIDs, name string) {
|
||||
path := s.cachePath + "/" + name
|
||||
logger.Infof("saving %s to %q...", name, path)
|
||||
startTime := time.Now()
|
||||
dst := make([]byte, 0, len(hm.m)*8+24)
|
||||
dst := make([]byte, 0, hm.m.Len()*8+24)
|
||||
isFull := uint64(0)
|
||||
if hm.isFull {
|
||||
isFull = 1
|
||||
}
|
||||
dst = encoding.MarshalUint64(dst, isFull)
|
||||
dst = encoding.MarshalUint64(dst, hm.hour)
|
||||
dst = encoding.MarshalUint64(dst, uint64(len(hm.m)))
|
||||
for metricID := range hm.m {
|
||||
dst = encoding.MarshalUint64(dst, uint64(hm.m.Len()))
|
||||
for _, metricID := range hm.m.AppendTo(nil) {
|
||||
dst = encoding.MarshalUint64(dst, metricID)
|
||||
}
|
||||
if err := ioutil.WriteFile(path, dst, 0644); err != nil {
|
||||
logger.Panicf("FATAL: cannot write %d bytes to %q: %s", len(dst), path, err)
|
||||
}
|
||||
logger.Infof("saved %s to %q in %s; entriesCount: %d; sizeBytes: %d", name, path, time.Since(startTime), len(hm.m), len(dst))
|
||||
logger.Infof("saved %s to %q in %s; entriesCount: %d; sizeBytes: %d", name, path, time.Since(startTime), hm.m.Len(), len(dst))
|
||||
}
|
||||
|
||||
func (s *Storage) mustLoadCache(info, name string, sizeBytes int) *workingsetcache.Cache {
|
||||
@@ -579,7 +582,7 @@ func nextRetentionDuration(retentionMonths int) time.Duration {
|
||||
return deadline.Sub(t)
|
||||
}
|
||||
|
||||
// searchTSIDs returns TSIDs for the given tfss and the given tr.
|
||||
// searchTSIDs returns sorted TSIDs for the given tfss and the given tr.
|
||||
func (s *Storage) searchTSIDs(tfss []*TagFilters, tr TimeRange, maxMetrics int) ([]TSID, error) {
|
||||
// Do not cache tfss -> tsids here, since the caching is performed
|
||||
// on idb level.
|
||||
@@ -770,7 +773,7 @@ var (
|
||||
|
||||
func (s *Storage) add(rows []rawRow, mrs []MetricRow, precisionBits uint8) ([]rawRow, error) {
|
||||
// Return only the last error, since it has no sense in returning all errors.
|
||||
var lastError error
|
||||
var lastWarn error
|
||||
|
||||
var is *indexSearch
|
||||
var mn *MetricName
|
||||
@@ -794,13 +797,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)
|
||||
lastWarn = 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)
|
||||
lastWarn = 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
|
||||
}
|
||||
@@ -810,11 +813,7 @@ func (s *Storage) add(rows []rawRow, mrs []MetricRow, precisionBits uint8) ([]ra
|
||||
r.Value = mr.Value
|
||||
r.PrecisionBits = precisionBits
|
||||
if s.getTSIDFromCache(&r.TSID, mr.MetricNameRaw) {
|
||||
if len(dmis) == 0 {
|
||||
// Fast path - the TSID for the given MetricName has been found in cache and isn't deleted.
|
||||
continue
|
||||
}
|
||||
if _, deleted := dmis[r.TSID.MetricID]; !deleted {
|
||||
if !dmis.Has(r.TSID.MetricID) {
|
||||
// Fast path - the TSID for the given MetricName has been found in cache and isn't deleted.
|
||||
continue
|
||||
}
|
||||
@@ -830,7 +829,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.
|
||||
lastError = fmt.Errorf("cannot unmarshal MetricNameRaw %q: %s", mr.MetricNameRaw, err)
|
||||
lastWarn = fmt.Errorf("cannot unmarshal MetricNameRaw %q: %s", mr.MetricNameRaw, err)
|
||||
j--
|
||||
continue
|
||||
}
|
||||
@@ -840,12 +839,15 @@ 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.
|
||||
lastError = fmt.Errorf("cannot obtain TSID for MetricName %q: %s", kb.B, err)
|
||||
lastWarn = fmt.Errorf("cannot obtain TSID for MetricName %q: %s", kb.B, err)
|
||||
j--
|
||||
continue
|
||||
}
|
||||
s.putTSIDToCache(&r.TSID, mr.MetricNameRaw)
|
||||
}
|
||||
if lastWarn != nil {
|
||||
logger.Errorf("warn occurred during rows addition: %s", lastWarn)
|
||||
}
|
||||
if is != nil {
|
||||
kbPool.Put(kb)
|
||||
PutMetricName(mn)
|
||||
@@ -853,12 +855,15 @@ func (s *Storage) add(rows []rawRow, mrs []MetricRow, precisionBits uint8) ([]ra
|
||||
}
|
||||
rows = rows[:rowsLen+j]
|
||||
|
||||
var lastError error
|
||||
if err := s.tb.AddRows(rows); err != nil {
|
||||
lastError = fmt.Errorf("cannot add rows to table: %s", err)
|
||||
}
|
||||
lastError = s.updateDateMetricIDCache(rows, lastError)
|
||||
if err := s.updateDateMetricIDCache(rows, lastError); err != nil {
|
||||
lastError = err
|
||||
}
|
||||
if lastError != nil {
|
||||
return rows, fmt.Errorf("errors occurred during rows addition: %s", lastError)
|
||||
return rows, fmt.Errorf("error occurred during rows addition: %s", lastError)
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
@@ -884,12 +889,12 @@ func (s *Storage) updateDateMetricIDCache(rows []rawRow, lastError error) error
|
||||
hm := s.currHourMetricIDs.Load().(*hourMetricIDs)
|
||||
if hour == hm.hour {
|
||||
// The r belongs to the current hour. Check for the current hour cache.
|
||||
if _, ok := hm.m[metricID]; ok {
|
||||
if hm.m.Has(metricID) {
|
||||
// Fast path: the metricID is in the current hour cache.
|
||||
continue
|
||||
}
|
||||
s.pendingHourMetricIDsLock.Lock()
|
||||
s.pendingHourMetricIDs[metricID] = struct{}{}
|
||||
s.pendingHourMetricIDs.Add(metricID)
|
||||
s.pendingHourMetricIDsLock.Unlock()
|
||||
}
|
||||
|
||||
@@ -915,7 +920,7 @@ func (s *Storage) updateDateMetricIDCache(rows []rawRow, lastError error) error
|
||||
func (s *Storage) updateCurrHourMetricIDs() {
|
||||
hm := s.currHourMetricIDs.Load().(*hourMetricIDs)
|
||||
s.pendingHourMetricIDsLock.Lock()
|
||||
newMetricIDsLen := len(s.pendingHourMetricIDs)
|
||||
newMetricIDsLen := s.pendingHourMetricIDs.Len()
|
||||
s.pendingHourMetricIDsLock.Unlock()
|
||||
hour := uint64(timestampFromTime(time.Now())) / msecPerHour
|
||||
if newMetricIDsLen == 0 && hm.hour == hour {
|
||||
@@ -924,24 +929,19 @@ func (s *Storage) updateCurrHourMetricIDs() {
|
||||
}
|
||||
|
||||
// Slow path: hm.m must be updated with non-empty s.pendingHourMetricIDs.
|
||||
var m map[uint64]struct{}
|
||||
var m *uint64set.Set
|
||||
isFull := hm.isFull
|
||||
if hm.hour == hour {
|
||||
m = make(map[uint64]struct{}, len(hm.m)+newMetricIDsLen)
|
||||
for metricID := range hm.m {
|
||||
m[metricID] = struct{}{}
|
||||
}
|
||||
m = hm.m.Clone()
|
||||
} else {
|
||||
m = make(map[uint64]struct{}, newMetricIDsLen)
|
||||
m = &uint64set.Set{}
|
||||
isFull = true
|
||||
}
|
||||
s.pendingHourMetricIDsLock.Lock()
|
||||
newMetricIDs := s.pendingHourMetricIDs
|
||||
s.pendingHourMetricIDs = make(map[uint64]struct{}, len(newMetricIDs))
|
||||
s.pendingHourMetricIDs = &uint64set.Set{}
|
||||
s.pendingHourMetricIDsLock.Unlock()
|
||||
for metricID := range newMetricIDs {
|
||||
m[metricID] = struct{}{}
|
||||
}
|
||||
m.Union(newMetricIDs)
|
||||
|
||||
hmNew := &hourMetricIDs{
|
||||
m: m,
|
||||
@@ -955,7 +955,7 @@ func (s *Storage) updateCurrHourMetricIDs() {
|
||||
}
|
||||
|
||||
type hourMetricIDs struct {
|
||||
m map[uint64]struct{}
|
||||
m *uint64set.Set
|
||||
hour uint64
|
||||
isFull bool
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"testing"
|
||||
"testing/quick"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/uint64set"
|
||||
)
|
||||
|
||||
func TestUpdateCurrHourMetricIDs(t *testing.T) {
|
||||
@@ -16,19 +18,18 @@ func TestUpdateCurrHourMetricIDs(t *testing.T) {
|
||||
var s Storage
|
||||
s.currHourMetricIDs.Store(&hourMetricIDs{})
|
||||
s.prevHourMetricIDs.Store(&hourMetricIDs{})
|
||||
s.pendingHourMetricIDs = make(map[uint64]struct{})
|
||||
s.pendingHourMetricIDs = &uint64set.Set{}
|
||||
return &s
|
||||
}
|
||||
t.Run("empty_pedning_metric_ids_stale_curr_hour", func(t *testing.T) {
|
||||
s := newStorage()
|
||||
hour := uint64(timestampFromTime(time.Now())) / msecPerHour
|
||||
hmOrig := &hourMetricIDs{
|
||||
m: map[uint64]struct{}{
|
||||
12: {},
|
||||
34: {},
|
||||
},
|
||||
m: &uint64set.Set{},
|
||||
hour: 123,
|
||||
}
|
||||
hmOrig.m.Add(12)
|
||||
hmOrig.m.Add(34)
|
||||
s.currHourMetricIDs.Store(hmOrig)
|
||||
s.updateCurrHourMetricIDs()
|
||||
hmCurr := s.currHourMetricIDs.Load().(*hourMetricIDs)
|
||||
@@ -39,8 +40,8 @@ func TestUpdateCurrHourMetricIDs(t *testing.T) {
|
||||
t.Fatalf("unexpected hmCurr.hour; got %d; want %d", hmCurr.hour, hour)
|
||||
}
|
||||
}
|
||||
if len(hmCurr.m) != 0 {
|
||||
t.Fatalf("unexpected length of hm.m; got %d; want %d", len(hmCurr.m), 0)
|
||||
if hmCurr.m.Len() != 0 {
|
||||
t.Fatalf("unexpected length of hm.m; got %d; want %d", hmCurr.m.Len(), 0)
|
||||
}
|
||||
if !hmCurr.isFull {
|
||||
t.Fatalf("unexpected hmCurr.isFull; got %v; want %v", hmCurr.isFull, true)
|
||||
@@ -51,20 +52,19 @@ func TestUpdateCurrHourMetricIDs(t *testing.T) {
|
||||
t.Fatalf("unexpected hmPrev; got %v; want %v", hmPrev, hmOrig)
|
||||
}
|
||||
|
||||
if len(s.pendingHourMetricIDs) != 0 {
|
||||
t.Fatalf("unexpected len(s.pendingHourMetricIDs); got %d; want %d", len(s.pendingHourMetricIDs), 0)
|
||||
if s.pendingHourMetricIDs.Len() != 0 {
|
||||
t.Fatalf("unexpected s.pendingHourMetricIDs.Len(); got %d; want %d", s.pendingHourMetricIDs.Len(), 0)
|
||||
}
|
||||
})
|
||||
t.Run("empty_pedning_metric_ids_valid_curr_hour", func(t *testing.T) {
|
||||
s := newStorage()
|
||||
hour := uint64(timestampFromTime(time.Now())) / msecPerHour
|
||||
hmOrig := &hourMetricIDs{
|
||||
m: map[uint64]struct{}{
|
||||
12: {},
|
||||
34: {},
|
||||
},
|
||||
m: &uint64set.Set{},
|
||||
hour: hour,
|
||||
}
|
||||
hmOrig.m.Add(12)
|
||||
hmOrig.m.Add(34)
|
||||
s.currHourMetricIDs.Store(hmOrig)
|
||||
s.updateCurrHourMetricIDs()
|
||||
hmCurr := s.currHourMetricIDs.Load().(*hourMetricIDs)
|
||||
@@ -90,27 +90,25 @@ func TestUpdateCurrHourMetricIDs(t *testing.T) {
|
||||
t.Fatalf("unexpected hmPrev; got %v; want %v", hmPrev, hmEmpty)
|
||||
}
|
||||
|
||||
if len(s.pendingHourMetricIDs) != 0 {
|
||||
t.Fatalf("unexpected len(s.pendingHourMetricIDs); got %d; want %d", len(s.pendingHourMetricIDs), 0)
|
||||
if s.pendingHourMetricIDs.Len() != 0 {
|
||||
t.Fatalf("unexpected s.pendingHourMetricIDs.Len(); got %d; want %d", s.pendingHourMetricIDs.Len(), 0)
|
||||
}
|
||||
})
|
||||
t.Run("nonempty_pending_metric_ids_stale_curr_hour", func(t *testing.T) {
|
||||
s := newStorage()
|
||||
pendingHourMetricIDs := map[uint64]struct{}{
|
||||
343: {},
|
||||
32424: {},
|
||||
8293432: {},
|
||||
}
|
||||
pendingHourMetricIDs := &uint64set.Set{}
|
||||
pendingHourMetricIDs.Add(343)
|
||||
pendingHourMetricIDs.Add(32424)
|
||||
pendingHourMetricIDs.Add(8293432)
|
||||
s.pendingHourMetricIDs = pendingHourMetricIDs
|
||||
|
||||
hour := uint64(timestampFromTime(time.Now())) / msecPerHour
|
||||
hmOrig := &hourMetricIDs{
|
||||
m: map[uint64]struct{}{
|
||||
12: {},
|
||||
34: {},
|
||||
},
|
||||
m: &uint64set.Set{},
|
||||
hour: 123,
|
||||
}
|
||||
hmOrig.m.Add(12)
|
||||
hmOrig.m.Add(34)
|
||||
s.currHourMetricIDs.Store(hmOrig)
|
||||
s.updateCurrHourMetricIDs()
|
||||
hmCurr := s.currHourMetricIDs.Load().(*hourMetricIDs)
|
||||
@@ -133,27 +131,25 @@ func TestUpdateCurrHourMetricIDs(t *testing.T) {
|
||||
t.Fatalf("unexpected hmPrev; got %v; want %v", hmPrev, hmOrig)
|
||||
}
|
||||
|
||||
if len(s.pendingHourMetricIDs) != 0 {
|
||||
t.Fatalf("unexpected len(s.pendingHourMetricIDs); got %d; want %d", len(s.pendingHourMetricIDs), 0)
|
||||
if s.pendingHourMetricIDs.Len() != 0 {
|
||||
t.Fatalf("unexpected s.pendingHourMetricIDs.Len(); got %d; want %d", s.pendingHourMetricIDs.Len(), 0)
|
||||
}
|
||||
})
|
||||
t.Run("nonempty_pending_metric_ids_valid_curr_hour", func(t *testing.T) {
|
||||
s := newStorage()
|
||||
pendingHourMetricIDs := map[uint64]struct{}{
|
||||
343: {},
|
||||
32424: {},
|
||||
8293432: {},
|
||||
}
|
||||
pendingHourMetricIDs := &uint64set.Set{}
|
||||
pendingHourMetricIDs.Add(343)
|
||||
pendingHourMetricIDs.Add(32424)
|
||||
pendingHourMetricIDs.Add(8293432)
|
||||
s.pendingHourMetricIDs = pendingHourMetricIDs
|
||||
|
||||
hour := uint64(timestampFromTime(time.Now())) / msecPerHour
|
||||
hmOrig := &hourMetricIDs{
|
||||
m: map[uint64]struct{}{
|
||||
12: {},
|
||||
34: {},
|
||||
},
|
||||
m: &uint64set.Set{},
|
||||
hour: hour,
|
||||
}
|
||||
hmOrig.m.Add(12)
|
||||
hmOrig.m.Add(34)
|
||||
s.currHourMetricIDs.Store(hmOrig)
|
||||
s.updateCurrHourMetricIDs()
|
||||
hmCurr := s.currHourMetricIDs.Load().(*hourMetricIDs)
|
||||
@@ -166,9 +162,10 @@ func TestUpdateCurrHourMetricIDs(t *testing.T) {
|
||||
// Do not run other checks, since they may fail.
|
||||
return
|
||||
}
|
||||
m := getMetricIDsCopy(pendingHourMetricIDs)
|
||||
for metricID := range hmOrig.m {
|
||||
m[metricID] = struct{}{}
|
||||
m := pendingHourMetricIDs.Clone()
|
||||
origMetricIDs := hmOrig.m.AppendTo(nil)
|
||||
for _, metricID := range origMetricIDs {
|
||||
m.Add(metricID)
|
||||
}
|
||||
if !reflect.DeepEqual(hmCurr.m, m) {
|
||||
t.Fatalf("unexpected hm.m; got %v; want %v", hmCurr.m, m)
|
||||
@@ -183,8 +180,8 @@ func TestUpdateCurrHourMetricIDs(t *testing.T) {
|
||||
t.Fatalf("unexpected hmPrev; got %v; want %v", hmPrev, hmEmpty)
|
||||
}
|
||||
|
||||
if len(s.pendingHourMetricIDs) != 0 {
|
||||
t.Fatalf("unexpected len(s.pendingHourMetricIDs); got %d; want %d", len(s.pendingHourMetricIDs), 0)
|
||||
if s.pendingHourMetricIDs.Len() != 0 {
|
||||
t.Fatalf("unexpected s.pendingHourMetricIDs.Len(); got %d; want %d", s.pendingHourMetricIDs.Len(), 0)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/uint64set"
|
||||
)
|
||||
|
||||
// table represents a single table with time series data.
|
||||
@@ -18,7 +19,7 @@ type table struct {
|
||||
smallPartitionsPath string
|
||||
bigPartitionsPath string
|
||||
|
||||
getDeletedMetricIDs func() map[uint64]struct{}
|
||||
getDeletedMetricIDs func() *uint64set.Set
|
||||
|
||||
ptws []*partitionWrapper
|
||||
ptwsLock sync.Mutex
|
||||
@@ -33,11 +34,15 @@ type table struct {
|
||||
|
||||
// partitionWrapper provides refcounting mechanism for the partition.
|
||||
type partitionWrapper struct {
|
||||
pt *partition
|
||||
// Atomic counters must be at the top of struct for proper 8-byte alignment on 32-bit archs.
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/212
|
||||
|
||||
refCount uint64
|
||||
|
||||
// The partition must be dropped if mustDrop > 0
|
||||
mustDrop uint64
|
||||
|
||||
pt *partition
|
||||
}
|
||||
|
||||
func (ptw *partitionWrapper) incRef() {
|
||||
@@ -75,7 +80,7 @@ func (ptw *partitionWrapper) scheduleToDrop() {
|
||||
// The table is created if it doesn't exist.
|
||||
//
|
||||
// Data older than the retentionMonths may be dropped at any time.
|
||||
func openTable(path string, retentionMonths int, getDeletedMetricIDs func() map[uint64]struct{}) (*table, error) {
|
||||
func openTable(path string, retentionMonths int, getDeletedMetricIDs func() *uint64set.Set) (*table, error) {
|
||||
path = filepath.Clean(path)
|
||||
|
||||
// Create a directory for the table if it doesn't exist yet.
|
||||
@@ -430,7 +435,7 @@ func (tb *table) PutPartitions(ptws []*partitionWrapper) {
|
||||
}
|
||||
}
|
||||
|
||||
func openPartitions(smallPartitionsPath, bigPartitionsPath string, getDeletedMetricIDs func() map[uint64]struct{}) ([]*partition, error) {
|
||||
func openPartitions(smallPartitionsPath, bigPartitionsPath string, getDeletedMetricIDs func() *uint64set.Set) ([]*partition, error) {
|
||||
smallD, err := os.Open(smallPartitionsPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot open directory with small partitions %q: %s", smallPartitionsPath, err)
|
||||
|
||||
@@ -54,6 +54,9 @@ func (ts *tableSearch) reset() {
|
||||
|
||||
// Init initializes the ts.
|
||||
//
|
||||
// tsids must be sorted.
|
||||
// tsids cannot be modified after the Init call, since it is owned by ts.
|
||||
//
|
||||
// MustClose must be called then the tableSearch is done.
|
||||
func (ts *tableSearch) Init(tb *table, tsids []TSID, tr TimeRange, fetchData bool) {
|
||||
if ts.needClosing {
|
||||
|
||||
@@ -19,14 +19,14 @@ type TagFilters struct {
|
||||
tfs []tagFilter
|
||||
|
||||
// Common prefix for all the tag filters.
|
||||
// Contains encoded nsPrefixTagToMetricID.
|
||||
// Contains encoded nsPrefixTagToMetricIDs.
|
||||
commonPrefix []byte
|
||||
}
|
||||
|
||||
// NewTagFilters returns new TagFilters.
|
||||
func NewTagFilters() *TagFilters {
|
||||
return &TagFilters{
|
||||
commonPrefix: marshalCommonPrefix(nil, nsPrefixTagToMetricID),
|
||||
commonPrefix: marshalCommonPrefix(nil, nsPrefixTagToMetricIDs),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ func (tfs *TagFilters) String() string {
|
||||
// Reset resets the tf
|
||||
func (tfs *TagFilters) Reset() {
|
||||
tfs.tfs = tfs.tfs[:0]
|
||||
tfs.commonPrefix = marshalCommonPrefix(tfs.commonPrefix[:0], nsPrefixTagToMetricID)
|
||||
tfs.commonPrefix = marshalCommonPrefix(tfs.commonPrefix[:0], nsPrefixTagToMetricIDs)
|
||||
}
|
||||
|
||||
func (tfs *TagFilters) marshal(dst []byte) []byte {
|
||||
@@ -95,7 +95,7 @@ type tagFilter struct {
|
||||
isNegative bool
|
||||
isRegexp bool
|
||||
|
||||
// Prefix always contains {nsPrefixTagToMetricID, key}.
|
||||
// Prefix always contains {nsPrefixTagToMetricIDs, key}.
|
||||
// Additionally it contains:
|
||||
// - value ending with tagSeparatorChar if !isRegexp.
|
||||
// - non-regexp prefix if isRegexp.
|
||||
@@ -317,6 +317,9 @@ func getSingleValueFuncExt(re *syntax.Regexp) func(b []byte) bool {
|
||||
case syntax.OpCapture:
|
||||
return getSingleValueFuncExt(re.Sub[0])
|
||||
case syntax.OpLiteral:
|
||||
if !isLiteral(re) {
|
||||
return nil
|
||||
}
|
||||
s := string(re.Rune)
|
||||
return func(b []byte) bool {
|
||||
return string(b) == s
|
||||
@@ -399,7 +402,7 @@ func isLiteral(re *syntax.Regexp) bool {
|
||||
if re.Op == syntax.OpCapture {
|
||||
return isLiteral(re.Sub[0])
|
||||
}
|
||||
return re.Op == syntax.OpLiteral
|
||||
return re.Op == syntax.OpLiteral && re.Flags&syntax.FoldCase == 0
|
||||
}
|
||||
|
||||
func getOrValues(expr string) []string {
|
||||
@@ -420,6 +423,9 @@ func getOrValuesExt(re *syntax.Regexp) []string {
|
||||
case syntax.OpCapture:
|
||||
return getOrValuesExt(re.Sub[0])
|
||||
case syntax.OpLiteral:
|
||||
if !isLiteral(re) {
|
||||
return nil
|
||||
}
|
||||
return []string{string(re.Rune)}
|
||||
case syntax.OpEmptyMatch:
|
||||
return []string{""}
|
||||
@@ -592,13 +598,13 @@ func extractRegexpPrefix(b []byte) ([]byte, []byte) {
|
||||
if re == emptyRegexp {
|
||||
return nil, nil
|
||||
}
|
||||
if re.Op == syntax.OpLiteral && re.Flags&syntax.FoldCase == 0 {
|
||||
if isLiteral(re) {
|
||||
return []byte(string(re.Rune)), nil
|
||||
}
|
||||
var prefix []byte
|
||||
if re.Op == syntax.OpConcat {
|
||||
sub0 := re.Sub[0]
|
||||
if sub0.Op == syntax.OpLiteral && sub0.Flags&syntax.FoldCase == 0 {
|
||||
if isLiteral(sub0) {
|
||||
prefix = []byte(string(sub0.Rune))
|
||||
re.Sub = re.Sub[1:]
|
||||
if len(re.Sub) == 0 {
|
||||
|
||||
@@ -53,11 +53,17 @@ func TestGetRegexpFromCache(t *testing.T) {
|
||||
f("((.*)foo(.*))", nil, []string{"foo", "xfoo", "foox", "xfoobar"}, []string{"", "bar", "foxx"})
|
||||
f(".+foo", nil, []string{"afoo", "bbfoo"}, []string{"foo", "foobar", "afoox", ""})
|
||||
f("a|b", []string{"a", "b"}, []string{"a", "b"}, []string{"xa", "bx", "xab", ""})
|
||||
f("(a|b)", []string{"a", "b"}, []string{"a", "b"}, []string{"xa", "bx", "xab", ""})
|
||||
f("foo.+", nil, []string{"foox", "foobar"}, []string{"foo", "afoox", "afoo", ""})
|
||||
f(".*foo.*bar", nil, []string{"foobar", "xfoobar", "xfooxbar", "fooxbar"}, []string{"", "foobarx", "afoobarx", "aaa"})
|
||||
f("foo.*bar", nil, []string{"foobar", "fooxbar"}, []string{"xfoobar", "", "foobarx", "aaa"})
|
||||
f("foo.*bar.*", nil, []string{"foobar", "fooxbar", "foobarx", "fooxbarx"}, []string{"", "afoobarx", "aaa", "afoobar"})
|
||||
|
||||
f("(?i)foo", nil, []string{"foo", "Foo", "FOO"}, []string{"xfoo", "foobar", "xFOObar"})
|
||||
f("(?i).+foo", nil, []string{"xfoo", "aaFoo", "bArFOO"}, []string{"foosdf", "xFOObar"})
|
||||
f("(?i)(foo|bar)", nil, []string{"foo", "Foo", "BAR", "bAR"}, []string{"foobar", "xfoo", "xFOObAR"})
|
||||
f("(?i)foo.*bar", nil, []string{"foobar", "FooBAR", "FOOxxbaR"}, []string{"xfoobar", "foobarx", "xFOObarx"})
|
||||
|
||||
f(".*", nil, []string{"", "a", "foo", "foobar"}, nil)
|
||||
f("foo|.*", nil, []string{"", "a", "foo", "foobar"}, nil)
|
||||
f(".+", nil, []string{"a", "foo"}, []string{""})
|
||||
@@ -323,6 +329,76 @@ func TestTagFilterMatchSuffix(t *testing.T) {
|
||||
mismatch("bar")
|
||||
match("xhttpbar")
|
||||
})
|
||||
t.Run("regexp-iflag-no-suffix", func(t *testing.T) {
|
||||
value := "(?i)http"
|
||||
isNegative := false
|
||||
isRegexp := true
|
||||
expectedPrefix := tvNoTrailingTagSeparator("")
|
||||
init(value, isNegative, isRegexp, expectedPrefix)
|
||||
|
||||
// Must match case-insenstive http
|
||||
match("http")
|
||||
match("HTTP")
|
||||
match("hTTp")
|
||||
|
||||
mismatch("")
|
||||
mismatch("foobar")
|
||||
mismatch("xhttp")
|
||||
mismatch("xhttp://")
|
||||
mismatch("hTTp://foobar.com")
|
||||
})
|
||||
t.Run("negative-regexp-iflag-no-suffix", func(t *testing.T) {
|
||||
value := "(?i)http"
|
||||
isNegative := true
|
||||
isRegexp := true
|
||||
expectedPrefix := tvNoTrailingTagSeparator("")
|
||||
init(value, isNegative, isRegexp, expectedPrefix)
|
||||
|
||||
// Mustn't match case-insensitive http
|
||||
mismatch("http")
|
||||
mismatch("HTTP")
|
||||
mismatch("hTTp")
|
||||
|
||||
match("")
|
||||
match("foobar")
|
||||
match("xhttp")
|
||||
match("xhttp://")
|
||||
match("hTTp://foobar.com")
|
||||
})
|
||||
t.Run("regexp-iflag-any-suffix", func(t *testing.T) {
|
||||
value := "(?i)http.*"
|
||||
isNegative := false
|
||||
isRegexp := true
|
||||
expectedPrefix := tvNoTrailingTagSeparator("")
|
||||
init(value, isNegative, isRegexp, expectedPrefix)
|
||||
|
||||
// Must match case-insenstive http
|
||||
match("http")
|
||||
match("HTTP")
|
||||
match("hTTp://foobar.com")
|
||||
|
||||
mismatch("")
|
||||
mismatch("foobar")
|
||||
mismatch("xhttp")
|
||||
mismatch("xhttp://")
|
||||
})
|
||||
t.Run("negative-regexp-iflag-any-suffix", func(t *testing.T) {
|
||||
value := "(?i)http.*"
|
||||
isNegative := true
|
||||
isRegexp := true
|
||||
expectedPrefix := tvNoTrailingTagSeparator("")
|
||||
init(value, isNegative, isRegexp, expectedPrefix)
|
||||
|
||||
// Mustn't match case-insensitive http
|
||||
mismatch("http")
|
||||
mismatch("HTTP")
|
||||
mismatch("hTTp://foobar.com")
|
||||
|
||||
match("")
|
||||
match("foobar")
|
||||
match("xhttp")
|
||||
match("xhttp://")
|
||||
})
|
||||
t.Run("non-empty-string-regexp-negative-match", func(t *testing.T) {
|
||||
value := ".+"
|
||||
isNegative := true
|
||||
@@ -409,6 +485,8 @@ func TestGetOrValues(t *testing.T) {
|
||||
f("foo(?:bar|baz)x(qwe|rt)", []string{"foobarxqwe", "foobarxrt", "foobazxqwe", "foobazxrt"})
|
||||
f("foo(bar||baz)", []string{"foo", "foobar", "foobaz"})
|
||||
f("(a|b|c)(d|e|f)(g|h|k)", nil)
|
||||
f("(?i)foo", nil)
|
||||
f("(?i)(foo|bar)", nil)
|
||||
}
|
||||
|
||||
func TestGetRegexpPrefix(t *testing.T) {
|
||||
@@ -463,6 +541,7 @@ func TestGetRegexpPrefix(t *testing.T) {
|
||||
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)foo|bar", "", "(?i:FOO)|(?i:BAR)")
|
||||
f(t, "(?i)up.+x", "", "(?i:UP)(?-s:.)+(?i:X)")
|
||||
f(t, "(?smi)xy.*z$", "", "(?i:XY)(?s:.)*(?i:Z)(?m:$)")
|
||||
|
||||
|
||||
@@ -88,34 +88,16 @@ func (t *TSID) Unmarshal(src []byte) ([]byte, error) {
|
||||
|
||||
// Less return true if t < b.
|
||||
func (t *TSID) Less(b *TSID) bool {
|
||||
if t.MetricID == b.MetricID {
|
||||
// Fast path - two TSID values are identical.
|
||||
return false
|
||||
// Do not compare MetricIDs here as fast path for determining identical TSIDs,
|
||||
// since identical TSIDs aren't passed here in hot paths.
|
||||
if t.MetricGroupID != b.MetricGroupID {
|
||||
return t.MetricGroupID < b.MetricGroupID
|
||||
}
|
||||
|
||||
if t.MetricGroupID < b.MetricGroupID {
|
||||
return true
|
||||
if t.JobID != b.JobID {
|
||||
return t.JobID < b.JobID
|
||||
}
|
||||
if t.MetricGroupID > b.MetricGroupID {
|
||||
return false
|
||||
if t.InstanceID != b.InstanceID {
|
||||
return t.InstanceID < b.InstanceID
|
||||
}
|
||||
if t.JobID < b.JobID {
|
||||
return true
|
||||
}
|
||||
if t.JobID > b.JobID {
|
||||
return false
|
||||
}
|
||||
if t.InstanceID < b.InstanceID {
|
||||
return true
|
||||
}
|
||||
if t.InstanceID > b.InstanceID {
|
||||
return false
|
||||
}
|
||||
if t.MetricID < b.MetricID {
|
||||
return true
|
||||
}
|
||||
if t.MetricID > b.MetricID {
|
||||
return false
|
||||
}
|
||||
return false
|
||||
return t.MetricID < b.MetricID
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user