Compare commits
37 Commits
fix/jwt-ui
...
roaring-bi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f1c619abb | ||
|
|
217d116c2c | ||
|
|
449d4ff1a1 | ||
|
|
6128134e84 | ||
|
|
d467faf739 | ||
|
|
673b2ca7db | ||
|
|
40ccf0c333 | ||
|
|
fe341a4204 | ||
|
|
83ebf00659 | ||
|
|
5e602726f5 | ||
|
|
a6200cc83d | ||
|
|
a5811d3c3b | ||
|
|
5962b47c31 | ||
|
|
9a4edc738a | ||
|
|
30d01e9cae | ||
|
|
6b46f3920c | ||
|
|
97b11146ee | ||
|
|
2ef74bd6ea | ||
|
|
845161e377 | ||
|
|
f176a6624a | ||
|
|
4d06e34b66 | ||
|
|
6d8ddcb9ed | ||
|
|
dd4167709a | ||
|
|
71e253e1f0 | ||
|
|
9e155ffd9e | ||
|
|
2e9e40dc75 | ||
|
|
10d4294f9b | ||
|
|
5e77771668 | ||
|
|
dda5545078 | ||
|
|
087efbc451 | ||
|
|
68e64536b1 | ||
|
|
6e3ce4d55c | ||
|
|
8d1b88f985 | ||
|
|
3d3c057d52 | ||
|
|
94622fef29 | ||
|
|
804d77ffc5 | ||
|
|
79b18e9742 |
4
.github/workflows/test.yml
vendored
@@ -86,7 +86,7 @@ jobs:
|
||||
- run: go version
|
||||
|
||||
- name: Run tests
|
||||
run: GOGC=10 make ${{ matrix.scenario}}
|
||||
run: make ${{ matrix.scenario}}
|
||||
|
||||
- name: Publish coverage
|
||||
uses: codecov/codecov-action@v5
|
||||
@@ -95,7 +95,7 @@ jobs:
|
||||
|
||||
apptest:
|
||||
name: apptest
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: apptest
|
||||
|
||||
steps:
|
||||
- name: Code checkout
|
||||
|
||||
@@ -31,8 +31,8 @@ var (
|
||||
"0 means no limit.")
|
||||
ruleUpdateEntriesLimit = flag.Int("rule.updateEntriesLimit", 20, "Defines the max number of rule's state updates stored in-memory. "+
|
||||
"Rule's updates are available on rule's Details page and are used for debugging purposes. The number of stored updates can be overridden per rule via update_entries_limit param.")
|
||||
resendDelay = flag.Duration("rule.resendDelay", 0, "MiniMum amount of time to wait before resending an alert to notifier.")
|
||||
maxResolveDuration = flag.Duration("rule.maxResolveDuration", 0, "Limits the maxiMum duration for automatic alert expiration, "+
|
||||
resendDelay = flag.Duration("rule.resendDelay", 0, "Minimum amount of time to wait before resending an alert to notifier.")
|
||||
maxResolveDuration = flag.Duration("rule.maxResolveDuration", 0, "Limits the maximum duration for automatic alert expiration, "+
|
||||
"which by default is 4 times evaluationInterval of the parent group")
|
||||
evalDelay = flag.Duration("rule.evalDelay", 30*time.Second, "Adjustment of the 'time' parameter for rule evaluation requests to compensate intentional data delay from the datasource. "+
|
||||
"Normally, should be equal to '-search.latencyOffset' (cmd-line flag configured for VictoriaMetrics single-node or vmselect). "+
|
||||
|
||||
@@ -104,10 +104,9 @@ type UserInfo struct {
|
||||
|
||||
// HeadersConf represents config for request and response headers.
|
||||
type HeadersConf struct {
|
||||
RequestHeaders []*Header `yaml:"headers,omitempty"`
|
||||
ResponseHeaders []*Header `yaml:"response_headers,omitempty"`
|
||||
KeepOriginalHost *bool `yaml:"keep_original_host,omitempty"`
|
||||
hasAnyPlaceHolders bool
|
||||
RequestHeaders []*Header `yaml:"headers,omitempty"`
|
||||
ResponseHeaders []*Header `yaml:"response_headers,omitempty"`
|
||||
KeepOriginalHost *bool `yaml:"keep_original_host,omitempty"`
|
||||
}
|
||||
|
||||
func (ui *UserInfo) beginConcurrencyLimit(ctx context.Context) error {
|
||||
@@ -350,7 +349,6 @@ func (bus *backendURLs) add(u *url.URL) {
|
||||
url: u,
|
||||
healthCheckContext: bus.healthChecksContext,
|
||||
healthCheckWG: &bus.healthChecksWG,
|
||||
hasPlaceHolders: hasAnyPlaceholders(u),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -368,8 +366,6 @@ type backendURL struct {
|
||||
concurrentRequests atomic.Int32
|
||||
|
||||
url *url.URL
|
||||
|
||||
hasPlaceHolders bool
|
||||
}
|
||||
|
||||
func (bu *backendURL) isBroken() bool {
|
||||
@@ -907,9 +903,6 @@ func parseAuthConfig(data []byte) (*AuthConfig, error) {
|
||||
if ui.Name != "" {
|
||||
return nil, fmt.Errorf("field name can't be specified for unauthorized_user section")
|
||||
}
|
||||
if err := parseJWTPlaceholdersForUserInfo(ui, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := ui.initURLs(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -967,10 +960,6 @@ func parseAuthConfigUsers(ac *AuthConfig) (map[string]*UserInfo, error) {
|
||||
at, ui.Username, ui.Name, uiOld.Username, uiOld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if err := parseJWTPlaceholdersForUserInfo(ui, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := ui.initURLs(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1070,7 +1059,6 @@ func (ui *UserInfo) initURLs() error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, e := range ui.URLMaps {
|
||||
if len(e.SrcPaths) == 0 && len(e.SrcHosts) == 0 && len(e.SrcQueryArgs) == 0 && len(e.SrcHeaders) == 0 {
|
||||
return fmt.Errorf("missing `src_paths`, `src_hosts`, `src_query_args` and `src_headers` in `url_map`")
|
||||
|
||||
@@ -276,50 +276,6 @@ users:
|
||||
url_prefix: http://foo.bar
|
||||
metric_labels:
|
||||
not-prometheus-compatible: value
|
||||
`)
|
||||
// placeholder in url_prefix
|
||||
f(`
|
||||
users:
|
||||
- username: foo
|
||||
password: bar
|
||||
url_prefix: 'http://ahost/{{a_placeholder}}/foobar'
|
||||
`)
|
||||
// placeholder in a header
|
||||
f(`
|
||||
users:
|
||||
- username: foo
|
||||
password: bar
|
||||
headers:
|
||||
- 'X-Foo: {{a_placeholder}}'
|
||||
url_prefix: 'http://ahost'
|
||||
`)
|
||||
// placeholder in url_prefix
|
||||
f(`
|
||||
users:
|
||||
- username: foo
|
||||
password: bar
|
||||
url_prefix: 'http://ahost/{{a_placeholder}}/foobar'
|
||||
`)
|
||||
// placeholder in a header in url_map
|
||||
f(`
|
||||
users:
|
||||
- username: foo
|
||||
password: bar
|
||||
url_map:
|
||||
- src_paths: ["/select/.*"]
|
||||
headers:
|
||||
- 'X-Foo: {{a_placeholder}}'
|
||||
url_prefix: 'http://ahost'
|
||||
`)
|
||||
|
||||
// placeholder in a header in url_map
|
||||
f(`
|
||||
users:
|
||||
- username: foo
|
||||
password: bar
|
||||
url_map:
|
||||
- src_paths: ["/select/.*"]
|
||||
url_prefix: 'http://ahost/{{a_placeholder}}/foobar'
|
||||
`)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -12,35 +10,6 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
metricsTenantPlaceholder = `{{.MetricsTenant}}`
|
||||
metricsExtraLabelsPlaceholder = `{{.MetricsExtraLabels}}`
|
||||
metricsExtraFiltersPlaceholder = `{{.MetricsExtraFilters}}`
|
||||
|
||||
logsAccountIDPlaceholder = `{{.LogsAccountID}}`
|
||||
logsProjectIDPlaceholder = `{{.LogsProjectID}}`
|
||||
logsExtraFiltersPlaceholder = `{{.LogsExtraFilters}}`
|
||||
logsExtraStreamFiltersPlaceholder = `{{.LogsExtraStreamFilters}}`
|
||||
|
||||
placeholderPrefix = `{{`
|
||||
)
|
||||
|
||||
var allPlaceholders = []string{
|
||||
metricsTenantPlaceholder,
|
||||
metricsExtraLabelsPlaceholder,
|
||||
metricsExtraFiltersPlaceholder,
|
||||
logsAccountIDPlaceholder,
|
||||
logsProjectIDPlaceholder,
|
||||
logsExtraFiltersPlaceholder,
|
||||
logsExtraStreamFiltersPlaceholder,
|
||||
}
|
||||
|
||||
var urlPathPlaceHolders = []string{
|
||||
metricsTenantPlaceholder,
|
||||
logsAccountIDPlaceholder,
|
||||
logsProjectIDPlaceholder,
|
||||
}
|
||||
|
||||
type jwtCache struct {
|
||||
// users contain UserInfo`s from AuthConfig with JWTConfig set
|
||||
users []*UserInfo
|
||||
@@ -99,9 +68,6 @@ func parseJWTUsers(ac *AuthConfig) ([]*UserInfo, error) {
|
||||
|
||||
jwtToken.verifierPool = vp
|
||||
}
|
||||
if err := parseJWTPlaceholdersForUserInfo(&ui, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := ui.initURLs(); err != nil {
|
||||
return nil, err
|
||||
@@ -135,7 +101,7 @@ func parseJWTUsers(ac *AuthConfig) ([]*UserInfo, error) {
|
||||
jui = append(jui, &ui)
|
||||
}
|
||||
|
||||
// TODO: the limitation will be lifted once claim based matching will be implemented
|
||||
// the limitation will be lifted once claim based matching will be implemented
|
||||
if len(jui) > 1 {
|
||||
return nil, fmt.Errorf("multiple users with JWT tokens are not supported; found %d users", len(jui))
|
||||
}
|
||||
@@ -143,10 +109,10 @@ func parseJWTUsers(ac *AuthConfig) ([]*UserInfo, error) {
|
||||
return jui, nil
|
||||
}
|
||||
|
||||
func getUserInfoByJWTToken(ats []string) (*UserInfo, *jwt.Token) {
|
||||
func getUserInfoByJWTToken(ats []string) *UserInfo {
|
||||
js := *jwtAuthCache.Load()
|
||||
if len(js.users) == 0 {
|
||||
return nil, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, at := range ats {
|
||||
@@ -165,8 +131,6 @@ func getUserInfoByJWTToken(ats []string) (*UserInfo, *jwt.Token) {
|
||||
}
|
||||
if tkn.IsExpired(time.Now()) {
|
||||
if *logInvalidAuthTokens {
|
||||
// TODO: add more context:
|
||||
// token claims with issuer
|
||||
logger.Infof("jwt token is expired")
|
||||
}
|
||||
continue
|
||||
@@ -174,7 +138,7 @@ func getUserInfoByJWTToken(ats []string) (*UserInfo, *jwt.Token) {
|
||||
|
||||
for _, ui := range js.users {
|
||||
if ui.JWT.SkipVerify {
|
||||
return ui, tkn
|
||||
return ui
|
||||
}
|
||||
|
||||
if err := ui.JWT.verifierPool.Verify(tkn); err != nil {
|
||||
@@ -184,190 +148,9 @@ func getUserInfoByJWTToken(ats []string) (*UserInfo, *jwt.Token) {
|
||||
continue
|
||||
}
|
||||
|
||||
return ui, tkn
|
||||
return ui
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func replaceJWTPlaceholders(bu *backendURL, hc HeadersConf, vma *jwt.VMAccessClaim) (*url.URL, HeadersConf) {
|
||||
if !bu.hasPlaceHolders && !hc.hasAnyPlaceHolders {
|
||||
return bu.url, hc
|
||||
}
|
||||
targetURL := bu.url
|
||||
data := jwtClaimsData(vma)
|
||||
if bu.hasPlaceHolders {
|
||||
// template url params and request path
|
||||
// make a copy of url
|
||||
uCopy := *bu.url
|
||||
for _, uph := range urlPathPlaceHolders {
|
||||
replacement := data[uph]
|
||||
uCopy.Path = strings.ReplaceAll(uCopy.Path, uph, replacement[0])
|
||||
}
|
||||
query := uCopy.Query()
|
||||
var foundAnyQueryPlaceholder bool
|
||||
var templatedValues []string
|
||||
for param, values := range query {
|
||||
templatedValues = templatedValues[:0]
|
||||
// filter in-place values with placeholders
|
||||
// and accumulate replacements
|
||||
// it will change the order of param values
|
||||
// but it's not guaranteed
|
||||
// and will be changed in any way with multiple arg templates
|
||||
var cnt int
|
||||
for _, value := range values {
|
||||
if dv, ok := data[value]; ok {
|
||||
foundAnyQueryPlaceholder = true
|
||||
templatedValues = append(templatedValues, dv...)
|
||||
continue
|
||||
}
|
||||
values[cnt] = value
|
||||
cnt++
|
||||
}
|
||||
values = values[:cnt]
|
||||
values = append(values, templatedValues...)
|
||||
query[param] = values
|
||||
}
|
||||
if foundAnyQueryPlaceholder {
|
||||
uCopy.RawQuery = query.Encode()
|
||||
}
|
||||
targetURL = &uCopy
|
||||
}
|
||||
if hc.hasAnyPlaceHolders {
|
||||
// make a copy of headers and update only values with placeholder
|
||||
rhs := make([]*Header, 0, len(hc.RequestHeaders))
|
||||
for _, rh := range hc.RequestHeaders {
|
||||
if dv, ok := data[rh.Value]; ok {
|
||||
rh := &Header{
|
||||
Name: rh.Name,
|
||||
Value: strings.Join(dv, ","),
|
||||
}
|
||||
rhs = append(rhs, rh)
|
||||
continue
|
||||
}
|
||||
rhs = append(rhs, rh)
|
||||
}
|
||||
hc.RequestHeaders = rhs
|
||||
}
|
||||
|
||||
return targetURL, hc
|
||||
}
|
||||
|
||||
func jwtClaimsData(vma *jwt.VMAccessClaim) map[string][]string {
|
||||
data := map[string][]string{
|
||||
// TODO: optimize at parsing stage
|
||||
metricsTenantPlaceholder: {fmt.Sprintf("%d:%d", vma.MetricsAccountID, vma.MetricsProjectID)},
|
||||
metricsExtraLabelsPlaceholder: vma.MetricsExtraLabels,
|
||||
metricsExtraFiltersPlaceholder: vma.MetricsExtraFilters,
|
||||
|
||||
// TODO: optimize at parsing stage
|
||||
logsAccountIDPlaceholder: {fmt.Sprintf("%d", vma.LogsAccountID)},
|
||||
logsProjectIDPlaceholder: {fmt.Sprintf("%d", vma.LogsProjectID)},
|
||||
logsExtraFiltersPlaceholder: vma.LogsExtraFilters,
|
||||
logsExtraStreamFiltersPlaceholder: vma.LogsExtraStreamFilters,
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func parseJWTPlaceholdersForUserInfo(ui *UserInfo, isAllowed bool) error {
|
||||
if ui.URLPrefix != nil {
|
||||
if err := validateJWTPlaceholdersForURL(ui.URLPrefix, isAllowed); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := parsePlaceholdersForHC(&ui.HeadersConf, isAllowed); err != nil {
|
||||
return err
|
||||
}
|
||||
if ui.DefaultURL != nil {
|
||||
if err := validateJWTPlaceholdersForURL(ui.DefaultURL, isAllowed); err != nil {
|
||||
return fmt.Errorf("invalid `default_url` placeholders: %w", err)
|
||||
}
|
||||
}
|
||||
for i := range ui.URLMaps {
|
||||
e := &ui.URLMaps[i]
|
||||
if e.URLPrefix != nil {
|
||||
if err := validateJWTPlaceholdersForURL(e.URLPrefix, isAllowed); err != nil {
|
||||
return fmt.Errorf("invalid `url_map` `url_prefix` placeholders: %w", err)
|
||||
}
|
||||
}
|
||||
if err := parsePlaceholdersForHC(&e.HeadersConf, isAllowed); err != nil {
|
||||
return fmt.Errorf("invalid `url_map` headers placeholders: %w", err)
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateJWTPlaceholdersForURL(up *URLPrefix, isAllowed bool) error {
|
||||
for _, bu := range up.busOriginal {
|
||||
ok := strings.Contains(bu.Path, placeholderPrefix)
|
||||
if ok && !isAllowed {
|
||||
return fmt.Errorf("placeholder: %q is only allowed at JWT token context", bu.Path)
|
||||
}
|
||||
if ok {
|
||||
p := bu.Path
|
||||
for _, ph := range allPlaceholders {
|
||||
p = strings.ReplaceAll(p, ph, ``)
|
||||
}
|
||||
if strings.Contains(p, placeholderPrefix) {
|
||||
return fmt.Errorf("invalid placeholder found in URL request path: %q, supported values are: %s", bu.Path, strings.Join(allPlaceholders, ", "))
|
||||
|
||||
}
|
||||
}
|
||||
for param, values := range bu.Query() {
|
||||
for _, value := range values {
|
||||
ok := strings.Contains(value, placeholderPrefix)
|
||||
if ok && !isAllowed {
|
||||
return fmt.Errorf("query param: %q with placeholder: %q is only allowed at JWT token context", param, value)
|
||||
}
|
||||
if ok {
|
||||
// possible placeholder
|
||||
if !slices.Contains(allPlaceholders, value) {
|
||||
return fmt.Errorf("query param: %q has unsupported placeholder string: %q, supported values are: %s", param, value, strings.Join(allPlaceholders, ", "))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parsePlaceholdersForHC(hc *HeadersConf, isAllowed bool) error {
|
||||
for _, rhs := range hc.RequestHeaders {
|
||||
ok := strings.Contains(rhs.Value, placeholderPrefix)
|
||||
if ok && !isAllowed {
|
||||
return fmt.Errorf("request header: %q placeholder: %q is only supported at JWT context", rhs.Name, rhs.Value)
|
||||
}
|
||||
if ok {
|
||||
if !slices.Contains(allPlaceholders, rhs.Value) {
|
||||
return fmt.Errorf("request header: %q has unsupported placeholder: %q, supported values are: %s", rhs.Name, rhs.Value, strings.Join(allPlaceholders, ", "))
|
||||
}
|
||||
hc.hasAnyPlaceHolders = true
|
||||
}
|
||||
}
|
||||
for _, rhs := range hc.ResponseHeaders {
|
||||
if strings.Contains(rhs.Value, placeholderPrefix) {
|
||||
return fmt.Errorf("response header placeholders are not supported; found placeholder prefix at header: %q with value: %q", rhs.Name, rhs.Value)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func hasAnyPlaceholders(u *url.URL) bool {
|
||||
if strings.Contains(u.Path, placeholderPrefix) {
|
||||
return true
|
||||
}
|
||||
if len(u.Query()) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, values := range u.Query() {
|
||||
for _, value := range values {
|
||||
if strings.HasPrefix(value, placeholderPrefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -32,14 +32,14 @@ XOtclIk1uhc03oL9nOQ=
|
||||
ac, err := parseAuthConfig([]byte(s))
|
||||
if err != nil {
|
||||
if expErr != err.Error() {
|
||||
t.Fatalf("unexpected error; got\n%q\nwant\n%q", err.Error(), expErr)
|
||||
t.Fatalf("unexpected error; got %q; want %q", err.Error(), expErr)
|
||||
}
|
||||
return
|
||||
}
|
||||
users, err := parseJWTUsers(ac)
|
||||
if err != nil {
|
||||
if expErr != err.Error() {
|
||||
t.Fatalf("unexpected error; got\n%q\nwant \n%q", err.Error(), expErr)
|
||||
t.Fatalf("unexpected error; got %q; want %q", err.Error(), expErr)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -164,38 +164,6 @@ users:
|
||||
- `+publicKeyFile+`
|
||||
url_prefix: http://foo.bar
|
||||
`, "cannot parse public key from file \""+publicKeyFile+"\": failed to parse key \"invalidPEM\": failed to decode PEM block containing public key")
|
||||
|
||||
// unsupported placeholder in a header
|
||||
f(`
|
||||
users:
|
||||
- jwt:
|
||||
skip_verify: true
|
||||
url_prefix: http://foo.bar/{{.UnsupportedPlaceholder}}/foo`,
|
||||
"invalid placeholder found in URL request path: \"/{{.UnsupportedPlaceholder}}/foo\", supported values are: {{.MetricsTenant}}, {{.MetricsExtraLabels}}, {{.MetricsExtraFilters}}, {{.LogsAccountID}}, {{.LogsProjectID}}, {{.LogsExtraFilters}}, {{.LogsExtraStreamFilters}}",
|
||||
)
|
||||
// unsupported placeholder in a header
|
||||
f(`
|
||||
users:
|
||||
- jwt:
|
||||
skip_verify: true
|
||||
headers:
|
||||
- "AccountID: {{.UnsupportedPlaceholder}}"
|
||||
url_prefix: http://foo.bar
|
||||
`,
|
||||
"request header: \"AccountID\" has unsupported placeholder: \"{{.UnsupportedPlaceholder}}\", supported values are: {{.MetricsTenant}}, {{.MetricsExtraLabels}}, {{.MetricsExtraFilters}}, {{.LogsAccountID}}, {{.LogsProjectID}}, {{.LogsExtraFilters}}, {{.LogsExtraStreamFilters}}",
|
||||
)
|
||||
|
||||
// spaces in templating not allowed
|
||||
f(`
|
||||
users:
|
||||
- jwt:
|
||||
skip_verify: true
|
||||
headers:
|
||||
- "AccountID: {{ .LogsAccountID }}"
|
||||
url_prefix: http://foo.bar
|
||||
`,
|
||||
"request header: \"AccountID\" has unsupported placeholder: \"{{ .LogsAccountID }}\", supported values are: {{.MetricsTenant}}, {{.MetricsExtraLabels}}, {{.MetricsExtraFilters}}, {{.LogsAccountID}}, {{.LogsProjectID}}, {{.LogsExtraFilters}}, {{.LogsExtraStreamFilters}}",
|
||||
)
|
||||
}
|
||||
|
||||
func TestJWTParseAuthConfigSuccess(t *testing.T) {
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/jwt"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/buildinfo"
|
||||
@@ -174,7 +173,7 @@ func requestHandler(w http.ResponseWriter, r *http.Request) bool {
|
||||
// Process requests for unauthorized users
|
||||
ui := authConfig.Load().UnauthorizedUser
|
||||
if ui != nil {
|
||||
processUserRequest(w, r, ui, nil)
|
||||
processUserRequest(w, r, ui)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -183,21 +182,17 @@ func requestHandler(w http.ResponseWriter, r *http.Request) bool {
|
||||
}
|
||||
|
||||
if ui := getUserInfoByAuthTokens(ats); ui != nil {
|
||||
processUserRequest(w, r, ui, nil)
|
||||
processUserRequest(w, r, ui)
|
||||
return true
|
||||
}
|
||||
if ui, tkn := getUserInfoByJWTToken(ats); ui != nil {
|
||||
if tkn == nil {
|
||||
logger.Panicf("BUG: unexpected nil jwt token for user %q", ui.name())
|
||||
}
|
||||
|
||||
processUserRequest(w, r, ui, tkn)
|
||||
if ui := getUserInfoByJWTToken(ats); ui != nil {
|
||||
processUserRequest(w, r, ui)
|
||||
return true
|
||||
}
|
||||
|
||||
uu := authConfig.Load().UnauthorizedUser
|
||||
if uu != nil {
|
||||
processUserRequest(w, r, uu, nil)
|
||||
processUserRequest(w, r, uu)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -226,7 +221,7 @@ func getUserInfoByAuthTokens(ats []string) *UserInfo {
|
||||
return nil
|
||||
}
|
||||
|
||||
func processUserRequest(w http.ResponseWriter, r *http.Request, ui *UserInfo, tkn *jwt.Token) {
|
||||
func processUserRequest(w http.ResponseWriter, r *http.Request, ui *UserInfo) {
|
||||
startTime := time.Now()
|
||||
defer ui.requestsDuration.UpdateDuration(startTime)
|
||||
|
||||
@@ -277,7 +272,7 @@ func processUserRequest(w http.ResponseWriter, r *http.Request, ui *UserInfo, tk
|
||||
defer ui.endConcurrencyLimit()
|
||||
|
||||
// Process the request.
|
||||
processRequest(w, r, ui, tkn)
|
||||
processRequest(w, r, ui)
|
||||
}
|
||||
|
||||
func beginConcurrencyLimit(ctx context.Context) error {
|
||||
@@ -350,7 +345,7 @@ func bufferRequestBody(ctx context.Context, r io.ReadCloser, userName string) (i
|
||||
return bb, nil
|
||||
}
|
||||
|
||||
func processRequest(w http.ResponseWriter, r *http.Request, ui *UserInfo, tkn *jwt.Token) {
|
||||
func processRequest(w http.ResponseWriter, r *http.Request, ui *UserInfo) {
|
||||
u := normalizeURL(r.URL)
|
||||
up, hc := ui.getURLPrefixAndHeaders(u, r.Host, r.Header)
|
||||
isDefault := false
|
||||
@@ -382,10 +377,6 @@ func processRequest(w http.ResponseWriter, r *http.Request, ui *UserInfo, tkn *j
|
||||
break
|
||||
}
|
||||
targetURL := bu.url
|
||||
if tkn != nil {
|
||||
// for security reasons allow templating only for configured url values and headers
|
||||
targetURL, hc = replaceJWTPlaceholders(bu, hc, tkn.VMAccess())
|
||||
}
|
||||
if isDefault {
|
||||
// Don't change path and add request_path query param for default route.
|
||||
query := targetURL.Query()
|
||||
@@ -395,6 +386,7 @@ func processRequest(w http.ResponseWriter, r *http.Request, ui *UserInfo, tkn *j
|
||||
// Update path for regular routes.
|
||||
targetURL = mergeURLs(targetURL, u, up.dropSrcPathPrefixParts, up.mergeQueryArgs)
|
||||
}
|
||||
|
||||
wasLocalRetry := false
|
||||
again:
|
||||
ok, needLocalRetry := tryProcessingRequest(w, r, targetURL, hc, up.retryStatusCodes, ui, bu)
|
||||
@@ -412,7 +404,7 @@ func processRequest(w http.ResponseWriter, r *http.Request, ui *UserInfo, tkn *j
|
||||
ui.backendErrors.Inc()
|
||||
}
|
||||
err := &httpserver.ErrorWithStatusCode{
|
||||
Err: fmt.Errorf("all the %d backends for the user %q are unavailable", up.getBackendsCount(), ui.name()),
|
||||
Err: fmt.Errorf("all the %d backends for the user %q are unavailable for proxying the request - check previous WARN logs to see the exact error for each failed backend", up.getBackendsCount(), ui.name()),
|
||||
StatusCode: http.StatusBadGateway,
|
||||
}
|
||||
httpserver.Errorf(w, r, "%s", err)
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
@@ -430,7 +429,7 @@ unauthorized_user:
|
||||
}
|
||||
responseExpected = `
|
||||
statusCode=502
|
||||
all the 2 backends for the user "" are unavailable`
|
||||
all the 2 backends for the user "" are unavailable for proxying the request - check previous WARN logs to see the exact error for each failed backend`
|
||||
f(cfgStr, requestURL, backendHandler, responseExpected)
|
||||
|
||||
// all the backend_urls are unavailable for authorized user
|
||||
@@ -448,7 +447,7 @@ users:
|
||||
}
|
||||
responseExpected = `
|
||||
statusCode=502
|
||||
all the 2 backends for the user "some-user" are unavailable`
|
||||
all the 2 backends for the user "some-user" are unavailable for proxying the request - check previous WARN logs to see the exact error for each failed backend`
|
||||
f(cfgStr, requestURL, backendHandler, responseExpected)
|
||||
|
||||
// zero discovered backend IPs
|
||||
@@ -470,7 +469,7 @@ unauthorized_user:
|
||||
}
|
||||
responseExpected = `
|
||||
statusCode=502
|
||||
all the 0 backends for the user "" are unavailable`
|
||||
all the 0 backends for the user "" are unavailable for proxying the request - check previous WARN logs to see the exact error for each failed backend`
|
||||
f(cfgStr, requestURL, backendHandler, responseExpected)
|
||||
netutil.Resolver = origResolver
|
||||
|
||||
@@ -487,7 +486,7 @@ unauthorized_user:
|
||||
}
|
||||
responseExpected = `
|
||||
statusCode=502
|
||||
all the 2 backends for the user "" are unavailable`
|
||||
all the 2 backends for the user "" are unavailable for proxying the request - check previous WARN logs to see the exact error for each failed backend`
|
||||
f(cfgStr, requestURL, backendHandler, responseExpected)
|
||||
if n := retries.Load(); n != 2 {
|
||||
t.Fatalf("unexpected number of retries; got %d; want 2", n)
|
||||
@@ -572,41 +571,22 @@ func TestJWTRequestHandler(t *testing.T) {
|
||||
|
||||
return payload + "." + signatureB64
|
||||
}
|
||||
genToken(t, nil, false)
|
||||
|
||||
f := func(cfgStr string, r *http.Request, responseExpected string) {
|
||||
t.Helper()
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if _, err := w.Write([]byte("path: " + r.URL.Path + "\n")); err != nil {
|
||||
if _, err := w.Write([]byte(r.RequestURI + "\n")); err != nil {
|
||||
panic(fmt.Errorf("cannot write response: %w", err))
|
||||
}
|
||||
if _, err := w.Write([]byte("query:\n")); err != nil {
|
||||
panic(fmt.Errorf("cannot write response: %w", err))
|
||||
}
|
||||
names := make([]string, 0, len(r.URL.Query()))
|
||||
query := r.URL.Query()
|
||||
for n := range query {
|
||||
names = append(names, n)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, n := range names {
|
||||
for _, v := range query[n] {
|
||||
if _, err := w.Write([]byte(" " + n + "=" + v + "\n")); err != nil {
|
||||
panic(fmt.Errorf("cannot write response: %w", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := w.Write([]byte("headers:\n")); err != nil {
|
||||
panic(fmt.Errorf("cannot write response: %w", err))
|
||||
}
|
||||
if v := r.Header.Get(`AccountID`); v != "" {
|
||||
if _, err := w.Write([]byte(` AccountID=` + v + "\n")); err != nil {
|
||||
if v := r.Header.Get(`extra_label`); v != "" {
|
||||
if _, err := w.Write([]byte(`extra_label=` + v + "\n")); err != nil {
|
||||
panic(fmt.Errorf("cannot write response: %w", err))
|
||||
}
|
||||
}
|
||||
if v := r.Header.Get(`ProjectID`); v != "" {
|
||||
if _, err := w.Write([]byte(` ProjectID=` + v + "\n")); err != nil {
|
||||
if v := r.Header.Get(`extra_filters`); v != "" {
|
||||
if _, err := w.Write([]byte(`extra_filters=` + v + "\n")); err != nil {
|
||||
panic(fmt.Errorf("cannot write response: %w", err))
|
||||
}
|
||||
}
|
||||
@@ -652,7 +632,7 @@ users:
|
||||
- %q
|
||||
url_prefix: {BACKEND}/foo`, string(publicKeyPEM))
|
||||
noVMAccessClaimToken := genToken(t, nil, true)
|
||||
minimalToken := genToken(t, map[string]any{
|
||||
defaultVMAccessClaimToken := genToken(t, map[string]any{
|
||||
"exp": time.Now().Add(10 * time.Minute).Unix(),
|
||||
"vm_access": map[string]any{},
|
||||
}, true)
|
||||
@@ -665,30 +645,6 @@ users:
|
||||
"vm_access": map[string]any{},
|
||||
}, false)
|
||||
|
||||
fullToken := genToken(t, map[string]any{
|
||||
"exp": time.Now().Add(10 * time.Minute).Unix(),
|
||||
"vm_access": map[string]any{
|
||||
"metrics_account_id": 123,
|
||||
"metrics_project_id": 234,
|
||||
"metrics_extra_labels": []string{
|
||||
"label1=value1",
|
||||
"label2=value2",
|
||||
},
|
||||
"metrics_extra_filters": []string{
|
||||
`{label3="value3"}`,
|
||||
`{label4="value4"}`,
|
||||
},
|
||||
"logs_account_id": 345,
|
||||
"logs_project_id": 456,
|
||||
"logs_extra_filters": []string{
|
||||
`{"namespace":"my-app","env":"prod"}`,
|
||||
},
|
||||
"logs_extra_stream_filters": []string{
|
||||
`{"team":"dev"}`,
|
||||
},
|
||||
},
|
||||
}, true)
|
||||
|
||||
// missing authorization
|
||||
request := httptest.NewRequest(`GET`, "http://some-host.com/abc", nil)
|
||||
responseExpected := `
|
||||
@@ -726,9 +682,7 @@ Unauthorized`
|
||||
request.Header.Set(`Authorization`, `Bearer `+invalidSignatureToken)
|
||||
responseExpected = `
|
||||
statusCode=200
|
||||
path: /foo/abc
|
||||
query:
|
||||
headers:`
|
||||
/foo/abc`
|
||||
f(`
|
||||
users:
|
||||
- jwt:
|
||||
@@ -737,17 +691,15 @@ users:
|
||||
|
||||
// token with default valid vm_access claim
|
||||
request = httptest.NewRequest(`GET`, "http://some-host.com/abc", nil)
|
||||
request.Header.Set(`Authorization`, `Bearer `+minimalToken)
|
||||
request.Header.Set(`Authorization`, `Bearer `+defaultVMAccessClaimToken)
|
||||
responseExpected = `
|
||||
statusCode=200
|
||||
path: /foo/abc
|
||||
query:
|
||||
headers:`
|
||||
/foo/abc`
|
||||
f(simpleCfgStr, request, responseExpected)
|
||||
|
||||
// jwt token used but no matching user with JWT token in config
|
||||
request = httptest.NewRequest(`GET`, "http://some-host.com/abc", nil)
|
||||
request.Header.Set(`Authorization`, `Bearer `+minimalToken)
|
||||
request.Header.Set(`Authorization`, `Bearer `+defaultVMAccessClaimToken)
|
||||
responseExpected = `
|
||||
statusCode=401
|
||||
Unauthorized`
|
||||
@@ -763,479 +715,16 @@ users:
|
||||
t.Fatalf("failed to write public key file: %s", err)
|
||||
}
|
||||
request = httptest.NewRequest(`GET`, "http://some-host.com/abc", nil)
|
||||
request.Header.Set(`Authorization`, `Bearer `+minimalToken)
|
||||
request.Header.Set(`Authorization`, `Bearer `+defaultVMAccessClaimToken)
|
||||
responseExpected = `
|
||||
statusCode=200
|
||||
path: /foo/abc
|
||||
query:
|
||||
headers:`
|
||||
/foo/abc`
|
||||
f(fmt.Sprintf(`
|
||||
users:
|
||||
- jwt:
|
||||
public_key_files:
|
||||
- %q
|
||||
url_prefix: {BACKEND}/foo`, publicKeyFile), request, responseExpected)
|
||||
|
||||
// ---- VictoriaMetrics specific tests ----
|
||||
|
||||
// extra_label and extra_filters dropped if empty in vm_access claim
|
||||
request = httptest.NewRequest(`GET`, "http://some-host.com/api/v1/query", nil)
|
||||
request.Header.Set(`Authorization`, `Bearer `+minimalToken)
|
||||
responseExpected = `
|
||||
statusCode=200
|
||||
path: /select/0:0/api/v1/query
|
||||
query:
|
||||
headers:`
|
||||
f(fmt.Sprintf(
|
||||
`
|
||||
users:
|
||||
- jwt:
|
||||
public_keys:
|
||||
- %q
|
||||
url_prefix: {BACKEND}/select/{{.MetricsTenant}}/?extra_label={{.MetricsExtraLabels}}&extra_filters={{.MetricsExtraFilters}}`, string(publicKeyPEM)),
|
||||
request,
|
||||
responseExpected,
|
||||
)
|
||||
|
||||
// extra_label and extra_filters set if present in vm_access claim
|
||||
request = httptest.NewRequest(`GET`, "http://some-host.com/api/v1/query", nil)
|
||||
request.Header.Set(`Authorization`, `Bearer `+fullToken)
|
||||
responseExpected = `
|
||||
statusCode=200
|
||||
path: /select/123:234/api/v1/query
|
||||
query:
|
||||
extra_filters={label3="value3"}
|
||||
extra_filters={label4="value4"}
|
||||
extra_label=label1=value1
|
||||
extra_label=label2=value2
|
||||
headers:`
|
||||
f(fmt.Sprintf(
|
||||
`
|
||||
users:
|
||||
- jwt:
|
||||
public_keys:
|
||||
- %q
|
||||
url_prefix: {BACKEND}/select/{{.MetricsTenant}}/?extra_label={{.MetricsExtraLabels}}&extra_filters={{.MetricsExtraFilters}}`, string(publicKeyPEM)),
|
||||
request,
|
||||
responseExpected,
|
||||
)
|
||||
|
||||
// extra_label and extra_filters from vm_access claim merged with statically defined
|
||||
request = httptest.NewRequest(`GET`, "http://some-host.com/api/v1/query", nil)
|
||||
request.Header.Set(`Authorization`, `Bearer `+fullToken)
|
||||
responseExpected = `
|
||||
statusCode=200
|
||||
path: /select/123:234/api/v1/query
|
||||
query:
|
||||
extra_filters=aStaticFilter
|
||||
extra_filters={label3="value3"}
|
||||
extra_filters={label4="value4"}
|
||||
extra_label=aStaticLabel
|
||||
extra_label=label1=value1
|
||||
extra_label=label2=value2
|
||||
headers:`
|
||||
f(fmt.Sprintf(
|
||||
`
|
||||
users:
|
||||
- jwt:
|
||||
public_keys:
|
||||
- %q
|
||||
url_prefix: {BACKEND}/select/{{.MetricsTenant}}/?extra_label=aStaticLabel&extra_filters=aStaticFilter&extra_label={{.MetricsExtraLabels}}&extra_filters={{.MetricsExtraFilters}}`, string(publicKeyPEM)),
|
||||
request,
|
||||
responseExpected,
|
||||
)
|
||||
|
||||
// extra_labels and extra_filters set from vm_access claim should override user provided query args
|
||||
request = httptest.NewRequest(`GET`, "http://some-host.com/api/v1/query?extra_label=userProvidedLabel&extra_filters=userProvidedFilter", nil)
|
||||
request.Header.Set(`Authorization`, `Bearer `+fullToken)
|
||||
responseExpected = `
|
||||
statusCode=200
|
||||
path: /select/123:234/api/v1/query
|
||||
query:
|
||||
extra_filters={label3="value3"}
|
||||
extra_filters={label4="value4"}
|
||||
extra_label=label1=value1
|
||||
extra_label=label2=value2
|
||||
headers:`
|
||||
f(
|
||||
fmt.Sprintf(`
|
||||
users:
|
||||
- jwt:
|
||||
public_keys:
|
||||
- %q
|
||||
url_prefix: {BACKEND}/select/{{.MetricsTenant}}/?extra_label={{.MetricsExtraLabels}}&extra_filters={{.MetricsExtraFilters}}`, string(publicKeyPEM)),
|
||||
request,
|
||||
responseExpected,
|
||||
)
|
||||
|
||||
// merge user provided query args with extra_labels and extra_filters from vm_access claim
|
||||
request = httptest.NewRequest(`GET`, "http://some-host.com/api/v1/query?extra_label=userProvidedLabel&extra_filters=userProvidedFilter", nil)
|
||||
request.Header.Set(`Authorization`, `Bearer `+fullToken)
|
||||
responseExpected = `
|
||||
statusCode=200
|
||||
path: /select/123:234/api/v1/query
|
||||
query:
|
||||
extra_filters={label3="value3"}
|
||||
extra_filters={label4="value4"}
|
||||
extra_filters=userProvidedFilter
|
||||
extra_label=label1=value1
|
||||
extra_label=label2=value2
|
||||
extra_label=userProvidedLabel
|
||||
headers:`
|
||||
f(fmt.Sprintf(`
|
||||
users:
|
||||
- jwt:
|
||||
public_keys:
|
||||
- %q
|
||||
merge_query_args: [extra_filters, extra_label]
|
||||
url_prefix: {BACKEND}/select/{{.MetricsTenant}}/?extra_label={{.MetricsExtraLabels}}&extra_filters={{.MetricsExtraFilters}}`, string(publicKeyPEM)),
|
||||
request,
|
||||
responseExpected,
|
||||
)
|
||||
|
||||
// pass user provided query args if vm_access claim has no extra_labels and extra_filters
|
||||
request = httptest.NewRequest(`GET`, "http://some-host.com/api/v1/query?extra_label=userProvidedLabel&extra_filters=userProvidedFilter", nil)
|
||||
request.Header.Set(`Authorization`, `Bearer `+fullToken)
|
||||
responseExpected = `
|
||||
statusCode=200
|
||||
path: /select/123:234/api/v1/query
|
||||
query:
|
||||
extra_filters=userProvidedFilter
|
||||
extra_label=userProvidedLabel
|
||||
headers:`
|
||||
f(fmt.Sprintf(`
|
||||
users:
|
||||
- jwt:
|
||||
public_keys:
|
||||
- %q
|
||||
merge_query_args: [extra_filters, extra_label]
|
||||
url_prefix: {BACKEND}/select/{{.MetricsTenant}}/`, string(publicKeyPEM)),
|
||||
request,
|
||||
responseExpected,
|
||||
)
|
||||
|
||||
// pass user provided query args if vm_access claim has no extra_labels and extra_filters
|
||||
request = httptest.NewRequest(`GET`, "http://some-host.com/api/v1/query?extra_label=userProvidedLabel&extra_filters=userProvidedFilter", nil)
|
||||
request.Header.Set(`Authorization`, `Bearer `+fullToken)
|
||||
responseExpected = `
|
||||
statusCode=200
|
||||
path: /select/123:234/api/v1/query
|
||||
query:
|
||||
extra_filters=userProvidedFilter
|
||||
extra_label=userProvidedLabel
|
||||
headers:`
|
||||
f(fmt.Sprintf(`
|
||||
users:
|
||||
- jwt:
|
||||
public_keys:
|
||||
- %q
|
||||
url_prefix: {BACKEND}/select/{{.MetricsTenant}}/`, string(publicKeyPEM)),
|
||||
request,
|
||||
responseExpected,
|
||||
)
|
||||
|
||||
// placeholders in url_map
|
||||
request = httptest.NewRequest(`GET`, "http://some-host.com/api/v1/query", nil)
|
||||
request.Header.Set(`Authorization`, `Bearer `+fullToken)
|
||||
responseExpected = `
|
||||
statusCode=200
|
||||
path: /select/123:234/api/v1/query
|
||||
query:
|
||||
extra_filters={label3="value3"}
|
||||
extra_filters={label4="value4"}
|
||||
extra_label=label1=value1
|
||||
extra_label=label2=value2
|
||||
headers:`
|
||||
f(fmt.Sprintf(
|
||||
`
|
||||
users:
|
||||
- jwt:
|
||||
public_keys:
|
||||
- %q
|
||||
url_map:
|
||||
- src_paths: ["/api/.*"]
|
||||
url_prefix: {BACKEND}/select/{{.MetricsTenant}}/?extra_label={{.MetricsExtraLabels}}&extra_filters={{.MetricsExtraFilters}}`, string(publicKeyPEM)),
|
||||
request,
|
||||
responseExpected,
|
||||
)
|
||||
|
||||
// ---- VictoriaLogs specific tests ----
|
||||
|
||||
// tenant headers not overwritten if set statically
|
||||
// extra_filters extra_stream_filters dropped if empty in vm_access claim
|
||||
request = httptest.NewRequest(`GET`, "http://some-host.com/query", nil)
|
||||
request.Header.Set(`Authorization`, `Bearer `+minimalToken)
|
||||
responseExpected = `
|
||||
statusCode=200
|
||||
path: /select/logsql/query
|
||||
query:
|
||||
headers:
|
||||
AccountID=555
|
||||
ProjectID=666`
|
||||
f(
|
||||
fmt.Sprintf(`
|
||||
users:
|
||||
- jwt:
|
||||
public_keys:
|
||||
- %q
|
||||
headers:
|
||||
- "AccountID: 555"
|
||||
- "ProjectID: 666"
|
||||
url_prefix: {BACKEND}/select/logsql/?extra_filters={{.LogsExtraFilters}}&extra_stream_filters={{.LogsExtraStreamFilters}}`, string(publicKeyPEM)),
|
||||
request,
|
||||
responseExpected,
|
||||
)
|
||||
|
||||
// tenant headers are overwritten if set as placeholders
|
||||
request = httptest.NewRequest(`GET`, "http://some-host.com/query", nil)
|
||||
request.Header.Set(`Authorization`, `Bearer `+minimalToken)
|
||||
responseExpected = `
|
||||
statusCode=200
|
||||
path: /select/logsql/query
|
||||
query:
|
||||
headers:
|
||||
AccountID=0
|
||||
ProjectID=0`
|
||||
f(
|
||||
fmt.Sprintf(`
|
||||
users:
|
||||
- jwt:
|
||||
public_keys:
|
||||
- %q
|
||||
headers:
|
||||
- "AccountID: {{.LogsAccountID}}"
|
||||
- "ProjectID: {{.LogsProjectID}}"
|
||||
url_prefix: {BACKEND}/select/logsql/?extra_filters={{.LogsExtraFilters}}&extra_stream_filters={{.LogsExtraStreamFilters}}`, string(publicKeyPEM)),
|
||||
request,
|
||||
responseExpected,
|
||||
)
|
||||
|
||||
// tenant headers are overwritten if set as placeholders
|
||||
// extra_filters extra_stream_filters from vm_access claim merged with statically defined
|
||||
request = httptest.NewRequest(`GET`, "http://some-host.com/query", nil)
|
||||
request.Header.Set(`Authorization`, `Bearer `+fullToken)
|
||||
responseExpected = `
|
||||
statusCode=200
|
||||
path: /select/logsql/query
|
||||
query:
|
||||
extra_filters=aStaticFilter
|
||||
extra_filters={"namespace":"my-app","env":"prod"}
|
||||
extra_stream_filters=aStaticStreamFilter
|
||||
extra_stream_filters={"team":"dev"}
|
||||
headers:
|
||||
AccountID=345
|
||||
ProjectID=456`
|
||||
f(
|
||||
fmt.Sprintf(`
|
||||
users:
|
||||
- jwt:
|
||||
public_keys:
|
||||
- %q
|
||||
headers:
|
||||
- "AccountID: {{.LogsAccountID}}"
|
||||
- "ProjectID: {{.LogsProjectID}}"
|
||||
url_prefix: {BACKEND}/select/logsql/?extra_filters=aStaticFilter&extra_stream_filters=aStaticStreamFilter&extra_filters={{.LogsExtraFilters}}&extra_stream_filters={{.LogsExtraStreamFilters}}`, string(publicKeyPEM)),
|
||||
request,
|
||||
responseExpected,
|
||||
)
|
||||
|
||||
// tenant headers are overwritten if set as placeholders
|
||||
// extra_filters extra_stream_filters from vm_access claim merged with statically defined
|
||||
request = httptest.NewRequest(`GET`, "http://some-host.com/query", nil)
|
||||
request.Header.Set(`Authorization`, `Bearer `+fullToken)
|
||||
responseExpected = `
|
||||
statusCode=200
|
||||
path: /select/logsql/query
|
||||
query:
|
||||
extra_filters=aStaticFilter
|
||||
extra_filters={"namespace":"my-app","env":"prod"}
|
||||
extra_stream_filters=aStaticStreamFilter
|
||||
extra_stream_filters={"team":"dev"}
|
||||
headers:
|
||||
AccountID=345
|
||||
ProjectID=456`
|
||||
f(
|
||||
fmt.Sprintf(`
|
||||
users:
|
||||
- jwt:
|
||||
public_keys:
|
||||
- %q
|
||||
headers:
|
||||
- "AccountID: {{.LogsAccountID}}"
|
||||
- "ProjectID: {{.LogsProjectID}}"
|
||||
url_prefix: {BACKEND}/select/logsql/?extra_filters=aStaticFilter&extra_stream_filters=aStaticStreamFilter&extra_filters={{.LogsExtraFilters}}&extra_stream_filters={{.LogsExtraStreamFilters}}`, string(publicKeyPEM)),
|
||||
request,
|
||||
responseExpected,
|
||||
)
|
||||
|
||||
// claim info should overwrite user provided query args and headers
|
||||
request = httptest.NewRequest(`GET`, "http://some-host.com/query?extra_filters=aUserFilter&extra_stream_filters=aUserStreamFilter", nil)
|
||||
request.Header.Set(`Authorization`, `Bearer `+fullToken)
|
||||
request.Header.Set(`AccountID`, `aUserAccountID`)
|
||||
request.Header.Set(`ProjectID`, `aUserProjectID`)
|
||||
responseExpected = `
|
||||
statusCode=200
|
||||
path: /select/logsql/query
|
||||
query:
|
||||
extra_filters={"namespace":"my-app","env":"prod"}
|
||||
extra_stream_filters={"team":"dev"}
|
||||
headers:
|
||||
AccountID=345
|
||||
ProjectID=456`
|
||||
f(
|
||||
fmt.Sprintf(`
|
||||
users:
|
||||
- jwt:
|
||||
public_keys:
|
||||
- %q
|
||||
headers:
|
||||
- "AccountID: {{.LogsAccountID}}"
|
||||
- "ProjectID: {{.LogsProjectID}}"
|
||||
url_prefix: {BACKEND}/select/logsql/?extra_filters={{.LogsExtraFilters}}&extra_stream_filters={{.LogsExtraStreamFilters}}`, string(publicKeyPEM)),
|
||||
request,
|
||||
responseExpected,
|
||||
)
|
||||
|
||||
// merge user provided query args with extra_filters and extra_stream_filters from vm_access claim
|
||||
request = httptest.NewRequest(`GET`, "http://some-host.com/query?extra_filters=aUserFilter&extra_stream_filters=aUserStreamFilter", nil)
|
||||
request.Header.Set(`Authorization`, `Bearer `+fullToken)
|
||||
responseExpected = `
|
||||
statusCode=200
|
||||
path: /select/logsql/query
|
||||
query:
|
||||
extra_filters={"namespace":"my-app","env":"prod"}
|
||||
extra_filters=aUserFilter
|
||||
extra_stream_filters={"team":"dev"}
|
||||
extra_stream_filters=aUserStreamFilter
|
||||
headers:
|
||||
AccountID=345
|
||||
ProjectID=456`
|
||||
f(
|
||||
fmt.Sprintf(`
|
||||
users:
|
||||
- jwt:
|
||||
public_keys:
|
||||
- %q
|
||||
headers:
|
||||
- "AccountID: {{.LogsAccountID}}"
|
||||
- "ProjectID: {{.LogsProjectID}}"
|
||||
merge_query_args: [extra_filters, extra_stream_filters]
|
||||
url_prefix: {BACKEND}/select/logsql/?extra_filters={{.LogsExtraFilters}}&extra_stream_filters={{.LogsExtraStreamFilters}}`, string(publicKeyPEM)),
|
||||
request,
|
||||
responseExpected,
|
||||
)
|
||||
|
||||
// pass user provided query args if vm_access claim has no extra_labels and extra_filters
|
||||
request = httptest.NewRequest(`GET`, "http://some-host.com/query?extra_filters=aUserFilter&extra_stream_filters=aUserStreamFilter", nil)
|
||||
request.Header.Set(`Authorization`, `Bearer `+minimalToken)
|
||||
responseExpected = `
|
||||
statusCode=200
|
||||
path: /select/logsql/query
|
||||
query:
|
||||
extra_filters=aUserFilter
|
||||
extra_stream_filters=aUserStreamFilter
|
||||
headers:
|
||||
AccountID=0
|
||||
ProjectID=0`
|
||||
f(
|
||||
fmt.Sprintf(`
|
||||
users:
|
||||
- jwt:
|
||||
public_keys:
|
||||
- %q
|
||||
headers:
|
||||
- "AccountID: {{.LogsAccountID}}"
|
||||
- "ProjectID: {{.LogsProjectID}}"
|
||||
merge_query_args: [extra_filters, extra_stream_filters]
|
||||
url_prefix: {BACKEND}/select/logsql/?extra_filters={{.LogsExtraFilters}}&extra_stream_filters={{.LogsExtraStreamFilters}}`, string(publicKeyPEM)),
|
||||
request,
|
||||
responseExpected,
|
||||
)
|
||||
|
||||
// placeholders in url_map
|
||||
request = httptest.NewRequest(`GET`, "http://some-host.com/query", nil)
|
||||
request.Header.Set(`Authorization`, `Bearer `+fullToken)
|
||||
responseExpected = `
|
||||
statusCode=200
|
||||
path: /select/logsql/query
|
||||
query:
|
||||
extra_filters={"namespace":"my-app","env":"prod"}
|
||||
extra_stream_filters={"team":"dev"}
|
||||
headers:
|
||||
AccountID=345
|
||||
ProjectID=456`
|
||||
f(fmt.Sprintf(
|
||||
`
|
||||
users:
|
||||
- jwt:
|
||||
public_keys:
|
||||
- %q
|
||||
url_map:
|
||||
- src_paths: ["/query"]
|
||||
headers:
|
||||
- "AccountID: {{.LogsAccountID}}"
|
||||
- "ProjectID: {{.LogsProjectID}}"
|
||||
url_prefix: {BACKEND}/select/logsql/?extra_filters={{.LogsExtraFilters}}&extra_stream_filters={{.LogsExtraStreamFilters}}`, string(publicKeyPEM)),
|
||||
request,
|
||||
responseExpected,
|
||||
)
|
||||
|
||||
// multiple placeholders in url_map for the same param
|
||||
request = httptest.NewRequest(`GET`, "http://some-host.com/query", nil)
|
||||
request.Header.Set(`Authorization`, `Bearer `+fullToken)
|
||||
responseExpected = `
|
||||
statusCode=200
|
||||
path: /select/logsql/query
|
||||
query:
|
||||
extra_filters={"namespace":"my-app","env":"prod"}
|
||||
extra_stream_filters={"team":"dev"}
|
||||
tenant_info=static=value
|
||||
tenant_info=345
|
||||
tenant_info=456
|
||||
headers:
|
||||
AccountID=345
|
||||
ProjectID=456`
|
||||
f(fmt.Sprintf(
|
||||
`
|
||||
users:
|
||||
- jwt:
|
||||
public_keys:
|
||||
- %q
|
||||
url_map:
|
||||
- src_paths: ["/query"]
|
||||
headers:
|
||||
- "AccountID: {{.LogsAccountID}}"
|
||||
- "ProjectID: {{.LogsProjectID}}"
|
||||
url_prefix: {BACKEND}/select/logsql/?extra_filters={{.LogsExtraFilters}}&extra_stream_filters={{.LogsExtraStreamFilters}}&tenant_info=static=value&tenant_info={{.LogsAccountID}}&tenant_info={{.LogsProjectID}}`, string(publicKeyPEM)),
|
||||
request,
|
||||
responseExpected,
|
||||
)
|
||||
// client request params must be ignored by placeholders
|
||||
request = httptest.NewRequest(`GET`, "http://some-host.com/query?template_attack={{.LogsExtraFilters}}", nil)
|
||||
request.Header.Set(`Authorization`, `Bearer `+fullToken)
|
||||
request.Header.Set(`AccountID`, `{{.LogsAccountID}}`)
|
||||
responseExpected = `
|
||||
statusCode=200
|
||||
path: /select/logsql/query
|
||||
query:
|
||||
extra_filters={"namespace":"my-app","env":"prod"}
|
||||
extra_stream_filters={"team":"dev"}
|
||||
template_attack={{.LogsExtraFilters}}
|
||||
headers:
|
||||
AccountID={{.LogsAccountID}}`
|
||||
f(fmt.Sprintf(
|
||||
`
|
||||
users:
|
||||
- jwt:
|
||||
public_keys:
|
||||
- %q
|
||||
url_map:
|
||||
- src_paths: ["/query"]
|
||||
url_prefix: {BACKEND}/select/logsql/?extra_filters={{.LogsExtraFilters}}&extra_stream_filters={{.LogsExtraStreamFilters}}`, string(publicKeyPEM)),
|
||||
request,
|
||||
responseExpected,
|
||||
)
|
||||
|
||||
url_prefix: {BACKEND}/foo`, string(publicKeyFile)), request, responseExpected)
|
||||
}
|
||||
|
||||
type fakeResponseWriter struct {
|
||||
|
||||
@@ -62,7 +62,7 @@ var (
|
||||
"Excess series are logged and dropped. This can be useful for limiting series churn rate. See https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#cardinality-limiter . "+
|
||||
"See also -storage.maxHourlySeries")
|
||||
|
||||
minFreeDiskSpaceBytes = flagutil.NewBytes("storage.minFreeDiskSpaceBytes", 10e6, "The minimum free disk space at -storageDataPath after which the storage stops accepting new data")
|
||||
minFreeDiskSpaceBytes = flagutil.NewBytes("storage.minFreeDiskSpaceBytes", 100e6, "The minimum free disk space at -storageDataPath after which the storage stops accepting new data")
|
||||
|
||||
cacheSizeStorageTSID = flagutil.NewBytes("storage.cacheSizeStorageTSID", 0, "Overrides max size for storage/tsid cache. "+
|
||||
"See https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#cache-tuning")
|
||||
|
||||
@@ -7,6 +7,7 @@ import { AUTOCOMPLETE_LIMITS } from "../../../constants/queryAutocomplete";
|
||||
import { QueryEditorAutocompleteProps } from "./QueryEditor";
|
||||
import { getExprLastPart, getValueByContext, getContext } from "./autocompleteUtils";
|
||||
import { extractCurrentLabel, extractLabelMatchers, extractMetric, splitByCursor } from "./utils/parser";
|
||||
import { escapeLabelName } from "../../../utils/metric";
|
||||
|
||||
const QueryEditorAutocomplete: FC<QueryEditorAutocompleteProps> = ({
|
||||
value,
|
||||
@@ -90,6 +91,7 @@ const QueryEditorAutocomplete: FC<QueryEditorAutocompleteProps> = ({
|
||||
}
|
||||
|
||||
if (context === QueryContextType.label) {
|
||||
insert = escapeLabelName(insert);
|
||||
valueAfterCursor = valueAfterCursor.replace(/^[^\s=!,{}()"|+\-/*^]*/, "");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { QueryUpdater } from "./types";
|
||||
import { escapeLabelName } from "../../utils/metric";
|
||||
|
||||
export const queryUpdater: QueryUpdater = {
|
||||
seriesCountByMetricName: ({ query }): string => {
|
||||
@@ -28,5 +29,5 @@ const getSeriesSelector = (label: string | null, value: string): string => {
|
||||
if (!label) {
|
||||
return "";
|
||||
}
|
||||
return "{" + label + "=" + JSON.stringify(value) + "}";
|
||||
return "{" + escapeLabelName(label) + "=" + JSON.stringify(value) + "}";
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
TipHighNumberOfValues
|
||||
} from "./CardinalityTips";
|
||||
import useSearchParamsFromObject from "../../hooks/useSearchParamsFromObject";
|
||||
import { escapeLabelName } from "../../utils/metric";
|
||||
|
||||
const spinnerMessage = `Please wait while cardinality stats is calculated.
|
||||
This may take some time if the db contains big number of time series.`;
|
||||
@@ -36,12 +37,17 @@ const CardinalityPanel: FC = () => {
|
||||
const defaultState = getDefaultState(match, focusLabel);
|
||||
|
||||
const handleFilterClick = (key: string) => (query: string) => {
|
||||
const rawQuery = query;
|
||||
|
||||
const isLabelKey = ["labelValueCountByLabelName", "seriesCountByLabelName"].includes(key);
|
||||
if (isLabelKey) query = escapeLabelName(query);
|
||||
|
||||
const value = queryUpdater[key]({ query, focusLabel, match });
|
||||
const params: Record<string, string> = { match: value };
|
||||
if (key === "labelValueCountByLabelName" || key == "seriesCountByLabelName") {
|
||||
params.focusLabel = query;
|
||||
if (isLabelKey) {
|
||||
params.focusLabel = rawQuery;
|
||||
}
|
||||
if (key == "seriesCountByFocusLabelValue") {
|
||||
if (key === "seriesCountByFocusLabelValue") {
|
||||
params.focusLabel = "";
|
||||
}
|
||||
setSearchParamsFromKeys(params);
|
||||
|
||||
@@ -52,3 +52,7 @@ export const isHistogramData = (result: MetricBase[]) => {
|
||||
|
||||
return isHistogram && result.every(r => histogramLabels.some(l => l in r.metric));
|
||||
};
|
||||
|
||||
export const escapeLabelName = (s: string) => {
|
||||
return s.replace(/([\\./-])/g, "\\$1");
|
||||
};
|
||||
|
||||
@@ -19,7 +19,6 @@ func TestSingleMetricsMetadata(t *testing.T) {
|
||||
sut := tc.MustStartVmsingle("vmsingle", []string{
|
||||
"-storageDataPath=" + tc.Dir(),
|
||||
"-retentionPeriod=100y",
|
||||
"-enableMetadata",
|
||||
})
|
||||
// verify empty stats
|
||||
resp := sut.PrometheusAPIV1Metadata(t, "", 0, apptest.QueryOpts{})
|
||||
@@ -120,15 +119,12 @@ func TestClusterMetricsMetadata(t *testing.T) {
|
||||
|
||||
vminsert1 := tc.MustStartVminsert("vminsert1", []string{
|
||||
fmt.Sprintf("-storageNode=%s,%s", vmstorage1.VminsertAddr(), vmstorage2.VminsertAddr()),
|
||||
"-enableMetadata",
|
||||
})
|
||||
vminsert2 := tc.MustStartVminsert("vminsert-2", []string{
|
||||
fmt.Sprintf("-storageNode=%s,%s", vmstorage1.VminsertAddr(), vmstorage2.VminsertAddr()),
|
||||
"-enableMetadata",
|
||||
})
|
||||
vminsertGlobal := tc.MustStartVminsert("vminsert-global", []string{
|
||||
fmt.Sprintf("-storageNode=%s,%s", vminsert1.ClusternativeListenAddr(), vminsert2.ClusternativeListenAddr()),
|
||||
"-enableMetadata",
|
||||
})
|
||||
vmselect := tc.MustStartVmselect("vmselect", []string{
|
||||
fmt.Sprintf("-storageNode=%s,%s", vmstorage1.VmselectAddr(), vmstorage2.VmselectAddr()),
|
||||
|
||||
@@ -171,6 +171,26 @@ func TestClusterMultiTenantSelect(t *testing.T) {
|
||||
t.Errorf("unexpected response (-want, +got):\n%s", diff)
|
||||
}
|
||||
|
||||
// /api/v1/label/../value with extra_filters
|
||||
|
||||
wantVR := apptest.NewPrometheusAPIV1LabelValuesResponse(t,
|
||||
`{"data": [
|
||||
"5"
|
||||
]
|
||||
}`)
|
||||
wantSR.Sort()
|
||||
gotVR := vmselect.PrometheusAPIV1LabelValues(t, "vm_account_id", "foo", apptest.QueryOpts{
|
||||
Start: "2022-05-10T08:00:00.000Z",
|
||||
End: "2022-05-10T08:30:00.000Z",
|
||||
ExtraFilters: []string{`{vm_account_id="5"}`},
|
||||
Tenant: "multitenant",
|
||||
})
|
||||
gotSR.Sort()
|
||||
|
||||
if diff := cmp.Diff(wantVR, gotVR, cmpopts.IgnoreFields(apptest.PrometheusAPIV1LabelValuesResponse{}, "Status", "IsPartial")); diff != "" {
|
||||
t.Errorf("unexpected response (-want, +got):\n%s", diff)
|
||||
}
|
||||
|
||||
// Delete series from specific tenant
|
||||
vmselect.APIV1AdminTSDBDeleteSeries(t, "foo_bar", apptest.QueryOpts{
|
||||
Tenant: "5:15",
|
||||
|
||||
@@ -506,6 +506,24 @@
|
||||
"value": 200
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Alert"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "links",
|
||||
"value": [
|
||||
{
|
||||
"targetBlank": true,
|
||||
"title": "Alert",
|
||||
"url": "/alerting/${ds:text}/${__value.text}/find"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -659,4 +677,4 @@
|
||||
"uid": "ehXxUsGSk",
|
||||
"version": 1,
|
||||
"weekStart": ""
|
||||
}
|
||||
}
|
||||
@@ -91,8 +91,26 @@
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "$ds"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
@@ -103,17 +121,42 @@
|
||||
},
|
||||
"id": 24,
|
||||
"options": {
|
||||
"code": {
|
||||
"language": "plaintext",
|
||||
"showLineNumbers": false,
|
||||
"showMiniMap": false
|
||||
"colorMode": "value",
|
||||
"graphMode": "none",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"percentChangeColorMode": "standard",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "/^short_version$/",
|
||||
"values": false
|
||||
},
|
||||
"content": "<div style=\"text-align: center;\">$version</div>",
|
||||
"mode": "markdown"
|
||||
"showPercentChange": false,
|
||||
"textMode": "value",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "12.3.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "$ds"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "vm_app_version{job=~\"$job\",instance=~\"$instance\"}",
|
||||
"format": "table",
|
||||
"instant": true,
|
||||
"interval": "",
|
||||
"legendFormat": "{{short_version}}",
|
||||
"range": false,
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Version",
|
||||
"type": "text"
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
|
||||
@@ -5129,7 +5129,7 @@
|
||||
"uid": "${ds}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum(rate(process_major_pagefaults_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) by (job)",
|
||||
"expr": "sum(rate(process_major_pagefaults_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) by (job) > 0",
|
||||
"legendFormat": "__auto",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
@@ -11153,7 +11153,7 @@
|
||||
"uid": "${ds}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum(rate(process_major_pagefaults_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) by (job,instance)",
|
||||
"expr": "sum(rate(process_major_pagefaults_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) by (job,instance) > 0",
|
||||
"legendFormat": "{{instance}} ({{job}})",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
|
||||
@@ -5174,7 +5174,7 @@
|
||||
"uid": "${ds}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum(rate(process_major_pagefaults_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) by (job)",
|
||||
"expr": "sum(rate(process_major_pagefaults_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) by (job) > 0",
|
||||
"legendFormat": "__auto",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
@@ -7667,7 +7667,7 @@
|
||||
"uid": "${ds}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum(rate(process_major_pagefaults_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) by (job,instance)",
|
||||
"expr": "sum(rate(process_major_pagefaults_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) by (job,instance) > 0",
|
||||
"legendFormat": "{{instance}} ({{job}})",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
|
||||
@@ -92,29 +92,72 @@
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "victoriametrics-metrics-datasource",
|
||||
"uid": "$ds"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 3,
|
||||
"h": 4,
|
||||
"w": 4,
|
||||
"x": 0,
|
||||
"y": 1
|
||||
},
|
||||
"id": 24,
|
||||
"options": {
|
||||
"code": {
|
||||
"language": "plaintext",
|
||||
"showLineNumbers": false,
|
||||
"showMiniMap": false
|
||||
"colorMode": "value",
|
||||
"graphMode": "none",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"percentChangeColorMode": "standard",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "/^short_version$/",
|
||||
"values": false
|
||||
},
|
||||
"content": "<div style=\"text-align: center;\">$version</div>",
|
||||
"mode": "markdown"
|
||||
"showPercentChange": false,
|
||||
"textMode": "value",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "12.3.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "victoriametrics-metrics-datasource",
|
||||
"uid": "$ds"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "vm_app_version{job=~\"$job\",instance=~\"$instance\"}",
|
||||
"format": "table",
|
||||
"instant": true,
|
||||
"interval": "",
|
||||
"legendFormat": "{{short_version}}",
|
||||
"range": false,
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Version",
|
||||
"type": "text"
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
|
||||
@@ -5130,7 +5130,7 @@
|
||||
"uid": "${ds}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum(rate(process_major_pagefaults_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) by (job)",
|
||||
"expr": "sum(rate(process_major_pagefaults_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) by (job) > 0",
|
||||
"legendFormat": "__auto",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
@@ -11154,7 +11154,7 @@
|
||||
"uid": "${ds}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum(rate(process_major_pagefaults_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) by (job,instance)",
|
||||
"expr": "sum(rate(process_major_pagefaults_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) by (job,instance) > 0",
|
||||
"legendFormat": "{{instance}} ({{job}})",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
|
||||
@@ -5175,7 +5175,7 @@
|
||||
"uid": "${ds}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum(rate(process_major_pagefaults_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) by (job)",
|
||||
"expr": "sum(rate(process_major_pagefaults_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) by (job) > 0",
|
||||
"legendFormat": "__auto",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
@@ -7668,7 +7668,7 @@
|
||||
"uid": "${ds}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum(rate(process_major_pagefaults_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) by (job,instance)",
|
||||
"expr": "sum(rate(process_major_pagefaults_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) by (job,instance) > 0",
|
||||
"legendFormat": "{{instance}} ({{job}})",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
|
||||
@@ -27,7 +27,7 @@ groups:
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/oS7Bi_0Wz?viewPanel=20&var-instance={{ $labels.instance }}"
|
||||
dashboard: "{{ $externalURL }}/d/oS7Bi_0Wz?viewPanel=20&var-instance={{ $labels.instance }}"
|
||||
summary: "Instance {{ $labels.instance }} will run out of disk space in 3 days"
|
||||
description: "Taking into account current ingestion rate, free disk space will be enough only
|
||||
for {{ $value | humanizeDuration }} on instance {{ $labels.instance }}.\n
|
||||
@@ -51,7 +51,7 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/oS7Bi_0Wz?viewPanel=20&var-instance={{ $labels.instance }}"
|
||||
dashboard: "{{ $externalURL }}/d/oS7Bi_0Wz?viewPanel=20&var-instance={{ $labels.instance }}"
|
||||
summary: "Instance {{ $labels.instance }} will become read-only in 3 days"
|
||||
description: "Taking into account current ingestion rate, free disk space and -storage.minFreeDiskSpaceBytes
|
||||
instance {{ $labels.instance }} will remain writable for {{ $value | humanizeDuration }}.\n
|
||||
@@ -68,7 +68,7 @@ groups:
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/oS7Bi_0Wz?viewPanel=20&var-instance={{ $labels.instance }}"
|
||||
dashboard: "{{ $externalURL }}/d/oS7Bi_0Wz?viewPanel=20&var-instance={{ $labels.instance }}"
|
||||
summary: "Instance {{ $labels.instance }} (job={{ $labels.job }}) will run out of disk space soon"
|
||||
description: "Disk utilisation on instance {{ $labels.instance }} is more than 80%.\n
|
||||
Having less than 20% of free disk space could cripple merges processes and overall performance.
|
||||
@@ -81,7 +81,7 @@ groups:
|
||||
severity: warning
|
||||
show_at: dashboard
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/oS7Bi_0Wz?viewPanel=52&var-instance={{ $labels.instance }}"
|
||||
dashboard: "{{ $externalURL }}/d/oS7Bi_0Wz?viewPanel=52&var-instance={{ $labels.instance }}"
|
||||
summary: "Too many errors served for {{ $labels.job }} path {{ $labels.path }} (instance {{ $labels.instance }})"
|
||||
description: "Requests to path {{ $labels.path }} are receiving errors.
|
||||
Please verify if clients are sending correct requests."
|
||||
@@ -100,7 +100,7 @@ groups:
|
||||
severity: warning
|
||||
show_at: dashboard
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/oS7Bi_0Wz?viewPanel=44&var-instance={{ $labels.instance }}"
|
||||
dashboard: "{{ $externalURL }}/d/oS7Bi_0Wz?viewPanel=44&var-instance={{ $labels.instance }}"
|
||||
summary: "Too many RPC errors for {{ $labels.job }} (instance {{ $labels.instance }})"
|
||||
description: "RPC errors are interconnection errors between cluster components.\n
|
||||
Possible reasons for errors are misconfiguration, overload, network blips or unreachable components."
|
||||
@@ -116,7 +116,7 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/oS7Bi_0Wz?viewPanel=102"
|
||||
dashboard: "{{ $externalURL }}/d/oS7Bi_0Wz?viewPanel=102"
|
||||
summary: "Churn rate is more than 10% for the last 15m"
|
||||
description: "VM constantly creates new time series.\n
|
||||
This effect is known as Churn Rate.\n
|
||||
@@ -132,7 +132,7 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/oS7Bi_0Wz?viewPanel=102"
|
||||
dashboard: "{{ $externalURL }}/d/oS7Bi_0Wz?viewPanel=102"
|
||||
summary: "Too high number of new series created over last 24h"
|
||||
description: "The number of created new time series over last 24h is 3x times higher than
|
||||
current number of active series.\n
|
||||
@@ -151,7 +151,7 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/oS7Bi_0Wz?viewPanel=108"
|
||||
dashboard: "{{ $externalURL }}/d/oS7Bi_0Wz?viewPanel=108"
|
||||
summary: "Percentage of slow inserts is more than 5% for the last 15m"
|
||||
description: "High rate of slow inserts may be a sign of resource exhaustion
|
||||
for the current load. It is likely more RAM is needed for optimal handling of the current number of active time series.
|
||||
@@ -164,7 +164,7 @@ groups:
|
||||
severity: warning
|
||||
show_at: dashboard
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/oS7Bi_0Wz?viewPanel=139&var-instance={{ $labels.instance }}"
|
||||
dashboard: "{{ $externalURL }}/d/oS7Bi_0Wz?viewPanel=139&var-instance={{ $labels.instance }}"
|
||||
summary: "Connection between vminsert on {{ $labels.instance }} and vmstorage on {{ $labels.addr }} is saturated"
|
||||
description: "The connection between vminsert (instance {{ $labels.instance }}) and vmstorage (instance {{ $labels.addr }})
|
||||
is saturated by more than 90% and vminsert won't be able to keep up.\n
|
||||
|
||||
@@ -15,7 +15,7 @@ groups:
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/G7Z9GzMGz?viewPanel=49&var-instance={{ $labels.instance }}"
|
||||
dashboard: "{{ $externalURL }}/d/G7Z9GzMGz?viewPanel=49&var-instance={{ $labels.instance }}"
|
||||
summary: "Instance {{ $labels.instance }} is dropping data from persistent queue"
|
||||
description: "Vmagent dropped {{ $value | humanize1024 }} from persistent queue
|
||||
on instance {{ $labels.instance }} for the last 10m."
|
||||
@@ -26,7 +26,7 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/G7Z9GzMGz?viewPanel=79&var-instance={{ $labels.instance }}"
|
||||
dashboard: "{{ $externalURL }}/d/G7Z9GzMGz?viewPanel=79&var-instance={{ $labels.instance }}"
|
||||
summary: "Vmagent is dropping data blocks that are rejected by remote storage"
|
||||
description: "Job \"{{ $labels.job }}\" on instance {{ $labels.instance }} drops the rejected by
|
||||
remote-write server data blocks. Check the logs to find the reason for rejects."
|
||||
@@ -37,7 +37,7 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/G7Z9GzMGz?viewPanel=31&var-instance={{ $labels.instance }}"
|
||||
dashboard: "{{ $externalURL }}/d/G7Z9GzMGz?viewPanel=31&var-instance={{ $labels.instance }}"
|
||||
summary: "Vmagent fails to scrape one or more targets"
|
||||
description: "Job \"{{ $labels.job }}\" on instance {{ $labels.instance }} fails to scrape targets for last 15m"
|
||||
|
||||
@@ -61,7 +61,7 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/G7Z9GzMGz?viewPanel=77&var-instance={{ $labels.instance }}"
|
||||
dashboard: "{{ $externalURL }}/d/G7Z9GzMGz?viewPanel=77&var-instance={{ $labels.instance }}"
|
||||
summary: "Vmagent responds with too many errors on data ingestion protocols"
|
||||
description: "Job \"{{ $labels.job }}\" on instance {{ $labels.instance }} responds with errors to write requests for last 15m."
|
||||
|
||||
@@ -71,7 +71,7 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/G7Z9GzMGz?viewPanel=61&var-instance={{ $labels.instance }}"
|
||||
dashboard: "{{ $externalURL }}/d/G7Z9GzMGz?viewPanel=61&var-instance={{ $labels.instance }}"
|
||||
summary: "Job \"{{ $labels.job }}\" on instance {{ $labels.instance }} fails to push to remote storage"
|
||||
description: "Vmagent fails to push data via remote write protocol to destination \"{{ $labels.url }}\"\n
|
||||
Ensure that destination is up and reachable."
|
||||
@@ -87,7 +87,7 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/G7Z9GzMGz?viewPanel=84&var-instance={{ $labels.instance }}"
|
||||
dashboard: "{{ $externalURL }}/d/G7Z9GzMGz?viewPanel=84&var-instance={{ $labels.instance }}"
|
||||
summary: "Remote write connection from \"{{ $labels.job }}\" (instance {{ $labels.instance }}) to {{ $labels.url }} is saturated"
|
||||
description: "The remote write connection between vmagent \"{{ $labels.job }}\" (instance {{ $labels.instance }}) and destination \"{{ $labels.url }}\"
|
||||
is saturated by more than 90% and vmagent won't be able to keep up.\n
|
||||
@@ -101,7 +101,7 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/G7Z9GzMGz?viewPanel=98&var-instance={{ $labels.instance }}"
|
||||
dashboard: "{{ $externalURL }}/d/G7Z9GzMGz?viewPanel=98&var-instance={{ $labels.instance }}"
|
||||
summary: "Persistent queue writes for instance {{ $labels.instance }} are saturated"
|
||||
description: "Persistent queue writes for vmagent \"{{ $labels.job }}\" (instance {{ $labels.instance }})
|
||||
are saturated by more than 90% and vmagent won't be able to keep up with flushing data on disk.
|
||||
@@ -113,7 +113,7 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/G7Z9GzMGz?viewPanel=99&var-instance={{ $labels.instance }}"
|
||||
dashboard: "{{ $externalURL }}/d/G7Z9GzMGz?viewPanel=99&var-instance={{ $labels.instance }}"
|
||||
summary: "Persistent queue reads for instance {{ $labels.instance }} are saturated"
|
||||
description: "Persistent queue reads for vmagent \"{{ $labels.job }}\" (instance {{ $labels.instance }})
|
||||
are saturated by more than 90% and vmagent won't be able to keep up with reading data from the disk.
|
||||
@@ -124,7 +124,7 @@ groups:
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/G7Z9GzMGz?viewPanel=88&var-instance={{ $labels.instance }}"
|
||||
dashboard: "{{ $externalURL }}/d/G7Z9GzMGz?viewPanel=88&var-instance={{ $labels.instance }}"
|
||||
summary: "Instance {{ $labels.instance }} reached 90% of the limit"
|
||||
description: "Max series limit set via -remoteWrite.maxHourlySeries flag is close to reaching the max value.
|
||||
Then samples for new time series will be dropped instead of sending them to remote storage systems."
|
||||
@@ -134,7 +134,7 @@ groups:
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/G7Z9GzMGz?viewPanel=90&var-instance={{ $labels.instance }}"
|
||||
dashboard: "{{ $externalURL }}/d/G7Z9GzMGz?viewPanel=90&var-instance={{ $labels.instance }}"
|
||||
summary: "Instance {{ $labels.instance }} reached 90% of the limit"
|
||||
description: "Max series limit set via -remoteWrite.maxDailySeries flag is close to reaching the max value.
|
||||
Then samples for new time series will be dropped instead of sending them to remote storage systems."
|
||||
|
||||
@@ -23,7 +23,7 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/LzldHAVnz?viewPanel=13&var-instance={{ $labels.instance }}&var-file={{ $labels.file }}&var-group={{ $labels.group }}"
|
||||
dashboard: "{{ $externalURL }}/d/LzldHAVnz?viewPanel=13&var-instance={{ $labels.instance }}&var-file={{ $labels.file }}&var-group={{ $labels.group }}"
|
||||
summary: "Alerting rules are failing for vmalert instance {{ $labels.instance }}"
|
||||
description: "Alerting rules execution is failing for \"{{ $labels.alertname }}\" from group \"{{ $labels.group }}\" in file \"{{ $labels.file }}\".
|
||||
Check vmalert's logs for detailed error message."
|
||||
@@ -34,7 +34,7 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/LzldHAVnz?viewPanel=30&var-instance={{ $labels.instance }}&var-file={{ $labels.file }}&var-group={{ $labels.group }}"
|
||||
dashboard: "{{ $externalURL }}/d/LzldHAVnz?viewPanel=30&var-instance={{ $labels.instance }}&var-file={{ $labels.file }}&var-group={{ $labels.group }}"
|
||||
summary: "Recording rules are failing for vmalert instance {{ $labels.instance }}"
|
||||
description: "Recording rules execution is failing for \"{{ $labels.recording }}\" from group \"{{ $labels.group }}\" in file \"{{ $labels.file }}\".
|
||||
Check vmalert's logs for detailed error message."
|
||||
@@ -45,7 +45,7 @@ groups:
|
||||
labels:
|
||||
severity: info
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/LzldHAVnz?viewPanel=33&var-file={{ $labels.file }}&var-group={{ $labels.group }}"
|
||||
dashboard: "{{ $externalURL }}/d/LzldHAVnz?viewPanel=33&var-file={{ $labels.file }}&var-group={{ $labels.group }}"
|
||||
summary: "Recording rule {{ $labels.recording }} ({{ $labels.group }}) produces no data"
|
||||
description: "Recording rule \"{{ $labels.recording }}\" from group \"{{ $labels.group }}\ in file \"{{ $labels.file }}\"
|
||||
produces 0 samples over the last 30min. It might be caused by a misconfiguration
|
||||
|
||||
@@ -11,7 +11,7 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/nbuo5Mr4k?viewPanel=10&var-instance={{ $labels.instance }}"
|
||||
dashboard: "{{ $externalURL }}/d/nbuo5Mr4k?viewPanel=10&var-instance={{ $labels.instance }}"
|
||||
summary: "vmauth ({{ $labels.instance }}) reached concurrent requests limit"
|
||||
description: "Possible solutions: increase -maxQueueDuration flag value, increase -maxConcurrentRequests flag value,
|
||||
deploy additional vmauth replicas, check requests latency at backend service.
|
||||
@@ -22,7 +22,7 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/nbuo5Mr4k?viewPanel=10&var-instance={{ $labels.instance }}"
|
||||
dashboard: "{{ $externalURL }}/d/nbuo5Mr4k?viewPanel=10&var-instance={{ $labels.instance }}"
|
||||
summary: "vmauth ({{ $labels.instance }}) has reached concurrent requests limit for username {{ $labels.username }}"
|
||||
description: "Possible solutions: increase -maxQueueDuration flag value, increase -maxConcurrentPerUserRequests flag value,
|
||||
deploy additional vmauth replicas, check requests latency at backend service."
|
||||
@@ -32,7 +32,7 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/nbuo5Mr4k?viewPanel=10&var-instance={{ $labels.instance }}"
|
||||
dashboard: "{{ $externalURL }}/d/nbuo5Mr4k?viewPanel=10&var-instance={{ $labels.instance }}"
|
||||
summary: "vmauth ({{ $labels.instance }}) has reached concurrent requests limit for unauthorized user"
|
||||
description: "Possible solutions: increase -maxQueueDuration flag value, increase -maxConcurrentPerUserRequests flag value,
|
||||
deploy additional vmauth replicas, check requests latency at backend service."
|
||||
@@ -42,7 +42,7 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/nbuo5Mr4k?viewPanel=37&var-instance={{ $labels.instance }}"
|
||||
dashboard: "{{ $externalURL }}/d/nbuo5Mr4k?viewPanel=37&var-instance={{ $labels.instance }}"
|
||||
summary: "Too many errors served for unauthorized user (instance {{ $labels.instance }})"
|
||||
description: "Requests from unauthorized user are receiving errors.
|
||||
Please check the vmauth logs to verify that the configuration is correct and clients are sending valid requests."
|
||||
@@ -52,7 +52,7 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/nbuo5Mr4k?viewPanel=37&var-instance={{ $labels.instance }}"
|
||||
dashboard: "{{ $externalURL }}/d/nbuo5Mr4k?viewPanel=37&var-instance={{ $labels.instance }}"
|
||||
summary: "Too many errors served for user {{ $labels.username }} (instance {{ $labels.instance }})"
|
||||
description: "Requests from user {{ $labels.username }} are receiving errors.
|
||||
Please check the vmauth logs to verify that the configuration is correct and clients are sending valid requests."
|
||||
|
||||
@@ -27,7 +27,7 @@ groups:
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/wNf0q_kZk?viewPanel=53&var-instance={{ $labels.instance }}"
|
||||
dashboard: "{{ $externalURL }}/d/wNf0q_kZk?viewPanel=53&var-instance={{ $labels.instance }}"
|
||||
summary: "Instance {{ $labels.instance }} will run out of disk space soon"
|
||||
description: "Taking into account current ingestion rate, free disk space will be enough only
|
||||
for {{ $value | humanizeDuration }} on instance {{ $labels.instance }}.\n
|
||||
@@ -51,7 +51,7 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/oS7Bi_0Wz?viewPanel=53&var-instance={{ $labels.instance }}"
|
||||
dashboard: "{{ $externalURL }}/d/oS7Bi_0Wz?viewPanel=53&var-instance={{ $labels.instance }}"
|
||||
summary: "Instance {{ $labels.instance }} will become read-only in 3 days"
|
||||
description: "Taking into account current ingestion rate and free disk space
|
||||
instance {{ $labels.instance }} is writable for {{ $value | humanizeDuration }}.\n
|
||||
@@ -68,7 +68,7 @@ groups:
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/wNf0q_kZk?viewPanel=53&var-instance={{ $labels.instance }}"
|
||||
dashboard: "{{ $externalURL }}/d/wNf0q_kZk?viewPanel=53&var-instance={{ $labels.instance }}"
|
||||
summary: "Instance {{ $labels.instance }} (job={{ $labels.job }}) will run out of disk space soon"
|
||||
description: "Disk utilisation on instance {{ $labels.instance }} is more than 80%.\n
|
||||
Having less than 20% of free disk space could cripple merge processes and overall performance.
|
||||
@@ -80,7 +80,7 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/wNf0q_kZk?viewPanel=35&var-instance={{ $labels.instance }}"
|
||||
dashboard: "{{ $externalURL }}/d/wNf0q_kZk?viewPanel=35&var-instance={{ $labels.instance }}"
|
||||
summary: "Too many errors served for path {{ $labels.path }} (instance {{ $labels.instance }})"
|
||||
description: "Requests to path {{ $labels.path }} are receiving errors.
|
||||
Please verify if clients are sending correct requests."
|
||||
@@ -96,7 +96,7 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/wNf0q_kZk?viewPanel=66&var-instance={{ $labels.instance }}"
|
||||
dashboard: "{{ $externalURL }}/d/wNf0q_kZk?viewPanel=66&var-instance={{ $labels.instance }}"
|
||||
summary: "Churn rate is more than 10% on \"{{ $labels.instance }}\" for the last 15m"
|
||||
description: "VM constantly creates new time series on \"{{ $labels.instance }}\".\n
|
||||
This effect is known as Churn Rate.\n
|
||||
@@ -112,7 +112,7 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/wNf0q_kZk?viewPanel=66&var-instance={{ $labels.instance }}"
|
||||
dashboard: "{{ $externalURL }}/d/wNf0q_kZk?viewPanel=66&var-instance={{ $labels.instance }}"
|
||||
summary: "Too high number of new series on \"{{ $labels.instance }}\" created over last 24h"
|
||||
description: "The number of created new time series over last 24h is 3x times higher than
|
||||
current number of active series on \"{{ $labels.instance }}\".\n
|
||||
@@ -131,7 +131,7 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/wNf0q_kZk?viewPanel=68&var-instance={{ $labels.instance }}"
|
||||
dashboard: "{{ $externalURL }}/d/wNf0q_kZk?viewPanel=68&var-instance={{ $labels.instance }}"
|
||||
summary: "Percentage of slow inserts is more than 5% on \"{{ $labels.instance }}\" for the last 15m"
|
||||
description: "High rate of slow inserts on \"{{ $labels.instance }}\" may be a sign of resource exhaustion
|
||||
for the current load. It is likely more RAM is needed for optimal handling of the current number of active time series.
|
||||
|
||||
276
docs/Makefile
@@ -68,17 +68,22 @@ docs-images-to-webp: docs-image
|
||||
-exec sh -c 'cwebp -preset drawing -m 6 -o $$(echo {} | cut -f-1 -d.).webp {} && rm -rf {}' {} \;
|
||||
|
||||
docs-update-vmsingle-flags:
|
||||
(cd /tmp/vm-enterprise-single-node && make victoria-metrics)
|
||||
(cd /tmp/vm-opensource-single-node && make victoria-metrics)
|
||||
(cd /tmp/vm-enterprise-single-node && ./bin/victoria-metrics -help 2>&1) > /tmp/vm-enterprise-single-node/victoria_metrics_enterprise_flags_tmp.md
|
||||
(cd /tmp/vm-opensource-single-node && ./bin/victoria-metrics -help 2>&1) > /tmp/vm-opensource-single-node/victoria_metrics_common_flags_tmp.md
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/victoria_metrics_common_flags.md
|
||||
cat /tmp/vm-opensource-single-node/victoria_metrics_common_flags_tmp.md >> docs/victoriametrics/victoria_metrics_common_flags.md
|
||||
printf -- '```\n' >> docs/victoriametrics/victoria_metrics_common_flags.md
|
||||
ifndef TAG
|
||||
$(error TAG must be provided to update flags in docs)
|
||||
endif
|
||||
git checkout $(TAG) && $(MAKE) victoria-metrics && \
|
||||
./bin/victoria-metrics -help > /tmp/victoria_metrics_common_flags_tmp.md
|
||||
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/victoria_metrics_enterprise_flags.md
|
||||
diff /tmp/vm-enterprise-single-node/victoria_metrics_enterprise_flags_tmp.md /tmp/vm-opensource-single-node/victoria_metrics_common_flags_tmp.md |grep '^<' | sed 's/^< //' >> docs/victoriametrics/victoria_metrics_enterprise_flags.md
|
||||
printf -- '```\n' >> docs/victoriametrics/victoria_metrics_enterprise_flags.md
|
||||
git checkout $(TAG)-enterprise && $(MAKE) victoria-metrics && \
|
||||
./bin/victoria-metrics -help > /tmp/victoria_metrics_enterprise_flags_tmp.md
|
||||
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/victoria_metrics_common_flags.md && \
|
||||
cat /tmp/victoria_metrics_common_flags_tmp.md >> docs/victoriametrics/victoria_metrics_common_flags.md && \
|
||||
printf '```\n' >> docs/victoriametrics/victoria_metrics_common_flags.md
|
||||
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/victoria_metrics_enterprise_flags.md && \
|
||||
diff /tmp/victoria_metrics_enterprise_flags_tmp.md /tmp/victoria_metrics_common_flags_tmp.md |grep '^<' | sed 's/^< //' >> docs/victoriametrics/victoria_metrics_enterprise_flags.md && \
|
||||
printf '```\n' >> docs/victoriametrics/victoria_metrics_enterprise_flags.md
|
||||
|
||||
# replace tabs in output with one space
|
||||
sed -i 's/\t/ /g' docs/victoriametrics/victoria_metrics_common_flags.md
|
||||
@@ -92,19 +97,21 @@ docs-update-vmsingle-flags:
|
||||
sed -i '/The maximum number of concurrent goroutines to work with files;/ s/(default [0-9]\+)/(default fsutil.getDefaultConcurrency())/' docs/victoriametrics/victoria_metrics_common_flags.md
|
||||
|
||||
docs-update-vmauth-flags:
|
||||
# ---- vmauth
|
||||
(cd /tmp/vm-enterprise-single-node && make vmauth)
|
||||
(cd /tmp/vm-opensource-single-node && make vmauth)
|
||||
(cd /tmp/vm-enterprise-single-node && ./bin/vmauth -help 2>&1) > /tmp/vm-enterprise-single-node/vmauth_enterprise_flags_tmp.md
|
||||
(cd /tmp/vm-opensource-single-node && ./bin/vmauth -help 2>&1) > /tmp/vm-opensource-single-node/vmauth_common_flags_tmp.md
|
||||
ifndef TAG
|
||||
$(error TAG must be provided to update flags in docs)
|
||||
endif
|
||||
git checkout $(TAG) && $(MAKE) vmauth && \
|
||||
./bin/vmauth -help > /tmp/vmauth_common_flags_tmp.md
|
||||
git checkout $(TAG)-enterprise && $(MAKE) vmauth && \
|
||||
./bin/vmauth -help > /tmp/vmauth_enterprise_flags_tmp.md
|
||||
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vmauth_common_flags.md
|
||||
cat /tmp/vm-opensource-single-node/vmauth_common_flags_tmp.md >> docs/victoriametrics/vmauth_common_flags.md
|
||||
printf -- '```\n' >> docs/victoriametrics/vmauth_common_flags.md
|
||||
cat /tmp/vmauth_common_flags_tmp.md >> docs/victoriametrics/vmauth_common_flags.md
|
||||
printf '```\n' >> docs/victoriametrics/vmauth_common_flags.md
|
||||
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vmauth_enterprise_flags.md
|
||||
diff /tmp/vm-enterprise-single-node/vmauth_enterprise_flags_tmp.md /tmp/vm-opensource-single-node/vmauth_common_flags_tmp.md |grep '^<' | sed 's/^< //' >> docs/victoriametrics/vmauth_enterprise_flags.md
|
||||
printf -- '```' >> docs/victoriametrics/vmauth_enterprise_flags.md
|
||||
diff /tmp/vmauth_enterprise_flags_tmp.md /tmp/vmauth_common_flags_tmp.md |grep '^<' | sed 's/^< //' >> docs/victoriametrics/vmauth_enterprise_flags.md
|
||||
printf '```' >> docs/victoriametrics/vmauth_enterprise_flags.md
|
||||
|
||||
# replace tabs in output with one space
|
||||
sed -i 's/\t/ /g' docs/victoriametrics/vmauth_common_flags.md
|
||||
@@ -115,17 +122,22 @@ docs-update-vmauth-flags:
|
||||
sed -i '/The maximum number of concurrent goroutines to work with files;/ s/(default [0-9]\+)/(default fsutil.getDefaultConcurrency())/' docs/victoriametrics/vmauth_common_flags.md
|
||||
|
||||
docs-update-vmagent-flags:
|
||||
(cd /tmp/vm-enterprise-single-node && make vmagent)
|
||||
(cd /tmp/vm-opensource-single-node && make vmagent)
|
||||
(cd /tmp/vm-enterprise-single-node && ./bin/vmagent -help 2>&1) > /tmp/vm-enterprise-single-node/vmagent_enterprise_flags_tmp.md
|
||||
(cd /tmp/vm-opensource-single-node && ./bin/vmagent -help 2>&1) > /tmp/vm-opensource-single-node/vmagent_common_flags_tmp.md
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vmagent_common_flags.md
|
||||
cat /tmp/vm-opensource-single-node/vmagent_common_flags_tmp.md >> docs/victoriametrics/vmagent_common_flags.md
|
||||
printf -- '```\n' >> docs/victoriametrics/vmagent_common_flags.md
|
||||
ifndef TAG
|
||||
$(error TAG must be provided to update flags in docs)
|
||||
endif
|
||||
git checkout $(TAG) && $(MAKE) vmagent && \
|
||||
./bin/vmagent -help > /tmp/vmagent_common_flags_tmp.md
|
||||
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vmagent_enterprise_flags.md
|
||||
diff /tmp/vm-enterprise-single-node/vmagent_enterprise_flags_tmp.md /tmp/vm-opensource-single-node/vmagent_common_flags_tmp.md |grep '^<' | sed 's/^< //' >> docs/victoriametrics/vmagent_enterprise_flags.md
|
||||
printf -- '```\n' >> docs/victoriametrics/vmagent_enterprise_flags.md
|
||||
git checkout $(TAG)-enterprise && $(MAKE) vmagent && \
|
||||
./bin/vmagent -help > /tmp/vmagent_enterprise_flags_tmp.md
|
||||
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vmagent_common_flags.md && \
|
||||
cat /tmp/vmagent_common_flags_tmp.md >> docs/victoriametrics/vmagent_common_flags.md && \
|
||||
printf '```\n' >> docs/victoriametrics/vmagent_common_flags.md
|
||||
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vmagent_enterprise_flags.md && \
|
||||
diff /tmp/vmagent_enterprise_flags_tmp.md /tmp/vmagent_common_flags_tmp.md |grep '^<' | sed 's/^< //' >> docs/victoriametrics/vmagent_enterprise_flags.md && \
|
||||
printf '```\n' >> docs/victoriametrics/vmagent_enterprise_flags.md
|
||||
|
||||
# replace tabs in output with one space
|
||||
sed -i 's/\t/ /g' docs/victoriametrics/vmagent_common_flags.md
|
||||
@@ -138,18 +150,22 @@ docs-update-vmagent-flags:
|
||||
sed -i '/The maximum number of concurrent goroutines to work with files;/ s/(default [0-9]\+)/(default fsutil.getDefaultConcurrency())/' docs/victoriametrics/vmagent_common_flags.md
|
||||
|
||||
docs-update-vmalert-flags:
|
||||
(cd /tmp/vm-enterprise-single-node && make vmalert)
|
||||
(cd /tmp/vm-opensource-single-node && make vmalert)
|
||||
(cd /tmp/vm-enterprise-single-node && ./bin/vmalert -help 2>&1) > /tmp/vm-enterprise-single-node/vmalert_enterprise_flags_tmp.md
|
||||
(cd /tmp/vm-opensource-single-node && ./bin/vmalert -help 2>&1) > /tmp/vm-opensource-single-node/vmalert_common_flags_tmp.md
|
||||
ifndef TAG
|
||||
$(error TAG must be provided to update flags in docs)
|
||||
endif
|
||||
git checkout $(TAG) && $(MAKE) vmalert && \
|
||||
./bin/vmalert -help > /tmp/vmalert_common_flags_tmp.md
|
||||
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vmalert_common_flags.md
|
||||
cat /tmp/vm-opensource-single-node/vmalert_common_flags_tmp.md >> docs/victoriametrics/vmalert_common_flags.md
|
||||
printf -- '```\n' >> docs/victoriametrics/vmalert_common_flags.md
|
||||
git checkout $(TAG)-enterprise && $(MAKE) vmalert && \
|
||||
./bin/vmalert -help > /tmp/vmalert_enterprise_flags_tmp.md
|
||||
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vmalert_enterprise_flags.md
|
||||
diff /tmp/vm-enterprise-single-node/vmalert_enterprise_flags_tmp.md /tmp/vm-opensource-single-node/vmalert_common_flags_tmp.md |grep '^<' | sed 's/^< //' >> docs/victoriametrics/vmalert_enterprise_flags.md
|
||||
printf -- '```' >> docs/victoriametrics/vmalert_enterprise_flags.md
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vmalert_common_flags.md && \
|
||||
cat /tmp/vmalert_common_flags_tmp.md >> docs/victoriametrics/vmalert_common_flags.md && \
|
||||
printf '```\n' >> docs/victoriametrics/vmalert_common_flags.md
|
||||
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vmalert_enterprise_flags.md && \
|
||||
diff /tmp/vmalert_enterprise_flags_tmp.md /tmp/vmalert_common_flags_tmp.md |grep '^<' | sed 's/^< //' >> docs/victoriametrics/vmalert_enterprise_flags.md && \
|
||||
printf '```' >> docs/victoriametrics/vmalert_enterprise_flags.md
|
||||
|
||||
# replace tabs in output with one space
|
||||
sed -i 's/\t/ /g' docs/victoriametrics/vmalert_common_flags.md
|
||||
@@ -161,18 +177,22 @@ docs-update-vmalert-flags:
|
||||
sed -i '/The maximum number of concurrent goroutines to work with files;/ s/(default [0-9]\+)/(default fsutil.getDefaultConcurrency())/' docs/victoriametrics/vmalert_common_flags.md
|
||||
|
||||
docs-update-vmselect-flags:
|
||||
(cd /tmp/vm-enterprise-cluster && make vmselect)
|
||||
(cd /tmp/vm-opensource-cluster && make vmselect)
|
||||
(cd /tmp/vm-enterprise-cluster && ./bin/vmselect -help 2>&1) > /tmp/vm-enterprise-cluster/vmselect_enterprise_flags_tmp.md
|
||||
(cd /tmp/vm-opensource-cluster && ./bin/vmselect -help 2>&1) > /tmp/vm-opensource-cluster/vmselect_common_flags_tmp.md
|
||||
ifndef TAG
|
||||
$(error TAG must be provided to update flags in docs)
|
||||
endif
|
||||
git checkout $(TAG)-cluster && $(MAKE) vmselect && \
|
||||
./bin/vmselect -help > /tmp/vmselect_common_flags_tmp.md
|
||||
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vmselect_common_flags.md
|
||||
cat /tmp/vm-opensource-cluster/vmselect_common_flags_tmp.md >> docs/victoriametrics/vmselect_common_flags.md
|
||||
printf -- '```\n' >> docs/victoriametrics/vmselect_common_flags.md
|
||||
git checkout $(TAG)-enterprise-cluster && $(MAKE) vmselect && \
|
||||
./bin/vmselect -help > /tmp/vmselect_enterprise_flags_tmp.md
|
||||
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vmselect_enterprise_flags.md
|
||||
diff /tmp/vm-enterprise-cluster/vmselect_enterprise_flags_tmp.md /tmp/vm-opensource-cluster/vmselect_common_flags_tmp.md |grep '^<' | sed 's/^< //' >> docs/victoriametrics/vmselect_enterprise_flags.md
|
||||
printf -- '```' >> docs/victoriametrics/vmselect_enterprise_flags.md
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vmselect_common_flags.md && \
|
||||
cat /tmp/vmselect_common_flags_tmp.md >> docs/victoriametrics/vmselect_common_flags.md && \
|
||||
printf '```\n' >> docs/victoriametrics/vmselect_common_flags.md
|
||||
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vmselect_enterprise_flags.md && \
|
||||
diff /tmp/vmselect_enterprise_flags_tmp.md /tmp/vmselect_common_flags_tmp.md |grep '^<' | sed 's/^< //' >> docs/victoriametrics/vmselect_enterprise_flags.md && \
|
||||
printf '```' >> docs/victoriametrics/vmselect_enterprise_flags.md
|
||||
|
||||
# replace tabs in output with one space
|
||||
sed -i 's/\t/ /g' docs/victoriametrics/vmselect_common_flags.md
|
||||
@@ -186,18 +206,22 @@ docs-update-vmselect-flags:
|
||||
sed -i '/The maximum number of concurrent goroutines to work with files;/ s/(default [0-9]\+)/(default fsutil.getDefaultConcurrency())/' docs/victoriametrics/vmselect_common_flags.md
|
||||
|
||||
docs-update-vminsert-flags:
|
||||
(cd /tmp/vm-enterprise-cluster && make vminsert)
|
||||
(cd /tmp/vm-opensource-cluster && make vminsert)
|
||||
(cd /tmp/vm-enterprise-cluster && ./bin/vminsert -help 2>&1) > /tmp/vm-enterprise-cluster/vminsert_enterprise_flags_tmp.md
|
||||
(cd /tmp/vm-opensource-cluster && ./bin/vminsert -help 2>&1) > /tmp/vm-opensource-cluster/vminsert_common_flags_tmp.md
|
||||
ifndef TAG
|
||||
$(error TAG must be provided to update flags in docs)
|
||||
endif
|
||||
git checkout $(TAG)-cluster && $(MAKE) vminsert && \
|
||||
./bin/vminsert -help > /tmp/vminsert_common_flags_tmp.md
|
||||
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vminsert_common_flags.md
|
||||
cat /tmp/vm-opensource-cluster/vminsert_common_flags_tmp.md >> docs/victoriametrics/vminsert_common_flags.md
|
||||
printf -- '```\n' >> docs/victoriametrics/vminsert_common_flags.md
|
||||
git checkout $(TAG)-enterprise-cluster && $(MAKE) vminsert && \
|
||||
./bin/vminsert -help > /tmp/vminsert_enterprise_flags_tmp.md
|
||||
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vminsert_enterprise_flags.md
|
||||
diff /tmp/vm-enterprise-cluster/vminsert_enterprise_flags_tmp.md /tmp/vm-opensource-cluster/vminsert_common_flags_tmp.md |grep '^<' | sed 's/^< //' >> docs/victoriametrics/vminsert_enterprise_flags.md
|
||||
printf -- '```' >> docs/victoriametrics/vminsert_enterprise_flags.md
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vminsert_common_flags.md && \
|
||||
cat /tmp/vminsert_common_flags_tmp.md >> docs/victoriametrics/vminsert_common_flags.md && \
|
||||
printf '```\n' >> docs/victoriametrics/vminsert_common_flags.md
|
||||
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vminsert_enterprise_flags.md && \
|
||||
diff /tmp/vminsert_enterprise_flags_tmp.md /tmp/vminsert_common_flags_tmp.md |grep '^<' | sed 's/^< //' >> docs/victoriametrics/vminsert_enterprise_flags.md && \
|
||||
printf '```' >> docs/victoriametrics/vminsert_enterprise_flags.md
|
||||
|
||||
# replace tabs in output with one space
|
||||
sed -i 's/\t/ /g' docs/victoriametrics/vminsert_common_flags.md
|
||||
@@ -218,18 +242,22 @@ docs-update-vminsert-flags:
|
||||
sed -i '/The maximum number of concurrent goroutines to work with files;/ s/(default [0-9]\+)/(default fsutil.getDefaultConcurrency())/' docs/victoriametrics/vminsert_common_flags.md
|
||||
|
||||
docs-update-vmstorage-flags:
|
||||
(cd /tmp/vm-enterprise-cluster && make vmstorage)
|
||||
(cd /tmp/vm-opensource-cluster && make vmstorage)
|
||||
(cd /tmp/vm-enterprise-cluster && ./bin/vmstorage -help 2>&1) > /tmp/vm-enterprise-cluster/vmstorage_enterprise_flags_tmp.md
|
||||
(cd /tmp/vm-opensource-cluster && ./bin/vmstorage -help 2>&1) > /tmp/vm-opensource-cluster/vmstorage_common_flags_tmp.md
|
||||
ifndef TAG
|
||||
$(error TAG must be provided to update flags in docs)
|
||||
endif
|
||||
git checkout $(TAG)-cluster && $(MAKE) vmstorage && \
|
||||
./bin/vmstorage -help > /tmp/vmstorage_common_flags_tmp.md
|
||||
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vmstorage_common_flags.md
|
||||
cat /tmp/vm-opensource-cluster/vmstorage_common_flags_tmp.md >> docs/victoriametrics/vmstorage_common_flags.md
|
||||
printf -- '```\n' >> docs/victoriametrics/vmstorage_common_flags.md
|
||||
git checkout $(TAG)-enterprise-cluster && $(MAKE) vmstorage && \
|
||||
./bin/vmstorage -help > /tmp/vmstorage_enterprise_flags_tmp.md
|
||||
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vmstorage_enterprise_flags.md
|
||||
diff /tmp/vm-enterprise-cluster/vmstorage_enterprise_flags_tmp.md /tmp/vm-opensource-cluster/vmstorage_common_flags_tmp.md |grep '^<' | sed 's/^< //' >> docs/victoriametrics/vmstorage_enterprise_flags.md
|
||||
printf -- '```' >> docs/victoriametrics/vmstorage_enterprise_flags.md
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vmstorage_common_flags.md && \
|
||||
cat /tmp/vmstorage_common_flags_tmp.md >> docs/victoriametrics/vmstorage_common_flags.md && \
|
||||
printf '```\n' >> docs/victoriametrics/vmstorage_common_flags.md
|
||||
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vmstorage_enterprise_flags.md && \
|
||||
diff /tmp/vmstorage_enterprise_flags_tmp.md /tmp/vmstorage_common_flags_tmp.md |grep '^<' | sed 's/^< //' >> docs/victoriametrics/vmstorage_enterprise_flags.md && \
|
||||
printf '```' >> docs/victoriametrics/vmstorage_enterprise_flags.md
|
||||
|
||||
# replace tabs in output with one space
|
||||
sed -i 's/\t/ /g' docs/victoriametrics/vmstorage_common_flags.md
|
||||
@@ -242,37 +270,40 @@ docs-update-vmstorage-flags:
|
||||
sed -i '/The maximum number of concurrent goroutines to work with files;/ s/(default [0-9]\+)/(default fsutil.getDefaultConcurrency())/' docs/victoriametrics/vmstorage_common_flags.md
|
||||
|
||||
docs-update-vmctl-flags:
|
||||
(cd /tmp/vm-opensource-single-node && make vmctl)
|
||||
(cd /tmp/vm-opensource-single-node && ./bin/vmctl -help 2>&1) > /tmp/vm-opensource-single-node/vmctl_flags_tmp.md
|
||||
(cd /tmp/vm-opensource-single-node && ./bin/vmctl opentsdb -help 2>&1) > /tmp/vm-opensource-single-node/vmctl_opentsdb_flags_tmp.md
|
||||
(cd /tmp/vm-opensource-single-node && ./bin/vmctl influx -help 2>&1) > /tmp/vm-opensource-single-node/vmctl_influx_flags_tmp.md
|
||||
(cd /tmp/vm-opensource-single-node && ./bin/vmctl remote-read -help 2>&1) > /tmp/vm-opensource-single-node/vmctl_remote-read_flags_tmp.md
|
||||
(cd /tmp/vm-opensource-single-node && ./bin/vmctl prometheus -help 2>&1) > /tmp/vm-opensource-single-node/vmctl_prometheus_flags_tmp.md
|
||||
(cd /tmp/vm-opensource-single-node && ./bin/vmctl vm-native -help 2>&1) > /tmp/vm-opensource-single-node/vmctl_vm-native_flags_tmp.md
|
||||
ifndef TAG
|
||||
$(error TAG must be provided to update flags in docs)
|
||||
endif
|
||||
git checkout $(TAG) && $(MAKE) vmctl && \
|
||||
./bin/vmctl -help > /tmp/vmctl_flags_tmp.md && \
|
||||
./bin/vmctl opentsdb -help > /tmp/vmctl_opentsdb_flags_tmp.md && \
|
||||
./bin/vmctl influx -help > /tmp/vmctl_influx_flags_tmp.md && \
|
||||
./bin/vmctl remote-read -help > /tmp/vmctl_remote-read_flags_tmp.md && \
|
||||
./bin/vmctl prometheus -help > /tmp/vmctl_prometheus_flags_tmp.md && \
|
||||
./bin/vmctl vm-native -help > /tmp/vmctl_vm-native_flags_tmp.md
|
||||
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vmctl/vmctl_flags.md
|
||||
cat /tmp/vm-opensource-single-node/vmctl_flags_tmp.md >> docs/victoriametrics/vmctl/vmctl_flags.md
|
||||
printf -- '```\n' >> docs/victoriametrics/vmctl/vmctl_flags.md
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vmctl/vmctl_flags.md && \
|
||||
cat /tmp/vmctl_flags_tmp.md >> docs/victoriametrics/vmctl/vmctl_flags.md && \
|
||||
printf '```\n' >> docs/victoriametrics/vmctl/vmctl_flags.md
|
||||
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vmctl/vmctl_opentsdb_flags.md
|
||||
cat /tmp/vm-opensource-single-node/vmctl_opentsdb_flags_tmp.md >> docs/victoriametrics/vmctl/vmctl_opentsdb_flags.md
|
||||
printf -- '```\n' >> docs/victoriametrics/vmctl/vmctl_opentsdb_flags.md
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vmctl/vmctl_opentsdb_flags.md && \
|
||||
cat /tmp/vmctl_opentsdb_flags_tmp.md >> docs/victoriametrics/vmctl/vmctl_opentsdb_flags.md && \
|
||||
printf '```\n' >> docs/victoriametrics/vmctl/vmctl_opentsdb_flags.md
|
||||
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vmctl/vmctl_influx_flags.md
|
||||
cat /tmp/vm-opensource-single-node/vmctl_influx_flags_tmp.md >> docs/victoriametrics/vmctl/vmctl_influx_flags.md
|
||||
printf -- '```\n' >> docs/victoriametrics/vmctl/vmctl_influx_flags.md
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vmctl/vmctl_influx_flags.md && \
|
||||
cat /tmp/vmctl_influx_flags_tmp.md >> docs/victoriametrics/vmctl/vmctl_influx_flags.md && \
|
||||
printf '```\n' >> docs/victoriametrics/vmctl/vmctl_influx_flags.md
|
||||
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vmctl/vmctl_remote-read_flags.md
|
||||
cat /tmp/vm-opensource-single-node/vmctl_remote-read_flags_tmp.md >> docs/victoriametrics/vmctl/vmctl_remote-read_flags.md
|
||||
printf -- '```\n' >> docs/victoriametrics/vmctl/vmctl_remote-read_flags.md
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vmctl/vmctl_remote-read_flags.md && \
|
||||
cat /tmp/vmctl_remote-read_flags_tmp.md >> docs/victoriametrics/vmctl/vmctl_remote-read_flags.md && \
|
||||
printf '```\n' >> docs/victoriametrics/vmctl/vmctl_remote-read_flags.md
|
||||
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vmctl/vmctl_prometheus_flags.md
|
||||
cat /tmp/vm-opensource-single-node/vmctl_prometheus_flags_tmp.md >> docs/victoriametrics/vmctl/vmctl_prometheus_flags.md
|
||||
printf -- '```\n' >> docs/victoriametrics/vmctl/vmctl_prometheus_flags.md
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vmctl/vmctl_prometheus_flags.md && \
|
||||
cat /tmp/vmctl_prometheus_flags_tmp.md >> docs/victoriametrics/vmctl/vmctl_prometheus_flags.md && \
|
||||
printf '```\n' >> docs/victoriametrics/vmctl/vmctl_prometheus_flags.md
|
||||
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vmctl/vmctl_vm-native_flags.md
|
||||
cat /tmp/vm-opensource-single-node/vmctl_vm-native_flags_tmp.md >> docs/victoriametrics/vmctl/vmctl_vm-native_flags.md
|
||||
printf -- '```\n' >> docs/victoriametrics/vmctl/vmctl_vm-native_flags.md
|
||||
echo "$$FLAGS_HEADER" > docs/victoriametrics/vmctl/vmctl_vm-native_flags.md && \
|
||||
cat /tmp/vmctl_vm-native_flags_tmp.md >> docs/victoriametrics/vmctl/vmctl_vm-native_flags.md && \
|
||||
printf '```\n' >> docs/victoriametrics/vmctl/vmctl_vm-native_flags.md
|
||||
|
||||
# remove Total time line from all vmctl flag files to reduce diffs noise
|
||||
sed -i '/Total time:/d' docs/victoriametrics/vmctl/vmctl_flags.md
|
||||
@@ -285,49 +316,30 @@ docs-update-vmctl-flags:
|
||||
# remove Version line and the actual version line from vmctl_flags.md to reduce diffs noise
|
||||
sed -i '/^VERSION:/,+1d' docs/victoriametrics/vmctl/vmctl_flags.md
|
||||
|
||||
# docs-update-flags updates flags in the documentation using the actual binaries compiled
|
||||
# from the latest enterprise-single-node and enterprise-cluster branches (hardcoded for now).
|
||||
# docs-update-flags updates flags in the documentation
|
||||
# using the actual binaries compiled from the provided $TAG.
|
||||
# The command also normalizes the output a bit.
|
||||
#
|
||||
# The command does not replace the need to manually update flags in the documentation when
|
||||
# new flags are added or existing flags are updated. It just helps to keep the documentation
|
||||
# in sync with the actual binaries.
|
||||
# There is no need to update flags manually while working on change in the code.
|
||||
# The flags will be synced when a new release tag is cut.
|
||||
#
|
||||
# It can be run from any branch.
|
||||
# All work happens inside temporary directories under /tmp.
|
||||
# The script checks out the required branch, builds the binaries, and updates the documentation.
|
||||
# The current Git repository is not touched.
|
||||
# The command can be run from any branch.
|
||||
# The script checks out the required $TAG, builds the binaries, and updates the documentation.
|
||||
docs-update-flags:
|
||||
ifndef TAG
|
||||
$(error TAG must be provided to update flags in docs)
|
||||
endif
|
||||
# Note for MacOS users:
|
||||
# You need to install gnu versions of sed and awk inorder fo inplace editing to work
|
||||
# You need to install gnu versions of sed and awk to enable in-place editing
|
||||
# Install using: brew install gnu-sed gawk
|
||||
# Add tools to PATH see how in `brew info gnu-sed` and `brew info gawk
|
||||
|
||||
git fetch enterprise
|
||||
git fetch opensource
|
||||
|
||||
rm -rf /tmp/vm-enterprise-cluster
|
||||
git worktree remove /tmp/vm-enterprise-cluster || true
|
||||
git worktree add /tmp/vm-enterprise-cluster enterprise/enterprise-cluster
|
||||
|
||||
rm -rf /tmp/vm-enterprise-single-node
|
||||
git worktree remove /tmp/vm-enterprise-single-node || true
|
||||
git worktree add /tmp/vm-enterprise-single-node enterprise/enterprise-single-node
|
||||
|
||||
|
||||
rm -rf /tmp/vm-opensource-cluster
|
||||
git worktree remove /tmp/vm-opensource-cluster || true
|
||||
git worktree add /tmp/vm-opensource-cluster opensource/cluster
|
||||
|
||||
rm -rf /tmp/vm-opensource-single-node
|
||||
git worktree remove /tmp/vm-opensource-single-node || true
|
||||
git worktree add /tmp/vm-opensource-single-node opensource/master
|
||||
|
||||
make docs-update-vmctl-flags
|
||||
make docs-update-vmsingle-flags
|
||||
make docs-update-vmalert-flags
|
||||
make docs-update-vmauth-flags
|
||||
make docs-update-vmagent-flags
|
||||
make docs-update-vmselect-flags
|
||||
make docs-update-vminsert-flags
|
||||
make docs-update-vmstorage-flags
|
||||
orig_branch=$$(git rev-parse --abbrev-ref HEAD); \
|
||||
$(MAKE) docs-update-vmctl-flags && git checkout "$$orig_branch" && \
|
||||
$(MAKE) docs-update-vmsingle-flags && git checkout "$$orig_branch" && \
|
||||
$(MAKE) docs-update-vmalert-flags && git checkout "$$orig_branch" && \
|
||||
$(MAKE) docs-update-vmauth-flags && git checkout "$$orig_branch" && \
|
||||
$(MAKE) docs-update-vmagent-flags && git checkout "$$orig_branch" && \
|
||||
$(MAKE) docs-update-vmselect-flags && git checkout "$$orig_branch" && \
|
||||
$(MAKE) docs-update-vminsert-flags && git checkout "$$orig_branch" && \
|
||||
$(MAKE) docs-update-vmstorage-flags && git checkout "$$orig_branch"
|
||||
@@ -136,21 +136,21 @@ models:
|
||||
|
||||
Here's how default (backward-compatible) behavior looks like - anomalies will be tracked in `both` directions (`y > yhat` or `y < yhat`). This is useful when there is no domain expertise to filter the required direction.
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
When set to `above_expected`, anomalies are tracked only when `y > yhat`.
|
||||
|
||||
*Example metrics*: Error rate, response time, page load time, number of failed transactions - metrics where *lower values are better*, so **higher** values are typically tracked.
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
When set to `below_expected`, anomalies are tracked only when `y < yhat`.
|
||||
|
||||
*Example metrics*: Service Level Agreement (SLA) compliance, conversion rate, Customer Satisfaction Score (CSAT) - metrics where *higher values are better*, so **lower** values are typically tracked.
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
Config with a split example:
|
||||
@@ -199,13 +199,13 @@ reader:
|
||||
|
||||
Visualizations below demonstrate this concept; the green zone defined as the `[yhat - min_dev_from_expected, yhat + min_dev_from_expected]` range excludes actual data points (`y`) from generating anomaly scores if they fall within that range.
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
Example config of how to use this param based on query results:
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
@@ -6,32 +6,55 @@ build:
|
||||
sitemap:
|
||||
disable: true
|
||||
---
|
||||
**The guide covers:**
|
||||
|
||||
* The setup of a [VM Operator](https://github.com/VictoriaMetrics/helm-charts/tree/master/charts/victoria-metrics-operator) via Helm in [Kubernetes](https://kubernetes.io/) with Helm charts.
|
||||
* The setup of a [VictoriaMetrics Cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/) via [VM Operator](https://github.com/VictoriaMetrics/helm-charts/tree/master/charts/victoria-metrics-operator).
|
||||
* How to add CRD for a [VictoriaMetrics Cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/) via [VM Operator](https://github.com/VictoriaMetrics/helm-charts/tree/master/charts/victoria-metrics-operator).
|
||||
* How to visualize stored data
|
||||
* How to store metrics in [VictoriaMetrics](https://victoriametrics.com)
|
||||
The [VictoriaMetrics Kubernetes Operator](https://docs.victoriametrics.com/operator/) simplifies deploying VictoriaMetrics Stack components on Kubernetes or OpenShift using declarative YAML [custom resources](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/).
|
||||
|
||||
By the end of this guide, you will be able to:
|
||||
|
||||
- Install and configure [VictoriaMetrics cluster](https://docs.victoriametrics.com/helm/victoria-logs-cluster/) using the Operator.
|
||||
- Scrape metrics from Kubernetes components.
|
||||
- Store metrics in [VictoriaMetrics](https://victoriametrics.com) time-series database.
|
||||
- Visualize metrics in Grafana.
|
||||
|
||||
**Preconditions**
|
||||
|
||||
* [Kubernetes cluster 1.20.9-gke.1001](https://cloud.google.com/kubernetes-engine). We use a GKE cluster from [GCP](https://cloud.google.com/) but this guide also applies to any Kubernetes cluster. For example, [Amazon EKS](https://aws.amazon.com/ru/eks/).
|
||||
* [Helm 3](https://helm.sh/docs/intro/install).
|
||||
* [kubectl 1.21+](https://kubernetes.io/docs/tasks/tools/install-kubectl).
|
||||
- A [Kubernetes GKE cluster 1.33](https://cloud.google.com/kubernetes-engine) or later
|
||||
- [Helm 4.1](https://helm.sh/docs/intro/install) or later
|
||||
- [kubectl 1.34](https://kubernetes.io/docs/tasks/tools/install-kubectl) or later
|
||||
|
||||
> [!NOTE] Tip
|
||||
> We use a GKE cluster from [GCP](https://cloud.google.com/), but this guide can also be applied to any Kubernetes cluster, such as [Amazon EKS](https://aws.amazon.com/ru/eks/) or an on-premises cluster.
|
||||
|
||||
## 1. VictoriaMetrics Helm repository
|
||||
|
||||
See how to work with a [VictoriaMetrics Helm repository in previous guide](https://docs.victoriametrics.com/guides/k8s-monitoring-via-vm-cluster/#1-victoriametrics-helm-repository).
|
||||
To start, add the VictoriaMetrics Helm repository with the following commands:
|
||||
|
||||
```sh
|
||||
helm repo add vm https://victoriametrics.github.io/helm-charts/
|
||||
helm repo update
|
||||
```
|
||||
|
||||
To verify that everything is set up correctly, you may run this command:
|
||||
|
||||
```sh
|
||||
helm search repo vm/
|
||||
```
|
||||
|
||||
You should see a list similar to this:
|
||||
|
||||
```text
|
||||
NAME CHART VERSION APP VERSION DESCRIPTION
|
||||
vm/victoria-metrics-operator 0.58.1 v0.67.0 VictoriaMetrics Operator
|
||||
vm/victoria-metrics-operator-crds 0.7.0 v0.67.0 VictoriaMetrics Operator CRDs
|
||||
...(list continues)...
|
||||
```
|
||||
|
||||
## 2. Install the VM Operator from the Helm chart
|
||||
|
||||
|
||||
```sh
|
||||
helm install vmoperator vm/victoria-metrics-operator
|
||||
```
|
||||
|
||||
|
||||
The expected output is:
|
||||
|
||||
```sh
|
||||
@@ -51,12 +74,12 @@ See "Getting started guide for VM Operator" on https://docs.victoriametrics.com/
|
||||
|
||||
Run the following command to check that VM Operator is up and running:
|
||||
|
||||
|
||||
```sh
|
||||
kubectl --namespace default get pods -l "app.kubernetes.io/instance=vmoperator"
|
||||
kubectl get pods -l "app.kubernetes.io/instance=vmoperator"
|
||||
```
|
||||
|
||||
The expected output:
|
||||
Wait until `STATUS=Running` and `Ready=1/1`, like this:
|
||||
|
||||
```sh
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
vmoperator-victoria-metrics-operator-67cff44cd6-s47n6 1/1 Running 0 77s
|
||||
@@ -64,21 +87,24 @@ vmoperator-victoria-metrics-operator-67cff44cd6-s47n6 1/1 Running 0
|
||||
|
||||
## 3. Install VictoriaMetrics Cluster
|
||||
|
||||
> For this example we will use default value for `name: example-vmcluster-persistent`. Change it value up to your needs.
|
||||
> [!NOTE]
|
||||
> For this example, we use the default name for the cluster (`name: example-vmcluster-persistent`). Change the name to suit your needs.
|
||||
|
||||
Run the following command to install [VictoriaMetrics Cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/) via [VM Operator](https://github.com/VictoriaMetrics/helm-charts/tree/master/charts/victoria-metrics-operator):
|
||||
First, create a YAML file to configure the deployment of VictoriaMetrics cluster version:
|
||||
|
||||
<p id="example-cluster-config"></p>
|
||||
|
||||
```sh
|
||||
cat << EOF | kubectl apply -f -
|
||||
cat << EOF > vmcluster-config.yml
|
||||
apiVersion: operator.victoriametrics.com/v1beta1
|
||||
kind: VMCluster
|
||||
metadata:
|
||||
# define the name of the VM cluster
|
||||
name: example-vmcluster-persistent
|
||||
spec:
|
||||
# Add fields here
|
||||
# define retention period (i.e., 12 months)
|
||||
retentionPeriod: "12"
|
||||
# define the number of pods for each of the services in the VM cluster
|
||||
vmstorage:
|
||||
replicaCount: 2
|
||||
vmselect:
|
||||
@@ -88,25 +114,41 @@ spec:
|
||||
EOF
|
||||
```
|
||||
|
||||
The expected output:
|
||||
Let's break down the main elements of the config file:
|
||||
|
||||
| Field | Purpose | Example |
|
||||
| --------------------------- | ----------------- | ---------------------------- |
|
||||
| `metadata: name` | Cluster name | example-vmcluster-persistent |
|
||||
| `spec: retentionPeriod` | Metrics retention | "12" (months) |
|
||||
| `spec: vmstorage: replicaCount` | vmstorage replicas | 2 |
|
||||
| `spec: vmselect: replicaCount` | vmselect replicas | 2 |
|
||||
| `spec: vminsert: replicaCount` | vminsert replicas | 2 |
|
||||
|
||||
> [!NOTE] Tip
|
||||
> A VictoriaMetrics cluster runs [three services](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#architecture-overview): `vmstorage`, `vminsert`, and `vmselect`. You can independently customize the number of replicas for each service.
|
||||
|
||||
Once you have defined the name, retention period, and number of replicas for your cluster, run the following command to deploy the VictoriaMetrics cluster in the default namespace:
|
||||
|
||||
```sh
|
||||
kubectl apply -f vmcluster-config.yml
|
||||
```
|
||||
|
||||
The command should output something like this:
|
||||
|
||||
```text
|
||||
vmcluster.operator.victoriametrics.com/example-vmcluster-persistent created
|
||||
```
|
||||
|
||||
* By applying this CRD we install the [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/) to the default [namespace](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/) of your k8s cluster with following params:
|
||||
* `retentionPeriod: "12"` defines the [retention](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#retention) to 12 months.
|
||||
* `replicaCount: 2` creates two replicas of vmselect, vminsert and vmstorage.
|
||||
|
||||
Please note that it may take some time for the pods to start. To check that the pods are started, run the following command:
|
||||
Pods may take some time to become ready. To check that the pods are started, run the following command:
|
||||
<p id="example-cluster-config"></p>
|
||||
|
||||
```sh
|
||||
kubectl get pods | grep vmcluster
|
||||
kubectl get pods -l managed-by=vm-operator
|
||||
```
|
||||
|
||||
The expected output:
|
||||
```sh
|
||||
The expected output is:
|
||||
|
||||
```text
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
vminsert-example-vmcluster-persistent-845849cb84-9vb6f 1/1 Running 0 5m15s
|
||||
vminsert-example-vmcluster-persistent-845849cb84-r7mmk 1/1 Running 0 5m15s
|
||||
@@ -116,43 +158,50 @@ vmstorage-example-vmcluster-persistent-0 1/1 Running 0
|
||||
vmstorage-example-vmcluster-persistent-1 1/1 Running 0 5m25s
|
||||
```
|
||||
|
||||
There is an extra command to get information about the cluster state:
|
||||
The VictoriaMetrics Operator adds an extra command to get information about the state of the cluster:
|
||||
|
||||
```sh
|
||||
kubectl get vmclusters
|
||||
```
|
||||
|
||||
The expected output:
|
||||
Output is typically:
|
||||
|
||||
```text
|
||||
NAME INSERT COUNT STORAGE COUNT SELECT COUNT AGE STATUS
|
||||
example-vmcluster-persistent 2 2 2 5m53s operational
|
||||
```
|
||||
|
||||
Internet traffic goes through the Kubernetes Load balancer which use the set of Pods targeted by a [Kubernetes Service](https://kubernetes.io/docs/concepts/services-networking/service/). The service in [VictoriaMetrics Cluster architecture](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#architecture-overview) which accepts the ingested data named `vminsert` and in Kubernetes it is a `vminsert ` service. So we need to use it for remote_write url.
|
||||
### Install vmagent
|
||||
|
||||
To get the name of `vminsert` services, please run the following command:
|
||||
In order to send metrics to the VictoriaMetrics database, we need a [vmagent](https://docs.victoriametrics.com/victoriametrics/vmagent/) service. This service scrapes metrics, applies relabeling, and forwards them to the `vminsert` service in the cluster.
|
||||
|
||||
First, we need to determine the URL for the `vminsert` service. Run the following command to obtain the service name of the service:
|
||||
|
||||
```sh
|
||||
kubectl get svc | grep vminsert
|
||||
kubectl get svc -l app.kubernetes.io/name=vminsert
|
||||
```
|
||||
|
||||
The expected output:
|
||||
The expected output is:
|
||||
|
||||
```sh
|
||||
vminsert-example-vmcluster-persistent ClusterIP 10.107.47.136 <none> 8480/TCP 5m58s
|
||||
```text
|
||||
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
|
||||
vminsert-example-vmcluster-persistent ClusterIP 10.43.42.217 <none> 8480/TCP 2d
|
||||
```
|
||||
|
||||
To scrape metrics from Kubernetes with a VictoriaMetrics Cluster we will need to install [VMAgent](https://docs.victoriametrics.com/victoriametrics/vmagent/) with some additional configurations.
|
||||
Copy `vminsert-example-vmcluster-persistent` (or whatever user put into metadata.name field [https://docs.victoriametrics.com/guides/getting-started-with-vm-operator/#example-cluster-config](https://docs.victoriametrics.com/guides/getting-started-with-vm-operator/#example-cluster-config)) service name and add it to the `remoteWrite` URL from [quick-start example](https://github.com/VictoriaMetrics/operator/blob/master/docs/quick-start.md#vmagent).
|
||||
Here is an example of the full configuration that we need to apply:
|
||||
The write URL for the `vminsert` service takes the form of `http://<service-name>.<namespace>.svc.cluster.local:<port-number>`. In our example, the URL is:
|
||||
|
||||
```text
|
||||
http://vminsert-example-vmcluster-persistent.default.svc.cluster.local:8480
|
||||
```
|
||||
|
||||
Create a YAML file to configure vmagent. Ensure that `spec: remoteWrite: url` matches the `vminsert` service URL:
|
||||
|
||||
```sh
|
||||
cat <<EOF | kubectl apply -f -
|
||||
cat <<EOF > vmagent-config.yml
|
||||
apiVersion: operator.victoriametrics.com/v1beta1
|
||||
kind: VMAgent
|
||||
metadata:
|
||||
# You may change the name of the vmagent service here
|
||||
name: example-vmagent
|
||||
spec:
|
||||
serviceScrapeNamespaceSelector: {}
|
||||
@@ -165,78 +214,156 @@ spec:
|
||||
staticScrapeNamespaceSelector: {}
|
||||
replicaCount: 1
|
||||
remoteWrite:
|
||||
# url must be "http://<service-name>.<namespace>.svc.cluster.local:<port-number>/insert/0/prometheus/api/v1/write"
|
||||
- url: "http://vminsert-example-vmcluster-persistent.default.svc.cluster.local:8480/insert/0/prometheus/api/v1/write"
|
||||
EOF
|
||||
```
|
||||
|
||||
Let's break down the main settings in the config:
|
||||
|
||||
The expected output:
|
||||
- `metadata: name` defines the name of the vmagent service (e.g., `example-vmagent`)
|
||||
- `spec: remoteWrite: url` defines the fully qualified URL for the `vminsert` service. Ensure the URL is correct and ends with `/insert/0/prometheus/api/v1/write`.
|
||||
|
||||
Install `vmagent` with:
|
||||
|
||||
```sh
|
||||
kubectl apply -f vmagent-config.yml
|
||||
```
|
||||
|
||||
You should get this message:
|
||||
|
||||
```text
|
||||
vmagent.operator.victoriametrics.com/example-vmagent created
|
||||
```
|
||||
|
||||
>`remoteWrite.url` for VMAgent consists of the following parts:
|
||||
> "service_name.VMCluster_namespace.svc.kubernetes_cluster_domain" that in our case will look like vminsert-example-vmcluster-persistent.default.svc.cluster.local
|
||||
|
||||
Verify that `VMAgent` is up and running by executing the following command:
|
||||
|
||||
Verify that `vmagent` is operational:
|
||||
|
||||
```sh
|
||||
kubectl get pods | grep vmagent
|
||||
kubectl get vmagent
|
||||
```
|
||||
|
||||
The expected output is:
|
||||
|
||||
```text
|
||||
vmagent-example-vmagent-7996844b5f-b5rzs 2/2 Running 0 9s
|
||||
NAME SHARDS COUNT REPLICA COUNT STATUS AGE
|
||||
example-vmagent 1 operational 21h
|
||||
```
|
||||
|
||||
> There are two containers for VMagent: the first one is a VMagent and the second one is a sidecar with a secret. VMagent use a secret with configuration which is mounted to the special sidecar. It observes the changes with configuration and send a signal to reload configuration for the VMagent.
|
||||
|
||||
Run the following command to make `VMAgent`'s port accessible from the local machine:
|
||||
|
||||
Run the following command to make the service port accessible from the local machine:
|
||||
|
||||
```sh
|
||||
kubectl port-forward svc/vmagent-example-vmagent 8429:8429
|
||||
```
|
||||
|
||||
The expected output is:
|
||||
The terminal should show the following. Keep the session open to access the forwarded connection:
|
||||
|
||||
```text
|
||||
Forwarding from 127.0.0.1:8429 -> 8429
|
||||
Forwarding from [::1]:8429 -> 8429
|
||||
```
|
||||
|
||||
To check that `VMAgent` collects metrics from the k8s cluster open in the browser `http://127.0.0.1:8429/targets`.
|
||||
You will see something like this:
|
||||
To check that `vmagent` is collecting metrics by browsing `http://127.0.0.1:8429/targets`. You will see something like this:
|
||||
|
||||

|
||||

|
||||
<figcaption style="text-align: center; font-style: italic;">vmagent's status for discovered targets</figcaption>
|
||||
|
||||
`VMAgent` connects to [kubernetes service discovery](https://kubernetes.io/docs/concepts/services-networking/service/) and gets targets which needs to be scraped. This service discovery is controlled by [VictoriaMetrics Operator](https://github.com/VictoriaMetrics/operator)
|
||||
Notice that only the VictoriaMetrics services are being targeted. By default, `vmagent` does not scrape Kubernetes cluster metrics. The next section explains how to enable scraping in Kubernetes.
|
||||
|
||||
### Enable Kubernetes metrics scraping {#kubernetes-scraping}
|
||||
|
||||
> [!NOTE] Tip
|
||||
> This step is optional. Skip to the next section if you do not want to collect metrics from the Kubernetes control plane and node components.
|
||||
|
||||
To enable metric collection from the Kubernetes system, we need to update `vmagent` configuration and set up various [Scrape CRDs](https://docs.victoriametrics.com/operator/resources/).
|
||||
|
||||
Update the `vmagent-config.yml` file as follows. Ensure you define `spec: remoteWrite: url:` value is still correct as in the previous step.
|
||||
|
||||
```sh
|
||||
cat <<EOF >vmagent-config.yml
|
||||
apiVersion: operator.victoriametrics.com/v1beta1
|
||||
kind: VMAgent
|
||||
metadata:
|
||||
name: example-vmagent
|
||||
namespace: default
|
||||
spec:
|
||||
replicaCount: 1
|
||||
# Enable CRD-based scraping
|
||||
selectAllByDefault: true
|
||||
remoteWrite:
|
||||
# url takes the form of "http://<service-name>.<namespace>.svc.cluster.local:<port-number>/insert/0/prometheus/api/v1/write"
|
||||
- url: "http://vminsert-example-vmcluster-persistent.default.svc.cluster.local:8480/insert/0/prometheus/api/v1/write"
|
||||
EOF
|
||||
```
|
||||
|
||||
Update `vmagent`:
|
||||
|
||||
```sh
|
||||
kubectl apply -f vmagent-config.yml
|
||||
```
|
||||
|
||||
Download the [vmscrape-config.yml-example](vmscrape-config.yml-example) file and rename it to `vmscrape-config.yml`. This config sets up scrape CRDs for key Kubernetes components, including nodes, pods, APIs, and services.
|
||||
|
||||
Apply the scrape CRDs:
|
||||
|
||||
```sh
|
||||
kubectl apply -f vmscrape-config.yml
|
||||
```
|
||||
|
||||
The expected output is:
|
||||
|
||||
```text
|
||||
vmnodescrape.operator.victoriametrics.com/kubelet-cadvisor created
|
||||
vmnodescrape.operator.victoriametrics.com/kubelet-metrics created
|
||||
vmscrapeconfig.operator.victoriametrics.com/kubernetes-apiservers created
|
||||
vmscrapeconfig.operator.victoriametrics.com/kubernetes-pods created
|
||||
vmscrapeconfig.operator.victoriametrics.com/kubernetes-service-endpoints created
|
||||
```
|
||||
|
||||
Go back to the `vmagent` target page by browsing `http://127.0.0.1:8429/targets`. This time, you should find targets such as `nodeScrape/default/kubelet-cadvisor` and `nodeScrape/default/kubelet-metrics` with an up status:
|
||||
|
||||

|
||||
<figcaption style="text-align: center; font-style: italic;">You should find Kubernetes-specific targets now</figcaption>
|
||||
|
||||
## 4. Verifying VictoriaMetrics cluster
|
||||
|
||||
See [how to install and connect Grafana to VictoriaMetrics](https://docs.victoriametrics.com/guides/k8s-monitoring-via-vm-cluster/#4-install-and-connect-grafana-to-victoriametrics-with-helm) but with one addition - we should get the name of `vmselect` service from the freshly installed VictoriaMetrics Cluster because it will now be different.
|
||||
|
||||
To get the new service name, please run the following command:
|
||||
The next step is to install Grafana to visualize collected metrics.
|
||||
|
||||
Add the Grafana Helm repository with:
|
||||
|
||||
```sh
|
||||
kubectl get svc | grep vmselect
|
||||
helm repo add grafana-community https://grafana-community.github.io/helm-charts
|
||||
helm repo update
|
||||
```
|
||||
|
||||
The expected output:
|
||||
Next, we need to determine the URL for the `vmselect` service. To get the service name, run the following command:
|
||||
|
||||
```sh
|
||||
vmselect-example-vmcluster-persistent ClusterIP None <none> 8481/TCP 7m
|
||||
kubectl get svc -l app.kubernetes.io/name=vmselect
|
||||
```
|
||||
|
||||
The final config will look like this:
|
||||
|
||||
You should get a message like this:
|
||||
|
||||
```sh
|
||||
cat <<EOF | helm install my-grafana grafana/grafana -f -
|
||||
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
|
||||
vmselect-example-vmcluster-persistent ClusterIP None <none> 8481/TCP 7m
|
||||
```
|
||||
|
||||
We'll need to supply a datasource URL for Grafana, which in VictoriaMetrics cluster takes the following form:
|
||||
|
||||
```text
|
||||
http://<service-name>.<namespace>.svc.cluster.local:<port-number>
|
||||
```
|
||||
|
||||
Thus, in our example, the URL is:
|
||||
|
||||
```text
|
||||
http://vmselect-example-vmcluster-persistent.default.svc.cluster.local:8481/select/0/prometheus/
|
||||
```
|
||||
|
||||
Create a values file for the Grafana Helm chart:
|
||||
|
||||
```
|
||||
cat << EOF > grafana-values.yml
|
||||
datasources:
|
||||
datasources.yaml:
|
||||
apiVersion: 1
|
||||
@@ -244,6 +371,7 @@ cat <<EOF | helm install my-grafana grafana/grafana -f -
|
||||
- name: victoriametrics
|
||||
type: prometheus
|
||||
orgId: 1
|
||||
# url takes the form of 'http://<vmselect-service-name>.<namespace>.svc.cluster.local:<port-number>/select/0/prometheus'
|
||||
url: http://vmselect-example-vmcluster-persistent.default.svc.cluster.local:8481/select/0/prometheus/
|
||||
access: proxy
|
||||
isDefault: true
|
||||
@@ -267,31 +395,108 @@ cat <<EOF | helm install my-grafana grafana/grafana -f -
|
||||
default:
|
||||
victoriametrics:
|
||||
gnetId: 11176
|
||||
revision: 18
|
||||
datasource: victoriametrics
|
||||
vmagent:
|
||||
gnetId: 12683
|
||||
revision: 7
|
||||
datasource: victoriametrics
|
||||
kubernetes:
|
||||
gnetId: 14205
|
||||
revision: 1
|
||||
datasource: victoriametrics
|
||||
EOF
|
||||
```
|
||||
|
||||
Let's break down the main parts of the config file:
|
||||
|
||||
- `datasources: datasources.yaml: datasources: url` defines the URL for the `vmselect` service. This endpoint is the datasource Grafana uses to query the metrics database.
|
||||
- `dashboards: default:` loads three starter dashboards to monitor the Kubernetes cluster, the VictoriaMetrics services, and the `vmagent` service.
|
||||
|
||||
Install Grafana into the Kubernetes cluster with the name `my-grafana` in the default namespace with the following command:
|
||||
|
||||
```sh
|
||||
helm install my-grafana grafana-community/grafana -f grafana-values.yml
|
||||
```
|
||||
|
||||
The output should look similar to this:
|
||||
|
||||
```text
|
||||
NAME: my-grafana
|
||||
LAST DEPLOYED: Fri Feb 6 19:00:15 2026
|
||||
NAMESPACE: default
|
||||
STATUS: deployed
|
||||
REVISION: 1
|
||||
DESCRIPTION: Install complete
|
||||
NOTES:
|
||||
1. Get your 'admin' user password by running:
|
||||
|
||||
kubectl get secret --namespace default my-grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo
|
||||
|
||||
|
||||
2. The Grafana server can be accessed via port 80 on the following DNS name from within your cluster:
|
||||
|
||||
my-grafana.default.svc.cluster.local
|
||||
|
||||
Get the Grafana URL to visit by running these commands in the same shell:
|
||||
export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=grafana,app.kubernetes.io/instance=my-grafana" -o jsonpath="{.items[0].metadata.name}")
|
||||
kubectl --namespace default port-forward $POD_NAME 3000
|
||||
|
||||
3. Login with the password from step 1 and the username: admin
|
||||
#################################################################################
|
||||
###### WARNING: Persistence is disabled!!! You will lose your data when #####
|
||||
###### the Grafana pod is terminated. #####
|
||||
#################################################################################
|
||||
```
|
||||
|
||||
Use the first command in the output to obtain the password for the `admin` user:
|
||||
|
||||
```sh
|
||||
kubectl get secret --namespace default my-grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo
|
||||
|
||||
```
|
||||
|
||||
The second part of the output shows how to port-forward the Grafana service to access it locally on `127.0.0.1:3000`:
|
||||
|
||||
```sh
|
||||
export pod_name=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=grafana,app.kubernetes.io/instance=my-grafana" -o jsonpath="{.items[0].metadata.name}")
|
||||
|
||||
kubectl --namespace default port-forward $pod_name 3000
|
||||
```
|
||||
|
||||
## 5. Check the result you obtained in your browser
|
||||
|
||||
To check that [VictoriaMetrics](https://victoriametrics.com) collecting metrics from the k8s cluster open in your browser `http://127.0.0.1:3000/dashboards` and choose the `VictoriaMetrics - cluster` dashboard. Use `admin` for login and the `password` that you previously got from kubectl.
|
||||
To check that [VictoriaMetrics](https://victoriametrics.com) is collecting metrics from the Kubernetes cluster, open your browser to http://127.0.0.1:3000/dashboards and choose the `VictoriaMetrics - cluster` dashboard.
|
||||
|
||||

|
||||
Use `admin` for login and the `password` obtained with `kubectl get secret ...`.
|
||||
|
||||
The expected output is:
|
||||

|
||||
<figcaption style="text-align: center; font-style: italic;">List of pre-installed dashboards in Grafana</figcaption>
|
||||
|
||||
The "VictoriaMetrics - cluster" dashboard shows activity of the VictoriaMetrics services.
|
||||
|
||||

|
||||
<figcaption style="text-align: center; font-style: italic;">Grafana dashboard showing metrics for the VictoriaMetrics cluster services</figcaption>
|
||||
|
||||
There is a separate dashboard for the `vmagent` service's activity. This shows the ingestion rate and resource utilization.
|
||||
|
||||

|
||||
<figcaption style="text-align: center; font-style: italic;">Grafana dashboard showing metrics for the vmagent service</figcaption>
|
||||
|
||||
If you [added the scrape configs](#kubernetes-scraping), the Kubernetes dashboard will be populated with metrics; otherwise, it will be empty.
|
||||
|
||||

|
||||
<figcaption style="text-align: center; font-style: italic;">Grafana dashboard showing Kubernetes cluster metrics</figcaption>
|
||||
|
||||

|
||||
|
||||
## 6. Summary
|
||||
|
||||
* We set up Kubernetes Operator for VictoriaMetrics with using CRD.
|
||||
* We collected metrics from all running services and stored them in the VictoriaMetrics database.
|
||||
- We set up a Kubernetes Operator for VictoriaMetrics using CRDs.
|
||||
- We collected metrics from all running services and stored them in the VictoriaMetrics database.
|
||||
- We installed Grafana to visualize metrics
|
||||
|
||||
Consider reading these resources to complete your setup:
|
||||
|
||||
- [VictoriaMetrics Operator Quickstart](https://docs.victoriametrics.com/operator/quick-start/)
|
||||
- See [VictoriaMetrics K8s Stack](https://docs.victoriametrics.com/helm/victoria-metrics-k8s-stack/) for an all-in-one solution for Kubernetes monitoring
|
||||
- Grafana
|
||||
- [Enable persistent storage](https://grafana.com/docs/grafana/latest/setup-grafana/installation/helm/#enable-persistent-storage-recommended)
|
||||
- [Configure private TLS authority](https://grafana.com/docs/grafana/latest/setup-grafana/installation/helm/#configure-a-private-ca-certificate-authority)
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 645 KiB |
|
After Width: | Height: | Size: 447 KiB |
|
After Width: | Height: | Size: 474 KiB |
|
After Width: | Height: | Size: 793 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 308 KiB |
|
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 917 KiB |
243
docs/guides/getting-started-with-vm-operator/vmscrape-config.yml-example
Executable file
@@ -0,0 +1,243 @@
|
||||
# vmnodescrape cadvisor
|
||||
apiVersion: operator.victoriametrics.com/v1beta1
|
||||
kind: VMNodeScrape
|
||||
metadata:
|
||||
name: kubelet-cadvisor
|
||||
spec:
|
||||
scheme: https
|
||||
path: /metrics/cadvisor
|
||||
tlsConfig:
|
||||
insecureSkipVerify: true
|
||||
caFile: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
|
||||
bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
|
||||
|
||||
# Override job label
|
||||
relabelConfigs:
|
||||
- targetLabel: job
|
||||
replacement: "kubernetes-nodes-cadvisor"
|
||||
- action: labelmap
|
||||
regex: __meta_kubernetes_node_label_(.+)
|
||||
|
||||
# relabel metrics to work with Grafana dashboard
|
||||
metricRelabelConfigs:
|
||||
- action: replace
|
||||
sourceLabels: [pod]
|
||||
regex: "(.+)"
|
||||
targetLabel: pod_name
|
||||
replacement: "$1"
|
||||
- action: replace
|
||||
sourceLabels: [container]
|
||||
regex: "(.+)"
|
||||
targetLabel: container_name
|
||||
replacement: "$1"
|
||||
- action: replace
|
||||
targetLabel: name
|
||||
replacement: "k8s_stub"
|
||||
- action: replace
|
||||
sourceLabels: [id]
|
||||
regex: "^/system\\.slice/(.+)\\.service$"
|
||||
targetLabel: systemd_service_name
|
||||
replacement: "$1"
|
||||
|
||||
---
|
||||
|
||||
# vmnodescrape kubelet
|
||||
apiVersion: operator.victoriametrics.com/v1beta1
|
||||
kind: VMNodeScrape
|
||||
metadata:
|
||||
name: kubelet-metrics
|
||||
spec:
|
||||
scheme: https
|
||||
tlsConfig:
|
||||
insecureSkipVerify: true
|
||||
caFile: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
|
||||
bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
|
||||
|
||||
# relabel metrics to work with Grafana dashboard
|
||||
relabelConfigs:
|
||||
- targetLabel: job
|
||||
replacement: "kubernetes-nodes"
|
||||
- action: labelmap
|
||||
regex: __meta_kubernetes_node_label_(.+)
|
||||
|
||||
---
|
||||
|
||||
# vmscrapeconfig apiservers
|
||||
apiVersion: operator.victoriametrics.com/v1beta1
|
||||
kind: VMScrapeConfig
|
||||
metadata:
|
||||
name: kubernetes-apiservers
|
||||
spec:
|
||||
kubernetesSDConfigs:
|
||||
- role: endpoints
|
||||
|
||||
scheme: https
|
||||
tlsConfig:
|
||||
caFile: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
|
||||
insecureSkipVerify: true
|
||||
bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
|
||||
|
||||
relabelConfigs:
|
||||
# Keep only the kubernetes service on https port
|
||||
- sourceLabels:
|
||||
- __meta_kubernetes_namespace
|
||||
- __meta_kubernetes_service_name
|
||||
- __meta_kubernetes_endpoint_port_name
|
||||
action: keep
|
||||
regex: "default;kubernetes;https"
|
||||
|
||||
# relabel metrics to work with Grafana dashboard
|
||||
- targetLabel: job
|
||||
replacement: "kubernetes-apiservers"
|
||||
|
||||
---
|
||||
|
||||
# vmscrapeconfig pods
|
||||
apiVersion: operator.victoriametrics.com/v1beta1
|
||||
kind: VMScrapeConfig
|
||||
metadata:
|
||||
name: kubernetes-pods
|
||||
spec:
|
||||
kubernetesSDConfigs:
|
||||
- role: pod
|
||||
|
||||
relabelConfigs:
|
||||
# Skip init containers
|
||||
- action: drop
|
||||
sourceLabels: [__meta_kubernetes_pod_container_init]
|
||||
regex: "true"
|
||||
|
||||
# Ensure port annotation matches container port
|
||||
- action: keep_if_equal
|
||||
sourceLabels:
|
||||
- __meta_kubernetes_pod_annotation_prometheus_io_port
|
||||
- __meta_kubernetes_pod_container_port_number
|
||||
|
||||
# Only pods with prometheus.io/scrape="true"
|
||||
- sourceLabels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
|
||||
action: keep
|
||||
regex: "true"
|
||||
|
||||
# Exclude pods marked as "slow"
|
||||
- sourceLabels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape_slow]
|
||||
action: drop
|
||||
regex: "true"
|
||||
|
||||
# Scheme override
|
||||
- sourceLabels: [__meta_kubernetes_pod_annotation_prometheus_io_scheme]
|
||||
action: replace
|
||||
targetLabel: __scheme__
|
||||
regex: "(https?)"
|
||||
|
||||
# Path override
|
||||
- sourceLabels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
|
||||
action: replace
|
||||
targetLabel: __metrics_path__
|
||||
regex: "(.+)"
|
||||
|
||||
# Address override using prometheus.io/port
|
||||
- sourceLabels:
|
||||
- __address__
|
||||
- __meta_kubernetes_pod_annotation_prometheus_io_port
|
||||
action: replace
|
||||
targetLabel: __address__
|
||||
regex: "([^:]+)(?::\\d+)?;(\\d+)"
|
||||
replacement: "$1:$2"
|
||||
|
||||
# Copy pod labels
|
||||
- action: labelmap
|
||||
regex: __meta_kubernetes_pod_label_(.+)
|
||||
|
||||
# Use kubernetes_namespace, kubernetes_pod_name, kubernetes_node labels
|
||||
- sourceLabels: [__meta_kubernetes_namespace]
|
||||
action: replace
|
||||
targetLabel: kubernetes_namespace
|
||||
- sourceLabels: [__meta_kubernetes_pod_name]
|
||||
action: replace
|
||||
targetLabel: kubernetes_pod_name
|
||||
- sourceLabels: [__meta_kubernetes_pod_node_name]
|
||||
action: replace
|
||||
targetLabel: kubernetes_node
|
||||
|
||||
# Drop non-running pods
|
||||
- sourceLabels: [__meta_kubernetes_pod_phase]
|
||||
action: drop
|
||||
regex: "Pending|Succeeded|Failed|Completed"
|
||||
|
||||
# Override job label
|
||||
- targetLabel: job
|
||||
replacement: "kubernetes-pods"
|
||||
|
||||
---
|
||||
|
||||
# vmscrapeconfig service endpoints
|
||||
apiVersion: operator.victoriametrics.com/v1beta1
|
||||
kind: VMScrapeConfig
|
||||
metadata:
|
||||
name: kubernetes-service-endpoints
|
||||
spec:
|
||||
kubernetesSDConfigs:
|
||||
- role: endpoints
|
||||
|
||||
relabelConfigs:
|
||||
# Skip init containers
|
||||
- action: drop
|
||||
sourceLabels: [__meta_kubernetes_pod_container_init]
|
||||
regex: "true"
|
||||
|
||||
# Ensure port annotation matches container port
|
||||
- action: keep_if_equal
|
||||
sourceLabels:
|
||||
- __meta_kubernetes_pod_annotation_prometheus_io_port
|
||||
- __meta_kubernetes_pod_container_port_number
|
||||
|
||||
# Only services with prometheus.io/scrape="true"
|
||||
- sourceLabels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
|
||||
action: keep
|
||||
regex: "true"
|
||||
|
||||
# Exclude "slow" services
|
||||
- sourceLabels: [__meta_kubernetes_service_annotation_prometheus_io_scrape_slow]
|
||||
action: drop
|
||||
regex: "true"
|
||||
|
||||
# Scheme override
|
||||
- sourceLabels: [__meta_kubernetes_service_annotation_prometheus_io_scheme]
|
||||
action: replace
|
||||
targetLabel: __scheme__
|
||||
regex: "(https?)"
|
||||
|
||||
# Path override
|
||||
- sourceLabels: [__meta_kubernetes_service_annotation_prometheus_io_path]
|
||||
action: replace
|
||||
targetLabel: __metrics_path__
|
||||
regex: "(.+)"
|
||||
|
||||
# Address override using prometheus.io/port
|
||||
- sourceLabels:
|
||||
- __address__
|
||||
- __meta_kubernetes_service_annotation_prometheus_io_port
|
||||
action: replace
|
||||
targetLabel: __address__
|
||||
regex: "([^:]+)(?::\\d+)?;(\\d+)"
|
||||
replacement: "$1:$2"
|
||||
|
||||
# Copy service labels
|
||||
- action: labelmap
|
||||
regex: __meta_kubernetes_service_label_(.+)
|
||||
|
||||
# relabel metrics to work with Grafana dashboard
|
||||
- sourceLabels: [__meta_kubernetes_namespace]
|
||||
action: replace
|
||||
targetLabel: kubernetes_namespace
|
||||
- sourceLabels: [__meta_kubernetes_service_name]
|
||||
action: replace
|
||||
targetLabel: kubernetes_name
|
||||
- sourceLabels: [__meta_kubernetes_pod_node_name]
|
||||
action: replace
|
||||
targetLabel: kubernetes_node
|
||||
|
||||
# Override job label
|
||||
- targetLabel: job
|
||||
replacement: "kubernetes-service-endpoints"
|
||||
|
||||
@@ -6,33 +6,148 @@ build:
|
||||
sitemap:
|
||||
disable: true
|
||||
---
|
||||
Data deletion is an operation people expect a database to have. [VictoriaMetrics](https://victoriametrics.com) supports
|
||||
[delete operation](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#how-to-delete-time-series) but to a limited extent. Due to implementation details, VictoriaMetrics remains an [append-only database](https://en.wikipedia.org/wiki/Append-only), which perfectly fits the case for storing time series data. But the drawback of such architecture is that it is extremely expensive to mutate the data. Hence, `delete` or `update` operations support is very limited. In this guide, we'll walk through the possible workarounds for deleting or changing already written data in VictoriaMetrics.
|
||||
|
||||
## Purpose
|
||||
|
||||
[Data deletion](https://docs.victoriametrics.com/victoriametrics/#how-to-delete-time-series) in VictoriaMetrics should be used only in specific, one-off cases, such as correcting malformed data or satisfying GDPR requirements. VictoriaMetrics architecture is optimized for appending data, not deleting or modifying existing metrics, which can cause a significant performance penalty. As a result, VictoriaMetrics provides a limited API for data deletion.
|
||||
|
||||
In addition, the data deletion API is not a reliable way to free up storage. You can use storage more efficiently by:
|
||||
|
||||
- Setting up [relabeling](https://docs.victoriametrics.com/victoriametrics/#relabeling) to drop unwanted targets and metrics before they reach storage. See [this post](https://www.robustperception.io/relabelling-can-discard-targets-timeseries-and-alerts/) for more details
|
||||
- Changing the [retention period](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#retention) to automatically prune old metrics
|
||||
|
||||
### Precondition
|
||||
|
||||
- [Single-node VictoriaMetrics](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/);
|
||||
- [Cluster version of VictoriaMetrics](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/);
|
||||
- [curl](https://curl.se/docs/manual.html)
|
||||
- [jq tool](https://stedolan.github.io/jq/)
|
||||
|
||||
## How to delete metrics
|
||||
This guide works with:
|
||||
- [VictoriaMetrics single node](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/)
|
||||
- [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/)
|
||||
- [VictoriaMetrics Cloud](https://docs.victoriametrics.com/victoriametrics-cloud/)
|
||||
|
||||
_Warning: time series deletion is not recommended to use on a regular basis. Each call to delete API could have a performance penalty. The API was provided for one-off operations to deleting malformed data or to satisfy GDPR compliance._
|
||||
## Identify API endpoints {#endpoints}
|
||||
|
||||
[Delete API](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#how-to-delete-time-series) expects from user to specify [time series selector](https://prometheus.io/docs/prometheus/latest/querying/basics/#time-series-selectors). So the first thing to do before the deletion is to verify whether the selector matches the correct series.
|
||||
VictoriaMetrics provides the following endpoints to manage metrics:
|
||||
|
||||
To check that metrics are present in **VictoriaMetrics Cluster** run the following command:
|
||||
- [series](https://docs.victoriametrics.com/victoriametrics/url-examples/#apiv1series): returns series names and their labels
|
||||
- [export](https://docs.victoriametrics.com/victoriametrics/url-examples/#apiv1export): exports raw samples in JSON line format
|
||||
- [import](https://docs.victoriametrics.com/victoriametrics/url-examples/#apiv1import): imports samples in JSON line format
|
||||
- [delete_series](https://docs.victoriametrics.com/victoriametrics/url-examples/#apiv1admintsdbdelete_series): deletes time series
|
||||
- [force_merge](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#forced-merge): forces data compaction in VictoriaMetrics storage
|
||||
|
||||
_Warning: response can return many metrics, so be careful with series selector._
|
||||
The actual [endpoints depend on whether you are running single-node or cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#url-format
|
||||
). Use the tables below as a reference.
|
||||
|
||||
### Single-node version
|
||||
|
||||
Below are the API endpoints for the single-node version of VictoriaMetrics.
|
||||
|
||||
| Type | Endpoint |
|
||||
|-----------------|-------------------------------------------------------------------|
|
||||
| `series` | http://localhost:8428/prometheus/api/v1/series |
|
||||
| `export` | http://localhost:8428/api/v1/export |
|
||||
| `import` | http://localhost:8428/api/v1/import |
|
||||
| `delete_series` | http://localhost:8428/api/v1/admin/tsdb/delete_series |
|
||||
| `force_merge` | http://localhost:8428/internal/force_merge |
|
||||
|
||||
The table assumes that:
|
||||
- You are logged into the machine running the single-node VictoriaMetrics process
|
||||
- Or, if on Kubernetes, that you have port-forwarded the VictoriaMetrics service to `localhost:8428`
|
||||
|
||||
{{% collapse name="Expand to see how to port-forward the VictoriaMetrics services in Kubernetes" %}}
|
||||
|
||||
Find the name of the VictoriaMetrics service:
|
||||
|
||||
```sh
|
||||
curl -s 'http://vmselect:8481/select/0/prometheus/api/v1/series?match[]=process_cpu_cores_available' | jq
|
||||
kubectl get svc -l app.kubernetes.io/instance=vmsingle
|
||||
|
||||
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
|
||||
vmsingle-victoria-metrics-single-server ClusterIP None <none> 8428/TCP 24s
|
||||
```
|
||||
|
||||
_See URL example for single-node [here](https://docs.victoriametrics.com/victoriametrics/url-examples/#apiv1series)._
|
||||
Port-forward the service to localhost with:
|
||||
|
||||
The expected output:
|
||||
```sh
|
||||
kubectl port-forward svc/vmsingle-victoria-metrics-single-server 8428 &
|
||||
```
|
||||
|
||||
{{% /collapse %}}
|
||||
|
||||
### Cluster version
|
||||
|
||||
To select, import, export, and delete series from a VictoriaMetrics cluster, you need to make the API request to the correct service. The table shows the service and its API endpoints for a VictoriaMetrics cluster.
|
||||
|
||||
| Type | Service | Endpoint |
|
||||
|-----------------|-----------|----------------------------------------------------------------------------|
|
||||
| `series` | vmselect | http://localhost:8481/select/0/prometheus/api/v1/series |
|
||||
| `export` | vmselect | http://localhost:8481/select/0/prometheus/api/v1/export |
|
||||
| `import` | vminsert | http://localhost:8480/insert/0/prometheus/api/v1/import |
|
||||
| `delete_series` | vmselect | http://localhost:8481/delete/0/prometheus/api/v1/admin/tsdb/delete_series |
|
||||
| `force_merge` | vmstorage | http://localhost:8482/internal/force_merge |
|
||||
|
||||
|
||||
The table assumes that:
|
||||
- The [Account/Tenant ID](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#multitenancy) is 0; adjust this value as needed
|
||||
- You are logged into the machine running the VictoriaMetrics processes
|
||||
- Or, if on Kubernetes, that you have port-forwarded the VictoriaMetrics services to localhost
|
||||
|
||||
{{% collapse name="Expand to see how to port-forward the VictoriaMetrics cluster services in Kubernetes" %}}
|
||||
|
||||
Find the name of the services:
|
||||
|
||||
```sh
|
||||
kubectl get svc -l app.kubernetes.io/instance=vmcluster
|
||||
|
||||
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
|
||||
vmcluster-victoria-metrics-cluster-vminsert ClusterIP 10.43.177.139 <none> 8480/TCP 5d7h
|
||||
vmcluster-victoria-metrics-cluster-vmselect ClusterIP 10.43.41.195 <none> 8481/TCP 5d7h
|
||||
vmcluster-victoria-metrics-cluster-vmstorage ClusterIP None <none> 8482/TCP,8401/TCP,8400/TCP 5d7h
|
||||
```
|
||||
|
||||
Port-forward the services to localhost:
|
||||
|
||||
```sh
|
||||
kubectl port-forward svc/vmcluster-victoria-metrics-cluster-vminsert 8480 &
|
||||
kubectl port-forward svc/vmcluster-victoria-metrics-cluster-vmselect 8481 &
|
||||
kubectl port-forward svc/vmcluster-victoria-metrics-cluster-vmstorage 8482 &
|
||||
```
|
||||
|
||||
{{% /collapse %}}
|
||||
|
||||
## How to delete metrics
|
||||
|
||||
### Select data to be deleted
|
||||
|
||||
The [delete API](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#how-to-delete-time-series) requires a [time series selector](https://prometheus.io/docs/prometheus/latest/querying/basics/#time-series-selectors). For example:
|
||||
|
||||
- `match[]=process_cpu_cores_available` selects the entire time series with metric name `process_cpu_cores_available` in VictoriaMetrics (including all label combinations)
|
||||
- `match[]=node_memory_MemTotal_bytes{instance="node-exporter:9100", job="hostname.com"}` selects only the time series with the provided labels
|
||||
|
||||
As a first step, query the `series` endpoint to confirm the series selector before deleting anything. For example, to retrieve the `process_cpu_cores_available` series in a single-node VictoriaMetrics use:
|
||||
|
||||
```sh
|
||||
curl -s -X POST 'http://localhost:8428/prometheus/api/v1/series' -d 'match[]=process_cpu_cores_available' | jq
|
||||
```
|
||||
|
||||
> [!NOTE] Warning
|
||||
> The response can return a long list of metrics, so be careful with the series selector.
|
||||
|
||||
To do the same on the cluster version:
|
||||
|
||||
```sh
|
||||
curl -s -X POST 'http://localhost:8481/select/0/prometheus/api/v1/series' -d 'match[]=process_cpu_cores_available' | jq
|
||||
```
|
||||
|
||||
If no records are returned, you should increase the time window (by default, only the last 5 minutes of data are returned). The following example adds `-d 'start=-30d'` to show the last 30 days:
|
||||
|
||||
```sh
|
||||
curl -s -X POST 'http://localhost:8428/prometheus/api/v1/series' \
|
||||
-d 'match[]=process_cpu_cores_available' \
|
||||
-d 'start=-30d' | jq
|
||||
```
|
||||
|
||||
The output should show the matching time series found in VictoriaMetrics:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -56,52 +171,116 @@ The expected output:
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
When you're sure [time series selector](https://prometheus.io/docs/prometheus/latest/querying/basics/#time-series-selectors) is correct, send a POST request to [delete API](https://docs.victoriametrics.com/victoriametrics/url-examples/#apiv1admintsdbdelete_series) with [`match[]=<time-series-selector>`](https://prometheus.io/docs/prometheus/latest/querying/basics/#time-series-selectors) argument. For example:
|
||||
```
|
||||
If you are using VictoriaMetrics Cloud, you need to:
|
||||
|
||||
- Replace the base URL with your [Access Endpoint](https://docs.victoriametrics.com/victoriametrics-cloud/get-started/quickstart/#start-writing-and-reading-data) (e.g., `https://<xxxx>.cloud.victoriametrics.com`)
|
||||
- Add an Authorization Header with your [Access Token](https://docs.victoriametrics.com/victoriametrics-cloud/get-started/quickstart/#start-writing-and-reading-data)
|
||||
- Modify the endpoint path based on your [Cloud deployment type](https://docs.victoriametrics.com/victoriametrics-cloud/deployments/single-or-cluster/)
|
||||
|
||||
The following example works with VictoriaMetrics Cloud single:
|
||||
|
||||
```sh
|
||||
curl -s 'http://vmselect:8481/delete/0/prometheus/api/v1/admin/tsdb/delete_series?match[]=process_cpu_cores_available'
|
||||
curl -s -X POST -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
|
||||
'https://<xxxx>.cloud.victoriametrics.com/prometheus/api/v1/series' \
|
||||
-d 'match[]=process_cpu_cores_available' | jq
|
||||
```
|
||||
|
||||
_See URL example for single-node [here](https://docs.victoriametrics.com/victoriametrics/url-examples/#apiv1admintsdbdelete_series)._
|
||||
|
||||
If operation was successful, the deleted series will stop being [queryable](https://docs.victoriametrics.com/victoriametrics/keyconcepts/#query-data). Storage space for the deleted time series isn't freed instantly - it is freed during subsequent [background merges of data files](https://medium.com/@valyala/how-victoriametrics-makes-instant-snapshots-for-multi-terabyte-time-series-data-e1f3fb0e0282). The background merges may never occur for data from previous months, so storage space won't be freed for historical data. In this case [forced merge](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#forced-merge) may help freeing up storage space.
|
||||
### Delete data
|
||||
|
||||
To trigger [forced merge](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#forced-merge) on VictoriaMetrics Cluster run the following command:
|
||||
> [!NOTE] Warning
|
||||
> The `delete_series` handler accepts any HTTP method, so sending a GET request to `/api/v1/admin/tsdb/delete_series` will result in the deletion of the time series.
|
||||
> It's highly recommended to set the `-deleteAuthKey` to [protect the endpoint](https://docs.victoriametrics.com/victoriametrics/#security) from CSRF attacks.
|
||||
|
||||
Once you have confirmed the time series selector, send a POST request to the `delete_series` endpoint and supply the selector with the format [`match[]=<time-series-selector>`](https://prometheus.io/docs/prometheus/latest/querying/basics/#time-series-selectors). This operation cannot be undone, so consider [exporting your metrics](#export-metrics) for backup purposes.
|
||||
|
||||
For example, to delete the `process_cpu_cores_available` time series in single-node VictoriaMetrics:
|
||||
|
||||
```sh
|
||||
curl -v -X POST http://vmstorage:8482/internal/force_merge
|
||||
curl -s -X POST 'http://localhost:8428/api/v1/admin/tsdb/delete_series' -d 'match[]=process_cpu_cores_available'
|
||||
```
|
||||
|
||||
After the merge is complete, the data will be permanently deleted from the disk.
|
||||
To do the same on the cluster version:
|
||||
|
||||
```sh
|
||||
curl -s -X POST 'http://localhost:8481/delete/0/prometheus/api/v1/admin/tsdb/delete_series' -d 'match[]=process_cpu_cores_available'
|
||||
```
|
||||
|
||||
On VictoriaMetrics Cloud single node, the command is:
|
||||
|
||||
```sh
|
||||
curl -s -X POST -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
|
||||
'https://<xxxxx>.cloud.victoriametrics.com/api/v1/admin/tsdb/delete_series' \
|
||||
-d 'match[]=process_cpu_cores_available'
|
||||
```
|
||||
|
||||
If the operation was successful, the deleted series will no longer be [queryable](https://docs.victoriametrics.com/victoriametrics/keyconcepts/#query-data).
|
||||
|
||||
### Storage
|
||||
|
||||
The storage used by the deleted time series isn't freed immediately. This is done during [background data files merges](https://medium.com/@valyala/how-victoriametrics-makes-instant-snapshots-for-multi-terabyte-time-series-data-e1f3fb0e0282), which may never happen for historical data. In this case, you can trigger a [forced merge](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#forced-merge) to free up storage. After the merge is complete, the data will be permanently deleted from the disk.
|
||||
|
||||
To force a merge on VictoriaMetrics single node, run the following command:
|
||||
|
||||
```sh
|
||||
curl -v -X POST http://localhost:8428/internal/force_merge
|
||||
```
|
||||
|
||||
To do the same on the cluster version:
|
||||
|
||||
```sh
|
||||
curl -v -X POST http://localhost:8482/internal/force_merge
|
||||
```
|
||||
|
||||
Forced merging is not available on VictoriaMetrics Cloud. If you need help managing storage after deleting a time series, please get in touch with support.
|
||||
|
||||
## How to update metrics
|
||||
|
||||
By default, VictoriaMetrics doesn't provide a mechanism for replacing or updating data. As a workaround, take the following actions:
|
||||
VictoriaMetrics doesn't provide a mechanism for replacing or updating data. As a workaround, you can take the following actions:
|
||||
|
||||
- [export time series to a file](https://docs.victoriametrics.com/victoriametrics/url-examples/#apiv1export);
|
||||
- change the values of time series in the file and save it;
|
||||
- [delete time series from a database](https://docs.victoriametrics.com/victoriametrics/url-examples/#apiv1admintsdbdelete_series);
|
||||
- [import saved file to VictoriaMetrics](https://docs.victoriametrics.com/victoriametrics/url-examples/#apiv1import).
|
||||
1. [Export time series to a file](https://docs.victoriametrics.com/victoriametrics/url-examples/#apiv1export)
|
||||
2. Change the values in the exported file
|
||||
3. [Delete time series from a database](https://docs.victoriametrics.com/victoriametrics/url-examples/#apiv1admintsdbdelete_series)
|
||||
4. [Import saved file back into VictoriaMetrics](https://docs.victoriametrics.com/victoriametrics/url-examples/#apiv1import)
|
||||
|
||||
### Export metrics
|
||||
|
||||
For example, let's export metric for `node_memory_MemTotal_bytes` with labels `instance="node-exporter:9100"` and `job="hostname.com"`:
|
||||
For example, let's export the time series for `node_memory_MemTotal_bytes` with labels `instance="node-exporter:9100"` and `job="hostname.com"`.
|
||||
|
||||
For the single-node version, run:
|
||||
|
||||
```sh
|
||||
curl -X POST -g http://vmselect:8481/select/0/prometheus/api/v1/export -d 'match[]=node_memory_MemTotal_bytes{instance="node-exporter:9100", job="hostname.com"}' > data.jsonl
|
||||
curl -s -X POST -g \
|
||||
http://localhost:8428/api/v1/export \
|
||||
-d 'match[]=node_memory_MemTotal_bytes{instance="node-exporter:9100", job="hostname.com"}' > data.jsonl
|
||||
```
|
||||
|
||||
_See URL example for single-node [here](https://docs.victoriametrics.com/victoriametrics/url-examples/#apiv1export)._
|
||||
|
||||
To check that exported file contains time series we can use [cat](https://man7.org/linux/man-pages/man1/cat.1.html) and [jq](https://stedolan.github.io/jq/download/):
|
||||
On the cluster version, the command is:
|
||||
|
||||
```sh
|
||||
cat data.jsonl | jq
|
||||
curl -s -X POST -g \
|
||||
http://localhost:8481/select/0/prometheus/api/v1/export \
|
||||
-d 'match[]=node_memory_MemTotal_bytes{instance="node-exporter:9100", job="hostname.com"}' > data.jsonl
|
||||
```
|
||||
|
||||
The expected output will look like the following:
|
||||
On VictoriaMetrics Cloud single, the command is:
|
||||
|
||||
```sh
|
||||
curl -s -X POST -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
|
||||
'https://<xxxxx>.cloud.victoriametrics.com/api/v1/export' \
|
||||
-d 'match[]=node_memory_MemTotal_bytes{instance="node-exporter:9100", job="hostname.com"}' > data.jsonl
|
||||
|
||||
```
|
||||
|
||||
You can use [jq](https://stedolan.github.io/jq/download/) to more easily verify the exported data:
|
||||
|
||||
```sh
|
||||
jq < data.jsonl
|
||||
```
|
||||
|
||||
The output should look like:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -125,20 +304,19 @@ The expected output will look like the following:
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
In this example, we will replace the values of `node_memory_MemTotal_bytes` from `33604390912` to `17179869184` (from 32Gb to 16Gb) via [sed](https://linux.die.net/man/1/sed), but it can be done in any of the available ways:
|
||||
We can replace the value of `node_memory_MemTotal_bytes` from `33604390912` to `17179869184` (from ~32GB to ~16GB) using [sed](https://linux.die.net/man/1/sed) or any other text-processing tool:
|
||||
|
||||
```sh
|
||||
sed -i 's/33604390912/17179869184/g' data.jsonl
|
||||
```
|
||||
|
||||
Let's check the changes in data.jsonl with `cat`:
|
||||
Check the changes in `data.jsonl`:
|
||||
|
||||
```sh
|
||||
cat data.jsonl | jq
|
||||
jq < data.jsonl
|
||||
```
|
||||
|
||||
The expected output will be the following:
|
||||
The output should look like this:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -164,29 +342,61 @@ The expected output will be the following:
|
||||
|
||||
### Delete metrics
|
||||
|
||||
See [How-to-delete-metrics](https://docs.victoriametrics.com/guides/guide-delete-or-replace-metrics/#how-to-delete-metrics) from the previous paragraph.
|
||||
Delete the metrics as explained above in [how to delete metrics](https://docs.victoriametrics.com/guides/guide-delete-or-replace-metrics/#how-to-delete-metrics).
|
||||
|
||||
### Import metrics
|
||||
|
||||
VictoriaMetrics supports a lot of [ingestion protocols](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#how-to-import-time-series-data) and we will use [import from JSON line format](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#how-to-import-data-in-json-line-format).
|
||||
VictoriaMetrics supports several [ingestion protocols](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#how-to-import-time-series-data). In this case, we can directly [import from JSON line format](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#how-to-import-data-in-json-line-format).
|
||||
|
||||
The next command will import metrics from `data.jsonl` to VictoriaMetrics:
|
||||
The next command imports metrics from `data.jsonl` to VictoriaMetrics single node:
|
||||
|
||||
```sh
|
||||
curl -v -X POST http://vminsert:8480/insert/0/prometheus/api/v1/import -T data.jsonl
|
||||
curl -v -X POST http://localhost:8428/api/v1/import -T data.jsonl
|
||||
```
|
||||
|
||||
_See URL example for single-node [here](https://docs.victoriametrics.com/victoriametrics/url-examples/#apiv1import)._
|
||||
On the cluster version, the command is:
|
||||
|
||||
Please note, importing data with old timestamps is called **backfilling** and may require resetting caches as described [here](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#backfilling).
|
||||
```sh
|
||||
curl -v -X POST http://localhost:8480/insert/0/prometheus/api/v1/import -T data.jsonl
|
||||
```
|
||||
|
||||
For VictoriaMetrics Cloud single node, the command is:
|
||||
|
||||
```sh
|
||||
curl -s -X POST -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
|
||||
'https://<xxxxx>.cloud.victoriametrics.com/api/v1/import' \
|
||||
-T data.jsonl
|
||||
```
|
||||
|
||||
Please note that importing data with old timestamps is called **backfilling** and may require resetting caches, as described [here](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#backfilling).
|
||||
|
||||
### Check imported metrics
|
||||
|
||||
The final step is to validate that the data was imported correctly.
|
||||
|
||||
To query the series on a single-node VictoriaMetrics:
|
||||
|
||||
```sh
|
||||
curl -X POST -g http://vmselect:8481/select/0/prometheus/api/v1/export -d match[]=node_memory_MemTotal_bytes
|
||||
curl -s -X POST -g 'http://localhost:8428/api/v1/export' \
|
||||
-d 'match[]=node_memory_MemTotal_bytes{instance="node-exporter:9100", job="hostname.com"}' | jq
|
||||
```
|
||||
|
||||
The expected output will look like:
|
||||
On the cluster version, the command is:
|
||||
|
||||
```sh
|
||||
curl -s -X POST -g 'http://localhost:8481/select/0/prometheus/api/v1/export' \
|
||||
-d 'match[]=node_memory_MemTotal_bytes{instance="node-exporter:9100", job="hostname.com"}' | jq
|
||||
```
|
||||
|
||||
On VictoriaMetrics Cloud single node, use:
|
||||
|
||||
```sh
|
||||
curl -s -X POST -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
|
||||
'https://<xxxxx>.cloud.victoriametrics.com/api/v1/export' \
|
||||
-d 'match[]=node_memory_MemTotal_bytes{instance="node-exporter:9100", job="hostname.com"}' | jq
|
||||
```
|
||||
|
||||
The output should look like:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -209,3 +419,18 @@ The expected output will look like:
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you have problems interacting with the API, try these steps:
|
||||
- Remove the `-s` from the curl command to see any errors
|
||||
- Add `-v` to the curl command for verbose output
|
||||
- Check that you are using the correct endpoint and port for your VictoriaMetrics deployment
|
||||
- On Kubernetes, you might need to port-forward the services in order to reach the API endpoints
|
||||
|
||||
## See also
|
||||
|
||||
- [API Examples](https://docs.victoriametrics.com/victoriametrics/url-examples/)
|
||||
- [Relabeling cookbook](https://docs.victoriametrics.com/victoriametrics/relabeling/)
|
||||
- [Retention period configuration](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#retention)
|
||||
|
||||
|
||||
@@ -7,12 +7,94 @@ sitemap:
|
||||
disable: true
|
||||
---
|
||||
|
||||
Headlamp is a user-friendly Kubernetes UI focused on extensibility. It supports displaying metrics for Kubernetes resources via a built-in Prometheus plugin. Here's the plugin configuration to use in-cluster VictoriaMetrics Single - set Prometheus Service Address to `<namespace>/<vmsingle service name>:8428`:
|
||||

|
||||
[Headlamp](https://headlamp.dev/) is a user-friendly Kubernetes UI with a built-in Prometheus plugin that can show metrics from VictoriaMetrics.
|
||||
|
||||
This guide shows how to point Headlamp’s Prometheus integration at VictoriaMetrics single node or VictoriaMetrics cluster to display CPU, memory, network, and filesystem graphs for your Kubernetes resources directly in the UI.
|
||||
|
||||
## 1. Install VictoriaMetrics
|
||||
|
||||
VictoriaMetrics must be running in your Kubernetes cluster.
|
||||
|
||||
This guide is compatible with both [single-node](https://docs.victoriametrics.com/guides/k8s-monitoring-via-vm-single/) and [cluster](https://docs.victoriametrics.com/guides/k8s-monitoring-via-vm-cluster/) versions of VictoriaMetrics.
|
||||
|
||||
Once VictoriaMetrics is running, note the `NAME`, `PORT`, and the namespace where the service is running.
|
||||
|
||||
- For the single-node version:
|
||||
|
||||
```sh
|
||||
kubectl get svc -l app.kubernetes.io/instance=vmsingle
|
||||
|
||||
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
|
||||
vmsingle-victoria-metrics-single-server ClusterIP None <none> 8428/TCP 15m
|
||||
```
|
||||
|
||||
- For the cluster version:
|
||||
|
||||
```sh
|
||||
kubectl get svc -l app=vmselect
|
||||
|
||||
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
|
||||
vmcluster-victoria-metrics-cluster-vmselect ClusterIP 10.43.41.195 <none> 8481/TCP 2m2s
|
||||
```
|
||||
|
||||
|
||||
## 2. Configure Headlamp
|
||||
|
||||
You can run Headlamp as a [desktop application](https://headlamp.dev/docs/latest/installation/desktop/) or as an [in-cluster service](https://headlamp.dev/docs/latest/installation/in-cluster/).
|
||||
|
||||
To configure the Prometheus plugin, go to **Settings** > **Plugins** and select Prometheus.
|
||||
|
||||

|
||||
<figcaption style="text-align: center; font-style: italic;">Built-in plugins in Headlamp UI (desktop version)</figcaption>
|
||||
|
||||
Ensure **Enable metrics** is activated and **Auto-detect** is disabled.
|
||||
|
||||

|
||||
<figcaption style="text-align: center; font-style: italic;">Prometheus plugin: Enable metrics and disable auto-detect</figcaption>
|
||||
|
||||
Fill in the Prometheus Service Address in the following format:
|
||||
|
||||
```text
|
||||
namespace/service-name:port
|
||||
```
|
||||
|
||||
For example, in the single-node version running in the default namespace, the address looks like:
|
||||
|
||||
```text
|
||||
default/vmsingle-victoria-metrics-single-server:8428
|
||||
```
|
||||
|
||||

|
||||
<figcaption style="text-align: center; font-style: italic;">Prometheus plugin configured for VictoriaMetrics single node</figcaption>
|
||||
|
||||
For the cluster version, the address looks like:
|
||||
|
||||
```text
|
||||
default/vmcluster-victoria-metrics-cluster-vmselect:8481
|
||||
```
|
||||
|
||||
In addition, only for the cluster version, you must fill in the following path in **Prometheus service subpath**, where `0` is the default [Tenant ID](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#multitenancy):
|
||||
|
||||
```text
|
||||
/select/0/prometheus
|
||||
```
|
||||
|
||||

|
||||
<figcaption style="text-align: center; font-style: italic;">Prometheus plugin configured for VictoriaMetrics cluster</figcaption>
|
||||
|
||||
Press **Save** to confirm your changes.
|
||||
|
||||
> [!NOTE]
|
||||
> The **Test Connection** button does not work with VictoriaMetrics. You can ignore this error; metrics should still be displayed correctly in Headlamp.
|
||||
|
||||
You should now find the Show Prometheus metrics option on several pages.
|
||||
|
||||

|
||||
<figcaption style="text-align: center; font-style: italic;">Headlamp showing CPU metrics for a pod</figcaption>
|
||||
|
||||
## See also
|
||||
|
||||
- [Kubernetes monitoring via VictoriaMetrics Single](https://docs.victoriametrics.com/guides/k8s-monitoring-via-vm-single/)
|
||||
- [Kubernetes monitoring with VictoriaMetrics Cluster](https://docs.victoriametrics.com/guides/k8s-monitoring-via-vm-cluster/)
|
||||
|
||||
In case of cluster installation, point to the vmselect service (
|
||||
` <namespace>/<vmselect service name>:8481`) and specify the prometheus-compatible subpath `select/<tenant id>/prometheus`:
|
||||

|
||||
|
||||
The plugin will display CPU/Memory/Network/Filesystem graphs for k8s resources:
|
||||

|
||||
|
||||
BIN
docs/guides/k8s-ui-headlamp/headlamp-plugins.webp
Normal file
|
After Width: | Height: | Size: 360 KiB |
BIN
docs/guides/k8s-ui-headlamp/pod-metrics.webp
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
docs/guides/k8s-ui-headlamp/prometheus-config-vmcluster.webp
Normal file
|
After Width: | Height: | Size: 326 KiB |
BIN
docs/guides/k8s-ui-headlamp/prometheus-config-vmsingle.webp
Normal file
|
After Width: | Height: | Size: 329 KiB |
BIN
docs/guides/k8s-ui-headlamp/prometheus-plugin-start.webp
Normal file
|
After Width: | Height: | Size: 662 KiB |
BIN
docs/guides/k8s-ui-headlamp/save-prometheus.webp
Normal file
|
After Width: | Height: | Size: 554 KiB |
91
docs/opentelemetry/README.md
Normal file
@@ -0,0 +1,91 @@
|
||||
VictoriaMetrics software provides native [OpenTelemetry](https://opentelemetry.io/) ingestion across **metrics**, **logs**, and **traces** via dedicated components.
|
||||
This allows running OpenTelemetry-based observability pipeline with VictoriaMetrics software as your backend.
|
||||
|
||||
VictoriaMetrics provides a dedicated database for each [signal type](https://opentelemetry.io/docs/concepts/signals/):
|
||||
|
||||
- [VictoriaMetrics](https://docs.victoriametrics.com/victoriametrics/) for [Metrics](https://opentelemetry.io/docs/concepts/signals/metrics/);
|
||||
- [VictoriaLogs](https://docs.victoriametrics.com/victorialogs/) for [Logs](https://opentelemetry.io/docs/concepts/signals/logs/);
|
||||
- [VictoriaTraces](https://docs.victoriametrics.com/victoriatraces/) for [Traces](https://opentelemetry.io/docs/concepts/signals/traces/).
|
||||
|
||||

|
||||
{width="700"}
|
||||
|
||||
Each database is optimized for its own signal and usage scenario to improve maintainability and efficiency.
|
||||
|
||||
Resources:
|
||||
* [OpenTelemetry Astronomy Shop demo](https://github.com/VictoriaMetrics-Community/opentelemetry-demo) with integrated VictoriaMetrics backends.
|
||||
* Live [Grafana Playground](https://play-grafana.victoriametrics.com/) with OTeL demo and VictoriaMetrics components.
|
||||
* [Full-Stack Observability with VictoriaMetrics in the OTel Demo](https://victoriametrics.com/blog/victoriametrics-full-stack-observability-otel-demo/) blogpost.
|
||||
|
||||
---
|
||||
|
||||
## Metrics (VictoriaMetrics)
|
||||
|
||||
VictoriaMetrics single-node, vmagent and vminsert components support ingestion of metrics via OpenTelemetry Protocol (OTLP)
|
||||
from [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) and applications instrumented with [OpenTelemetry SDKs](https://opentelemetry.io/docs/languages/).
|
||||
|
||||
See the detailed description about protocol support [here](https://docs.victoriametrics.com/victoriametrics/#sending-data-via-opentelemetry).
|
||||
|
||||
> See a practical guide [How to use OpenTelemetry metrics with VictoriaMetrics](https://docs.victoriametrics.com/guides/getting-started-with-opentelemetry/).
|
||||
|
||||
Once metrics are ingested into VictoriaMetrics, they can be read via the following tools:
|
||||
1. [vmui](https://docs.victoriametrics.com/victoriametrics/#vmui) - VictoriaMetrics User Interface for ad-hoc queries
|
||||
and data exploration.
|
||||
1. [Grafana](https://docs.victoriametrics.com/victoriametrics/integrations/grafana/) - integrates with VictoriaMetrics
|
||||
using [Prometheus datasource](https://grafana.com/docs/grafana/latest/datasources/prometheus/)
|
||||
or [VictoriaMetrics datasource](https://grafana.com/grafana/plugins/victoriametrics-metrics-datasource/) plugins.
|
||||
1. [Perses](https://docs.victoriametrics.com/victoriametrics/integrations/perses/) - integrates with VictoriaMetrics
|
||||
via [Prometheus plugins](https://perses.dev/plugins/docs/prometheus/).
|
||||
1. [vmalert](https://docs.victoriametrics.com/victoriametrics/vmalert/) - is an alerting tool for VictoriaMetrics.
|
||||
It executes a list of the given alerting or recording rules and sends notifications to Alertmanager.
|
||||
|
||||
|
||||
## Logs (VictoriaLogs)
|
||||
|
||||
VictoriaLogs single-node, vlagent and vlinsert components support ingestion of logs via OpenTelemetry Protocol (OTLP) from [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/)
|
||||
and applications instrumented with [OpenTelemetry SDKs](https://opentelemetry.io/docs/languages/).
|
||||
|
||||
See the detailed description about protocol support [here](https://docs.victoriametrics.com/victorialogs/data-ingestion/opentelemetry/).
|
||||
|
||||
> See a practical guide
|
||||
[How to use OpenTelemetry metrics with VictoriaLogs](https://docs.victoriametrics.com/guides/getting-started-with-opentelemetry/).
|
||||
|
||||
Once logs are ingested into Victorialogs, they can be read via the following tools:
|
||||
1. [vmui](https://docs.victoriametrics.com/victorialogs/querying/#web-ui) - VictoriaLogs User Interface for ad-hoc queries
|
||||
and data exploration.
|
||||
1. [Grafana](https://docs.victoriametrics.com/victorialogs/integrations/grafana/) - integrates with VictoriaLogs
|
||||
using [VictoriaLogs datasource](https://grafana.com/grafana/plugins/victoriametrics-logs-datasource/) plugin.
|
||||
1. [Perses](https://docs.victoriametrics.com/victorialogs/integrations/perses/) - integrates with VictoriaLogs
|
||||
via [VictoriaLogs plugins](https://perses.dev/plugins/docs/victorialogs/).
|
||||
1. [vmalert](https://docs.victoriametrics.com/victorialogs/vmalert/) - is an alerting tool for VictoriaLogs.
|
||||
It executes a list of the given alerting and sends notifications to Alertmanager. It can convert LogsQL queries
|
||||
into metrics via recording rules and persist them into VictoriaMetrics.
|
||||
|
||||
## Traces (VictoriaTraces)
|
||||
|
||||
VictoriaTraces single-node and vtinsert components support ingestion of traces via OpenTelemetry Protocol (OTLP) from [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/)
|
||||
and applications instrumented with [OpenTelemetry SDKs](https://opentelemetry.io/docs/languages/).
|
||||
|
||||
See the detailed description about protocol support [here](https://docs.victoriametrics.com/victoriatraces/data-ingestion/opentelemetry/).
|
||||
|
||||
Once traces are ingested into VictoriaTraces, they can be read via the following tools:
|
||||
1. [Grafana](https://docs.victoriametrics.com/victorialogs/integrations/grafana/) - integrates with VictoriaTraces
|
||||
using [Jaeger datasource](https://grafana.com/docs/grafana/latest/datasources/jaeger/) plugin.
|
||||
1. [Jaeger frontend](https://www.jaegertracing.io/docs/2.6/deployment/frontend-ui/) - integrates with VictoriaTraces
|
||||
via [Jaeger Query Service JSON APIs](https://www.jaegertracing.io/docs/2.6/apis/#internal-http-json).
|
||||
1. [vmalert](https://docs.victoriametrics.com/victoriatraces/vmalert/) - is an alerting tool for VictoriaTraces.
|
||||
It executes a list of the given alerting and sends notifications to Alertmanager. It can convert LogsQL queries
|
||||
into metrics via recording rules and persist them into VictoriaMetrics.
|
||||
|
||||
## Correlations
|
||||
|
||||
Signals can be correlated together if they share the same list of attributes, so they can uniquely identify the
|
||||
same system or event. The recommended user interface for correlations is Grafana thanks to its [correlation interfaces](https://grafana.com/docs/grafana/latest/administration/correlations/).
|
||||
See below various scenarios of correlating signals in Grafana using VictoriaMetrics, VictoriaLogs and VictoriaTraces as backends.
|
||||
|
||||
Depending on the Grafana datasource plugin there could be multiple correlations available:
|
||||
1. Trace to logs, log to trace, log to metrics - see [correlations via VictoriaLogs plugin](https://docs.victoriametrics.com/victorialogs/integrations/grafana/#correlations).
|
||||
1. Trace to metrics, metric to logs, metric to traces - see [correlations via VictoriaMetrics plugin](https://docs.victoriametrics.com/victoriametrics/integrations/grafana/datasource/#correlations).
|
||||
1. Metrics to logs or traces correlations are possible via Prometheus datasource as well.
|
||||
1. Plugins Tempo, Jaeger, and Zipkin can correlate with logs or metrics using [Trace to logs](https://grafana.com/docs/grafana/latest/explore/trace-integration/#trace-to-logs)
|
||||
and [Trace to metrics](https://grafana.com/docs/grafana/latest/visualizations/explore/trace-integration/#trace-to-metrics) feature.
|
||||
BIN
docs/opentelemetry/README.webp
Normal file
|
After Width: | Height: | Size: 80 KiB |
14
docs/opentelemetry/_index.md
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: OpenTelemetry
|
||||
weight: 60
|
||||
menu:
|
||||
docs:
|
||||
weight: 60
|
||||
identifier: opentelemetry
|
||||
tags:
|
||||
- metrics
|
||||
- logs
|
||||
- traces
|
||||
- otel
|
||||
---
|
||||
{{% content "README.md" %}}
|
||||
@@ -1015,7 +1015,7 @@ to ensure query results consistency, even if storage layer didn't complete dedup
|
||||
## Metrics Metadata
|
||||
|
||||
Cluster version of VictoriaMetrics can store metric metadata (TYPE, HELP, UNIT) {{% available_from "v1.130.0" %}}.
|
||||
Metadata ingestion is disabled by default. To enable it, set `-enableMetadata=true` on `vminsert` and `vmagent`.
|
||||
Metadata ingestion is enabled by default{{% available_from "#" %}}. To disable it, set `-enableMetadata=false` on `vminsert`, and `vmagent`.
|
||||
|
||||
The metadata is cached in-memory in a ring buffer and can use up to 1% of available memory by default (see `-storage.maxMetadataStorageSize` cmd-line flag).
|
||||
When in-memory size is exceeded, the least updated entries are dropped first. Entries that weren't updated for 1h are cleaned up automatically.
|
||||
|
||||
@@ -1227,7 +1227,10 @@ Metric names are stripped from the resulting series. Add [keep_metric_names](#ke
|
||||
#### buckets_limit
|
||||
|
||||
`buckets_limit(limit, buckets)` is a [transform function](#transform-functions), which limits the number
|
||||
of [histogram buckets](https://valyala.medium.com/improving-histogram-usability-for-prometheus-and-grafana-bc7e5df0e350) to the given `limit`.
|
||||
of [histogram buckets](https://valyala.medium.com/improving-histogram-usability-for-prometheus-and-grafana-bc7e5df0e350) to the given `limit`.
|
||||
|
||||
The result will preserve the first and the last bucket to improve accuracy for min and max values.
|
||||
So, if the `limit` is greater than 0 and less than 3, the function will still return 3 buckets: the first bucket, the last bucket, and a selected bucket.
|
||||
|
||||
See also [prometheus_buckets](#prometheus_buckets) and [histogram_quantile](#histogram_quantile).
|
||||
|
||||
|
||||
@@ -1033,18 +1033,10 @@ VictoriaMetrics also may scrape Prometheus targets - see [these docs](#how-to-sc
|
||||
|
||||
### Sending data via OpenTelemetry
|
||||
|
||||
VictoriaMetrics supports data ingestion via [OpenTelemetry protocol for metrics](https://github.com/open-telemetry/opentelemetry-specification/blob/ffddc289462dfe0c2041e3ca42a7b1df805706de/specification/metrics/data-model.md) at `/opentelemetry/v1/metrics` path.
|
||||
VictoriaMetrics supports data ingestion via [OpenTelemetry protocol (OTLP) for metrics](https://github.com/open-telemetry/opentelemetry-specification/blob/ffddc289462dfe0c2041e3ca42a7b1df805706de/specification/metrics/data-model.md) at `/opentelemetry/v1/metrics` path.
|
||||
It expects `protobuf`-encoded requests at `/opentelemetry/v1/metrics`. For gzip-compressed workload set HTTP request header `Content-Encoding: gzip`.
|
||||
|
||||
VictoriaMetrics expects `protobuf`-encoded requests at `/opentelemetry/v1/metrics`.
|
||||
Set HTTP request header `Content-Encoding: gzip` when sending gzip-compressed data to `/opentelemetry/v1/metrics`.
|
||||
|
||||
VictoriaMetrics stores the ingested OpenTelemetry [raw samples](https://docs.victoriametrics.com/victoriametrics/keyconcepts/#raw-samples) as is without any transformations.
|
||||
Pass `-opentelemetry.usePrometheusNaming` command-line flag to VictoriaMetrics for automatic conversion of metric names and labels into Prometheus-compatible format.
|
||||
Pass `-opentelemetry.convertMetricNamesToPrometheus` command-line flag to VictoriaMetrics for applying Prometheus-compatible format conversion only for metrics names.
|
||||
OpenTelemetry [exponential histogram](https://opentelemetry.io/docs/specs/otel/metrics/data-model/#exponentialhistogram) is automatically converted
|
||||
to [VictoriaMetrics histogram format](https://valyala.medium.com/improving-histogram-usability-for-prometheus-and-grafana-bc7e5df0e350).
|
||||
|
||||
Using the following exporter configuration in the OpenTelemetry collector will allow you to send metrics into VictoriaMetrics:
|
||||
Use the following OpenTelemetry collector exporter configuration to push metrics to VictoriaMetrics:
|
||||
|
||||
```yaml
|
||||
exporters:
|
||||
@@ -1057,7 +1049,7 @@ exporters:
|
||||
> Note, [cluster version of VM](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#url-format) expects specifying tenant ID, i.e. `http://<vminsert>:<port>/insert/<accountID>/opentelemetry`.
|
||||
> See more about [multitenancy](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#multitenancy).
|
||||
|
||||
Remember to add the exporter to the desired service pipeline in order to activate the exporter.
|
||||
Remember to add the exporter to the desired service pipeline to activate the exporter.
|
||||
|
||||
```yaml
|
||||
service:
|
||||
@@ -1069,7 +1061,22 @@ service:
|
||||
- otlp
|
||||
```
|
||||
|
||||
By default, VictoriaMetrics stores the ingested OpenTelemetry [metric samples](https://docs.victoriametrics.com/victoriametrics/keyconcepts/#raw-samples) as is **without any transformations**.
|
||||
The following label transformations can be enabled:
|
||||
* `--usePromCompatibleNaming` - replaces characters unsupported by Prometheus with `_` in metric names and labels **for all ingestion protocols**.
|
||||
For example, `process.cpu.time{service.name="foo"}` is converted to `process_cpu_time{service_name="foo"}`.
|
||||
* `--opentelemetry.usePrometheusNaming` - converts metric names and labels according to [OTLP Metric points to Prometheus specification](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.33.0/specification/compatibility/prometheus_and_openmetrics.md#otlp-metric-points-to-prometheus) for metrics ingested via OTLP.
|
||||
For example, `process.cpu.time{service.name="foo"}` is converted to `process_cpu_time_seconds_total{service_name="foo"}`.
|
||||
* `-opentelemetry.convertMetricNamesToPrometheus` - converts **only metric names** according to [OTLP Metric points to Prometheus specification](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.33.0/specification/compatibility/prometheus_and_openmetrics.md#otlp-metric-points-to-prometheus) for metrics ingested via OTLP.
|
||||
For example, `process.cpu.time{service.name="foo"}` is converted to `process_cpu_time_seconds_total{service.name="foo"}`. See more about this use case [here](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9830).
|
||||
|
||||
> These flags can applied on vmagent, vminsert or VictoriaMetrics single-node.
|
||||
|
||||
OpenTelemetry [exponential histogram](https://opentelemetry.io/docs/specs/otel/metrics/data-model/#exponentialhistogram) is automatically converted
|
||||
to [VictoriaMetrics histogram format](https://valyala.medium.com/improving-histogram-usability-for-prometheus-and-grafana-bc7e5df0e350).
|
||||
|
||||
See [How to use OpenTelemetry metrics with VictoriaMetrics](https://docs.victoriametrics.com/guides/getting-started-with-opentelemetry/).
|
||||
See more about [OpenTelemetry in VictoriaMetrics](https://docs.victoriametrics.com/opentelemetry/).
|
||||
|
||||
## JSON line format
|
||||
|
||||
@@ -1371,7 +1378,7 @@ see [these docs](https://docs.victoriametrics.com/victoriametrics/stream-aggrega
|
||||
## Metrics Metadata
|
||||
|
||||
Single-node VictoriaMetrics can store metric metadata (`TYPE`, `HELP`, `UNIT`) {{% available_from "v1.130.0" %}}.
|
||||
Metadata ingestion and querying are disabled by default. To enable them, set `-enableMetadata=true`.
|
||||
Metadata ingestion and querying are enabled by default{{% available_from "#" %}}. To disable them, set `-enableMetadata=false`.
|
||||
|
||||
The metadata is cached in-memory in a ring buffer and can use up to 1% of available memory by default (see `-storage.maxMetadataStorageSize` cmd-line flag).
|
||||
When in-memory size is exceeded, the least updated entries are dropped first. Entries that weren't updated for 1h are cleaned up automatically.
|
||||
|
||||
@@ -113,8 +113,7 @@ and the candidate is deployed to the sandbox environment.
|
||||
|
||||
1. Make sure that the release branches have no security issues.
|
||||
1. Update release versions if needed in [SECURITY.md](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/SECURITY.md).
|
||||
1. Run `PKG_TAG=v1.xx.y make docs-update-version` command to update version help tooltips.
|
||||
1. Run `make docs-update-flags` command to update command-line flags in the documentation. [Commit example](https://github.com/VictoriaMetrics/VictoriaMetrics/commit/4d42b291e55ac9211130efbd5a56aa819998516d).
|
||||
1. Run `PKG_TAG=v1.xx.y make docs-update-version` command to update version help tooltips.
|
||||
1. Cut new version in [CHANGELOG.md](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/docs/victoriametrics/changelog/CHANGELOG.md) and commit it. See example in this [commit](https://github.com/VictoriaMetrics/VictoriaMetrics/commit/b771152039d23b5ccd637a23ea748bc44a9511a7).
|
||||
1. Create the following release tags:
|
||||
* `git tag -s v1.xx.y` in `master` branch
|
||||
@@ -199,6 +198,7 @@ Issues included in the release are closed, with the comment.
|
||||
```
|
||||
|
||||
1. Bump VictoriaMetrics version mentioned in [docs](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7388).
|
||||
1. Run `TAG=v1.xx.y make docs-update-flags` command to update command-line flags in the documentation. [Commit example](https://github.com/VictoriaMetrics/VictoriaMetrics/commit/4d42b291e55ac9211130efbd5a56aa819998516d).
|
||||
1. Follow the instructions in [release follow-up](https://github.com/VictoriaMetrics/VictoriaMetrics-enterprise/blob/enterprise-single-node/Release-Guide.md).
|
||||
|
||||
#### Operator
|
||||
|
||||
@@ -12,96 +12,78 @@ aliases:
|
||||
- /troubleshooting/index.html
|
||||
- /troubleshooting/
|
||||
---
|
||||
This document contains troubleshooting guides for the most common issues when working with VictoriaMetrics:
|
||||
|
||||
- [General troubleshooting checklist](#general-troubleshooting-checklist)
|
||||
- [Unexpected query results](#unexpected-query-results)
|
||||
- [Slow data ingestion](#slow-data-ingestion)
|
||||
- [Slow queries](#slow-queries)
|
||||
- [Out of memory errors](#out-of-memory-errors)
|
||||
- [Cluster instability](#cluster-instability)
|
||||
- [Too much disk space used](#too-much-disk-space-used)
|
||||
- [Monitoring](#monitoring)
|
||||
This document contains troubleshooting guides for the most common issues when working with VictoriaMetrics.
|
||||
|
||||
## General troubleshooting checklist
|
||||
|
||||
If you hit some issue or have some question about VictoriaMetrics components,
|
||||
then please follow these steps in order to quickly find the solution:
|
||||
If you encounter an issue or have a question about VictoriaMetrics components, follow these steps to quickly find a solution:
|
||||
|
||||
1. Check the version of VictoriaMetrics component, you are troubleshooting and compare
|
||||
it to [the latest available version](https://docs.victoriametrics.com/victoriametrics/changelog/).
|
||||
If the used version is lower than the latest available version, then there are high chances
|
||||
that the issue is already resolved in newer versions. Carefully read [the changelog](https://docs.victoriametrics.com/victoriametrics/changelog/)
|
||||
between your version and the latest version and check whether the issue is already fixed there.
|
||||
1. Check the version of the VictoriaMetrics component you are troubleshooting and compare
|
||||
it with [the latest available version](https://docs.victoriametrics.com/victoriametrics/changelog/).
|
||||
|
||||
If the issue is already fixed in newer versions, then upgrade to the newer version and verify whether the issue is fixed:
|
||||
If you are running an older version, the issue may already be fixed. Review the [changelog](https://docs.victoriametrics.com/victoriametrics/changelog/)
|
||||
for all releases between your version and the latest release to see whether the problem has been resolved.
|
||||
|
||||
If the issue is fixed in a newer release, upgrade and verify that the problem no longer occurs:
|
||||
|
||||
- [How to upgrade single-node VictoriaMetrics](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#how-to-upgrade-victoriametrics)
|
||||
- [How to upgrade VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#updating--reconfiguring-cluster-nodes)
|
||||
|
||||
Upgrade procedure for other VictoriaMetrics components is as simple as gracefully stopping the component
|
||||
by sending `SIGINT` signal to it and starting the new version of the component.
|
||||
The upgrade procedure for other VictoriaMetrics components is as simple as gracefully stopping the component
|
||||
by sending it a `SIGINT` signal and starting the new version of the component.
|
||||
|
||||
There may be breaking changes between different versions of VictoriaMetrics components in rare cases.
|
||||
These cases are documented in [the changelog](https://docs.victoriametrics.com/victoriametrics/changelog/).
|
||||
So please read the changelog before the upgrade.
|
||||
In rare cases, upgrades may include breaking changes. These cases are documented in the [changelog](https://docs.victoriametrics.com/victoriametrics/changelog/),
|
||||
especially check the **Update notes** near the top of the changelog, as they point out any special actions or considerations to take when upgrading.
|
||||
|
||||
1. Inspect command-line flags passed to VictoriaMetrics components and remove flags that have unclear outcomes for your workload.
|
||||
VictoriaMetrics components are designed to work optimally with the default command-line flag values (e.g. when these flags aren't set explicitly).
|
||||
It is recommended to remove flags with unclear outcomes, since they may result in unexpected issues.
|
||||
1. Review command-line flags passed to VictoriaMetrics components and remove any flags whose impact on your workload is unclear.
|
||||
VictoriaMetrics components are optimized to work well with default settings (that is, when flags aren't explicitly set).
|
||||
Unnecessary or poorly understood flags can lead to unexpected behavior, so it's best to remove them unless you clearly understand why they are needed.
|
||||
|
||||
1. Check for logs in VictoriaMetrics components. They may contain useful information about cause of the issue
|
||||
and how to fix the issue. If the log message doesn't have enough useful information for troubleshooting,
|
||||
then search the log message in Google. There are high chances that the issue is already reported
|
||||
somewhere (docs, StackOverflow, Github issues, etc.) and the solution is already documented there.
|
||||
1. Check logs. They often contain useful details about the root cause and possible fixes.
|
||||
|
||||
1. If VictoriaMetrics logs have no relevant information, then try searching for the issue in Google
|
||||
via multiple keywords and phrases specific to the issue. There are high chances that the issue
|
||||
and the solution is already documented somewhere.
|
||||
If the logs don't provide enough information, try searching the error message on Google. In many cases, the issue has
|
||||
already been discussed (in documentation, on Stack Overflow, or in GitHub issues), and a solution may already be available.
|
||||
|
||||
1. Try searching for the issue at [VictoriaMetrics GitHub](https://github.com/VictoriaMetrics/VictoriaMetrics/issues).
|
||||
The signal/noise quality of search results here is much lower than in Google, but sometimes it may help
|
||||
finding the relevant information about the issue when Google fails to find the needed information.
|
||||
If you located the relevant GitHub issue, but it misses some information on how to diagnose or troubleshoot it,
|
||||
then please provide this information in comments to the issue. This increases chances that it will be resolved soon.
|
||||
1. If VictoriaMetrics logs do not have relevant information, then try searching for the issue on Google
|
||||
using multiple keywords and phrases specific to the issue. In many cases, both the issue and its solution are already documented.
|
||||
|
||||
1. Try searching for information about the issue in [VictoriaMetrics source code](https://github.com/search?q=repo%3AVictoriaMetrics%2FVictoriaMetrics&type=code).
|
||||
GitHub code search may be not very good in some cases, so it is recommended [checking out VictoriaMetrics source code](https://github.com/VictoriaMetrics/VictoriaMetrics/)
|
||||
and perform local search in the checked out code.
|
||||
Note that the source code for VictoriaMetrics cluster is located in [the cluster](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/cluster) branch.
|
||||
1. Try searching for the issue in [VictoriaMetrics GitHub](https://github.com/VictoriaMetrics/VictoriaMetrics/issues).
|
||||
The signal-to-noise ratio of search results here is much lower than on Google, but sometimes it can help
|
||||
find relevant information when Google fails.
|
||||
If you located the relevant GitHub issue, but it lacks details for diagnosis or troubleshooting,
|
||||
then please add them in the issue comments. This increases the chance that it will be resolved soon.
|
||||
|
||||
1. Try searching for information about the issue in the history of [VictoriaMetrics Slack chat](https://victoriametrics.slack.com).
|
||||
There are non-zero chances that somebody already stuck with the same issue and documented the solution at Slack.
|
||||
1. Try searching for information about the issue in the [VictoriaMetrics source code](https://github.com/search?q=repo%3AVictoriaMetrics%2FVictoriaMetrics&type=code).
|
||||
GitHub code search may not be very effective in some cases, so it is recommended [to check out the VictoriaMetrics source code](https://github.com/VictoriaMetrics/VictoriaMetrics/)
|
||||
and perform a local search in the code.
|
||||
Note that the source code for the VictoriaMetrics cluster is located in [the cluster](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/cluster) branch.
|
||||
|
||||
1. If steps above didn't help finding the solution to the issue, then please [file a new issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/new/choose)
|
||||
by providing the maximum details on how to reproduce the issue.
|
||||
1. If the steps above didn't help to find the solution to the issue, then please [file a new issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/new/choose)
|
||||
with as many details as possible on how to reproduce it.
|
||||
|
||||
After that you can post the link to the issue to [VictoriaMetrics Slack chat](https://victoriametrics.slack.com),
|
||||
so VictoriaMetrics community could help finding the solution to the issue. It is better filing the issue at VictoriaMetrics GitHub
|
||||
before posting your question to VictoriaMetrics Slack chat, since GitHub issues are indexed by Google,
|
||||
while Slack messages aren't indexed by Google. This simplifies searching for the solution to the issue for future VictoriaMetrics users.
|
||||
After that you can post the link to the issue in the [VictoriaMetrics Slack chat](https://victoriametrics.slack.com),
|
||||
so the VictoriaMetrics community can help find a solution. It is better to file the issue on VictoriaMetrics GitHub
|
||||
before posting your question to the VictoriaMetrics Slack chat, since GitHub issues are indexed by Google,
|
||||
while Slack messages are not. This simplifies finding a solution to the issue for future VictoriaMetrics users.
|
||||
|
||||
1. Pro tip 1: if you see that [VictoriaMetrics docs](https://docs.victoriametrics.com/victoriametrics/) contain incomplete or incorrect information,
|
||||
then please create a pull request with the relevant changes. This will help VictoriaMetrics community.
|
||||
|
||||
All the docs published at `https://docs.victoriametrics.com` are located in the [docs](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/docs)
|
||||
folder inside VictoriaMetrics repository.
|
||||
then please create a pull request with the relevant changes or a new issue explaining the problem. This will help the VictoriaMetrics community.
|
||||
|
||||
1. Pro tip 2: please provide links to existing docs / GitHub issues / StackOverflow questions
|
||||
instead of copy-n-pasting the information from these sources when asking or answering questions
|
||||
from VictoriaMetrics community. If the linked resources have no enough information,
|
||||
then it is better posting the missing information in the web resource before providing links
|
||||
to this information in Slack chat. This will simplify searching for this information in the future
|
||||
instead of copying and pasting the information from these sources when asking or answering questions
|
||||
to the VictoriaMetrics community. If the linked resources do not have enough information,
|
||||
then it is better to add the missing information to the original web resource before linking it to Slack chat. This will simplify searching for this information in the future
|
||||
for VictoriaMetrics users via Google and [Perplexity](https://www.perplexity.ai/).
|
||||
|
||||
1. Pro tip 3: if you are answering somebody's question about VictoriaMetrics components
|
||||
at GitHub issues / Slack chat / StackOverflow, then the best answer is a direct link to the information
|
||||
regarding the question.
|
||||
in GitHub issues / Slack chat / StackOverflow, then the best answer is a direct link to the information
|
||||
with the answer or solution to the question.
|
||||
The better answer is a concise message with multiple links to the relevant information.
|
||||
The worst answer is a message with misleading or completely wrong information.
|
||||
|
||||
1. Pro tip 4: if you can fix the issue on yourself, then please do it and provide the corresponding pull request!
|
||||
We are glad to get pull requests from VictoriaMetrics community.
|
||||
1. Pro tip 4: If you can fix the issue on your own, then please do it and provide the corresponding pull request!
|
||||
We are happy to get pull requests from the VictoriaMetrics community.
|
||||
|
||||
## Unexpected query results
|
||||
|
||||
@@ -111,150 +93,150 @@ If you see unexpected or unreliable query results from VictoriaMetrics, then try
|
||||
`sum(rate(http_requests_total[5m])) by (job)`, then check whether the following queries return
|
||||
expected results:
|
||||
|
||||
- Remove the outer `sum` and execute `rate(http_requests_total[5m])`,
|
||||
since aggregations could hide some missing series, gaps in data or anomalies in existing series.
|
||||
If this query returns too many time series, then try adding more specific label filters to it.
|
||||
- Remove the outer `sum` and execute `rate(http_requests_total[5m])`.
|
||||
Aggregations could hide missing series, data gaps, or anomalies.
|
||||
|
||||
- If the query returns too many series, try adding more specific label filters.
|
||||
For example, if you see that the original query returns unexpected results for the `job="foo"`,
|
||||
then use `rate(http_requests_total{job="foo"}[5m])` query.
|
||||
If this isn't enough, then continue adding more specific label filters, so the resulting query returns
|
||||
manageable number of time series.
|
||||
then use the `rate(http_requests_total{job="foo"}[5m])` query.
|
||||
Continue adding more specific label filters until the resulting query returns a manageable number of time series.
|
||||
|
||||
- Remove the outer `rate` and execute `http_requests_total`. Additional label filters may be added here in order
|
||||
to reduce the number of returned series.
|
||||
- Remove the outer `rate` and execute `http_requests_total`. Add label filters to reduce the number of returned series
|
||||
if needed.
|
||||
|
||||
Sometimes the query may be improperly constructed, so it returns unexpected results.
|
||||
It is recommended reading and understanding [MetricsQL docs](https://docs.victoriametrics.com/victoriametrics/metricsql/),
|
||||
Sometimes the query may be improperly constructed, leading to unexpected results.
|
||||
It is recommended to read and understand [MetricsQL docs](https://docs.victoriametrics.com/victoriametrics/metricsql/),
|
||||
especially [subqueries](https://docs.victoriametrics.com/victoriametrics/metricsql/#subqueries)
|
||||
and [rollup functions](https://docs.victoriametrics.com/victoriametrics/metricsql/#rollup-functions) sections.
|
||||
|
||||
|
||||
1. If the simplest query continues returning unexpected / unreliable results, then try verifying correctness
|
||||
of raw unprocessed samples for this query via [/api/v1/export](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#how-to-export-data-in-json-line-format)
|
||||
on the given `[start..end]` time range and check whether they are expected:
|
||||
of raw unprocessed samples in [vmui](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#vmui) via the `Raw Query` tab.
|
||||
|
||||
Responses returned from [/api/v1/query](https://docs.victoriametrics.com/victoriametrics/keyconcepts/#instant-query)
|
||||
and [/api/v1/query_range](https://docs.victoriametrics.com/victoriametrics/keyconcepts/#range-query) contain **evaluated** data
|
||||
instead of stored raw samples. In some cases, [staleness](https://prometheus.io/docs/prometheus/latest/querying/basics/#staleness),
|
||||
[deduplication](https://docs.victoriametrics.com/victoriametrics/#deduplication), or irregular scrapes can affect evaluations.
|
||||
See [this short video](https://www.youtube.com/watch?v=7AyVCC6uKfI) for details.
|
||||
|
||||
Raw data can be downloaded via the `Export` button in vmui's `Raw Query` tab or via [/api/v1/export](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#how-to-export-data-in-json-line-format)
|
||||
query on the given `[start..end]` time range and check whether they are expected:
|
||||
|
||||
```sh
|
||||
single-node: curl http://victoriametrics:8428/api/v1/export -d 'match[]=http_requests_total' -d 'start=...' -d 'end=...' -d 'reduce_mem_usage=1'
|
||||
|
||||
cluster: curl http://<vmselect>:8481/select/<tenantID>/prometheus/api/v1/export -d 'match[]=http_requests_total' -d 'start=...' -d 'end=...' -d 'reduce_mem_usage=1'
|
||||
```
|
||||
When raising a GitHub ticket about query issues, please also attach the raw data, so maintainers can reproduce your case locally.
|
||||
|
||||
Note that responses returned from [/api/v1/query](https://docs.victoriametrics.com/victoriametrics/keyconcepts/#instant-query)
|
||||
and from [/api/v1/query_range](https://docs.victoriametrics.com/victoriametrics/keyconcepts/#range-query) contain **evaluated** data
|
||||
instead of raw samples stored in VictoriaMetrics. See [these docs](https://prometheus.io/docs/prometheus/latest/querying/basics/#staleness)
|
||||
for details. The raw samples can be also viewed in [vmui](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#vmui) in `Raw Query` tab and shared via `export` button.
|
||||
1. Try executing the query with [tracer](https://docs.victoriametrics.com/victoriametrics/#query-tracing) enabled. The trace
|
||||
contains a lot of additional information about query execution, series matching, caches, and internal modifications.
|
||||
When raising a GitHub ticket about query issues, please also attach the trace so maintainers can investigate.
|
||||
|
||||
If you migrate from InfluxDB, then pass `-search.setLookbackToStep` command-line flag to single-node VictoriaMetrics
|
||||
or to `vmselect` in VictoriaMetrics cluster. See also [how to migrate from InfluxDB to VictoriaMetrics](https://docs.victoriametrics.com/guides/migrate-from-influx/).
|
||||
1. If you observe gaps when plotting series, it is likely caused by irregular intervals for metrics collection (network delays
|
||||
or targets unavailability during scrapes, irregular pushes, irregular timestamps).
|
||||
VictoriaMetrics automatically [fills the gaps](https://docs.victoriametrics.com/victoriametrics/keyconcepts/#range-query)
|
||||
based on the median interval between [data samples](https://docs.victoriametrics.com/victoriametrics/keyconcepts/#raw-samples).
|
||||
This may yield incorrect results for irregular data, as the median will be skewed. In this case, it is recommended to either fix the
|
||||
irregularities or switch to the static interval for gaps filling by setting `-search.minStalenessInterval=5m` command-line flag (`5m` is
|
||||
used by Prometheus by default).
|
||||
|
||||
1. Sometimes response caching may lead to unexpected results when samples with older timestamps
|
||||
1. Sometimes, response caching may lead to unexpected results when samples with older timestamps
|
||||
are ingested into VictoriaMetrics (aka [backfilling](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#backfilling)).
|
||||
Try disabling response cache and see whether this helps. This can be done in the following ways:
|
||||
Try disabling response cache and see whether this helps:
|
||||
|
||||
- By clicking on the toggle `Disable cache` in vmui.
|
||||
|
||||
- By passing `-search.disableCache` command-line flag to a single-node VictoriaMetrics
|
||||
or to all the `vmselect` components if cluster version of VictoriaMetrics is used.
|
||||
or to all the `vmselect` components if the cluster version of VictoriaMetrics is used.
|
||||
|
||||
- By passing `nocache=1` query arg to every request to `/api/v1/query` and `/api/v1/query_range`.
|
||||
If you use Grafana, then this query arg can be specified in `Custom Query Parameters` field
|
||||
at Prometheus datasource settings - see [these docs](https://grafana.com/docs/grafana/latest/datasources/prometheus/) for details.
|
||||
If you use Grafana, then this query arg can be specified in the `Custom Query Parameters` field
|
||||
in Prometheus datasource settings. See [these docs](https://grafana.com/docs/grafana/latest/datasources/prometheus/) for details.
|
||||
|
||||
If the problem was in the cache, try resetting it via [resetRollupCache handler](https://docs.victoriametrics.com/victoriametrics/url-examples/#internalresetrollupresultcache).
|
||||
If the problem was in the cache, try resetting it via the [resetRollupCache handler](https://docs.victoriametrics.com/victoriametrics/url-examples/#internalresetrollupresultcache).
|
||||
|
||||
1. If you use cluster version of VictoriaMetrics, then it may return partial responses by default
|
||||
when some of `vmstorage` nodes are temporarily unavailable - see [cluster availability docs](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#cluster-availability)
|
||||
for details. If you want to prioritize query consistency over cluster availability,
|
||||
then you can pass `-search.denyPartialResponse` command-line flag to all the `vmselect` nodes.
|
||||
In this case VictoriaMetrics returns an error during querying if at least a single `vmstorage` node is unavailable.
|
||||
Another option is to pass `deny_partial_response=1` query arg to `/api/v1/query` and `/api/v1/query_range`.
|
||||
If you use Grafana, then this query arg can be specified in `Custom Query Parameters` field
|
||||
at Prometheus datasource settings - see [these docs](https://grafana.com/docs/grafana/latest/datasources/prometheus/) for details.
|
||||
1. Cluster version of VictoriaMetrics may return partial responses by default when some of the `vmstorage` nodes are temporarily
|
||||
unavailable. See [cluster availability docs](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#cluster-availability).
|
||||
If you want to prioritize query consistency over cluster availability, then pass `-search.denyPartialResponse` command-line flag to all the `vmselect` nodes.
|
||||
This causes VictoriaMetrics to return an error during query execution if at least one `vmstorage` node is unavailable.
|
||||
Another option is to pass `deny_partial_response=1` query argument to `/api/v1/query` and `/api/v1/query_range`.
|
||||
If you use Grafana, then this query argument can be specified in the `Custom Query Parameters` field
|
||||
in Prometheus/VictoriaMetrics datasource settings. See [these docs](https://grafana.com/docs/grafana/latest/datasources/prometheus/) for details.
|
||||
|
||||
1. If you pass `-replicationFactor` command-line flag to `vmselect`, then it is recommended removing this flag from `vmselect`,
|
||||
1. If you pass the `-replicationFactor` command-line flag to `vmselect`, then it is recommended to remove this flag from `vmselect`,
|
||||
since it may lead to incomplete responses when `vmstorage` nodes contain less than `-replicationFactor`
|
||||
copies of the requested data.
|
||||
|
||||
1. If you observe gaps when plotting time series try simplifying your query according to p2 and follow the list.
|
||||
If problem still remains, then it is likely caused by irregular intervals for metrics collection (network delays
|
||||
or targets unavailability on scrapes, irregular pushes, irregular timestamps).
|
||||
VictoriaMetrics automatically [fills the gaps](https://docs.victoriametrics.com/victoriametrics/keyconcepts/#range-query)
|
||||
based on median interval between [data samples](https://docs.victoriametrics.com/victoriametrics/keyconcepts/#raw-samples).
|
||||
This may work incorrectly for irregular data as median will be skewed. In this case it is recommended to switch
|
||||
to the static interval for gaps filling by setting `-search.minStalenessInterval=5m` command-line flag (`5m` is
|
||||
the static interval used by Prometheus).
|
||||
|
||||
1. If you observe recently written data is not immediately visible/queryable, then read more about
|
||||
1. If you observe that recently written data is not immediately visible/queryable, then read more about
|
||||
[query latency](https://docs.victoriametrics.com/victoriametrics/keyconcepts/#query-latency) behavior.
|
||||
|
||||
1. Try upgrading to the [latest available version of VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest)
|
||||
and verifying whether the issue is fixed there.
|
||||
|
||||
1. Try executing the query with `trace=1` query arg. This enables query tracing, that may contain
|
||||
useful information on why the query returns unexpected data. See [query tracing docs](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#query-tracing) for details.
|
||||
1. Inspect command-line flags passed to VictoriaMetrics components. If you don't clearly understand the purpose
|
||||
or the effect of some flags, then remove them from the list of flags.
|
||||
VictoriaMetrics components are optimized to work well with default settings (that is, when flags aren't explicitly set).
|
||||
Unnecessary or poorly understood flags can lead to unexpected behavior, so it's best to remove them unless you clearly understand why they are needed.
|
||||
|
||||
1. Inspect command-line flags passed to VictoriaMetrics components. If you don't understand clearly the purpose
|
||||
or the effect of some flags, then remove them from the list of flags passed to VictoriaMetrics components,
|
||||
because some command-line flags may change query results in unexpected ways when set to improper values.
|
||||
VictoriaMetrics is optimized for running with default flag values (e.g. when they aren't set explicitly).
|
||||
|
||||
1. If the steps above didn't help identifying the root cause of unexpected query results,
|
||||
then [file a bugreport](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/new) with details on how to reproduce the issue.
|
||||
Instead of sharing screenshots in the issue, consider sharing query and [trace](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#query-tracing)
|
||||
results in [VMUI](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#vmui) by clicking on `Export query` button in top right corner of the graph area.
|
||||
1. If the steps above didn't help identify the root cause of unexpected query results,
|
||||
then [file a bug report](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/new) with details on how to reproduce the issue.
|
||||
Instead of sharing screenshots in the issue, consider sharing the query, [raw samples](https://docs.victoriametrics.com/victoriametrics/#vmui) and [trace](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#query-tracing)
|
||||
results via [VMUI](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#vmui).
|
||||
|
||||
## Slow data ingestion
|
||||
|
||||
These are the most commons reasons for slow data ingestion in VictoriaMetrics:
|
||||
These are the most common reasons for slow data ingestion in VictoriaMetrics:
|
||||
|
||||
1. Memory shortage for the given amounts of [active time series](https://docs.victoriametrics.com/victoriametrics/faq/#what-is-an-active-time-series).
|
||||
|
||||
VictoriaMetrics (or `vmstorage` in cluster version of VictoriaMetrics) maintains an in-memory cache
|
||||
for quick search for internal series ids per each incoming metric.
|
||||
This cache is named `storage/tsid`. VictoriaMetrics automatically determines the maximum size for this cache
|
||||
depending on the available memory on the host where VictoriaMetrics (or `vmstorage`) runs. If the cache size isn't enough
|
||||
for holding all the entries for active time series, then VictoriaMetrics locates the needed data on disk,
|
||||
unpacks it, re-constructs the missing entry and puts it into the cache. This takes additional CPU time and disk read IO.
|
||||
VictoriaMetrics (or `vmstorage` in the cluster version of VictoriaMetrics) maintains an in-memory cache `storage/tsid`
|
||||
for a quick search for internal series IDs for each incoming metric. VictoriaMetrics automatically determines the maximum
|
||||
size for this cache depending on the available memory on the host where VictoriaMetrics (or `vmstorage`) runs.
|
||||
If the cache size isn't enough to hold all the entries for active time series, then VictoriaMetrics locates the required data on disk,
|
||||
unpacks it, reconstructs the missing entry, and adds it to the cache. This takes additional CPU time and disk read I/O.
|
||||
|
||||
The [official Grafana dashboards for VictoriaMetrics](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#monitoring)
|
||||
contain `Slow inserts` graph, that shows the cache miss percentage for `storage/tsid` cache
|
||||
during data ingestion. If `slow inserts` graph shows values greater than 5% for more than 10 minutes,
|
||||
then it is likely the current number of [active time series](https://docs.victoriametrics.com/victoriametrics/faq/#what-is-an-active-time-series)
|
||||
contain a `Slow inserts` graph that shows the cache miss percentage for the `storage/tsid` cache during data ingestion.
|
||||
If the `slow inserts` graph shows values greater than 5% for more than 10 minutes,
|
||||
then it is likely that the current number of [active time series](https://docs.victoriametrics.com/victoriametrics/faq/#what-is-an-active-time-series)
|
||||
cannot fit the `storage/tsid` cache.
|
||||
|
||||
These are the solutions that exist for this issue:
|
||||
|
||||
- To increase the available memory on the host where VictoriaMetrics runs until `slow inserts` percentage
|
||||
will become lower than 5%. If you run VictoriaMetrics cluster, then you need increasing total available
|
||||
memory at `vmstorage` nodes. This can be done in two ways: either to increase the available memory
|
||||
per each existing `vmstorage` node or to add more `vmstorage` nodes to the cluster.
|
||||
- Increase the available memory on the host where VictoriaMetrics runs until the `slow inserts` percentage
|
||||
drops to 5% or less. If you run a VictoriaMetrics cluster, then you need to increase the total available
|
||||
memory at all `vmstorage` nodes. This can be done in two ways: either to increase the available memory
|
||||
for each `vmstorage` node or to add more `vmstorage` nodes to the cluster to spread the load.
|
||||
|
||||
- To reduce the number of active time series. The [official Grafana dashboards for VictoriaMetrics](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#monitoring)
|
||||
contain a graph showing the number of active time series. Recent versions of VictoriaMetrics
|
||||
provide [cardinality explorer](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#cardinality-explorer),
|
||||
that can help determining and fixing the source of [high cardinality](https://docs.victoriametrics.com/victoriametrics/faq/#what-is-high-cardinality).
|
||||
- Reduce the number of active time series. The [official Grafana dashboards for VictoriaMetrics](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#monitoring)
|
||||
contain a graph showing the number of active time series. Use the [cardinality explorer](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#cardinality-explorer)
|
||||
to determine and fix the source of [high cardinality](https://docs.victoriametrics.com/victoriametrics/faq/#what-is-high-cardinality).
|
||||
|
||||
- Insert performance can degrade when the same time series arrives with labels in different order.
|
||||
- Insert performance can degrade when the same time series arrives with labels in a different order.
|
||||
Ensure your ingestion client always sends labels in a consistent order for each series.
|
||||
Prometheus and `vmagent` already guarantee this, but custom or third-party clients might not.
|
||||
As a fallback, you can enable `-sortLabels=true` on VictoriaMetrics or on `vminsert` in cluster mode.
|
||||
This forces the server to normalize label order, though it increases CPU usage during ingestion.
|
||||
|
||||
1. [High churn rate](https://docs.victoriametrics.com/victoriametrics/faq/#what-is-high-churn-rate),
|
||||
e.g. when old time series are substituted with new time series at a high rate.
|
||||
When VictoriaMetrics encounters a sample for new time series, it needs to register the time series
|
||||
in the internal index (aka `indexdb`), so it can be quickly located on subsequent select queries.
|
||||
1. [High churn rate](https://docs.victoriametrics.com/victoriametrics/faq/#what-is-high-churn-rate).
|
||||
When VictoriaMetrics encounters a sample for a new time series, it needs to register the time series
|
||||
in the internal index (aka `indexdb`), so it can be quickly located during select queries.
|
||||
The process of registering new time series in the internal index is an order of magnitude slower
|
||||
than the process of adding new sample to already registered time series.
|
||||
than the process of adding a new sample to an already registered time series.
|
||||
So VictoriaMetrics may work slower than expected under [high churn rate](https://docs.victoriametrics.com/victoriametrics/faq/#what-is-high-churn-rate).
|
||||
|
||||
The [official Grafana dashboards for VictoriaMetrics](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#monitoring)
|
||||
provides `Churn rate` graph, that shows the average number of new time series registered
|
||||
provide a `Churn rate` graph, which shows the average number of new time series registered
|
||||
during the last 24 hours. If this number exceeds the number of [active time series](https://docs.victoriametrics.com/victoriametrics/faq/#what-is-an-active-time-series),
|
||||
then you need to identify and fix the source of [high churn rate](https://docs.victoriametrics.com/victoriametrics/faq/#what-is-high-churn-rate).
|
||||
The most common source of high churn rate is a label, that frequently changes its value. Try avoiding such labels.
|
||||
The [cardinality explorer](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#cardinality-explorer) can help identifying
|
||||
The most common source of high churn rate is a label that frequently changes its value (like timestamp, session_id). **Try avoiding such labels.**
|
||||
The [cardinality explorer](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#cardinality-explorer) can help identify
|
||||
such labels.
|
||||
|
||||
1. Resource shortage. The [official Grafana dashboards for VictoriaMetrics](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#monitoring)
|
||||
contain `resource usage` graphs, that show memory usage, CPU usage, disk IO usage and free disk size.
|
||||
Make sure VictoriaMetrics has enough free resources for graceful handling of potential spikes in workload
|
||||
contain `Resource usage` graphs that show memory usage, CPU usage, disk I/O usage, etc.
|
||||
Make sure VictoriaMetrics has enough free resources for gracefully handling potential spikes in workload
|
||||
according to the following recommendations:
|
||||
|
||||
- 50% of free CPU
|
||||
@@ -262,52 +244,51 @@ These are the most commons reasons for slow data ingestion in VictoriaMetrics:
|
||||
- 20% of free disk space
|
||||
|
||||
If VictoriaMetrics components have lower amounts of free resources, then this may lead
|
||||
to **significant** performance degradation after workload increases slightly.
|
||||
to **significant** performance degradation when workload increases slightly.
|
||||
For example:
|
||||
|
||||
- If the percentage of free CPU is close to 0, then VictoriaMetrics
|
||||
may experience arbitrary long delays during data ingestion when it cannot keep up
|
||||
with slightly increased data ingestion rate.
|
||||
may experience arbitrarily long delays during data ingestion, even with slight increases in ingestion rate.
|
||||
|
||||
- If the percentage of free memory reaches 0, then the Operating System where VictoriaMetrics components run,
|
||||
may not have enough memory for [page cache](https://en.wikipedia.org/wiki/Page_cache).
|
||||
VictoriaMetrics relies on page cache for quick queries over recently ingested data.
|
||||
If the operating system has no enough free memory for page cache, then it needs
|
||||
to re-read the requested data from disk. This may **significantly** increase disk read IO
|
||||
- If the percentage of free memory reaches 0, then the Operating System where VictoriaMetrics components run
|
||||
may not have enough memory for the [page cache](https://en.wikipedia.org/wiki/Page_cache).
|
||||
VictoriaMetrics relies on the page cache for quick queries over recently ingested data.
|
||||
If the operating system does not have enough free memory for the page cache, then it must
|
||||
re-read the requested data from disk. This may **significantly** increase disk read I/O
|
||||
and slow down both queries and data ingestion.
|
||||
|
||||
- If free disk space is lower than 20%, then VictoriaMetrics is unable to perform optimal
|
||||
background merge of the incoming data. This leads to increased number of data files on disk,
|
||||
that, in turn, slows down both data ingestion and querying. See [these docs](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#storage) for details.
|
||||
- If free disk space is below 20%, then VictoriaMetrics may be unable to perform optimal
|
||||
background merge of the incoming data. This results in more data files on disk.
|
||||
That, in turn, slows down both data ingestion and querying. See [these docs](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#storage) for details.
|
||||
|
||||
1. If you run cluster version of VictoriaMetrics, then make sure `vminsert` and `vmstorage` components
|
||||
are located in the same network with small network latency between them.
|
||||
`vminsert` packs incoming data into batch packets and sends them to `vmstorage` one-by-one.
|
||||
It waits until `vmstorage` returns back `ack` response before sending the next packet.
|
||||
1. If you run the cluster version of VictoriaMetrics, then make sure `vminsert` and `vmstorage` components
|
||||
are located in the same network with a low network latency between them.
|
||||
`vminsert` packs incoming data into batch packets and sends them to `vmstorage` one by one.
|
||||
It waits until `vmstorage` returns back an `ack` response before sending the next packet.
|
||||
If the network latency between `vminsert` and `vmstorage` is high (for example, if they run in different datacenters),
|
||||
then this may become limiting factor for data ingestion speed.
|
||||
then this may become a limiting factor for data ingestion speed.
|
||||
|
||||
The [official Grafana dashboard for cluster version of VictoriaMetrics](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#monitoring)
|
||||
contain `connection saturation` graph for `vminsert` components. If this graph reaches 100% (1s),
|
||||
The [official Grafana dashboard for the cluster version of VictoriaMetrics](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#monitoring)
|
||||
contains a `connection saturation` panel for `vminsert` components. If this graph reaches 100% (1s),
|
||||
then it is likely you have issues with network latency between `vminsert` and `vmstorage`.
|
||||
Another possible issue for 100% connection saturation between `vminsert` and `vmstorage`
|
||||
is resource shortage at `vmstorage` nodes. In this case you need to increase amounts
|
||||
of available resources (CPU, RAM, disk IO) at `vmstorage` nodes or to add more `vmstorage` nodes to the cluster.
|
||||
is a resource shortage in the `vmstorage` nodes. In this case, you need to increase the amount
|
||||
of available resources (CPU, RAM, disk I/O) at `vmstorage` nodes or add more `vmstorage` nodes to the cluster.
|
||||
|
||||
1. Noisy neighbor. Make sure VictoriaMetrics components run in an environment without other resource-hungry apps.
|
||||
Such apps may steal RAM, CPU, disk IO and network bandwidth, that is needed for VictoriaMetrics components.
|
||||
Issues like this are very hard to catch via [official Grafana dashboard for cluster version of VictoriaMetrics](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#monitoring)
|
||||
Such apps may steal RAM, CPU, disk I/O, and network bandwidth that are needed for VictoriaMetrics components.
|
||||
Issues like this are hard to catch via the [official Grafana dashboard for the cluster version of VictoriaMetrics](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#monitoring)
|
||||
and proper diagnosis would require checking resource usage on the instances where VictoriaMetrics runs.
|
||||
|
||||
1. If you see `TooHighSlowInsertsRate` [alert](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#monitoring) when single-node VictoriaMetrics or `vmstorage` has enough
|
||||
free CPU and RAM, then increase `-cacheExpireDuration` command-line flag at single-node VictoriaMetrics or at `vmstorage` to the value,
|
||||
1. If you see a `TooHighSlowInsertsRate` [alert](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#monitoring) when single-node VictoriaMetrics or `vmstorage` has enough
|
||||
free CPU and RAM, then increase the `-cacheExpireDuration` command-line flag at single-node VictoriaMetrics or at `vmstorage` to a value
|
||||
that exceeds the interval between ingested samples for the same time series (aka `scrape_interval`).
|
||||
See [this comment](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3976#issuecomment-1476883183) for more details.
|
||||
|
||||
1. If you see constant and abnormally high CPU usage of VictoriaMetrics component, check `CPU spent on GC` panel
|
||||
on the corresponding [Grafana dashboard](https://grafana.com/orgs/victoriametrics) in `Resource usage` section. If percentage of CPU time spent on garbage collection
|
||||
is high, then CPU usage of the component can be reduced at the cost of higher memory usage by changing [GOGC](https://tip.golang.org/doc/gc-guide#GOGC) environment variable
|
||||
to higher values. By default VictoriaMetrics components use `GOGC=30`. Try running VictoriaMetrics components with `GOGC=100` and see whether this helps reducing CPU usage.
|
||||
1. If you see constant and abnormally high CPU usage for the VictoriaMetrics component, check the `CPU spent on GC` panel
|
||||
on the corresponding [Grafana dashboard](https://grafana.com/orgs/victoriametrics) in the `Resource usage` section. If the percentage of CPU time spent on garbage collection
|
||||
is high, then CPU usage of the component can be reduced at the cost of higher memory usage by increasing the [GOGC](https://tip.golang.org/doc/gc-guide#GOGC) environment variable.
|
||||
By default, VictoriaMetrics components use `GOGC=30`. Try running VictoriaMetrics components with `GOGC=100` and see whether this helps reduce CPU usage.
|
||||
Note that higher `GOGC` values may increase memory usage.
|
||||
|
||||
## Slow queries
|
||||
@@ -316,40 +297,43 @@ Some queries may take more time and resources (CPU, RAM, network bandwidth) than
|
||||
VictoriaMetrics logs slow queries if their execution time exceeds the duration passed
|
||||
to `-search.logSlowQueryDuration` command-line flag (5s by default).
|
||||
|
||||
VictoriaMetrics provides [`top queries` page at VMUI](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#top-queries), that shows
|
||||
queries that took the most time to execute.
|
||||
VictoriaMetrics provides a [`top queries` page in VMUI](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#top-queries) that shows
|
||||
the longest-running queries. And [Query execution stats](https://docs.victoriametrics.com/victoriametrics/query-stats/) for dumping slow queries
|
||||
to logs.
|
||||
|
||||
These are the solutions that exist for improving performance of slow queries:
|
||||
These are the solutions that exist for improving the performance of slow queries:
|
||||
|
||||
- Investigating the bottleneck in query execution using [query tracing](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#query-tracing).
|
||||
It will show the percentage of time spent on each execution step and help understand the volume of processed data.
|
||||
|
||||
- Adding more CPU and memory to VictoriaMetrics, so it may perform the slow query faster.
|
||||
If you use cluster version of VictoriaMetrics, then migrating `vmselect` nodes to machines
|
||||
with more CPU and RAM should help improving speed for slow queries. Query performance
|
||||
is always limited by resources of one `vmselect` that processes the query. For example, if 2vCPU cores on `vmselect`
|
||||
isn't enough to process query fast enough, then migrating `vmselect` to a machine with 4vCPU cores should increase heavy query performance by up to 2x.
|
||||
If the line on `concurrent select` graph form the [official Grafana dashboard for VictoriaMetrics](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#monitoring)
|
||||
If you use the cluster version of VictoriaMetrics, then migrating `vmselect` nodes to machines
|
||||
with more CPU and RAM should help improve speed for slow queries. Query performance
|
||||
is always limited by the resources of **one** `vmselect` that processes the query. For example, if 2 vCPU cores on `vmselect`
|
||||
can't process queries fast enough, then migrating `vmselect` to a machine with 4 vCPU cores should increase heavy query performance by up to 2x.
|
||||
If the line on the `concurrent select` graph from the [official Grafana dashboard for VictoriaMetrics](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#monitoring)
|
||||
is close to the limit, then prefer adding more `vmselect` nodes to the cluster.
|
||||
Sometimes adding more `vmstorage` nodes also can help improving the speed for slow queries.
|
||||
Sometimes adding more `vmstorage` nodes can also help improve the speed for slow queries.
|
||||
|
||||
- Rewriting slow queries, so they become faster. Unfortunately it is hard determining
|
||||
whether the given query is slow by just looking at it.
|
||||
- Rewriting slow queries, so they become faster.
|
||||
|
||||
The main source of slow queries in practice is [alerting and recording rules](https://docs.victoriametrics.com/victoriametrics/vmalert/#rules)
|
||||
with long lookbehind windows in square brackets. These queries are frequently used in SLI/SLO calculations such as [Sloth](https://github.com/slok/sloth).
|
||||
|
||||
For example, `avg_over_time(up[30d]) > 0.99` needs to read and process
|
||||
all the [raw samples](https://docs.victoriametrics.com/victoriametrics/keyconcepts/#raw-samples)
|
||||
for `up` [time series](https://docs.victoriametrics.com/victoriametrics/keyconcepts/#time-series) over the last 30 days
|
||||
each time it executes. If this query is executed frequently, then it can take significant share of CPU, disk read IO, network bandwidth and RAM.
|
||||
for the `up` [time series](https://docs.victoriametrics.com/victoriametrics/keyconcepts/#time-series) over the last 30 days
|
||||
each time it executes. If this query is executed frequently, it can take a significant share of CPU, disk read I/O, network bandwidth, and RAM.
|
||||
Such queries can be optimized in the following ways:
|
||||
|
||||
- To reduce the lookbehind window in square brackets. For example, `avg_over_time(up[10d])` takes up to 3x less compute resources
|
||||
- To reduce the look-behind window in square brackets. For example, `avg_over_time(up[10d])` takes up to 3x less compute resources
|
||||
than `avg_over_time(up[30d])` at VictoriaMetrics.
|
||||
- To increase evaluation interval for alerting and recording rules, so they are executed less frequently.
|
||||
For example, increasing `-evaluationInterval` command-line flag value at [vmalert](https://docs.victoriametrics.com/victoriametrics/vmalert/)
|
||||
from `1m` to `2m` should reduce compute resource usage at VictoriaMetrics by 2x.
|
||||
- To increase the evaluation interval for alerting and recording rules, so they are executed less frequently.
|
||||
For example, increasing the `-evaluationInterval` command-line flag value at [vmalert](https://docs.victoriametrics.com/victoriametrics/vmalert/)
|
||||
from `1m` to `2m` should reduce compute resource usage by VictoriaMetrics 2x.
|
||||
|
||||
Another source of slow queries is improper use of [subqueries](https://docs.victoriametrics.com/victoriametrics/metricsql/#subqueries).
|
||||
It is recommended avoiding subqueries if you don't understand clearly how they work.
|
||||
It is recommended to avoid subqueries if you don't clearly understand how they work.
|
||||
It is easy to create a subquery without knowing about it.
|
||||
For example, `rate(sum(some_metric))` is implicitly transformed into the following subquery
|
||||
according to [implicit conversion rules for MetricsQL queries](https://docs.victoriametrics.com/victoriametrics/metricsql/#implicit-query-conversions):
|
||||
@@ -365,67 +349,64 @@ These are the solutions that exist for improving performance of slow queries:
|
||||
It is likely this query won't return the expected results. Instead, `sum(rate(some_metric))` must be used instead.
|
||||
See [this article](https://www.robustperception.io/rate-then-sum-never-sum-then-rate/) for more details.
|
||||
|
||||
VictoriaMetrics provides [query tracing](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#query-tracing) feature,
|
||||
that can help determining the source of slow query.
|
||||
See also [this article](https://valyala.medium.com/how-to-optimize-promql-and-metricsql-queries-85a1b75bf986),
|
||||
that explains how to determine and optimize slow queries.
|
||||
which explains how to identify and optimize slow queries.
|
||||
|
||||
## Out of memory errors
|
||||
|
||||
There are the following most common sources of out of memory (aka OOM) crashes in VictoriaMetrics:
|
||||
The following are the most common sources of out-of-memory (aka OOM) crashes in VictoriaMetrics:
|
||||
|
||||
1. Improper command-line flag values. Inspect command-line flags passed to VictoriaMetrics components.
|
||||
If you don't understand clearly the purpose or the effect of some flags - remove them
|
||||
from the list of flags passed to VictoriaMetrics components. Improper command-line flags values
|
||||
may lead to increased memory and CPU usage. The increased memory usage increases chances for OOM crashes.
|
||||
VictoriaMetrics is optimized for running with default flag values (e.g. when they aren't set explicitly).
|
||||
If you don't clearly understand the purpose or the effect of some flags, remove them
|
||||
from the list of flags passed to VictoriaMetrics components. Improper command-line flag values
|
||||
may lead to increased memory and CPU usage. Increased memory usage increases the risk of OOM crashes.
|
||||
VictoriaMetrics is optimized to run with default flag values (e.g., when they aren't explicitly set).
|
||||
|
||||
For example, it isn't recommended tuning cache sizes in VictoriaMetrics, since it frequently leads to OOM exceptions.
|
||||
[These docs](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#cache-tuning) refer command-line flags, that aren't
|
||||
recommended to tune. If you see that VictoriaMetrics needs increasing some cache sizes for the current workload,
|
||||
then it is better migrating to a host with more memory instead of trying to tune cache sizes manually.
|
||||
For example, it isn't recommended to change cache sizes in VictoriaMetrics, as this frequently leads to OOM exceptions.
|
||||
[These docs](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#cache-tuning) refer to command-line flags that aren't
|
||||
recommended to tune. If you see that VictoriaMetrics needs to increase some cache sizes for the current workload,
|
||||
then it is better to migrate to a host with more memory instead of trying to tune cache sizes manually.
|
||||
|
||||
1. Unexpected heavy queries. The query is considered as heavy if it needs to select and process millions of unique time series.
|
||||
Such query may lead to OOM exception, since VictoriaMetrics needs to keep some of per-series data in memory.
|
||||
VictoriaMetrics provides [various settings](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#resource-usage-limits),
|
||||
1. Unexpected heavy queries. The query is considered heavy if it needs to select and process millions of unique time series.
|
||||
Such a query may cause an OOM exception, as VictoriaMetrics needs to keep some per-series data in memory.
|
||||
VictoriaMetrics provides [various settings](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#resource-usage-limits)
|
||||
that can help limit resource usage.
|
||||
For more context, see [How to optimize PromQL and MetricsQL queries](https://valyala.medium.com/how-to-optimize-promql-and-metricsql-queries-85a1b75bf986).
|
||||
VictoriaMetrics also provides [query tracer](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#query-tracing)
|
||||
to help identify the source of heavy query.
|
||||
to help identify the source of heavy queries. Slow queries can be logged with additional details via [Query execution stats](https://docs.victoriametrics.com/victoriametrics/query-stats/).
|
||||
|
||||
1. Lack of free memory for processing workload spikes. If VictoriaMetrics components use almost all the available memory
|
||||
under the current workload, then it is recommended migrating to a host with bigger amounts of memory.
|
||||
under the current workload, then it is recommended to migrate to a host with larger amounts of memory.
|
||||
This would protect from possible OOM crashes on workload spikes. It is recommended to have at least 50%
|
||||
of free memory for graceful handling of possible workload spikes.
|
||||
of free memory to gracefully handle possible workload spikes.
|
||||
See [capacity planning for single-node VictoriaMetrics](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#capacity-planning)
|
||||
and [capacity planning for cluster version of VictoriaMetrics](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#capacity-planning).
|
||||
and [capacity planning for the cluster version of VictoriaMetrics](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#capacity-planning).
|
||||
|
||||
## Cluster instability
|
||||
|
||||
VictoriaMetrics cluster may become unstable if there is no enough free resources (CPU, RAM, disk IO, network bandwidth)
|
||||
The VictoriaMetrics cluster may become unstable if there are not enough free resources (CPU, RAM, disk I/O, network bandwidth)
|
||||
for processing the current workload.
|
||||
|
||||
The most common sources of cluster instability are:
|
||||
|
||||
- Workload spikes. For example, if the number of active time series increases by 2x while
|
||||
the cluster has no enough free resources for processing the increased workload,
|
||||
then it may become unstable.
|
||||
VictoriaMetrics provides various configuration settings, that can be used for limiting unexpected workload spikes.
|
||||
the cluster does not have enough free resources for processing the increased workload, then it may become unstable.
|
||||
VictoriaMetrics provides several configuration settings to limit unexpected workload spikes.
|
||||
See [these docs](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#resource-usage-limits) for details.
|
||||
|
||||
- Various maintenance tasks such as rolling upgrades or rolling restarts during configuration changes.
|
||||
- Various maintenance tasks, such as rolling upgrades or rolling restarts, during configuration changes.
|
||||
For example, if a cluster contains `N=3` `vmstorage` nodes and they are restarted one-by-one (aka rolling restart),
|
||||
then the cluster will have only `N-1=2` healthy `vmstorage` nodes during the rolling restart.
|
||||
This means that the load on healthy `vmstorage` nodes increases by at least `100%/(N-1)=50%`
|
||||
comparing to the load before rolling restart. E.g. they need to process 50% more incoming
|
||||
compared to the load before rolling restart. E.g., they need to process 50% more incoming
|
||||
data and to return 50% more data during queries. In reality, the load on the remaining `vmstorage`
|
||||
nodes increases even more because they need to register new time series, that were re-routed
|
||||
from temporarily unavailable `vmstorage` node. If `vmstorage` nodes had less than 50%
|
||||
of free resources (CPU, RAM, disk IO) before the rolling restart, then it
|
||||
nodes increases even more because they need to register new time series that were re-routed
|
||||
from a temporarily unavailable `vmstorage` node. If `vmstorage` nodes had less than 50%
|
||||
of free resources (CPU, RAM, disk I/O) before the rolling restart, then it
|
||||
can lead to cluster overload and instability for both data ingestion and querying.
|
||||
|
||||
The workload increase during rolling restart can be reduced by increasing
|
||||
the number of `vmstorage` nodes in the cluster. For example, if VictoriaMetrics cluster contains
|
||||
the number of `vmstorage` nodes in the cluster. For example, if the VictoriaMetrics cluster contains
|
||||
`N=11` `vmstorage` nodes, then the workload increase during rolling restart of `vmstorage` nodes
|
||||
would be `100%/(N-1)=10%`. It is recommended to have at least 8 `vmstorage` nodes in the cluster.
|
||||
The recommended number of `vmstorage` nodes should be multiplied by `-replicationFactor` if replication is enabled -
|
||||
@@ -433,11 +414,11 @@ The most common sources of cluster instability are:
|
||||
for details.
|
||||
|
||||
- Time series sharding. Received time series [are consistently sharded](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#architecture-overview)
|
||||
by `vminsert` between configured `vmstorage` nodes. As a sharding key `vminsert` is using time series name and labels,
|
||||
respecting their order. If the order of labels in time series is constantly changing, this could cause wrong sharding
|
||||
calculation and result in un-even and sub-optimal time series distribution across available vmstorages. It is expected
|
||||
that metrics pushing client is responsible for consistent labels order (like `Prometheus` or `vmagent` during scraping).
|
||||
If this can't be guaranteed, set `-sortLabels=true` command-line flag to `vminsert`. Please note, sorting may increase
|
||||
by `vminsert` between configured `vmstorage` nodes. As a sharding key, `vminsert` is using time series name and labels,
|
||||
respecting their order. If the order of labels in a time series is constantly changing, this could cause wrong sharding
|
||||
calculation and result in uneven and suboptimal time series distribution across available vmstorages. It is expected
|
||||
that the client who is pushing metrics is responsible for consistent label order (like `Prometheus` or `vmagent` during scraping).
|
||||
If this can't be guaranteed, set `-sortLabels=true` command-line flag to `vminsert`. Please note that sorting may increase
|
||||
CPU usage for `vminsert`.
|
||||
|
||||
- Network instability between cluster components (`vminsert`, `vmselect`, `vmstorage`) may lead to increased error rates, timeouts, or degraded performance.
|
||||
@@ -447,14 +428,14 @@ The most common sources of cluster instability are:
|
||||
but can still cause transient network failures. In such cases, check CPU usage at the OS level with higher-resolution tools.
|
||||
Consider increasing `-vmstorageDialTimeout` and `-rpc.handshakeTimeout`{{% available_from "v1.124.0" %}} to mitigate the effects of CPU spikes.
|
||||
|
||||
If resource usage looks normal but networking issues still occur, then the root cause is likely outside VictoriaMetrics.
|
||||
If resource usage appears normal but networking issues persist, the root cause is likely outside VictoriaMetrics.
|
||||
This may be caused by unreliable or congested network links, especially across availability zones or regions.
|
||||
In multi-AZ setups, consider [a multi-level cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#multi-level-cluster-setup) with region-local load balancers to reduce cross-zone connections.
|
||||
If the network cannot be improved, increasing timeouts such as `-vmstorageDialTimeout`, `-rpc.handshakeTimeout`{{% available_from "v1.124.0" %}}, or `-search.maxQueueDuration` may help, but should be done cautiously, as higher timeouts can impact cluster stability in other ways.
|
||||
Keep in mind that VictoriaMetrics assumes reliable networking between components. If the network is unstable, the overall cluster stability may degrade regardless of resource availability.
|
||||
|
||||
The obvious solution against VictoriaMetrics cluster instability is to make sure cluster components
|
||||
have enough free resources for graceful processing of the increased workload.
|
||||
The obvious solution to VictoriaMetrics cluster instability is to make sure cluster components
|
||||
have sufficient free resources to handle the increased workload gracefully.
|
||||
See [capacity planning docs](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#capacity-planning)
|
||||
and [cluster resizing and scalability docs](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#cluster-resizing-and-scalability)
|
||||
for details.
|
||||
@@ -464,10 +445,10 @@ for details.
|
||||
If too much disk space is used by a [single-node VictoriaMetrics](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/) or by `vmstorage` component
|
||||
at [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/), then please check the following:
|
||||
|
||||
- Make sure that there are no old snapshots, since they can occupy disk space. See [how to work with snapshots](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#how-to-work-with-snapshots)
|
||||
, [snapshot troubleshooting](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#snapshot-troubleshooting) and [vmbackup troubleshooting](https://docs.victoriametrics.com/victoriametrics/vmbackup/#troubleshooting).
|
||||
- Make sure that there are no old snapshots, since they can occupy disk space. See [how to work with snapshots](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#how-to-work-with-snapshots),
|
||||
[snapshot troubleshooting](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#snapshot-troubleshooting) and [vmbackup troubleshooting](https://docs.victoriametrics.com/victoriametrics/vmbackup/#troubleshooting).
|
||||
|
||||
- Under normal conditions the size of `<-storageDataPath>/indexdb` folder must be smaller than the size of `<-storageDataPath>/data` folder, where `-storageDataPath`
|
||||
- Under normal conditions, the size of `<-storageDataPath>/indexdb` folder must be smaller than the size of `<-storageDataPath>/data` folder, where `-storageDataPath`
|
||||
is the corresponding command-line flag value. This can be checked by the following query if [VictoriaMetrics monitoring](#monitoring) is properly set up:
|
||||
|
||||
```metricsql
|
||||
@@ -476,22 +457,22 @@ at [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cl
|
||||
sum(vm_data_size_bytes{type=~"(storage|indexdb)/.+"}) without(type)
|
||||
```
|
||||
|
||||
If this query returns values bigger than 0.5, then it is likely there is a [high churn rate](https://docs.victoriametrics.com/victoriametrics/faq/#what-is-high-churn-rate) issue,
|
||||
that results in excess disk space usage for both `indexdb` and `data` folders under `-storageDataPath` folder.
|
||||
The solution is to identify and fix the source of high churn rate with [cardinality explorer](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#cardinality-explorer).
|
||||
If this query returns values greater than 0.5, then it is likely there is a [high churn rate](https://docs.victoriametrics.com/victoriametrics/faq/#what-is-high-churn-rate) issue,
|
||||
that results in excess disk space usage for both the `indexdb` and `data` folders under the `-storageDataPath` folder.
|
||||
The solution is to identify and fix the source of the high churn rate with the [cardinality explorer](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#cardinality-explorer).
|
||||
|
||||
## Monitoring
|
||||
|
||||
Having proper [monitoring](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#monitoring)
|
||||
would help identify and prevent most of the issues listed above.
|
||||
can help identify and prevent most of the issues listed above.
|
||||
|
||||
[Grafana dashboards](https://grafana.com/orgs/victoriametrics/dashboards) contain panels reflecting the
|
||||
health state, resource usage and other specific metrics for VictoriaMetrics components.
|
||||
health state, resource usage, and other specific metrics for VictoriaMetrics components.
|
||||
|
||||
The list of [recommended alerting rules](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/deployment/docker#alerts)
|
||||
for VictoriaMetrics components will notify about issues and provide recommendations for how to solve them.
|
||||
Check the list of [recommended alerting rules](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/deployment/docker#alerts)
|
||||
for VictoriaMetrics components to receive notifications about issues and receive recommendations for resolving them.
|
||||
|
||||
Internally, we heavily rely both on dashboards and alerts, and constantly improve them.
|
||||
Internally, we rely heavily on both dashboards and alerts, and we constantly improve them.
|
||||
It is important to stay up to date with such changes.
|
||||
|
||||
|
||||
@@ -500,11 +481,12 @@ It is important to stay up to date with such changes.
|
||||
On some ZFS filesystems, mixing reads from memory-mapped files (`mmap`) with usage of the `mincore()` syscall can trigger a bug in the ZFS in-memory cache (ARC), potentially resulting in **data read corruption** in VictoriaMetrics processes. This scenario has been observed when VictoriaMetrics instances access data directories on ZFS.
|
||||
|
||||
Symptoms:
|
||||
Note that the source code for the VictoriaMetrics cluster is located in [the cluster](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/cluster) branch.
|
||||
- Unexpected read errors when accessing data on ZFS.
|
||||
- Corrupted or inconsistent query results.
|
||||
- Crashes or panics in storage/query components when reading from ZFS.
|
||||
|
||||
It could be mitigated with `--fs.disableMincore` flag:
|
||||
It could be mitigated with the `--fs.disableMincore` flag:
|
||||
|
||||
```text
|
||||
./bin/victoria-metrics --storageDataPath /path/to/zfs/data --fs.disableMincore
|
||||
|
||||
@@ -25,11 +25,23 @@ The sandbox cluster installation runs under the constant load generated by
|
||||
See also [LTS releases](https://docs.victoriametrics.com/victoriametrics/lts-releases/).
|
||||
|
||||
## tip
|
||||
* FEATURE: [vmbackup](https://docs.victoriametrics.com/vmbackup/): Add cross backup location type copying support.
|
||||
Vmbackup can now copy backups between different storage backends, such as from s3 to local disk or gcs to s3. See [#10401](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/10401)
|
||||
|
||||
* FEATURE: [vmbackup](https://docs.victoriametrics.com/vmbackup/): can now copy backups between different storage backends, such as from s3 to local disk or gcs to s3. See [#10401](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/10401). Thanks to @BenNF for the contribution.
|
||||
* FEATURE: [vmauth](https://docs.victoriametrics.com/victoriametrics/vmauth/): add JWT token authentication support with signature verification based on provided `public_keys`. Read more about configuration in [JWT Token auth proxy](https://docs.victoriametrics.com/victoriametrics/vmauth/#jwt-token-auth-proxy) documentation. See [#10445](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/10445).
|
||||
* FEATURE: all VictoriaMetrics components: expose `process_cpu_seconds_total`, `process_resident_memory_bytes`, and other process-level metrics when running on macOS. See [metrics#75](https://github.com/VictoriaMetrics/metrics/issues/75).
|
||||
* FEATURE: [dashboards/vmauth](https://grafana.com/grafana/dashboards/21394): add `Request body buffering duration` panel to the `Troubleshooting` section. This panel shows the time spent buffering incoming client request bodies, helping identify slow client uploads and potential concurrency issues. The panel is only available when `-requestBufferSize` is non-zero. See [#10309](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/10309).
|
||||
* FEATURE: [vmagent](https://docs.victoriametrics.com/victoriametrics/vmagent/), [vmsingle](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/), `vminsert` and `vmstorage` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): enable [ingestion](https://docs.victoriametrics.com/victoriametrics/vmagent/#metric-metadata) and in-memory [storage](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#metrics-metadata) of metrics metadata by default. Metadata ingestion can be disabled with `-enableMetadata=false`. See [#2974](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2974).
|
||||
* FEATURE: [dashboards/operator](https://grafana.com/grafana/dashboards/17869): extract operator version from metrics instead of hardcoded value
|
||||
* FEATURE: [vmsingle](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/) and `vmstorage` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): increase default value for `-storage.minFreeDiskSpaceBytes` flag from 10M to 100M to reduce risk of panics under high ingestion on small disks. See [#9561](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9561).
|
||||
* FEATURE: [vmagent](https://docs.victoriametrics.com/victoriametrics/vmagent/) and [vmsingle](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/): improve [InfluxDB ingestion](https://docs.victoriametrics.com/victoriametrics/integrations/influxdb/) parsing error message when a closing quote is missing for a quoted field value, by adding a hint that this may be caused by a raw newline (`\n`) inside the quoted field value. See [#10067](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/10067). Thanks to @hklhai for the contribution.
|
||||
* FEATURE: [dashboards/alert-statistics](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/dashboards/alert-statistics.json): add a link to a specific alerting rule on the table of firing alerts. Thanks to @sias32.
|
||||
* FEATURE: [alerts](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/deployment/docker/rules): use `$externalURL` instead of `localhost` in the alerting rules. This should improve usability of the rules if `$externalURL` is correctly configured, without need to update rules annotations. Thanks to @sias32.
|
||||
|
||||
* BUGFIX: [vmsingle](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/) and `vmstorage` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): prevent panic `error parsing regexp: expression nests too deeply` triggered by large repetition ranges in regex, for example `{"__name__"=~"a{0,1000}"}`. See [VictoriaLogs#1112](https://github.com/VictoriaMetrics/VictoriaLogs/issues/1112).
|
||||
* BUGFIX: [vmui](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#vmui): fix escaping for label names with special characters. See [#10485](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/10485).
|
||||
* BUGFIX: `vmstorage` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): properly search tenants for [multitenant](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#multitenancy) query request. See [#10422](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/10422).
|
||||
* BUGFIX: `vmstorage` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): properly apply `extra_filters[]` filter when querying `vm_account_id` or `vm_project_id` labels via [multitenant](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#multitenancy) request for `/api/v1/label/…/values` API. Before, `extra_filters` was ignored.
|
||||
|
||||
|
||||
## [v1.136.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.136.0)
|
||||
|
||||
|
||||
@@ -113,6 +113,56 @@ curl -d 'measurement,tag1=value1,tag2=value2 field1=123,field2=1.23' -X POST 'ht
|
||||
```
|
||||
|
||||
An arbitrary number of lines delimited by '\n' (aka newline char) can be sent in a single request.
|
||||
> **Note:** The above refers to newline characters (`\n`) used as line separators
|
||||
> between multiple points. According to the [Influx Line Protocol specification](https://docs.influxdata.com/influxdb/v2/reference/syntax/line-protocol/),
|
||||
> raw newline bytes **must not** appear inside quoted tag or field values. For example:
|
||||
>
|
||||
> ```text
|
||||
> SystemProperties.line.separator="
|
||||
> "
|
||||
> ```
|
||||
>
|
||||
> A raw newline inside a quoted value will result in a parsing error.
|
||||
>
|
||||
> If metrics are generated by Telegraf and written to VictoriaMetrics, fields containing raw newline characters
|
||||
> (such as `SystemProperties.line.separator`) can be pre-processed using a `regex` processor to escape newline
|
||||
> characters before serialization. For example:
|
||||
|
||||
<details>
|
||||
<summary><strong>telegraf.conf</strong></summary>
|
||||
|
||||
```toml
|
||||
[agent]
|
||||
interval = "60s"
|
||||
flush_interval = "30s"
|
||||
debug = true
|
||||
|
||||
[[inputs.jolokia2_agent]]
|
||||
urls = ["http://springboot:8080/jolokia"]
|
||||
name_prefix = "springBoot."
|
||||
|
||||
[[inputs.jolokia2_agent.metric]]
|
||||
name = "JavaRuntime"
|
||||
mbean = "java.lang:type=Runtime"
|
||||
paths = ["SystemProperties"]
|
||||
|
||||
[[processors.regex]]
|
||||
namepass = ["springBoot.JavaRuntime"]
|
||||
|
||||
[[processors.regex.fields]]
|
||||
key = "SystemProperties.line.separator"
|
||||
pattern = '\n'
|
||||
replacement = '\\n'
|
||||
|
||||
[[outputs.influxdb]]
|
||||
urls = ["http://victoriametrics:8428"]
|
||||
skip_database_creation = true
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
> This ensures the newline is properly escaped and the metric can be ingested successfully.
|
||||
|
||||
After that the data may be read via [/api/v1/export](https://docs.victoriametrics.com/victoriametrics/#how-to-export-data-in-json-line-format) endpoint:
|
||||
```sh
|
||||
curl -G 'http://<victoriametrics-addr>:8428/api/v1/export' -d 'match={__name__=~"measurement_.*"}'
|
||||
|
||||
@@ -658,12 +658,12 @@ e.g. it sets `scrape_series_added` metric to zero. See [these docs](#automatical
|
||||
|
||||
## Metric metadata
|
||||
|
||||
By default, `vmagent` ignores metric metadata exposed by scrape targets in [Prometheus exposition format](https://github.com/prometheus/docs/blob/main/docs/instrumenting/exposition_formats.md), received via [Prometheus remote write v1](https://prometheus.io/docs/specs/prw/remote_write_spec/) or [OpenTelemetry protocol](https://github.com/open-telemetry/opentelemetry-proto/blob/v1.7.0/opentelemetry/proto/metrics/v1/metrics.proto). Set `-enableMetadata=true` to enable metadata processing{{% available_from "v1.125.1" %}}.
|
||||
`vmagent` accepts{{% available_from "#" %}} metric metadata exposed by scrape targets in [Prometheus exposition format](https://github.com/prometheus/docs/blob/main/docs/instrumenting/exposition_formats.md), received via [Prometheus remote write v1](https://prometheus.io/docs/specs/prw/remote_write_spec/) or [OpenTelemetry protocol](https://github.com/open-telemetry/opentelemetry-proto/blob/v1.7.0/opentelemetry/proto/metrics/v1/metrics.proto) by default. Set `-enableMetadata=false` to disable metadata processing{{% available_from "v1.125.1" %}}.
|
||||
During processing, metadata won't be dropped or modified by [relabeling](https://docs.victoriametrics.com/victoriametrics/relabeling/) or [streaming aggregation](https://docs.victoriametrics.com/victoriametrics/stream-aggregation/).
|
||||
|
||||
When `-enableMultitenantHandlers` is enabled, vmagent adds tenant info to metadata received via the [multitenant endpoints](https://docs.victoriametrics.com/victoriametrics/vmagent/#multitenancy) (`/insert/<accountID>/<suffix>`). However, if `vm_account_id` or `vm_project_id` labels are added directly to metrics before reaching vmagent, and vmagent writes to the [vminsert multitenant endpoints](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#multitenancy-via-labels), the tenant info won't be attached and the metadata will be stored under the default tenant of VictoriaMetrics cluster.
|
||||
|
||||
>Enabling metadata requires extra memory, disk space, and network traffic.
|
||||
> Metadata requires extra memory, disk space, and network traffic.
|
||||
|
||||
## Stream parsing mode
|
||||
|
||||
|
||||
@@ -429,9 +429,9 @@ See the docs at https://docs.victoriametrics.com/victoriametrics/vmalert/ .
|
||||
-rule.evalDelay duration
|
||||
Adjustment of the 'time' parameter for rule evaluation requests to compensate intentional data delay from the datasource. Normally, should be equal to '-search.latencyOffset' (cmd-line flag configured for VictoriaMetrics single-node or vmselect). This doesn't apply to groups with eval_offset specified. (default 30s)
|
||||
-rule.maxResolveDuration duration
|
||||
Limits the maxiMum duration for automatic alert expiration, which by default is 4 times evaluationInterval of the parent group
|
||||
Limits the maximum duration for automatic alert expiration, which by default is 4 times evaluationInterval of the parent group
|
||||
-rule.resendDelay duration
|
||||
MiniMum amount of time to wait before resending an alert to notifier.
|
||||
Minimum amount of time to wait before resending an alert to notifier.
|
||||
-rule.resultsLimit int
|
||||
Limits the number of alerts or recording results a single rule can produce. Can be overridden by the limit option under group if specified. If exceeded, the rule will be marked with an error and all its results will be discarded. 0 means no limit.
|
||||
-rule.templates array
|
||||
|
||||
@@ -690,6 +690,7 @@ unauthorized_user:
|
||||
users:
|
||||
- username: "foo"
|
||||
password: "bar"
|
||||
# dump request details on errors (can contain sensitive information)
|
||||
dump_request_on_errors: true
|
||||
url_map:
|
||||
- src_paths: ["/select/.*"]
|
||||
@@ -698,7 +699,6 @@ users:
|
||||
- "ProjectID: 0"
|
||||
url_prefix:
|
||||
- "http://backend:9428/"
|
||||
|
||||
```
|
||||
|
||||
`vmauth` also supports the ability to set and remove HTTP response headers before returning the response from the backend to client.
|
||||
@@ -1066,7 +1066,9 @@ users:
|
||||
#
|
||||
# Regular expressions are allowed in `src_paths` and `src_hosts` entries.
|
||||
- username: "foobar"
|
||||
# log requests that failed url_map rules, for debugging purposes
|
||||
# log requests that failed url_map rules in the following form:
|
||||
# statusCode=<SC> remoteAddr: "<IP>, X-Forwarded-For: <IP>"; requestURI: <URI>; missing route for <URL>"(host: <HOST>; path: <PATH>; args: <ARGS>; headers: <HEADERS>)
|
||||
# May contain sensitive information and is recommended to use only for debugging purposes.
|
||||
dump_request_on_errors: true
|
||||
url_map:
|
||||
- src_paths:
|
||||
|
||||
5
go.mod
@@ -7,6 +7,7 @@ require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4
|
||||
github.com/RoaringBitmap/roaring/v2 v2.14.4
|
||||
github.com/VictoriaMetrics/VictoriaLogs v0.0.0-20260218111324-95b48d57d032
|
||||
github.com/VictoriaMetrics/easyproto v1.2.0
|
||||
github.com/VictoriaMetrics/fastcache v1.13.3
|
||||
@@ -27,7 +28,7 @@ require (
|
||||
github.com/klauspost/compress v1.18.4
|
||||
github.com/prometheus/prometheus v0.309.1
|
||||
github.com/urfave/cli/v2 v2.27.7
|
||||
github.com/valyala/fastjson v1.6.8
|
||||
github.com/valyala/fastjson v1.6.10
|
||||
github.com/valyala/fastrand v1.1.0
|
||||
github.com/valyala/fasttemplate v1.2.2
|
||||
github.com/valyala/gozstd v1.24.0
|
||||
@@ -73,6 +74,7 @@ require (
|
||||
github.com/aws/smithy-go v1.24.0 // indirect
|
||||
github.com/bboreham/go-loser v0.0.0-20230920113527-fcc2c21820a3 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.24.2 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
||||
@@ -106,6 +108,7 @@ require (
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||
github.com/mschoch/smat v0.2.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
|
||||
github.com/oklog/ulid/v2 v2.1.1 // indirect
|
||||
|
||||
10
go.sum
@@ -52,6 +52,8 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapp
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.14.4 h1:4aKySrrg9G/5oRtJ3TrZLObVqxgQ9f1znCRBwEwjuVw=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.14.4/go.mod h1:oMvV6omPWr+2ifRdeZvVJyaz+aoEUopyv5iH0u/+wbY=
|
||||
github.com/VictoriaMetrics/VictoriaLogs v0.0.0-20260218111324-95b48d57d032 h1:kKVeXC+HAcMeMLefoKCWf934y9MoLU8V3Da7k6WP4K8=
|
||||
github.com/VictoriaMetrics/VictoriaLogs v0.0.0-20260218111324-95b48d57d032/go.mod h1:WQ8hGgfKx1lXCCcS1SJSOklN9fToSbshtvKHp3xsv4w=
|
||||
github.com/VictoriaMetrics/easyproto v1.2.0 h1:FJT9uNXA2isppFuJErbLqD306KoFlehl7Wn2dg/6oIE=
|
||||
@@ -120,6 +122,8 @@ github.com/bboreham/go-loser v0.0.0-20230920113527-fcc2c21820a3 h1:6df1vn4bBlDDo
|
||||
github.com/bboreham/go-loser v0.0.0-20230920113527-fcc2c21820a3/go.mod h1:CIWtjkly68+yqLPbvwwR/fjNJA/idrtULjZWh2v1ys0=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bits-and-blooms/bitset v1.24.2 h1:M7/NzVbsytmtfHbumG+K2bremQPMJuqv1JD3vOaFxp0=
|
||||
github.com/bits-and-blooms/bitset v1.24.2/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs=
|
||||
github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
@@ -330,6 +334,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
|
||||
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
|
||||
@@ -404,8 +410,8 @@ github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU=
|
||||
github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fastjson v1.6.8 h1:mklAxcsGgvAa8/4fF9b3peayPTiCxuF0gY5gR2SS9go=
|
||||
github.com/valyala/fastjson v1.6.8/go.mod h1:e6FubmQouUNP73jtMLmcbxS6ydWIpOfhz34TSfO3JaE=
|
||||
github.com/valyala/fastjson v1.6.10 h1:/yjJg8jaVQdYR3arGxPE2X5z89xrlhS0eGXdv+ADTh4=
|
||||
github.com/valyala/fastjson v1.6.10/go.mod h1:e6FubmQouUNP73jtMLmcbxS6ydWIpOfhz34TSfO3JaE=
|
||||
github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8=
|
||||
github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
|
||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||
|
||||
104
lib/jwt/jwt.go
@@ -5,7 +5,6 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
@@ -48,10 +47,10 @@ type body struct {
|
||||
// expired at time unix_ts
|
||||
Exp int64 `json:"exp"`
|
||||
// issued at time unix_ts
|
||||
Iat int64 `json:"iat"`
|
||||
Jti string `json:"jti,omitempty"`
|
||||
Scope string `json:"scope,omitempty"`
|
||||
VMAccess *VMAccessClaim `json:"vm_access"`
|
||||
Iat int64 `json:"iat"`
|
||||
Jti string `json:"jti,omitempty"`
|
||||
Scope string `json:"scope,omitempty"`
|
||||
VMAccess *access `json:"vm_access"`
|
||||
}
|
||||
|
||||
// Labels defines labels added to filters or incoming time series.
|
||||
@@ -71,7 +70,7 @@ func (l Labels) AsExtraLabels() []string {
|
||||
return res
|
||||
}
|
||||
|
||||
type VMAccessClaim struct {
|
||||
type access struct {
|
||||
Tenant TenantID `json:"tenant_id"`
|
||||
Labels Labels `json:"extra_labels,omitempty"`
|
||||
// promql filters applied to each select query
|
||||
@@ -79,86 +78,6 @@ type VMAccessClaim struct {
|
||||
// role can be denied as 1 = read, 2 = write, 3 = read and write
|
||||
// 0 = unconfigured - read and write
|
||||
Mode int `json:"mode,omitempty"`
|
||||
|
||||
// TODO: use different claim struct for vmauth and vmgateway
|
||||
// parsing must be dynamic based on provided hint
|
||||
MetricsAccountID uint32 `json:"-"`
|
||||
MetricsProjectID uint32 `json:"-"`
|
||||
MetricsExtraFilters []string `json:"metrics_extra_filters,omitempty"`
|
||||
MetricsExtraLabels []string `json:"metrics_extra_labels,omitempty"`
|
||||
|
||||
LogsAccountID uint32 `json:"-"`
|
||||
LogsProjectID uint32 `json:"-"`
|
||||
LogsExtraFilters []string `json:"logs_extra_filters,omitempty"`
|
||||
LogsExtraStreamFilters []string `json:"logs_extra_stream_filters,omitempty"`
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements custom JSON unmarshalling for VMAccessClaim.
|
||||
// It deserializes MetricsAccountID, MetricsProjectID, LogsAccountID and LogsProjectID
|
||||
// from JSON numbers as int64 first, then performs range-checked conversion to uint32.
|
||||
// This maintains backward compatibility with JWT tokens that may carry values
|
||||
// representable as int64 but not directly as uint32 (e.g., negative values from
|
||||
// previously signed int fields).
|
||||
func (vma *VMAccessClaim) UnmarshalJSON(data []byte) error {
|
||||
type vmAccessClaimJSON struct {
|
||||
Tenant TenantID `json:"tenant_id"`
|
||||
Labels Labels `json:"extra_labels,omitempty"`
|
||||
// promql filters applied to each select query
|
||||
ExtraFilters []string `json:"extra_filters,omitempty"`
|
||||
Mode int `json:"mode,omitempty"`
|
||||
|
||||
MetricsAccountID int64 `json:"metrics_account_id,omitempty"`
|
||||
MetricsProjectID int64 `json:"metrics_project_id,omitempty"`
|
||||
MetricsExtraFilters []string `json:"metrics_extra_filters,omitempty"`
|
||||
MetricsExtraLabels []string `json:"metrics_extra_labels,omitempty"`
|
||||
|
||||
LogsAccountID int64 `json:"logs_account_id,omitempty"`
|
||||
LogsProjectID int64 `json:"logs_project_id,omitempty"`
|
||||
LogsExtraFilters []string `json:"logs_extra_filters,omitempty"`
|
||||
LogsExtraStreamFilters []string `json:"logs_extra_stream_filters,omitempty"`
|
||||
}
|
||||
|
||||
var raw vmAccessClaimJSON
|
||||
if err := json.Unmarshal(data, &raw); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vma.Tenant = raw.Tenant
|
||||
vma.Labels = raw.Labels
|
||||
vma.ExtraFilters = raw.ExtraFilters
|
||||
vma.Mode = raw.Mode
|
||||
vma.MetricsExtraFilters = raw.MetricsExtraFilters
|
||||
vma.MetricsExtraLabels = raw.MetricsExtraLabels
|
||||
vma.LogsExtraFilters = raw.LogsExtraFilters
|
||||
vma.LogsExtraStreamFilters = raw.LogsExtraStreamFilters
|
||||
|
||||
var err error
|
||||
vma.MetricsAccountID, err = safeUint32("metrics_account_id", raw.MetricsAccountID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vma.MetricsProjectID, err = safeUint32("metrics_project_id", raw.MetricsProjectID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vma.LogsAccountID, err = safeUint32("logs_account_id", raw.LogsAccountID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vma.LogsProjectID, err = safeUint32("logs_project_id", raw.LogsProjectID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// safeUint32 converts an int64 JSON value to uint32 with range checking.
|
||||
func safeUint32(field string, v int64) (uint32, error) {
|
||||
if v < 0 || v > math.MaxUint32 {
|
||||
return 0, fmt.Errorf("field %q value %d is out of uint32 range [0, %d]", field, v, uint32(math.MaxUint32))
|
||||
}
|
||||
return uint32(v), nil
|
||||
}
|
||||
|
||||
// TenantID represents tenantID.
|
||||
@@ -256,7 +175,7 @@ func (t *Token) CanRead() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// AccessLabels returns vm_access labels for given JWT token,
|
||||
// AccessLabels returns access labels for given JWT token,
|
||||
// in key=value format.
|
||||
func (t *Token) AccessLabels() []string {
|
||||
return t.body.VMAccess.Labels.AsExtraLabels()
|
||||
@@ -272,10 +191,6 @@ func (t *Token) ExtraFilters() []string {
|
||||
return t.body.VMAccess.ExtraFilters
|
||||
}
|
||||
|
||||
func (t *Token) VMAccess() *VMAccessClaim {
|
||||
return t.body.VMAccess
|
||||
}
|
||||
|
||||
func parseJWTHeader(data string) (*header, error) {
|
||||
var jh header
|
||||
decoded, err := decodeB64([]byte(data))
|
||||
@@ -315,12 +230,11 @@ func parseJWTBody(data string) (*body, error) {
|
||||
|
||||
// some IDPs encode custom claims as a string
|
||||
// try parsing as an object and fallback to a string
|
||||
var a VMAccessClaim
|
||||
if unmarshalErr := json.Unmarshal(*tb.VMAccess, &a); unmarshalErr != nil {
|
||||
var a access
|
||||
if err := json.Unmarshal(*tb.VMAccess, &a); err != nil {
|
||||
var s string
|
||||
if err := json.Unmarshal(*tb.VMAccess, &s); err != nil {
|
||||
// raw value is not a string either; return the original object unmarshal error
|
||||
return nil, fmt.Errorf("cannot parse jwt body vm_access: %w", unmarshalErr)
|
||||
return nil, fmt.Errorf("cannot parse jwt body vm_access: %w", err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(s), &a); err != nil {
|
||||
|
||||
@@ -141,7 +141,7 @@ func TestParseJWTBody_Failure(t *testing.T) {
|
||||
// vm_access claim invalid type
|
||||
f(
|
||||
`{"vm_access": 123}`,
|
||||
"cannot parse jwt body vm_access: json: cannot unmarshal number into Go value of type jwt.vmAccessClaimJSON",
|
||||
"cannot parse jwt body vm_access: json: cannot unmarshal number into Go value of type string",
|
||||
true,
|
||||
)
|
||||
|
||||
@@ -155,14 +155,14 @@ func TestParseJWTBody_Failure(t *testing.T) {
|
||||
// invalid vm_access: account_id type mismatch
|
||||
f(
|
||||
`{"vm_access": {"tenant_id": {"account_id": "1", "project_id": 5}}}`,
|
||||
`cannot parse jwt body vm_access: json: cannot unmarshal string into Go struct field TenantID.tenant_id.account_id of type int32`,
|
||||
`cannot parse jwt body vm_access: json: cannot unmarshal object into Go value of type string`,
|
||||
true,
|
||||
)
|
||||
|
||||
// invalid vm_access: project_id type mismatch
|
||||
f(
|
||||
`{"vm_access": {"tenant_id": {"account_id": 1, "project_id": "5"}}}`,
|
||||
`cannot parse jwt body vm_access: json: cannot unmarshal string into Go struct field TenantID.tenant_id.project_id of type int32`,
|
||||
`cannot parse jwt body vm_access: json: cannot unmarshal object into Go value of type string`,
|
||||
true,
|
||||
)
|
||||
|
||||
@@ -180,7 +180,7 @@ func TestParseJWTBody_Failure(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}`,
|
||||
`cannot parse jwt body vm_access: json: cannot unmarshal array into Go struct field vmAccessClaimJSON.extra_labels of type jwt.Labels`,
|
||||
`cannot parse jwt body vm_access: json: cannot unmarshal object into Go value of type string`,
|
||||
true,
|
||||
)
|
||||
|
||||
@@ -195,7 +195,7 @@ func TestParseJWTBody_Failure(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}`,
|
||||
`cannot parse jwt body vm_access: json: cannot unmarshal object into Go struct field vmAccessClaimJSON.extra_filters of type string`,
|
||||
`cannot parse jwt body vm_access: json: cannot unmarshal object into Go value of type string`,
|
||||
true,
|
||||
)
|
||||
|
||||
@@ -205,90 +205,6 @@ func TestParseJWTBody_Failure(t *testing.T) {
|
||||
`cannot parse jwt body: json: cannot unmarshal string into Go struct field tbody.exp of type int64`,
|
||||
true,
|
||||
)
|
||||
|
||||
// invalid metrics metrics_account_id claim value type
|
||||
f(
|
||||
`{"vm_access": {"metrics_account_id": "1"}}`,
|
||||
`cannot parse jwt body vm_access: json: cannot unmarshal string into Go struct field vmAccessClaimJSON.metrics_account_id of type int64`,
|
||||
true,
|
||||
)
|
||||
|
||||
// invalid metrics metrics_project_id claim value type
|
||||
f(
|
||||
`{"vm_access": {"metrics_project_id": "1"}}`,
|
||||
`cannot parse jwt body vm_access: json: cannot unmarshal string into Go struct field vmAccessClaimJSON.metrics_project_id of type int64`,
|
||||
true,
|
||||
)
|
||||
|
||||
// invalid metrics metrics_extra_labels claim value type
|
||||
f(
|
||||
`{"vm_access": {"metrics_extra_labels": "aString"}}`,
|
||||
`cannot parse jwt body vm_access: json: cannot unmarshal string into Go struct field vmAccessClaimJSON.metrics_extra_labels of type []string`,
|
||||
true,
|
||||
)
|
||||
|
||||
// invalid metrics metrics_extra_filters claim value type
|
||||
f(
|
||||
`{"vm_access": {"metrics_extra_filters": "aString"}}`,
|
||||
`cannot parse jwt body vm_access: json: cannot unmarshal string into Go struct field vmAccessClaimJSON.metrics_extra_filters of type []string`,
|
||||
true,
|
||||
)
|
||||
|
||||
// invalid metrics logs_account_id claim value type
|
||||
f(
|
||||
`{"vm_access": {"logs_account_id": "1"}}`,
|
||||
`cannot parse jwt body vm_access: json: cannot unmarshal string into Go struct field vmAccessClaimJSON.logs_account_id of type int64`,
|
||||
true,
|
||||
)
|
||||
|
||||
// invalid metrics logs_project_id claim value type
|
||||
f(
|
||||
`{"vm_access": {"logs_project_id": "1"}}`,
|
||||
`cannot parse jwt body vm_access: json: cannot unmarshal string into Go struct field vmAccessClaimJSON.logs_project_id of type int64`,
|
||||
true,
|
||||
)
|
||||
|
||||
// invalid metrics logs_extra_filters claim value type
|
||||
f(
|
||||
`{"vm_access": {"logs_extra_filters": "aString"}}`,
|
||||
`cannot parse jwt body vm_access: json: cannot unmarshal string into Go struct field vmAccessClaimJSON.logs_extra_filters of type []string`,
|
||||
true,
|
||||
)
|
||||
|
||||
// invalid metrics logs_extra_stream_filters claim value type
|
||||
f(
|
||||
`{"vm_access": {"logs_extra_stream_filters": "aString"}}`,
|
||||
`cannot parse jwt body vm_access: json: cannot unmarshal string into Go struct field vmAccessClaimJSON.logs_extra_stream_filters of type []string`,
|
||||
true,
|
||||
)
|
||||
|
||||
// negative metrics_account_id value
|
||||
f(
|
||||
`{"vm_access": {"metrics_account_id": -1}}`,
|
||||
`cannot parse jwt body vm_access: field "metrics_account_id" value -1 is out of uint32 range [0, 4294967295]`,
|
||||
true,
|
||||
)
|
||||
|
||||
// metrics_project_id exceeding uint32 max
|
||||
f(
|
||||
`{"vm_access": {"metrics_project_id": 4294967296}}`,
|
||||
`cannot parse jwt body vm_access: field "metrics_project_id" value 4294967296 is out of uint32 range [0, 4294967295]`,
|
||||
true,
|
||||
)
|
||||
|
||||
// negative logs_account_id value
|
||||
f(
|
||||
`{"vm_access": {"logs_account_id": -100}}`,
|
||||
`cannot parse jwt body vm_access: field "logs_account_id" value -100 is out of uint32 range [0, 4294967295]`,
|
||||
true,
|
||||
)
|
||||
|
||||
// logs_project_id exceeding uint32 max
|
||||
f(
|
||||
`{"vm_access": {"logs_project_id": 5000000000}}`,
|
||||
`cannot parse jwt body vm_access: field "logs_project_id" value 5000000000 is out of uint32 range [0, 4294967295]`,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
func TestParseJWTBody_Success(t *testing.T) {
|
||||
@@ -324,16 +240,13 @@ func TestParseJWTBody_Success(t *testing.T) {
|
||||
if !reflect.DeepEqual(result.VMAccess.ExtraFilters, resultExpected.VMAccess.ExtraFilters) {
|
||||
t.Fatalf("unexpected extra_filters; got %v; want %v", result.VMAccess.ExtraFilters, resultExpected.VMAccess.ExtraFilters)
|
||||
}
|
||||
if !reflect.DeepEqual(result.VMAccess, resultExpected.VMAccess) {
|
||||
t.Fatalf("unexpected VMAccess;\ngot\n%+v\nwant\n%+v", result.VMAccess, resultExpected.VMAccess)
|
||||
}
|
||||
}
|
||||
|
||||
f(`{"vm_access": {}}`, &body{
|
||||
VMAccess: &VMAccessClaim{},
|
||||
VMAccess: &access{},
|
||||
})
|
||||
f(`{"vm_access": {"tenant_id": {}}}`, &body{
|
||||
VMAccess: &VMAccessClaim{},
|
||||
VMAccess: &access{},
|
||||
})
|
||||
|
||||
f(
|
||||
@@ -347,7 +260,7 @@ func TestParseJWTBody_Success(t *testing.T) {
|
||||
}
|
||||
}`,
|
||||
&body{
|
||||
VMAccess: &VMAccessClaim{
|
||||
VMAccess: &access{
|
||||
Tenant: TenantID{
|
||||
ProjectID: 5,
|
||||
AccountID: 1,
|
||||
@@ -367,7 +280,7 @@ func TestParseJWTBody_Success(t *testing.T) {
|
||||
}
|
||||
}`,
|
||||
&body{
|
||||
VMAccess: &VMAccessClaim{
|
||||
VMAccess: &access{
|
||||
Labels: Labels{
|
||||
"project": "dev",
|
||||
"team": "mobile",
|
||||
@@ -387,7 +300,7 @@ func TestParseJWTBody_Success(t *testing.T) {
|
||||
}
|
||||
}`,
|
||||
&body{
|
||||
VMAccess: &VMAccessClaim{
|
||||
VMAccess: &access{
|
||||
ExtraFilters: []string{
|
||||
`{project="dev"}`,
|
||||
`{team=~"mobile"}`,
|
||||
@@ -415,7 +328,7 @@ func TestParseJWTBody_Success(t *testing.T) {
|
||||
}
|
||||
}`,
|
||||
&body{
|
||||
VMAccess: &VMAccessClaim{
|
||||
VMAccess: &access{
|
||||
Tenant: TenantID{
|
||||
ProjectID: 5,
|
||||
AccountID: 1,
|
||||
@@ -446,88 +359,7 @@ func TestParseJWTBody_Success(t *testing.T) {
|
||||
Iat: 1610975889,
|
||||
Jti: "9b194187-6bb7-4244-9d1b-559eab2ef7f3",
|
||||
Scope: "openid email profile",
|
||||
VMAccess: &VMAccessClaim{},
|
||||
},
|
||||
)
|
||||
|
||||
// metrics vm_access claim
|
||||
f(
|
||||
`
|
||||
{
|
||||
"vm_access": {
|
||||
"metrics_account_id": 1,
|
||||
"metrics_project_id": 5,
|
||||
"metrics_extra_labels": [
|
||||
"project=dev",
|
||||
"team=mobile"
|
||||
],
|
||||
"metrics_extra_filters": [
|
||||
"{project=\"dev\"}"
|
||||
]
|
||||
}
|
||||
}`,
|
||||
&body{
|
||||
VMAccess: &VMAccessClaim{
|
||||
MetricsAccountID: 1,
|
||||
MetricsProjectID: 5,
|
||||
MetricsExtraLabels: []string{
|
||||
"project=dev",
|
||||
"team=mobile",
|
||||
},
|
||||
MetricsExtraFilters: []string{
|
||||
`{project="dev"}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
// logs vm_access claim
|
||||
f(
|
||||
`
|
||||
{
|
||||
"vm_access": {
|
||||
"logs_account_id": 1,
|
||||
"logs_project_id": 5,
|
||||
"logs_extra_filters": [
|
||||
"{\"namespace\":\"my-app\",\"env\":\"prod\"}"
|
||||
],
|
||||
"logs_extra_stream_filters": [
|
||||
"{project=\"dev\"}"
|
||||
]
|
||||
}
|
||||
}`,
|
||||
&body{
|
||||
VMAccess: &VMAccessClaim{
|
||||
LogsAccountID: 1,
|
||||
LogsProjectID: 5,
|
||||
LogsExtraFilters: []string{
|
||||
`{"namespace":"my-app","env":"prod"}`,
|
||||
},
|
||||
LogsExtraStreamFilters: []string{
|
||||
`{project="dev"}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
// uint32 max value for metrics_account_id
|
||||
f(
|
||||
`
|
||||
{
|
||||
"vm_access": {
|
||||
"metrics_account_id": 4294967295,
|
||||
"metrics_project_id": 0,
|
||||
"logs_account_id": 4294967295,
|
||||
"logs_project_id": 4294967295
|
||||
}
|
||||
}`,
|
||||
&body{
|
||||
VMAccess: &VMAccessClaim{
|
||||
MetricsAccountID: 4294967295,
|
||||
MetricsProjectID: 0,
|
||||
LogsAccountID: 4294967295,
|
||||
LogsProjectID: 4294967295,
|
||||
},
|
||||
VMAccess: &access{},
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -595,7 +427,7 @@ func TestNewTokenFromRequest_Success(t *testing.T) {
|
||||
Iat: 1610888966,
|
||||
Jti: "09a058a2-0752-4ecd-a4e9-b65e85af423f",
|
||||
Scope: "openid email profile",
|
||||
VMAccess: &VMAccessClaim{
|
||||
VMAccess: &access{
|
||||
Tenant: TenantID{
|
||||
ProjectID: 5,
|
||||
AccountID: 1,
|
||||
@@ -628,7 +460,7 @@ func TestNewTokenFromRequest_Success(t *testing.T) {
|
||||
Iat: 1610888966,
|
||||
Jti: "09a058a2-0752-4ecd-a4e9-b65e85af423f",
|
||||
Scope: "openid email profile",
|
||||
VMAccess: &VMAccessClaim{
|
||||
VMAccess: &access{
|
||||
Tenant: TenantID{
|
||||
ProjectID: 5,
|
||||
AccountID: 1,
|
||||
@@ -657,7 +489,7 @@ func TestNewTokenFromRequest_Success(t *testing.T) {
|
||||
}
|
||||
resultExpected = &Token{
|
||||
body: &body{
|
||||
VMAccess: &VMAccessClaim{
|
||||
VMAccess: &access{
|
||||
Tenant: TenantID{
|
||||
ProjectID: 0,
|
||||
AccountID: 1,
|
||||
@@ -685,7 +517,7 @@ func TestNewTokenFromRequest_Success(t *testing.T) {
|
||||
}
|
||||
resultExpected = &Token{
|
||||
body: &body{
|
||||
VMAccess: &VMAccessClaim{
|
||||
VMAccess: &access{
|
||||
Tenant: TenantID{
|
||||
ProjectID: 0,
|
||||
AccountID: 1,
|
||||
@@ -717,7 +549,7 @@ func TestNewTokenFromRequest_Success(t *testing.T) {
|
||||
Iat: 1610888966,
|
||||
Jti: "09a058a2-0752-4ecd-a4e9-b65e85af423f",
|
||||
Scope: "openid email profile",
|
||||
VMAccess: &VMAccessClaim{
|
||||
VMAccess: &access{
|
||||
Tenant: TenantID{
|
||||
ProjectID: 5,
|
||||
AccountID: 1,
|
||||
@@ -751,7 +583,7 @@ func TestNewTokenFromRequest_Success(t *testing.T) {
|
||||
Iat: 1610888966,
|
||||
Jti: "09a058a2-0752-4ecd-a4e9-b65e85af423f",
|
||||
Scope: "openid email profile",
|
||||
VMAccess: &VMAccessClaim{
|
||||
VMAccess: &access{
|
||||
Tenant: TenantID{
|
||||
ProjectID: 5,
|
||||
AccountID: 1,
|
||||
@@ -784,7 +616,7 @@ func TestNewTokenFromRequest_Success(t *testing.T) {
|
||||
body: &body{
|
||||
Exp: 1725629232,
|
||||
Iat: 1725625332,
|
||||
VMAccess: &VMAccessClaim{
|
||||
VMAccess: &access{
|
||||
Tenant: TenantID{
|
||||
ProjectID: 5,
|
||||
AccountID: 1,
|
||||
@@ -812,7 +644,7 @@ func TestNewTokenFromRequest_Success(t *testing.T) {
|
||||
body: &body{
|
||||
Exp: 1725629232,
|
||||
Iat: 1725625332,
|
||||
VMAccess: &VMAccessClaim{
|
||||
VMAccess: &access{
|
||||
Tenant: TenantID{
|
||||
ProjectID: 5,
|
||||
AccountID: 1,
|
||||
|
||||
@@ -197,4 +197,4 @@ func getRandomBytes(r *rand.Rand) []byte {
|
||||
return iv.Interface().([]byte)
|
||||
}
|
||||
|
||||
var bytesType = reflect.TypeOf([]byte(nil))
|
||||
var bytesType = reflect.TypeFor[[]byte]()
|
||||
|
||||
@@ -2,7 +2,7 @@ package prommetadata
|
||||
|
||||
import "flag"
|
||||
|
||||
var enableMetadata = flag.Bool("enableMetadata", false, "Whether to enable metadata processing for metrics scraped from targets, received via VictoriaMetrics remote write, Prometheus remote write v1 or OpenTelemetry protocol. "+
|
||||
var enableMetadata = flag.Bool("enableMetadata", true, "Whether to enable metadata processing for metrics scraped from targets, received via VictoriaMetrics remote write, Prometheus remote write v1 or OpenTelemetry protocol. "+
|
||||
"See also remoteWrite.maxMetadataPerBlock")
|
||||
|
||||
// IsEnabled reports whether metadata processing is enabled.
|
||||
|
||||
@@ -358,7 +358,11 @@ func parseFieldValue(s string, uc *unmarshalContext) (float64, error) {
|
||||
}
|
||||
if uc.hasQuotedFields && s[0] == '"' {
|
||||
if len(s) < 2 || s[len(s)-1] != '"' {
|
||||
return 0, fmt.Errorf("missing closing quote for quoted field value %s", s)
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/10067
|
||||
return 0, fmt.Errorf("missing closing quote for quoted field value %s; "+
|
||||
"this may be caused by a raw newline (`\\n`) inside the quoted field value",
|
||||
s,
|
||||
)
|
||||
}
|
||||
// Try converting quoted string to number, since sometimes InfluxDB agents
|
||||
// send numbers as strings.
|
||||
|
||||
@@ -2,6 +2,7 @@ package influx
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -140,6 +141,25 @@ func TestRowsUnmarshalFailure(t *testing.T) {
|
||||
f("GET /foo?bar=baz HTTP/1.0")
|
||||
}
|
||||
|
||||
func TestParseFieldValue_MissingClosingQuoteWithRawNewlineHint(t *testing.T) {
|
||||
uc := &unmarshalContext{
|
||||
hasQuotedFields: true,
|
||||
}
|
||||
|
||||
// Simulate the truncated value that happens
|
||||
// after line splitting on raw newline
|
||||
input := "\"hello"
|
||||
|
||||
_, err := parseFieldValue(input, uc)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error for missing closing quote")
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), "this may be caused by a raw newline") {
|
||||
t.Fatalf("unexpected error message: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRowsUnmarshalSuccess(t *testing.T) {
|
||||
f := func(s string, rowsExpected *Rows) {
|
||||
t.Helper()
|
||||
|
||||
@@ -153,7 +153,7 @@ func (wctx *writeRequestContext) PushSample(mm *pb.MetricMetadata, suffix string
|
||||
Samples: wctx.samplesBuf[len(wctx.samplesBuf)-1:],
|
||||
})
|
||||
|
||||
// check if we should flush it right now, if the buf is already huge (2MiB).
|
||||
// check if we should flush it right now, if the buf is already huge (4MiB).
|
||||
if len(wctx.buf) > 4*1024*1024 {
|
||||
if err := wctx.flushFunc(wctx.tss, wctx.mms); err != nil {
|
||||
if wctx.firstErr == nil {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"runtime/debug"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -821,6 +822,11 @@ func sortLabels(labels []prompb.Label) {
|
||||
func TestPutBigWriteRequestContext(t *testing.T) {
|
||||
f := func(l, c, expectC int) {
|
||||
t.Helper()
|
||||
|
||||
// disable GC here so the items in pool won't be recycled too fast. reset it after the test.
|
||||
prevPercent := debug.SetGCPercent(-1)
|
||||
defer debug.SetGCPercent(prevPercent)
|
||||
|
||||
// let's reset the whole pool first, as different test case could interfere
|
||||
wctxPool = sync.Pool{}
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ func TestNewRegexFailure(t *testing.T) {
|
||||
|
||||
f("[foo")
|
||||
f("(foo")
|
||||
// Trigger syntax.ErrInvalidRepeatOp
|
||||
f("a{0,10000}")
|
||||
}
|
||||
|
||||
func TestRegexMatchString(t *testing.T) {
|
||||
@@ -144,6 +146,10 @@ func TestRegexMatchString(t *testing.T) {
|
||||
f("foo(bar|baz)", "a fooxfoobaz a", true)
|
||||
f("foo(bar|baz)", "a fooxfooban a", false)
|
||||
f("foo(bar|baz)", "a fooxfooban foobar a", true)
|
||||
|
||||
// Trigger syntax.ErrNestingDepth
|
||||
// See https://github.com/VictoriaMetrics/VictoriaLogs/issues/1112
|
||||
f("a{0,1000}", "a", true)
|
||||
}
|
||||
|
||||
func TestGetLiterals(t *testing.T) {
|
||||
|
||||
@@ -202,7 +202,11 @@ func simplifyRegex(expr string, keepAnchors bool) (string, string) {
|
||||
// Cannot parse the regexp. Return it all as prefix.
|
||||
return expr, ""
|
||||
}
|
||||
sre = simplifyRegexp(sre, keepAnchors, keepAnchors)
|
||||
sre, ok := simplifyRegexp(sre, keepAnchors, keepAnchors)
|
||||
if !ok {
|
||||
// The regexp is valid but cannot be simplified. Return it all as suffix.
|
||||
return "", expr
|
||||
}
|
||||
if sre == emptyRegexp {
|
||||
return "", ""
|
||||
}
|
||||
@@ -218,7 +222,10 @@ func simplifyRegex(expr string, keepAnchors bool) (string, string) {
|
||||
if len(sre.Sub) == 0 {
|
||||
return prefix, ""
|
||||
}
|
||||
sre = simplifyRegexp(sre, true, keepAnchors)
|
||||
sreNew, ok := simplifyRegexp(sre, true, keepAnchors)
|
||||
if ok {
|
||||
sre = sreNew
|
||||
}
|
||||
}
|
||||
}
|
||||
if _, err := syntax.Compile(sre); err != nil {
|
||||
@@ -232,7 +239,7 @@ func simplifyRegex(expr string, keepAnchors bool) (string, string) {
|
||||
return prefix, s
|
||||
}
|
||||
|
||||
func simplifyRegexp(sre *syntax.Regexp, keepBeginOp, keepEndOp bool) *syntax.Regexp {
|
||||
func simplifyRegexp(sre *syntax.Regexp, keepBeginOp, keepEndOp bool) (*syntax.Regexp, bool) {
|
||||
s := sre.String()
|
||||
for {
|
||||
sre = simplifyRegexpExt(sre, keepBeginOp, keepEndOp)
|
||||
@@ -244,10 +251,19 @@ func simplifyRegexp(sre *syntax.Regexp, keepBeginOp, keepEndOp bool) *syntax.Reg
|
||||
}
|
||||
sNew := sre.String()
|
||||
if sNew == s {
|
||||
return sre
|
||||
return sre, true
|
||||
}
|
||||
sre = mustParseRegexp(sNew)
|
||||
s = sNew
|
||||
|
||||
sreNew, err := parseRegexp(sNew)
|
||||
if err != nil {
|
||||
// Parsing errors can occur due to deep nesting limits or other validation parameters.
|
||||
// This usually happens when the Simplify method returns an optimized regex that is technically valid
|
||||
// but exceeds internal complexity thresholds.
|
||||
// See https://github.com/VictoriaMetrics/VictoriaLogs/issues/1112
|
||||
return nil, false
|
||||
}
|
||||
sre = sreNew
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -96,4 +96,4 @@ func initTestMetaindexRow(mr *metaindexRow) {
|
||||
}
|
||||
}
|
||||
|
||||
var metaindexRowType = reflect.TypeOf(&metaindexRow{})
|
||||
var metaindexRowType = reflect.TypeFor[*metaindexRow]()
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
|
||||
func TestSearchQueryMarshalUnmarshal(t *testing.T) {
|
||||
rnd := rand.New(rand.NewSource(0))
|
||||
typ := reflect.TypeOf(&SearchQuery{})
|
||||
typ := reflect.TypeFor[*SearchQuery]()
|
||||
var buf []byte
|
||||
var sq2 SearchQuery
|
||||
|
||||
|
||||
@@ -891,7 +891,7 @@ func (s *Storage) mustLoadNextDayMetricIDs(date uint64) *nextDayMetricIDs {
|
||||
}
|
||||
|
||||
// Unmarshal uint64set
|
||||
m, tail, err := unmarshalUint64Set(src)
|
||||
m, tail, err := uint64set.Unmarshal(src)
|
||||
if err != nil {
|
||||
logger.Infof("discarding %s because cannot load uint64set: %s", path, err)
|
||||
return e
|
||||
@@ -931,7 +931,7 @@ func (s *Storage) mustLoadHourMetricIDs(hour uint64, name string) *hourMetricIDs
|
||||
}
|
||||
|
||||
// Unmarshal uint64set
|
||||
m, tail, err := unmarshalUint64Set(src)
|
||||
m, tail, err := uint64set.Unmarshal(src)
|
||||
if err != nil {
|
||||
logger.Infof("discarding %s because cannot load uint64set: %s", path, err)
|
||||
return hm
|
||||
@@ -952,7 +952,7 @@ func (s *Storage) mustSaveNextDayMetricIDs(e *nextDayMetricIDs) {
|
||||
dst = encoding.MarshalUint64(dst, e.date)
|
||||
|
||||
// Marshal metricIDs
|
||||
dst = marshalUint64Set(dst, &e.metricIDs)
|
||||
dst = e.metricIDs.Marshal(dst)
|
||||
|
||||
fs.MustWriteSync(path, dst)
|
||||
}
|
||||
@@ -965,37 +965,11 @@ func (s *Storage) mustSaveHourMetricIDs(hm *hourMetricIDs, name string) {
|
||||
dst = encoding.MarshalUint64(dst, hm.hour)
|
||||
|
||||
// Marshal hm.m
|
||||
dst = marshalUint64Set(dst, hm.m)
|
||||
dst = hm.m.Marshal(dst)
|
||||
|
||||
fs.MustWriteSync(path, dst)
|
||||
}
|
||||
|
||||
func unmarshalUint64Set(src []byte) (*uint64set.Set, []byte, error) {
|
||||
mLen := encoding.UnmarshalUint64(src)
|
||||
src = src[8:]
|
||||
if uint64(len(src)) < 8*mLen {
|
||||
return nil, nil, fmt.Errorf("cannot unmarshal uint64set; got %d bytes; want at least %d bytes", len(src), 8*mLen)
|
||||
}
|
||||
m := &uint64set.Set{}
|
||||
for range mLen {
|
||||
metricID := encoding.UnmarshalUint64(src)
|
||||
src = src[8:]
|
||||
m.Add(metricID)
|
||||
}
|
||||
return m, src, nil
|
||||
}
|
||||
|
||||
func marshalUint64Set(dst []byte, m *uint64set.Set) []byte {
|
||||
dst = encoding.MarshalUint64(dst, uint64(m.Len()))
|
||||
m.ForEach(func(part []uint64) bool {
|
||||
for _, metricID := range part {
|
||||
dst = encoding.MarshalUint64(dst, metricID)
|
||||
}
|
||||
return true
|
||||
})
|
||||
return dst
|
||||
}
|
||||
|
||||
func mustGetMinTimestampForCompositeIndex(metadataDir string, isEmptyDB bool) int64 {
|
||||
path := filepath.Join(metadataDir, "minTimestampForCompositeIndex")
|
||||
minTimestamp, err := loadMinTimestampForCompositeIndex(path)
|
||||
|
||||
@@ -271,7 +271,7 @@ func TestUpdateCurrHourMetricIDs(t *testing.T) {
|
||||
|
||||
func TestMetricRowMarshalUnmarshal(t *testing.T) {
|
||||
var buf []byte
|
||||
typ := reflect.TypeOf(&MetricRow{})
|
||||
typ := reflect.TypeFor[*MetricRow]()
|
||||
rng := rand.New(rand.NewSource(1))
|
||||
|
||||
for range 1000 {
|
||||
|
||||
@@ -103,7 +103,7 @@ func initTestTSID(tsid *TSID) {
|
||||
*tsid = *rndTSID
|
||||
}
|
||||
|
||||
var tsidType = reflect.TypeOf(&TSID{})
|
||||
var tsidType = reflect.TypeFor[*TSID]()
|
||||
|
||||
var (
|
||||
rnd = rand.New(rand.NewSource(1))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package uint64set
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/bits"
|
||||
"slices"
|
||||
"sort"
|
||||
@@ -8,6 +9,7 @@ import (
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/slicesutil"
|
||||
)
|
||||
|
||||
@@ -38,6 +40,49 @@ func (s *bucket32Sorter) Swap(i, j int) {
|
||||
a[i], a[j] = a[j], a[i]
|
||||
}
|
||||
|
||||
// Unmarshal creates an instance of a set from bytes.
|
||||
//
|
||||
// The first 8 src bytes contain the set length (number of the elements in the
|
||||
// set). Since each element is 8-byte long, the number of remaining src bytes
|
||||
// must be at least 8*length, or else the function will return an error. The
|
||||
// function will read exactly 8*length bytes and construct an instance of a
|
||||
// set. The remaining src bytes will be returned along with the set.
|
||||
func Unmarshal(src []byte) (*Set, []byte, error) {
|
||||
if len(src) < 8 {
|
||||
return nil, nil, fmt.Errorf("cannot unmarshal uint64set; got %d bytes; want at least 8 bytes", len(src))
|
||||
}
|
||||
sLen := encoding.UnmarshalUint64(src)
|
||||
src = src[8:]
|
||||
if uint64(len(src)) < 8*sLen {
|
||||
return nil, nil, fmt.Errorf("cannot unmarshal uint64set; got %d bytes; want at least %d bytes", len(src), 8*sLen)
|
||||
}
|
||||
s := &Set{}
|
||||
for range sLen {
|
||||
e := encoding.UnmarshalUint64(src)
|
||||
src = src[8:]
|
||||
s.Add(e)
|
||||
}
|
||||
return s, src, nil
|
||||
}
|
||||
|
||||
// Marshal encodes the set as a sequence of bytes.
|
||||
//
|
||||
// The first 8 bytes contain the length of the set (number of the elements the
|
||||
// set contains). The subsequent bytes are actual uint64 elements.
|
||||
//
|
||||
// The marshaling result is appended to the end of dst, i.e. the initial dst
|
||||
// content is not overwritten.
|
||||
func (s *Set) Marshal(dst []byte) []byte {
|
||||
dst = encoding.MarshalUint64(dst, uint64(s.Len()))
|
||||
s.ForEach(func(part []uint64) bool {
|
||||
for _, e := range part {
|
||||
dst = encoding.MarshalUint64(dst, e)
|
||||
}
|
||||
return true
|
||||
})
|
||||
return dst
|
||||
}
|
||||
|
||||
// Clone returns an independent copy of s.
|
||||
func (s *Set) Clone() *Set {
|
||||
if s == nil || s.itemsCount == 0 {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package uint64set
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
@@ -895,3 +896,115 @@ func TestSubtract(t *testing.T) {
|
||||
f(a, b1, want1)
|
||||
f(a, b2, want2)
|
||||
}
|
||||
|
||||
func TestUnmarshal(t *testing.T) {
|
||||
n := uint64(100_000)
|
||||
src := make([]byte, (n+1)*8+10)
|
||||
binary.BigEndian.PutUint64(src, n)
|
||||
want := &Set{}
|
||||
for i := range n {
|
||||
binary.BigEndian.PutUint64(src[(i+1)*8:], i)
|
||||
want.Add(i)
|
||||
}
|
||||
|
||||
got, gotTail, err := Unmarshal(src)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !got.Equal(want) {
|
||||
diff := cmp.Diff(want.AppendTo(nil), got.AppendTo(nil))
|
||||
t.Fatalf("unexpected set (-want, +got):\n%s", diff)
|
||||
}
|
||||
wantTail := make([]byte, 10)
|
||||
if diff := cmp.Diff(wantTail, gotTail); diff != "" {
|
||||
t.Fatalf("unexpected tail bytes (-want, +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshal_zeroLenSet(t *testing.T) {
|
||||
src := make([]byte, 8)
|
||||
want := &Set{}
|
||||
got, gotTail, err := Unmarshal(src)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !got.Equal(want) {
|
||||
diff := cmp.Diff(want.AppendTo(nil), got.AppendTo(nil))
|
||||
t.Fatalf("unexpected set (-want, +got):\n%s", diff)
|
||||
}
|
||||
wantTail := []byte{}
|
||||
if diff := cmp.Diff(wantTail, gotTail); diff != "" {
|
||||
t.Fatalf("unexpected tail bytes (-want, +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshal_tooShortToIncludeSetLen(t *testing.T) {
|
||||
src := make([]byte, 7) // set length occupies 8 bytes.
|
||||
got, gotTail, err := Unmarshal(src)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error but got nil")
|
||||
}
|
||||
if got != nil {
|
||||
t.Fatalf("unexpected nil set but got: %v", got.AppendTo(nil))
|
||||
}
|
||||
if gotTail != nil {
|
||||
t.Fatalf("unexpected nil tail bytes but got: %v", gotTail)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshal_numElementsLessThanLen(t *testing.T) {
|
||||
n := uint64(10)
|
||||
src := make([]byte, n*8) // contains only 9 elements instead of 10.
|
||||
binary.BigEndian.PutUint64(src, n)
|
||||
for i := range n - 1 {
|
||||
binary.BigEndian.PutUint64(src[(i+1)*8:], i)
|
||||
}
|
||||
|
||||
got, gotTail, err := Unmarshal(src)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error but got nil")
|
||||
}
|
||||
if got != nil {
|
||||
t.Fatalf("unexpected nil set but got: %v", got.AppendTo(nil))
|
||||
}
|
||||
if gotTail != nil {
|
||||
t.Fatalf("unexpected nil tail bytes but got: %v", gotTail)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshal_emptyDst(t *testing.T) {
|
||||
n := uint64(100_000)
|
||||
want := make([]byte, (n+1)*8)
|
||||
binary.BigEndian.PutUint64(want, n)
|
||||
s := &Set{}
|
||||
for i := range n {
|
||||
binary.BigEndian.PutUint64(want[(i+1)*8:], i)
|
||||
s.Add(i)
|
||||
}
|
||||
|
||||
got := s.Marshal(nil)
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Fatalf("unexpected bytes (-want, +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshal_nonEmptyDst(t *testing.T) {
|
||||
n := uint64(100_000)
|
||||
got := make([]byte, 10)
|
||||
want := make([]byte, 10+(n+1)*8)
|
||||
for i := range 10 {
|
||||
got[i] = byte(i)
|
||||
want[i] = byte(i)
|
||||
}
|
||||
binary.BigEndian.PutUint64(want[10:], n)
|
||||
s := &Set{}
|
||||
for i := range n {
|
||||
binary.BigEndian.PutUint64(want[10+(i+1)*8:], i)
|
||||
s.Add(i)
|
||||
}
|
||||
|
||||
got = s.Marshal(got)
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Fatalf("unexpected bytes (-want, +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,14 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/RoaringBitmap/roaring/v2/roaring64"
|
||||
"github.com/valyala/fastrand"
|
||||
)
|
||||
|
||||
func BenchmarkAddMulti(b *testing.B) {
|
||||
for _, itemsCount := range []int{1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7} {
|
||||
start := uint64(time.Now().UnixNano())
|
||||
sa := createRangeSet(start, itemsCount)
|
||||
a := sa.AppendTo(nil)
|
||||
a := createRangeSet(start, itemsCount).ToArray()
|
||||
b.Run(fmt.Sprintf("items_%d", itemsCount), func(b *testing.B) {
|
||||
benchmarkAddMulti(b, a)
|
||||
})
|
||||
@@ -22,8 +22,7 @@ func BenchmarkAddMulti(b *testing.B) {
|
||||
func BenchmarkAdd(b *testing.B) {
|
||||
for _, itemsCount := range []int{1e3, 1e4, 1e5, 1e6, 1e7} {
|
||||
start := uint64(time.Now().UnixNano())
|
||||
sa := createRangeSet(start, itemsCount)
|
||||
a := sa.AppendTo(nil)
|
||||
a := createRangeSet(start, itemsCount).ToArray()
|
||||
b.Run(fmt.Sprintf("items_%d", itemsCount), func(b *testing.B) {
|
||||
benchmarkAdd(b, a)
|
||||
})
|
||||
@@ -68,7 +67,7 @@ func benchmarkAdd(b *testing.B, a []uint64) {
|
||||
b.SetBytes(int64(len(a)))
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
var s Set
|
||||
s := roaring64.New()
|
||||
for _, x := range a {
|
||||
s.Add(x)
|
||||
}
|
||||
@@ -81,26 +80,26 @@ func benchmarkAddMulti(b *testing.B, a []uint64) {
|
||||
b.SetBytes(int64(len(a)))
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
var s Set
|
||||
s := roaring64.New()
|
||||
n := 0
|
||||
for n < len(a) {
|
||||
m := min(n+64, len(a))
|
||||
s.AddMulti(a[n:m])
|
||||
s.AddMany(a[n:m])
|
||||
n = m
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func benchmarkUnion(b *testing.B, sa, sb *Set) {
|
||||
func benchmarkUnion(b *testing.B, sa, sb *roaring64.Bitmap) {
|
||||
b.ReportAllocs()
|
||||
b.SetBytes(int64(sa.Len() + sb.Len()))
|
||||
b.SetBytes(int64(sa.Stats().Cardinality + sb.Stats().Cardinality))
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
saCopy := sa.Clone()
|
||||
sbCopy := sb.Clone()
|
||||
saCopy.Union(sb)
|
||||
sbCopy.Union(sa)
|
||||
saCopy.Or(sb)
|
||||
sbCopy.Or(sa)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -138,15 +137,15 @@ func BenchmarkIntersectFullOverlap(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkIntersect(b *testing.B, sa, sb *Set) {
|
||||
func benchmarkIntersect(b *testing.B, sa, sb *roaring64.Bitmap) {
|
||||
b.ReportAllocs()
|
||||
b.SetBytes(int64(sa.Len() + sb.Len()))
|
||||
b.SetBytes(int64(sa.Stats().Cardinality + sb.Stats().Cardinality))
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
saCopy := sa.Clone()
|
||||
sbCopy := sb.Clone()
|
||||
saCopy.Intersect(sb)
|
||||
sbCopy.Intersect(sa)
|
||||
saCopy.And(sb)
|
||||
sbCopy.And(sa)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -156,10 +155,10 @@ func BenchmarkSubtract(b *testing.B) {
|
||||
sa := createRangeSet(startA, int(itemsCountA))
|
||||
sb := createRangeSet(startB, int(itemsCountB))
|
||||
b.ReportAllocs()
|
||||
b.SetBytes(int64(sa.Len() + sb.Len()))
|
||||
b.SetBytes(int64(sa.Stats().Cardinality + sb.Stats().Cardinality))
|
||||
for b.Loop() {
|
||||
saCopy := sa.Clone()
|
||||
saCopy.Subtract(sb)
|
||||
saCopy.AndNot(sb)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,13 +210,13 @@ func BenchmarkSubtract(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
func createRangeSet(start uint64, itemsCount int) *Set {
|
||||
var s Set
|
||||
func createRangeSet(start uint64, itemsCount int) *roaring64.Bitmap {
|
||||
s := roaring64.New()
|
||||
for i := range itemsCount {
|
||||
n := start + uint64(i)
|
||||
s.Add(n)
|
||||
}
|
||||
return &s
|
||||
return s
|
||||
}
|
||||
|
||||
func BenchmarkSetAddRandomLastBits(b *testing.B) {
|
||||
@@ -231,7 +230,7 @@ func BenchmarkSetAddRandomLastBits(b *testing.B) {
|
||||
var rng fastrand.RNG
|
||||
for pb.Next() {
|
||||
start := uint64(time.Now().UnixNano())
|
||||
var s Set
|
||||
s := roaring64.New()
|
||||
for range int(itemsCount) {
|
||||
n := start | (uint64(rng.Uint32()) & mask)
|
||||
s.Add(n)
|
||||
@@ -273,7 +272,7 @@ func BenchmarkSetAddWithAllocs(b *testing.B) {
|
||||
for pb.Next() {
|
||||
start := uint64(time.Now().UnixNano())
|
||||
end := start + itemsCount
|
||||
var s Set
|
||||
s := roaring64.New()
|
||||
n := start
|
||||
for n < end {
|
||||
s.Add(n)
|
||||
@@ -357,13 +356,13 @@ func BenchmarkSetHasHitRandomLastBits(b *testing.B) {
|
||||
mask := (uint64(1) << lastBits) - 1
|
||||
b.Run(fmt.Sprintf("lastBits_%d", lastBits), func(b *testing.B) {
|
||||
start := uint64(time.Now().UnixNano())
|
||||
var s Set
|
||||
s := roaring64.New()
|
||||
var rng fastrand.RNG
|
||||
for range int(itemsCount) {
|
||||
n := start | (uint64(rng.Uint32()) & mask)
|
||||
s.Add(n)
|
||||
}
|
||||
a := s.AppendTo(nil)
|
||||
a := s.ToArray()
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
@@ -371,7 +370,7 @@ func BenchmarkSetHasHitRandomLastBits(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
for _, n := range a {
|
||||
if !s.Has(n) {
|
||||
if !s.Contains(n) {
|
||||
panic("unexpected miss")
|
||||
}
|
||||
}
|
||||
@@ -415,7 +414,7 @@ func BenchmarkSetHasHit(b *testing.B) {
|
||||
b.Run(fmt.Sprintf("items_%d", itemsCount), func(b *testing.B) {
|
||||
start := uint64(time.Now().UnixNano())
|
||||
end := start + itemsCount
|
||||
var s Set
|
||||
s := roaring64.New()
|
||||
n := start
|
||||
for n < end {
|
||||
s.Add(n)
|
||||
@@ -429,7 +428,7 @@ func BenchmarkSetHasHit(b *testing.B) {
|
||||
for pb.Next() {
|
||||
n := start
|
||||
for n < end {
|
||||
if !s.Has(n) {
|
||||
if !s.Contains(n) {
|
||||
panic("unexpected miss")
|
||||
}
|
||||
n++
|
||||
@@ -475,7 +474,7 @@ func BenchmarkSetHasMiss(b *testing.B) {
|
||||
b.Run(fmt.Sprintf("items_%d", itemsCount), func(b *testing.B) {
|
||||
start := uint64(time.Now().UnixNano())
|
||||
end := start + itemsCount
|
||||
var s Set
|
||||
s := roaring64.New()
|
||||
n := start
|
||||
for n < end {
|
||||
s.Add(n)
|
||||
@@ -490,7 +489,7 @@ func BenchmarkSetHasMiss(b *testing.B) {
|
||||
n := end
|
||||
nEnd := end + itemsCount
|
||||
for n < nEnd {
|
||||
if s.Has(n) {
|
||||
if s.Contains(n) {
|
||||
panic("unexpected hit")
|
||||
}
|
||||
n++
|
||||
@@ -531,3 +530,62 @@ func BenchmarkMapHasMiss(b *testing.B) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSizeBytes_uint64slice(b *testing.B) {
|
||||
benchmarkSizeBytes(b, func(start, n, step uint64) uint64 {
|
||||
s := []uint64{}
|
||||
for i := range n {
|
||||
v := start + i*step
|
||||
s = append(s, v)
|
||||
}
|
||||
return uint64(len(s) * 8)
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkSizeBytes_uint64set(b *testing.B) {
|
||||
benchmarkSizeBytes(b, func(start, n, step uint64) uint64 {
|
||||
s := &Set{}
|
||||
for i := range n {
|
||||
v := start + i*step
|
||||
s.Add(v)
|
||||
}
|
||||
return s.SizeBytes()
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkSizeBytes_roaring(b *testing.B) {
|
||||
benchmarkSizeBytes(b, func(start, n, step uint64) uint64 {
|
||||
s := roaring64.New()
|
||||
for i := range n {
|
||||
v := start + i*step
|
||||
s.Add(v)
|
||||
}
|
||||
stats := s.Stats()
|
||||
sizeBytes := stats.ArrayContainerBytes
|
||||
sizeBytes += stats.BitmapContainerBytes
|
||||
sizeBytes += stats.RunContainerBytes
|
||||
return sizeBytes
|
||||
})
|
||||
}
|
||||
|
||||
func benchmarkSizeBytes(b *testing.B, sizeBytesFunc func(start, n, step uint64) uint64) {
|
||||
f := func(b *testing.B, n, step uint64) {
|
||||
start := uint64(time.Now().UnixNano())
|
||||
var sizeBytes uint64
|
||||
for b.Loop() {
|
||||
sizeBytes = sizeBytesFunc(start, n, step)
|
||||
}
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ReportMetric(float64(sizeBytes), "bytes")
|
||||
}
|
||||
|
||||
for _, n := range []uint64{15_000_000} {
|
||||
for _, step := range []uint64{1, 10, 100, 1e3, 1e4, 1e5, 1e6} {
|
||||
name := fmt.Sprintf("%d/%d", n, step)
|
||||
b.Run(name, func(b *testing.B) {
|
||||
f(b, n, step)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
19
vendor/github.com/RoaringBitmap/roaring/v2/.drone.yml
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
kind: pipeline
|
||||
name: default
|
||||
|
||||
workspace:
|
||||
base: /go
|
||||
path: src/github.com/RoaringBitmap/roaring
|
||||
|
||||
steps:
|
||||
- name: test
|
||||
image: golang
|
||||
commands:
|
||||
- go get -t
|
||||
- go test
|
||||
- go build -tags appengine
|
||||
- go test -tags appengine
|
||||
- GOARCH=386 go build
|
||||
- GOARCH=386 go test
|
||||
- GOARCH=arm go build
|
||||
- GOARCH=arm64 go build
|
||||
5
vendor/github.com/RoaringBitmap/roaring/v2/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
*~
|
||||
roaring-fuzz.zip
|
||||
workdir
|
||||
coverage.out
|
||||
testdata/all3.classic
|
||||
0
vendor/github.com/RoaringBitmap/roaring/v2/.gitmodules
generated
vendored
Normal file
11
vendor/github.com/RoaringBitmap/roaring/v2/AUTHORS
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# This is the official list of roaring authors for copyright purposes.
|
||||
|
||||
Todd Gruben (@tgruben),
|
||||
Daniel Lemire (@lemire),
|
||||
Elliot Murphy (@statik),
|
||||
Bob Potter (@bpot),
|
||||
Tyson Maly (@tvmaly),
|
||||
Will Glynn (@willglynn),
|
||||
Brent Pedersen (@brentp)
|
||||
Maciej Biłas (@maciej),
|
||||
Joe Nall (@joenall)
|
||||
18
vendor/github.com/RoaringBitmap/roaring/v2/CONTRIBUTORS
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
# This is the official list of roaring contributors
|
||||
|
||||
Todd Gruben (@tgruben),
|
||||
Daniel Lemire (@lemire),
|
||||
Elliot Murphy (@statik),
|
||||
Bob Potter (@bpot),
|
||||
Tyson Maly (@tvmaly),
|
||||
Will Glynn (@willglynn),
|
||||
Brent Pedersen (@brentp),
|
||||
Jason E. Aten (@glycerine),
|
||||
Vali Malinoiu (@0x4139),
|
||||
Forud Ghafouri (@fzerorubigd),
|
||||
Joe Nall (@joenall),
|
||||
(@fredim),
|
||||
Edd Robinson (@e-dard),
|
||||
Alexander Petrov (@alldroll),
|
||||
Guy Molinari (@guymolinari),
|
||||
Ling Jin (@JinLingChristopher)
|
||||
235
vendor/github.com/RoaringBitmap/roaring/v2/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,235 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2016 by the authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
================================================================================
|
||||
|
||||
Portions of runcontainer.go are from the Go standard library, which is licensed
|
||||
under:
|
||||
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
202
vendor/github.com/RoaringBitmap/roaring/v2/LICENSE-2.0.txt
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2016 by the authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
432
vendor/github.com/RoaringBitmap/roaring/v2/README.md
generated
vendored
Normal file
@@ -0,0 +1,432 @@
|
||||
# roaring
|
||||
|
||||
[](https://godoc.org/github.com/RoaringBitmap/roaring) [](https://goreportcard.com/report/github.com/RoaringBitmap/roaring)
|
||||
|
||||

|
||||

|
||||

|
||||
=============
|
||||
|
||||
This is a go version of the Roaring bitmap data structure.
|
||||
|
||||
Roaring bitmaps are used by several major systems such as [Apache Lucene][lucene] and derivative systems such as [Solr][solr] and
|
||||
[Elasticsearch][elasticsearch], [Apache Druid (Incubating)][druid], [LinkedIn Pinot][pinot], [Netflix Atlas][atlas], [Apache Spark][spark], [OpenSearchServer][opensearchserver], [anacrolix/torrent][anacrolix/torrent], [Whoosh][whoosh], [Redpanda](https://github.com/redpanda-data/redpanda), [Pilosa][pilosa], [Microsoft Visual Studio Team Services (VSTS)][vsts], and eBay's [Apache Kylin][kylin]. The YouTube SQL Engine, [Google Procella](https://research.google/pubs/pub48388/), uses Roaring bitmaps for indexing.
|
||||
|
||||
[lucene]: https://lucene.apache.org/
|
||||
[solr]: https://lucene.apache.org/solr/
|
||||
[elasticsearch]: https://www.elastic.co/products/elasticsearch
|
||||
[druid]: https://druid.apache.org/
|
||||
[spark]: https://spark.apache.org/
|
||||
[opensearchserver]: http://www.opensearchserver.com
|
||||
[anacrolix/torrent]: https://github.com/anacrolix/torrent
|
||||
[whoosh]: https://bitbucket.org/mchaput/whoosh/wiki/Home
|
||||
[pilosa]: https://www.pilosa.com/
|
||||
[kylin]: http://kylin.apache.org/
|
||||
[pinot]: http://github.com/linkedin/pinot/wiki
|
||||
[vsts]: https://www.visualstudio.com/team-services/
|
||||
[atlas]: https://github.com/Netflix/atlas
|
||||
[quanta]: https://github.com/disney/quanta
|
||||
|
||||
Roaring bitmaps are found to work well in many important applications:
|
||||
|
||||
> Use Roaring for bitmap compression whenever possible. Do not use other bitmap compression methods ([Wang et al., SIGMOD 2017](http://db.ucsd.edu/wp-content/uploads/2017/03/sidm338-wangA.pdf))
|
||||
|
||||
|
||||
The ``roaring`` Go library is used by
|
||||
* [anacrolix/torrent]
|
||||
* [InfluxDB](https://www.influxdata.com)
|
||||
* [Pilosa](https://www.pilosa.com/)
|
||||
* [Bleve](http://www.blevesearch.com)
|
||||
* [Weaviate](https://github.com/weaviate/weaviate)
|
||||
* [lindb](https://github.com/lindb/lindb)
|
||||
* [Elasticell](https://github.com/deepfabric/elasticell)
|
||||
* [SourceGraph](https://github.com/sourcegraph/sourcegraph)
|
||||
* [M3](https://github.com/m3db/m3)
|
||||
* [trident](https://github.com/NetApp/trident)
|
||||
* [Husky](https://www.datadoghq.com/blog/engineering/introducing-husky/)
|
||||
* [FrostDB](https://github.com/polarsignals/frostdb)
|
||||
* [Disney Quanta](https://github.com/disney/quanta)
|
||||
|
||||
|
||||
|
||||
|
||||
This library is used in production in several systems, it is part of the [Awesome Go collection](https://awesome-go.com).
|
||||
|
||||
|
||||
There are also [Java](https://github.com/RoaringBitmap/RoaringBitmap) and [C/C++](https://github.com/RoaringBitmap/CRoaring) versions. The Java, C, C++ and Go version are binary compatible: e.g, you can save bitmaps
|
||||
from a Java program and load them back in Go, and vice versa. We have a [format specification](https://github.com/RoaringBitmap/RoaringFormatSpec).
|
||||
|
||||
|
||||
This code is licensed under Apache License, Version 2.0 (ASL2.0).
|
||||
|
||||
Copyright 2016-... by the authors.
|
||||
|
||||
When should you use a bitmap?
|
||||
===================================
|
||||
|
||||
|
||||
Sets are a fundamental abstraction in
|
||||
software. They can be implemented in various
|
||||
ways, as hash sets, as trees, and so forth.
|
||||
In databases and search engines, sets are often an integral
|
||||
part of indexes. For example, we may need to maintain a set
|
||||
of all documents or rows (represented by numerical identifier)
|
||||
that satisfy some property. Besides adding or removing
|
||||
elements from the set, we need fast functions
|
||||
to compute the intersection, the union, the difference between sets, and so on.
|
||||
|
||||
|
||||
To implement a set
|
||||
of integers, a particularly appealing strategy is the
|
||||
bitmap (also called bitset or bit vector). Using n bits,
|
||||
we can represent any set made of the integers from the range
|
||||
[0,n): the ith bit is set to one if integer i is present in the set.
|
||||
Commodity processors use words of W=32 or W=64 bits. By combining many such words, we can
|
||||
support large values of n. Intersections, unions and differences can then be implemented
|
||||
as bitwise AND, OR and ANDNOT operations.
|
||||
More complicated set functions can also be implemented as bitwise operations.
|
||||
|
||||
When the bitset approach is applicable, it can be orders of
|
||||
magnitude faster than other possible implementation of a set (e.g., as a hash set)
|
||||
while using several times less memory.
|
||||
|
||||
However, a bitset, even a compressed one is not always applicable. For example, if
|
||||
you have 1000 random-looking integers, then a simple array might be the best representation.
|
||||
We refer to this case as the "sparse" scenario.
|
||||
|
||||
When should you use compressed bitmaps?
|
||||
===================================
|
||||
|
||||
An uncompressed BitSet can use a lot of memory. For example, if you take a BitSet
|
||||
and set the bit at position 1,000,000 to true and you have just over 100kB. That is over 100kB
|
||||
to store the position of one bit. This is wasteful even if you do not care about memory:
|
||||
suppose that you need to compute the intersection between this BitSet and another one
|
||||
that has a bit at position 1,000,001 to true, then you need to go through all these zeroes,
|
||||
whether you like it or not. That can become very wasteful.
|
||||
|
||||
This being said, there are definitively cases where attempting to use compressed bitmaps is wasteful.
|
||||
For example, if you have a small universe size. E.g., your bitmaps represent sets of integers
|
||||
from [0,n) where n is small (e.g., n=64 or n=128). If you can use uncompressed BitSet and
|
||||
it does not blow up your memory usage, then compressed bitmaps are probably not useful
|
||||
to you. In fact, if you do not need compression, then a BitSet offers remarkable speed.
|
||||
|
||||
The sparse scenario is another use case where compressed bitmaps should not be used.
|
||||
Keep in mind that random-looking data is usually not compressible. E.g., if you have a small set of
|
||||
32-bit random integers, it is not mathematically possible to use far less than 32 bits per integer,
|
||||
and attempts at compression can be counterproductive.
|
||||
|
||||
How does Roaring compares with the alternatives?
|
||||
==================================================
|
||||
|
||||
|
||||
Most alternatives to Roaring are part of a larger family of compressed bitmaps that are run-length-encoded
|
||||
bitmaps. They identify long runs of 1s or 0s and they represent them with a marker word.
|
||||
If you have a local mix of 1s and 0, you use an uncompressed word.
|
||||
|
||||
There are many formats in this family:
|
||||
|
||||
* Oracle's BBC is an obsolete format at this point: though it may provide good compression,
|
||||
it is likely much slower than more recent alternatives due to excessive branching.
|
||||
* WAH is a patented variation on BBC that provides better performance.
|
||||
* Concise is a variation on the patented WAH. It some specific instances, it can compress
|
||||
much better than WAH (up to 2x better), but it is generally slower.
|
||||
* EWAH is both free of patent, and it is faster than all the above. On the downside, it
|
||||
does not compress quite as well. It is faster because it allows some form of "skipping"
|
||||
over uncompressed words. So though none of these formats are great at random access, EWAH
|
||||
is better than the alternatives.
|
||||
|
||||
|
||||
|
||||
There is a big problem with these formats however that can hurt you badly in some cases: there is no random access. If you want to check whether a given value is present in the set, you have to start from the beginning and "uncompress" the whole thing. This means that if you want to intersect a big set with a large set, you still have to uncompress the whole big set in the worst case...
|
||||
|
||||
Roaring solves this problem. It works in the following manner. It divides the data into chunks of 2<sup>16</sup> integers
|
||||
(e.g., [0, 2<sup>16</sup>), [2<sup>16</sup>, 2 x 2<sup>16</sup>), ...). Within a chunk, it can use an uncompressed bitmap, a simple list of integers,
|
||||
or a list of runs. Whatever format it uses, they all allow you to check for the presence of any one value quickly
|
||||
(e.g., with a binary search). The net result is that Roaring can compute many operations much faster than run-length-encoded
|
||||
formats like WAH, EWAH, Concise... Maybe surprisingly, Roaring also generally offers better compression ratios.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### References
|
||||
|
||||
- Daniel Lemire, Owen Kaser, Nathan Kurz, Luca Deri, Chris O'Hara, François Saint-Jacques, Gregory Ssi-Yan-Kai, Roaring Bitmaps: Implementation of an Optimized Software Library, Software: Practice and Experience 48 (4), 2018 [arXiv:1709.07821](https://arxiv.org/abs/1709.07821)
|
||||
- Samy Chambi, Daniel Lemire, Owen Kaser, Robert Godin,
|
||||
Better bitmap performance with Roaring bitmaps,
|
||||
Software: Practice and Experience 46 (5), 2016.[arXiv:1402.6407](http://arxiv.org/abs/1402.6407) This paper used data from http://lemire.me/data/realroaring2014.html
|
||||
- Daniel Lemire, Gregory Ssi-Yan-Kai, Owen Kaser, Consistently faster and smaller compressed bitmaps with Roaring, Software: Practice and Experience 46 (11), 2016. [arXiv:1603.06549](http://arxiv.org/abs/1603.06549)
|
||||
|
||||
### Dependencies
|
||||
|
||||
Dependencies are fetched automatically by giving the `-t` flag to `go get`.
|
||||
|
||||
they include
|
||||
- github.com/bits-and-blooms/bitset
|
||||
- github.com/mschoch/smat
|
||||
- github.com/glycerine/go-unsnap-stream
|
||||
- github.com/philhofer/fwd
|
||||
- github.com/jtolds/gls
|
||||
|
||||
Note that the smat library requires Go 1.15 or better.
|
||||
|
||||
#### Installation
|
||||
|
||||
- go get -t github.com/RoaringBitmap/roaring
|
||||
|
||||
### Instructions for contributors
|
||||
|
||||
Using bash or other common shells:
|
||||
```
|
||||
$ git clone git@github.com:RoaringBitmap/roaring.git
|
||||
$ export GO111MODULE=on
|
||||
$ go mod tidy
|
||||
$ go test -v
|
||||
```
|
||||
|
||||
### Example
|
||||
|
||||
Here is a simplified but complete example:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/RoaringBitmap/roaring/v2"
|
||||
"bytes"
|
||||
)
|
||||
|
||||
|
||||
func main() {
|
||||
// example inspired by https://github.com/fzandona/goroar
|
||||
fmt.Println("==roaring==")
|
||||
rb1 := roaring.BitmapOf(1, 2, 3, 4, 5, 100, 1000)
|
||||
fmt.Println(rb1.String())
|
||||
|
||||
rb2 := roaring.BitmapOf(3, 4, 1000)
|
||||
fmt.Println(rb2.String())
|
||||
|
||||
rb3 := roaring.New()
|
||||
fmt.Println(rb3.String())
|
||||
|
||||
fmt.Println("Cardinality: ", rb1.GetCardinality())
|
||||
|
||||
fmt.Println("Contains 3? ", rb1.Contains(3))
|
||||
|
||||
rb1.And(rb2)
|
||||
|
||||
rb3.Add(1)
|
||||
rb3.Add(5)
|
||||
|
||||
rb3.Or(rb1)
|
||||
|
||||
// computes union of the three bitmaps in parallel using 4 workers
|
||||
roaring.ParOr(4, rb1, rb2, rb3)
|
||||
// computes intersection of the three bitmaps in parallel using 4 workers
|
||||
roaring.ParAnd(4, rb1, rb2, rb3)
|
||||
|
||||
|
||||
// prints 1, 3, 4, 5, 1000
|
||||
i := rb3.Iterator()
|
||||
for i.HasNext() {
|
||||
fmt.Println(i.Next())
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// next we include an example of serialization
|
||||
buf := new(bytes.Buffer)
|
||||
rb1.WriteTo(buf) // we omit error handling
|
||||
newrb:= roaring.New()
|
||||
newrb.ReadFrom(buf)
|
||||
if rb1.Equals(newrb) {
|
||||
fmt.Println("I wrote the content to a byte stream and read it back.")
|
||||
}
|
||||
// you can iterate over bitmaps using ReverseIterator(), Iterator, ManyIterator()
|
||||
}
|
||||
```
|
||||
|
||||
If you wish to use serialization and handle errors, you might want to
|
||||
consider the following sample of code:
|
||||
|
||||
```go
|
||||
rb := BitmapOf(1, 2, 3, 4, 5, 100, 1000)
|
||||
buf := new(bytes.Buffer)
|
||||
size,err:=rb.WriteTo(buf)
|
||||
if err != nil {
|
||||
fmt.Println("Failed writing") // return or panic
|
||||
}
|
||||
newrb:= New()
|
||||
size,err=newrb.ReadFrom(buf)
|
||||
if err != nil {
|
||||
fmt.Println("Failed reading") // return or panic
|
||||
}
|
||||
// if buf is an untrusted source, you should validate the result
|
||||
// (this adds a bit of complexity but it is necessary for security)
|
||||
if newrb.Validate() != nil {
|
||||
fmt.Println("Failed validation") // return or panic
|
||||
}
|
||||
if ! rb.Equals(newrb) {
|
||||
fmt.Println("Cannot retrieve serialized version")
|
||||
}
|
||||
```
|
||||
|
||||
Given N integers in [0,x), then the serialized size in bytes of
|
||||
a Roaring bitmap should never exceed this bound:
|
||||
|
||||
`` 8 + 9 * ((long)x+65535)/65536 + 2 * N ``
|
||||
|
||||
That is, given a fixed overhead for the universe size (x), Roaring
|
||||
bitmaps never use more than 2 bytes per integer. You can call
|
||||
``BoundSerializedSizeInBytes`` for a more precise estimate.
|
||||
|
||||
### 64-bit Roaring
|
||||
|
||||
By default, roaring is used to stored unsigned 32-bit integers. However, we also offer
|
||||
an extension dedicated to 64-bit integers. It supports roughly the same functions:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/RoaringBitmap/roaring/v2/roaring64"
|
||||
"bytes"
|
||||
)
|
||||
|
||||
|
||||
func main() {
|
||||
// example inspired by https://github.com/fzandona/goroar
|
||||
fmt.Println("==roaring64==")
|
||||
rb1 := roaring64.BitmapOf(1, 2, 3, 4, 5, 100, 1000)
|
||||
fmt.Println(rb1.String())
|
||||
|
||||
rb2 := roaring64.BitmapOf(3, 4, 1000)
|
||||
fmt.Println(rb2.String())
|
||||
|
||||
rb3 := roaring64.New()
|
||||
fmt.Println(rb3.String())
|
||||
|
||||
fmt.Println("Cardinality: ", rb1.GetCardinality())
|
||||
|
||||
fmt.Println("Contains 3? ", rb1.Contains(3))
|
||||
|
||||
rb1.And(rb2)
|
||||
|
||||
rb3.Add(1)
|
||||
rb3.Add(5)
|
||||
|
||||
rb3.Or(rb1)
|
||||
|
||||
|
||||
|
||||
// prints 1, 3, 4, 5, 1000
|
||||
i := rb3.Iterator()
|
||||
for i.HasNext() {
|
||||
fmt.Println(i.Next())
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// next we include an example of serialization
|
||||
buf := new(bytes.Buffer)
|
||||
rb1.WriteTo(buf) // we omit error handling
|
||||
newrb:= roaring64.New()
|
||||
newrb.ReadFrom(buf)
|
||||
if rb1.Equals(newrb) {
|
||||
fmt.Println("I wrote the content to a byte stream and read it back.")
|
||||
}
|
||||
// you can iterate over bitmaps using ReverseIterator(), Iterator, ManyIterator()
|
||||
}
|
||||
```
|
||||
|
||||
Only the 32-bit roaring format is standard and cross-operable between Java, C++, C and Go. There is no guarantee that the 64-bit versions are compatible.
|
||||
|
||||
### Documentation
|
||||
|
||||
Current documentation is available at https://pkg.go.dev/github.com/RoaringBitmap/roaring and https://pkg.go.dev/github.com/RoaringBitmap/roaring/roaring64
|
||||
|
||||
### Goroutine safety
|
||||
|
||||
In general, it should not generally be considered safe to access
|
||||
the same bitmaps using different goroutines--they are left
|
||||
unsynchronized for performance. Should you want to access
|
||||
a Bitmap from more than one goroutine, you should
|
||||
provide synchronization. Typically this is done by using channels to pass
|
||||
the *Bitmap around (in Go style; so there is only ever one owner),
|
||||
or by using `sync.Mutex` to serialize operations on Bitmaps.
|
||||
|
||||
### Coverage
|
||||
|
||||
We test our software. For a report on our test coverage, see
|
||||
|
||||
https://coveralls.io/github/RoaringBitmap/roaring?branch=master
|
||||
|
||||
### Benchmark
|
||||
|
||||
Type
|
||||
|
||||
go test -bench Benchmark -run -
|
||||
|
||||
To run benchmarks on [Real Roaring Datasets](https://github.com/RoaringBitmap/real-roaring-datasets)
|
||||
run the following:
|
||||
|
||||
```sh
|
||||
go get github.com/RoaringBitmap/real-roaring-datasets
|
||||
BENCH_REAL_DATA=1 go test -bench BenchmarkRealData -run -
|
||||
```
|
||||
|
||||
### Iterative use
|
||||
|
||||
You can use roaring with gore:
|
||||
|
||||
- go install github.com/x-motemen/gore/cmd/gore@latest
|
||||
- Make sure that ``$GOPATH/bin`` is in your ``$PATH``.
|
||||
|
||||
```go
|
||||
$ gore
|
||||
gore version 0.2.6 :help for help
|
||||
gore> :import github.com/RoaringBitmap/roaring
|
||||
gore> x:=roaring.New()
|
||||
gore> x.Add(1)
|
||||
gore> x.String()
|
||||
"{1}"
|
||||
```
|
||||
|
||||
|
||||
### Fuzzy testing
|
||||
|
||||
You can help us test further the library with fuzzy testing:
|
||||
|
||||
go get github.com/dvyukov/go-fuzz/go-fuzz
|
||||
go get github.com/dvyukov/go-fuzz/go-fuzz-build
|
||||
go test -tags=gofuzz -run=TestGenerateSmatCorpus
|
||||
go-fuzz-build github.com/RoaringBitmap/roaring
|
||||
go-fuzz -bin=./roaring-fuzz.zip -workdir=workdir/ -timeout=200 -func FuzzSmat
|
||||
|
||||
Let it run, and if the # of crashers is > 0, check out the reports in
|
||||
the workdir where you should be able to find the panic goroutine stack
|
||||
traces.
|
||||
|
||||
You may also replace `-func FuzzSmat` by `-func FuzzSerializationBuffer` or `-func FuzzSerializationStream`.
|
||||
|
||||
### Alternative in Go
|
||||
|
||||
There is a Go version wrapping the C/C++ implementation https://github.com/RoaringBitmap/gocroaring
|
||||
|
||||
For an alternative implementation in Go, see https://github.com/fzandona/goroar
|
||||
The two versions were written independently.
|
||||
|
||||
|
||||
### Mailing list/discussion group
|
||||
|
||||
https://groups.google.com/g/roaring-bitmaps
|
||||
|
||||
## Stars
|
||||
|
||||
|
||||
[](https://www.star-history.com/#RoaringBitmap/roaring&Date)
|
||||
|
||||
### Further reading
|
||||
|
||||
<p>Mastering Programming: From Testing to Performance in Go</p>
|
||||
<div><a href="https://www.amazon.com/dp/B0FMPGSWR5"><img style="margin-left: auto; margin-right: auto;" src="https://m.media-amazon.com/images/I/61feneHS7kL._SL1499_.jpg" alt="" width="250px" /></a></div>
|
||||
1317
vendor/github.com/RoaringBitmap/roaring/v2/arraycontainer.go
generated
vendored
Normal file
1497
vendor/github.com/RoaringBitmap/roaring/v2/bitmapcontainer.go
generated
vendored
Normal file
19
vendor/github.com/RoaringBitmap/roaring/v2/clz.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
//go:build go1.9
|
||||
// +build go1.9
|
||||
|
||||
// "go1.9", from Go version 1.9 onward
|
||||
// See https://golang.org/pkg/go/build/#hdr-Build_Constraints
|
||||
|
||||
package roaring
|
||||
|
||||
import "math/bits"
|
||||
|
||||
// countLeadingOnes returns the number of leading zeros bits in x; the result is 64 for x == 0.
|
||||
func countLeadingZeros(x uint64) int {
|
||||
return bits.LeadingZeros64(x)
|
||||
}
|
||||
|
||||
// countLeadingOnes returns the number of leading ones bits in x; the result is 0 for x == 0.
|
||||
func countLeadingOnes(x uint64) int {
|
||||
return bits.LeadingZeros64(^x)
|
||||
}
|
||||
37
vendor/github.com/RoaringBitmap/roaring/v2/clz_compat.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
//go:build !go1.9
|
||||
// +build !go1.9
|
||||
|
||||
package roaring
|
||||
|
||||
// LeadingZeroBits returns the number of consecutive most significant zero
|
||||
// bits of x.
|
||||
func countLeadingZeros(i uint64) int {
|
||||
if i == 0 {
|
||||
return 64
|
||||
}
|
||||
n := 1
|
||||
x := uint32(i >> 32)
|
||||
if x == 0 {
|
||||
n += 32
|
||||
x = uint32(i)
|
||||
}
|
||||
if (x >> 16) == 0 {
|
||||
n += 16
|
||||
x <<= 16
|
||||
}
|
||||
if (x >> 24) == 0 {
|
||||
n += 8
|
||||
x <<= 8
|
||||
}
|
||||
if x>>28 == 0 {
|
||||
n += 4
|
||||
x <<= 4
|
||||
}
|
||||
if x>>30 == 0 {
|
||||
n += 2
|
||||
x <<= 2
|
||||
|
||||
}
|
||||
n -= int(x >> 31)
|
||||
return n
|
||||
}
|
||||
21
vendor/github.com/RoaringBitmap/roaring/v2/ctz.go
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
//go:build go1.9
|
||||
// +build go1.9
|
||||
|
||||
// "go1.9", from Go version 1.9 onward
|
||||
// See https://golang.org/pkg/go/build/#hdr-Build_Constraints
|
||||
|
||||
package roaring
|
||||
|
||||
import "math/bits"
|
||||
|
||||
// countTrailingZeros returns the number of trailing zero bits in x; the result is 64 for x == 0.
|
||||
func countTrailingZeros(x uint64) int {
|
||||
return bits.TrailingZeros64(x)
|
||||
}
|
||||
|
||||
// countTrailingOnes returns the number of trailing one bits in x
|
||||
// The result is 64 for x == 9,223,372,036,854,775,807.
|
||||
// The result is 0 for x == 0.
|
||||
func countTrailingOnes(x uint64) int {
|
||||
return bits.TrailingZeros64(^x)
|
||||
}
|
||||
72
vendor/github.com/RoaringBitmap/roaring/v2/ctz_compat.go
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
//go:build !go1.9
|
||||
// +build !go1.9
|
||||
|
||||
package roaring
|
||||
|
||||
// Reuse of portions of go/src/math/big standard lib code
|
||||
// under this license:
|
||||
/*
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
const deBruijn32 = 0x077CB531
|
||||
|
||||
var deBruijn32Lookup = []byte{
|
||||
0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8,
|
||||
31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9,
|
||||
}
|
||||
|
||||
const deBruijn64 = 0x03f79d71b4ca8b09
|
||||
|
||||
var deBruijn64Lookup = []byte{
|
||||
0, 1, 56, 2, 57, 49, 28, 3, 61, 58, 42, 50, 38, 29, 17, 4,
|
||||
62, 47, 59, 36, 45, 43, 51, 22, 53, 39, 33, 30, 24, 18, 12, 5,
|
||||
63, 55, 48, 27, 60, 41, 37, 16, 46, 35, 44, 21, 52, 32, 23, 11,
|
||||
54, 26, 40, 15, 34, 20, 31, 10, 25, 14, 19, 9, 13, 8, 7, 6,
|
||||
}
|
||||
|
||||
// trailingZeroBits returns the number of consecutive least significant zero
|
||||
// bits of x.
|
||||
func countTrailingZeros(x uint64) int {
|
||||
// x & -x leaves only the right-most bit set in the word. Let k be the
|
||||
// index of that bit. Since only a single bit is set, the value is two
|
||||
// to the power of k. Multiplying by a power of two is equivalent to
|
||||
// left shifting, in this case by k bits. The de Bruijn constant is
|
||||
// such that all six bit, consecutive substrings are distinct.
|
||||
// Therefore, if we have a left shifted version of this constant we can
|
||||
// find by how many bits it was shifted by looking at which six bit
|
||||
// substring ended up at the top of the word.
|
||||
// (Knuth, volume 4, section 7.3.1)
|
||||
if x == 0 {
|
||||
// We have to special case 0; the fomula
|
||||
// below doesn't work for 0.
|
||||
return 64
|
||||
}
|
||||
return int(deBruijn64Lookup[((x&-x)*(deBruijn64))>>58])
|
||||
}
|
||||
313
vendor/github.com/RoaringBitmap/roaring/v2/fastaggregation.go
generated
vendored
Normal file
@@ -0,0 +1,313 @@
|
||||
package roaring
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
)
|
||||
|
||||
// Or function that requires repairAfterLazy
|
||||
func lazyOR(x1, x2 *Bitmap) *Bitmap {
|
||||
answer := NewBitmap()
|
||||
pos1 := 0
|
||||
pos2 := 0
|
||||
length1 := x1.highlowcontainer.size()
|
||||
length2 := x2.highlowcontainer.size()
|
||||
main:
|
||||
for (pos1 < length1) && (pos2 < length2) {
|
||||
s1 := x1.highlowcontainer.getKeyAtIndex(pos1)
|
||||
s2 := x2.highlowcontainer.getKeyAtIndex(pos2)
|
||||
|
||||
for {
|
||||
if s1 < s2 {
|
||||
answer.highlowcontainer.appendCopy(x1.highlowcontainer, pos1)
|
||||
pos1++
|
||||
if pos1 == length1 {
|
||||
break main
|
||||
}
|
||||
s1 = x1.highlowcontainer.getKeyAtIndex(pos1)
|
||||
} else if s1 > s2 {
|
||||
answer.highlowcontainer.appendCopy(x2.highlowcontainer, pos2)
|
||||
pos2++
|
||||
if pos2 == length2 {
|
||||
break main
|
||||
}
|
||||
s2 = x2.highlowcontainer.getKeyAtIndex(pos2)
|
||||
} else {
|
||||
c1 := x1.highlowcontainer.getContainerAtIndex(pos1)
|
||||
answer.highlowcontainer.appendContainer(s1, c1.lazyOR(x2.highlowcontainer.getContainerAtIndex(pos2)), false)
|
||||
pos1++
|
||||
pos2++
|
||||
if (pos1 == length1) || (pos2 == length2) {
|
||||
break main
|
||||
}
|
||||
s1 = x1.highlowcontainer.getKeyAtIndex(pos1)
|
||||
s2 = x2.highlowcontainer.getKeyAtIndex(pos2)
|
||||
}
|
||||
}
|
||||
}
|
||||
if pos1 == length1 {
|
||||
answer.highlowcontainer.appendCopyMany(x2.highlowcontainer, pos2, length2)
|
||||
} else if pos2 == length2 {
|
||||
answer.highlowcontainer.appendCopyMany(x1.highlowcontainer, pos1, length1)
|
||||
}
|
||||
return answer
|
||||
}
|
||||
|
||||
// In-place Or function that requires repairAfterLazy
|
||||
func (x1 *Bitmap) lazyOR(x2 *Bitmap) *Bitmap {
|
||||
pos1 := 0
|
||||
pos2 := 0
|
||||
length1 := x1.highlowcontainer.size()
|
||||
length2 := x2.highlowcontainer.size()
|
||||
main:
|
||||
for (pos1 < length1) && (pos2 < length2) {
|
||||
s1 := x1.highlowcontainer.getKeyAtIndex(pos1)
|
||||
s2 := x2.highlowcontainer.getKeyAtIndex(pos2)
|
||||
|
||||
for {
|
||||
if s1 < s2 {
|
||||
pos1++
|
||||
if pos1 == length1 {
|
||||
break main
|
||||
}
|
||||
s1 = x1.highlowcontainer.getKeyAtIndex(pos1)
|
||||
} else if s1 > s2 {
|
||||
x1.highlowcontainer.insertNewKeyValueAt(pos1, s2, x2.highlowcontainer.getContainerAtIndex(pos2).clone())
|
||||
pos2++
|
||||
pos1++
|
||||
length1++
|
||||
if pos2 == length2 {
|
||||
break main
|
||||
}
|
||||
s2 = x2.highlowcontainer.getKeyAtIndex(pos2)
|
||||
} else {
|
||||
c1 := x1.highlowcontainer.getWritableContainerAtIndex(pos1)
|
||||
x1.highlowcontainer.containers[pos1] = c1.lazyIOR(x2.highlowcontainer.getContainerAtIndex(pos2))
|
||||
x1.highlowcontainer.needCopyOnWrite[pos1] = false
|
||||
pos1++
|
||||
pos2++
|
||||
if (pos1 == length1) || (pos2 == length2) {
|
||||
break main
|
||||
}
|
||||
s1 = x1.highlowcontainer.getKeyAtIndex(pos1)
|
||||
s2 = x2.highlowcontainer.getKeyAtIndex(pos2)
|
||||
}
|
||||
}
|
||||
}
|
||||
if pos1 == length1 {
|
||||
x1.highlowcontainer.appendCopyMany(x2.highlowcontainer, pos2, length2)
|
||||
}
|
||||
return x1
|
||||
}
|
||||
|
||||
// to be called after lazy aggregates
|
||||
func (x1 *Bitmap) repairAfterLazy() {
|
||||
for pos := 0; pos < x1.highlowcontainer.size(); pos++ {
|
||||
c := x1.highlowcontainer.getContainerAtIndex(pos)
|
||||
switch c.(type) {
|
||||
case *bitmapContainer:
|
||||
if c.(*bitmapContainer).cardinality == invalidCardinality {
|
||||
c = x1.highlowcontainer.getWritableContainerAtIndex(pos)
|
||||
c.(*bitmapContainer).computeCardinality()
|
||||
if c.(*bitmapContainer).getCardinality() <= arrayDefaultMaxSize {
|
||||
x1.highlowcontainer.setContainerAtIndex(pos, c.(*bitmapContainer).toArrayContainer())
|
||||
} else if c.(*bitmapContainer).isFull() {
|
||||
x1.highlowcontainer.setContainerAtIndex(pos, newRunContainer16Range(0, MaxUint16))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FastAnd computes the intersection between many bitmaps quickly
|
||||
// Compared to the And function, it can take many bitmaps as input, thus saving the trouble
|
||||
// of manually calling "And" many times.
|
||||
//
|
||||
// Performance hints: if you have very large and tiny bitmaps,
|
||||
// it may be beneficial performance-wise to put a tiny bitmap
|
||||
// in first position.
|
||||
func FastAnd(bitmaps ...*Bitmap) *Bitmap {
|
||||
if len(bitmaps) == 0 {
|
||||
return NewBitmap()
|
||||
} else if len(bitmaps) == 1 {
|
||||
return bitmaps[0].Clone()
|
||||
}
|
||||
answer := And(bitmaps[0], bitmaps[1])
|
||||
for _, bm := range bitmaps[2:] {
|
||||
answer.And(bm)
|
||||
}
|
||||
return answer
|
||||
}
|
||||
|
||||
// FastOr computes the union between many bitmaps quickly, as opposed to having to call Or repeatedly.
|
||||
// It might also be faster than calling Or repeatedly.
|
||||
func FastOr(bitmaps ...*Bitmap) *Bitmap {
|
||||
if len(bitmaps) == 0 {
|
||||
return NewBitmap()
|
||||
} else if len(bitmaps) == 1 {
|
||||
return bitmaps[0].Clone()
|
||||
}
|
||||
answer := lazyOR(bitmaps[0], bitmaps[1])
|
||||
for _, bm := range bitmaps[2:] {
|
||||
answer = answer.lazyOR(bm)
|
||||
}
|
||||
// here is where repairAfterLazy is called.
|
||||
answer.repairAfterLazy()
|
||||
return answer
|
||||
}
|
||||
|
||||
// HeapOr computes the union between many bitmaps quickly using a heap.
|
||||
// It might be faster than calling Or repeatedly.
|
||||
func HeapOr(bitmaps ...*Bitmap) *Bitmap {
|
||||
if len(bitmaps) == 0 {
|
||||
return NewBitmap()
|
||||
}
|
||||
// TODO: for better speed, we could do the operation lazily, see Java implementation
|
||||
pq := make(priorityQueue, len(bitmaps))
|
||||
for i, bm := range bitmaps {
|
||||
pq[i] = &item{bm, i}
|
||||
}
|
||||
heap.Init(&pq)
|
||||
|
||||
for pq.Len() > 1 {
|
||||
x1 := heap.Pop(&pq).(*item)
|
||||
x2 := heap.Pop(&pq).(*item)
|
||||
heap.Push(&pq, &item{Or(x1.value, x2.value), 0})
|
||||
}
|
||||
return heap.Pop(&pq).(*item).value
|
||||
}
|
||||
|
||||
// HeapXor computes the symmetric difference between many bitmaps quickly (as opposed to calling Xor repeated).
|
||||
// Internally, this function uses a heap.
|
||||
// It might be faster than calling Xor repeatedly.
|
||||
func HeapXor(bitmaps ...*Bitmap) *Bitmap {
|
||||
if len(bitmaps) == 0 {
|
||||
return NewBitmap()
|
||||
}
|
||||
|
||||
pq := make(priorityQueue, len(bitmaps))
|
||||
for i, bm := range bitmaps {
|
||||
pq[i] = &item{bm, i}
|
||||
}
|
||||
heap.Init(&pq)
|
||||
|
||||
for pq.Len() > 1 {
|
||||
x1 := heap.Pop(&pq).(*item)
|
||||
x2 := heap.Pop(&pq).(*item)
|
||||
heap.Push(&pq, &item{Xor(x1.value, x2.value), 0})
|
||||
}
|
||||
return heap.Pop(&pq).(*item).value
|
||||
}
|
||||
|
||||
// AndAny provides a result equivalent to x1.And(FastOr(bitmaps)).
|
||||
// It's optimized to minimize allocations. It also might be faster than separate calls.
|
||||
func (x1 *Bitmap) AndAny(bitmaps ...*Bitmap) {
|
||||
if len(bitmaps) == 0 {
|
||||
return
|
||||
} else if len(bitmaps) == 1 {
|
||||
x1.And(bitmaps[0])
|
||||
return
|
||||
}
|
||||
|
||||
type withPos struct {
|
||||
bitmap *roaringArray
|
||||
pos int
|
||||
key uint16
|
||||
}
|
||||
filters := make([]withPos, 0, len(bitmaps))
|
||||
|
||||
for _, b := range bitmaps {
|
||||
if b.highlowcontainer.size() > 0 {
|
||||
filters = append(filters, withPos{
|
||||
bitmap: &b.highlowcontainer,
|
||||
pos: 0,
|
||||
key: b.highlowcontainer.getKeyAtIndex(0),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
basePos := 0
|
||||
intersections := 0
|
||||
keyContainers := make([]container, 0, len(filters))
|
||||
var (
|
||||
tmpArray *arrayContainer
|
||||
tmpBitmap *bitmapContainer
|
||||
minNextKey uint16
|
||||
)
|
||||
|
||||
for basePos < x1.highlowcontainer.size() && len(filters) > 0 {
|
||||
baseKey := x1.highlowcontainer.getKeyAtIndex(basePos)
|
||||
|
||||
// accumulate containers for current key, find next minimal key in filters
|
||||
// and exclude filters that do not have related values anymore
|
||||
i := 0
|
||||
maxPossibleOr := 0
|
||||
minNextKey = MaxUint16
|
||||
for _, f := range filters {
|
||||
if f.key < baseKey {
|
||||
f.pos = f.bitmap.advanceUntil(baseKey, f.pos)
|
||||
if f.pos == f.bitmap.size() {
|
||||
continue
|
||||
}
|
||||
f.key = f.bitmap.getKeyAtIndex(f.pos)
|
||||
}
|
||||
|
||||
if f.key == baseKey {
|
||||
cont := f.bitmap.getContainerAtIndex(f.pos)
|
||||
keyContainers = append(keyContainers, cont)
|
||||
maxPossibleOr += cont.getCardinality()
|
||||
|
||||
f.pos++
|
||||
if f.pos == f.bitmap.size() {
|
||||
continue
|
||||
}
|
||||
f.key = f.bitmap.getKeyAtIndex(f.pos)
|
||||
}
|
||||
|
||||
minNextKey = minOfUint16(minNextKey, f.key)
|
||||
filters[i] = f
|
||||
i++
|
||||
}
|
||||
filters = filters[:i]
|
||||
|
||||
if len(keyContainers) == 0 {
|
||||
basePos = x1.highlowcontainer.advanceUntil(minNextKey, basePos)
|
||||
continue
|
||||
}
|
||||
|
||||
var ored container
|
||||
|
||||
if len(keyContainers) == 1 {
|
||||
ored = keyContainers[0]
|
||||
} else {
|
||||
//TODO: special case for run containers?
|
||||
if maxPossibleOr > arrayDefaultMaxSize {
|
||||
if tmpBitmap == nil {
|
||||
tmpBitmap = newBitmapContainer()
|
||||
}
|
||||
tmpBitmap.resetTo(keyContainers[0])
|
||||
ored = tmpBitmap
|
||||
} else {
|
||||
if tmpArray == nil {
|
||||
tmpArray = newArrayContainerCapacity(maxPossibleOr)
|
||||
}
|
||||
tmpArray.realloc(maxPossibleOr)
|
||||
tmpArray.resetTo(keyContainers[0])
|
||||
ored = tmpArray
|
||||
}
|
||||
for _, c := range keyContainers[1:] {
|
||||
ored = ored.ior(c)
|
||||
}
|
||||
}
|
||||
|
||||
result := x1.highlowcontainer.getWritableContainerAtIndex(basePos).iand(ored)
|
||||
if !result.isEmpty() {
|
||||
x1.highlowcontainer.replaceKeyAndContainerAtIndex(intersections, baseKey, result, false)
|
||||
intersections++
|
||||
}
|
||||
|
||||
keyContainers = keyContainers[:0]
|
||||
basePos = x1.highlowcontainer.advanceUntil(minNextKey, basePos)
|
||||
}
|
||||
|
||||
x1.highlowcontainer.resize(intersections)
|
||||
}
|
||||