Compare commits
33 Commits
v1.108.1
...
debug/erro
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
005068ea5a | ||
|
|
7941877233 | ||
|
|
f303081304 | ||
|
|
a84628f701 | ||
|
|
f823a225ac | ||
|
|
79f1a37ee6 | ||
|
|
f9cd408ca9 | ||
|
|
c2811d8d11 | ||
|
|
8d981b15c9 | ||
|
|
58f09fe3f8 | ||
|
|
afd926a0b0 | ||
|
|
204c102342 | ||
|
|
c5949af9e8 | ||
|
|
5dc0413bc0 | ||
|
|
f919783de9 | ||
|
|
60f9f44150 | ||
|
|
0fcbe8fdae | ||
|
|
458b602938 | ||
|
|
471f1d0a09 | ||
|
|
7f80c1633f | ||
|
|
186b00df6b | ||
|
|
4205ae3011 | ||
|
|
491028774a | ||
|
|
565b79c9ca | ||
|
|
5478cc61c2 | ||
|
|
79c08ecac4 | ||
|
|
f47fd83e54 | ||
|
|
9c39bac565 | ||
|
|
1042f07498 | ||
|
|
79a595c6d0 | ||
|
|
40b47601d1 | ||
|
|
6bfcbe66f7 | ||
|
|
94118c63f6 |
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/promql"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmstorage"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/buildinfo"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/cgroup"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/envflag"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
|
||||
@@ -39,9 +40,20 @@ var (
|
||||
"The saved data survives unclean shutdowns such as OOM crash, hardware reset, SIGKILL, etc. "+
|
||||
"Bigger intervals may help increase the lifetime of flash storage with limited write cycles (e.g. Raspberry PI). "+
|
||||
"Smaller intervals increase disk IO load. Minimum supported value is 1s")
|
||||
maxIngestionRate = flag.Int("maxIngestionRate", 0, "The maximum number of samples vmsingle can receive per second. Data ingestion is paused when the limit is exceeded. "+
|
||||
"By default there are no limits on samples ingestion rate.")
|
||||
)
|
||||
|
||||
func main() {
|
||||
// VictoriaMetrics is optimized for reduced memory allocations,
|
||||
// so it can run with the reduced GOGC in order to reduce the used memory,
|
||||
// while keeping CPU usage spent in GC at low levels.
|
||||
//
|
||||
// Some workloads may need increased GOGC values. Then such values can be set via GOGC environment variable.
|
||||
// It is recommended increasing GOGC if go_memstats_gc_cpu_fraction metric exposed at /metrics page
|
||||
// exceeds 0.05 for extended periods of time.
|
||||
cgroup.SetGOGC(30)
|
||||
|
||||
// Write flags and help message to stdout, since it is easier to grep or pipe.
|
||||
flag.CommandLine.SetOutput(os.Stdout)
|
||||
flag.Usage = usage
|
||||
@@ -76,6 +88,7 @@ func main() {
|
||||
storage.SetDataFlushInterval(*inmemoryDataFlushInterval)
|
||||
vmstorage.Init(promql.ResetRollupResultCacheIfNeeded)
|
||||
vmselect.Init()
|
||||
vminsertcommon.StartIngestionRateLimiter(*maxIngestionRate)
|
||||
vminsert.Init()
|
||||
|
||||
startSelfScraper()
|
||||
@@ -97,6 +110,7 @@ func main() {
|
||||
}
|
||||
logger.Infof("successfully shut down the webservice in %.3f seconds", time.Since(startTime).Seconds())
|
||||
vminsert.Stop()
|
||||
vminsertcommon.StopIngestionRateLimiter()
|
||||
|
||||
vmstorage.Stop()
|
||||
vmselect.Stop()
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vlinsert/insertutils"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vlstorage"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logstorage"
|
||||
@@ -21,6 +22,11 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/writeconcurrencylimiter"
|
||||
)
|
||||
|
||||
var (
|
||||
datadogStreamFields = flagutil.NewArrayString("datadog.streamFields", "Datadog tags to be used as stream fields.")
|
||||
datadogIgnoreFields = flagutil.NewArrayString("datadog.ignoreFields", "Datadog tags to ignore.")
|
||||
)
|
||||
|
||||
var parserPool fastjson.ParserPool
|
||||
|
||||
// RequestHandler processes Datadog insert requests
|
||||
@@ -79,6 +85,13 @@ func datadogLogsIngestion(w http.ResponseWriter, r *http.Request) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(cp.StreamFields) == 0 {
|
||||
cp.StreamFields = *datadogStreamFields
|
||||
}
|
||||
if len(cp.IgnoreFields) == 0 {
|
||||
cp.IgnoreFields = *datadogIgnoreFields
|
||||
}
|
||||
|
||||
if err := vlstorage.CanWriteData(); err != nil {
|
||||
httpserver.Errorf(w, r, "%s", err)
|
||||
return true
|
||||
@@ -105,6 +118,70 @@ var (
|
||||
v2LogsRequestDuration = metrics.NewHistogram(`vl_http_request_duration_seconds{path="/insert/datadog/api/v2/logs"}`)
|
||||
)
|
||||
|
||||
// datadog message field has two formats:
|
||||
// - regular log message with string text
|
||||
// - nested json format for serverless plugins
|
||||
// which has folowing format:
|
||||
// {"message": {"message": "text","lamdba": {"arn": "string","requestID": "string"}, "timestamp": int64} }
|
||||
//
|
||||
// See https://github.com/DataDog/datadog-lambda-extension/blob/28b90c7e4e985b72d60b5f5a5147c69c7ac693c4/bottlecap/src/logs/lambda/mod.rs#L24
|
||||
func appendMsgFields(fields []logstorage.Field, v *fastjson.Value) ([]logstorage.Field, error) {
|
||||
switch v.Type() {
|
||||
case fastjson.TypeString:
|
||||
val := v.GetStringBytes()
|
||||
fields = append(fields, logstorage.Field{
|
||||
Name: "_msg",
|
||||
Value: bytesutil.ToUnsafeString(val),
|
||||
})
|
||||
case fastjson.TypeObject:
|
||||
var firstErr error
|
||||
v.GetObject().Visit(func(k []byte, v *fastjson.Value) {
|
||||
if firstErr != nil {
|
||||
return
|
||||
}
|
||||
switch bytesutil.ToUnsafeString(k) {
|
||||
case "message":
|
||||
val := v.GetStringBytes()
|
||||
fields = append(fields, logstorage.Field{
|
||||
Name: "_msg",
|
||||
Value: bytesutil.ToUnsafeString(val),
|
||||
})
|
||||
case "status":
|
||||
val := v.GetStringBytes()
|
||||
fields = append(fields, logstorage.Field{
|
||||
Name: "status",
|
||||
Value: bytesutil.ToUnsafeString(val),
|
||||
})
|
||||
case "lamdba":
|
||||
obj, err := v.Object()
|
||||
if err != nil {
|
||||
firstErr = err
|
||||
firstErr = fmt.Errorf("unexpected lambda value type for %q:%q; want object", k, v)
|
||||
return
|
||||
}
|
||||
obj.Visit(func(k []byte, v *fastjson.Value) {
|
||||
if firstErr != nil {
|
||||
return
|
||||
}
|
||||
val, err := v.StringBytes()
|
||||
if err != nil {
|
||||
firstErr = fmt.Errorf("unexpected lambda label value type for %q:%q; want string", k, v)
|
||||
return
|
||||
}
|
||||
fields = append(fields, logstorage.Field{
|
||||
Name: bytesutil.ToUnsafeString(k),
|
||||
Value: bytesutil.ToUnsafeString(val),
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
})
|
||||
default:
|
||||
return fields, fmt.Errorf("unsupported message type %q", v.Type().String())
|
||||
}
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
// readLogsRequest parses data according to DataDog logs format
|
||||
// https://docs.datadoghq.com/api/latest/logs/#send-logs
|
||||
func readLogsRequest(ts int64, data []byte, lmp insertutils.LogMessageProcessor) error {
|
||||
@@ -129,19 +206,27 @@ func readLogsRequest(ts int64, data []byte, lmp insertutils.LogMessageProcessor)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
val, e := v.StringBytes()
|
||||
if e != nil {
|
||||
err = fmt.Errorf("unexpected label value type for %q:%q; want string", k, v)
|
||||
return
|
||||
}
|
||||
switch string(k) {
|
||||
switch bytesutil.ToUnsafeString(k) {
|
||||
case "message":
|
||||
fields = append(fields, logstorage.Field{
|
||||
Name: "_msg",
|
||||
Value: bytesutil.ToUnsafeString(val),
|
||||
})
|
||||
fields, err = appendMsgFields(fields, v)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case "timestamp":
|
||||
val, e := v.Int64()
|
||||
if e != nil {
|
||||
err = fmt.Errorf("failed to parse timestamp for %q:%q", k, v)
|
||||
}
|
||||
if val > 0 {
|
||||
ts = val * 1e6
|
||||
}
|
||||
case "ddtags":
|
||||
// https://docs.datadoghq.com/getting_started/tagging/
|
||||
val, e := v.StringBytes()
|
||||
if e != nil {
|
||||
err = fmt.Errorf("unexpected label value type for %q:%q; want string", k, v)
|
||||
return
|
||||
}
|
||||
var pair []byte
|
||||
idx := 0
|
||||
for idx >= 0 {
|
||||
@@ -168,12 +253,20 @@ func readLogsRequest(ts int64, data []byte, lmp insertutils.LogMessageProcessor)
|
||||
}
|
||||
}
|
||||
default:
|
||||
val, e := v.StringBytes()
|
||||
if e != nil {
|
||||
err = fmt.Errorf("unexpected label value type for %q:%q; want string", k, v)
|
||||
return
|
||||
}
|
||||
fields = append(fields, logstorage.Field{
|
||||
Name: bytesutil.ToUnsafeString(k),
|
||||
Value: bytesutil.ToUnsafeString(val),
|
||||
})
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lmp.AddRow(ts, fields, nil)
|
||||
fields = fields[:0]
|
||||
}
|
||||
|
||||
@@ -54,6 +54,12 @@ func TestReadLogsRequestSuccess(t *testing.T) {
|
||||
"hostname":"127.0.0.1",
|
||||
"message":"bar",
|
||||
"service":"test"
|
||||
}, {
|
||||
"ddsource":"nginx",
|
||||
"ddtags":"tag1:value1,tag2:value2",
|
||||
"hostname":"127.0.0.1",
|
||||
"message":{"message": "nested"},
|
||||
"service":"test"
|
||||
}, {
|
||||
"ddsource":"nginx",
|
||||
"ddtags":"tag1:value1,tag2:value2",
|
||||
@@ -86,8 +92,9 @@ func TestReadLogsRequestSuccess(t *testing.T) {
|
||||
"service":"test"
|
||||
}
|
||||
]`
|
||||
rowsExpected := 6
|
||||
rowsExpected := 7
|
||||
resultExpected := `{"ddsource":"nginx","tag1":"value1","tag2":"value2","hostname":"127.0.0.1","_msg":"bar","service":"test"}
|
||||
{"ddsource":"nginx","tag1":"value1","tag2":"value2","hostname":"127.0.0.1","_msg":"nested","service":"test"}
|
||||
{"ddsource":"nginx","tag1":"value1","tag2":"value2","hostname":"127.0.0.1","_msg":"foobar","service":"test"}
|
||||
{"ddsource":"nginx","tag1":"value1","tag2":"value2","hostname":"127.0.0.1","_msg":"baz","service":"test"}
|
||||
{"ddsource":"nginx","tag1":"value1","tag2":"value2","hostname":"127.0.0.1","_msg":"xyz","service":"test"}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "./static/css/main.d05122da.css",
|
||||
"main.js": "./static/js/main.6082e5a5.js",
|
||||
"main.css": "./static/css/main.fa83344e.css",
|
||||
"main.js": "./static/js/main.8ad2bc1f.js",
|
||||
"static/js/685.f772060c.chunk.js": "./static/js/685.f772060c.chunk.js",
|
||||
"static/media/MetricsQL.md": "./static/media/MetricsQL.a00044c91d9781cf8557.md",
|
||||
"index.html": "./index.html"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.d05122da.css",
|
||||
"static/js/main.6082e5a5.js"
|
||||
"static/css/main.fa83344e.css",
|
||||
"static/js/main.8ad2bc1f.js"
|
||||
]
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.svg"/><link rel="apple-touch-icon" href="./favicon.svg"/><link rel="mask-icon" href="./favicon.svg" color="#000000"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=5"/><meta name="theme-color" content="#000000"/><meta name="description" content="Explore your log data with VictoriaLogs UI"/><link rel="manifest" href="./manifest.json"/><title>UI for VictoriaLogs</title><meta name="twitter:card" content="summary"><meta name="twitter:title" content="UI for VictoriaLogs"><meta name="twitter:site" content="@https://victoriametrics.com/products/victorialogs/"><meta name="twitter:description" content="Explore your log data with VictoriaLogs UI"><meta name="twitter:image" content="./preview.jpg"><meta property="og:type" content="website"><meta property="og:title" content="UI for VictoriaLogs"><meta property="og:url" content="https://victoriametrics.com/products/victorialogs/"><meta property="og:description" content="Explore your log data with VictoriaLogs UI"><script defer="defer" src="./static/js/main.6082e5a5.js"></script><link href="./static/css/main.d05122da.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.svg"/><link rel="apple-touch-icon" href="./favicon.svg"/><link rel="mask-icon" href="./favicon.svg" color="#000000"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=5"/><meta name="theme-color" content="#000000"/><meta name="description" content="Explore your log data with VictoriaLogs UI"/><link rel="manifest" href="./manifest.json"/><title>UI for VictoriaLogs</title><meta name="twitter:card" content="summary"><meta name="twitter:title" content="UI for VictoriaLogs"><meta name="twitter:site" content="@https://victoriametrics.com/products/victorialogs/"><meta name="twitter:description" content="Explore your log data with VictoriaLogs UI"><meta name="twitter:image" content="./preview.jpg"><meta property="og:type" content="website"><meta property="og:title" content="UI for VictoriaLogs"><meta property="og:url" content="https://victoriametrics.com/products/victorialogs/"><meta property="og:description" content="Explore your log data with VictoriaLogs UI"><script defer="defer" src="./static/js/main.8ad2bc1f.js"></script><link href="./static/css/main.fa83344e.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||
1
app/vlselect/vmui/static/css/main.fa83344e.css
Normal file
2
app/vlselect/vmui/static/js/main.8ad2bc1f.js
Normal file
@@ -30,6 +30,7 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/auth"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/buildinfo"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/cgroup"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/envflag"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
|
||||
@@ -97,6 +98,15 @@ var (
|
||||
)
|
||||
|
||||
func main() {
|
||||
// vmagent is optimized for reduced memory allocations,
|
||||
// so it can run with the reduced GOGC in order to reduce the used memory,
|
||||
// while keeping CPU usage spent in GC at low levels.
|
||||
//
|
||||
// Some workloads may need increased GOGC values. Then such values can be set via GOGC environment variable.
|
||||
// It is recommended increasing GOGC if go_memstats_gc_cpu_fraction metric exposed at /metrics page
|
||||
// exceeds 0.05 for extended periods of time.
|
||||
cgroup.SetGOGC(30)
|
||||
|
||||
// Write flags and help message to stdout, since it is easier to grep or pipe.
|
||||
flag.CommandLine.SetOutput(os.Stdout)
|
||||
flag.Usage = usage
|
||||
|
||||
@@ -213,7 +213,7 @@ func processRequest(w http.ResponseWriter, r *http.Request, ui *UserInfo) {
|
||||
missingRouteRequests.Inc()
|
||||
var di string
|
||||
if ui.DumpRequestOnErrors {
|
||||
di = debugInfo(u, r.Header)
|
||||
di = debugInfo(u, r)
|
||||
}
|
||||
httpserver.Errorf(w, r, "missing route for %q%s", u.String(), di)
|
||||
return
|
||||
@@ -668,13 +668,13 @@ func (rtb *readTrackingBody) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func debugInfo(u *url.URL, h http.Header) string {
|
||||
func debugInfo(u *url.URL, r *http.Request) string {
|
||||
s := &strings.Builder{}
|
||||
fmt.Fprintf(s, " (host: %q; ", u.Host)
|
||||
fmt.Fprintf(s, " (host: %q; ", r.Host)
|
||||
fmt.Fprintf(s, "path: %q; ", u.Path)
|
||||
fmt.Fprintf(s, "args: %q; ", u.Query().Encode())
|
||||
fmt.Fprint(s, "headers:")
|
||||
_ = h.WriteSubset(s, nil)
|
||||
_ = r.Header.WriteSubset(s, nil)
|
||||
fmt.Fprint(s, ")")
|
||||
return s.String()
|
||||
}
|
||||
|
||||
@@ -4,16 +4,48 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/relabel"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmstorage"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/ratelimiter"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/slicesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timeserieslimits"
|
||||
)
|
||||
|
||||
// StartIngestionRateLimiter starts ingestion rate limiter.
|
||||
//
|
||||
// Ingestion rate limiter must be started before Init() call.
|
||||
//
|
||||
// StopIngestionRateLimiter must be called before Stop() call in order to unblock all the callers
|
||||
// to ingestion rate limiter. Otherwise deadlock may occur at Stop() call.
|
||||
func StartIngestionRateLimiter(maxIngestionRate int) {
|
||||
if maxIngestionRate <= 0 {
|
||||
return
|
||||
}
|
||||
ingestionRateLimitReached := metrics.NewCounter(`vm_max_ingestion_rate_limit_reached_total`)
|
||||
ingestionRateLimiterStopCh = make(chan struct{})
|
||||
ingestionRateLimiter = ratelimiter.New(int64(maxIngestionRate), ingestionRateLimitReached, ingestionRateLimiterStopCh)
|
||||
}
|
||||
|
||||
// StopIngestionRateLimiter stops ingestion rate limiter.
|
||||
func StopIngestionRateLimiter() {
|
||||
if ingestionRateLimiterStopCh == nil {
|
||||
return
|
||||
}
|
||||
close(ingestionRateLimiterStopCh)
|
||||
ingestionRateLimiterStopCh = nil
|
||||
}
|
||||
|
||||
var (
|
||||
ingestionRateLimiter *ratelimiter.RateLimiter
|
||||
ingestionRateLimiterStopCh chan struct{}
|
||||
)
|
||||
|
||||
// InsertCtx contains common bits for data points insertion.
|
||||
type InsertCtx struct {
|
||||
Labels sortedLabels
|
||||
@@ -172,9 +204,12 @@ func (ctx *InsertCtx) FlushBufs() error {
|
||||
}
|
||||
matchIdxsPool.Put(matchIdxs)
|
||||
}
|
||||
ingestionRateLimiter.Register(len(ctx.mrs))
|
||||
|
||||
// There is no need in limiting the number of concurrent calls to vmstorage.AddRows() here,
|
||||
// since the number of concurrent FlushBufs() calls should be already limited via writeconcurrencylimiter
|
||||
// used at every stream.Parse() call under lib/protoparser/*
|
||||
|
||||
err := vmstorage.AddRows(ctx.mrs)
|
||||
ctx.Reset(0)
|
||||
if err == nil {
|
||||
|
||||
@@ -48,3 +48,11 @@ export interface LogHits {
|
||||
[key: string]: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ReportMetaData {
|
||||
id: number;
|
||||
title: string;
|
||||
endpoint: string;
|
||||
comment: string;
|
||||
params: Record<string, string>;
|
||||
}
|
||||
|
||||
@@ -38,6 +38,10 @@
|
||||
align-items: flex-start;
|
||||
gap: $padding-small;
|
||||
|
||||
ul {
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
button {
|
||||
color: inherit;
|
||||
min-height: 29px;
|
||||
|
||||
@@ -570,3 +570,14 @@ export const SpinnerIcon = () => (
|
||||
</path>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const CommentIcon = () => (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
d="M21.99 4c0-1.1-.89-2-1.99-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h14l4 4zM18 14H6v-2h12zm0-3H6V9h12zm0-3H6V6h12z"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import React, { FC } from "preact/compat";
|
||||
import useBoolean from "../../../hooks/useBoolean";
|
||||
import classNames from "classnames";
|
||||
import TextField from "../TextField/TextField";
|
||||
import "./style.scss";
|
||||
import { marked } from "marked";
|
||||
|
||||
interface Props {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
}
|
||||
|
||||
const tabs = [
|
||||
{ title: "Write", value: false },
|
||||
{ title: "Preview", value: true },
|
||||
];
|
||||
|
||||
const MarkdownEditor: FC<Props> = ({ value, onChange }) => {
|
||||
const {
|
||||
value: markdownPreview,
|
||||
setTrue: setMarkdownPreviewTrue,
|
||||
setFalse: setMarkdownPreviewFalse,
|
||||
} = useBoolean(false);
|
||||
|
||||
return (
|
||||
<div className="vm-markdown-editor">
|
||||
<div className="vm-markdown-editor-header">
|
||||
<div className="vm-markdown-editor-header-tabs">
|
||||
{tabs.map(({ title, value }) => (
|
||||
<div
|
||||
key={title}
|
||||
className={classNames({
|
||||
"vm-markdown-editor-header-tabs__tab": true,
|
||||
"vm-markdown-editor-header-tabs__tab_active": markdownPreview === value,
|
||||
})}
|
||||
onClick={value ? setMarkdownPreviewTrue : setMarkdownPreviewFalse}
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<span className="vm-markdown-editor-header__info">
|
||||
Markdown is supported
|
||||
</span>
|
||||
</div>
|
||||
{markdownPreview ? (
|
||||
<div
|
||||
className="vm-markdown-editor-preview vm-markdown"
|
||||
dangerouslySetInnerHTML={{ __html: marked(value) as string }}
|
||||
/>
|
||||
) : (
|
||||
<TextField
|
||||
type="textarea"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MarkdownEditor;
|
||||
@@ -0,0 +1,75 @@
|
||||
@use "src/styles/variables" as *;
|
||||
|
||||
.vm-markdown-editor {
|
||||
margin-top: 6px;
|
||||
padding: 0 6px;
|
||||
border-radius: $border-radius-small;
|
||||
border: $border-divider;
|
||||
overflow: hidden;
|
||||
|
||||
&-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: $color-hover-black;
|
||||
padding-right: $padding-global;
|
||||
border-bottom: $border-divider;
|
||||
margin: -1px -7px 6px;
|
||||
|
||||
&-tabs {
|
||||
display: flex;
|
||||
|
||||
&__tab {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: -1px;
|
||||
padding: $padding-small $padding-large;
|
||||
min-height: 40px;
|
||||
color: $color-text-secondary;
|
||||
transition: color 0.3s;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: $color-text;
|
||||
}
|
||||
|
||||
&_active {
|
||||
position: relative;
|
||||
color: $color-text;
|
||||
background-color: $color-background-body;
|
||||
border-top-right-radius: $border-radius-small;
|
||||
border-top-left-radius: $border-radius-small;
|
||||
z-index: 1;
|
||||
|
||||
&:first-child {
|
||||
border-right: $border-divider;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-right: $border-divider;
|
||||
border-left: $border-divider;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__info {
|
||||
margin-left: auto;
|
||||
margin-right: 0;
|
||||
color: $color-text-secondary;
|
||||
font-size: $font-size-small;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
&-preview {
|
||||
padding: $padding-small;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
&-preview,
|
||||
textarea {
|
||||
min-height: 200px;
|
||||
resize: vertical;
|
||||
}
|
||||
}
|
||||
@@ -16,17 +16,20 @@ const UploadJsonButtons: FC<Props> = ({ onOpenModal, onChange }) => (
|
||||
>
|
||||
Paste JSON
|
||||
</Button>
|
||||
<Button>
|
||||
Upload Files
|
||||
<div className="vm-upload-json-buttons__upload">
|
||||
<Button>
|
||||
Upload Files
|
||||
</Button>
|
||||
<input
|
||||
id="json"
|
||||
name="json"
|
||||
type="file"
|
||||
accept="application/json"
|
||||
multiple
|
||||
title=" "
|
||||
onChange={onChange}
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
@@ -6,4 +6,8 @@
|
||||
gap: $padding-global;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&__upload {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { FC, useCallback, useEffect, useMemo, useRef, useState } from "preact/compat";
|
||||
import React, { FC, useCallback, useEffect, useRef, useState } from "preact/compat";
|
||||
import { DownloadIcon } from "../../../components/Main/Icons";
|
||||
import Button from "../../../components/Main/Button/Button";
|
||||
import Tooltip from "../../../components/Main/Tooltip/Tooltip";
|
||||
@@ -12,32 +12,65 @@ import TextField from "../../../components/Main/TextField/TextField";
|
||||
import { useQueryState } from "../../../state/query/QueryStateContext";
|
||||
import { ErrorTypes } from "../../../types";
|
||||
import Alert from "../../../components/Main/Alert/Alert";
|
||||
import qs from "qs";
|
||||
import Popper from "../../../components/Main/Popper/Popper";
|
||||
import helperText from "./helperText";
|
||||
import { Link } from "react-router-dom";
|
||||
import router from "../../../router";
|
||||
import { parseLineToJSON } from "../../../utils/json";
|
||||
import { ExportMetricResult, ReportMetaData } from "../../../api/types";
|
||||
import { getApiEndpoint } from "../../../utils/url";
|
||||
import MarkdownEditor from "../../../components/Main/MarkdownEditor/MarkdownEditor";
|
||||
|
||||
export enum ReportType {
|
||||
QUERY_DATA,
|
||||
RAW_DATA,
|
||||
}
|
||||
|
||||
type Props = {
|
||||
fetchUrl?: string[];
|
||||
reportType?: ReportType
|
||||
}
|
||||
|
||||
const getDefaultReportName = () => `vmui_report_${dayjs().utc().format(DATE_FILENAME_FORMAT)}`;
|
||||
type MetaData = {
|
||||
id: number;
|
||||
url: URL;
|
||||
title: string;
|
||||
comment: string;
|
||||
}
|
||||
|
||||
const DownloadReport: FC<Props> = ({ fetchUrl }) => {
|
||||
const getDefaultTitle = (type: ReportType) => {
|
||||
switch (type) {
|
||||
case ReportType.RAW_DATA:
|
||||
return "Raw report";
|
||||
default:
|
||||
return "Report";
|
||||
}
|
||||
};
|
||||
|
||||
const getDefaultFilename = (title: string) => {
|
||||
const timestamp = dayjs().utc().format(DATE_FILENAME_FORMAT);
|
||||
return `vmui_${title.toLowerCase().replace(/ /g, "_")}_${timestamp}`;
|
||||
};
|
||||
|
||||
const DownloadReport: FC<Props> = ({ fetchUrl, reportType = ReportType.QUERY_DATA }) => {
|
||||
const { query } = useQueryState();
|
||||
|
||||
const [filename, setFilename] = useState(getDefaultReportName());
|
||||
const defaultTitle = getDefaultTitle(reportType);
|
||||
const defaultFilename = getDefaultFilename(defaultTitle);
|
||||
|
||||
const [title, setTitle] = useState(defaultTitle);
|
||||
const [filename, setFilename] = useState(defaultFilename);
|
||||
const [comment, setComment] = useState("");
|
||||
const [trace, setTrace] = useState(true);
|
||||
const [trace, setTrace] = useState(reportType === ReportType.QUERY_DATA);
|
||||
const [error, setError] = useState<ErrorTypes | string>();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const titleRef = useRef<HTMLDivElement>(null);
|
||||
const filenameRef = useRef<HTMLDivElement>(null);
|
||||
const commentRef = useRef<HTMLDivElement>(null);
|
||||
const traceRef = useRef<HTMLDivElement>(null);
|
||||
const generateRef = useRef<HTMLDivElement>(null);
|
||||
const helperRefs = [filenameRef, commentRef, traceRef, generateRef];
|
||||
const helperRefs = [filenameRef, titleRef, commentRef, traceRef, generateRef];
|
||||
const [stepHelper, setStepHelper] = useState(0);
|
||||
|
||||
const {
|
||||
@@ -52,13 +85,17 @@ const DownloadReport: FC<Props> = ({ fetchUrl }) => {
|
||||
setFalse: handleCloseHelper,
|
||||
} = useBoolean(false);
|
||||
|
||||
const fetchUrlReport = useMemo(() => {
|
||||
const getFetchUrlReport = useCallback(() => {
|
||||
if (!fetchUrl) return;
|
||||
return fetchUrl.map((str, i) => {
|
||||
const url = new URL(str);
|
||||
trace ? url.searchParams.set("trace", "1") : url.searchParams.delete("trace");
|
||||
return { id: i, url: url };
|
||||
});
|
||||
try {
|
||||
return fetchUrl.map((str, i) => {
|
||||
const url = new URL(str);
|
||||
trace ? url.searchParams.set("trace", "1") : url.searchParams.delete("trace");
|
||||
return { id: i, url: url };
|
||||
});
|
||||
} catch (e) {
|
||||
setError(String(e));
|
||||
}
|
||||
}, [fetchUrl, trace]);
|
||||
|
||||
const generateFile = useCallback((data: unknown) => {
|
||||
@@ -68,7 +105,7 @@ const DownloadReport: FC<Props> = ({ fetchUrl }) => {
|
||||
|
||||
const link = document.createElement("a");
|
||||
link.href = href;
|
||||
link.download = `${filename || getDefaultReportName()}.json`;
|
||||
link.download = `${filename || defaultFilename}.json`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
|
||||
@@ -77,9 +114,63 @@ const DownloadReport: FC<Props> = ({ fetchUrl }) => {
|
||||
handleClose();
|
||||
}, [filename]);
|
||||
|
||||
const getMetaData = ({ id, url, comment, title }: MetaData): ReportMetaData => {
|
||||
return {
|
||||
id,
|
||||
title: title || defaultTitle,
|
||||
comment,
|
||||
endpoint: getApiEndpoint(url.pathname) || "",
|
||||
params: Object.fromEntries(url.searchParams)
|
||||
};
|
||||
};
|
||||
|
||||
const processJsonLineResponse = async (response: Response, metaData: MetaData) => {
|
||||
const result: { metric: { [p: string]: string }, values: number[][] }[] = [];
|
||||
const text = await response.text();
|
||||
|
||||
if (response.ok) {
|
||||
const lines = text.split("\n").filter(line => line);
|
||||
lines.forEach((line: string) => {
|
||||
const jsonLine = parseLineToJSON(line) as (ExportMetricResult | null);
|
||||
if (!jsonLine) return;
|
||||
result.push({
|
||||
metric: jsonLine.metric,
|
||||
values: jsonLine.values.map((value, index) => [(jsonLine.timestamps[index] / 1000), value]),
|
||||
});
|
||||
});
|
||||
} else {
|
||||
setError(String(text));
|
||||
}
|
||||
|
||||
return { data: { result, resultType: "matrix" }, vmui: getMetaData(metaData) };
|
||||
};
|
||||
|
||||
const processJsonResponse = async (response: Response, metaData: MetaData) => {
|
||||
const resp = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
resp.vmui = getMetaData(metaData);
|
||||
return resp;
|
||||
} else {
|
||||
const errorType = resp.errorType ? `${resp.errorType}\r\n` : "";
|
||||
setError(`${errorType}${resp?.error || resp?.message || "unknown error"}`);
|
||||
}
|
||||
};
|
||||
|
||||
const processResponse = async (response: Response, metaData: MetaData) => {
|
||||
switch (reportType) {
|
||||
case ReportType.RAW_DATA:
|
||||
return await processJsonLineResponse(response, metaData);
|
||||
default:
|
||||
return await processJsonResponse(response, metaData);
|
||||
}
|
||||
};
|
||||
|
||||
const handleGenerateReport = useCallback(async () => {
|
||||
const fetchUrlReport = getFetchUrlReport();
|
||||
|
||||
if (!fetchUrlReport) {
|
||||
setError(ErrorTypes.validQuery);
|
||||
setError(prev => !prev ? ErrorTypes.validQuery : prev);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -88,20 +179,12 @@ const DownloadReport: FC<Props> = ({ fetchUrl }) => {
|
||||
|
||||
try {
|
||||
const result = [];
|
||||
for await (const { url, id } of fetchUrlReport) {
|
||||
for await (const fetchOps of fetchUrlReport) {
|
||||
if (!fetchOps) continue;
|
||||
const { url, id } = fetchOps;
|
||||
const response = await fetch(url);
|
||||
const resp = await response.json();
|
||||
if (response.ok) {
|
||||
resp.vmui = {
|
||||
id,
|
||||
comment,
|
||||
params: qs.parse(new URL(url).search.replace(/^\?/, ""))
|
||||
};
|
||||
result.push(resp);
|
||||
} else {
|
||||
const errorType = resp.errorType ? `${resp.errorType}\r\n` : "";
|
||||
setError(`${errorType}${resp?.error || resp?.message || "unknown error"}`);
|
||||
}
|
||||
const data = await processResponse(response, { id, url, comment, title });
|
||||
result.push(data);
|
||||
}
|
||||
result.length && generateFile(result);
|
||||
} catch (e) {
|
||||
@@ -111,15 +194,20 @@ const DownloadReport: FC<Props> = ({ fetchUrl }) => {
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [fetchUrlReport, comment, generateFile, query]);
|
||||
}, [getFetchUrlReport, comment, generateFile, query, title]);
|
||||
|
||||
const handleChangeHelp = (step: number) => () => {
|
||||
setStepHelper(prevStep => prevStep + step);
|
||||
const findNextRef = (index: number): number => {
|
||||
const nextIndex = index + step;
|
||||
if (helperRefs[nextIndex]?.current) return nextIndex;
|
||||
return findNextRef(nextIndex);
|
||||
};
|
||||
setStepHelper(findNextRef);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setError("");
|
||||
setFilename(getDefaultReportName());
|
||||
setFilename(defaultFilename);
|
||||
setComment("");
|
||||
}, [openModal]);
|
||||
|
||||
@@ -155,31 +243,41 @@ const DownloadReport: FC<Props> = ({ fetchUrl }) => {
|
||||
<div className="vm-download-report">
|
||||
<div className="vm-download-report-settings">
|
||||
<div ref={filenameRef}>
|
||||
<div className="vm-download-report-settings__title">Filename</div>
|
||||
<TextField
|
||||
label="Filename"
|
||||
value={filename}
|
||||
onChange={setFilename}
|
||||
/>
|
||||
</div>
|
||||
<div ref={commentRef}>
|
||||
<div ref={titleRef}>
|
||||
<div className="vm-download-report-settings__title">Report title</div>
|
||||
<TextField
|
||||
type="textarea"
|
||||
label="Comment"
|
||||
value={title}
|
||||
onChange={setTitle}
|
||||
/>
|
||||
</div>
|
||||
<div ref={commentRef}>
|
||||
<div className="vm-download-report-settings__title">Comment</div>
|
||||
<MarkdownEditor
|
||||
value={comment}
|
||||
onChange={setComment}
|
||||
/>
|
||||
</div>
|
||||
<div ref={traceRef}>
|
||||
<Checkbox
|
||||
checked={trace}
|
||||
onChange={setTrace}
|
||||
label={"Include query trace"}
|
||||
/>
|
||||
</div>
|
||||
<Alert variant="info">
|
||||
If confused with the query results,
|
||||
try viewing the raw samples for selected series in <RawQueryLink/> tab.
|
||||
</Alert>
|
||||
{reportType === ReportType.QUERY_DATA && (
|
||||
<>
|
||||
<div ref={traceRef}>
|
||||
<Checkbox
|
||||
checked={trace}
|
||||
onChange={setTrace}
|
||||
label={"Include query trace"}
|
||||
/>
|
||||
</div>
|
||||
<Alert variant="info">
|
||||
If confused with the query results,
|
||||
try viewing the raw samples for selected series in <RawQueryLink/> tab.
|
||||
</Alert>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{error && <Alert variant="error">{error}</Alert>}
|
||||
<div className="vm-download-report__buttons">
|
||||
|
||||
@@ -11,6 +11,18 @@ const filename = (
|
||||
</>
|
||||
);
|
||||
|
||||
const tittle = (
|
||||
<>
|
||||
<p>Title - specify the title that will be displayed on the <Link
|
||||
to={router.queryAnalyzer}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="vm-link vm-link_underlined"
|
||||
>{routerOptions[router.queryAnalyzer].title}</Link> page.</p>
|
||||
<p>This helps identify your report in the interface.</p>
|
||||
</>
|
||||
);
|
||||
|
||||
const comment = (
|
||||
<>
|
||||
<p>Comment (optional) - add a comment to your report.</p>
|
||||
@@ -39,6 +51,7 @@ const generate = (
|
||||
|
||||
export default [
|
||||
filename,
|
||||
tittle,
|
||||
comment,
|
||||
trace,
|
||||
generate,
|
||||
|
||||
@@ -9,10 +9,15 @@
|
||||
|
||||
&-settings {
|
||||
display: grid;
|
||||
gap: $padding-global;
|
||||
gap: $padding-large;
|
||||
|
||||
textarea {
|
||||
min-height: 200px;
|
||||
&__title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: $padding-global;
|
||||
font-size: $font-size;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +39,7 @@
|
||||
line-height: 1.3;
|
||||
|
||||
p {
|
||||
margin-bottom: calc($padding-small/2);
|
||||
margin-bottom: calc($padding-small / 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
import React, { FC, useMemo } from "preact/compat";
|
||||
import { DataAnalyzerType } from "../index";
|
||||
import Button from "../../../components/Main/Button/Button";
|
||||
import { ClockIcon, InfoIcon, TimelineIcon } from "../../../components/Main/Icons";
|
||||
import useBoolean from "../../../hooks/useBoolean";
|
||||
import Modal from "../../../components/Main/Modal/Modal";
|
||||
import {
|
||||
ClockIcon,
|
||||
CommentIcon,
|
||||
InfoIcon,
|
||||
TimelineIcon
|
||||
} from "../../../components/Main/Icons";
|
||||
import { TimeParams } from "../../../types";
|
||||
import "./style.scss";
|
||||
import dayjs from "dayjs";
|
||||
import { DATE_TIME_FORMAT } from "../../../constants/date";
|
||||
import useBoolean from "../../../hooks/useBoolean";
|
||||
import Modal from "../../../components/Main/Modal/Modal";
|
||||
import { marked } from "marked";
|
||||
import Button from "../../../components/Main/Button/Button";
|
||||
import get from "lodash.get";
|
||||
|
||||
type Props = {
|
||||
data: DataAnalyzerType[];
|
||||
@@ -15,8 +22,23 @@ type Props = {
|
||||
}
|
||||
|
||||
const QueryAnalyzerInfo: FC<Props> = ({ data, period }) => {
|
||||
const dataWithStats = useMemo(() => data.filter(d => d.stats && d.data.resultType === "matrix"), [data]);
|
||||
const comment = useMemo(() => data.find(d => d?.vmui?.comment)?.vmui?.comment, [data]);
|
||||
const dataWithStats = useMemo(() => data.filter(d => d.vmui || d.stats), [data]);
|
||||
const title = dataWithStats.find(d => d?.vmui?.title)?.vmui?.title || "Report";
|
||||
const comment = dataWithStats.find(d => d?.vmui?.comment)?.vmui?.comment;
|
||||
|
||||
const table = useMemo(() => {
|
||||
return [
|
||||
"vmui.endpoint",
|
||||
...new Set(dataWithStats.flatMap(d => [
|
||||
...Object.keys(d.vmui?.params || []).map(key => `vmui.params.${key}`),
|
||||
...Object.keys(d.stats || []).map(key => `stats.${key}`),
|
||||
"isPartial"
|
||||
]))
|
||||
].map(key => ({
|
||||
column: key.split(".").pop(),
|
||||
values: dataWithStats.map(data => get(data, key, "-"))
|
||||
})).filter(({ values }) => values.length && values.every(v => v !== "-"));
|
||||
}, [dataWithStats]);
|
||||
|
||||
const timeRange = useMemo(() => {
|
||||
if (!period) return "";
|
||||
@@ -34,59 +56,80 @@ const QueryAnalyzerInfo: FC<Props> = ({ data, period }) => {
|
||||
return (
|
||||
<>
|
||||
<div className="vm-query-analyzer-info-header">
|
||||
<Button
|
||||
startIcon={<InfoIcon/>}
|
||||
variant="outlined"
|
||||
color="warning"
|
||||
onClick={handleOpenModal}
|
||||
>
|
||||
Show report info
|
||||
</Button>
|
||||
{period && (
|
||||
<>
|
||||
<div className="vm-query-analyzer-info-header__period">
|
||||
<TimelineIcon/> step: {period.step}
|
||||
</div>
|
||||
<div className="vm-query-analyzer-info-header__period">
|
||||
<ClockIcon/> {timeRange}
|
||||
</div>
|
||||
</>
|
||||
<h1 className="vm-query-analyzer-info-header__title">{title}</h1>
|
||||
{timeRange && (
|
||||
<div className="vm-query-analyzer-info-header__timerange">
|
||||
<ClockIcon/> {timeRange}
|
||||
</div>
|
||||
)}
|
||||
{period?.step && (
|
||||
<div className="vm-query-analyzer-info-header__timerange">
|
||||
<TimelineIcon/> step {period.step}
|
||||
</div>
|
||||
)}
|
||||
{(comment || !!table.length) && (
|
||||
<div className="vm-query-analyzer-info-header__info">
|
||||
<Button
|
||||
startIcon={<InfoIcon/>}
|
||||
variant="outlined"
|
||||
color="warning"
|
||||
onClick={handleOpenModal}
|
||||
>
|
||||
Show stats{comment && " & comments"}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{openModal && (
|
||||
<Modal
|
||||
title="Report info"
|
||||
title={title}
|
||||
onClose={handleCloseModal}
|
||||
>
|
||||
<div className="vm-query-analyzer-info">
|
||||
{comment && (
|
||||
<div className="vm-query-analyzer-info-item vm-query-analyzer-info-item_comment">
|
||||
<div className="vm-query-analyzer-info-item__title">Comment:</div>
|
||||
<div className="vm-query-analyzer-info-item__text">{comment}</div>
|
||||
<div className="vm-query-analyzer-info__modal">
|
||||
{!!table.length && (
|
||||
<div className="vm-query-analyzer-info-stats">
|
||||
<div className="vm-query-analyzer-info-comment-header">
|
||||
<InfoIcon/>
|
||||
Stats
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
{table.map(({ column }) => (
|
||||
<th key={column}>
|
||||
{column}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{table[0]?.values.map((_, rowIndex) => (
|
||||
<tr key={rowIndex}>
|
||||
{table.map(({ values }, j) => (
|
||||
<td key={j}>
|
||||
{values[rowIndex]}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
{dataWithStats.map((d, i) => (
|
||||
<div
|
||||
className="vm-query-analyzer-info-item"
|
||||
key={i}
|
||||
>
|
||||
<div className="vm-query-analyzer-info-item__title">
|
||||
{dataWithStats.length > 1 ? `Query ${i + 1}:` : "Stats:"}
|
||||
</div>
|
||||
<div className="vm-query-analyzer-info-item__text">
|
||||
{Object.entries(d.stats || {}).map(([key, value]) => (
|
||||
<div key={key}>
|
||||
{key}: {value ?? "-"}
|
||||
</div>
|
||||
))}
|
||||
isPartial: {String(d.isPartial ?? "-")}
|
||||
|
||||
{comment && (
|
||||
<div className="vm-query-analyzer-info-comment">
|
||||
<div className="vm-query-analyzer-info-comment-header">
|
||||
<CommentIcon/>
|
||||
Comments
|
||||
</div>
|
||||
<div
|
||||
className="vm-query-analyzer-info-comment-body vm-markdown"
|
||||
dangerouslySetInnerHTML={{ __html: (marked(comment) as string) || comment }}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
<div className="vm-query-analyzer-info-type">
|
||||
{dataWithStats[0]?.vmui?.params ? "The report was created using vmui" : "The report was created manually"}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
)}
|
||||
|
||||
@@ -1,47 +1,115 @@
|
||||
@use "src/styles/variables" as *;
|
||||
|
||||
.vm-query-analyzer-info-header {
|
||||
display: flex;
|
||||
gap: $padding-global;
|
||||
.vm-query-analyzer-info {
|
||||
|
||||
&__period {
|
||||
&-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
gap: $padding-small;
|
||||
border: $border-divider;
|
||||
border-radius: $border-radius-small;
|
||||
padding: 6px $padding-global;
|
||||
|
||||
svg {
|
||||
width: calc($font-size-small + 1px);
|
||||
color: $color-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vm-query-analyzer-info {
|
||||
display: grid;
|
||||
gap: $padding-large;
|
||||
min-width: 300px;
|
||||
|
||||
&-type {
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
color: $color-text-secondary;
|
||||
}
|
||||
|
||||
&-item {
|
||||
display: grid;
|
||||
padding-bottom: $padding-large;
|
||||
border-bottom: $border-divider;
|
||||
line-height: 130%;
|
||||
font-size: $font-size-small;
|
||||
background-color: $color-background-body;
|
||||
z-index: 1;
|
||||
|
||||
&__title {
|
||||
font-weight: bold;
|
||||
font-size: $font-size-large;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&__text {
|
||||
white-space: pre-wrap;
|
||||
&__timerange {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: calc($padding-small / 2);
|
||||
border: $border-divider;
|
||||
border-radius: $border-radius-small;
|
||||
padding: calc($padding-small / 2) $padding-small;
|
||||
font-size: $font-size-small;
|
||||
|
||||
svg {
|
||||
width: calc($font-size-small + 1px);
|
||||
color: $color-primary;
|
||||
}
|
||||
}
|
||||
|
||||
&__info {
|
||||
margin-left: auto;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__modal {
|
||||
width: min(800px, 90vw);
|
||||
}
|
||||
|
||||
&-comment {
|
||||
position: relative;
|
||||
max-width: 800px;
|
||||
border-radius: $border-radius-medium;
|
||||
border: $border-divider;
|
||||
font-size: $font-size-small;
|
||||
|
||||
&-header {
|
||||
display: grid;
|
||||
grid-template-columns: 16px 1fr;
|
||||
align-items: center;
|
||||
gap: $padding-small;
|
||||
padding: $padding-small;
|
||||
border-bottom: $border-divider;
|
||||
background-color: $color-hover-black;
|
||||
font-weight: 500;
|
||||
z-index: 1;
|
||||
|
||||
svg {
|
||||
color: $color-primary;
|
||||
}
|
||||
}
|
||||
|
||||
&-body {
|
||||
padding: $padding-small;
|
||||
max-height: 60vh;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&-stats {
|
||||
border-radius: $border-radius-medium;
|
||||
border: $border-divider;
|
||||
font-size: $font-size-small;
|
||||
margin-bottom: $padding-global;
|
||||
overflow: hidden;
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
td, th {
|
||||
padding: $padding-small;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
tr {
|
||||
border-bottom: $border-divider;
|
||||
}
|
||||
|
||||
thead {
|
||||
th {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
tbody {
|
||||
tr {
|
||||
transition: background-color 0.3s;
|
||||
|
||||
&:hover {
|
||||
background-color: $color-hover-black;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,9 @@ import useBoolean from "../../hooks/useBoolean";
|
||||
import UploadJsonButtons from "../../components/UploadJsonButtons/UploadJsonButtons";
|
||||
import JsonForm from "./JsonForm/JsonForm";
|
||||
import "../TracePage/style.scss";
|
||||
import "./style.scss";
|
||||
import QueryAnalyzerView from "./QueryAnalyzerView/QueryAnalyzerView";
|
||||
import { InstantMetricResult, MetricResult, TracingData } from "../../api/types";
|
||||
import { InstantMetricResult, MetricResult, ReportMetaData, TracingData } from "../../api/types";
|
||||
import QueryAnalyzerInfo from "./QueryAnalyzerInfo/QueryAnalyzerInfo";
|
||||
import { TimeParams } from "../../types";
|
||||
import { dateFromSeconds, formatDateToUTC, humanizeSeconds } from "../../utils/time";
|
||||
@@ -21,15 +22,8 @@ export type DataAnalyzerType = {
|
||||
resultType: "vector" | "matrix";
|
||||
result: MetricResult[] | InstantMetricResult[]
|
||||
};
|
||||
stats?: {
|
||||
seriesFetched?: string;
|
||||
executionTimeMsec?: number
|
||||
};
|
||||
vmui?: {
|
||||
id: number;
|
||||
comment: string;
|
||||
params: Record<string, string>;
|
||||
};
|
||||
stats?: Record<string, string>;
|
||||
vmui?: ReportMetaData;
|
||||
status: string;
|
||||
trace?: TracingData;
|
||||
isPartial?: boolean;
|
||||
@@ -92,10 +86,12 @@ const QueryAnalyzer: FC = () => {
|
||||
setData(response);
|
||||
} else {
|
||||
setError("Invalid structure - JSON does not match the expected format");
|
||||
setData([]);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
setError(`${e.name}: ${e.message}`);
|
||||
setData([]);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -129,33 +125,16 @@ const QueryAnalyzer: FC = () => {
|
||||
}, [files]);
|
||||
|
||||
return (
|
||||
<div className="vm-trace-page">
|
||||
<div className="vm-query-analyzer">
|
||||
{hasData && (
|
||||
<div className="vm-trace-page-header">
|
||||
<div className="vm-trace-page-header-errors">
|
||||
<QueryAnalyzerInfo
|
||||
data={data}
|
||||
period={period}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<UploadJsonButtons
|
||||
onOpenModal={handleOpenModal}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="vm-trace-page-header-errors-item vm-trace-page-header-errors-item_margin-bottom">
|
||||
<Alert variant="error">{error}</Alert>
|
||||
<Button
|
||||
className="vm-trace-page-header-errors-item__close"
|
||||
startIcon={<CloseIcon/>}
|
||||
variant="text"
|
||||
color="error"
|
||||
onClick={handleCloseError}
|
||||
<div className="vm-query-analyzer-header">
|
||||
<QueryAnalyzerInfo
|
||||
data={data}
|
||||
period={period}
|
||||
/>
|
||||
<UploadJsonButtons
|
||||
onOpenModal={handleOpenModal}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -185,6 +164,19 @@ const QueryAnalyzer: FC = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="vm-query-analyzer-error">
|
||||
<Alert variant="error">{error}</Alert>
|
||||
<Button
|
||||
className="vm-query-analyzer-error__close"
|
||||
startIcon={<CloseIcon/>}
|
||||
variant="text"
|
||||
color="error"
|
||||
onClick={handleCloseError}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{openModal && (
|
||||
<Modal
|
||||
title="Paste JSON"
|
||||
|
||||
29
app/vmui/packages/vmui/src/pages/QueryAnalyzer/style.scss
Normal file
@@ -0,0 +1,29 @@
|
||||
@use "src/styles/variables" as *;
|
||||
|
||||
.vm-query-analyzer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
padding: $padding-medium 0;
|
||||
}
|
||||
|
||||
&-header {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
gap: $padding-global;
|
||||
margin-bottom: $padding-global;
|
||||
}
|
||||
|
||||
&-error {
|
||||
position: relative;
|
||||
margin: $padding-global 0;
|
||||
|
||||
&__close {
|
||||
position: absolute;
|
||||
top: $padding-small;
|
||||
right: $padding-small;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { useAppState } from "../../../state/common/StateContext";
|
||||
import { useCustomPanelState } from "../../../state/customPanel/CustomPanelStateContext";
|
||||
import { isValidHttpUrl } from "../../../utils/url";
|
||||
import { getExportDataUrl } from "../../../api/query-range";
|
||||
import { parseLineToJSON } from "../../../utils/json";
|
||||
|
||||
interface FetchQueryParams {
|
||||
hideQuery?: number[];
|
||||
@@ -24,14 +25,6 @@ interface FetchQueryReturn {
|
||||
abortFetch: () => void
|
||||
}
|
||||
|
||||
const parseLineToJSON = (line: string): ExportMetricResult | null => {
|
||||
try {
|
||||
return JSON.parse(line);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const useFetchExport = ({ hideQuery, showAllSeries }: FetchQueryParams): FetchQueryReturn => {
|
||||
const { query } = useQueryState();
|
||||
const { period } = useTimeState();
|
||||
@@ -62,7 +55,7 @@ export const useFetchExport = ({ hideQuery, showAllSeries }: FetchQueryParams):
|
||||
}
|
||||
}, [serverUrl, period, hideQuery, reduceMemUsage]);
|
||||
|
||||
const fetchData = useCallback(async ( { fetchUrl, stateSeriesLimits, showAllSeries }: {
|
||||
const fetchData = useCallback(async ({ fetchUrl, stateSeriesLimits, showAllSeries }: {
|
||||
fetchUrl: string[];
|
||||
stateSeriesLimits: SeriesLimits;
|
||||
showAllSeries?: boolean;
|
||||
@@ -99,12 +92,12 @@ export const useFetchExport = ({ hideQuery, showAllSeries }: FetchQueryParams):
|
||||
const lines = text.split("\n").filter(line => line);
|
||||
const lineLimited = lines.slice(0, freeTempSize).sort();
|
||||
lineLimited.forEach((line: string) => {
|
||||
const jsonLine = parseLineToJSON(line);
|
||||
const jsonLine = parseLineToJSON(line) as (ExportMetricResult | null);
|
||||
if (!jsonLine) return;
|
||||
tempData.push({
|
||||
group: counter,
|
||||
metric: jsonLine.metric,
|
||||
values: jsonLine.values.map((value, index) => [(jsonLine.timestamps[index]/1000), value]),
|
||||
values: jsonLine.values.map((value, index) => [(jsonLine.timestamps[index] / 1000), value]),
|
||||
} as MetricBase);
|
||||
});
|
||||
totalLength += lines.length;
|
||||
@@ -119,7 +112,7 @@ export const useFetchExport = ({ hideQuery, showAllSeries }: FetchQueryParams):
|
||||
} catch (e) {
|
||||
setIsLoading(false);
|
||||
if (e instanceof Error && e.name !== "AbortError") {
|
||||
setError(error);
|
||||
setError(String(e));
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import { DisplayType } from "../../types";
|
||||
import Hyperlink from "../../components/Main/Hyperlink/Hyperlink";
|
||||
import { CloseIcon } from "../../components/Main/Icons";
|
||||
import Button from "../../components/Main/Button/Button";
|
||||
import DownloadReport, { ReportType } from "../CustomPanel/DownloadReport/DownloadReport";
|
||||
|
||||
const RawSamplesLink = () => (
|
||||
<Hyperlink
|
||||
@@ -65,6 +66,7 @@ const RawQueryPage: FC = () => {
|
||||
queryErrors,
|
||||
setQueryErrors,
|
||||
abortFetch,
|
||||
fetchUrl,
|
||||
} = useFetchExport({ hideQuery, showAllSeries });
|
||||
|
||||
const controlsRef = useRef<HTMLDivElement>(null);
|
||||
@@ -106,12 +108,22 @@ const RawQueryPage: FC = () => {
|
||||
{showPageDescription && (
|
||||
<Alert variant="info">
|
||||
<div className="vm-explore-metrics-header-description">
|
||||
<p>
|
||||
This page provides a dedicated view for querying and displaying <RawSamplesLink/> from VictoriaMetrics.
|
||||
It expects only <TimeSeriesSelectorLink/> as a query argument.
|
||||
Users often assume that the <QueryDataLink/> returns data exactly as stored,
|
||||
but data samples and timestamps may be modified by the API.
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
This page provides a dedicated view for querying and displaying <RawSamplesLink/> from VictoriaMetrics.
|
||||
</li>
|
||||
<li>
|
||||
It expects only <TimeSeriesSelectorLink/> as a query argument.
|
||||
</li>
|
||||
<li>
|
||||
Deduplication can only be disabled if it was previously enabled on the server
|
||||
(<code>-dedup.minScrapeInterval</code>).
|
||||
</li>
|
||||
<li>
|
||||
Users often assume that the <QueryDataLink/> returns data exactly as stored,
|
||||
but data samples and timestamps may be modified by the API.
|
||||
</li>
|
||||
</ul>
|
||||
<Button
|
||||
variant="text"
|
||||
size="small"
|
||||
@@ -146,6 +158,12 @@ const RawQueryPage: FC = () => {
|
||||
<div className="vm-custom-panel-body-header__tabs">
|
||||
<DisplayTypeSwitch tabFilter={(tab) => (tab.value !== DisplayType.table)}/>
|
||||
</div>
|
||||
{data && (
|
||||
<DownloadReport
|
||||
fetchUrl={fetchUrl}
|
||||
reportType={ReportType.RAW_DATA}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<CustomPanelTabs
|
||||
graphData={data}
|
||||
|
||||
@@ -61,7 +61,8 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
justify-content: flex-end;
|
||||
min-height: calc(($vh * 50) - var(--scrollbar-height));
|
||||
|
||||
&__text {
|
||||
margin-bottom: $padding-global;
|
||||
|
||||
139
app/vmui/packages/vmui/src/styles/components/markdown.scss
Normal file
@@ -0,0 +1,139 @@
|
||||
@use "src/styles/variables" as *;
|
||||
|
||||
.vm-markdown {
|
||||
display: block;
|
||||
line-height: 1.5;
|
||||
|
||||
pre,
|
||||
code {
|
||||
font-size: 1em;
|
||||
font-family: $font-family-monospace
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: .5em;
|
||||
line-height: 1.25;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
|
||||
a,
|
||||
a:visited {
|
||||
color: $color-primary;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
body {
|
||||
line-height: 1.85;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1em;
|
||||
margin-bottom: 1.3em;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin: .5em 0 0.25em;
|
||||
font-weight: inherit;
|
||||
line-height: 1.42;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.8em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.6em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.4em;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
img,
|
||||
canvas,
|
||||
iframe,
|
||||
video,
|
||||
svg,
|
||||
select,
|
||||
textarea {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: $color-hover-black;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: 3px solid rgba($color-black, 0.2);
|
||||
padding-left: 1em;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
margin: 0.3em 0;
|
||||
list-style-position: inside;
|
||||
|
||||
li {
|
||||
margin-bottom: 0.3em;
|
||||
|
||||
ul, ol {
|
||||
margin-left: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 0.2em 0.4em;
|
||||
}
|
||||
|
||||
hr {
|
||||
border-top: $border-divider;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
em {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
del {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
code:not(pre code) {
|
||||
display: inline;
|
||||
vertical-align: middle;
|
||||
background: $color-hover-black;
|
||||
border-radius: $border-radius-small;
|
||||
border: $border-divider;
|
||||
tab-size: 4;
|
||||
font-variant-ligatures: none;
|
||||
padding: 0.12em 0.4em;
|
||||
font-size: 0.9em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
@@ -50,10 +50,6 @@ button {
|
||||
background: none;
|
||||
}
|
||||
|
||||
strong {
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
input[type='file'] {
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
@forward "./components/table";
|
||||
@forward "./components/link";
|
||||
@forward "./components/dynamic-number";
|
||||
@forward "./components/markdown";
|
||||
|
||||
:root {
|
||||
/* base palette */
|
||||
|
||||
7
app/vmui/packages/vmui/src/utils/json.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export const parseLineToJSON = (line: string) => {
|
||||
try {
|
||||
return JSON.parse(line);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -25,3 +25,13 @@ export const isEqualURLSearchParams = (params1: URLSearchParams, params2: URLSea
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const getApiEndpoint = (url: string): string | null => {
|
||||
try {
|
||||
const match = url.match(/\/api\/v1\/[^?]+/);
|
||||
return match ? match[0] : null;
|
||||
} catch (error) {
|
||||
console.error("Invalid URL:", error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -128,7 +128,7 @@ func (app *ServesMetrics) GetMetric(t *testing.T, metricName string) float64 {
|
||||
return res
|
||||
}
|
||||
}
|
||||
t.Fatalf("metic not found: %s", metricName)
|
||||
t.Fatalf("metric not found: %s", metricName)
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,10 @@ package tests
|
||||
import (
|
||||
"testing"
|
||||
|
||||
at "github.com/VictoriaMetrics/VictoriaMetrics/apptest"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
|
||||
at "github.com/VictoriaMetrics/VictoriaMetrics/apptest"
|
||||
)
|
||||
|
||||
// Data used in examples in
|
||||
|
||||
33
apptest/tests/maxingestionrate_test.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/apptest"
|
||||
)
|
||||
|
||||
// Data used in tests
|
||||
var testData = []string{
|
||||
"foo_bar 1.00",
|
||||
"foo_bar 2.00",
|
||||
}
|
||||
|
||||
func TestSingleMaxIngestionRateIncrementsMetric(t *testing.T) {
|
||||
tc := apptest.NewTestCase(t)
|
||||
defer tc.Stop()
|
||||
sut := tc.MustStartVmsingle("vmsingle", []string{"-maxIngestionRate=1"})
|
||||
sut.PrometheusAPIV1ImportPrometheus(t, testData, apptest.QueryOpts{})
|
||||
if got := sut.GetMetric(t, "vm_max_ingestion_rate_limit_reached_total"); got <= 0 {
|
||||
t.Fatalf("Unexpected vm_max_ingestion_rate_limit_reached_total: got %f, want >0", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSingleMaxIngestionRateDoesNotIncrementMetric(t *testing.T) {
|
||||
tc := apptest.NewTestCase(t)
|
||||
defer tc.Stop()
|
||||
sut := tc.MustStartVmsingle("vmsingle", []string{"-maxIngestionRate=15"})
|
||||
sut.PrometheusAPIV1ImportPrometheus(t, testData, apptest.QueryOpts{})
|
||||
if got, want := sut.GetMetric(t, "vm_max_ingestion_rate_limit_reached_total"), 0.0; got != want {
|
||||
t.Fatalf("Unexpected vm_max_ingestion_rate_limit_reached_total: got %f, want >0", got)
|
||||
}
|
||||
}
|
||||
@@ -210,3 +210,4 @@ Please see more examples on integration of VictoriaLogs with other log shippers
|
||||
* [opentelemetry-collector](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/deployment/docker/victorialogs/opentelemetry-collector)
|
||||
* [telegraf](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/deployment/docker/victorialogs/telegraf)
|
||||
* [fluentd]((https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/deployment/docker/victorialogs/fluentd)
|
||||
* [datadog-serverless](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/deployment/docker/victorialogs/datadog-serverless)
|
||||
|
||||
@@ -4,7 +4,7 @@ services:
|
||||
# And forward them to --remoteWrite.url
|
||||
vmagent:
|
||||
container_name: vmagent
|
||||
image: victoriametrics/vmagent:v1.108.0
|
||||
image: victoriametrics/vmagent:v1.108.1
|
||||
depends_on:
|
||||
- "vminsert"
|
||||
ports:
|
||||
@@ -39,7 +39,7 @@ services:
|
||||
# where N is number of vmstorages (2 in this case).
|
||||
vmstorage-1:
|
||||
container_name: vmstorage-1
|
||||
image: victoriametrics/vmstorage:v1.108.0-cluster
|
||||
image: victoriametrics/vmstorage:v1.108.1-cluster
|
||||
ports:
|
||||
- 8482
|
||||
- 8400
|
||||
@@ -51,7 +51,7 @@ services:
|
||||
restart: always
|
||||
vmstorage-2:
|
||||
container_name: vmstorage-2
|
||||
image: victoriametrics/vmstorage:v1.108.0-cluster
|
||||
image: victoriametrics/vmstorage:v1.108.1-cluster
|
||||
ports:
|
||||
- 8482
|
||||
- 8400
|
||||
@@ -66,7 +66,7 @@ services:
|
||||
# pre-process them and distributes across configured vmstorage shards.
|
||||
vminsert:
|
||||
container_name: vminsert
|
||||
image: victoriametrics/vminsert:v1.108.0-cluster
|
||||
image: victoriametrics/vminsert:v1.108.1-cluster
|
||||
depends_on:
|
||||
- "vmstorage-1"
|
||||
- "vmstorage-2"
|
||||
@@ -81,7 +81,7 @@ services:
|
||||
# vmselect collects results from configured `--storageNode` shards.
|
||||
vmselect-1:
|
||||
container_name: vmselect-1
|
||||
image: victoriametrics/vmselect:v1.108.0-cluster
|
||||
image: victoriametrics/vmselect:v1.108.1-cluster
|
||||
depends_on:
|
||||
- "vmstorage-1"
|
||||
- "vmstorage-2"
|
||||
@@ -94,7 +94,7 @@ services:
|
||||
restart: always
|
||||
vmselect-2:
|
||||
container_name: vmselect-2
|
||||
image: victoriametrics/vmselect:v1.108.0-cluster
|
||||
image: victoriametrics/vmselect:v1.108.1-cluster
|
||||
depends_on:
|
||||
- "vmstorage-1"
|
||||
- "vmstorage-2"
|
||||
@@ -112,7 +112,7 @@ services:
|
||||
# It can be used as an authentication proxy.
|
||||
vmauth:
|
||||
container_name: vmauth
|
||||
image: victoriametrics/vmauth:v1.108.0
|
||||
image: victoriametrics/vmauth:v1.108.1
|
||||
depends_on:
|
||||
- "vmselect-1"
|
||||
- "vmselect-2"
|
||||
@@ -127,7 +127,7 @@ services:
|
||||
# vmalert executes alerting and recording rules
|
||||
vmalert:
|
||||
container_name: vmalert
|
||||
image: victoriametrics/vmalert:v1.108.0
|
||||
image: victoriametrics/vmalert:v1.108.1
|
||||
depends_on:
|
||||
- "vmauth"
|
||||
ports:
|
||||
|
||||
@@ -45,7 +45,7 @@ services:
|
||||
# storing logs and serving read queries.
|
||||
victorialogs:
|
||||
container_name: victorialogs
|
||||
image: victoriametrics/victoria-logs:v1.3.2-victorialogs
|
||||
image: victoriametrics/victoria-logs:v1.4.0-victorialogs
|
||||
command:
|
||||
- "--storageDataPath=/vlogs"
|
||||
- "--httpListenAddr=:9428"
|
||||
@@ -60,7 +60,7 @@ services:
|
||||
# scraping, storing metrics and serve read requests.
|
||||
victoriametrics:
|
||||
container_name: victoriametrics
|
||||
image: victoriametrics/victoria-metrics:v1.108.0
|
||||
image: victoriametrics/victoria-metrics:v1.108.1
|
||||
ports:
|
||||
- 8428:8428
|
||||
volumes:
|
||||
@@ -79,7 +79,7 @@ services:
|
||||
# depending on the requested path.
|
||||
vmauth:
|
||||
container_name: vmauth
|
||||
image: victoriametrics/vmauth:v1.108.0
|
||||
image: victoriametrics/vmauth:v1.108.1
|
||||
depends_on:
|
||||
- "victoriametrics"
|
||||
- "victorialogs"
|
||||
@@ -96,7 +96,7 @@ services:
|
||||
# vmalert executes alerting and recording rules according to given rule type.
|
||||
vmalert:
|
||||
container_name: vmalert
|
||||
image: victoriametrics/vmalert:v1.108.0
|
||||
image: victoriametrics/vmalert:v1.108.1
|
||||
depends_on:
|
||||
- "vmauth"
|
||||
- "alertmanager"
|
||||
|
||||
@@ -4,7 +4,7 @@ services:
|
||||
# And forward them to --remoteWrite.url
|
||||
vmagent:
|
||||
container_name: vmagent
|
||||
image: victoriametrics/vmagent:v1.108.0
|
||||
image: victoriametrics/vmagent:v1.108.1
|
||||
depends_on:
|
||||
- "victoriametrics"
|
||||
ports:
|
||||
@@ -22,7 +22,7 @@ services:
|
||||
# storing metrics and serve read requests.
|
||||
victoriametrics:
|
||||
container_name: victoriametrics
|
||||
image: victoriametrics/victoria-metrics:v1.108.0
|
||||
image: victoriametrics/victoria-metrics:v1.108.1
|
||||
ports:
|
||||
- 8428:8428
|
||||
- 8089:8089
|
||||
@@ -65,7 +65,7 @@ services:
|
||||
# vmalert executes alerting and recording rules
|
||||
vmalert:
|
||||
container_name: vmalert
|
||||
image: victoriametrics/vmalert:v1.108.0
|
||||
image: victoriametrics/vmalert:v1.108.1
|
||||
depends_on:
|
||||
- "victoriametrics"
|
||||
- "alertmanager"
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
services:
|
||||
# meta service will be ignored by compose
|
||||
.victorialogs:
|
||||
image: docker.io/victoriametrics/victoria-logs:v1.3.2-victorialogs
|
||||
image: docker.io/victoriametrics/victoria-logs:v1.4.0-victorialogs
|
||||
command:
|
||||
- -storageDataPath=/vlogs
|
||||
- -loggerFormat=json
|
||||
- -syslog.listenAddr.tcp=0.0.0.0:8094
|
||||
- -datadog.streamFields=service,hostname,ddsource
|
||||
- -journald.streamFields=_HOSTNAME,_SYSTEMD_UNIT,_PID
|
||||
- -journald.ignoreFields=MESSAGE_ID,INVOCATION_ID,USER_INVOCATION_ID,
|
||||
- -journald.ignoreFields=_BOOT_ID,_MACHINE_ID,_SYSTEMD_INVOCATION_ID,_STREAM_ID,_UID
|
||||
@@ -17,8 +18,8 @@ services:
|
||||
timeout: 1s
|
||||
retries: 10
|
||||
|
||||
dd-logs:
|
||||
image: docker.io/victoriametrics/vmauth:v1.108.0
|
||||
dd-proxy:
|
||||
image: docker.io/victoriametrics/vmauth:v1.108.1
|
||||
restart: on-failure
|
||||
volumes:
|
||||
- ./:/etc/vmauth
|
||||
|
||||
@@ -11,8 +11,8 @@ services:
|
||||
- /sys/fs/cgroup/:/host/sys/fs/cgroup:ro
|
||||
environment:
|
||||
DD_API_KEY: test
|
||||
DD_URL: http://victoriametrics:8428/datadog
|
||||
DD_LOGS_CONFIG_LOGS_DD_URL: http://dd-logs:8427
|
||||
DD_URL: http://dd-proxy:8427
|
||||
DD_LOGS_CONFIG_LOGS_DD_URL: http://dd-proxy:8427
|
||||
DD_LOGS_CONFIG_CONTAINER_COLLECT_ALL: true
|
||||
DD_LOGS_ENABLED: true
|
||||
DD_LOGS_CONFIG_USE_HTTP: true
|
||||
|
||||
1
deployment/docker/victorialogs/datadog-serverless/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
**/logs
|
||||
31
deployment/docker/victorialogs/datadog-serverless/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Docker compose Serverless with DataDog extension integration with VictoriaLogs
|
||||
|
||||
The folder contains examples of [DataDog serverless](https://docs.datadoghq.com/serverless) integration with VictoriaLogs for:
|
||||
|
||||
* [AWS Lambda](./aws)
|
||||
* [GCP Cloud Run](./gcp)
|
||||
|
||||
To spin-up environment `cd` to any of listed above directories run the following command:
|
||||
```
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
To shut down the docker-compose environment run the following command:
|
||||
```
|
||||
docker compose down
|
||||
docker compose rm -f
|
||||
```
|
||||
|
||||
The docker compose file contains the following components:
|
||||
|
||||
* dd-proxy - VMAuth proxy, with path-based routing to `victoriametrics` and `victorialogs`
|
||||
* lambda - Serverless application with Datadog logs collection extension, which is configured to collect and write data to `victorialogs` and `victoriametrics` via `dd-proxy`
|
||||
* victorialogs - VictoriaLogs log database, which accepts the data from `datadog`
|
||||
* victoriametrics - VictoriaMetrics metrics database, which collects metrics from `victorialogs` and `datadog`
|
||||
|
||||
Querying the data
|
||||
|
||||
* [vmui](https://docs.victoriametrics.com/victorialogs/querying/#vmui) - a web UI is accessible by `http://localhost:9428/select/vmui`
|
||||
* for querying the data via command-line please check [these docs](https://docs.victoriametrics.com/victorialogs/querying/#command-line)
|
||||
|
||||
Please, note that `_stream_fields` parameter must follow recommended [best practices](https://docs.victoriametrics.com/victorialogs/keyconcepts/#stream-fields) to achieve better performance.
|
||||
@@ -0,0 +1,32 @@
|
||||
FROM golang:1.23-bullseye as aws-lambda-rie
|
||||
|
||||
# Install custom aws-lambda-rie till Telemetry API support is not merged
|
||||
# https://github.com/aws/aws-lambda-runtime-interface-emulator/pull/137
|
||||
|
||||
RUN \
|
||||
git clone https://github.com/VictoriaMetrics/aws-lambda-runtime-interface-emulator -b added-telemetry-api-support /tmp/aws-lambda-rie && \
|
||||
cd /tmp/aws-lambda-rie && \
|
||||
CGO_ENABLED=0 go build -buildvcs=false -ldflags "-s -w" -o /aws-lambda-rie ./cmd/aws-lambda-rie
|
||||
|
||||
FROM python:3.12-bullseye
|
||||
|
||||
RUN \
|
||||
apt update && \
|
||||
apt install -y \
|
||||
curl \
|
||||
g++ \
|
||||
make \
|
||||
cmake \
|
||||
unzip \
|
||||
libcurl4-openssl-dev && \
|
||||
mkdir -p /var/task && \
|
||||
pip install \
|
||||
--target /var/task awslambdaric datadog-lambda
|
||||
|
||||
WORKDIR /var/task
|
||||
COPY --from=aws-lambda-rie /aws-lambda-rie /var/task/aws-lambda-rie
|
||||
COPY main.py /var/task/
|
||||
COPY --from=public.ecr.aws/datadog/lambda-extension:67 /opt/. /opt/
|
||||
|
||||
ENTRYPOINT ["/var/task/aws-lambda-rie"]
|
||||
CMD ["/usr/local/bin/python", "-m", "awslambdaric", "main.lambda_handler"]
|
||||
@@ -0,0 +1,25 @@
|
||||
name: datadog-serverless-aws
|
||||
include:
|
||||
- ../../compose-base.yml
|
||||
services:
|
||||
lambda:
|
||||
build: .
|
||||
restart: on-failure
|
||||
ports:
|
||||
- 8080:8080
|
||||
environment:
|
||||
DD_LOG_LEVEL: trace
|
||||
DD_LOGS_ENABLED: true
|
||||
DD_SOURCE: test
|
||||
DD_API_KEY: test
|
||||
DD_DD_URL: http://dd-proxy:8427
|
||||
DD_EXTENSION_VERSION: compatibility
|
||||
DD_PROFILING_ENABLED: false
|
||||
DD_ENHANCED_METRICS: false
|
||||
DD_LOGS_CONFIG_LOGS_DD_URL: http://dd-proxy:8427
|
||||
DD_SERVERLESS_FLUSH_STRATEGY: periodically,100
|
||||
depends_on:
|
||||
victorialogs:
|
||||
condition: service_healthy
|
||||
victoriametrics:
|
||||
condition: service_healthy
|
||||
@@ -0,0 +1,15 @@
|
||||
from datadog_lambda.metric import lambda_metric
|
||||
|
||||
def lambda_handler(event, context):
|
||||
lambda_metric(
|
||||
metric_name='coffee_house.order_value',
|
||||
value=12.45,
|
||||
tags=['product:latte', 'order:online']
|
||||
)
|
||||
|
||||
print('Hello, world!')
|
||||
|
||||
return {
|
||||
'statusCode': 200,
|
||||
'body': 'Hello from serverless!'
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
FROM python:3.12-bullseye
|
||||
|
||||
COPY --from=datadog/serverless-init:1 /datadog-init /app/datadog-init
|
||||
ENV DD_SERVICE=datadog-demo-run-go
|
||||
ENV DD_ENV=datadog-demo
|
||||
ENV DD_VERSION=1
|
||||
|
||||
RUN pip install Flask gunicorn datadog
|
||||
|
||||
WORKDIR /var/task
|
||||
COPY main.py /var/task/
|
||||
|
||||
ENTRYPOINT ["/app/datadog-init"]
|
||||
CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 main:app
|
||||
@@ -0,0 +1,24 @@
|
||||
name: datadog-serverless-gcp
|
||||
include:
|
||||
- ../../compose-base.yml
|
||||
services:
|
||||
cloud-run:
|
||||
build: .
|
||||
restart: on-failure
|
||||
ports:
|
||||
- 8080:8080
|
||||
environment:
|
||||
PORT: 8080
|
||||
DD_LOG_LEVEL: trace
|
||||
DD_LOGS_ENABLED: true
|
||||
DD_SOURCE: test
|
||||
DD_API_KEY: test
|
||||
DD_DD_URL: http://dd-proxy:8427
|
||||
DD_PROFILING_ENABLED: false
|
||||
DD_ENHANCED_METRICS: false
|
||||
DD_LOGS_CONFIG_LOGS_DD_URL: http://dd-proxy:8427
|
||||
depends_on:
|
||||
victorialogs:
|
||||
condition: service_healthy
|
||||
victoriametrics:
|
||||
condition: service_healthy
|
||||
@@ -0,0 +1,23 @@
|
||||
from datadog import initialize, statsd
|
||||
|
||||
import os
|
||||
|
||||
from flask import Flask
|
||||
|
||||
options = {
|
||||
"statsd_host": "127.0.0.1",
|
||||
"statsd_port": 8125,
|
||||
}
|
||||
|
||||
initialize(**options)
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/')
|
||||
def hello_world():
|
||||
statsd.gauge('active.connections', 1001, tags=["protocol:http"])
|
||||
target = os.environ.get('TARGET', 'World')
|
||||
return 'Hello {}!\n'.format(target)
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(debug=True,host='0.0.0.0',port=int(os.environ.get('PORT', 8080)))
|
||||
@@ -4,3 +4,12 @@ unauthorized_user:
|
||||
- "/api/v2/logs"
|
||||
- "/api/v1/validate"
|
||||
url_prefix: "http://victorialogs:9428/insert/datadog/"
|
||||
- src_paths:
|
||||
- "/api/v1/series"
|
||||
- "/api/v2/series"
|
||||
- "/api/beta/sketches"
|
||||
- "/api/v1/validate"
|
||||
- "/api/v1/check_run"
|
||||
- "/intake"
|
||||
- "/api/v1/metadata"
|
||||
url_prefix: "http://victoriametrics:8428/datadog/"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
services:
|
||||
vmagent:
|
||||
container_name: vmagent
|
||||
image: victoriametrics/vmagent:v1.108.0
|
||||
image: victoriametrics/vmagent:v1.108.1
|
||||
depends_on:
|
||||
- "victoriametrics"
|
||||
ports:
|
||||
@@ -18,7 +18,7 @@ services:
|
||||
|
||||
victoriametrics:
|
||||
container_name: victoriametrics
|
||||
image: victoriametrics/victoria-metrics:v1.108.0
|
||||
image: victoriametrics/victoria-metrics:v1.108.1
|
||||
ports:
|
||||
- 8428:8428
|
||||
volumes:
|
||||
@@ -50,7 +50,7 @@ services:
|
||||
|
||||
vmalert:
|
||||
container_name: vmalert
|
||||
image: victoriametrics/vmalert:v1.108.0
|
||||
image: victoriametrics/vmalert:v1.108.1
|
||||
depends_on:
|
||||
- "victoriametrics"
|
||||
ports:
|
||||
|
||||
@@ -3,7 +3,7 @@ version: "3"
|
||||
services:
|
||||
# Run `make package-victoria-logs` to build victoria-logs image
|
||||
vlogs:
|
||||
image: docker.io/victoriametrics/victoria-logs:v1.3.2-victorialogs
|
||||
image: docker.io/victoriametrics/victoria-logs:v1.4.0-victorialogs
|
||||
volumes:
|
||||
- vlogs:/vlogs
|
||||
ports:
|
||||
@@ -46,7 +46,7 @@ services:
|
||||
- "--config=/config.yml"
|
||||
|
||||
vmsingle:
|
||||
image: victoriametrics/victoria-metrics:v1.108.0
|
||||
image: victoriametrics/victoria-metrics:v1.108.1
|
||||
ports:
|
||||
- "8428:8428"
|
||||
command:
|
||||
|
||||
@@ -41,7 +41,7 @@ We use [labels](https://docs.github.com/en/issues/using-labels-and-milestones-to
|
||||
to categorize GitHub issues. We have the following labels:
|
||||
1. A component label: vmalert, vmagent, etc. Add this label to the issue if it is related to a specific component.
|
||||
1. An issue type: `bug`, `enhancement`, `question`.
|
||||
1. `enterprize`, assigned to issues related to ENT features
|
||||
1. `enterprise`, assigned to issues related to ENT features
|
||||
1. `need more info`, assigned to issues which require elaboration from the issue creator.
|
||||
For example, if we weren't able to reproduce the reported bug based on the ticket description then we ask additional
|
||||
questions which could help to reproduce the issue and add `need more info` label. This label helps other maintainers
|
||||
|
||||
@@ -1715,6 +1715,7 @@ See also [resource usage limits docs](#resource-usage-limits).
|
||||
|
||||
By default, VictoriaMetrics is tuned for an optimal resource usage under typical workloads. Some workloads may need fine-grained resource usage limits. In these cases the following command-line flags may be useful:
|
||||
|
||||
- `-maxIngestionRate` limits samples/second ingested. This may be useful when CPU resources are limited or overloaded.
|
||||
- `-memory.allowedPercent` and `-memory.allowedBytes` limit the amounts of memory, which may be used for various internal caches at VictoriaMetrics.
|
||||
Note that VictoriaMetrics may use more memory, since these flags don't limit additional memory, which may be needed on a per-query basis.
|
||||
- `-search.maxMemoryPerQuery` limits the amounts of memory, which can be used for processing a single query. Queries, which need more memory, are rejected.
|
||||
@@ -1793,7 +1794,7 @@ By default, VictoriaMetrics is tuned for an optimal resource usage under typical
|
||||
- `-search.maxExportSeries` limits maximum number of time series, which can be returned from [/api/v1/export* APIs](#how-to-export-data-in-json-line-format).
|
||||
The duration of the export queries is limited via `-search.maxExportDuration` flag. This option allows limiting memory usage.
|
||||
- `-search.maxTSDBStatusSeries` limits maximum number of time series, which can be processed during the call to [/api/v1/status/tsdb](#tsdb-stats).
|
||||
The duration of the status queries is limited via `-search.maxStatusRequestDuration` flag. This option allows limiting memory usage.
|
||||
The duration of the status queries is limited via `-search.maxStatusRequestDuration` flag. This option allows limiting memory usage.
|
||||
|
||||
See also [resource usage limits at VictoriaMetrics cluster](https://docs.victoriametrics.com/cluster-victoriametrics/#resource-usage-limits),
|
||||
[cardinality limiter](#cardinality-limiter) and [capacity planning docs](#capacity-planning).
|
||||
@@ -2969,6 +2970,9 @@ Pass `-help` to VictoriaMetrics in order to see the list of supported command-li
|
||||
Per-second limit on the number of WARN messages. If more than the given number of warns are emitted per second, then the remaining warns are suppressed. Zero values disable the rate limit
|
||||
-maxConcurrentInserts int
|
||||
The maximum number of concurrent insert requests. Set higher value when clients send data over slow networks. Default value depends on the number of available CPU cores. It should work fine in most cases since it minimizes resource usage. See also -insert.maxQueueDuration (default 32)
|
||||
-maxIngestionRate int
|
||||
The maximum number of samples vmsingle can receive per second. Data ingestion is paused when the limit is exceeded
|
||||
By default there are no limits on samples ingestion rate.
|
||||
-maxInsertRequestSize size
|
||||
The maximum size in bytes of a single Prometheus remote_write API request
|
||||
Supports the following optional suffixes for size values: KB, MB, GB, TB, KiB, MiB, GiB, TiB (default 33554432)
|
||||
|
||||
@@ -133,6 +133,8 @@ The helm chart repository [https://github.com/VictoriaMetrics/helm-charts/](http
|
||||
|
||||
### Bump the version of images
|
||||
|
||||
> Note that helm charts versioning uses its own versioning scheme. The version of the charts not tied to the version of VictoriaMetrics components.
|
||||
|
||||
Bump `tag` field in `values.yaml` with new release version.
|
||||
Bump `appVersion` field in `Chart.yaml` with new release version.
|
||||
Add new line to "Next release" section in `CHANGELOG.md` about version update (the line must always start with "`-`"). Do **NOT** change headers in `CHANGELOG.md`.
|
||||
@@ -157,6 +159,8 @@ Once updated, run the following commands:
|
||||
|
||||
## Ansible Roles
|
||||
|
||||
> Note that ansible playbooks versioning uses its own versioning scheme. The version of the playbooks is not tied to the version of VictoriaMetrics components.
|
||||
|
||||
1. Update the version of VictoriaMetrics components at [https://github.com/VictoriaMetrics/ansible-playbooks](https://github.com/VictoriaMetrics/ansible-playbooks).
|
||||
1. Commit changes.
|
||||
1. Create a new tag with `git tag -sm <TAG> <TAG>`.
|
||||
|
||||
@@ -136,7 +136,7 @@ If you see unexpected or unreliable query results from VictoriaMetrics, then try
|
||||
Note that responses returned from [/api/v1/query](https://docs.victoriametrics.com/keyconcepts/#instant-query)
|
||||
and from [/api/v1/query_range](https://docs.victoriametrics.com/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/#vmui) in `Raw Query` tab.
|
||||
for details. The raw samples can be also viewed in [vmui](https://docs.victoriametrics.com/#vmui) in `Raw Query` tab and shared via `export` button.
|
||||
|
||||
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.html).
|
||||
|
||||
@@ -16,13 +16,29 @@ according to [these docs](https://docs.victoriametrics.com/victorialogs/quicksta
|
||||
|
||||
## tip
|
||||
|
||||
* FEATURE: [Datadog data ingestion](https://docs.victoriametrics.com/victorialogs/data-ingestion/datadog-agent/): added `-datadog.streamFields` and `-datadog.ignoreFields` flags to configured default stream and ignore fields. Useful for Datadog serverless plugin, which doesn't allow to provide extra headers of query args.
|
||||
|
||||
* BUGFIX: [Datadog data ingestion](https://docs.victoriametrics.com/victorialogs/data-ingestion/datadog-agent/): accepts `message` field as both string and object type to fix compatibility with Datadog serverless extension, which sends logs data in format, which is not documented. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7761).
|
||||
* BUGFIX: [vlinsert](https://docs.victoriametrics.com/victorialogs/): order of VL-Msg-Field values now defines a priority of these fields and it's now obvious for a user which field will be picked if multiple msg_field values exist in a row.
|
||||
|
||||
## [v1.4.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.4.0-victorialogs)
|
||||
|
||||
Released at 2024-12-22
|
||||
|
||||
* FEATURE: [`stats` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#stats-pipe): allow non-numeric field values at [`median`](https://docs.victoriametrics.com/victorialogs/logsql/#median-stats) and [`quantile`](https://docs.victoriametrics.com/victorialogs/logsql/#quantile-stats) stats functions.
|
||||
* FEATURE: improve performance of [`stats` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#stats-pipe) and [`top` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#top-pipe) by up to 2x when these pipes are applied to logs with millions of unique `by (...)` groups.
|
||||
* FEATURE: [`stats` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#stats-pipe): add [`count_uniq_hash`](https://docs.victoriametrics.com/victorialogs/logsql/#count_uniq_hash-stats) function, which counts the number of unique value hashes. This number is usually a good approximation to the number of unique values, so the `count_uniq_hash` can be used as a faster alternative to [`count_uniq`](https://docs.victoriametrics.com/victorialogs/logsql/#count_uniq-stats).
|
||||
* FEATURE: [`stats` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#stats-pipe): improve performance of [`count_uniq`](https://docs.victoriametrics.com/victorialogs/logsql/#count_uniq-stats) and [`uniq_values`](https://docs.victoriametrics.com/victorialogs/logsql/#uniq_values-stats) functions when they are applied to fields with big number of unique values.
|
||||
* FEATURE: [`facets` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#facets-pipe): add an ability to return log fields with the same values across all the selected logs by adding `keep_const_fields` option. Such log fields aren't interesting in most cases, so they aren't returned by default.
|
||||
* FEATURE: [`in` filter](https://docs.victoriametrics.com/victorialogs/logsql/#multi-exact-filter): improve performance for `in(<query>)` when the `<query>` returns big number of values.
|
||||
* FEATURE: [HTTP querying APIs](https://docs.victoriametrics.com/victorialogs/querying/#http-api): allow passing arbitrary [LogsQL filters](https://docs.victoriametrics.com/victorialogs/logsql/#filters) to `extra_filters` and `extra_stream_filters` query args. See [these docs](https://docs.victoriametrics.com/victorialogs/querying/#extra-filters) and [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5542) for details.
|
||||
* FEATURE: [Grafana Loki data ingestion](https://docs.victoriametrics.com/victorialogs/data-ingestion/promtail/): add support of Loki healthcheck `/insert/ready` endpoint. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7824).
|
||||
* FEATURE: [`stream_context` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#stream_context-pipe): return an error as soon as too many logs and/or [log streams](https://docs.victoriametrics.com/victorialogs/keyconcepts/#stream-fields) are passed to this pipe. This prevents from excess resource usage by the `stream_context` pipe when it is improperly used. It is expected that the results of this pipe are investigated by humans, who cannot inspect surrounding logs for millions of the logs passed to `stream_context`. This change addresses [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7903) and [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7766) issues.
|
||||
|
||||
* BUGFIX: [syslog data ingestion](https://docs.victoriametrics.com/victorialogs/data-ingestion/syslog/): correctly parse rows with multiple consecutive spaces between fields. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7776).
|
||||
* BUGFIX: [web UI](https://docs.victoriametrics.com/victorialogs/querying/#web-ui): fix cursor reset in query input field. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7288).
|
||||
* BUGFIX: [`sort` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#sort-pipe): fix improper sorting of numeric fields in some cases.
|
||||
* BUGFIX: properly return an empty minimum value from [`min` stats function](https://docs.victoriametrics.com/victorialogs/logsql/#min-stats).
|
||||
|
||||
## [v1.3.2](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.3.2-victorialogs)
|
||||
|
||||
|
||||
@@ -2668,7 +2668,7 @@ See also:
|
||||
with the maximum number of matching log entries.
|
||||
|
||||
For example, the following query returns top 7 [log streams](https://docs.victoriametrics.com/victorialogs/keyconcepts/#stream-fields)
|
||||
with the maximum number of log entries over the last 5 minutes:
|
||||
with the maximum number of log entries over the last 5 minutes. The number of entries are returned in the `hits` field:
|
||||
|
||||
```logsql
|
||||
_time:5m | top 7 by (_stream)
|
||||
@@ -2689,6 +2689,12 @@ For example, the following query is equivalent to the previous one:
|
||||
_time:5m | fields ip | top
|
||||
```
|
||||
|
||||
It is possible to give another name for the `hits` field via `hits as <new_name>` syntax. For example, the following query returns top per-`path` hits in the `visits` field:
|
||||
|
||||
```logsql
|
||||
_time:5m | top by (path) hits as visits
|
||||
```
|
||||
|
||||
It is possible to set `rank` field per each returned entry for `top` pipe by adding `with rank`. For example, the following query sets the `rank` field per each returned `ip`:
|
||||
|
||||
```logsql
|
||||
@@ -3054,9 +3060,9 @@ LogsQL supports the following functions for [`stats` pipe](#stats-pipe):
|
||||
- [`count_uniq`](#count_uniq-stats) returns the number of unique non-empty values for the given [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
|
||||
- [`count_uniq_hash`](#count_uniq_hash-stats) returns the number of unique hashes for non-empty values at the given [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
|
||||
- [`max`](#max-stats) returns the maximum value over the given [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
|
||||
- [`median`](#median-stats) returns the [median](https://en.wikipedia.org/wiki/Median) value over the given numeric [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
|
||||
- [`median`](#median-stats) returns the [median](https://en.wikipedia.org/wiki/Median) value over the given [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
|
||||
- [`min`](#min-stats) returns the minimum value over the given [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
|
||||
- [`quantile`](#quantile-stats) returns the given quantile for the given numeric [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
|
||||
- [`quantile`](#quantile-stats) returns the given quantile for the given [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
|
||||
- [`rate`](#rate-stats) returns the average per-second rate of matching logs on the selected time range.
|
||||
- [`rate_sum`](#rate_sum-stats) returns the average per-second rate of sum for the given [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
|
||||
- [`row_any`](#row_any-stats) returns a sample [log entry](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model) per each selected [stats group](#stats-by-fields).
|
||||
@@ -3174,7 +3180,7 @@ See also:
|
||||
- [`uniq_values`](#uniq_values-stats)
|
||||
- [`count`](#count-stats)
|
||||
|
||||
### count_uniq_hash
|
||||
### count_uniq_hash stats
|
||||
|
||||
`count_uniq_hash(field1, ..., fieldN)` [stats pipe function](#stats-pipe-functions) calculates the number of unique hashes for non-empty `(field1, ..., fieldN)` tuples.
|
||||
This is a good estimation for the number of unique values in general case, while it works faster and uses less memory than [`count_uniq`](#count_uniq-stats)
|
||||
@@ -3223,8 +3229,8 @@ See also:
|
||||
|
||||
### median stats
|
||||
|
||||
`median(field1, ..., fieldN)` [stats pipe function](#stats-pipe-functions) calculates the [median](https://en.wikipedia.org/wiki/Median) value across
|
||||
the give numeric [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
|
||||
`median(field1, ..., fieldN)` [stats pipe function](#stats-pipe-functions) calculates the estimated [median](https://en.wikipedia.org/wiki/Median) value across
|
||||
the given [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
|
||||
|
||||
For example, the following query return median for the `duration` [field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model)
|
||||
over logs for the last 5 minutes:
|
||||
@@ -3261,7 +3267,7 @@ See also:
|
||||
|
||||
### quantile stats
|
||||
|
||||
`quantile(phi, field1, ..., fieldN)` [stats pipe function](#stats-pipe-functions) calculates `phi` [percentile](https://en.wikipedia.org/wiki/Percentile) over numeric values
|
||||
`quantile(phi, field1, ..., fieldN)` [stats pipe function](#stats-pipe-functions) calculates an estimated `phi` [percentile](https://en.wikipedia.org/wiki/Percentile) over values
|
||||
for the given [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model). The `phi` must be in the range `0 ... 1`, where `0` means `0th` percentile,
|
||||
while `1` means `100th` percentile.
|
||||
|
||||
|
||||
@@ -33,8 +33,8 @@ Just download archive for the needed Operating system and architecture, unpack i
|
||||
For example, the following commands download VictoriaLogs archive for Linux/amd64, unpack and run it:
|
||||
|
||||
```sh
|
||||
curl -L -O https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.3.2-victorialogs/victoria-logs-linux-amd64-v1.3.2-victorialogs.tar.gz
|
||||
tar xzf victoria-logs-linux-amd64-v1.3.2-victorialogs.tar.gz
|
||||
curl -L -O https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.4.0-victorialogs/victoria-logs-linux-amd64-v1.4.0-victorialogs.tar.gz
|
||||
tar xzf victoria-logs-linux-amd64-v1.4.0-victorialogs.tar.gz
|
||||
./victoria-logs-prod
|
||||
```
|
||||
|
||||
@@ -58,7 +58,7 @@ Here is the command to run VictoriaLogs in a Docker container:
|
||||
|
||||
```sh
|
||||
docker run --rm -it -p 9428:9428 -v ./victoria-logs-data:/victoria-logs-data \
|
||||
docker.io/victoriametrics/victoria-logs:v1.3.2-victorialogs
|
||||
docker.io/victoriametrics/victoria-logs:v1.4.0-victorialogs
|
||||
```
|
||||
|
||||
See also:
|
||||
|
||||
@@ -20,7 +20,17 @@ unauthorized_user:
|
||||
url_map:
|
||||
- src_paths:
|
||||
- "/api/v2/logs"
|
||||
url_prefix: "`<victoria-logs-base-url>`/insert/datadog/"
|
||||
- "/api/v1/validate"
|
||||
url_prefix: `<victoria-logs-base-url>`/insert/datadog/
|
||||
- src_paths:
|
||||
- "/api/v1/series"
|
||||
- "/api/v2/series"
|
||||
- "/api/beta/sketches"
|
||||
- "/api/v1/validate"
|
||||
- "/api/v1/check_run"
|
||||
- "/intake"
|
||||
- "/api/v1/metadata"
|
||||
url_prefix: `<victoria-metrics-base-url>`/datadog/
|
||||
```
|
||||
|
||||
To start ingesting logs from DataDog agent please specify a custom URL instead of default one for sending collected logs to [VictoriaLogs](https://docs.victoriametrics.com/VictoriaLogs/):
|
||||
@@ -40,7 +50,7 @@ custom:
|
||||
apiKey: fakekey # Set any key, otherwise plugin fails
|
||||
provider:
|
||||
environment:
|
||||
LOGS_DD_URL: `<vmauth-base-url>`/ # VictoriaLogs endpoint for DataDog
|
||||
DD_DD_URL: `<vmauth-base-url>`/ # VMAuth endpoint for DataDog
|
||||
```
|
||||
|
||||
Substitute the `<vmauth-base-url>` address with the real address of VMAuth proxy.
|
||||
@@ -50,3 +60,4 @@ See also:
|
||||
- [Data ingestion troubleshooting](https://docs.victoriametrics.com/victorialogs/data-ingestion/#troubleshooting).
|
||||
- [How to query VictoriaLogs](https://docs.victoriametrics.com/victorialogs/querying/).
|
||||
- [Docker-compose demo for Datadog integration with VictoriaLogs](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/deployment/docker/victorialogs/datadog-agent).
|
||||
- [Docker-compose demo for Datadog Serverless integration with VictoriaLogs](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/deployment/docker/victorialogs/datadog-serverless).
|
||||
|
||||
@@ -11,6 +11,9 @@ aliases:
|
||||
- /victorialogs/data-ingestion/Filebeat.html
|
||||
- /victorialogs/data-ingestion/filebeat.html
|
||||
---
|
||||
|
||||
_Tested with filebeat [8.15.1+](https://www.elastic.co/guide/en/beats/libbeat/8.17/release-notes-8.15.1.html.)._
|
||||
|
||||
Specify [`output.elasticsearch`](https://www.elastic.co/guide/en/beats/filebeat/current/elasticsearch-output.html) section in the `filebeat.yml`
|
||||
for sending the collected logs to [VictoriaLogs](https://docs.victoriametrics.com/victorialogs/):
|
||||
|
||||
|
||||
@@ -23,15 +23,15 @@ or from [docker images](https://hub.docker.com/r/victoriametrics/vlogscli/tags):
|
||||
### Running `vlogscli` from release binary
|
||||
|
||||
```sh
|
||||
curl -L -O https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.3.2-victorialogs/vlogscli-linux-amd64-v1.3.2-victorialogs.tar.gz
|
||||
tar xzf vlogscli-linux-amd64-v1.3.2-victorialogs.tar.gz
|
||||
curl -L -O https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.4.0-victorialogs/vlogscli-linux-amd64-v1.4.0-victorialogs.tar.gz
|
||||
tar xzf vlogscli-linux-amd64-v1.4.0-victorialogs.tar.gz
|
||||
./vlogscli-prod
|
||||
```
|
||||
|
||||
### Running `vlogscli` from Docker image
|
||||
|
||||
```sh
|
||||
docker run --rm -it docker.io/victoriametrics/vlogscli:v1.3.2-victorialogs
|
||||
docker run --rm -it docker.io/victoriametrics/vlogscli:v1.4.0-victorialogs
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -98,9 +98,9 @@ groups:
|
||||
interval: 5m
|
||||
rules:
|
||||
- alert: HasErrorLog
|
||||
expr: 'env: "prod" AND status:~"error|warn" | stats by (service) count() as errorLog | filter errorLog:>0'
|
||||
expr: 'env: "prod" AND status:~"error|warn" | stats by (service, kubernetes.pod) count() as errorLog | filter errorLog:>0'
|
||||
annotations:
|
||||
description: "Service {{$labels.service}} generated {{$labels.errorLog}} error logs in the last 5 minutes"
|
||||
description: 'Service {{$labels.service}} (pod {{ index $labels "kubernetes.pod" }}) generated {{$labels.errorLog}} error logs in the last 5 minutes'
|
||||
|
||||
- name: ServiceRequest
|
||||
type: vlogs
|
||||
@@ -216,32 +216,91 @@ For additional tips on writing LogsQL, refer to this [doc](https://docs.victoria
|
||||
|
||||
## Frequently Asked Questions
|
||||
|
||||
* How to use [multitenancy](https://docs.victoriametrics.com/victorialogs/#multitenancy) in rules?
|
||||
* vmalert doesn't support multi-tenancy for VictoriaLogs in the same way as it [supports it for VictoriaMetrics in ENT version](https://docs.victoriametrics.com/vmalert/#multitenancy).
|
||||
However, it is possible to specify the queried tenant from VictoriaLogs datasource via `headers` param in [Group config](https://docs.victoriametrics.com/vmalert/#groups).
|
||||
For example, the following config will execute all the rules within the group against tenant with `AccountID=1` and `ProjectID=2`:
|
||||
```yaml
|
||||
groups:
|
||||
- name: MyGroup
|
||||
headers:
|
||||
- "AccountID: 1"
|
||||
- "ProjectID: 2"
|
||||
rules: ...
|
||||
### How to use [multitenancy](https://docs.victoriametrics.com/victorialogs/#multitenancy) in rules?
|
||||
vmalert doesn't support multi-tenancy for VictoriaLogs in the same way as it [supports it for VictoriaMetrics in ENT version](https://docs.victoriametrics.com/vmalert/#multitenancy).
|
||||
However, it is possible to specify the queried tenant from VictoriaLogs datasource via `headers` param in [Group config](https://docs.victoriametrics.com/vmalert/#groups).
|
||||
For example, the following config will execute all the rules within the group against tenant with `AccountID=1` and `ProjectID=2`:
|
||||
```yaml
|
||||
groups:
|
||||
- name: MyGroup
|
||||
headers:
|
||||
- "AccountID: 1"
|
||||
- "ProjectID: 2"
|
||||
rules: ...
|
||||
```
|
||||
By default, vmalert persists all results to the specific tenant in VictoriaMetrics that specified by `-remotewrite.url`. For example, if the `-remotewrite.url=http://vminsert:8480/insert/0/prometheus/`, all data goes to tenant `0`.
|
||||
To persist different rule results to different tenants in VictoriaMetrics, there are following approaches:
|
||||
1. To use the [multitenant endpoint of vminsert](https://docs.victoriametrics.com/cluster-victoriametrics/#multitenancy-via-labels) as the `-remoteWrite.url`, and add tenant labels under the group configuration.
|
||||
|
||||
For example, run vmalert with:
|
||||
```
|
||||
* How to use one vmalert for VictoriaLogs and VictoriaMetrics rules in the same time?
|
||||
* We recommend running separate instances of vmalert for VictoriaMetrics and VictoriaLogs.
|
||||
However, vmalert allows having many groups with different rule types (`vlogs`, `prometheus`, `graphite`).
|
||||
But only one `-datasource.url` cmd-line flag can be specified, so it can't be configured with more than 1 datasource.
|
||||
However, VictoriaMetrics and VictoriaLogs datasources have different query path prefixes, and it is possible to use [vmauth](https://docs.victoriametrics.com/vmauth/) to route requests of different types between datasources.
|
||||
See example of vmauth config for such routing below:
|
||||
```yaml
|
||||
unauthorized_user:
|
||||
url_map:
|
||||
- src_paths:
|
||||
- "/api/v1/query.*"
|
||||
url_prefix: "http://victoriametrics:8428"
|
||||
- src_paths:
|
||||
- "/select/logsql/.*"
|
||||
url_prefix: "http://victorialogs:9428"
|
||||
./bin/vmalert -datasource.url=http://localhost:9428 -remoteWrite.url=http://vminsert:8480/insert/multitenant/prometheus ...
|
||||
```
|
||||
Now, vmalert needs to be configured with `--datasource.url=http://vmauth:8427/` to send queries to vmauth, and vmauth will route them to the specified destinations as in configuration example above.
|
||||
With the rules below, `recordingTenant123` will be queried from VictoriaLogs tenant `123` and persisted to tenant `123` in VictoriaMetrics, while `recordingTenant123-456:789` will be queried from VictoriaLogs tenant `124` and persisted to tenant `456:789` in VictoriaMetrics.
|
||||
```
|
||||
groups:
|
||||
- name: recordingTenant123
|
||||
type: vlogs
|
||||
headers:
|
||||
- "AccountID: 123"
|
||||
labels:
|
||||
vm_account_id: 123
|
||||
rules:
|
||||
- record: recordingTenant123
|
||||
expr: 'tags.path:/var/log/httpd OR tags.path:/var/log/nginx | stats by (tags.host) count() requests'
|
||||
- name: recordingTenant124-456:789
|
||||
type: vlogs
|
||||
headers:
|
||||
- "AccountID: 124"
|
||||
labels:
|
||||
vm_account_id: 456
|
||||
vm_project_id: 789
|
||||
rules:
|
||||
- record: recordingTenant124-456:789
|
||||
expr: 'tags.path:/var/log/httpd OR tags.path:/var/log/nginx | stats by (tags.host) count() requests'
|
||||
```
|
||||
|
||||
2. To run [enterprise version of vmalert](https://docs.victoriametrics.com/enterprise/) with `-clusterMode` enabled, and specify tenant parameter per each group.
|
||||
|
||||
For example, run vmalert with:
|
||||
```
|
||||
./bin/vmalert -datasource.url=http://localhost:9428 -clusterMode=true -remoteWrite.url=http://vminsert:8480/ ...
|
||||
```
|
||||
With the rules below, `recordingTenant123` will be queried from VictoriaLogs tenant `123` and persisted to tenant `123` in VictoriaMetrics, while `recordingTenant123-456:789` will be queried from VictoriaLogs tenant `124` and persisted to tenant `456:789` in VictoriaMetrics.
|
||||
```
|
||||
groups:
|
||||
- name: recordingTenant123
|
||||
type: vlogs
|
||||
headers:
|
||||
- "AccountID: 123"
|
||||
tenant: "123"
|
||||
rules:
|
||||
- record: recordingTenant123
|
||||
expr: 'tags.path:/var/log/httpd OR tags.path:/var/log/nginx | stats by (tags.host) count() requests'
|
||||
- name: recordingTenant124-456:789
|
||||
type: vlogs
|
||||
headers:
|
||||
- "AccountID: 124"
|
||||
tenant: "456:789"
|
||||
rules:
|
||||
- record: recordingTenant124-456:789
|
||||
expr: 'tags.path:/var/log/httpd OR tags.path:/var/log/nginx | stats by (tags.host) count() requests'
|
||||
```
|
||||
|
||||
### How to use one vmalert for VictoriaLogs and VictoriaMetrics rules in the same time?
|
||||
We recommend running separate instances of vmalert for VictoriaMetrics and VictoriaLogs.
|
||||
However, vmalert allows having many groups with different rule types (`vlogs`, `prometheus`, `graphite`).
|
||||
But only one `-datasource.url` cmd-line flag can be specified, so it can't be configured with more than 1 datasource.
|
||||
However, VictoriaMetrics and VictoriaLogs datasources have different query path prefixes, and it is possible to use [vmauth](https://docs.victoriametrics.com/vmauth/) to route requests of different types between datasources.
|
||||
See example of vmauth config for such routing below:
|
||||
```yaml
|
||||
unauthorized_user:
|
||||
url_map:
|
||||
- src_paths:
|
||||
- "/api/v1/query.*"
|
||||
url_prefix: "http://victoriametrics:8428"
|
||||
- src_paths:
|
||||
- "/select/logsql/.*"
|
||||
url_prefix: "http://victorialogs:9428"
|
||||
```
|
||||
Now, vmalert needs to be configured with `--datasource.url=http://vmauth:8427/` to send queries to vmauth, and vmauth will route them to the specified destinations as in configuration example above.
|
||||
|
||||
@@ -6,11 +6,11 @@ This chapter describes different components, that correspond to respective secti
|
||||
- [Writer section](https://docs.victoriametrics.com/anomaly-detection/components/writer/) - Required
|
||||
- [Monitoring section](https://docs.victoriametrics.com/anomaly-detection/components/monitoring/) - Optional
|
||||
|
||||
> **Note**: starting from [v1.7.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v172), once the service starts, automated config validation is performed. Please see container logs for errors that need to be fixed to create fully valid config, visiting sections above for examples and documentation.
|
||||
> **Note**: Once the service starts, automated config validation is performed{{% available_from "v1.7.2" anomaly %}}. Please see container logs for errors that need to be fixed to create fully valid config, visiting sections above for examples and documentation.
|
||||
|
||||
> **Note**: starting from [v1.13.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1130), components' class can be referenced by a short alias instead of a full class path - i.e. `model.zscore.ZscoreModel` becomes `zscore`, `reader.vm.VmReader` becomes `vm`, `scheduler.periodic.PeriodicScheduler` becomes `periodic`, etc. Please see according sections for the details.
|
||||
> **Note**: Components' class{{% available_from "v1.13.0" anomaly %}} can be referenced by a short alias instead of a full class path - i.e. `model.zscore.ZscoreModel` becomes `zscore`, `reader.vm.VmReader` becomes `vm`, `scheduler.periodic.PeriodicScheduler` becomes `periodic`, etc. Please see according sections for the details.
|
||||
|
||||
> **Note:** Starting from [v1.13.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1130) `preset` modes are available for `vmanomaly`. Please find the guide [here](https://docs.victoriametrics.com/anomaly-detection/presets/).
|
||||
> **Note:** `preset` modes are available{{% available_from "v1.13.0" anomaly %}} for `vmanomaly`. Please find the guide [here](https://docs.victoriametrics.com/anomaly-detection/presets/).
|
||||
|
||||
Below, you will find an example illustrating how the components of `vmanomaly` interact with each other and with a single-node VictoriaMetrics setup.
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ Tenant ID for cluster version. Example: `"0:0"`
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Deprecated since [v1.8.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v180). Absolute, to override `/health` path
|
||||
{{% deprecated_from "v1.8.0" anomaly %}}. Absolute, to override `/health` path
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -316,7 +316,7 @@ For detailed guidance on configuring mTLS parameters such as `verify_tls`, `tls_
|
||||
### Reader behaviour metrics
|
||||
Label names [description](#labelnames)
|
||||
|
||||
> **Note**: To improve consistency across the components additional labels (`scheduler_alias`, `preset`) were added to writer and reader metrics{{% available_from "v1.17.0" anomaly %}}. Also, metrics `vmanomaly_reader_request_duration_seconds` and `vmanomaly_reader_response_parsing_seconds` changed their type to `Histogram` (was `Summary` prior to [v1.17.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1170)).
|
||||
> **Note**: To improve consistency across the components additional labels (`scheduler_alias`, `preset`) were added to writer and reader metrics{{% available_from "v1.17.0" anomaly %}}. Also, metrics `vmanomaly_reader_request_duration_seconds` and `vmanomaly_reader_response_parsing_seconds` changed their type to `Histogram` (was `Summary`{{% deprecated_from "v1.17.0" anomaly %}}).
|
||||
|
||||
<table class="params">
|
||||
<thead>
|
||||
@@ -333,7 +333,7 @@ Label names [description](#labelnames)
|
||||
|
||||
`vmanomaly_reader_request_duration_seconds`
|
||||
</td>
|
||||
<td>`Histogram` (was `Summary` prior to [v1.17.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1170))</td>
|
||||
<td>`Histogram` (was `Summary`{{% deprecated_from "v1.17.0" anomaly %}})</td>
|
||||
<td>The total time (in seconds) taken by queries to VictoriaMetrics `url` for the `query_key` query within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode.</td>
|
||||
<td>
|
||||
|
||||
@@ -343,7 +343,7 @@ Label names [description](#labelnames)
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`vmanomaly_reader_responses` (named `vmanomaly_reader_response_count` prior to [v1.17.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1170))
|
||||
`vmanomaly_reader_responses` (named `vmanomaly_reader_response_count`{{% deprecated_from "v1.17.0" anomaly %}})
|
||||
</td>
|
||||
<td>`Counter`</td>
|
||||
<td>The count of responses received from VictoriaMetrics `url` for the `query_key` query, categorized by `code`, within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode.</td>
|
||||
@@ -369,7 +369,7 @@ Label names [description](#labelnames)
|
||||
|
||||
`vmanomaly_reader_response_parsing_seconds`
|
||||
</td>
|
||||
<td>`Histogram` (was `Summary` prior to [v1.17.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1170))</td>
|
||||
<td>`Histogram` (was `Summary`{{% deprecated_from "v1.17.0" anomaly %}}</td>
|
||||
<td>The total time (in seconds) taken for data parsing at each `step` (json, dataframe) for the `query_key` query within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode.</td>
|
||||
<td>
|
||||
|
||||
@@ -439,7 +439,7 @@ Label names [description](#labelnames)
|
||||
|
||||
`vmanomaly_model_run_duration_seconds`
|
||||
</td>
|
||||
<td>`Histogram` (was `Summary` prior to [v1.17.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1170)) </td>
|
||||
<td>`Histogram` (was `Summary`{{% deprecated_from "v1.17.0" anomaly %}}) </td>
|
||||
<td>The total time (in seconds) taken by model invocations during the `stage` (`fit`, `infer`, `fit_infer`), based on the results of the `query_key` query, for models of class `model_alias`, within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode.</td>
|
||||
<td>
|
||||
|
||||
@@ -513,7 +513,7 @@ Label names [description](#labelnames)
|
||||
### Writer behaviour metrics
|
||||
Label names [description](#labelnames)
|
||||
|
||||
> **Note**: additional labels (`scheduler_alias`, `preset`){{% available_from "v1.17.0" anomaly %}} were added to writer and reader metrics to improve consistency across the components. Also, metrics `vmanomaly_writer_request_duration_seconds` and `vmanomaly_writer_request_serialize_seconds` changed their type to `Histogram` (was `Summary` prior to [v1.17.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1170)).
|
||||
> **Note**: additional labels (`scheduler_alias`, `preset`){{% available_from "v1.17.0" anomaly %}} were added to writer and reader metrics to improve consistency across the components. Also, metrics `vmanomaly_writer_request_duration_seconds` and `vmanomaly_writer_request_serialize_seconds` changed their type to `Histogram` (was `Summary`{{% deprecated_from "v1.17.0" anomaly %}}).
|
||||
|
||||
<table class="params">
|
||||
<thead>
|
||||
@@ -530,7 +530,7 @@ Label names [description](#labelnames)
|
||||
|
||||
`vmanomaly_writer_request_duration_seconds`
|
||||
</td>
|
||||
<td>`Histogram` (was `Summary` prior to [v1.17.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1170))</td>
|
||||
<td>`Histogram` (was `Summary`{{% deprecated_from "v1.17.0" anomaly %}})</td>
|
||||
<td>The total time (in seconds) taken by write requests to VictoriaMetrics `url` for the `query_key` query within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode.
|
||||
</td>
|
||||
<td>
|
||||
@@ -541,7 +541,7 @@ Label names [description](#labelnames)
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`vmanomaly_writer_responses` (named `vmanomaly_reader_response_count` prior to [v1.17.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1170))
|
||||
`vmanomaly_writer_responses` (named `vmanomaly_reader_response_count`{{% deprecated_from "v1.17.0" anomaly %}})
|
||||
</td>
|
||||
<td>`Counter`</td>
|
||||
<td>The count of response codes received from VictoriaMetrics `url` for the `query_key` query, categorized by `code`, within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode.
|
||||
@@ -568,7 +568,7 @@ Label names [description](#labelnames)
|
||||
|
||||
`vmanomaly_writer_request_serialize_seconds`
|
||||
</td>
|
||||
<td>`Histogram` (was `Summary` prior to [v1.17.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1170))</td>
|
||||
<td>`Histogram` (was `Summary`{{% deprecated_from "v1.17.0" anomaly %}}</td>
|
||||
<td>The total time (in seconds) taken for serializing data for the `query_key` query within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode.</td>
|
||||
<td>
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ Future updates will introduce additional readers, expanding the range of data so
|
||||
|
||||
## VM reader
|
||||
|
||||
> **Note**: Starting from [v1.13.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1130) there is backward-compatible change of [`queries`](https://docs.victoriametrics.com/anomaly-detection/components/reader?highlight=queries#vm-reader) arg of [VmReader](#vm-reader). New format allows to specify per-query parameters, like `step` to reduce amount of data read from VictoriaMetrics TSDB and to allow config flexibility. Please see [per-query parameters](#per-query-parameters) section for the details.
|
||||
> **Note**: There is backward-compatible change{{% available_from "v1.13.0" anomaly %}} of [`queries`](https://docs.victoriametrics.com/anomaly-detection/components/reader?highlight=queries#vm-reader) arg of [VmReader](#vm-reader). New format allows to specify per-query parameters, like `step` to reduce amount of data read from VictoriaMetrics TSDB and to allow config flexibility. Please see [per-query parameters](#per-query-parameters) section for the details.
|
||||
|
||||
Old format like
|
||||
|
||||
@@ -54,7 +54,7 @@ reader:
|
||||
|
||||
### Per-query parameters
|
||||
|
||||
Starting from [v1.13.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1130) there is change of [`queries`](https://docs.victoriametrics.com/anomaly-detection/components/reader?highlight=queries#vm-reader) arg format. Now each query alias supports the next (sub)fields:
|
||||
There is change{{% available_from "v1.13.0" anomaly %}} of [`queries`](https://docs.victoriametrics.com/anomaly-detection/components/reader?highlight=queries#vm-reader) arg format. Now each query alias supports the next (sub)fields:
|
||||
|
||||
- `expr` (string): MetricsQL/PromQL expression that defines an input for VmReader. As accepted by `/query_range?query=%s`. i.e. `avg(vm_blocks)`
|
||||
|
||||
@@ -64,15 +64,15 @@ Starting from [v1.13.0](https://docs.victoriametrics.com/anomaly-detection/chang
|
||||
|
||||
> **Note**: having **different** individual `step` args for queries (i.e. `30s` for `q1` and `2m` for `q2`) is not yet supported for [multivariate model](https://docs.victoriametrics.com/anomaly-detection/components/models/#multivariate-models) if you want to run it on several queries simultaneously (i.e. setting [`queries`](https://docs.victoriametrics.com/anomaly-detection/components/models/#queries) arg of a model to [`q1`, `q2`]).
|
||||
|
||||
- `data_range` (list[float | string]): Introduced in [v1.15.1](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1151), it allows defining **valid** data ranges for input per individual query in `queries`, resulting in:
|
||||
- `data_range`{{% available_from "v1.15.1" anomaly %}} (list[float | string]): It allows defining **valid** data ranges for input per individual query in `queries`, resulting in:
|
||||
- **High anomaly scores** (>1) when the *data falls outside the expected range*, indicating a data constraint violation.
|
||||
- **Lowest anomaly scores** (=0) when the *model's predictions (`yhat`) fall outside the expected range*, meaning uncertain predictions.
|
||||
|
||||
> **Note**: if not set explicitly (or if older config style prior to [v1.13.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1130)) is used, then it is set to reader-level `data_range` arg (since [v1.18.1](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1181))
|
||||
> **Note**: if not set explicitly (or if older config style prior to [v1.13.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1130)) is used, then it is set to reader-level `data_range` arg{{% available_from "v1.18.1" anomaly %}}
|
||||
|
||||
- `max_points_per_query` (int): Introduced in [v1.17.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1170), optional arg overrides how `search.maxPointsPerTimeseries` flag (available since [v1.14.1](#v1141)) impacts `vmanomaly` on splitting long `fit_window` [queries](https://docs.victoriametrics.com/anomaly-detection/components/reader/?highlight=queries#vm-reader) into smaller sub-intervals. This helps users avoid hitting the `search.maxQueryDuration` limit for individual queries by distributing initial query across multiple subquery requests with minimal overhead. Set less than `search.maxPointsPerTimeseries` if hitting `maxQueryDuration` limits. If set on a query-level, it overrides the global `max_points_per_query` (reader-level).
|
||||
- `max_points_per_query` (int): Optional arg{{% available_from "v1.17.0" anomaly %}} overrides how `search.maxPointsPerTimeseries` flag{{% available_from "v1.14.1" anomaly %}} impacts `vmanomaly` on splitting long `fit_window` [queries](https://docs.victoriametrics.com/anomaly-detection/components/reader/?highlight=queries#vm-reader) into smaller sub-intervals. This helps users avoid hitting the `search.maxQueryDuration` limit for individual queries by distributing initial query across multiple subquery requests with minimal overhead. Set less than `search.maxPointsPerTimeseries` if hitting `maxQueryDuration` limits. If set on a query-level, it overrides the global `max_points_per_query` (reader-level).
|
||||
|
||||
- `tz` (string): Introduced in [v1.18.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1180), this optional argument enables timezone specification per query, overriding the reader’s default `tz`. This setting helps to account for local timezone shifts, such as [DST](https://en.wikipedia.org/wiki/Daylight_saving_time), in models that are sensitive to seasonal variations (e.g., [`ProphetModel`](https://docs.victoriametrics.com/anomaly-detection/components/models/#prophet) or [`OnlineQuantileModel`](https://docs.victoriametrics.com/anomaly-detection/components/models/#online-seasonal-quantile)).
|
||||
- `tz`{{% available_from "v1.18.0" anomaly %}} (string): this optional argument enables timezone specification per query, overriding the reader’s default `tz`. This setting helps to account for local timezone shifts, such as [DST](https://en.wikipedia.org/wiki/Daylight_saving_time), in models that are sensitive to seasonal variations (e.g., [`ProphetModel`](https://docs.victoriametrics.com/anomaly-detection/components/models/#prophet) or [`OnlineQuantileModel`](https://docs.victoriametrics.com/anomaly-detection/components/models/#online-seasonal-quantile)).
|
||||
|
||||
|
||||
### Per-query config example
|
||||
@@ -108,7 +108,7 @@ reader:
|
||||
`class`
|
||||
</td>
|
||||
<td>
|
||||
`reader.vm.VmReader` (or `vm` starting from [v1.13.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1130))
|
||||
`reader.vm.VmReader` (or `vm`{{% available_from "v1.13.0" anomaly %}})
|
||||
</td>
|
||||
<td>
|
||||
Name of the class needed to enable reading from VictoriaMetrics or Prometheus. VmReader is the default option, if not specified.
|
||||
@@ -252,7 +252,7 @@ If a path to a CA bundle file (like `ca.crt`), it will verify the certificate us
|
||||
`path/to/cert.crt`
|
||||
</td>
|
||||
<td>
|
||||
Path to a file with the client certificate, i.e. `client.crt`. Available since [v1.16.3](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1163).
|
||||
Path to a file with the client certificate, i.e. `client.crt`{{% available_from "v1.16.3" anomaly %}}.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -265,7 +265,7 @@ Path to a file with the client certificate, i.e. `client.crt`. Available since [
|
||||
`path/to/key.crt`
|
||||
</td>
|
||||
<td>
|
||||
Path to a file with the client certificate key, i.e. `client.key`. Available since [v1.16.3](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1163).
|
||||
Path to a file with the client certificate key, i.e. `client.key`{{% available_from "v1.16.3" anomaly %}}.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -291,7 +291,7 @@ Token is passed in the standard format with header: `Authorization: bearer {toke
|
||||
`path_to_file`
|
||||
</td>
|
||||
<td>
|
||||
Path to a file, which contains token, that is passed in the standard format with header: `Authorization: bearer {token}`. Available since [v1.15.9](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1159)
|
||||
Path to a file, which contains token, that is passed in the standard format with header: `Authorization: bearer {token}`{{% available_from "v1.15.9" anomaly %}}.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -330,7 +330,7 @@ If True, then query will be performed from the last seen timestamp for a given s
|
||||
`1ms`
|
||||
</td>
|
||||
<td>
|
||||
Introduced in [v1.15.1](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1151), it allows overriding the default `-search.latencyOffset` [flag of VictoriaMetrics](https://docs.victoriametrics.com/?highlight=search.latencyOffset#list-of-command-line-flags) (30s). The default value is set to 1ms, which should help in cases where `sampling_frequency` is low (10-60s) and `sampling_frequency` equals `infer_every` in the [PeriodicScheduler](https://docs.victoriametrics.com/anomaly-detection/components/scheduler/?highlight=infer_every#periodic-scheduler). This prevents users from receiving `service - WARNING - [Scheduler [scheduler_alias]] No data available for inference.` warnings in logs and allows for consecutive `infer` calls without gaps. To restore the old behavior, set it equal to your `-search.latencyOffset` [flag value]((https://docs.victoriametrics.com/?highlight=search.latencyOffset#list-of-command-line-flags)).
|
||||
It allows overriding the default `-search.latencyOffset`{{% available_from "v1.15.1" anomaly %}} [flag of VictoriaMetrics](https://docs.victoriametrics.com/?highlight=search.latencyOffset#list-of-command-line-flags) (30s). The default value is set to 1ms, which should help in cases where `sampling_frequency` is low (10-60s) and `sampling_frequency` equals `infer_every` in the [PeriodicScheduler](https://docs.victoriametrics.com/anomaly-detection/components/scheduler/?highlight=infer_every#periodic-scheduler). This prevents users from receiving `service - WARNING - [Scheduler [scheduler_alias]] No data available for inference.` warnings in logs and allows for consecutive `infer` calls without gaps. To restore the old behavior, set it equal to your `-search.latencyOffset` [flag value]((https://docs.victoriametrics.com/?highlight=search.latencyOffset#list-of-command-line-flags)).
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -343,7 +343,7 @@ Introduced in [v1.15.1](https://docs.victoriametrics.com/anomaly-detection/chang
|
||||
`10000`
|
||||
</td>
|
||||
<td>
|
||||
Introduced in [v1.17.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1170), optional arg overrides how `search.maxPointsPerTimeseries` flag (available since [v1.14.1](#v1141)) impacts `vmanomaly` on splitting long `fit_window` [queries](https://docs.victoriametrics.com/anomaly-detection/components/reader/?highlight=queries#vm-reader) into smaller sub-intervals. This helps users avoid hitting the `search.maxQueryDuration` limit for individual queries by distributing initial query across multiple subquery requests with minimal overhead. Set less than `search.maxPointsPerTimeseries` if hitting `maxQueryDuration` limits. You can also set it on [per-query](#per-query-parameters) basis to override this global one.
|
||||
Optional arg{{% available_from "v1.17.0" anomaly %}} overrides how `search.maxPointsPerTimeseries` flag{{% available_from "v1.14.1" anomaly %}} impacts `vmanomaly` on splitting long `fit_window` [queries](https://docs.victoriametrics.com/anomaly-detection/components/reader/?highlight=queries#vm-reader) into smaller sub-intervals. This helps users avoid hitting the `search.maxQueryDuration` limit for individual queries by distributing initial query across multiple subquery requests with minimal overhead. Set less than `search.maxPointsPerTimeseries` if hitting `maxQueryDuration` limits. You can also set it on [per-query](#per-query-parameters) basis to override this global one.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -356,7 +356,7 @@ Introduced in [v1.17.0](https://docs.victoriametrics.com/anomaly-detection/chang
|
||||
`UTC`
|
||||
</td>
|
||||
<td>
|
||||
Introduced in [v1.18.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1180), this optional argument specifies the [IANA](https://nodatime.org/TimeZones) timezone to account for local shifts, like [DST](https://en.wikipedia.org/wiki/Daylight_saving_time), in models sensitive to seasonal patterns (e.g., [`ProphetModel`](https://docs.victoriametrics.com/anomaly-detection/components/models/#prophet) or [`OnlineQuantileModel`](https://docs.victoriametrics.com/anomaly-detection/components/models/#online-seasonal-quantile)). Defaults to `UTC` if not set and can be overridden on a [per-query basis](#per-query-parameters).
|
||||
Optional argument{{% available_from "v1.18.0" anomaly %}} specifies the [IANA](https://nodatime.org/TimeZones) timezone to account for local shifts, like [DST](https://en.wikipedia.org/wiki/Daylight_saving_time), in models sensitive to seasonal patterns (e.g., [`ProphetModel`](https://docs.victoriametrics.com/anomaly-detection/components/models/#prophet) or [`OnlineQuantileModel`](https://docs.victoriametrics.com/anomaly-detection/components/models/#online-seasonal-quantile)). Defaults to `UTC` if not set and can be overridden on a [per-query basis](#per-query-parameters).
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -369,7 +369,7 @@ Introduced in [v1.18.0](https://docs.victoriametrics.com/anomaly-detection/chang
|
||||
`["-inf", "inf"]`
|
||||
</td>
|
||||
<td>
|
||||
Added in [v1.18.1](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1180), this optional argument allows defining **valid** data ranges for input of all the queries in `queries`. Defaults to `["-inf", "inf"]` if not set and can be overridden on a [per-query basis](#per-query-parameters).
|
||||
Optional argument{{% available_from "v1.18.1" anomaly %}} allows defining **valid** data ranges for input of all the queries in `queries`. Defaults to `["-inf", "inf"]` if not set and can be overridden on a [per-query basis](#per-query-parameters).
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -397,7 +397,7 @@ reader:
|
||||
|
||||
### mTLS protection
|
||||
|
||||
As of [v1.16.3](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1163), `vmanomaly` supports [mutual TLS (mTLS)](https://en.wikipedia.org/wiki/Mutual_authentication) for secure communication across its components, including [VmReader](https://docs.victoriametrics.com/anomaly-detection/components/reader/#vm-reader), [VmWriter](https://docs.victoriametrics.com/anomaly-detection/components/writer/#vm-writer), and [Monitoring/Push](https://docs.victoriametrics.com/anomaly-detection/components/monitoring/#push-config-parameters). This allows for mutual authentication between the client and server when querying or writing data to [VictoriaMetrics Enterprise, configured for mTLS](https://docs.victoriametrics.com/#mtls-protection).
|
||||
`vmanomaly` supports [mutual TLS (mTLS)](https://en.wikipedia.org/wiki/Mutual_authentication){{% available_from "v1.16.3" anomaly %}} for secure communication across its components, including [VmReader](https://docs.victoriametrics.com/anomaly-detection/components/reader/#vm-reader), [VmWriter](https://docs.victoriametrics.com/anomaly-detection/components/writer/#vm-writer), and [Monitoring/Push](https://docs.victoriametrics.com/anomaly-detection/components/monitoring/#push-config-parameters). This allows for mutual authentication between the client and server when querying or writing data to [VictoriaMetrics Enterprise, configured for mTLS](https://docs.victoriametrics.com/#mtls-protection).
|
||||
|
||||
mTLS ensures that both the client and server verify each other's identity using certificates, which enhances security by preventing unauthorized access.
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ aliases:
|
||||
Scheduler defines how often to run and make inferences, as well as what timerange to use to train the model.
|
||||
Is specified in `scheduler` section of a config for VictoriaMetrics Anomaly Detection.
|
||||
|
||||
> **Note: Starting from [v1.11.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1110) scheduler section in config supports multiple schedulers via aliasing. <br>Also, `vmanomaly` expects scheduler section to be named `schedulers`. Using old (flat) format with `scheduler` key is deprecated and will be removed in future versions.**
|
||||
> **Note: Scheduler section in config supports multiple schedulers via aliasing{{% available_from "v1.11.0" anomaly %}}. <br>Also, `vmanomaly` expects scheduler section to be named `schedulers`. Using old (flat) format with `scheduler` key is deprecated and will be removed in future versions.**
|
||||
|
||||
```yaml
|
||||
schedulers:
|
||||
@@ -28,7 +28,7 @@ schedulers:
|
||||
...
|
||||
```
|
||||
|
||||
Old-style configs (< [1.11.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1110))
|
||||
Old-style configs {{% deprecated_from "v1.11.0" anomaly %}}
|
||||
|
||||
```yaml
|
||||
scheduler:
|
||||
@@ -60,7 +60,7 @@ options={`"scheduler.periodic.PeriodicScheduler"`, `"scheduler.oneoff.OneoffSche
|
||||
- `"scheduler.oneoff.OneoffScheduler"`: runs the process once and exits. Useful for testing.
|
||||
- `"scheduler.backtesting.BacktestingScheduler"`: imitates consecutive backtesting runs of OneoffScheduler. Runs the process once and exits. Use to get more granular control over testing on historical data.
|
||||
|
||||
> **Note**: starting from [v1.13.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1130), class aliases are supported, so `"scheduler.periodic.PeriodicScheduler"` can be substituted to `"periodic"`, `"scheduler.oneoff.OneoffScheduler"` - to `"oneoff"`, `"scheduler.backtesting.BacktestingScheduler"` - to `"backtesting"`
|
||||
> **Note**: Class aliases are supported{{% available_from "v1.13.0" anomaly %}}, so `"scheduler.periodic.PeriodicScheduler"` can be substituted to `"periodic"`, `"scheduler.oneoff.OneoffScheduler"` - to `"oneoff"`, `"scheduler.backtesting.BacktestingScheduler"` - to `"backtesting"`
|
||||
|
||||
**Depending on selected class, different parameters should be used**
|
||||
|
||||
@@ -155,7 +155,7 @@ How often to completely retrain the models. If not set, value of `infer_every` i
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`start_from`
|
||||
`start_from`{{% available_from "v1.18.5" anomaly %}}
|
||||
</td>
|
||||
<td>str, Optional</td>
|
||||
<td>
|
||||
@@ -164,13 +164,13 @@ How often to completely retrain the models. If not set, value of `infer_every` i
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Available since [v1.18.5](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1185). Specifies when to initiate the first `fit_every` call. Accepts either an ISO 8601 datetime or a time in HH:MM format. If the specified time is in the past, the next suitable time is calculated based on the `fit_every` interval. For the HH:MM format, if the time is in the past, it will be scheduled for the same time on the following day, respecting the `tz` argument if provided. By default, the timezone defaults to `UTC`.
|
||||
Specifies when to initiate the first `fit_every` call. Accepts either an ISO 8601 datetime or a time in HH:MM format. If the specified time is in the past, the next suitable time is calculated based on the `fit_every` interval. For the HH:MM format, if the time is in the past, it will be scheduled for the same time on the following day, respecting the `tz` argument if provided. By default, the timezone defaults to `UTC`.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`tz`
|
||||
`tz`{{% available_from "v1.18.5" anomaly %}}
|
||||
</td>
|
||||
<td>str, Optional</td>
|
||||
<td>
|
||||
@@ -179,7 +179,7 @@ Available since [v1.18.5](https://docs.victoriametrics.com/anomaly-detection/cha
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Available since [v1.18.5](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1185). Defines the local timezone for the `start_from` parameter, if specified. Defaults to `UTC` if no timezone is provided.
|
||||
Defines the local timezone for the `start_from` parameter, if specified. Defaults to `UTC` if no timezone is provided.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -32,7 +32,7 @@ Future updates will introduce additional export methods, offering users more fle
|
||||
</td>
|
||||
<td>
|
||||
|
||||
`writer.vm.VmWriter` or `vm` starting from [`v1.13.0`](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1130)
|
||||
`writer.vm.VmWriter` or `vm`{{% available_from "v1.13.0" anomaly %}}
|
||||
</td>
|
||||
<td>
|
||||
|
||||
@@ -60,11 +60,11 @@ Datasource URL address
|
||||
</td>
|
||||
<td>
|
||||
|
||||
`0:0`, `multitenant` (starting from [v1.16.2](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1162))
|
||||
`0:0`, `multitenant`{{% available_from "v1.16.2" anomaly %}}
|
||||
</td>
|
||||
<td>
|
||||
|
||||
For VictoriaMetrics Cluster version only, tenants are identified by `accountID` or `accountID:projectID`. Starting from [v1.16.2](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1162), `multitenant` [endpoint](https://docs.victoriametrics.com/cluster-victoriametrics/?highlight=writes#multitenancy-via-labels) is supported, to write data to multiple [tenants](https://docs.victoriametrics.com/cluster-victoriametrics/#multitenancy). See VictoriaMetrics Cluster [multitenancy docs](https://docs.victoriametrics.com/cluster-victoriametrics/#multitenancy)
|
||||
For VictoriaMetrics Cluster version only, tenants are identified by `accountID` or `accountID:projectID`. multitenant` [endpoint](https://docs.victoriametrics.com/cluster-victoriametrics/?highlight=writes#multitenancy-via-labels){{% available_from "v1.16.2" anomaly %}} is supported, to write data to multiple [tenants](https://docs.victoriametrics.com/cluster-victoriametrics/#multitenancy). See VictoriaMetrics Cluster [multitenancy docs](https://docs.victoriametrics.com/cluster-victoriametrics/#multitenancy)
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Additional rows for metric_format -->
|
||||
@@ -208,7 +208,7 @@ If a path to a CA bundle file (like `ca.crt`), it will verify the certificate us
|
||||
`path/to/cert.crt`
|
||||
</td>
|
||||
<td>
|
||||
Path to a file with the client certificate, i.e. `client.crt`. Available since [v1.16.3](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1163).
|
||||
Path to a file with the client certificate, i.e. `client.crt`{{% available_from "v1.16.3" anomaly %}}.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -221,7 +221,7 @@ Path to a file with the client certificate, i.e. `client.crt`. Available since [
|
||||
`path/to/key.crt`
|
||||
</td>
|
||||
<td>
|
||||
Path to a file with the client certificate key, i.e. `client.key`. Available since [v1.16.3](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1163).
|
||||
Path to a file with the client certificate key, i.e. `client.key`{{% available_from "v1.16.3" anomaly %}}.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -247,7 +247,7 @@ Token is passed in the standard format with header: `Authorization: bearer {toke
|
||||
`path_to_file`
|
||||
</td>
|
||||
<td>
|
||||
Path to a file, which contains token, that is passed in the standard format with header: `Authorization: bearer {token}`. Available since [v1.15.9](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1159)
|
||||
Path to a file, which contains token, that is passed in the standard format with header: `Authorization: bearer {token}`{{% available_from "v1.15.9" anomaly %}}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -273,7 +273,7 @@ writer:
|
||||
|
||||
### Multitenancy support
|
||||
|
||||
> This feature applies to the VictoriaMetrics Cluster version only. Tenants are identified by either `accountID` or `accountID:projectID`. Starting with [v1.16.2](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1162), the `multitenant` [endpoint](https://docs.victoriametrics.com/cluster-victoriametrics/?highlight=writes#multitenancy-via-labels) is supported for writing data across multiple [tenants](https://docs.victoriametrics.com/cluster-victoriametrics/#multitenancy). For more details, refer to the VictoriaMetrics Cluster [multitenancy documentation](https://docs.victoriametrics.com/cluster-victoriametrics/#multitenancy).
|
||||
> This feature applies to the VictoriaMetrics Cluster version only. Tenants are identified by either `accountID` or `accountID:projectID`. `multitenant` [endpoint](https://docs.victoriametrics.com/cluster-victoriametrics/?highlight=writes#multitenancy-via-labels){{% available_from "v1.15.9" anomaly %}} is supported for writing data across multiple [tenants](https://docs.victoriametrics.com/cluster-victoriametrics/#multitenancy). For more details, refer to the VictoriaMetrics Cluster [multitenancy documentation](https://docs.victoriametrics.com/cluster-victoriametrics/#multitenancy).
|
||||
|
||||
Please note the different behaviors depending on the `tenant_id` value:
|
||||
|
||||
@@ -305,7 +305,7 @@ Please note the different behaviors depending on the `tenant_id` value:
|
||||
|
||||
### mTLS protection
|
||||
|
||||
Starting from [v1.16.3](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1163), `vmanomaly` components such as [VmWriter](https://docs.victoriametrics.com/anomaly-detection/components/writer/#vm-writer) support [mTLS](https://en.wikipedia.org/wiki/Mutual_authentication) to ensure secure communication with [VictoriaMetrics Enterprise, configured with mTLS](https://docs.victoriametrics.com/#mtls-protection).
|
||||
`vmanomaly` components such as [VmWriter](https://docs.victoriametrics.com/anomaly-detection/components/writer/#vm-writer) support [mTLS](https://en.wikipedia.org/wiki/Mutual_authentication) to ensure secure communication with [VictoriaMetrics Enterprise, configured with mTLS](https://docs.victoriametrics.com/#mtls-protection){{% available_from "v1.16.3" anomaly %}}.
|
||||
|
||||
For detailed guidance on configuring mTLS parameters such as `verify_tls`, `tls_cert_file`, and `tls_key_file`, please refer to the [mTLS protection section](https://docs.victoriametrics.com/anomaly-detection/components/reader/#mtls-protection) in the [Reader](https://docs.victoriametrics.com/anomaly-detection/components/reader/#vm-reader) documentation. The configuration principles apply consistently across all these `vmanomaly` components.
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
- To use *vmanomaly*, part of the enterprise package, a license key is required. Obtain your key [here](https://victoriametrics.com/products/enterprise/trial/) for this tutorial or for enterprise use.
|
||||
- In the tutorial, we'll be using the following VictoriaMetrics components:
|
||||
- [VictoriaMetrics Single-Node](https://docs.victoriametrics.com/single-server-victoriametrics) (v1.108.0)
|
||||
- [vmalert](https://docs.victoriametrics.com/vmalert/) (v1.108.0)
|
||||
- [vmagent](https://docs.victoriametrics.com/vmagent/) (v1.108.0)
|
||||
- [VictoriaMetrics Single-Node](https://docs.victoriametrics.com/single-server-victoriametrics) (v1.108.1)
|
||||
- [vmalert](https://docs.victoriametrics.com/vmalert/) (v1.108.1)
|
||||
- [vmagent](https://docs.victoriametrics.com/vmagent/) (v1.108.1)
|
||||
- [Grafana](https://grafana.com/) (v.10.2.1)
|
||||
- [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/)
|
||||
- [Node exporter](https://github.com/prometheus/node_exporter#node-exporter) (v1.7.0) and [Alertmanager](https://prometheus.io/docs/alerting/latest/alertmanager/) (v0.27.0)
|
||||
@@ -313,7 +313,7 @@ Let's wrap it all up together into the `docker-compose.yml` file.
|
||||
services:
|
||||
vmagent:
|
||||
container_name: vmagent
|
||||
image: victoriametrics/vmagent:v1.108.0
|
||||
image: victoriametrics/vmagent:v1.108.1
|
||||
depends_on:
|
||||
- "victoriametrics"
|
||||
ports:
|
||||
@@ -330,7 +330,7 @@ services:
|
||||
|
||||
victoriametrics:
|
||||
container_name: victoriametrics
|
||||
image: victoriametrics/victoria-metrics:v1.108.0
|
||||
image: victoriametrics/victoria-metrics:v1.108.1
|
||||
ports:
|
||||
- 8428:8428
|
||||
volumes:
|
||||
@@ -363,7 +363,7 @@ services:
|
||||
|
||||
vmalert:
|
||||
container_name: vmalert
|
||||
image: victoriametrics/vmalert:v1.108.0
|
||||
image: victoriametrics/vmalert:v1.108.1
|
||||
depends_on:
|
||||
- "victoriametrics"
|
||||
ports:
|
||||
|
||||
@@ -18,6 +18,15 @@ See also [LTS releases](https://docs.victoriametrics.com/lts-releases/).
|
||||
|
||||
## tip
|
||||
|
||||
* FEATURE: all the VictoriaMetrics components: increase the default value for [`GOGC`](https://tip.golang.org/doc/gc-guide#GOGC) from `30` to `100`. This should reduce CPU usage at the cost of slightly higher memory usage. [Single-node VictoriaMetrics](https://docs.victoriametrics.com/), [vmagent](https://docs.victoriametrics.com/vmagent/) and [vmstorage](https://docs.victoriametrics.com/cluster-victoriametrics/#architecture-overview) components continue using `GOGC=30`, since they are optimized for low memory allocations and low memory usage, so they do not benefit from the increased GOGC value too much. It is possible to override the default `GOGC` value in any VictoriaMetrics component by setting `GOGC` environment variable to the desired value. For example, `GOGC=200 ./path/to/vmagent` starts `vmagent` with `GOGC=200`. See [these docs](https://tip.golang.org/doc/gc-guide#GOGC) about `GOGC` tuning. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7902).
|
||||
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add export data functionality for the `Raw Query` page and the ability to import exported data into the `Query Analyzer` page. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7628).
|
||||
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add `markdown` support for comments during data export. [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/7828).
|
||||
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent/) and [Single-node VictoriaMetrics](https://docs.victoriametrics.com/): added `min` and `max` metrics for Datadog Sketches API metrics, changed `_` metric name separator to `.` if metrics are not sanitized for consistency.
|
||||
* FEATURE: [Single-node VictoriaMetrics](https://docs.victoriametrics.com/): support `-maxIngestionRate` cmd-line flag to ratelimit samples/sec ingested. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7377) for details.
|
||||
* FEATURE: [vminsert](https://docs.victoriametrics.com/vminsert/): Storage nodes defined in `-storageNode` are now sorted, ensuring that varying node orders across different vminsert instances do not result in inconsistent replication.
|
||||
|
||||
* BUGFIX: [vmauth](https://docs.victoriametrics.com/vmauth/): properly set `host` field at debug information formatted with `dump_request_on_errors: true` setting.
|
||||
|
||||
## [v1.108.1](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.108.1)
|
||||
|
||||
Released at 2024-12-18
|
||||
@@ -49,6 +58,7 @@ Released at 2024-12-13
|
||||
* FEATURE: [vmauth](https://docs.victoriametrics.com/vmauth/): allow to start `vmauth` with empty configuration file. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6467) for details.
|
||||
* FEATURE: [vmalert-tool](https://docs.victoriametrics.com/vmalert-tool/): support debug mode for alerting rule. See [this doc](https://docs.victoriametrics.com/vmalert-tool/#debug-mode).
|
||||
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): update error messages for Clipboard API issues with docs links. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7677).
|
||||
* FEATURE: [Single-node VictoriaMetrics](https://docs.victoriametrics.com/): support `-maxIngestionRate` flag to ratelimit samples/sec ingested
|
||||
|
||||
* BUGFIX: all VictoriaMetrics components: consistently deduplicate values with stale markers within deduplication interval. Previously, deduplication could randomly prefer stale marker or value on the deduplication interval. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7674) for details. Thanks to @tIGO for the [pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/7675).
|
||||
* BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent/) and [Single-node VictoriaMetrics](https://docs.victoriametrics.com/): add missing common service labels for docker swarm service discovery when `role` is set to `tasks`. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7800).
|
||||
|
||||
@@ -82,7 +82,7 @@ VictoriaMetrics Enterprise components are available in the following forms:
|
||||
It is allowed to run VictoriaMetrics Enterprise components in [cases listed here](#valid-cases-for-victoriametrics-enterprise).
|
||||
|
||||
Binary releases of VictoriaMetrics Enterprise are available [at the releases page](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest).
|
||||
Enterprise binaries and packages have `enterprise` suffix in their names. For example, `victoria-metrics-linux-amd64-v1.108.0-enterprise.tar.gz`.
|
||||
Enterprise binaries and packages have `enterprise` suffix in their names. For example, `victoria-metrics-linux-amd64-v1.108.1-enterprise.tar.gz`.
|
||||
|
||||
In order to run binary release of VictoriaMetrics Enterprise component, please download the `*-enterprise.tar.gz` archive for your OS and architecture
|
||||
from the [releases page](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest) and unpack it. Then run the unpacked binary.
|
||||
@@ -100,8 +100,8 @@ For example, the following command runs VictoriaMetrics Enterprise binary with t
|
||||
obtained at [this page](https://victoriametrics.com/products/enterprise/trial/):
|
||||
|
||||
```sh
|
||||
wget https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.108.0/victoria-metrics-linux-amd64-v1.108.0-enterprise.tar.gz
|
||||
tar -xzf victoria-metrics-linux-amd64-v1.108.0-enterprise.tar.gz
|
||||
wget https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.108.1/victoria-metrics-linux-amd64-v1.108.1-enterprise.tar.gz
|
||||
tar -xzf victoria-metrics-linux-amd64-v1.108.1-enterprise.tar.gz
|
||||
./victoria-metrics-prod -license=BASE64_ENCODED_LICENSE_KEY
|
||||
```
|
||||
|
||||
@@ -116,7 +116,7 @@ Alternatively, VictoriaMetrics Enterprise license can be stored in the file and
|
||||
It is allowed to run VictoriaMetrics Enterprise components in [cases listed here](#valid-cases-for-victoriametrics-enterprise).
|
||||
|
||||
Docker images for VictoriaMetrics Enterprise are available [at VictoriaMetrics DockerHub](https://hub.docker.com/u/victoriametrics).
|
||||
Enterprise docker images have `enterprise` suffix in their names. For example, `victoriametrics/victoria-metrics:v1.108.0-enterprise`.
|
||||
Enterprise docker images have `enterprise` suffix in their names. For example, `victoriametrics/victoria-metrics:v1.108.1-enterprise`.
|
||||
|
||||
In order to run Docker image of VictoriaMetrics Enterprise component, it is required to provide the license key via command-line
|
||||
flag as described [here](#binary-releases).
|
||||
@@ -126,13 +126,13 @@ Enterprise license key can be obtained at [this page](https://victoriametrics.co
|
||||
For example, the following command runs VictoriaMetrics Enterprise Docker image with the specified license key:
|
||||
|
||||
```sh
|
||||
docker run --name=victoria-metrics victoriametrics/victoria-metrics:v1.108.0-enterprise -license=BASE64_ENCODED_LICENSE_KEY
|
||||
docker run --name=victoria-metrics victoriametrics/victoria-metrics:v1.108.1-enterprise -license=BASE64_ENCODED_LICENSE_KEY
|
||||
```
|
||||
|
||||
Alternatively, the license code can be stored in the file and then referred via `-licenseFile` command-line flag:
|
||||
|
||||
```sh
|
||||
docker run --name=victoria-metrics -v /vm-license:/vm-license victoriametrics/victoria-metrics:v1.108.0-enterprise -licenseFile=/path/to/vm-license
|
||||
docker run --name=victoria-metrics -v /vm-license:/vm-license victoriametrics/victoria-metrics:v1.108.1-enterprise -licenseFile=/path/to/vm-license
|
||||
```
|
||||
|
||||
Example docker-compose configuration:
|
||||
@@ -141,7 +141,7 @@ version: "3.5"
|
||||
services:
|
||||
victoriametrics:
|
||||
container_name: victoriametrics
|
||||
image: victoriametrics/victoria-metrics:v1.108.0
|
||||
image: victoriametrics/victoria-metrics:v1.108.1
|
||||
ports:
|
||||
- 8428:8428
|
||||
volumes:
|
||||
@@ -173,7 +173,7 @@ is used to provide key in plain-text:
|
||||
```yaml
|
||||
server:
|
||||
image:
|
||||
tag: v1.108.0-enterprise
|
||||
tag: v1.108.1-enterprise
|
||||
|
||||
license:
|
||||
key: {BASE64_ENCODED_LICENSE_KEY}
|
||||
@@ -184,7 +184,7 @@ In order to provide key via existing secret, the following values file is used:
|
||||
```yaml
|
||||
server:
|
||||
image:
|
||||
tag: v1.108.0-enterprise
|
||||
tag: v1.108.1-enterprise
|
||||
|
||||
license:
|
||||
secret:
|
||||
@@ -233,7 +233,7 @@ spec:
|
||||
license:
|
||||
key: {BASE64_ENCODED_LICENSE_KEY}
|
||||
image:
|
||||
tag: v1.108.0-enterprise
|
||||
tag: v1.108.1-enterprise
|
||||
```
|
||||
|
||||
In order to provide key via existing secret, the following custom resource is used:
|
||||
@@ -250,7 +250,7 @@ spec:
|
||||
name: vm-license
|
||||
key: license
|
||||
image:
|
||||
tag: v1.108.0-enterprise
|
||||
tag: v1.108.1-enterprise
|
||||
```
|
||||
|
||||
Example secret with license key:
|
||||
|
||||
@@ -1,58 +1,112 @@
|
||||
VictoriaMetrics supports metrics ingestion with [OpenTelemetry metrics format](https://docs.victoriametrics.com/single-server-victoriametrics/#sending-data-via-opentelemetry).
|
||||
This guide covers data ingestion via [opentelemetry-collector](https://opentelemetry.io/docs/collector/) and direct metrics push from application.
|
||||
VictoriaMetrics and VictoriaLogs support ingestion [metrics](https://docs.victoriametrics.com/single-server-victoriametrics/#sending-data-via-opentelemetry)
|
||||
and [logs](https://docs.victoriametrics.com/victorialogs/data-ingestion/opentelemetry/) in OpenTelemetry format.
|
||||
This guide covers examples of using [opentelemetry-collector](https://opentelemetry.io/docs/collector/) and direct pushing of metrics and logs from the Go application.
|
||||
|
||||
## Pre-Requirements
|
||||
## Pre-Requirements
|
||||
|
||||
* [kubernetes cluster](https://kubernetes.io/docs/tasks/tools/#kind)
|
||||
* [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl)
|
||||
* [helm](https://helm.sh/docs/intro/install/)
|
||||
|
||||
### Install VictoriaMetrics single-server via helm chart
|
||||
## Installation
|
||||
|
||||
Install single-server version:
|
||||
### VictoriaMetrics
|
||||
|
||||
Install VictoriaMetrics helm repo:
|
||||
```sh
|
||||
helm repo add vm https://victoriametrics.github.io/helm-charts/
|
||||
helm repo update
|
||||
helm install victoria-metrics vm/victoria-metrics-single
|
||||
helm repo update
|
||||
```
|
||||
|
||||
Add VictoriaMetrics chart values to convert OTEL metric names to Prometheus canonical format:
|
||||
```sh
|
||||
cat << EOF > vm-values.yaml
|
||||
server:
|
||||
extraArgs:
|
||||
opentelemetry.usePrometheusNaming: true
|
||||
EOF
|
||||
```
|
||||
|
||||
Install VictoriaMetrics single-server version:
|
||||
```sh
|
||||
helm install victoria-metrics vm/victoria-metrics-single -f vm-values.yaml
|
||||
```
|
||||
|
||||
Verify it's up and running:
|
||||
|
||||
```sh
|
||||
kubectl get pods
|
||||
# NAME READY STATUS RESTARTS AGE
|
||||
# victoria-metrics-victoria-metrics-single-server-0 1/1 Running 0 3m1s
|
||||
```
|
||||
|
||||
Helm chart provides the following urls for reading and writing data:
|
||||
VictoriaMetrics helm chart provides the following URL for writing data:
|
||||
```text
|
||||
Write URL inside the kubernetes cluster:
|
||||
http://victoria-metrics-victoria-metrics-single-server.default.svc.cluster.local.:8428/<protocol-specific-write-endpoint>
|
||||
|
||||
```text
|
||||
Write url inside the kubernetes cluster:
|
||||
http://victoria-metrics-victoria-metrics-single-server.default.svc.cluster.local:8428
|
||||
|
||||
Read Data:
|
||||
The following url can be used as the datasource url in Grafana:
|
||||
http://victoria-metrics-victoria-metrics-single-server.default.svc.cluster.local:8428
|
||||
All supported write endpoints can be found at https://docs.victoriametrics.com/single-server-victoriametrics/#how-to-import-time-series-data.
|
||||
```
|
||||
|
||||
## Using opentelemetry-collector with VictoriaMetrics
|
||||
For OpenTelemetry VictoriaMetrics write endpoint is:
|
||||
```text
|
||||
http://victoria-metrics-victoria-metrics-single-server.default.svc.cluster.local.:8428/opentelemetry/v1/metrics
|
||||
```
|
||||
|
||||
### VictoriaLogs
|
||||
|
||||
Install VictoriaLogs:
|
||||
```sh
|
||||
helm install victoria-logs vm/victoria-logs-single
|
||||
```
|
||||
|
||||
Verify it's up and running:
|
||||
```sh
|
||||
kubectl get pods
|
||||
# NAME READY STATUS RESTARTS AGE
|
||||
# victoria-logs-victoria-logs-single-server-0 1/1 Running 0 1m10s
|
||||
```
|
||||
|
||||
VictoriaLogs helm chart provides the following URL for writing data:
|
||||
```text
|
||||
Write URL inside the kubernetes cluster:
|
||||
http://victoria-logs-victoria-logs-single-server.default.svc.cluster.local.:9428/<protocol-specific-write-endpoint>
|
||||
|
||||
All supported write endpoints can be found at https://docs.victoriametrics.com/victorialogs/data-ingestion/
|
||||
```
|
||||
|
||||
For OpenTelemetry VictoriaLogs write endpoint is:
|
||||
```text
|
||||
http://victoria-logs-victoria-logs-single-server.default.svc.cluster.local.:9428/insert/opentelemetry/v1/logs
|
||||
```
|
||||
|
||||
## OpenTelemetry collector with VictoriaMetrics and VictoriaLogs
|
||||
|
||||

|
||||
{width="500"}
|
||||
|
||||
### Deploy opentelemetry-collector and configure metrics forwarding
|
||||
|
||||
Add OpenTelemetry helm repo:
|
||||
```sh
|
||||
helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
|
||||
helm repo update
|
||||
helm repo update
|
||||
```
|
||||
|
||||
# add values
|
||||
cat << EOF > values.yaml
|
||||
Add OpenTelemetry Collector values:
|
||||
```sh
|
||||
cat << EOF > otel-values.yaml
|
||||
mode: deployment
|
||||
image:
|
||||
repository: "otel/opentelemetry-collector-contrib"
|
||||
presets:
|
||||
clusterMetrics:
|
||||
enabled: true
|
||||
logsCollection:
|
||||
enabled: true
|
||||
config:
|
||||
# deltatocumulative processor is needed to convert metrics with delta temporality to cumulative temporality.
|
||||
# VictoriaMetrics doesn't support delta temporality. Skip this processor if you don't use delta temporality.
|
||||
processors:
|
||||
deltatocumulative:
|
||||
max_stale: 5m
|
||||
receivers:
|
||||
otlp:
|
||||
protocols:
|
||||
@@ -61,290 +115,143 @@ config:
|
||||
http:
|
||||
endpoint: 0.0.0.0:4318
|
||||
exporters:
|
||||
otlphttp/victoriametrics:
|
||||
compression: gzip
|
||||
encoding: proto
|
||||
# Setting below will work for sending data to VictoriaMetrics single-node version.
|
||||
# Cluster version of VictoriaMetrics will require a different URL - https://docs.victoriametrics.com/cluster-victoriametrics/#url-format
|
||||
endpoint: http://victoria-metrics-victoria-metrics-single-server.default.svc.cluster.local:8428/opentelemetry
|
||||
tls:
|
||||
otlphttp/victoriametrics:
|
||||
compression: gzip
|
||||
encoding: proto
|
||||
# Setting below will work for sending data to VictoriaMetrics single-node version.
|
||||
# Cluster version of VictoriaMetrics will require a different URL - https://docs.victoriametrics.com/cluster-victoriametrics/#url-format
|
||||
metrics_endpoint: http://victoria-metrics-victoria-metrics-single-server.default.svc.cluster.local:8428/opentelemetry/v1/metrics
|
||||
logs_endpoint: http://victoria-logs-victoria-logs-single-server.default.svc.cluster.local:9428/insert/opentelemetry/v1/logs
|
||||
tls:
|
||||
insecure: true
|
||||
service:
|
||||
pipelines:
|
||||
metrics:
|
||||
receivers: [otlp]
|
||||
logs:
|
||||
processors: []
|
||||
exporters: [otlphttp/victoriametrics]
|
||||
metrics:
|
||||
receivers: [otlp]
|
||||
processors: [deltatocumulative]
|
||||
exporters: [otlphttp/victoriametrics]
|
||||
EOF
|
||||
|
||||
# install helm chart
|
||||
helm upgrade -i otl-collector open-telemetry/opentelemetry-collector -f values.yaml
|
||||
|
||||
# check if pod is healthy
|
||||
kubectl get pod
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
otl-collector-opentelemetry-collector-7467bbb559-2pq2n 1/1 Running 0 23m
|
||||
|
||||
# forward port to local machine to verify metrics are ingested
|
||||
kubectl port-forward service/victoria-metrics-victoria-metrics-single-server 8428
|
||||
|
||||
# check metric `k8s_container_ready` via browser http://localhost:8428/vmui/#/?g0.expr=k8s_container_ready
|
||||
|
||||
# forward port to local machine to setup opentelemetry-collector locally
|
||||
kubectl port-forward otl-collector-opentelemetry-collector 4318
|
||||
```
|
||||
|
||||
The full version of possible configuration options could be found in [OpenTelemetry docs](https://opentelemetry.io/docs/collector/configuration/).
|
||||
Install OpenTelemetry Collector helm chart:
|
||||
```sh
|
||||
helm upgrade -i otel open-telemetry/opentelemetry-collector -f otel-values.yaml
|
||||
```
|
||||
|
||||
## Sending to VictoriaMetrics via OpenTelemetry
|
||||
Metrics could be sent to VictoriaMetrics via OpenTelemetry instrumentation libraries. You can use any compatible OpenTelemetry instrumentation [clients](https://opentelemetry.io/docs/languages/).
|
||||
In our example, we'll create a WEB server in [Golang](https://go.dev/) and instrument it with metrics.
|
||||
Check if OpenTelemetry Collector pod is up and running:
|
||||
```sh
|
||||
kubectl get pod
|
||||
# NAME READY STATUS RESTARTS AGE
|
||||
# otel-opentelemetry-collector-7467bbb559-2pq2n 1/1 Running 0 23m
|
||||
```
|
||||
|
||||
### Building the Go application instrumented with metrics
|
||||
Copy the go file from [here](app.go-collector.example). This will give you a basic implementation of a dice roll WEB server with the urls for opentelemetry-collector pointing to localhost:4318.
|
||||
In the same directory run the following command to create the `go.mod` file:
|
||||
Forward VictoriaMetrics port to local machine to explore metrics ingested by the collector:
|
||||
```sh
|
||||
kubectl port-forward svc/victoria-metrics-victoria-metrics-single-server 8428
|
||||
```
|
||||
|
||||
Visit [http://localhost:8428/vmui/#/?g0.expr=k8s_container_ready](http://localhost:8428/vmui/#/?g0.expr=k8s_container_ready) to check if metric `k8s_container_ready` is present.
|
||||
Check other available metrics by visiting [cardinality explorer](https://docs.victoriametrics.com/#cardinality-explorer) page.
|
||||
|
||||
Forward VictoriaLogs port to local machine to explore logs ingested by the collector:
|
||||
```sh
|
||||
kubectl port-forward svc/victoria-logs-victoria-logs-single-server 9428
|
||||
```
|
||||
|
||||
Visit [http://localhost:9428/select/vmui](http://localhost:9428/select/vmui) to check if logs ingested by collector are present.
|
||||
|
||||
The full version of possible configuration options for the collector can be found in [OpenTelemetry docs](https://opentelemetry.io/docs/collector/configuration/).
|
||||
|
||||
## Sending metrics and logs from Go application
|
||||
|
||||
Metrics and logs can be sent via OpenTelemetry instrumentation libraries. You can use any compatible OpenTelemetry
|
||||
instrumentation [clients](https://opentelemetry.io/docs/languages/).
|
||||
In our example, we'll create a WEB server in [Golang](https://go.dev/), instrument it with metrics and logs and configure
|
||||
it to send telemetry data to OpenTelemetry collector. The collector will then forward received data to
|
||||
VictoriaMetrics or VictoriaLogs.
|
||||
|
||||
### Sending to OpenTelemetry collector
|
||||
|
||||
Create file `main.go` from [example](app.go-collector.example) that implements a dice roll WEB server instrumented with
|
||||
OpenTelemetry SDK and is configured to send data to OpenTelemetry collector at http://localhost:4318 address.
|
||||
See how to setup and run OpenTelemetry collector [here](#OpenTelemetry-collector-with-VictoriaMetrics-and-VictoriaLogs).
|
||||
|
||||
In the same directory with the file create the `go.mod` file and execute following commands:
|
||||
```sh
|
||||
go mod init vm/otel
|
||||
```
|
||||
|
||||
For demo purposes, we'll add the following dependencies to `go.mod` file:
|
||||
```go
|
||||
|
||||
require (
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0
|
||||
go.opentelemetry.io/otel v1.27.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0
|
||||
go.opentelemetry.io/otel/metric v1.27.0
|
||||
go.opentelemetry.io/otel/sdk v1.27.0
|
||||
go.opentelemetry.io/otel/sdk/metric v1.27.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirectdice.rolls
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.27.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.2.0 // indirect
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect
|
||||
google.golang.org/grpc v1.64.0 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
)
|
||||
```
|
||||
|
||||
Once you have these in your `go.mod` file, you can run the following command to download the dependencies:
|
||||
```sh
|
||||
go mod tidy
|
||||
```
|
||||
|
||||
Now you can run the application:
|
||||
Now try running the application:
|
||||
```sh
|
||||
go run .
|
||||
```
|
||||
|
||||
### Test metrics ingestion
|
||||
By default, the application will be available at `localhost:8080`. You can start sending requests to /rolldice endpoint to generate metrics. The following command will send 20 requests to the /rolldice endpoint:
|
||||
By default, the application from example is listening at `http://localhost:8080`. Start sending requests
|
||||
to http://localhost:8080/rolldice endpoint to generate some metrics. The following command will send 20 requests:
|
||||
```sh
|
||||
for i in `seq 1 20`; do curl http://localhost:8080/rolldice; done
|
||||
```
|
||||
|
||||
After a few seconds you should start to see metrics sent over to the vmui interface by visiting `http://localhost:8428/vmui/#/?g0.expr=dice.rolls` in your browser or by querying the metric `dice.rolls` in the vmui interface.
|
||||

|
||||
## Direct metrics push
|
||||
After a few seconds you should start seeing metrics sent to VictoriaMetrics by visiting [http://localhost:8428/vmui/#/?g0.expr=dice_rolls_total](http://localhost:8428/vmui/#/?g0.expr=dice_rolls_total)
|
||||
in your browser or by querying the metric `dice_rolls_total` in the UI interface.
|
||||

|
||||
|
||||
Metrics could be ingested into VictoriaMetrics directly with HTTP requests. You can use any compatible OpenTelemetry
|
||||
instrumentation [clients](https://opentelemetry.io/docs/languages/).
|
||||
In our example, we'll create a WEB server in [Golang](https://go.dev/) and instrument it with metrics.
|
||||
Logs should be available by visiting [http://localhost:9428/select/vmui](http://localhost:9428/select/vmui)
|
||||
using query `service.name: unknown_service:otel`.
|
||||

|
||||
|
||||
### Sending without OpenTelemetry collector
|
||||
|
||||
Metrics and logs can be ingested into VictoriaMetrics and VictoriaLogs directly via HTTP requests.
|
||||
Use any compatible OpenTelemetry instrumentation [clients](https://opentelemetry.io/docs/languages/).
|
||||
|
||||

|
||||
{width="500"}
|
||||
|
||||
In our example, we'll create a WEB server in [Golang](https://go.dev/), instrument it with metrics and logs and configure
|
||||
it to send this telemetry data to VictoriaMetrics and VictoriaLogs.
|
||||
|
||||
### Building the Go application instrumented with metrics
|
||||
|
||||
See the full source code of the example [here](app.go.example).
|
||||
|
||||
The list of OpenTelemetry dependencies for `go.mod` is the following:
|
||||
|
||||
```go
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
go.opentelemetry.io/otel v1.7.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.30.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.30.0
|
||||
go.opentelemetry.io/otel/metric v0.30.0
|
||||
go.opentelemetry.io/otel/sdk v1.7.0
|
||||
go.opentelemetry.io/otel/sdk/metric v0.30.0
|
||||
)
|
||||
```
|
||||
|
||||
Let's create a new file `main.go` with basic implementation of the WEB server:
|
||||
```go
|
||||
package main
|
||||
|
||||
func main() {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/api/fast", func(writer http.ResponseWriter, request *http.Request) {
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
writer.Write([]byte(`fast ok`))
|
||||
})
|
||||
mux.HandleFunc("/api/slow", func(writer http.ResponseWriter, request *http.Request) {
|
||||
time.Sleep(time.Second * 2)
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
writer.Write([]byte(`slow ok`))
|
||||
})
|
||||
|
||||
mw, err := newMetricsMiddleware(mux)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("cannot build metricMiddleWare: %q", err))
|
||||
}
|
||||
|
||||
go func() {
|
||||
http.ListenAndServe("localhost:8081", mw)
|
||||
}()
|
||||
}
|
||||
```
|
||||
|
||||
In the code above, we used `newMetricsMiddleware` function to create a `handler` for our server.
|
||||
Let's define it below:
|
||||
```go
|
||||
|
||||
type metricMiddleWare struct {
|
||||
h http.Handler
|
||||
requestsCount syncint64.Counter
|
||||
requestsLatency syncfloat64.Histogram
|
||||
activeRequests int64
|
||||
}
|
||||
|
||||
func newMetricsMiddleware(h http.Handler) (*metricMiddleWare, error) {
|
||||
mw := &metricMiddleWare{h: h}
|
||||
mc, err := newMetricsController(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot build metrics collector: %w", err)
|
||||
}
|
||||
global.SetMeterProvider(mc)
|
||||
|
||||
prov := mc.Meter("")
|
||||
|
||||
mw.requestsLatency, err = prov.SyncFloat64().Histogram("http_request_latency_seconds")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot create histogram: %w", err)
|
||||
}
|
||||
mw.requestsCount, err = prov.SyncInt64().Counter("http_requests_total")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot create syncInt64 counter: %w", err)
|
||||
}
|
||||
ar, err := prov.AsyncInt64().Gauge("http_active_requests")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot create AsyncInt64 gauge: %w", err)
|
||||
}
|
||||
if err := prov.RegisterCallback([]instrument.Asynchronous{ar}, func(ctx context.Context) {
|
||||
ar.Observe(ctx, atomic.LoadInt64(&mw.activeRequests))
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("cannot Register int64 gauge: %w", err)
|
||||
}
|
||||
|
||||
return mw, nil
|
||||
}
|
||||
```
|
||||
|
||||
The new type `metricMiddleWare` is instrumented with 3 [metrics](https://opentelemetry.io/docs/specs/otel/metrics/data-model/#timeseries-model)
|
||||
initialized in `newMetricsMiddleware` method:
|
||||
* counter `http_requests_total`
|
||||
* histogram `http_request_latency_seconds`
|
||||
* gauge `http_active_requests`
|
||||
|
||||
Let's implement http.Handler interface for `metricMiddleWare` by adding `ServeHTTP` method:
|
||||
```go
|
||||
func (m *metricMiddleWare) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
t := time.Now()
|
||||
path := r.URL.Path
|
||||
m.requestsCount.Add(nil, 1, attribute.String("path", path))
|
||||
atomic.AddInt64(&m.activeRequests, 1)
|
||||
defer func() {
|
||||
atomic.AddInt64(&m.activeRequests, -1)
|
||||
m.requestsLatency.Record(nil, time.Since(t).Seconds(), attribute.String("path", path))
|
||||
}()
|
||||
m.h.ServeHTTP(w, r)
|
||||
}
|
||||
```
|
||||
|
||||
In method above, our middleware processes received HTTP requests and updates metrics with each new request.
|
||||
But for these metrics to be shipped we need to add a new method `newMetricsController` to organize metrics collection:
|
||||
```go
|
||||
func newMetricsController(ctx context.Context) (*controller.Controller, error) {
|
||||
options := []otlpmetrichttp.Option{
|
||||
otlpmetrichttp.WithEndpoint("<VictoriaMetrics endpoint - host:port>"),
|
||||
otlpmetrichttp.WithURLPath("/opentelemetry/api/v1/push"),
|
||||
}
|
||||
|
||||
metricExporter, err := otlpmetrichttp.New(ctx, options...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot create otlphttp exporter: %w", err)
|
||||
}
|
||||
|
||||
resourceConfig, err := resource.New(ctx, resource.WithAttributes(attribute.String("job", "otlp"), attribute.String("instance", "localhost")))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot create meter resource: %w", err)
|
||||
}
|
||||
meterController := controller.New(
|
||||
processor.NewFactory(
|
||||
selector.NewWithHistogramDistribution(
|
||||
histogram.WithExplicitBoundaries([]float64{0.01, 0.05, 0.1, 0.5, 0.9, 1.0, 5.0, 10.0, 100.0}),
|
||||
),
|
||||
aggregation.CumulativeTemporalitySelector(),
|
||||
processor.WithMemory(true),
|
||||
),
|
||||
controller.WithExporter(metricExporter),
|
||||
controller.WithCollectPeriod(time.Second * 10),
|
||||
controller.WithResource(resourceConfig),
|
||||
)
|
||||
if err := meterController.Start(ctx); err != nil {
|
||||
return nil, fmt.Errorf("cannot start meter controller: %w", err)
|
||||
}
|
||||
return meterController, nil
|
||||
}
|
||||
```
|
||||
|
||||
This controller will collect and push collected metrics to VictoriaMetrics address with interval of `10s`.
|
||||
|
||||
See the full source code of the example [here](app.go.example).
|
||||
|
||||
### Test metrics ingestion
|
||||
|
||||
In order to push metrics of our WEB server to VictoriaMetrics it is necessary to ensure that VictoriaMetrics ingestion
|
||||
endpoint is available locally.
|
||||
In previous steps we already deployed a single-server VictoriaMetrics, so let's make it available locally:
|
||||
Create file `main.go` from [example](app.go.example). In the same directory with the file create the `go.mod` file and execute following commands:
|
||||
```sh
|
||||
# port-forward victoriametrics to ingest metrics
|
||||
kubectl port-forward victoria-metrics-victoria-metrics-single-server-0 8428
|
||||
go mod init vm/otel
|
||||
go mod tidy
|
||||
```
|
||||
|
||||
Now let's run our WEB server and call its APIs:
|
||||
The example implements WEB server with two HTTP handlers: `/api/slow` and `/api/fast`. Start the application:
|
||||
```sh
|
||||
# build and run the app
|
||||
go run main.go
|
||||
2024/03/25 19:27:41 Starting web server...
|
||||
2024/03/25 19:27:41 web server started at localhost:8081.
|
||||
|
||||
# execute few queries with curl
|
||||
curl http://localhost:8081/api/fast
|
||||
curl http://localhost:8081/api/slow
|
||||
```
|
||||
|
||||
Open [vmui](https://docs.victoriametrics.com/#vmui) and query `http_requests_total` or `http_active_requests`
|
||||
with [metricsql](https://docs.victoriametrics.com/metricsql/).
|
||||
Make sure that VictoriaMetrics and VictoriaLogs are available locally at their default ports:
|
||||
```sh
|
||||
# port-forward victoriametrics to ingest metrics
|
||||
kubectl port-forward victoria-metrics-victoria-metrics-single-server-0 8428
|
||||
# port-forward victorialogs to ingest logs
|
||||
kubectl port-forward victoria-logs-victoria-logs-single-server-0 9428
|
||||
```
|
||||
|
||||

|
||||
Visit application links [http://localhost:8081/api/fast](http://localhost:8081/api/fast) or [http://localhost:8081/api/slow](http://localhost:8081/api/slow)
|
||||
couple of times. The application will generate metrics and logs and will send them to VictoriaMetrics and VictoriaLogs.
|
||||
|
||||
After a few seconds you should start seeing metrics sent to VictoriaMetrics by visiting
|
||||
[http://localhost:8428/vmui/#/?g0.expr=http_requests_total](http://localhost:8428/vmui/#/?g0.expr=http_requests_total).
|
||||
|
||||

|
||||
|
||||
Check other available metrics by visiting [cardinality explorer](https://docs.victoriametrics.com/#cardinality-explorer) page.
|
||||
|
||||
Logs should be available by visiting [http://localhost:9428/select/vmui](http://localhost:9428/select/vmui)
|
||||
using query `service.name: unknown_service:otel`.
|
||||
|
||||

|
||||
|
||||
## Limitations
|
||||
|
||||
* VictoriaMetrics doesn't support experimental JSON encoding [format](https://github.com/open-telemetry/opentelemetry-proto/blob/main/examples/metrics.json).
|
||||
* VictoriaMetrics supports only `AggregationTemporalityCumulative` type for [histogram](https://opentelemetry.io/docs/specs/otel/metrics/data-model/#histogram) and [summary](https://opentelemetry.io/docs/specs/otel/metrics/data-model/#summary-legacy)
|
||||
* VictoriaMetrics and VictoriaLogs do not support experimental JSON encoding [format](https://github.com/open-telemetry/opentelemetry-proto/blob/main/examples/metrics.json).
|
||||
* VictoriaMetrics supports only `AggregationTemporalityCumulative` type for [histogram](https://opentelemetry.io/docs/specs/otel/metrics/data-model/#histogram) and [summary](https://opentelemetry.io/docs/specs/otel/metrics/data-model/#summary-legacy). Either consider using cumulative temporality or try [`delta-to-cumulative processor`](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/deltatocumulativeprocessor) to make conversion to cumulative temporality in OTEL Collector.
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
---
|
||||
weight: 5
|
||||
title: How to use OpenTelemetry metrics with VictoriaMetrics
|
||||
title: How to use OpenTelemetry with VictoriaMetrics and VictoriaLogs
|
||||
menu:
|
||||
docs:
|
||||
parent: "guides"
|
||||
weight: 5
|
||||
aliases:
|
||||
- /guides/how-to-use-opentelemetry-metrics-with-victoriametrics/
|
||||
- /guides/how-to-use-opentelemetry-with-victoriametrics-and-victorialogs/
|
||||
---
|
||||
{{% content "README.md" %}}
|
||||
|
||||
@@ -13,17 +13,22 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/contrib/bridges/otelslog"
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
|
||||
"go.opentelemetry.io/otel/log/global"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
otelmetric "go.opentelemetry.io/otel/metric"
|
||||
"go.opentelemetry.io/otel/sdk/metric"
|
||||
sdklog "go.opentelemetry.io/otel/sdk/log"
|
||||
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
|
||||
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
"go.opentelemetry.io/otel/sdk/trace"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.25.0"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -98,14 +103,15 @@ func newHTTPHandler() http.Handler {
|
||||
var (
|
||||
tracer = otel.Tracer("rolldice")
|
||||
meter = otel.Meter("rolldice")
|
||||
rollCnt otelmetric.Int64Counter
|
||||
logger = otelslog.NewLogger("rolldice")
|
||||
rollCnt metric.Int64Counter
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
rollCnt, err = meter.Int64Counter("dice.rolls",
|
||||
otelmetric.WithDescription("The number of rolls by roll value"),
|
||||
otelmetric.WithUnit("{roll}"))
|
||||
metric.WithDescription("The number of rolls by roll value"),
|
||||
metric.WithUnit("{roll}"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -119,7 +125,8 @@ func rolldice(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
rollValueAttr := attribute.Int("roll.value", roll)
|
||||
span.SetAttributes(rollValueAttr)
|
||||
rollCnt.Add(ctx, 1, otelmetric.WithAttributes(rollValueAttr))
|
||||
rollCnt.Add(ctx, 1, metric.WithAttributes(rollValueAttr))
|
||||
logger.InfoContext(ctx, "Anonymous player is rolling the dice", "result", roll)
|
||||
|
||||
resp := strconv.Itoa(roll) + "\n"
|
||||
if _, err := io.WriteString(w, resp); err != nil {
|
||||
@@ -171,6 +178,15 @@ func setupOTelSDK(ctx context.Context) (shutdown func(context.Context) error, er
|
||||
shutdownFuncs = append(shutdownFuncs, meterProvider.Shutdown)
|
||||
otel.SetMeterProvider(meterProvider)
|
||||
|
||||
// Set up log provider.
|
||||
logProvider, err := newLoggerProvider(ctx)
|
||||
if err != nil {
|
||||
handleErr(err)
|
||||
return
|
||||
}
|
||||
global.SetLoggerProvider(logProvider)
|
||||
shutdownFuncs = append(shutdownFuncs, logProvider.Shutdown)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -182,28 +198,32 @@ func newPropagator() propagation.TextMapPropagator {
|
||||
}
|
||||
|
||||
func newTraceProvider(ctx context.Context) (*trace.TracerProvider, error) {
|
||||
traceExporter, err := otlptracehttp.New(ctx, otlptracehttp.WithInsecure(), otlptracehttp.WithEndpoint("localhost:4318"))
|
||||
exporter, err := otlptracehttp.New(ctx, otlptracehttp.WithInsecure(), otlptracehttp.WithEndpoint("localhost:4318"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
traceProvider := trace.NewTracerProvider(
|
||||
trace.WithBatcher(traceExporter,
|
||||
provider := trace.NewTracerProvider(
|
||||
trace.WithBatcher(exporter,
|
||||
// Default is 5s. Set to 1s for demonstrative purposes.
|
||||
trace.WithBatchTimeout(time.Second)),
|
||||
)
|
||||
return traceProvider, nil
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
func newMeterProvider(ctx context.Context) (*metric.MeterProvider, error) {
|
||||
metricExporter, err := otlpmetrichttp.New(ctx, otlpmetrichttp.WithInsecure(), otlpmetrichttp.WithEndpoint("localhost:4318"))
|
||||
func newMeterProvider(ctx context.Context) (*sdkmetric.MeterProvider, error) {
|
||||
exporter, err := otlpmetrichttp.New(ctx,
|
||||
otlpmetrichttp.WithInsecure(),
|
||||
otlpmetrichttp.WithEndpoint("localhost:4318"),
|
||||
otlpmetrichttp.WithTemporalitySelector(
|
||||
func(kind sdkmetric.InstrumentKind) metricdata.Temporality {
|
||||
return metricdata.DeltaTemporality
|
||||
},
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//metricExporter, err := stdoutmetric.New()
|
||||
//if err != nil {
|
||||
// return nil, err
|
||||
//}
|
||||
res, err := resource.Merge(resource.Default(),
|
||||
resource.NewWithAttributes(semconv.SchemaURL,
|
||||
semconv.ServiceName("dice-roller"),
|
||||
@@ -212,12 +232,26 @@ func newMeterProvider(ctx context.Context) (*metric.MeterProvider, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
meterProvider := metric.NewMeterProvider(
|
||||
metric.WithResource(res),
|
||||
metric.WithReader(metric.NewPeriodicReader(metricExporter,
|
||||
// Default is 1m. Set to 3s for demonstrative purposes.
|
||||
metric.WithInterval(3*time.Second))),
|
||||
provider := sdkmetric.NewMeterProvider(
|
||||
sdkmetric.WithResource(res),
|
||||
sdkmetric.WithReader(
|
||||
sdkmetric.NewPeriodicReader(
|
||||
exporter,
|
||||
sdkmetric.WithInterval(3*time.Second),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
return meterProvider, nil
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
func newLoggerProvider(ctx context.Context) (*sdklog.LoggerProvider, error) {
|
||||
exporter, err := otlploghttp.New(ctx, otlploghttp.WithInsecure(), otlploghttp.WithEndpoint("localhost:4318"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
provider := sdklog.NewLoggerProvider(
|
||||
sdklog.WithProcessor(sdklog.NewBatchProcessor(exporter)),
|
||||
)
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
@@ -12,155 +11,180 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/contrib/bridges/otelslog"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
|
||||
"go.opentelemetry.io/otel/metric/global"
|
||||
"go.opentelemetry.io/otel/metric/instrument"
|
||||
"go.opentelemetry.io/otel/metric/instrument/syncfloat64"
|
||||
"go.opentelemetry.io/otel/metric/instrument/syncint64"
|
||||
"go.opentelemetry.io/otel/sdk/metric/aggregator/histogram"
|
||||
controller "go.opentelemetry.io/otel/sdk/metric/controller/basic"
|
||||
"go.opentelemetry.io/otel/sdk/metric/export/aggregation"
|
||||
processor "go.opentelemetry.io/otel/sdk/metric/processor/basic"
|
||||
selector "go.opentelemetry.io/otel/sdk/metric/selector/simple"
|
||||
"go.opentelemetry.io/otel/log/global"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
sdklog "go.opentelemetry.io/otel/sdk/log"
|
||||
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
)
|
||||
|
||||
var (
|
||||
collectorEndpoint = flag.String("vm.endpoint", "localhost:8428", "VictoriaMetrics endpoint - host:port")
|
||||
collectorURL = flag.String("vm.ingestPath", "/opentelemetry/api/v1/push", "url path for ingestion path")
|
||||
isSecure = flag.Bool("vm.isSecure", false, "enables https connection for metrics push")
|
||||
pushInterval = flag.Duration("vm.pushInterval", 10*time.Second, "how often push samples, aka scrapeInterval at pull model")
|
||||
jobName = flag.String("metrics.jobName", "otlp", "job name for web-application")
|
||||
instanceName = flag.String("metrics.instance", "localhost", "hostname of web-application instance")
|
||||
metricsEndpoint = flag.String("vm.endpoint", "http://localhost:8428/opentelemetry/v1/metrics", "VictoriaMetrics endpoint")
|
||||
logsEndpoint = flag.String("vl.endpoint", "http://localhost:9428/insert/opentelemetry/v1/logs", "VictoriaLogs endpoint")
|
||||
pushInterval = flag.Duration("vm.pushInterval", 10*time.Second, "How often to push collected metrics samples to vm.endpoint")
|
||||
)
|
||||
|
||||
var (
|
||||
logger = otelslog.NewLogger("demo-app")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
log.Printf("Starting web server...")
|
||||
fmt.Println("Starting web server...")
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/api/fast", func(writer http.ResponseWriter, request *http.Request) {
|
||||
logger.InfoContext(ctx, "Anonymous access to fast endpoint")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
writer.Write([]byte(`fast ok`))
|
||||
})
|
||||
mux.HandleFunc("/api/slow", func(writer http.ResponseWriter, request *http.Request) {
|
||||
time.Sleep(time.Second * 2)
|
||||
logger.InfoContext(ctx, "Anonymous access to slow endpoint")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
writer.Write([]byte(`slow ok`))
|
||||
})
|
||||
mw, err := newMetricsMiddleware(ctx, mux)
|
||||
mw, err := newMiddleware(ctx, mux)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("cannot build metricMiddleWare: %q", err))
|
||||
panic(fmt.Sprintf("cannot build middleware: %q", err))
|
||||
}
|
||||
|
||||
mustStop := make(chan os.Signal, 1)
|
||||
signal.Notify(mustStop, os.Interrupt, syscall.SIGTERM)
|
||||
go func() {
|
||||
http.ListenAndServe("localhost:8081", mw)
|
||||
err := http.ListenAndServe("localhost:8081", mw)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
log.Printf("web server started at localhost:8081.")
|
||||
|
||||
fmt.Println("web server started at http://localhost:8081")
|
||||
<-mustStop
|
||||
log.Println("receive shutdown signal, stopping webserver")
|
||||
|
||||
if err := mw.onShutdown(ctx); err != nil {
|
||||
log.Println("cannot shutdown metric provider ", err)
|
||||
fmt.Println("receive shutdown signal, stopping webserver")
|
||||
for _, shutdown := range mw.onShutdown {
|
||||
if err := shutdown(ctx); err != nil {
|
||||
fmt.Printf("cannot shutdown metric provider: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
cancel()
|
||||
log.Printf("Done!")
|
||||
fmt.Println("Done!")
|
||||
}
|
||||
|
||||
func newMetricsController(ctx context.Context) (*controller.Controller, error) {
|
||||
options := []otlpmetrichttp.Option{
|
||||
otlpmetrichttp.WithEndpoint(*collectorEndpoint),
|
||||
otlpmetrichttp.WithURLPath(*collectorURL),
|
||||
}
|
||||
if !*isSecure {
|
||||
options = append(options, otlpmetrichttp.WithInsecure())
|
||||
}
|
||||
|
||||
metricExporter, err := otlpmetrichttp.New(ctx, options...)
|
||||
func newMeterProvider(ctx context.Context) (*sdkmetric.MeterProvider, error) {
|
||||
exporter, err := otlpmetrichttp.New(ctx, otlpmetrichttp.WithEndpointURL(*metricsEndpoint))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot create otlphttp exporter: %w", err)
|
||||
}
|
||||
|
||||
resourceConfig, err := resource.New(ctx, resource.WithAttributes(attribute.String("job", *jobName), attribute.String("instance", *instanceName)))
|
||||
res, err := resource.New(ctx,
|
||||
resource.WithAttributes(
|
||||
attribute.String("job", "otlp"),
|
||||
attribute.String("instance", "localhost"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot create meter resource: %w", err)
|
||||
}
|
||||
meterController := controller.New(
|
||||
processor.NewFactory(
|
||||
selector.NewWithHistogramDistribution(
|
||||
histogram.WithExplicitBoundaries([]float64{0.01, 0.05, 0.1, 0.5, 0.9, 1.0, 5.0, 10.0, 100.0}),
|
||||
),
|
||||
aggregation.CumulativeTemporalitySelector(),
|
||||
processor.WithMemory(true),
|
||||
),
|
||||
controller.WithExporter(metricExporter),
|
||||
controller.WithCollectPeriod(*pushInterval),
|
||||
controller.WithResource(resourceConfig),
|
||||
|
||||
// define histograms for measuring requests latency
|
||||
expView := sdkmetric.NewView(
|
||||
// classic histogram
|
||||
sdkmetric.Instrument{
|
||||
Name: "http.requests.latency",
|
||||
Kind: sdkmetric.InstrumentKindHistogram,
|
||||
},
|
||||
// exponential histogram
|
||||
sdkmetric.Stream{
|
||||
Name: "http.requests.latency.exp",
|
||||
Aggregation: sdkmetric.AggregationBase2ExponentialHistogram{
|
||||
MaxSize: 160,
|
||||
MaxScale: 20,
|
||||
},
|
||||
},
|
||||
)
|
||||
if err := meterController.Start(ctx); err != nil {
|
||||
return nil, fmt.Errorf("cannot start meter controller: %w", err)
|
||||
}
|
||||
return meterController, nil
|
||||
return sdkmetric.NewMeterProvider(
|
||||
sdkmetric.WithReader(sdkmetric.NewPeriodicReader(exporter, sdkmetric.WithInterval(*pushInterval))),
|
||||
sdkmetric.WithResource(res),
|
||||
sdkmetric.WithView(expView),
|
||||
), nil
|
||||
}
|
||||
|
||||
func newMetricsMiddleware(ctx context.Context, h http.Handler) (*metricMiddleWare, error) {
|
||||
mw := &metricMiddleWare{
|
||||
func newLoggerProvider(ctx context.Context) (*sdklog.LoggerProvider, error) {
|
||||
exporter, err := otlploghttp.New(ctx, otlploghttp.WithEndpointURL(*logsEndpoint))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
provider := sdklog.NewLoggerProvider(
|
||||
sdklog.WithProcessor(sdklog.NewBatchProcessor(exporter)),
|
||||
)
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
func newMiddleware(ctx context.Context, h http.Handler) (*middleware, error) {
|
||||
mw := &middleware{
|
||||
ctx: ctx,
|
||||
h: h,
|
||||
}
|
||||
mc, err := newMetricsController(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot build metrics collector: %w", err)
|
||||
}
|
||||
global.SetMeterProvider(mc)
|
||||
|
||||
prov := mc.Meter("")
|
||||
lp, err := newLoggerProvider(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot create logs provider: %w", err)
|
||||
}
|
||||
global.SetLoggerProvider(lp)
|
||||
|
||||
mw.requestsLatency, err = prov.SyncFloat64().Histogram("http_request_latency_seconds")
|
||||
mp, err := newMeterProvider(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot create histogram: %w", err)
|
||||
return nil, fmt.Errorf("cannot create metrics provider: %w", err)
|
||||
}
|
||||
mw.requestsCount, err = prov.SyncInt64().Counter("http_requests_total")
|
||||
otel.SetMeterProvider(mp)
|
||||
meter := mp.Meter("")
|
||||
|
||||
mw.requestsLatency, err = meter.Float64Histogram("http.requests.latency")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot create syncInt64 counter: %w", err)
|
||||
return nil, fmt.Errorf("cannot create Float64Histogram: %w", err)
|
||||
}
|
||||
ar, err := prov.AsyncInt64().Gauge("http_active_requests")
|
||||
mw.requestsCount, err = meter.Int64Counter("http.requests")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot create AsyncInt64 gauge: %w", err)
|
||||
return nil, fmt.Errorf("cannot create Int64Counter: %w", err)
|
||||
}
|
||||
if err := prov.RegisterCallback([]instrument.Asynchronous{ar}, func(ctx context.Context) {
|
||||
ar.Observe(ctx, atomic.LoadInt64(&mw.activeRequests))
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("cannot Register int64 gauge: %w", err)
|
||||
cb := func(c context.Context, o metric.Int64Observer) error {
|
||||
o.Observe(atomic.LoadInt64(&mw.activeRequests))
|
||||
return nil
|
||||
}
|
||||
mw.onShutdown = mc.Stop
|
||||
_, err = meter.Int64ObservableGauge("http.requests.active", metric.WithInt64Callback(cb))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot create Int64ObservableGauge: %w", err)
|
||||
}
|
||||
mw.onShutdown = append(mw.onShutdown, mp.Shutdown, lp.Shutdown)
|
||||
|
||||
return mw, nil
|
||||
}
|
||||
|
||||
type metricMiddleWare struct {
|
||||
type middleware struct {
|
||||
ctx context.Context
|
||||
h http.Handler
|
||||
requestsCount syncint64.Counter
|
||||
requestsLatency syncfloat64.Histogram
|
||||
requestsCount metric.Int64Counter
|
||||
requestsLatency metric.Float64Histogram
|
||||
activeRequests int64
|
||||
|
||||
onShutdown func(ctx context.Context) error
|
||||
onShutdown []func(ctx context.Context) error
|
||||
}
|
||||
|
||||
func (m *metricMiddleWare) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
func (m *middleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
t := time.Now()
|
||||
path := r.URL.Path
|
||||
m.requestsCount.Add(m.ctx, 1, attribute.String("path", path))
|
||||
m.requestsCount.Add(m.ctx, 1, metric.WithAttributes(attribute.String("path", path)))
|
||||
atomic.AddInt64(&m.activeRequests, 1)
|
||||
defer func() {
|
||||
atomic.AddInt64(&m.activeRequests, -1)
|
||||
m.requestsLatency.Record(m.ctx, time.Since(t).Seconds(), attribute.String("path", path))
|
||||
m.requestsLatency.Record(m.ctx, time.Since(t).Seconds(), metric.WithAttributes(attribute.String("path", path)))
|
||||
}()
|
||||
|
||||
m.h.ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 46 KiB |
@@ -236,27 +236,27 @@ services:
|
||||
- grafana_data:/var/lib/grafana/
|
||||
|
||||
vmsingle:
|
||||
image: victoriametrics/victoria-metrics:v1.108.0
|
||||
image: victoriametrics/victoria-metrics:v1.108.1
|
||||
command:
|
||||
- -httpListenAddr=0.0.0.0:8429
|
||||
|
||||
vmstorage:
|
||||
image: victoriametrics/vmstorage:v1.108.0-cluster
|
||||
image: victoriametrics/vmstorage:v1.108.1-cluster
|
||||
|
||||
vminsert:
|
||||
image: victoriametrics/vminsert:v1.108.0-cluster
|
||||
image: victoriametrics/vminsert:v1.108.1-cluster
|
||||
command:
|
||||
- -storageNode=vmstorage:8400
|
||||
- -httpListenAddr=0.0.0.0:8480
|
||||
|
||||
vmselect:
|
||||
image: victoriametrics/vmselect:v1.108.0-cluster
|
||||
image: victoriametrics/vmselect:v1.108.1-cluster
|
||||
command:
|
||||
- -storageNode=vmstorage:8401
|
||||
- -httpListenAddr=0.0.0.0:8481
|
||||
|
||||
vmagent:
|
||||
image: victoriametrics/vmagent:v1.108.0
|
||||
image: victoriametrics/vmagent:v1.108.1
|
||||
volumes:
|
||||
- ./scrape.yaml:/etc/vmagent/config.yaml
|
||||
command:
|
||||
@@ -265,7 +265,7 @@ services:
|
||||
- -remoteWrite.url=http://vmsingle:8429/api/v1/write
|
||||
|
||||
vmgateway-cluster:
|
||||
image: victoriametrics/vmgateway:v1.108.0-enterprise
|
||||
image: victoriametrics/vmgateway:v1.108.1-enterprise
|
||||
ports:
|
||||
- 8431:8431
|
||||
volumes:
|
||||
@@ -281,7 +281,7 @@ services:
|
||||
- -auth.oidcDiscoveryEndpoints=http://keycloak:8080/realms/master/.well-known/openid-configuration
|
||||
|
||||
vmgateway-single:
|
||||
image: victoriametrics/vmgateway:v1.108.0-enterprise
|
||||
image: victoriametrics/vmgateway:v1.108.1-enterprise
|
||||
ports:
|
||||
- 8432:8431
|
||||
volumes:
|
||||
@@ -393,7 +393,7 @@ Once iDP configuration is done, vmagent configuration needs to be updated to use
|
||||
|
||||
```yaml
|
||||
vmagent:
|
||||
image: victoriametrics/vmagent:v1.108.0
|
||||
image: victoriametrics/vmagent:v1.108.1
|
||||
volumes:
|
||||
- ./scrape.yaml:/etc/vmagent/config.yaml
|
||||
- ./vmagent-client-secret:/etc/vmagent/oauth2-client-secret
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
## Next release
|
||||
|
||||
- updated common dependency 0.0.34 -> 0.0.35
|
||||
- Exclude markdown files from package
|
||||
|
||||
## 0.8.11
|
||||
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
## Next release
|
||||
|
||||
- Exclude markdown files from package
|
||||
|
||||
## 0.15.2
|
||||
|
||||
**Release date:** 19 Dec 2024
|
||||
|
||||
 
|
||||
|
||||
- updated common dependency 0.0.34 -> 0.0.35
|
||||
- bump version of VM components to [v1.108.1](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.108.1)
|
||||
|
||||
## 0.15.1
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
 
|
||||
 
|
||||
[](https://artifacthub.io/packages/helm/victoriametrics/victoria-metrics-agent)
|
||||
[](https://slack.victoriametrics.com/)
|
||||
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
## Next release
|
||||
|
||||
- Exclude markdown files from package
|
||||
|
||||
## 0.13.4
|
||||
|
||||
**Release date:** 19 Dec 2024
|
||||
|
||||
 
|
||||
|
||||
- updated common dependency 0.0.34 -> 0.0.35
|
||||
- bump version of VM components to [v1.108.1](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.108.1)
|
||||
|
||||
## 0.13.3
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
 
|
||||
 
|
||||
[](https://artifacthub.io/packages/helm/victoriametrics/victoria-metrics-alert)
|
||||
[](https://slack.victoriametrics.com/)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
## Next release
|
||||
|
||||
- updated common dependency 0.0.34 -> 0.0.35
|
||||
- Exclude markdown files from package
|
||||
|
||||
## 1.6.11
|
||||
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
## Next release
|
||||
|
||||
- Exclude markdown files from package
|
||||
|
||||
## 0.8.2
|
||||
|
||||
**Release date:** 19 Dec 2024
|
||||
|
||||
 
|
||||
|
||||
- updated common dependency 0.0.34 -> 0.0.35
|
||||
- bump version of VM components to [v1.108.1](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.108.1)
|
||||
|
||||
## 0.8.1
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
 
|
||||
 
|
||||
[](https://artifacthub.io/packages/helm/victoriametrics/victoria-metrics-auth)
|
||||
[](https://slack.victoriametrics.com/)
|
||||
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
## Next release
|
||||
|
||||
- Exclude markdown files from package
|
||||
|
||||
## 0.16.1
|
||||
|
||||
**Release date:** 19 Dec 2024
|
||||
|
||||
 
|
||||
|
||||
- updated common dependency 0.0.34 -> 0.0.35
|
||||
- bump version of VM components to [v1.108.1](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.108.1)
|
||||
|
||||
## 0.16.0
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
 
|
||||
 
|
||||
[](https://artifacthub.io/packages/helm/victoriametrics/victoria-metrics-cluster)
|
||||
[](https://slack.victoriametrics.com/)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## Next release
|
||||
|
||||
- TODO
|
||||
- Exclude markdown files from package
|
||||
|
||||
## 0.0.35
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
## Next release
|
||||
|
||||
- TODO
|
||||
- upgraded operator, it's required to [update CRDs manually](../victoriametrics-k8s-stack/#upgrade-guide)
|
||||
- bump version of VM components to [v1.108.1](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.108.1)
|
||||
- Exclude markdown files from package
|
||||
|
||||
## 0.6.2
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
 
|
||||
 
|
||||
[](https://artifacthub.io/packages/helm/victoriametrics/victoria-metrics-distributed)
|
||||
[](https://slack.victoriametrics.com/)
|
||||
|
||||
|
||||