mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-06-03 09:02:16 +03:00
Compare commits
28 Commits
v1.24.0-vi
...
docs/trace
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83aef6f86d | ||
|
|
e08534a1c7 | ||
|
|
4aef00c2a9 | ||
|
|
88c617c51b | ||
|
|
5a053d5714 | ||
|
|
a79a7cea1b | ||
|
|
58469efd0d | ||
|
|
0767fe1367 | ||
|
|
d395ddec2d | ||
|
|
235a789a96 | ||
|
|
06bf32d1f6 | ||
|
|
ff32d15560 | ||
|
|
84044db837 | ||
|
|
f1e6aaa949 | ||
|
|
27f2cf84e5 | ||
|
|
8fff6e4173 | ||
|
|
9de6941147 | ||
|
|
8a2e208d1b | ||
|
|
d01cffd3e7 | ||
|
|
96a6443be1 | ||
|
|
ebbb921d90 | ||
|
|
f8ce411a70 | ||
|
|
1e3791cbf6 | ||
|
|
b1f3d7f3eb | ||
|
|
c2c6f97bd3 | ||
|
|
274e38ebee | ||
|
|
e05738749c | ||
|
|
3428d0d27d |
@@ -28,24 +28,6 @@ import (
|
||||
// See https://github.com/systemd/systemd/blob/main/src/libsystemd/sd-journal/journal-file.c#L1703
|
||||
const maxFieldNameLen = 64
|
||||
|
||||
func isValidJournaldFieldName(s string) bool {
|
||||
if len(s) == 0 {
|
||||
return false
|
||||
}
|
||||
c := s[0]
|
||||
if !(c >= 'A' && c <= 'Z' || c == '_') {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := 1; i < len(s); i++ {
|
||||
c := s[i]
|
||||
if !(c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c == '_') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var (
|
||||
journaldStreamFields = flagutil.NewArrayString("journald.streamFields", "Comma-separated list of fields to use as log stream fields for logs ingested over journald protocol. "+
|
||||
"See https://docs.victoriametrics.com/victorialogs/data-ingestion/journald/#stream-fields")
|
||||
@@ -319,7 +301,7 @@ func readJournaldLogEntry(streamName string, lr *insertutil.LineReader, lmp inse
|
||||
logger.Errorf("%s: field name size should not exceed %d bytes; got %d bytes: %q; skipping this field", streamName, maxFieldNameLen, len(name), name)
|
||||
continue
|
||||
}
|
||||
if !isValidJournaldFieldName(name) {
|
||||
if !isValidFieldName(name) {
|
||||
logger.Errorf("%s: invalid field name %q; it must consist of `A-Z0-9_` chars and must start from non-digit char; skipping this field", streamName, name)
|
||||
continue
|
||||
}
|
||||
@@ -375,3 +357,21 @@ func journaldPriorityToLevel(priority string) string {
|
||||
return priority
|
||||
}
|
||||
}
|
||||
|
||||
func isValidFieldName(s string) bool {
|
||||
if len(s) == 0 {
|
||||
return false
|
||||
}
|
||||
c := s[0]
|
||||
if !(c >= 'A' && c <= 'Z' || c == '_') {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := 1; i < len(s); i++ {
|
||||
c := s[i]
|
||||
if !(c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c == '_') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vlinsert/insertutil"
|
||||
)
|
||||
|
||||
func TestIsValidJournaldFieldName(t *testing.T) {
|
||||
func TestIsValidFieldName(t *testing.T) {
|
||||
f := func(name string, resultExpected bool) {
|
||||
t.Helper()
|
||||
|
||||
result := isValidJournaldFieldName(name)
|
||||
result := isValidFieldName(name)
|
||||
if result != resultExpected {
|
||||
t.Fatalf("unexpected result for isValidJournaldFieldName(%q); got %v; want %v", name, result, resultExpected)
|
||||
}
|
||||
|
||||
@@ -4,12 +4,55 @@ import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vlinsert/insertutil"
|
||||
)
|
||||
|
||||
func BenchmarkIsValidFieldName(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.SetBytes(int64(len(benchmarkFields)))
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
for _, field := range benchmarkFields {
|
||||
if !isValidFieldName(field) {
|
||||
panic(fmt.Errorf("cannot validate field %q", field))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var benchmarkFields = strings.Split(
|
||||
"E,_BOOT_ID,_UID,_GID,_MACHINE_ID,_HOSTNAME,_RUNTIME_SCOPE,_TRANSPORT,_CAP_EFFECTIVE,_SYSTEMD_CGROUP,_SYSTEMD_UNIT,"+
|
||||
"_SYSTEMD_SLICE,CODE_FILE,CODE_LINE,CODE_FUNC,SYSLOG_IDENTIFIER,_COMM,_EXE,_CMDLINE,MESSAGE,_PID,_SOURCE_REALTIME_TIMESTAMP,_REALTIME_TIMESTAMP",
|
||||
",")
|
||||
|
||||
func BenchmarkPushJournaldPerformance(b *testing.B) {
|
||||
cp := &insertutil.CommonParams{
|
||||
TimeFields: []string{"__REALTIME_TIMESTAMP"},
|
||||
MsgFields: []string{"MESSAGE"},
|
||||
}
|
||||
const dataChunkSize = 1024 * 1024
|
||||
|
||||
data := generateJournaldData(dataChunkSize)
|
||||
|
||||
b.ReportAllocs()
|
||||
b.SetBytes(int64(len(data)))
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
r := &bytes.Reader{}
|
||||
blp := &insertutil.BenchmarkLogMessageProcessor{}
|
||||
for pb.Next() {
|
||||
r.Reset(data)
|
||||
if err := processStreamInternal("performance_test", r, blp, cp); err != nil {
|
||||
panic(fmt.Errorf("unexpected error: %w", err))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func generateJournaldData(size int) []byte {
|
||||
var buf []byte
|
||||
timestamp := time.Now().UnixMicro()
|
||||
@@ -37,26 +80,3 @@ func generateJournaldData(size int) []byte {
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
func BenchmarkPushJournaldPerformance(b *testing.B) {
|
||||
cp := &insertutil.CommonParams{
|
||||
TimeFields: []string{"__REALTIME_TIMESTAMP"},
|
||||
MsgFields: []string{"MESSAGE"},
|
||||
}
|
||||
const dataChunkSize = 1024 * 1024
|
||||
|
||||
data := generateJournaldData(dataChunkSize)
|
||||
|
||||
b.ReportAllocs()
|
||||
b.SetBytes(int64(len(data)))
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
r := &bytes.Reader{}
|
||||
blp := &insertutil.BenchmarkLogMessageProcessor{}
|
||||
for pb.Next() {
|
||||
r.Reset(data)
|
||||
if err := processStreamInternal("performance_test", r, blp, cp); err != nil {
|
||||
panic(fmt.Errorf("unexpected error: %w", err))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -36,30 +36,30 @@ services:
|
||||
user: root
|
||||
|
||||
vlinsert:
|
||||
image: victoriametrics/victoria-logs:v1.23.3-victorialogs
|
||||
image: victoriametrics/victoria-logs:v1.24.0-victorialogs
|
||||
command:
|
||||
- "--storageNode=vlstorage-1:9428"
|
||||
- "--storageNode=vlstorage-2:9428"
|
||||
|
||||
vlselect-1:
|
||||
image: victoriametrics/victoria-logs:v1.23.3-victorialogs
|
||||
image: victoriametrics/victoria-logs:v1.24.0-victorialogs
|
||||
command:
|
||||
- "--storageNode=vlstorage-1:9428"
|
||||
- "--storageNode=vlstorage-2:9428"
|
||||
vlselect-2:
|
||||
image: victoriametrics/victoria-logs:v1.23.3-victorialogs
|
||||
image: victoriametrics/victoria-logs:v1.24.0-victorialogs
|
||||
command:
|
||||
- "--storageNode=vlstorage-1:9428"
|
||||
- "--storageNode=vlstorage-2:9428"
|
||||
|
||||
vlstorage-1:
|
||||
image: victoriametrics/victoria-logs:v1.23.3-victorialogs
|
||||
image: victoriametrics/victoria-logs:v1.24.0-victorialogs
|
||||
command:
|
||||
- "--storageDataPath=/vlogs"
|
||||
volumes:
|
||||
- vldata-1:/vlogs
|
||||
vlstorage-2:
|
||||
image: victoriametrics/victoria-logs:v1.23.3-victorialogs
|
||||
image: victoriametrics/victoria-logs:v1.24.0-victorialogs
|
||||
command:
|
||||
- "--storageDataPath=/vlogs"
|
||||
volumes:
|
||||
|
||||
@@ -38,7 +38,7 @@ services:
|
||||
# VictoriaLogs instance, a single process responsible for
|
||||
# storing logs and serving read queries.
|
||||
victorialogs:
|
||||
image: victoriametrics/victoria-logs:v1.23.3-victorialogs
|
||||
image: victoriametrics/victoria-logs:v1.24.0-victorialogs
|
||||
ports:
|
||||
- "9428:9428"
|
||||
command:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
services:
|
||||
# meta service will be ignored by compose
|
||||
.victorialogs:
|
||||
image: docker.io/victoriametrics/victoria-logs:v1.23.3-victorialogs
|
||||
image: docker.io/victoriametrics/victoria-logs:v1.24.0-victorialogs
|
||||
command:
|
||||
- -storageDataPath=/vlogs
|
||||
- -loggerFormat=json
|
||||
|
||||
@@ -3,7 +3,7 @@ version: "3"
|
||||
services:
|
||||
# Run `make package-victoria-logs` to build victoria-logs image
|
||||
vlogs:
|
||||
image: docker.io/victoriametrics/victoria-logs:v1.23.3-victorialogs
|
||||
image: docker.io/victoriametrics/victoria-logs:v1.24.0-victorialogs
|
||||
volumes:
|
||||
- vlogs:/vlogs
|
||||
ports:
|
||||
|
||||
@@ -30,10 +30,10 @@ Released at 2025-06-20
|
||||
* FEATURE: [`unpack_json` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#unpack_json-pipe): allow unpacking JSON fields with common prefix via `... fields (prefix*)` syntax.
|
||||
* FEATURE: [`unpack_logfmt` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#unpack_logfmt-pipe): allow unpacking JSON fields with common prefix via `... fields (prefix*)` syntax.
|
||||
* FEATURE: [`avg` stats function](https://docs.victoriametrics.com/victorialogs/logsql/#avg-stats): allow calculating the average value over all the fields with common prefix via `avg(prefix*)` syntax.
|
||||
* FEATURE: [`max` stats function](https://docs.victoriametrics.com/victorialogs/logsql/#avg-stats): allow calculating the maximum value over all the fields with common prefix via `max(prefix*)` syntax.
|
||||
* FEATURE: [`min` stats function](https://docs.victoriametrics.com/victorialogs/logsql/#avg-stats): allow calculating the minimum value over all the fields with common prefix via `min(prefix*)` syntax.
|
||||
* FEATURE: [`median` stats function](https://docs.victoriametrics.com/victorialogs/logsql/#avg-stats): allow calculating the median value over all the fields with common prefix via `median(prefix*)` syntax.
|
||||
* FEATURE: [`quantile` stats function](https://docs.victoriametrics.com/victorialogs/logsql/#avg-stats): allow calculating the maximum value over all the fields with common prefix via `quantile(prefix*)` syntax.
|
||||
* FEATURE: [`max` stats function](https://docs.victoriametrics.com/victorialogs/logsql/#max-stats): allow calculating the maximum value over all the fields with common prefix via `max(prefix*)` syntax.
|
||||
* FEATURE: [`min` stats function](https://docs.victoriametrics.com/victorialogs/logsql/#min-stats): allow calculating the minimum value over all the fields with common prefix via `min(prefix*)` syntax.
|
||||
* FEATURE: [`median` stats function](https://docs.victoriametrics.com/victorialogs/logsql/#median-stats): allow calculating the median value over all the fields with common prefix via `median(prefix*)` syntax.
|
||||
* FEATURE: [`quantile` stats function](https://docs.victoriametrics.com/victorialogs/logsql/#quantile-stats): allow calculating the maximum value over all the fields with common prefix via `quantile(prefix*)` syntax.
|
||||
* FEATURE: [`sum` stats function](https://docs.victoriametrics.com/victorialogs/logsql/#sum-stats): allow calculating the sum for all the fields with common prefix via `sum(prefix*)` syntax.
|
||||
* FEATURE: [`sum_len` stats function](https://docs.victoriametrics.com/victorialogs/logsql/#sum_len-stats): allow calculating the sum of byte lengths for all the fields with common prefix via `sum_len(prefix*)` syntax.
|
||||
* FEATURE: [`count` stats function](https://docs.victoriametrics.com/victorialogs/logsql/#count-stats): allow calculating the number of logs with at least a single non-empty field across fields with common prefix via `count(prefix*)` syntax.
|
||||
|
||||
@@ -36,8 +36,8 @@ Just download archive for the needed Operating system and architecture, unpack i
|
||||
For example, the following commands download VictoriaLogs archive for Linux/amd64, unpack and run it:
|
||||
|
||||
```sh
|
||||
curl -L -O https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.23.3-victorialogs/victoria-logs-linux-amd64-v1.23.3-victorialogs.tar.gz
|
||||
tar xzf victoria-logs-linux-amd64-v1.23.3-victorialogs.tar.gz
|
||||
curl -L -O https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.24.0-victorialogs/victoria-logs-linux-amd64-v1.24.0-victorialogs.tar.gz
|
||||
tar xzf victoria-logs-linux-amd64-v1.24.0-victorialogs.tar.gz
|
||||
./victoria-logs-prod -storageDataPath=victoria-logs-data
|
||||
```
|
||||
|
||||
@@ -61,7 +61,7 @@ Here is the command to run VictoriaLogs in a Docker container:
|
||||
|
||||
```sh
|
||||
docker run --rm -it -p 9428:9428 -v ./victoria-logs-data:/victoria-logs-data \
|
||||
docker.io/victoriametrics/victoria-logs:v1.23.3-victorialogs -storageDataPath=victoria-logs-data
|
||||
docker.io/victoriametrics/victoria-logs:v1.24.0-victorialogs -storageDataPath=victoria-logs-data
|
||||
```
|
||||
|
||||
See also:
|
||||
|
||||
@@ -155,8 +155,8 @@ The following guide covers the following topics for Linux host:
|
||||
Download and unpack the latest VictoriaLogs release:
|
||||
|
||||
```sh
|
||||
curl -L -O https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.23.3-victorialogs/victoria-logs-linux-amd64-v1.23.3-victorialogs.tar.gz
|
||||
tar xzf victoria-logs-linux-amd64-v1.23.3-victorialogs.tar.gz
|
||||
curl -L -O https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.24.0-victorialogs/victoria-logs-linux-amd64-v1.24.0-victorialogs.tar.gz
|
||||
tar xzf victoria-logs-linux-amd64-v1.24.0-victorialogs.tar.gz
|
||||
```
|
||||
|
||||
Start the first [`vlstorage` node](#architecture), which accepts incoming requests at the port `9491` and stores the ingested logs at `victoria-logs-data-1` directory:
|
||||
|
||||
@@ -36,7 +36,14 @@ VictoriaLogs uses `(_MACHINE_ID, _HOSTNAME, _SYSTEMD_UNIT)` as [stream fields](h
|
||||
for logs ingested via jorunald protocol. The list of log stream fields can be changed via `-journald.streamFields` command-line flag if needed,
|
||||
by providing comma-separated list of journald fields form [this list](https://www.freedesktop.org/software/systemd/man/latest/systemd.journal-fields.html).
|
||||
|
||||
See [the list of supported Journald fields](https://www.freedesktop.org/software/systemd/man/latest/systemd.journal-fields.html).
|
||||
Please make sure that the log stream fields passed to `-journlad.streamFields` do not contain fields with high number or unbound number of unique values,
|
||||
since this may lead to [high cardinality issues](https://docs.victoriametrics.com/victorialogs/keyconcepts/#high-cardinality).
|
||||
|
||||
The following Journald fields are also good candidates for stream fields:
|
||||
|
||||
- `_TRANSPORT`
|
||||
- `_SYSTEMD_USER_UNIT`
|
||||
|
||||
|
||||
## Dropping fields
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ or from docker images at [Docker Hub](https://hub.docker.com/r/victoriametrics/v
|
||||
### Running `vlogscli` from release binary
|
||||
|
||||
```sh
|
||||
curl -L -O https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.23.3-victorialogs/vlogscli-linux-amd64-v1.23.3-victorialogs.tar.gz
|
||||
tar xzf vlogscli-linux-amd64-v1.23.3-victorialogs.tar.gz
|
||||
curl -L -O https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.24.0-victorialogs/vlogscli-linux-amd64-v1.24.0-victorialogs.tar.gz
|
||||
tar xzf vlogscli-linux-amd64-v1.24.0-victorialogs.tar.gz
|
||||
./vlogscli-prod
|
||||
```
|
||||
|
||||
@@ -48,7 +48,6 @@ which queries `(AccountID=123, ProjectID=456)` [tenant](https://docs.victoriamet
|
||||
./vlogscli -header='AccountID: 123' -header='ProjectID: 456'
|
||||
```
|
||||
|
||||
|
||||
## Multitenancy
|
||||
|
||||
`AccountID` and `ProjectID` [values](https://docs.victoriametrics.com/victorialogs/#multitenancy)
|
||||
|
||||
153
docs/victoriatraces/README.md
Normal file
153
docs/victoriatraces/README.md
Normal file
@@ -0,0 +1,153 @@
|
||||
> VictoriaTraces is currently under active development and not ready for production use. It is built on top of VictoriaLogs and therefore shares some flags and APIs. These will be fully separated once VictoriaTraces reaches a stable release. Until then, features may change or break without notice.
|
||||
|
||||
VictoriaTraces is an open-source, user-friendly database designed for storing and querying distributed [tracing data](https://en.wikipedia.org/wiki/Tracing_(software)),
|
||||
built by the [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics) team.
|
||||
|
||||
VictoriaTraces provides the following features:
|
||||
- It is resource-efficient and fast. It uses up to [**3.7x less RAM and up to 2.6x less CPU**](https://victoriametrics.com/blog/dev-note-distributed-tracing-with-victorialogs/) than other solutions such as Grafana Tempo.
|
||||
- VictoriaTraces' capacity and performance scales linearly with the available resources (CPU, RAM, disk IO, disk space).
|
||||
- It accepts trace spans in the popular [OpenTelemetry protocol](https://opentelemetry.io/docs/specs/otel/protocol/)(OTLP).
|
||||
- It provides [Jaeger Query Service JSON APIs](https://www.jaegertracing.io/docs/2.6/apis/#internal-http-json)
|
||||
to integrate with [Grafana](https://grafana.com/docs/grafana/latest/datasources/jaeger/) or [Jaeger Frontend](https://www.jaegertracing.io/docs/2.6/frontend-ui/).
|
||||
|
||||
## Quick Start
|
||||
|
||||
The easiest way to get started with VictoriaTraces is by using the pre-built Docker Compose file.
|
||||
It launches VictoriaTraces, Grafana, and HotROD (a sample application that generates tracing data).
|
||||
Everything is preconfigured and connected out of the box, so you can start exploring distributed tracing within minutes.
|
||||
|
||||
Clone the repository:
|
||||
```bash
|
||||
git clone -b victoriatraces --single-branch https://github.com/VictoriaMetrics/VictoriaMetrics.git;
|
||||
cd VictoriaMetrics;
|
||||
```
|
||||
|
||||
Run VictoriaTraces with Docker Compose:
|
||||
```bash
|
||||
make docker-vt-single-up;
|
||||
```
|
||||
|
||||
Now you can open HotROD at [http://localhost:8080](http://localhost:8080) and click around to generate some traces.
|
||||
Then, you can open Grafana at [http://localhost:3000/explore](http://localhost:3000/explore) and explore the traces using the Jaeger data source.
|
||||
|
||||
To stop the services, run:
|
||||
```bash
|
||||
make docker-vt-single-down;
|
||||
```
|
||||
|
||||
You can read more about docker compose and what's available there in the [Docker compose environment for VictoriaTraces](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/deployment/victoriatraces/deployment/docker/README.md#victoriatraces-server).
|
||||
|
||||
### How to build from sources
|
||||
|
||||
Building from sources is reasonable when developing additional features specific to your needs or when testing bugfixes.
|
||||
|
||||
{{% collapse name="How to build from sources" %}}
|
||||
|
||||
Clone VictoriaMetrics repository:
|
||||
```bash
|
||||
git clone -b victoriatraces --single-branch https://github.com/VictoriaMetrics/VictoriaMetrics.git;
|
||||
cd VictoriaMetrics;
|
||||
```
|
||||
|
||||
#### Build binary with go build
|
||||
|
||||
1. [Install Go](https://golang.org/doc/install).
|
||||
1. Run `make victoria-traces` from the root folder of [the repository](https://github.com/VictoriaMetrics/VictoriaTraces).
|
||||
It builds `victoria-traces` binary and puts it into the `bin` folder.
|
||||
|
||||
#### Build binary with Docker
|
||||
|
||||
1. [Install docker](https://docs.docker.com/install/).
|
||||
1. Run `make victoria-traces-prod` from the root folder of [the repository](https://github.com/VictoriaMetrics/VictoriaTraces).
|
||||
It builds `victoria-traces-prod` binary and puts it into the `bin` folder.
|
||||
|
||||
#### Building docker images
|
||||
|
||||
Run `make package-victoria-traces`. It builds `victoriametrics/victoria-traces:<PKG_TAG>` docker image locally.
|
||||
`<PKG_TAG>` is auto-generated image tag, which depends on source code in the repository.
|
||||
The `<PKG_TAG>` may be manually set via `PKG_TAG=foobar make package-victoria-traces`.
|
||||
|
||||
The base docker image is [alpine](https://hub.docker.com/_/alpine) but it is possible to use any other base image
|
||||
by setting it via `<ROOT_IMAGE>` environment variable.
|
||||
For example, the following command builds the image on top of [scratch](https://hub.docker.com/_/scratch) image:
|
||||
|
||||
```sh
|
||||
ROOT_IMAGE=scratch make package-victoria-traces
|
||||
```
|
||||
|
||||
{{% /collapse %}}
|
||||
|
||||
### Configure and run VictoriaTraces
|
||||
|
||||
VictoriaTraces can be run with:
|
||||
```shell
|
||||
/path/to/victoria-traces -storageDataPath=victoria-traces-data -retentionPeriod=7d
|
||||
```
|
||||
|
||||
or with Docker:
|
||||
```shell
|
||||
docker run --rm -it -p 9428:9428 -v ./victoria-traces-data:/victoria-traces-data \
|
||||
docker.io/victoriametrics/victoria-traces:latest -storageDataPath=victoria-traces-data
|
||||
```
|
||||
|
||||
VictoriaTraces is configured via command-line flags.
|
||||
All the command-line flags have sane defaults, so there is no need in tuning them in general case.
|
||||
VictoriaTraces runs smoothly in most environments without additional configuration.
|
||||
|
||||
Pass `-help` to VictoriaTraces in order to see the list of supported command-line flags with their description and default values:
|
||||
|
||||
```bash
|
||||
/path/to/victoria-traces -help
|
||||
```
|
||||
|
||||
The following command-line flags are used the most:
|
||||
|
||||
* `-storageDataPath` - VictoriaTraces stores all the data in this directory. The default path is `victoria-traces-data` in the current working directory.
|
||||
* `-retentionPeriod` - retention for stored data. Older data is automatically deleted. Default retention is 7 days.
|
||||
|
||||
Once it's running, it will listen to port `9428` (`-httpListenAddr`) and provide the following APIs:
|
||||
1. for ingestion:
|
||||
```
|
||||
http://<victoria-traces>:9428/insert/opentelemetry/v1/traces
|
||||
```
|
||||
2. for querying:
|
||||
```
|
||||
http://<victoria-traces>:9428/select/jaeger/<endpoints>
|
||||
```
|
||||
|
||||
See [data ingestion](https://docs.victoriametrics.com/victoriatraces/data-ingestion/) and [querying](https://docs.victoriametrics.com/VictoriaTraces/querying/) for more details.
|
||||
|
||||
## How does it work
|
||||
|
||||
VictoriaTraces was initially built on top of [VictoriaLogs](https://docs.victoriametrics.com/victorialogs/), a log database.
|
||||
It receives trace spans in OTLP format, transforms them into structured logs, and provides [Jaeger Query Service JSON APIs](https://www.jaegertracing.io/docs/2.6/apis/#internal-http-json) for querying.
|
||||
|
||||
For detailed data model and example, see: [Key Concepts](https://docs.victoriametrics.com/victoriatraces/keyConcepts).
|
||||
|
||||

|
||||
|
||||
Building VictoriaTraces in this way enables it to scale easily and linearly with the available resources, like VictoriaLogs.
|
||||
|
||||
## List of command-line flags
|
||||
|
||||
```shell
|
||||
-search.traceMaxDurationWindow
|
||||
The lookbehind/lookahead window of searching for the rest trace spans after finding one span.
|
||||
It allows extending the search start time and end time by `-search.traceMaxDurationWindow` to make sure all spans are included.
|
||||
It affects both Jaeger's `/api/traces` and `/api/traces/<trace_id>` APIs. (default: 10m)
|
||||
-search.traceMaxServiceNameList
|
||||
The maximum number of service name can return in a get service name request.
|
||||
This limit affects Jaeger's `/api/services` API. (default: 1000)
|
||||
-search.traceMaxSpanNameList
|
||||
The maximum number of span name can return in a get span name request.
|
||||
This limit affects Jaeger's `/api/services/*/operations` API. (default: 1000)
|
||||
-search.traceSearchStep
|
||||
Splits the [0, now] time range into many small time ranges by -search.traceSearchStep
|
||||
when searching for spans by `trace_id`. Once it finds spans in a time range, it performs an additional search according to `-search.traceMaxDurationWindow` and then stops.
|
||||
It affects Jaeger's `/api/traces/<trace_id>` API. (default: 1d)
|
||||
-search.traceServiceAndSpanNameLookbehind
|
||||
The time range of searching for service names and span names.
|
||||
It affects Jaeger's `/api/services` and `/api/services/*/operations` APIs. (default: 7d)
|
||||
```
|
||||
|
||||
See also: [VictoriaLogs - List of Command-line flags](https://docs.victoriametrics.com/victorialogs/#list-of-command-line-flags)
|
||||
11
docs/victoriatraces/_index.md
Normal file
11
docs/victoriatraces/_index.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: VictoriaTraces
|
||||
menu:
|
||||
docs:
|
||||
weight: 17
|
||||
identifier: victoriatraces
|
||||
pageRef: "/"
|
||||
tags:
|
||||
- traces
|
||||
---
|
||||
{{% content "README.md" %}}
|
||||
90
docs/victoriatraces/data-ingestion/README.md
Normal file
90
docs/victoriatraces/data-ingestion/README.md
Normal file
@@ -0,0 +1,90 @@
|
||||
> VictoriaTraces is currently under active development and not ready for production use. It is built on top of VictoriaLogs and therefore shares some flags and APIs. These will be fully separated once VictoriaTraces reaches a stable release. Until then, features may change or break without notice.
|
||||
|
||||
[VictoriaTraces](https://docs.victoriametrics.com/victoriatraces/) can accept trace spans via [the OpenTelemetry protocol (OTLP)](https://opentelemetry.io/docs/specs/otlp/).
|
||||
|
||||
## HTTP APIs
|
||||
|
||||
### Opentelemetry API
|
||||
|
||||
VictoriaTraces provides the following API for OpenTelemetry data ingestion:
|
||||
|
||||
- `/insert/opentelemetry/v1/traces`
|
||||
|
||||
See more details [in this docs](https://docs.victoriametrics.com/victoriatraces/data-ingestion/opentelemetry/).
|
||||
|
||||
### HTTP parameters
|
||||
|
||||
VictoriaTraces accepts optional HTTP parameters at data ingestion HTTP API via [HTTP query string parameters](https://en.wikipedia.org/wiki/Query_string), or via [HTTP headers](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields).
|
||||
|
||||
HTTP query string parameters have priority over HTTP Headers.
|
||||
|
||||
#### HTTP Query string parameters
|
||||
|
||||
All the [HTTP-based data ingestion protocols](#http-apis) support the following [HTTP query string](https://en.wikipedia.org/wiki/Query_string) args:
|
||||
|
||||
- `extra_fields` - an optional comma-separated list of [trace fields](https://docs.victoriametrics.com/victoriatraces/keyconcepts/#data-model),
|
||||
which must be added to all the ingested traces. The format of every `extra_fields` entry is `field_name=field_value`.
|
||||
If the trace entry contains fields from the `extra_fields`, then they are overwritten by the values specified in `extra_fields`.
|
||||
|
||||
- `debug` - if this arg is set to `1`, then the ingested traces aren't stored in VictoriaTraces. Instead,
|
||||
the ingested data is traceged by VictoriaTraces, so it can be investigated later.
|
||||
|
||||
See also [HTTP headers](#http-headers).
|
||||
|
||||
#### HTTP headers
|
||||
|
||||
All the [HTTP-based data ingestion protocols](#http-apis) support the following [HTTP Headers](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields)
|
||||
additionally to [HTTP query args](#http-query-string-parameters):
|
||||
|
||||
- `AccountID` - accountID of the tenant to ingest data to. See [multitenancy docs](https://docs.victoriametrics.com/victoriatraces/#multitenancy) for details.
|
||||
|
||||
- `ProjectID`- projectID of the tenant to ingest data to. See [multitenancy docs](https://docs.victoriametrics.com/victoriatraces/#multitenancy) for details.
|
||||
|
||||
- `VL-Extra-Fields` - an optional comma-separated list of [trace fields](https://docs.victoriametrics.com/victoriatraces/keyconcepts/#data-model),
|
||||
which must be added to all the ingested traces. The format of every `extra_fields` entry is `field_name=field_value`.
|
||||
If the trace entry contains fields from the `extra_fields`, then they are overwritten by the values specified in `extra_fields`.
|
||||
|
||||
- `VL-Debug` - if this parameter is set to `1`, then the ingested traces aren't stored in VictoriaTraces. Instead,
|
||||
the ingested data is traceged by VictoriaTraces, so it can be investigated later.
|
||||
|
||||
See also [HTTP Query string parameters](#http-query-string-parameters).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
The following command can be used for verifying whether the data is successfully ingested into VictoriaTraces:
|
||||
|
||||
```sh
|
||||
curl http://<victoria-traces>:9428/select/logsql/query -d 'query=*' | head
|
||||
```
|
||||
|
||||
This command selects all the data ingested into VictoriaTraces via [HTTP query API](https://docs.victoriametrics.com/victoriatraces/querying/#http-api)
|
||||
using [any value filter](https://docs.victoriametrics.com/victorialogs/logsql/#any-value-filter),
|
||||
while `head` cancels query execution after reading the first 10 trace spans. See [these docs](https://docs.victoriametrics.com/victoriatraces/querying/#command-line)
|
||||
for more details on how `head` integrates with VictoriaTraces.
|
||||
|
||||
The response by default contains all the [trace span fields](https://docs.victoriametrics.com/victoriatraces/keyconcepts/#data-model).
|
||||
See [how to query specific fields](https://docs.victoriametrics.com/victoriatraces/logsql/#querying-specific-fields).
|
||||
|
||||
VictoriaTraces provides the following command-line flags, which can help debugging data ingestion issues:
|
||||
|
||||
- `-logNewStreams` - if this flag is passed to VictoriaTraces, then it traces all the newly
|
||||
registered [streams](https://docs.victoriametrics.com/victoriatraces/keyconcepts/#stream-fields).
|
||||
This may help debugging [high cardinality issues](https://docs.victoriametrics.com/victoriatraces/keyconcepts/#high-cardinality).
|
||||
- `-logIngestedRows` - if this flag is passed to VictoriaTraces, then it traces all the ingested
|
||||
[trace span entries](https://docs.victoriametrics.com/victoriatraces/keyconcepts/#data-model).
|
||||
See also `debug` [parameter](#http-parameters).
|
||||
|
||||
VictoriaTraces exposes various [metrics](https://docs.victoriametrics.com/victoriatraces/#monitoring), which may help debugging data ingestion issues:
|
||||
|
||||
- `vl_rows_ingested_total` - the number of ingested [trace span entries](https://docs.victoriametrics.com/victoriatraces/keyconcepts/#data-model)
|
||||
since the last VictoriaTraces restart. If this number increases over time, then trace spans are successfully ingested into VictoriaTraces.
|
||||
The ingested trace spans can be inspected in the following ways:
|
||||
- By passing `debug=1` parameter to every request to [data ingestion APIs](#http-apis). The ingested spans aren't stored in VictoriaTraces
|
||||
in this case. Instead, they are logged, so they can be investigated later.
|
||||
The `vl_rows_dropped_total` [metric](https://docs.victoriametrics.com/victoriatraces/#monitoring) is incremented for each logged row.
|
||||
- By passing `-logIngestedRows` command-line flag to VictoriaTraces. In this case it traces all the ingested data, so it can be investigated later.
|
||||
- `vl_streams_created_total` - the number of created [trace streams](https://docs.victoriametrics.com/victoriatraces/keyconcepts/#stream-fields)
|
||||
since the last VictoriaTraces restart. If this metric grows rapidly during extended periods of time, then this may lead
|
||||
to [high cardinality issues](https://docs.victoriametrics.com/victoriatraces/keyconcepts/#high-cardinality).
|
||||
The newly created trace streams can be inspected in traces by passing `-logNewStreams` command-line flag to VictoriaTraces.
|
||||
|
||||
15
docs/victoriatraces/data-ingestion/_index.md
Normal file
15
docs/victoriatraces/data-ingestion/_index.md
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Data ingestion
|
||||
weight: 3
|
||||
menu:
|
||||
docs:
|
||||
identifier: victoriatraces-data-ingestion
|
||||
parent: "victoriatraces"
|
||||
weight: 3
|
||||
tags:
|
||||
- traces
|
||||
aliases:
|
||||
- /victoriatraces/data-ingestion/
|
||||
- /victoriatraces/data-ingestion/index.html
|
||||
---
|
||||
{{% content "README.md" %}}
|
||||
82
docs/victoriatraces/data-ingestion/opentelemetry.md
Normal file
82
docs/victoriatraces/data-ingestion/opentelemetry.md
Normal file
@@ -0,0 +1,82 @@
|
||||
---
|
||||
weight: 4
|
||||
title: OpenTelemetry setup
|
||||
disableToc: true
|
||||
menu:
|
||||
docs:
|
||||
parent: "victoriatraces-data-ingestion"
|
||||
weight: 4
|
||||
tags:
|
||||
- traces
|
||||
aliases:
|
||||
- /victoriatraces/data-ingestion/OpenTelemetry.html
|
||||
---
|
||||
|
||||
> VictoriaTraces is currently under active development and not ready for production use. It is built on top of VictoriaLogs and therefore shares some flags and APIs. These will be fully separated once VictoriaTraces reaches a stable release. Until then, features may change or break without notice.
|
||||
|
||||
VictoriaTraces supports both client open-telemetry [SDK](https://opentelemetry.io/docs/languages/) and [collector](https://opentelemetry.io/docs/collector/).
|
||||
|
||||
## Client SDK
|
||||
|
||||
The OpenTelemetry provides detailed document and examples for various programming languages:
|
||||
- [C++](https://opentelemetry.io/docs/languages/cpp/)
|
||||
- [C#/.NET](https://opentelemetry.io/docs/languages/dotnet/)
|
||||
- [Erlang/Elixir](https://opentelemetry.io/docs/languages/erlang/)
|
||||
- [Go](https://opentelemetry.io/docs/languages/go/)
|
||||
- [Java](https://opentelemetry.io/docs/languages/java/)
|
||||
- [JavaScript](https://opentelemetry.io/docs/languages/js/)
|
||||
- [PHP](https://opentelemetry.io/docs/languages/php/)
|
||||
- [Python](https://opentelemetry.io/docs/languages/python/)
|
||||
- [Ruby](https://opentelemetry.io/docs/languages/ruby/)
|
||||
- [Rust](https://opentelemetry.io/docs/languages/rust/)
|
||||
- [Swift](https://opentelemetry.io/docs/languages/swift/)
|
||||
|
||||
To send data to VictoriaTraces, specify the `EndpointURL` for http-exporter builder to `http://<victoria-traces>:9428/insert/opentelemetry/v1/traces`.
|
||||
|
||||
Consider the following example for Go SDK:
|
||||
|
||||
```go
|
||||
traceExporter, err := otlptracehttp.New(ctx,
|
||||
otlptracehttp.WithEndpointURL("http://<victoria-traces>:9428/insert/opentelemetry/v1/traces"),
|
||||
)
|
||||
```
|
||||
|
||||
VictoriaTraces automatically use `service.name` in **resource attributes** and `name` in **span** as [stream fields](https://docs.victoriametrics.com/victoriatraces/keyconcepts/#stream-fields).
|
||||
While the remaining data (including [resource](https://opentelemetry.io/docs/specs/otel/overview/#resources), [instrumentation scope](https://opentelemetry.io/docs/specs/otel/common/instrumentation-scope/), and fields in [span](https://opentelemetry.io/docs/specs/otel/trace/api/#span), like `trace_id`, `span_id`, span `attributes` and more) are stored as [regular fields](https://docs.victoriametrics.com/victoriatraces/keyconcepts/#data-model):
|
||||
|
||||
VictoriaTraces supports other HTTP headers - see the list [here](https://docs.victoriametrics.com/victoriatraces/data-ingestion/#http-headers).
|
||||
|
||||
The ingested trace spans can be queried according to [these docs](https://docs.victoriametrics.com/victoriatraces/querying/).
|
||||
|
||||
## Collector configuration
|
||||
|
||||
VictoriaTraces supports receiving traces from the following OpenTelemetry collector:
|
||||
|
||||
* [OpenTelemetry](#opentelemetry)
|
||||
|
||||
### OpenTelemetry
|
||||
|
||||
To send the collected traces to VictoriaTraces, specify traces endpoint for [OTLP/HTTP exporter](https://github.com/open-telemetry/opentelemetry-collector/blob/main/exporter/otlphttpexporter/README.md) in configuration file:
|
||||
|
||||
```yaml
|
||||
exporters:
|
||||
otlphttp:
|
||||
traces_endpoint: http://<victoria-traces>:9428/insert/opentelemetry/v1/traces
|
||||
```
|
||||
|
||||
VictoriaTraces supports various HTTP headers, which can be used during data ingestion - see the list [here](https://docs.victoriametrics.com/victoriatraces/data-ingestion/#http-headers).
|
||||
These headers can be passed to OpenTelemetry exporter config via `headers` options. For example, the following configs add (or overwrites) `foo: bar` field to each trace span during data ingestion:
|
||||
|
||||
```yaml
|
||||
exporters:
|
||||
otlphttp:
|
||||
traces_endpoint: http://<victoria-traces>:9428/insert/opentelemetry/v1/traces
|
||||
headers:
|
||||
VL-Extra-Fields: foo=bar
|
||||
```
|
||||
|
||||
See also:
|
||||
|
||||
* [Data ingestion troubleshooting](https://docs.victoriametrics.com/victoriatraces/data-ingestion/#troubleshooting).
|
||||
* [How to query VictoriaTraces](https://docs.victoriametrics.com/victoriatraces/querying/).
|
||||
* [Docker-compose demo for OpenTelemetry collector integration with VictoriaTraces](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/deployment/docker/victoriatraces/opentelemetry-collector).
|
||||
BIN
docs/victoriatraces/how-does-it-work.webp
Normal file
BIN
docs/victoriatraces/how-does-it-work.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
234
docs/victoriatraces/keyConcepts.md
Normal file
234
docs/victoriatraces/keyConcepts.md
Normal file
@@ -0,0 +1,234 @@
|
||||
---
|
||||
weight: 2
|
||||
title: Key concepts
|
||||
menu:
|
||||
docs:
|
||||
identifier: vt-key-concepts
|
||||
parent: victoriatraces
|
||||
weight: 2
|
||||
title: Key concepts
|
||||
tags:
|
||||
- traces
|
||||
aliases:
|
||||
- /victoriatraces/keyConcepts.html
|
||||
---
|
||||
|
||||
> VictoriaTraces is currently under active development and not ready for production use. It is built on top of VictoriaLogs and therefore shares some flags and APIs. These will be fully separated once VictoriaTraces reaches a stable release. Until then, features may change or break without notice.
|
||||
|
||||
## Data model
|
||||
|
||||
VictoriaTraces is built on VictoriaLogs. It's recommended to go through [the data model of VictoriaLogs](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model) first.
|
||||
|
||||
**Every [trace span](https://github.com/open-telemetry/opentelemetry-proto/blob/v1.7.0/opentelemetry/proto/trace/v1/trace.proto) must contain `service.name` in its resource attributes and a span `name`**. They will be used as the [stream fields](#stream-fields). All other data of the span will be mapped as the ordinary fields.
|
||||
|
||||
For example, here's how a trace span looks like in an OTLP export request:
|
||||
|
||||
{{% collapse name="OTLP trace span" %}}
|
||||
|
||||
```json
|
||||
{
|
||||
"ResourceSpans": [{
|
||||
"Resource": {
|
||||
"Attributes": {
|
||||
"service.name": "payment",
|
||||
"telemetry.sdk.language": "nodejs",
|
||||
"telemetry.sdk.name": "opentelemetry",
|
||||
"telemetry.sdk.version": "1.30.1",
|
||||
"container.id": "18ee03279d38ed0e0eedad037c260df78dfc3323aa662ca14a2d38fcc8bf3762",
|
||||
"service.namespace": "opentelemetry-demo",
|
||||
"service.version": "2.0.2",
|
||||
"host.name": "18ee03279d38",
|
||||
"host.arch": "arm64",
|
||||
"os.type": "linux",
|
||||
"os.version": "6.10.14-linuxkit",
|
||||
"process.pid": 17,
|
||||
"process.executable.name": "node",
|
||||
"process.executable.path": "/usr/local/bin/node",
|
||||
"process.command_args": [
|
||||
"/usr/local/bin/node",
|
||||
"--require",
|
||||
"./opentelemetry.js",
|
||||
"/usr/src/app/index.js"
|
||||
],
|
||||
"process.runtime.version": "22.16.0",
|
||||
"process.runtime.name": "nodejs",
|
||||
"process.runtime.description": "Node.js",
|
||||
"process.command": "/usr/src/app/index.js",
|
||||
"process.owner": "node"
|
||||
}
|
||||
},
|
||||
"ScopeSpans": [{
|
||||
"Scope": {
|
||||
"Name": "@opentelemetry/instrumentation-net",
|
||||
"Version": "0.43.1",
|
||||
"Attributes": null,
|
||||
"DroppedAttributesCount": 0
|
||||
},
|
||||
"Spans": [{
|
||||
"TraceID": "769d28c4b8633dc9de2cc421d1a1616f",
|
||||
"SpanID": "2a1f3c4bda1d0e43",
|
||||
"TraceState": "",
|
||||
"ParentSpanID": "3ea61f2d2a5a003d",
|
||||
"Flags": 0,
|
||||
"Name": "tcp.connect",
|
||||
"Kind": 1,
|
||||
"StartTimeUnixNano": 1750044408780000000,
|
||||
"EndTimeUnixNano": 1750044408796828584,
|
||||
"Attributes": {
|
||||
"net.transport": "ip_tcp",
|
||||
"net.peer.name": "169.254.169.254",
|
||||
"net.peer.port": 80,
|
||||
"net.peer.ip": "169.254.169.254",
|
||||
"net.host.ip": "172.18.0.14",
|
||||
"net.host.port": 50722
|
||||
},
|
||||
"DroppedAttributesCount": 0,
|
||||
"Events": null,
|
||||
"DroppedEventsCount": 0,
|
||||
"Links": null,
|
||||
"DroppedLinksCount": 0,
|
||||
"Status": {
|
||||
"Message": "",
|
||||
"Code": 0
|
||||
}
|
||||
}],
|
||||
"SchemaURL": ""
|
||||
}],
|
||||
"SchemaURL": ""
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
{{% /collapse %}}
|
||||
|
||||
And here's how this trace span looks like in VictoriaTraces:
|
||||
|
||||
{{% collapse name="span in VictoriaTraces" %}}
|
||||
|
||||
```json
|
||||
{
|
||||
"_time": "2025-06-16T03:26:48.796828584Z",
|
||||
"_stream_id": "00000000000000006fc12c07dedb6f693e73fc7b11b943e6",
|
||||
"_stream": "{name=\"tcp.connect\",resource_attr:service.name=\"payment\"}",
|
||||
"_msg": "-",
|
||||
"dropped_attributes_count": "0",
|
||||
"dropped_events_count": "0",
|
||||
"dropped_links_count": "0",
|
||||
"flags": "0",
|
||||
"kind": "1",
|
||||
"name": "tcp.connect",
|
||||
"resource_attr:container.id": "18ee03279d38ed0e0eedad037c260df78dfc3323aa662ca14a2d38fcc8bf3762",
|
||||
"resource_attr:host.arch": "arm64",
|
||||
"resource_attr:host.name": "18ee03279d38",
|
||||
"resource_attr:os.type": "linux",
|
||||
"resource_attr:os.version": "6.10.14-linuxkit",
|
||||
"resource_attr:process.command": "/usr/src/app/index.js",
|
||||
"resource_attr:process.command_args": "[\"/usr/local/bin/node\",\"--require\",\"./opentelemetry.js\",\"/usr/src/app/index.js\"]",
|
||||
"resource_attr:process.executable.name": "node",
|
||||
"resource_attr:process.executable.path": "/usr/local/bin/node",
|
||||
"resource_attr:process.owner": "node",
|
||||
"resource_attr:process.pid": "17",
|
||||
"resource_attr:process.runtime.description": "Node.js",
|
||||
"resource_attr:process.runtime.name": "nodejs",
|
||||
"resource_attr:process.runtime.version": "22.16.0",
|
||||
"resource_attr:service.name": "payment",
|
||||
"resource_attr:service.namespace": "opentelemetry-demo",
|
||||
"resource_attr:service.version": "2.0.2",
|
||||
"resource_attr:telemetry.sdk.language": "nodejs",
|
||||
"resource_attr:telemetry.sdk.name": "opentelemetry",
|
||||
"resource_attr:telemetry.sdk.version": "1.30.1",
|
||||
"scope_name": "@opentelemetry/instrumentation-net",
|
||||
"scope_version": "0.43.1",
|
||||
"span_attr:net.transport": "ip_tcp",
|
||||
"duration": "16828584",
|
||||
"end_time_unix_nano": "1750044408796828584",
|
||||
"parent_span_id": "3ea61f2d2a5a003d",
|
||||
"span_attr:net.host.ip": "172.18.0.14",
|
||||
"span_attr:net.host.port": "50722",
|
||||
"span_attr:net.peer.ip": "169.254.169.254",
|
||||
"span_attr:net.peer.name": "169.254.169.254",
|
||||
"span_attr:net.peer.port": "80",
|
||||
"span_id": "2a1f3c4bda1d0e43",
|
||||
"start_time_unix_nano": "1750044408780000000",
|
||||
"status_code": "0",
|
||||
"trace_id": "769d28c4b8633dc9de2cc421d1a1616f"
|
||||
}
|
||||
```
|
||||
|
||||
{{% /collapse %}}
|
||||
|
||||
### Special mappings
|
||||
|
||||
There are some special mappings when transforming a trace span into VictoriaTraces data model:
|
||||
1. Empty attribute values in trace spans are replaced with `-`.
|
||||
2. Resource, scope and span attributes are stored with corresponding prefixes `resource_attr`, `scope_attr` and `span_attr:` accordingly.
|
||||
3. For some attributes within a list (event list, link list in span), a corresponding prefix and index (such as `event:0:` and `event:0:event_attr:`) is added.
|
||||
4. The `duration` field does not exist in the OTLP request, but for query efficiency, it's calculated during ingestion and stored as a separated field.
|
||||
|
||||
VictoriaTraces automatically indexes all the fields for ingested trace spans.
|
||||
This enables [full-text search](https://docs.victoriametrics.com/victorialogs/logsql/) across all the fields.
|
||||
|
||||
VictoriaTraces stores data in different fields. There are some special fields in addition to [arbitrary fields](#other-fields):
|
||||
|
||||
* [`_time` field](#time-field)
|
||||
* [`_stream` and `_stream_id` fields](#stream-fields)
|
||||
|
||||
### Time field
|
||||
|
||||
The ingested [trace spans](#data-model) may contain `_time` field with the timestamp of the ingested trace span.
|
||||
|
||||
By default, VictoriaTraces use the `EndTimeUnixNano` in trace span as the `_time` field.
|
||||
|
||||
The `_time` field is used by [time filter](https://docs.victoriametrics.com/victorialogs/logsql/#time-filter) for quickly narrowing down the search to the selected time range.
|
||||
|
||||
### Stream fields
|
||||
|
||||
As service name and span name usually identify the application instance, VictoriaTraces uses `service.name` in resource attributes and `name` in span
|
||||
as the stream fields.
|
||||
|
||||
VictoriaTraces optimizes storing and [querying](https://docs.victoriametrics.com/victorialogs/logsql/#stream-filter) of individual trace span streams.
|
||||
This provides the following benefits:
|
||||
- Reduced disk space usage, since a trace span stream from a single application instance is usually compressed better
|
||||
than a mixed trace span stream from multiple distinct applications.
|
||||
|
||||
- Increased query performance, since VictoriaTraces needs to scan lower amounts of data
|
||||
when [searching by stream fields](https://docs.victoriametrics.com/victorialogs/logsql/#stream-filter).
|
||||
|
||||
Every ingested trace span is associated with a trace span stream. Every trace span stream consists of the following special fields:
|
||||
|
||||
- `_stream_id` - this is an unique identifier for the trace span stream. All the trace spans for the particular stream can be selected
|
||||
via [`_stream_id:...` filter](https://docs.victoriametrics.com/victorialogs/logsql/#_stream_id-filter).
|
||||
|
||||
- `_stream` - this field contains stream labels in the format similar to [labels in Prometheus metrics](https://docs.victoriametrics.com/victoriametrics/keyconcepts/#labels):
|
||||
```
|
||||
{resource_attr:service_name="svc name", name="span name"}
|
||||
```
|
||||
The `_stream` field can be searched with [stream filters](https://docs.victoriametrics.com/victorialogs/logsql/#stream-filter).
|
||||
|
||||
#### High cardinality
|
||||
|
||||
Some fields in the [trace spans](#data-model) may contain big number of unique values across log entries.
|
||||
For example, fields with names such as `trace_id`, `span_id` or `ip` tend to contain big number of unique values.
|
||||
VictoriaTraces works perfectly with such fields unless they are associated with [trace span streams](#stream-fields).
|
||||
|
||||
**Never** associate high-cardinality fields with [trace span streams](#stream-fields), since this may lead to the following issues:
|
||||
|
||||
- Performance degradation during [data ingestion](https://docs.victoriametrics.com/victoriatraces/data-ingestion/)
|
||||
and [querying](https://docs.victoriametrics.com/victoriatraces/querying/)
|
||||
- Increased memory usage
|
||||
- Increased CPU usage
|
||||
- Increased disk space usage
|
||||
- Increased disk read / write IO
|
||||
|
||||
VictoriaTraces exposes `vl_streams_created_total` [metric](https://docs.victoriametrics.com/victorialogs/#monitoring),
|
||||
which shows the number of created streams since the last VictoriaTraces restart. If this metric grows at a rapid rate
|
||||
during long period of time, then there are high chances of high cardinality issues mentioned above.
|
||||
VictoriaTraces can log all the newly registered streams when `-logNewStreams` command-line flag is passed to it.
|
||||
This can help narrowing down and eliminating high-cardinality fields from [trace span streams](#stream-fields).
|
||||
|
||||
### Other fields
|
||||
|
||||
Every ingested log entry may contain arbitrary number of [fields](#data-model) additionally to [`_time`](#time-field).
|
||||
For example, `name`, `span_attr:ip`, `span_attr:ip`, etc. Such fields can be used for simplifying and optimizing [search queries](https://docs.victoriametrics.com/victorialogs/logsql/).
|
||||
|
||||
See [LogsQL docs](https://docs.victoriametrics.com/victorialogs/logsql/) for more details.
|
||||
112
docs/victoriatraces/querying/README.md
Normal file
112
docs/victoriatraces/querying/README.md
Normal file
File diff suppressed because one or more lines are too long
16
docs/victoriatraces/querying/_index.md
Normal file
16
docs/victoriatraces/querying/_index.md
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: Querying
|
||||
weight: 4
|
||||
menu:
|
||||
docs:
|
||||
identifier: victoriatraces-querying
|
||||
parent: "victoriatraces"
|
||||
weight: 4
|
||||
tags:
|
||||
- traces
|
||||
aliases:
|
||||
- /VictoriaTraces/querying/
|
||||
- /victoriatraces/querying/
|
||||
- /victoriatraces/querying/index.html
|
||||
---
|
||||
{{% content "README.md" %}}
|
||||
BIN
docs/victoriatraces/querying/grafana-jaeger.webp
Normal file
BIN
docs/victoriatraces/querying/grafana-jaeger.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
28
docs/victoriatraces/querying/grafana.md
Normal file
28
docs/victoriatraces/querying/grafana.md
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
weight: 4
|
||||
title: Visualization in Grafana
|
||||
disableToc: true
|
||||
menu:
|
||||
docs:
|
||||
parent: "victoriatraces-querying"
|
||||
weight: 4
|
||||
tags:
|
||||
- traces
|
||||
aliases:
|
||||
- /victoriatraces/querying/grafana.html
|
||||
---
|
||||
|
||||
> VictoriaTraces is currently under active development and not ready for production use. It is built on top of VictoriaLogs and therefore shares some flags and APIs. These will be fully separated once VictoriaTraces reaches a stable release. Until then, features may change or break without notice.
|
||||
|
||||
[Grafana Jaeger Datasource](https://grafana.com/docs/grafana/latest/datasources/jaeger/) allows you to query and visualize VictoriaTraces data in Grafana.
|
||||
|
||||

|
||||
|
||||
Simply click "Add new data source" on Grafana, and then fill your VictoriaTraces URL to "Connection.URL".
|
||||
|
||||
The URL format for VictoriaTraces single-node is:
|
||||
```
|
||||
http://<victoria-traces>:9428/select/jaeger
|
||||
```
|
||||
|
||||
Finally, click "Save & Test" at the bottom to complete the process.
|
||||
65
docs/victoriatraces/querying/jaeger-frontend.md
Normal file
65
docs/victoriatraces/querying/jaeger-frontend.md
Normal file
@@ -0,0 +1,65 @@
|
||||
---
|
||||
weight: 4
|
||||
title: Visualization in Jaeger UI
|
||||
disableToc: true
|
||||
menu:
|
||||
docs:
|
||||
parent: "victoriatraces-querying"
|
||||
weight: 4
|
||||
tags:
|
||||
- traces
|
||||
aliases:
|
||||
- /victoriatraces/querying/jaeger-frontend.html
|
||||
---
|
||||
|
||||
> VictoriaTraces is currently under active development and not ready for production use. It is built on top of VictoriaLogs and therefore shares some flags and APIs. These will be fully separated once VictoriaTraces reaches a stable release. Until then, features may change or break without notice.
|
||||
|
||||
[Jaeger UI](https://github.com/jaegertracing/jaeger-ui) is the official frontend that ships with Jaeger. It queries [Jaeger Query Service JSON APIs](https://www.jaegertracing.io/docs/2.6/apis/#internal-http-json)
|
||||
and visualizes the response trace data.
|
||||
|
||||
## Deploy Jaeger UI
|
||||
|
||||
You can get Jaeger UI from [release page](https://github.com/jaegertracing/jaeger-ui/releases/tag/v1.70.0).
|
||||
|
||||
As it provides only assets and source code, an HTTP server is needed for serving requests.
|
||||
|
||||
### Nginx Example
|
||||
|
||||
Here's an example where we use Nginx to:
|
||||
- Serve static content of Jaeger UI.
|
||||
- Forward query requests to VictoriaTraces.
|
||||
|
||||
Assume you already have:
|
||||
1. VictoriaTraces running locally and listening on port `:9428`.
|
||||
2. Jaeger UI assets (`index.html` and `/static`) located under `/path/to/jaeger-ui/build/`.
|
||||
3. Nginx Installed.
|
||||
|
||||
Create the following config file `jaeger.conf` and place it to the Nginx config folder:
|
||||
|
||||
```
|
||||
server {
|
||||
listen 8080;
|
||||
listen [::]:8080;
|
||||
server_name localhost;
|
||||
|
||||
location / {
|
||||
root /path/to/jaeger-ui/build; # change this path to your asserts location.
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location /api {
|
||||
proxy_pass http://127.0.0.1:9428/select/jaeger/api; # change this address to VictoriaTraces' address.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here are some common paths of Nginx config folder:
|
||||
```sh
|
||||
# Ubuntu & Install with apt
|
||||
cd /etc/nginx/sites-available/
|
||||
|
||||
# MacOS & Install with homebrew
|
||||
cd /opt/homebrew/etc/nginx/servers/
|
||||
```
|
||||
|
||||
After reloading Nginx, you should be able to visit Jaeger UI on: [http://127.0.0.1:8080/](http://127.0.0.1:8080/).
|
||||
@@ -1,42 +1,5 @@
|
||||
package fasttime
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/atomicutil"
|
||||
)
|
||||
|
||||
func init() {
|
||||
go func() {
|
||||
ticker := time.NewTicker(time.Second)
|
||||
defer ticker.Stop()
|
||||
for tm := range ticker.C {
|
||||
t := uint64(tm.Unix())
|
||||
currentTimestamp.Store(t)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
var currentTimestamp = func() *atomicutil.Uint64 {
|
||||
var x atomicutil.Uint64
|
||||
x.Store(uint64(time.Now().Unix()))
|
||||
return &x
|
||||
}()
|
||||
|
||||
// UnixTimestamp returns the current unix timestamp in seconds.
|
||||
//
|
||||
// It is faster than time.Now().Unix()
|
||||
func UnixTimestamp() uint64 {
|
||||
if testing.Testing() {
|
||||
// When executing inside the tests, use the time package directly.
|
||||
// This allows to override time using synctest package.
|
||||
return uint64(time.Now().Unix())
|
||||
}
|
||||
|
||||
return currentTimestamp.Load()
|
||||
}
|
||||
|
||||
// UnixDate returns date from the current unix timestamp.
|
||||
//
|
||||
// The date is calculated by dividing unix timestamp by (24*3600)
|
||||
|
||||
33
lib/fasttime/fasttime_normal.go
Normal file
33
lib/fasttime/fasttime_normal.go
Normal file
@@ -0,0 +1,33 @@
|
||||
//go:build !goexperiment.synctest
|
||||
|
||||
package fasttime
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/atomicutil"
|
||||
)
|
||||
|
||||
func init() {
|
||||
go func() {
|
||||
ticker := time.NewTicker(time.Second)
|
||||
defer ticker.Stop()
|
||||
for tm := range ticker.C {
|
||||
t := uint64(tm.Unix())
|
||||
currentTimestamp.Store(t)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
var currentTimestamp = func() *atomicutil.Uint64 {
|
||||
var x atomicutil.Uint64
|
||||
x.Store(uint64(time.Now().Unix()))
|
||||
return &x
|
||||
}()
|
||||
|
||||
// UnixTimestamp returns the current unix timestamp in seconds.
|
||||
//
|
||||
// It is faster than time.Now().Unix()
|
||||
func UnixTimestamp() uint64 {
|
||||
return currentTimestamp.Load()
|
||||
}
|
||||
13
lib/fasttime/fasttime_synctest.go
Normal file
13
lib/fasttime/fasttime_synctest.go
Normal file
@@ -0,0 +1,13 @@
|
||||
//go:build goexperiment.synctest
|
||||
|
||||
package fasttime
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// UnixTimestamp returns the current unix timestamp in seconds.
|
||||
func UnixTimestamp() uint64 {
|
||||
// Fall back to time.Now().Unix(), since this is needed for synctest.
|
||||
return uint64(time.Now().Unix())
|
||||
}
|
||||
82
lib/storage/index_db_synctest_test.go
Normal file
82
lib/storage/index_db_synctest_test.go
Normal file
@@ -0,0 +1,82 @@
|
||||
//go:build goexperiment.synctest
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/uint64set"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
func TestIndexDB_MetricIDsNotMappedToTSIDsAreDeleted(t *testing.T) {
|
||||
defer testRemoveAll(t)
|
||||
|
||||
keys := func(missingMetricIDs map[uint64]uint64) []uint64 {
|
||||
keys := []uint64{}
|
||||
for k := range missingMetricIDs {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
slices.Sort(keys)
|
||||
return keys
|
||||
}
|
||||
|
||||
synctest.Run(func() {
|
||||
s := MustOpenStorage(t.Name(), OpenOptions{})
|
||||
defer s.MustClose()
|
||||
idb, putIndexDB := s.getCurrIndexDB()
|
||||
defer putIndexDB()
|
||||
|
||||
type want struct {
|
||||
missingMetricIDs []uint64
|
||||
missingTSIDsForMetricID uint64
|
||||
deletedMetricIDs []uint64
|
||||
}
|
||||
assertGetTSIDsFromMetricIDs := func(metricIDs []uint64, want want) {
|
||||
t.Helper()
|
||||
tsids, err := idb.getTSIDsFromMetricIDs(nil, metricIDs, noDeadline)
|
||||
if err != nil {
|
||||
t.Fatalf("getTSIDsFromMetricIDs() failed unexpectedly: %v", err)
|
||||
}
|
||||
if diff := cmp.Diff([]TSID{}, tsids); diff != "" {
|
||||
t.Fatalf("unexpected tsids (-want, +got):\n%s", diff)
|
||||
}
|
||||
missingMetricIDs := keys(s.missingMetricIDs)
|
||||
if diff := cmp.Diff(want.missingMetricIDs, missingMetricIDs); diff != "" {
|
||||
t.Fatalf("unexpected tsids (-want, +got):\n%s", diff)
|
||||
}
|
||||
if got, want := idb.extDB.missingTSIDsForMetricID.Load(), want.missingTSIDsForMetricID; got != want {
|
||||
t.Fatalf("unexpected missingTSIDsForMetricID metric value: got %d, want %d", got, want)
|
||||
}
|
||||
wantDeletedMetricIDs := &uint64set.Set{}
|
||||
wantDeletedMetricIDs.AddMulti(want.deletedMetricIDs)
|
||||
if !s.getDeletedMetricIDs().Equal(wantDeletedMetricIDs) {
|
||||
t.Fatalf("deleted metricIDs set is different from %v", want.deletedMetricIDs)
|
||||
}
|
||||
}
|
||||
|
||||
metricIDs := []uint64{1, 2, 3, 4}
|
||||
|
||||
// These metricIDs are not mapped to the corresponding TSIDs so they are
|
||||
// expected to be placed in missingMetricIDs cache but not be deleted yet.
|
||||
assertGetTSIDsFromMetricIDs(metricIDs, want{
|
||||
missingMetricIDs: metricIDs,
|
||||
missingTSIDsForMetricID: 0,
|
||||
deletedMetricIDs: []uint64{},
|
||||
})
|
||||
|
||||
// If we repeat search after one minute, the get soft-deleted and a
|
||||
// corresponding metric is incremented. The metric will remain in
|
||||
// missingMetricIDs cache for another minute.
|
||||
time.Sleep(61 * time.Second)
|
||||
synctest.Wait()
|
||||
assertGetTSIDsFromMetricIDs(metricIDs, want{
|
||||
missingMetricIDs: metricIDs,
|
||||
missingTSIDsForMetricID: uint64(len(metricIDs)),
|
||||
deletedMetricIDs: metricIDs,
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -7,11 +7,9 @@ import (
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"slices"
|
||||
"sort"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
@@ -21,7 +19,6 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/uint64set"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/workingsetcache"
|
||||
"github.com/VictoriaMetrics/fastcache"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
func TestMarshalUnmarshalMetricIDs(t *testing.T) {
|
||||
@@ -2091,75 +2088,6 @@ func TestSearchTSIDWithTimeRange(t *testing.T) {
|
||||
fs.MustRemoveAll(path)
|
||||
}
|
||||
|
||||
func TestIndexDB_MetricIDsNotMappedToTSIDsAreDeleted(t *testing.T) {
|
||||
defer testRemoveAll(t)
|
||||
|
||||
keys := func(missingMetricIDs map[uint64]uint64) []uint64 {
|
||||
keys := []uint64{}
|
||||
for k := range missingMetricIDs {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
slices.Sort(keys)
|
||||
return keys
|
||||
}
|
||||
|
||||
synctest.Run(func() {
|
||||
s := MustOpenStorage(t.Name(), OpenOptions{})
|
||||
defer s.MustClose()
|
||||
idb, putIndexDB := s.getCurrIndexDB()
|
||||
defer putIndexDB()
|
||||
|
||||
type want struct {
|
||||
missingMetricIDs []uint64
|
||||
missingTSIDsForMetricID uint64
|
||||
deletedMetricIDs []uint64
|
||||
}
|
||||
assertGetTSIDsFromMetricIDs := func(metricIDs []uint64, want want) {
|
||||
t.Helper()
|
||||
tsids, err := idb.getTSIDsFromMetricIDs(nil, metricIDs, noDeadline)
|
||||
if err != nil {
|
||||
t.Fatalf("getTSIDsFromMetricIDs() failed unexpectedly: %v", err)
|
||||
}
|
||||
if diff := cmp.Diff([]TSID{}, tsids); diff != "" {
|
||||
t.Fatalf("unexpected tsids (-want, +got):\n%s", diff)
|
||||
}
|
||||
missingMetricIDs := keys(s.missingMetricIDs)
|
||||
if diff := cmp.Diff(want.missingMetricIDs, missingMetricIDs); diff != "" {
|
||||
t.Fatalf("unexpected tsids (-want, +got):\n%s", diff)
|
||||
}
|
||||
if got, want := idb.extDB.missingTSIDsForMetricID.Load(), want.missingTSIDsForMetricID; got != want {
|
||||
t.Fatalf("unexpected missingTSIDsForMetricID metric value: got %d, want %d", got, want)
|
||||
}
|
||||
wantDeletedMetricIDs := &uint64set.Set{}
|
||||
wantDeletedMetricIDs.AddMulti(want.deletedMetricIDs)
|
||||
if !s.getDeletedMetricIDs().Equal(wantDeletedMetricIDs) {
|
||||
t.Fatalf("deleted metricIDs set is different from %v", want.deletedMetricIDs)
|
||||
}
|
||||
}
|
||||
|
||||
metricIDs := []uint64{1, 2, 3, 4}
|
||||
|
||||
// These metricIDs are not mapped to the corresponding TSIDs so they are
|
||||
// expected to be placed in missingMetricIDs cache but not be deleted yet.
|
||||
assertGetTSIDsFromMetricIDs(metricIDs, want{
|
||||
missingMetricIDs: metricIDs,
|
||||
missingTSIDsForMetricID: 0,
|
||||
deletedMetricIDs: []uint64{},
|
||||
})
|
||||
|
||||
// If we repeat search after one minute, the get soft-deleted and a
|
||||
// corresponding metric is incremented. The metric will remain in
|
||||
// missingMetricIDs cache for another minute.
|
||||
time.Sleep(61 * time.Second)
|
||||
synctest.Wait()
|
||||
assertGetTSIDsFromMetricIDs(metricIDs, want{
|
||||
missingMetricIDs: metricIDs,
|
||||
missingTSIDsForMetricID: uint64(len(metricIDs)),
|
||||
deletedMetricIDs: metricIDs,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func toTFPointers(tfs []tagFilter) []*tagFilter {
|
||||
tfps := make([]*tagFilter, len(tfs))
|
||||
for i := range tfs {
|
||||
|
||||
110
lib/storage/storage_synctest_test.go
Normal file
110
lib/storage/storage_synctest_test.go
Normal file
@@ -0,0 +1,110 @@
|
||||
//go:build goexperiment.synctest
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
func TestStorageSearchMetricNames_CorruptedIndex(t *testing.T) {
|
||||
defer testRemoveAll(t)
|
||||
|
||||
synctest.Run(func() {
|
||||
s := MustOpenStorage(t.Name(), OpenOptions{})
|
||||
defer s.MustClose()
|
||||
|
||||
now := time.Now().UTC()
|
||||
tr := TimeRange{
|
||||
MinTimestamp: time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC).UnixMilli(),
|
||||
MaxTimestamp: time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 999_999_999, time.UTC).UnixMilli(),
|
||||
}
|
||||
const numMetrics = 10
|
||||
date := uint64(tr.MinTimestamp) / msecPerDay
|
||||
idb, putCurrIndexDB := s.getCurrIndexDB()
|
||||
defer putCurrIndexDB()
|
||||
var wantMetricIDs []uint64
|
||||
|
||||
// Symulate corrupted index by inserting `(date, tag) -> metricID`
|
||||
// entries only.
|
||||
for i := range numMetrics {
|
||||
metricName := []byte(fmt.Sprintf("metric_%d", i))
|
||||
metricID := generateUniqueMetricID()
|
||||
wantMetricIDs = append(wantMetricIDs, metricID)
|
||||
|
||||
ii := getIndexItems()
|
||||
|
||||
// Create per-day tag -> metricID entries for every tag in mn.
|
||||
kb := kbPool.Get()
|
||||
kb.B = marshalCommonPrefix(kb.B[:0], nsPrefixDateTagToMetricIDs)
|
||||
kb.B = encoding.MarshalUint64(kb.B, date)
|
||||
ii.B = append(ii.B, kb.B...)
|
||||
ii.B = marshalTagValue(ii.B, nil)
|
||||
ii.B = marshalTagValue(ii.B, metricName)
|
||||
ii.B = encoding.MarshalUint64(ii.B, metricID)
|
||||
ii.Next()
|
||||
kbPool.Put(kb)
|
||||
|
||||
idb.tb.AddItems(ii.Items)
|
||||
|
||||
putIndexItems(ii)
|
||||
}
|
||||
idb.tb.DebugFlush()
|
||||
|
||||
tfsAll := NewTagFilters()
|
||||
if err := tfsAll.Add([]byte("__name__"), []byte(".*"), false, true); err != nil {
|
||||
panic(fmt.Sprintf("unexpected error in TagFilters.Add: %v", err))
|
||||
}
|
||||
tfssAll := []*TagFilters{tfsAll}
|
||||
|
||||
searchMetricIDs := func() []uint64 {
|
||||
metricIDs, err := idb.searchMetricIDs(nil, tfssAll, tr, 1e9, noDeadline)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("searchMetricIDs() failed unexpectedly: %v", err))
|
||||
}
|
||||
return metricIDs
|
||||
}
|
||||
searchMetricNames := func() []string {
|
||||
metricNames, err := s.SearchMetricNames(nil, tfssAll, tr, 1e9, noDeadline)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("SearchMetricNames() failed unexpectedly: %v", err))
|
||||
}
|
||||
return metricNames
|
||||
}
|
||||
|
||||
// Ensure that metricIDs can be searched.
|
||||
if diff := cmp.Diff(wantMetricIDs, searchMetricIDs()); diff != "" {
|
||||
t.Fatalf("unexpected metricIDs (-want, +got):\n%s", diff)
|
||||
}
|
||||
// Ensure that Storage.SearchMetricNames() returns empty result.
|
||||
// The corrupted index lets to find metricIDs by tag (`__name__` tag in
|
||||
// our case) but it lacks metricID->metricName mapping and hence the
|
||||
// empty search result.
|
||||
// The code detects this and puts such metricIDs into a special cache.
|
||||
if diff := cmp.Diff([]string{}, searchMetricNames()); diff != "" {
|
||||
t.Fatalf("unexpected metric names (-want, +got):\n%s", diff)
|
||||
}
|
||||
// Ensure that the metricIDs still can be searched.
|
||||
if diff := cmp.Diff(wantMetricIDs, searchMetricIDs()); diff != "" {
|
||||
t.Fatalf("unexpected metricIDs (-want, +got):\n%s", diff)
|
||||
}
|
||||
|
||||
time.Sleep(61 * time.Second)
|
||||
synctest.Wait()
|
||||
|
||||
// If the same search is repeated after 1 minute, the metricIDs are
|
||||
// marked as deleted.
|
||||
if diff := cmp.Diff([]string{}, searchMetricNames()); diff != "" {
|
||||
t.Fatalf("unexpected metric names (-want, +got):\n%s", diff)
|
||||
}
|
||||
// As a result they cannot be searched anymore.
|
||||
if diff := cmp.Diff([]uint64{}, searchMetricIDs()); diff != "" {
|
||||
t.Fatalf("unexpected metricIDs (-want, +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -14,10 +14,8 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
"testing/synctest"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
||||
vmfs "github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/uint64set"
|
||||
@@ -4014,100 +4012,3 @@ func TestStorageSearchTagValueSuffixes_maxTagValueSuffixes(t *testing.T) {
|
||||
wantCount = maxTagValueSuffixes
|
||||
assertSuffixCount(maxTagValueSuffixes, wantCount)
|
||||
}
|
||||
|
||||
func TestStorageSearchMetricNames_CorruptedIndex(t *testing.T) {
|
||||
defer testRemoveAll(t)
|
||||
|
||||
synctest.Run(func() {
|
||||
s := MustOpenStorage(t.Name(), OpenOptions{})
|
||||
defer s.MustClose()
|
||||
|
||||
now := time.Now().UTC()
|
||||
tr := TimeRange{
|
||||
MinTimestamp: time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC).UnixMilli(),
|
||||
MaxTimestamp: time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 999_999_999, time.UTC).UnixMilli(),
|
||||
}
|
||||
const numMetrics = 10
|
||||
date := uint64(tr.MinTimestamp) / msecPerDay
|
||||
idb, putCurrIndexDB := s.getCurrIndexDB()
|
||||
defer putCurrIndexDB()
|
||||
var wantMetricIDs []uint64
|
||||
|
||||
// Symulate corrupted index by inserting `(date, tag) -> metricID`
|
||||
// entries only.
|
||||
for i := range numMetrics {
|
||||
metricName := []byte(fmt.Sprintf("metric_%d", i))
|
||||
metricID := generateUniqueMetricID()
|
||||
wantMetricIDs = append(wantMetricIDs, metricID)
|
||||
|
||||
ii := getIndexItems()
|
||||
|
||||
// Create per-day tag -> metricID entries for every tag in mn.
|
||||
kb := kbPool.Get()
|
||||
kb.B = marshalCommonPrefix(kb.B[:0], nsPrefixDateTagToMetricIDs)
|
||||
kb.B = encoding.MarshalUint64(kb.B, date)
|
||||
ii.B = append(ii.B, kb.B...)
|
||||
ii.B = marshalTagValue(ii.B, nil)
|
||||
ii.B = marshalTagValue(ii.B, metricName)
|
||||
ii.B = encoding.MarshalUint64(ii.B, metricID)
|
||||
ii.Next()
|
||||
kbPool.Put(kb)
|
||||
|
||||
idb.tb.AddItems(ii.Items)
|
||||
|
||||
putIndexItems(ii)
|
||||
}
|
||||
idb.tb.DebugFlush()
|
||||
|
||||
tfsAll := NewTagFilters()
|
||||
if err := tfsAll.Add([]byte("__name__"), []byte(".*"), false, true); err != nil {
|
||||
panic(fmt.Sprintf("unexpected error in TagFilters.Add: %v", err))
|
||||
}
|
||||
tfssAll := []*TagFilters{tfsAll}
|
||||
|
||||
searchMetricIDs := func() []uint64 {
|
||||
metricIDs, err := idb.searchMetricIDs(nil, tfssAll, tr, 1e9, noDeadline)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("searchMetricIDs() failed unexpectedly: %v", err))
|
||||
}
|
||||
return metricIDs
|
||||
}
|
||||
searchMetricNames := func() []string {
|
||||
metricNames, err := s.SearchMetricNames(nil, tfssAll, tr, 1e9, noDeadline)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("SearchMetricNames() failed unexpectedly: %v", err))
|
||||
}
|
||||
return metricNames
|
||||
}
|
||||
|
||||
// Ensure that metricIDs can be searched.
|
||||
if diff := cmp.Diff(wantMetricIDs, searchMetricIDs()); diff != "" {
|
||||
t.Fatalf("unexpected metricIDs (-want, +got):\n%s", diff)
|
||||
}
|
||||
// Ensure that Storage.SearchMetricNames() returns empty result.
|
||||
// The corrupted index lets to find metricIDs by tag (`__name__` tag in
|
||||
// our case) but it lacks metricID->metricName mapping and hence the
|
||||
// empty search result.
|
||||
// The code detects this and puts such metricIDs into a special cache.
|
||||
if diff := cmp.Diff([]string{}, searchMetricNames()); diff != "" {
|
||||
t.Fatalf("unexpected metric names (-want, +got):\n%s", diff)
|
||||
}
|
||||
// Ensure that the metricIDs still can be searched.
|
||||
if diff := cmp.Diff(wantMetricIDs, searchMetricIDs()); diff != "" {
|
||||
t.Fatalf("unexpected metricIDs (-want, +got):\n%s", diff)
|
||||
}
|
||||
|
||||
time.Sleep(61 * time.Second)
|
||||
synctest.Wait()
|
||||
|
||||
// If the same search is repeated after 1 minute, the metricIDs are
|
||||
// marked as deleted.
|
||||
if diff := cmp.Diff([]string{}, searchMetricNames()); diff != "" {
|
||||
t.Fatalf("unexpected metric names (-want, +got):\n%s", diff)
|
||||
}
|
||||
// As a result they cannot be searched anymore.
|
||||
if diff := cmp.Diff([]uint64{}, searchMetricIDs()); diff != "" {
|
||||
t.Fatalf("unexpected metricIDs (-want, +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
793
lib/streamaggr/streamaggr_synctest_test.go
Normal file
793
lib/streamaggr/streamaggr_synctest_test.go
Normal file
@@ -0,0 +1,793 @@
|
||||
//go:build goexperiment.synctest
|
||||
|
||||
package streamaggr
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"sync"
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/prometheus"
|
||||
)
|
||||
|
||||
func TestAggregatorsSuccess(t *testing.T) {
|
||||
f := func(inputMetrics []string, interval time.Duration, outputMetricsExpected, config, matchIdxsStrExpected string) {
|
||||
t.Helper()
|
||||
synctest.Run(func() {
|
||||
var matchIdxs []byte
|
||||
var tssOutput []prompbmarshal.TimeSeries
|
||||
var tssOutputLock sync.Mutex
|
||||
|
||||
// Initialize Aggregators
|
||||
pushFunc := func(tss []prompbmarshal.TimeSeries) {
|
||||
tssOutputLock.Lock()
|
||||
tssOutput = appendClonedTimeseries(tssOutput, tss)
|
||||
tssOutputLock.Unlock()
|
||||
}
|
||||
a, err := LoadFromData([]byte(config), pushFunc, nil, "some_alias")
|
||||
if err != nil {
|
||||
t.Fatalf("cannot initialize aggregators: %s", err)
|
||||
}
|
||||
for _, metrics := range inputMetrics {
|
||||
// Push the inputMetrics to Aggregators
|
||||
offsetMsecs := time.Now().UnixMilli()
|
||||
tssInput := prometheus.MustParsePromMetrics(metrics, offsetMsecs)
|
||||
matchIdxs = append(matchIdxs, a.Push(tssInput, nil)...)
|
||||
time.Sleep(interval)
|
||||
}
|
||||
|
||||
a.MustStop()
|
||||
|
||||
// Verify matchIdxs equals to matchIdxsExpected
|
||||
matchIdxsStr := ""
|
||||
for _, v := range matchIdxs {
|
||||
matchIdxsStr += strconv.Itoa(int(v))
|
||||
}
|
||||
if matchIdxsStr != matchIdxsStrExpected {
|
||||
t.Fatalf("unexpected matchIdxs;\ngot\n%s\nwant\n%s", matchIdxsStr, matchIdxsStrExpected)
|
||||
}
|
||||
|
||||
// Verify the tssOutput contains the expected metrics
|
||||
outputMetrics := timeSeriessToString(tssOutput)
|
||||
if outputMetrics != outputMetricsExpected {
|
||||
t.Fatalf("unexpected output metrics;\ngot\n%s\nwant\n%s", outputMetrics, outputMetricsExpected)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Empty config
|
||||
f([]string{}, time.Second, ``, ``, "")
|
||||
f([]string{`foo{bar="baz"} 1`}, time.Second, ``, ``, "0")
|
||||
f([]string{"foo 1\nbaz 2"}, time.Second, ``, ``, "00")
|
||||
|
||||
// Empty by list - aggregate only by time
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5 11
|
||||
bar 34 10
|
||||
foo{abc="123"} 8.5
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `bar:1m_count_samples 2
|
||||
bar:1m_count_series 1
|
||||
bar:1m_last 5
|
||||
bar:1m_sum_samples 39
|
||||
foo:1m_count_samples{abc="123"} 2
|
||||
foo:1m_count_samples{abc="456",de="fg"} 1
|
||||
foo:1m_count_series{abc="123"} 1
|
||||
foo:1m_count_series{abc="456",de="fg"} 1
|
||||
foo:1m_last{abc="123"} 8.5
|
||||
foo:1m_last{abc="456",de="fg"} 8
|
||||
foo:1m_sum_samples{abc="123"} 12.5
|
||||
foo:1m_sum_samples{abc="456",de="fg"} 8
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [count_samples, sum_samples, count_series, last]
|
||||
`, "11111")
|
||||
|
||||
// Special case: __name__ in `by` list - this is the same as empty `by` list
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `bar:1m_count_samples 1
|
||||
bar:1m_count_series 1
|
||||
bar:1m_sum_samples 5
|
||||
foo:1m_count_samples 3
|
||||
foo:1m_count_series 2
|
||||
foo:1m_sum_samples 20.5
|
||||
`, `
|
||||
- interval: 1m
|
||||
by: [__name__]
|
||||
outputs: [count_samples, sum_samples, count_series]
|
||||
`, "1111")
|
||||
|
||||
// Non-empty `by` list with non-existing labels
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `bar:1m_by_bar_foo_count_samples 1
|
||||
bar:1m_by_bar_foo_count_series 1
|
||||
bar:1m_by_bar_foo_sum_samples 5
|
||||
foo:1m_by_bar_foo_count_samples 3
|
||||
foo:1m_by_bar_foo_count_series 2
|
||||
foo:1m_by_bar_foo_sum_samples 20.5
|
||||
`, `
|
||||
- interval: 1m
|
||||
by: [foo, bar]
|
||||
outputs: [count_samples, sum_samples, count_series]
|
||||
`, "1111")
|
||||
|
||||
// Non-empty `by` list with existing label
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `bar:1m_by_abc_count_samples 1
|
||||
bar:1m_by_abc_count_series 1
|
||||
bar:1m_by_abc_sum_samples 5
|
||||
foo:1m_by_abc_count_samples{abc="123"} 2
|
||||
foo:1m_by_abc_count_samples{abc="456"} 1
|
||||
foo:1m_by_abc_count_series{abc="123"} 1
|
||||
foo:1m_by_abc_count_series{abc="456"} 1
|
||||
foo:1m_by_abc_sum_samples{abc="123"} 12.5
|
||||
foo:1m_by_abc_sum_samples{abc="456"} 8
|
||||
`, `
|
||||
- interval: 1m
|
||||
by: [abc]
|
||||
outputs: [count_samples, sum_samples, count_series]
|
||||
`, "1111")
|
||||
|
||||
// Non-empty `by` list with duplicate existing label
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `bar:1m_by_abc_count_samples 1
|
||||
bar:1m_by_abc_count_series 1
|
||||
bar:1m_by_abc_sum_samples 5
|
||||
foo:1m_by_abc_count_samples{abc="123"} 2
|
||||
foo:1m_by_abc_count_samples{abc="456"} 1
|
||||
foo:1m_by_abc_count_series{abc="123"} 1
|
||||
foo:1m_by_abc_count_series{abc="456"} 1
|
||||
foo:1m_by_abc_sum_samples{abc="123"} 12.5
|
||||
foo:1m_by_abc_sum_samples{abc="456"} 8
|
||||
`, `
|
||||
- interval: 1m
|
||||
by: [abc, abc]
|
||||
outputs: [count_samples, sum_samples, count_series]
|
||||
`, "1111")
|
||||
|
||||
// Non-empty `without` list with non-existing labels
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `bar:1m_without_foo_count_samples 1
|
||||
bar:1m_without_foo_count_series 1
|
||||
bar:1m_without_foo_sum_samples 5
|
||||
foo:1m_without_foo_count_samples{abc="123"} 2
|
||||
foo:1m_without_foo_count_samples{abc="456",de="fg"} 1
|
||||
foo:1m_without_foo_count_series{abc="123"} 1
|
||||
foo:1m_without_foo_count_series{abc="456",de="fg"} 1
|
||||
foo:1m_without_foo_sum_samples{abc="123"} 12.5
|
||||
foo:1m_without_foo_sum_samples{abc="456",de="fg"} 8
|
||||
`, `
|
||||
- interval: 1m
|
||||
without: [foo]
|
||||
outputs: [count_samples, sum_samples, count_series]
|
||||
`, "1111")
|
||||
|
||||
// Non-empty `without` list with existing labels
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `bar:1m_without_abc_count_samples 1
|
||||
bar:1m_without_abc_count_series 1
|
||||
bar:1m_without_abc_sum_samples 5
|
||||
foo:1m_without_abc_count_samples 2
|
||||
foo:1m_without_abc_count_samples{de="fg"} 1
|
||||
foo:1m_without_abc_count_series 1
|
||||
foo:1m_without_abc_count_series{de="fg"} 1
|
||||
foo:1m_without_abc_sum_samples 12.5
|
||||
foo:1m_without_abc_sum_samples{de="fg"} 8
|
||||
`, `
|
||||
- interval: 1m
|
||||
without: [abc]
|
||||
outputs: [count_samples, sum_samples, count_series]
|
||||
`, "1111")
|
||||
|
||||
// Special case: __name__ in `without` list
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `:1m_count_samples 1
|
||||
:1m_count_samples{abc="123"} 2
|
||||
:1m_count_samples{abc="456",de="fg"} 1
|
||||
:1m_count_series 1
|
||||
:1m_count_series{abc="123"} 1
|
||||
:1m_count_series{abc="456",de="fg"} 1
|
||||
:1m_sum_samples 5
|
||||
:1m_sum_samples{abc="123"} 12.5
|
||||
:1m_sum_samples{abc="456",de="fg"} 8
|
||||
`, `
|
||||
- interval: 1m
|
||||
without: [__name__]
|
||||
outputs: [count_samples, sum_samples, count_series]
|
||||
`, "1111")
|
||||
|
||||
// drop some input metrics
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `bar:1m_without_abc_count_samples 1
|
||||
bar:1m_without_abc_count_series 1
|
||||
bar:1m_without_abc_sum_samples 5
|
||||
`, `
|
||||
- interval: 1m
|
||||
without: [abc]
|
||||
outputs: [count_samples, sum_samples, count_series]
|
||||
input_relabel_configs:
|
||||
- if: 'foo'
|
||||
action: drop
|
||||
`, "1111")
|
||||
|
||||
// rename output metrics
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `bar-1m-without-abc-count-samples 1
|
||||
bar-1m-without-abc-count-series 1
|
||||
bar-1m-without-abc-sum-samples 5
|
||||
foo-1m-without-abc-count-samples 2
|
||||
foo-1m-without-abc-count-series 1
|
||||
foo-1m-without-abc-sum-samples 12.5
|
||||
`, `
|
||||
- interval: 1m
|
||||
without: [abc]
|
||||
outputs: [count_samples, sum_samples, count_series]
|
||||
output_relabel_configs:
|
||||
- action: replace_all
|
||||
source_labels: [__name__]
|
||||
regex: ":|_"
|
||||
replacement: "-"
|
||||
target_label: __name__
|
||||
- action: drop
|
||||
source_labels: [de]
|
||||
regex: fg
|
||||
`, "1111")
|
||||
|
||||
// match doesn't match anything
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, ``, `
|
||||
- interval: 1m
|
||||
without: [abc]
|
||||
outputs: [count_samples, sum_samples, count_series]
|
||||
match: '{non_existing_label!=""}'
|
||||
name: foobar
|
||||
`, "0000")
|
||||
|
||||
// match matches foo series with non-empty abc label
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `foo:1m_by_abc_count_samples{abc="123"} 2
|
||||
foo:1m_by_abc_count_samples{abc="456"} 1
|
||||
foo:1m_by_abc_count_series{abc="123"} 1
|
||||
foo:1m_by_abc_count_series{abc="456"} 1
|
||||
foo:1m_by_abc_sum_samples{abc="123"} 12.5
|
||||
foo:1m_by_abc_sum_samples{abc="456"} 8
|
||||
`, `
|
||||
- interval: 1m
|
||||
by: [abc]
|
||||
outputs: [count_samples, sum_samples, count_series]
|
||||
name: abcdef
|
||||
match:
|
||||
- foo{abc=~".+"}
|
||||
- '{non_existing_label!=""}'
|
||||
`, "1011")
|
||||
|
||||
// total output for non-repeated series
|
||||
f([]string{`
|
||||
foo 123
|
||||
bar{baz="qwe"} 4.34
|
||||
`}, time.Minute, `bar:1m_total{baz="qwe"} 0
|
||||
foo:1m_total 0
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [total]
|
||||
`, "11")
|
||||
|
||||
// total output for non-repeated series, ignore first sample 0s
|
||||
f([]string{`
|
||||
foo 123
|
||||
bar{baz="qwe"} 4.34
|
||||
`}, time.Minute, `bar:1m_total{baz="qwe"} 4.34
|
||||
foo:1m_total 123
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [total]
|
||||
ignore_first_sample_interval: 0s
|
||||
`, "11")
|
||||
|
||||
// total_prometheus output for non-repeated series
|
||||
f([]string{`
|
||||
foo 123
|
||||
bar{baz="qwe"} 4.34
|
||||
`}, time.Minute, `bar:1m_total_prometheus{baz="qwe"} 0
|
||||
foo:1m_total_prometheus 0
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [total_prometheus]
|
||||
`, "11")
|
||||
|
||||
// total output for repeated series
|
||||
f([]string{`
|
||||
foo 123
|
||||
bar{baz="qwe"} 1.31
|
||||
bar{baz="qwe"} 4.34 1
|
||||
bar{baz="qwe"} 2
|
||||
foo{baz="qwe"} -5
|
||||
bar{baz="qwer"} 343
|
||||
bar{baz="qwer"} 344
|
||||
foo{baz="qwe"} 10
|
||||
`}, time.Minute, `bar:1m_total{baz="qwe"} 3.03
|
||||
bar:1m_total{baz="qwer"} 1
|
||||
foo:1m_total 0
|
||||
foo:1m_total{baz="qwe"} 15
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [total]
|
||||
`, "11111111")
|
||||
|
||||
// total_prometheus output for repeated series
|
||||
f([]string{`
|
||||
foo 123
|
||||
bar{baz="qwe"} 1.32
|
||||
bar{baz="qwe"} 4.34
|
||||
bar{baz="qwe"} 2
|
||||
foo{baz="qwe"} -5
|
||||
bar{baz="qwer"} 343
|
||||
bar{baz="qwer"} 344
|
||||
foo{baz="qwe"} 10
|
||||
`}, time.Minute, `bar:1m_total_prometheus{baz="qwe"} 5.02
|
||||
bar:1m_total_prometheus{baz="qwer"} 1
|
||||
foo:1m_total_prometheus 0
|
||||
foo:1m_total_prometheus{baz="qwe"} 15
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [total_prometheus]
|
||||
`, "11111111")
|
||||
|
||||
// total output for repeated series with group by __name__
|
||||
f([]string{`
|
||||
foo 123
|
||||
bar{baz="qwe"} 1.32
|
||||
bar{baz="qwe"} 4.34
|
||||
bar{baz="qwe"} 2
|
||||
foo{baz="qwe"} -5
|
||||
bar{baz="qwer"} 343
|
||||
bar{baz="qwer"} 344
|
||||
foo{baz="qwe"} 10
|
||||
`}, time.Minute, `bar:1m_total 6.02
|
||||
foo:1m_total 15
|
||||
`, `
|
||||
- interval: 1m
|
||||
by: [__name__]
|
||||
outputs: [total]
|
||||
`, "11111111")
|
||||
|
||||
// total_prometheus output for repeated series with group by __name__
|
||||
f([]string{`
|
||||
foo 123
|
||||
bar{baz="qwe"} 1.32
|
||||
bar{baz="qwe"} 4.34
|
||||
bar{baz="qwe"} 2
|
||||
foo{baz="qwe"} -5
|
||||
bar{baz="qwer"} 343
|
||||
bar{baz="qwer"} 344
|
||||
foo{baz="qwe"} 10
|
||||
`}, time.Minute, `bar:1m_total_prometheus 6.02
|
||||
foo:1m_total_prometheus 15
|
||||
`, `
|
||||
- interval: 1m
|
||||
by: [__name__]
|
||||
outputs: [total_prometheus]
|
||||
`, "11111111")
|
||||
|
||||
// increase output for non-repeated series
|
||||
f([]string{`
|
||||
foo 123
|
||||
bar{baz="qwe"} 4.34
|
||||
`}, time.Minute, `bar:1m_increase{baz="qwe"} 0
|
||||
foo:1m_increase 0
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [increase]
|
||||
`, "11")
|
||||
|
||||
// increase_prometheus output for non-repeated series
|
||||
f([]string{`
|
||||
foo 123
|
||||
bar{baz="qwe"} 4.34
|
||||
`}, time.Minute, `bar:1m_increase_prometheus{baz="qwe"} 0
|
||||
foo:1m_increase_prometheus 0
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [increase_prometheus]
|
||||
`, "11")
|
||||
|
||||
// increase output for repeated series
|
||||
f([]string{`
|
||||
foo 123
|
||||
bar{baz="qwe"} 1.32
|
||||
bar{baz="qwe"} 4.34
|
||||
bar{baz="qwe"} 2
|
||||
foo{baz="qwe"} -5
|
||||
bar{baz="qwer"} 343
|
||||
bar{baz="qwer"} 344
|
||||
foo{baz="qwe"} 10
|
||||
`}, time.Minute, `bar:1m_increase{baz="qwe"} 5.02
|
||||
bar:1m_increase{baz="qwer"} 1
|
||||
foo:1m_increase 0
|
||||
foo:1m_increase{baz="qwe"} 15
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [increase]
|
||||
`, "11111111")
|
||||
|
||||
// increase_prometheus output for repeated series
|
||||
f([]string{`
|
||||
foo 123
|
||||
bar{baz="qwe"} 1.32
|
||||
bar{baz="qwe"} 4.34
|
||||
bar{baz="qwe"} 2
|
||||
foo{baz="qwe"} -5
|
||||
bar{baz="qwer"} 343
|
||||
bar{baz="qwer"} 344
|
||||
foo{baz="qwe"} 10
|
||||
`}, time.Minute, `bar:1m_increase_prometheus{baz="qwe"} 5.02
|
||||
bar:1m_increase_prometheus{baz="qwer"} 1
|
||||
foo:1m_increase_prometheus 0
|
||||
foo:1m_increase_prometheus{baz="qwe"} 15
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [increase_prometheus]
|
||||
`, "11111111")
|
||||
|
||||
// multiple aggregate configs
|
||||
f([]string{`
|
||||
foo 1
|
||||
foo{bar="baz"} 2
|
||||
foo 3.3
|
||||
`, ``, ``, ``, ``}, time.Minute, `foo:1m_count_series 1
|
||||
foo:1m_count_series{bar="baz"} 1
|
||||
foo:1m_sum_samples 0
|
||||
foo:1m_sum_samples 0
|
||||
foo:1m_sum_samples 4.3
|
||||
foo:1m_sum_samples{bar="baz"} 0
|
||||
foo:1m_sum_samples{bar="baz"} 0
|
||||
foo:1m_sum_samples{bar="baz"} 2
|
||||
foo:5m_by_bar_sum_samples 4.3
|
||||
foo:5m_by_bar_sum_samples{bar="baz"} 2
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [count_series, sum_samples]
|
||||
- interval: 5m
|
||||
by: [bar]
|
||||
outputs: [sum_samples]
|
||||
`, "111")
|
||||
|
||||
// min and max outputs
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `bar:1m_max 5
|
||||
bar:1m_min 5
|
||||
foo:1m_max{abc="123"} 8.5
|
||||
foo:1m_max{abc="456",de="fg"} 8
|
||||
foo:1m_min{abc="123"} 4
|
||||
foo:1m_min{abc="456",de="fg"} 8
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [min, max]
|
||||
`, "1111")
|
||||
|
||||
// avg output
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `bar:1m_avg 5
|
||||
foo:1m_avg{abc="123"} 6.25
|
||||
foo:1m_avg{abc="456",de="fg"} 8
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [avg]
|
||||
`, "1111")
|
||||
|
||||
// stddev output
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `bar:1m_stddev 0
|
||||
foo:1m_stddev{abc="123"} 2.25
|
||||
foo:1m_stddev{abc="456",de="fg"} 0
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [stddev]
|
||||
`, "1111")
|
||||
|
||||
// stdvar output
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `bar:1m_stdvar 0
|
||||
foo:1m_stdvar{abc="123"} 5.0625
|
||||
foo:1m_stdvar{abc="456",de="fg"} 0
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [stdvar]
|
||||
`, "1111")
|
||||
|
||||
// histogram_bucket output
|
||||
f([]string{`
|
||||
cpu_usage{cpu="1"} 12.5
|
||||
cpu_usage{cpu="1"} 13.3
|
||||
cpu_usage{cpu="1"} 13
|
||||
cpu_usage{cpu="1"} 12
|
||||
cpu_usage{cpu="1"} 14
|
||||
cpu_usage{cpu="1"} 25
|
||||
cpu_usage{cpu="2"} 90
|
||||
`}, time.Minute, `cpu_usage:1m_histogram_bucket{cpu="1",vmrange="1.136e+01...1.292e+01"} 2
|
||||
cpu_usage:1m_histogram_bucket{cpu="1",vmrange="1.292e+01...1.468e+01"} 3
|
||||
cpu_usage:1m_histogram_bucket{cpu="1",vmrange="2.448e+01...2.783e+01"} 1
|
||||
cpu_usage:1m_histogram_bucket{cpu="2",vmrange="8.799e+01...1.000e+02"} 1
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [histogram_bucket]
|
||||
`, "1111111")
|
||||
|
||||
// histogram_bucket output without cpu
|
||||
f([]string{`
|
||||
cpu_usage{cpu="1"} 12.5
|
||||
cpu_usage{cpu="1"} 13.3
|
||||
cpu_usage{cpu="1"} 13
|
||||
cpu_usage{cpu="1"} 12
|
||||
cpu_usage{cpu="1"} 14
|
||||
cpu_usage{cpu="1"} 25
|
||||
cpu_usage{cpu="2"} 90
|
||||
`}, time.Minute, `cpu_usage:1m_without_cpu_histogram_bucket{vmrange="1.136e+01...1.292e+01"} 2
|
||||
cpu_usage:1m_without_cpu_histogram_bucket{vmrange="1.292e+01...1.468e+01"} 3
|
||||
cpu_usage:1m_without_cpu_histogram_bucket{vmrange="2.448e+01...2.783e+01"} 1
|
||||
cpu_usage:1m_without_cpu_histogram_bucket{vmrange="8.799e+01...1.000e+02"} 1
|
||||
`, `
|
||||
- interval: 1m
|
||||
without: [cpu]
|
||||
outputs: [histogram_bucket]
|
||||
`, "1111111")
|
||||
|
||||
// quantiles output
|
||||
f([]string{`
|
||||
cpu_usage{cpu="1"} 12.5
|
||||
cpu_usage{cpu="1"} 13.3
|
||||
cpu_usage{cpu="1"} 13
|
||||
cpu_usage{cpu="1"} 12
|
||||
cpu_usage{cpu="1"} 14
|
||||
cpu_usage{cpu="1"} 25
|
||||
cpu_usage{cpu="2"} 90
|
||||
`}, time.Minute, `cpu_usage:1m_quantiles{cpu="1",quantile="0"} 12
|
||||
cpu_usage:1m_quantiles{cpu="1",quantile="0.5"} 13.3
|
||||
cpu_usage:1m_quantiles{cpu="1",quantile="1"} 25
|
||||
cpu_usage:1m_quantiles{cpu="2",quantile="0"} 90
|
||||
cpu_usage:1m_quantiles{cpu="2",quantile="0.5"} 90
|
||||
cpu_usage:1m_quantiles{cpu="2",quantile="1"} 90
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: ["quantiles(0, 0.5, 1)"]
|
||||
`, "1111111")
|
||||
|
||||
// quantiles output without cpu
|
||||
f([]string{`
|
||||
cpu_usage{cpu="1"} 12.5
|
||||
cpu_usage{cpu="1"} 13.3
|
||||
cpu_usage{cpu="1"} 13
|
||||
cpu_usage{cpu="1"} 12
|
||||
cpu_usage{cpu="1"} 14
|
||||
cpu_usage{cpu="1"} 25
|
||||
cpu_usage{cpu="2"} 90
|
||||
`}, time.Minute, `cpu_usage:1m_without_cpu_quantiles{quantile="0"} 12
|
||||
cpu_usage:1m_without_cpu_quantiles{quantile="0.5"} 13.3
|
||||
cpu_usage:1m_without_cpu_quantiles{quantile="1"} 90
|
||||
`, `
|
||||
- interval: 1m
|
||||
without: [cpu]
|
||||
outputs: ["quantiles(0, 0.5, 1)"]
|
||||
`, "1111111")
|
||||
|
||||
// append additional label
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5 10
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `bar-1m-without-abc-count-samples{new_label="must_keep_metric_name"} 1
|
||||
bar-1m-without-abc-count-series{new_label="must_keep_metric_name"} 1
|
||||
bar-1m-without-abc-sum-samples{new_label="must_keep_metric_name"} 5
|
||||
foo-1m-without-abc-count-samples{new_label="must_keep_metric_name"} 2
|
||||
foo-1m-without-abc-count-series{new_label="must_keep_metric_name"} 1
|
||||
foo-1m-without-abc-sum-samples{new_label="must_keep_metric_name"} 12.5
|
||||
`, `
|
||||
- interval: 1m
|
||||
without: [abc]
|
||||
outputs: [count_samples, sum_samples, count_series]
|
||||
output_relabel_configs:
|
||||
- action: replace_all
|
||||
source_labels: [__name__]
|
||||
regex: ":|_"
|
||||
replacement: "-"
|
||||
target_label: __name__
|
||||
- action: drop
|
||||
source_labels: [de]
|
||||
regex: fg
|
||||
- target_label: new_label
|
||||
replacement: must_keep_metric_name
|
||||
`, "1111")
|
||||
|
||||
// test rate_sum and rate_avg
|
||||
f([]string{`
|
||||
foo{abc="123", cde="1"} 3
|
||||
foo{abc="456", cde="1"} 8.5
|
||||
foo 12 34
|
||||
`, `
|
||||
foo{abc="123", cde="1"} 8
|
||||
foo{abc="456", cde="1"} 11
|
||||
`}, time.Minute, `foo:1m_by_cde_rate_avg{cde="1"} 0.0625
|
||||
foo:1m_by_cde_rate_sum{cde="1"} 0.125
|
||||
`, `
|
||||
- interval: 1m
|
||||
by: [cde]
|
||||
outputs: [rate_sum, rate_avg]
|
||||
`, "11111")
|
||||
|
||||
// test rate_sum and rate_avg, when two aggregation intervals are empty
|
||||
f([]string{`
|
||||
foo{abc="123", cde="1"} 2
|
||||
foo{abc="456", cde="1"} 8
|
||||
foo{abc="777", cde="1"} 9 -10
|
||||
`, ``, ``, `
|
||||
foo{abc="123", cde="1"} 20
|
||||
foo{abc="456", cde="1"} 26
|
||||
foo{abc="777", cde="1"} 27 -10
|
||||
`}, time.Minute, `foo:1m_by_cde_rate_avg{cde="1"} 0.1
|
||||
foo:1m_by_cde_rate_sum{cde="1"} 0.2
|
||||
`, `
|
||||
- interval: 1m
|
||||
by: [cde]
|
||||
outputs: [rate_sum, rate_avg]
|
||||
enable_windows: true
|
||||
`, "111111")
|
||||
|
||||
// rate_sum and rate_avg with duplicated events
|
||||
f([]string{`
|
||||
foo{abc="123", cde="1"} 4 10
|
||||
foo{abc="123", cde="1"} 4 10
|
||||
`}, time.Minute, ``, `
|
||||
- interval: 1m
|
||||
outputs: [rate_sum, rate_avg]
|
||||
`, "11")
|
||||
|
||||
// rate_sum and rate_avg for a single sample
|
||||
f([]string{`
|
||||
foo 4 10
|
||||
bar 5 10
|
||||
`}, time.Minute, ``, `
|
||||
- interval: 1m
|
||||
outputs: [rate_sum, rate_avg]
|
||||
`, "11")
|
||||
|
||||
// unique_samples output
|
||||
f([]string{`
|
||||
foo 1 10
|
||||
foo 2 20
|
||||
foo 1 10
|
||||
foo 2 20
|
||||
foo 3 20
|
||||
`}, time.Minute, `foo:1m_unique_samples 3
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [unique_samples]
|
||||
`, "11111")
|
||||
|
||||
// keep_metric_names
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
bar -34.3
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `bar 2
|
||||
foo{abc="123"} 2
|
||||
foo{abc="456",de="fg"} 1
|
||||
`, `
|
||||
- interval: 1m
|
||||
keep_metric_names: true
|
||||
outputs: [count_samples]
|
||||
`, "11111")
|
||||
|
||||
// drop_input_labels
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
bar -34.3
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `bar 2
|
||||
foo 2
|
||||
foo{de="fg"} 1
|
||||
`, `
|
||||
- interval: 1m
|
||||
drop_input_labels: [abc]
|
||||
keep_metric_names: true
|
||||
outputs: [count_samples]
|
||||
`, "11111")
|
||||
|
||||
f([]string{`
|
||||
foo 123
|
||||
bar 567
|
||||
`, ``, ``}, 30*time.Second, `bar:1m_sum_samples 567
|
||||
foo:1m_sum_samples 123
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [sum_samples]
|
||||
dedup_interval: 30s
|
||||
`, "11")
|
||||
|
||||
f([]string{`
|
||||
foo 123
|
||||
bar{baz="qwe"} 1.32
|
||||
bar{baz="qwe"} 4.34
|
||||
bar{baz="qwe"} 2
|
||||
foo{baz="qwe"} -5
|
||||
bar{baz="qwer"} 343
|
||||
bar{baz="qwer"} 344
|
||||
foo{baz="qwe"} 10
|
||||
`, ``, ``}, 30*time.Second, `bar:1m_sum_samples{baz="qwe"} 4.34
|
||||
bar:1m_sum_samples{baz="qwer"} 344
|
||||
foo:1m_sum_samples 123
|
||||
foo:1m_sum_samples{baz="qwe"} 10
|
||||
`, `
|
||||
- interval: 1m
|
||||
dedup_interval: 30s
|
||||
outputs: [sum_samples]
|
||||
`, "11111111")
|
||||
}
|
||||
@@ -3,16 +3,11 @@ package streamaggr
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/prometheus"
|
||||
)
|
||||
|
||||
func TestAggregatorsFailure(t *testing.T) {
|
||||
@@ -256,785 +251,6 @@ func TestAggregatorsEqual(t *testing.T) {
|
||||
ignore_first_intervals: 4`, false)
|
||||
}
|
||||
|
||||
func TestAggregatorsSuccess(t *testing.T) {
|
||||
f := func(inputMetrics []string, interval time.Duration, outputMetricsExpected, config, matchIdxsStrExpected string) {
|
||||
t.Helper()
|
||||
synctest.Run(func() {
|
||||
var matchIdxs []byte
|
||||
var tssOutput []prompbmarshal.TimeSeries
|
||||
var tssOutputLock sync.Mutex
|
||||
|
||||
// Initialize Aggregators
|
||||
pushFunc := func(tss []prompbmarshal.TimeSeries) {
|
||||
tssOutputLock.Lock()
|
||||
tssOutput = appendClonedTimeseries(tssOutput, tss)
|
||||
tssOutputLock.Unlock()
|
||||
}
|
||||
a, err := LoadFromData([]byte(config), pushFunc, nil, "some_alias")
|
||||
if err != nil {
|
||||
t.Fatalf("cannot initialize aggregators: %s", err)
|
||||
}
|
||||
for _, metrics := range inputMetrics {
|
||||
// Push the inputMetrics to Aggregators
|
||||
offsetMsecs := time.Now().UnixMilli()
|
||||
tssInput := prometheus.MustParsePromMetrics(metrics, offsetMsecs)
|
||||
matchIdxs = append(matchIdxs, a.Push(tssInput, nil)...)
|
||||
time.Sleep(interval)
|
||||
}
|
||||
|
||||
a.MustStop()
|
||||
|
||||
// Verify matchIdxs equals to matchIdxsExpected
|
||||
matchIdxsStr := ""
|
||||
for _, v := range matchIdxs {
|
||||
matchIdxsStr += strconv.Itoa(int(v))
|
||||
}
|
||||
if matchIdxsStr != matchIdxsStrExpected {
|
||||
t.Fatalf("unexpected matchIdxs;\ngot\n%s\nwant\n%s", matchIdxsStr, matchIdxsStrExpected)
|
||||
}
|
||||
|
||||
// Verify the tssOutput contains the expected metrics
|
||||
outputMetrics := timeSeriessToString(tssOutput)
|
||||
if outputMetrics != outputMetricsExpected {
|
||||
t.Fatalf("unexpected output metrics;\ngot\n%s\nwant\n%s", outputMetrics, outputMetricsExpected)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Empty config
|
||||
f([]string{}, time.Second, ``, ``, "")
|
||||
f([]string{`foo{bar="baz"} 1`}, time.Second, ``, ``, "0")
|
||||
f([]string{"foo 1\nbaz 2"}, time.Second, ``, ``, "00")
|
||||
|
||||
// Empty by list - aggregate only by time
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5 11
|
||||
bar 34 10
|
||||
foo{abc="123"} 8.5
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `bar:1m_count_samples 2
|
||||
bar:1m_count_series 1
|
||||
bar:1m_last 5
|
||||
bar:1m_sum_samples 39
|
||||
foo:1m_count_samples{abc="123"} 2
|
||||
foo:1m_count_samples{abc="456",de="fg"} 1
|
||||
foo:1m_count_series{abc="123"} 1
|
||||
foo:1m_count_series{abc="456",de="fg"} 1
|
||||
foo:1m_last{abc="123"} 8.5
|
||||
foo:1m_last{abc="456",de="fg"} 8
|
||||
foo:1m_sum_samples{abc="123"} 12.5
|
||||
foo:1m_sum_samples{abc="456",de="fg"} 8
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [count_samples, sum_samples, count_series, last]
|
||||
`, "11111")
|
||||
|
||||
// Special case: __name__ in `by` list - this is the same as empty `by` list
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `bar:1m_count_samples 1
|
||||
bar:1m_count_series 1
|
||||
bar:1m_sum_samples 5
|
||||
foo:1m_count_samples 3
|
||||
foo:1m_count_series 2
|
||||
foo:1m_sum_samples 20.5
|
||||
`, `
|
||||
- interval: 1m
|
||||
by: [__name__]
|
||||
outputs: [count_samples, sum_samples, count_series]
|
||||
`, "1111")
|
||||
|
||||
// Non-empty `by` list with non-existing labels
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `bar:1m_by_bar_foo_count_samples 1
|
||||
bar:1m_by_bar_foo_count_series 1
|
||||
bar:1m_by_bar_foo_sum_samples 5
|
||||
foo:1m_by_bar_foo_count_samples 3
|
||||
foo:1m_by_bar_foo_count_series 2
|
||||
foo:1m_by_bar_foo_sum_samples 20.5
|
||||
`, `
|
||||
- interval: 1m
|
||||
by: [foo, bar]
|
||||
outputs: [count_samples, sum_samples, count_series]
|
||||
`, "1111")
|
||||
|
||||
// Non-empty `by` list with existing label
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `bar:1m_by_abc_count_samples 1
|
||||
bar:1m_by_abc_count_series 1
|
||||
bar:1m_by_abc_sum_samples 5
|
||||
foo:1m_by_abc_count_samples{abc="123"} 2
|
||||
foo:1m_by_abc_count_samples{abc="456"} 1
|
||||
foo:1m_by_abc_count_series{abc="123"} 1
|
||||
foo:1m_by_abc_count_series{abc="456"} 1
|
||||
foo:1m_by_abc_sum_samples{abc="123"} 12.5
|
||||
foo:1m_by_abc_sum_samples{abc="456"} 8
|
||||
`, `
|
||||
- interval: 1m
|
||||
by: [abc]
|
||||
outputs: [count_samples, sum_samples, count_series]
|
||||
`, "1111")
|
||||
|
||||
// Non-empty `by` list with duplicate existing label
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `bar:1m_by_abc_count_samples 1
|
||||
bar:1m_by_abc_count_series 1
|
||||
bar:1m_by_abc_sum_samples 5
|
||||
foo:1m_by_abc_count_samples{abc="123"} 2
|
||||
foo:1m_by_abc_count_samples{abc="456"} 1
|
||||
foo:1m_by_abc_count_series{abc="123"} 1
|
||||
foo:1m_by_abc_count_series{abc="456"} 1
|
||||
foo:1m_by_abc_sum_samples{abc="123"} 12.5
|
||||
foo:1m_by_abc_sum_samples{abc="456"} 8
|
||||
`, `
|
||||
- interval: 1m
|
||||
by: [abc, abc]
|
||||
outputs: [count_samples, sum_samples, count_series]
|
||||
`, "1111")
|
||||
|
||||
// Non-empty `without` list with non-existing labels
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `bar:1m_without_foo_count_samples 1
|
||||
bar:1m_without_foo_count_series 1
|
||||
bar:1m_without_foo_sum_samples 5
|
||||
foo:1m_without_foo_count_samples{abc="123"} 2
|
||||
foo:1m_without_foo_count_samples{abc="456",de="fg"} 1
|
||||
foo:1m_without_foo_count_series{abc="123"} 1
|
||||
foo:1m_without_foo_count_series{abc="456",de="fg"} 1
|
||||
foo:1m_without_foo_sum_samples{abc="123"} 12.5
|
||||
foo:1m_without_foo_sum_samples{abc="456",de="fg"} 8
|
||||
`, `
|
||||
- interval: 1m
|
||||
without: [foo]
|
||||
outputs: [count_samples, sum_samples, count_series]
|
||||
`, "1111")
|
||||
|
||||
// Non-empty `without` list with existing labels
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `bar:1m_without_abc_count_samples 1
|
||||
bar:1m_without_abc_count_series 1
|
||||
bar:1m_without_abc_sum_samples 5
|
||||
foo:1m_without_abc_count_samples 2
|
||||
foo:1m_without_abc_count_samples{de="fg"} 1
|
||||
foo:1m_without_abc_count_series 1
|
||||
foo:1m_without_abc_count_series{de="fg"} 1
|
||||
foo:1m_without_abc_sum_samples 12.5
|
||||
foo:1m_without_abc_sum_samples{de="fg"} 8
|
||||
`, `
|
||||
- interval: 1m
|
||||
without: [abc]
|
||||
outputs: [count_samples, sum_samples, count_series]
|
||||
`, "1111")
|
||||
|
||||
// Special case: __name__ in `without` list
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `:1m_count_samples 1
|
||||
:1m_count_samples{abc="123"} 2
|
||||
:1m_count_samples{abc="456",de="fg"} 1
|
||||
:1m_count_series 1
|
||||
:1m_count_series{abc="123"} 1
|
||||
:1m_count_series{abc="456",de="fg"} 1
|
||||
:1m_sum_samples 5
|
||||
:1m_sum_samples{abc="123"} 12.5
|
||||
:1m_sum_samples{abc="456",de="fg"} 8
|
||||
`, `
|
||||
- interval: 1m
|
||||
without: [__name__]
|
||||
outputs: [count_samples, sum_samples, count_series]
|
||||
`, "1111")
|
||||
|
||||
// drop some input metrics
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `bar:1m_without_abc_count_samples 1
|
||||
bar:1m_without_abc_count_series 1
|
||||
bar:1m_without_abc_sum_samples 5
|
||||
`, `
|
||||
- interval: 1m
|
||||
without: [abc]
|
||||
outputs: [count_samples, sum_samples, count_series]
|
||||
input_relabel_configs:
|
||||
- if: 'foo'
|
||||
action: drop
|
||||
`, "1111")
|
||||
|
||||
// rename output metrics
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `bar-1m-without-abc-count-samples 1
|
||||
bar-1m-without-abc-count-series 1
|
||||
bar-1m-without-abc-sum-samples 5
|
||||
foo-1m-without-abc-count-samples 2
|
||||
foo-1m-without-abc-count-series 1
|
||||
foo-1m-without-abc-sum-samples 12.5
|
||||
`, `
|
||||
- interval: 1m
|
||||
without: [abc]
|
||||
outputs: [count_samples, sum_samples, count_series]
|
||||
output_relabel_configs:
|
||||
- action: replace_all
|
||||
source_labels: [__name__]
|
||||
regex: ":|_"
|
||||
replacement: "-"
|
||||
target_label: __name__
|
||||
- action: drop
|
||||
source_labels: [de]
|
||||
regex: fg
|
||||
`, "1111")
|
||||
|
||||
// match doesn't match anything
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, ``, `
|
||||
- interval: 1m
|
||||
without: [abc]
|
||||
outputs: [count_samples, sum_samples, count_series]
|
||||
match: '{non_existing_label!=""}'
|
||||
name: foobar
|
||||
`, "0000")
|
||||
|
||||
// match matches foo series with non-empty abc label
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `foo:1m_by_abc_count_samples{abc="123"} 2
|
||||
foo:1m_by_abc_count_samples{abc="456"} 1
|
||||
foo:1m_by_abc_count_series{abc="123"} 1
|
||||
foo:1m_by_abc_count_series{abc="456"} 1
|
||||
foo:1m_by_abc_sum_samples{abc="123"} 12.5
|
||||
foo:1m_by_abc_sum_samples{abc="456"} 8
|
||||
`, `
|
||||
- interval: 1m
|
||||
by: [abc]
|
||||
outputs: [count_samples, sum_samples, count_series]
|
||||
name: abcdef
|
||||
match:
|
||||
- foo{abc=~".+"}
|
||||
- '{non_existing_label!=""}'
|
||||
`, "1011")
|
||||
|
||||
// total output for non-repeated series
|
||||
f([]string{`
|
||||
foo 123
|
||||
bar{baz="qwe"} 4.34
|
||||
`}, time.Minute, `bar:1m_total{baz="qwe"} 0
|
||||
foo:1m_total 0
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [total]
|
||||
`, "11")
|
||||
|
||||
// total output for non-repeated series, ignore first sample 0s
|
||||
f([]string{`
|
||||
foo 123
|
||||
bar{baz="qwe"} 4.34
|
||||
`}, time.Minute, `bar:1m_total{baz="qwe"} 4.34
|
||||
foo:1m_total 123
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [total]
|
||||
ignore_first_sample_interval: 0s
|
||||
`, "11")
|
||||
|
||||
// total_prometheus output for non-repeated series
|
||||
f([]string{`
|
||||
foo 123
|
||||
bar{baz="qwe"} 4.34
|
||||
`}, time.Minute, `bar:1m_total_prometheus{baz="qwe"} 0
|
||||
foo:1m_total_prometheus 0
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [total_prometheus]
|
||||
`, "11")
|
||||
|
||||
// total output for repeated series
|
||||
f([]string{`
|
||||
foo 123
|
||||
bar{baz="qwe"} 1.31
|
||||
bar{baz="qwe"} 4.34 1
|
||||
bar{baz="qwe"} 2
|
||||
foo{baz="qwe"} -5
|
||||
bar{baz="qwer"} 343
|
||||
bar{baz="qwer"} 344
|
||||
foo{baz="qwe"} 10
|
||||
`}, time.Minute, `bar:1m_total{baz="qwe"} 3.03
|
||||
bar:1m_total{baz="qwer"} 1
|
||||
foo:1m_total 0
|
||||
foo:1m_total{baz="qwe"} 15
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [total]
|
||||
`, "11111111")
|
||||
|
||||
// total_prometheus output for repeated series
|
||||
f([]string{`
|
||||
foo 123
|
||||
bar{baz="qwe"} 1.32
|
||||
bar{baz="qwe"} 4.34
|
||||
bar{baz="qwe"} 2
|
||||
foo{baz="qwe"} -5
|
||||
bar{baz="qwer"} 343
|
||||
bar{baz="qwer"} 344
|
||||
foo{baz="qwe"} 10
|
||||
`}, time.Minute, `bar:1m_total_prometheus{baz="qwe"} 5.02
|
||||
bar:1m_total_prometheus{baz="qwer"} 1
|
||||
foo:1m_total_prometheus 0
|
||||
foo:1m_total_prometheus{baz="qwe"} 15
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [total_prometheus]
|
||||
`, "11111111")
|
||||
|
||||
// total output for repeated series with group by __name__
|
||||
f([]string{`
|
||||
foo 123
|
||||
bar{baz="qwe"} 1.32
|
||||
bar{baz="qwe"} 4.34
|
||||
bar{baz="qwe"} 2
|
||||
foo{baz="qwe"} -5
|
||||
bar{baz="qwer"} 343
|
||||
bar{baz="qwer"} 344
|
||||
foo{baz="qwe"} 10
|
||||
`}, time.Minute, `bar:1m_total 6.02
|
||||
foo:1m_total 15
|
||||
`, `
|
||||
- interval: 1m
|
||||
by: [__name__]
|
||||
outputs: [total]
|
||||
`, "11111111")
|
||||
|
||||
// total_prometheus output for repeated series with group by __name__
|
||||
f([]string{`
|
||||
foo 123
|
||||
bar{baz="qwe"} 1.32
|
||||
bar{baz="qwe"} 4.34
|
||||
bar{baz="qwe"} 2
|
||||
foo{baz="qwe"} -5
|
||||
bar{baz="qwer"} 343
|
||||
bar{baz="qwer"} 344
|
||||
foo{baz="qwe"} 10
|
||||
`}, time.Minute, `bar:1m_total_prometheus 6.02
|
||||
foo:1m_total_prometheus 15
|
||||
`, `
|
||||
- interval: 1m
|
||||
by: [__name__]
|
||||
outputs: [total_prometheus]
|
||||
`, "11111111")
|
||||
|
||||
// increase output for non-repeated series
|
||||
f([]string{`
|
||||
foo 123
|
||||
bar{baz="qwe"} 4.34
|
||||
`}, time.Minute, `bar:1m_increase{baz="qwe"} 0
|
||||
foo:1m_increase 0
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [increase]
|
||||
`, "11")
|
||||
|
||||
// increase_prometheus output for non-repeated series
|
||||
f([]string{`
|
||||
foo 123
|
||||
bar{baz="qwe"} 4.34
|
||||
`}, time.Minute, `bar:1m_increase_prometheus{baz="qwe"} 0
|
||||
foo:1m_increase_prometheus 0
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [increase_prometheus]
|
||||
`, "11")
|
||||
|
||||
// increase output for repeated series
|
||||
f([]string{`
|
||||
foo 123
|
||||
bar{baz="qwe"} 1.32
|
||||
bar{baz="qwe"} 4.34
|
||||
bar{baz="qwe"} 2
|
||||
foo{baz="qwe"} -5
|
||||
bar{baz="qwer"} 343
|
||||
bar{baz="qwer"} 344
|
||||
foo{baz="qwe"} 10
|
||||
`}, time.Minute, `bar:1m_increase{baz="qwe"} 5.02
|
||||
bar:1m_increase{baz="qwer"} 1
|
||||
foo:1m_increase 0
|
||||
foo:1m_increase{baz="qwe"} 15
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [increase]
|
||||
`, "11111111")
|
||||
|
||||
// increase_prometheus output for repeated series
|
||||
f([]string{`
|
||||
foo 123
|
||||
bar{baz="qwe"} 1.32
|
||||
bar{baz="qwe"} 4.34
|
||||
bar{baz="qwe"} 2
|
||||
foo{baz="qwe"} -5
|
||||
bar{baz="qwer"} 343
|
||||
bar{baz="qwer"} 344
|
||||
foo{baz="qwe"} 10
|
||||
`}, time.Minute, `bar:1m_increase_prometheus{baz="qwe"} 5.02
|
||||
bar:1m_increase_prometheus{baz="qwer"} 1
|
||||
foo:1m_increase_prometheus 0
|
||||
foo:1m_increase_prometheus{baz="qwe"} 15
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [increase_prometheus]
|
||||
`, "11111111")
|
||||
|
||||
// multiple aggregate configs
|
||||
f([]string{`
|
||||
foo 1
|
||||
foo{bar="baz"} 2
|
||||
foo 3.3
|
||||
`, ``, ``, ``, ``}, time.Minute, `foo:1m_count_series 1
|
||||
foo:1m_count_series{bar="baz"} 1
|
||||
foo:1m_sum_samples 0
|
||||
foo:1m_sum_samples 0
|
||||
foo:1m_sum_samples 4.3
|
||||
foo:1m_sum_samples{bar="baz"} 0
|
||||
foo:1m_sum_samples{bar="baz"} 0
|
||||
foo:1m_sum_samples{bar="baz"} 2
|
||||
foo:5m_by_bar_sum_samples 4.3
|
||||
foo:5m_by_bar_sum_samples{bar="baz"} 2
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [count_series, sum_samples]
|
||||
- interval: 5m
|
||||
by: [bar]
|
||||
outputs: [sum_samples]
|
||||
`, "111")
|
||||
|
||||
// min and max outputs
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `bar:1m_max 5
|
||||
bar:1m_min 5
|
||||
foo:1m_max{abc="123"} 8.5
|
||||
foo:1m_max{abc="456",de="fg"} 8
|
||||
foo:1m_min{abc="123"} 4
|
||||
foo:1m_min{abc="456",de="fg"} 8
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [min, max]
|
||||
`, "1111")
|
||||
|
||||
// avg output
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `bar:1m_avg 5
|
||||
foo:1m_avg{abc="123"} 6.25
|
||||
foo:1m_avg{abc="456",de="fg"} 8
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [avg]
|
||||
`, "1111")
|
||||
|
||||
// stddev output
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `bar:1m_stddev 0
|
||||
foo:1m_stddev{abc="123"} 2.25
|
||||
foo:1m_stddev{abc="456",de="fg"} 0
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [stddev]
|
||||
`, "1111")
|
||||
|
||||
// stdvar output
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `bar:1m_stdvar 0
|
||||
foo:1m_stdvar{abc="123"} 5.0625
|
||||
foo:1m_stdvar{abc="456",de="fg"} 0
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [stdvar]
|
||||
`, "1111")
|
||||
|
||||
// histogram_bucket output
|
||||
f([]string{`
|
||||
cpu_usage{cpu="1"} 12.5
|
||||
cpu_usage{cpu="1"} 13.3
|
||||
cpu_usage{cpu="1"} 13
|
||||
cpu_usage{cpu="1"} 12
|
||||
cpu_usage{cpu="1"} 14
|
||||
cpu_usage{cpu="1"} 25
|
||||
cpu_usage{cpu="2"} 90
|
||||
`}, time.Minute, `cpu_usage:1m_histogram_bucket{cpu="1",vmrange="1.136e+01...1.292e+01"} 2
|
||||
cpu_usage:1m_histogram_bucket{cpu="1",vmrange="1.292e+01...1.468e+01"} 3
|
||||
cpu_usage:1m_histogram_bucket{cpu="1",vmrange="2.448e+01...2.783e+01"} 1
|
||||
cpu_usage:1m_histogram_bucket{cpu="2",vmrange="8.799e+01...1.000e+02"} 1
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [histogram_bucket]
|
||||
`, "1111111")
|
||||
|
||||
// histogram_bucket output without cpu
|
||||
f([]string{`
|
||||
cpu_usage{cpu="1"} 12.5
|
||||
cpu_usage{cpu="1"} 13.3
|
||||
cpu_usage{cpu="1"} 13
|
||||
cpu_usage{cpu="1"} 12
|
||||
cpu_usage{cpu="1"} 14
|
||||
cpu_usage{cpu="1"} 25
|
||||
cpu_usage{cpu="2"} 90
|
||||
`}, time.Minute, `cpu_usage:1m_without_cpu_histogram_bucket{vmrange="1.136e+01...1.292e+01"} 2
|
||||
cpu_usage:1m_without_cpu_histogram_bucket{vmrange="1.292e+01...1.468e+01"} 3
|
||||
cpu_usage:1m_without_cpu_histogram_bucket{vmrange="2.448e+01...2.783e+01"} 1
|
||||
cpu_usage:1m_without_cpu_histogram_bucket{vmrange="8.799e+01...1.000e+02"} 1
|
||||
`, `
|
||||
- interval: 1m
|
||||
without: [cpu]
|
||||
outputs: [histogram_bucket]
|
||||
`, "1111111")
|
||||
|
||||
// quantiles output
|
||||
f([]string{`
|
||||
cpu_usage{cpu="1"} 12.5
|
||||
cpu_usage{cpu="1"} 13.3
|
||||
cpu_usage{cpu="1"} 13
|
||||
cpu_usage{cpu="1"} 12
|
||||
cpu_usage{cpu="1"} 14
|
||||
cpu_usage{cpu="1"} 25
|
||||
cpu_usage{cpu="2"} 90
|
||||
`}, time.Minute, `cpu_usage:1m_quantiles{cpu="1",quantile="0"} 12
|
||||
cpu_usage:1m_quantiles{cpu="1",quantile="0.5"} 13.3
|
||||
cpu_usage:1m_quantiles{cpu="1",quantile="1"} 25
|
||||
cpu_usage:1m_quantiles{cpu="2",quantile="0"} 90
|
||||
cpu_usage:1m_quantiles{cpu="2",quantile="0.5"} 90
|
||||
cpu_usage:1m_quantiles{cpu="2",quantile="1"} 90
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: ["quantiles(0, 0.5, 1)"]
|
||||
`, "1111111")
|
||||
|
||||
// quantiles output without cpu
|
||||
f([]string{`
|
||||
cpu_usage{cpu="1"} 12.5
|
||||
cpu_usage{cpu="1"} 13.3
|
||||
cpu_usage{cpu="1"} 13
|
||||
cpu_usage{cpu="1"} 12
|
||||
cpu_usage{cpu="1"} 14
|
||||
cpu_usage{cpu="1"} 25
|
||||
cpu_usage{cpu="2"} 90
|
||||
`}, time.Minute, `cpu_usage:1m_without_cpu_quantiles{quantile="0"} 12
|
||||
cpu_usage:1m_without_cpu_quantiles{quantile="0.5"} 13.3
|
||||
cpu_usage:1m_without_cpu_quantiles{quantile="1"} 90
|
||||
`, `
|
||||
- interval: 1m
|
||||
without: [cpu]
|
||||
outputs: ["quantiles(0, 0.5, 1)"]
|
||||
`, "1111111")
|
||||
|
||||
// append additional label
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5 10
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `bar-1m-without-abc-count-samples{new_label="must_keep_metric_name"} 1
|
||||
bar-1m-without-abc-count-series{new_label="must_keep_metric_name"} 1
|
||||
bar-1m-without-abc-sum-samples{new_label="must_keep_metric_name"} 5
|
||||
foo-1m-without-abc-count-samples{new_label="must_keep_metric_name"} 2
|
||||
foo-1m-without-abc-count-series{new_label="must_keep_metric_name"} 1
|
||||
foo-1m-without-abc-sum-samples{new_label="must_keep_metric_name"} 12.5
|
||||
`, `
|
||||
- interval: 1m
|
||||
without: [abc]
|
||||
outputs: [count_samples, sum_samples, count_series]
|
||||
output_relabel_configs:
|
||||
- action: replace_all
|
||||
source_labels: [__name__]
|
||||
regex: ":|_"
|
||||
replacement: "-"
|
||||
target_label: __name__
|
||||
- action: drop
|
||||
source_labels: [de]
|
||||
regex: fg
|
||||
- target_label: new_label
|
||||
replacement: must_keep_metric_name
|
||||
`, "1111")
|
||||
|
||||
// test rate_sum and rate_avg
|
||||
f([]string{`
|
||||
foo{abc="123", cde="1"} 3
|
||||
foo{abc="456", cde="1"} 8.5
|
||||
foo 12 34
|
||||
`, `
|
||||
foo{abc="123", cde="1"} 8
|
||||
foo{abc="456", cde="1"} 11
|
||||
`}, time.Minute, `foo:1m_by_cde_rate_avg{cde="1"} 0.0625
|
||||
foo:1m_by_cde_rate_sum{cde="1"} 0.125
|
||||
`, `
|
||||
- interval: 1m
|
||||
by: [cde]
|
||||
outputs: [rate_sum, rate_avg]
|
||||
`, "11111")
|
||||
|
||||
// test rate_sum and rate_avg, when two aggregation intervals are empty
|
||||
f([]string{`
|
||||
foo{abc="123", cde="1"} 2
|
||||
foo{abc="456", cde="1"} 8
|
||||
foo{abc="777", cde="1"} 9 -10
|
||||
`, ``, ``, `
|
||||
foo{abc="123", cde="1"} 20
|
||||
foo{abc="456", cde="1"} 26
|
||||
foo{abc="777", cde="1"} 27 -10
|
||||
`}, time.Minute, `foo:1m_by_cde_rate_avg{cde="1"} 0.1
|
||||
foo:1m_by_cde_rate_sum{cde="1"} 0.2
|
||||
`, `
|
||||
- interval: 1m
|
||||
by: [cde]
|
||||
outputs: [rate_sum, rate_avg]
|
||||
enable_windows: true
|
||||
`, "111111")
|
||||
|
||||
// rate_sum and rate_avg with duplicated events
|
||||
f([]string{`
|
||||
foo{abc="123", cde="1"} 4 10
|
||||
foo{abc="123", cde="1"} 4 10
|
||||
`}, time.Minute, ``, `
|
||||
- interval: 1m
|
||||
outputs: [rate_sum, rate_avg]
|
||||
`, "11")
|
||||
|
||||
// rate_sum and rate_avg for a single sample
|
||||
f([]string{`
|
||||
foo 4 10
|
||||
bar 5 10
|
||||
`}, time.Minute, ``, `
|
||||
- interval: 1m
|
||||
outputs: [rate_sum, rate_avg]
|
||||
`, "11")
|
||||
|
||||
// unique_samples output
|
||||
f([]string{`
|
||||
foo 1 10
|
||||
foo 2 20
|
||||
foo 1 10
|
||||
foo 2 20
|
||||
foo 3 20
|
||||
`}, time.Minute, `foo:1m_unique_samples 3
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [unique_samples]
|
||||
`, "11111")
|
||||
|
||||
// keep_metric_names
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
bar -34.3
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `bar 2
|
||||
foo{abc="123"} 2
|
||||
foo{abc="456",de="fg"} 1
|
||||
`, `
|
||||
- interval: 1m
|
||||
keep_metric_names: true
|
||||
outputs: [count_samples]
|
||||
`, "11111")
|
||||
|
||||
// drop_input_labels
|
||||
f([]string{`
|
||||
foo{abc="123"} 4
|
||||
bar 5
|
||||
foo{abc="123"} 8.5
|
||||
bar -34.3
|
||||
foo{abc="456",de="fg"} 8
|
||||
`}, time.Minute, `bar 2
|
||||
foo 2
|
||||
foo{de="fg"} 1
|
||||
`, `
|
||||
- interval: 1m
|
||||
drop_input_labels: [abc]
|
||||
keep_metric_names: true
|
||||
outputs: [count_samples]
|
||||
`, "11111")
|
||||
|
||||
f([]string{`
|
||||
foo 123
|
||||
bar 567
|
||||
`, ``, ``}, 30*time.Second, `bar:1m_sum_samples 567
|
||||
foo:1m_sum_samples 123
|
||||
`, `
|
||||
- interval: 1m
|
||||
outputs: [sum_samples]
|
||||
dedup_interval: 30s
|
||||
`, "11")
|
||||
|
||||
f([]string{`
|
||||
foo 123
|
||||
bar{baz="qwe"} 1.32
|
||||
bar{baz="qwe"} 4.34
|
||||
bar{baz="qwe"} 2
|
||||
foo{baz="qwe"} -5
|
||||
bar{baz="qwer"} 343
|
||||
bar{baz="qwer"} 344
|
||||
foo{baz="qwe"} 10
|
||||
`, ``, ``}, 30*time.Second, `bar:1m_sum_samples{baz="qwe"} 4.34
|
||||
bar:1m_sum_samples{baz="qwer"} 344
|
||||
foo:1m_sum_samples 123
|
||||
foo:1m_sum_samples{baz="qwe"} 10
|
||||
`, `
|
||||
- interval: 1m
|
||||
dedup_interval: 30s
|
||||
outputs: [sum_samples]
|
||||
`, "11111111")
|
||||
}
|
||||
|
||||
func timeSeriessToString(tss []prompbmarshal.TimeSeries) string {
|
||||
a := make([]string, len(tss))
|
||||
for i, ts := range tss {
|
||||
|
||||
Reference in New Issue
Block a user