Compare commits
25 Commits
configwatc
...
v1.122.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7967ad661e | ||
|
|
1aa72ecbfd | ||
|
|
3b656147ef | ||
|
|
3c4004673e | ||
|
|
45c9f31987 | ||
|
|
37013d36c0 | ||
|
|
c9b3088c9c | ||
|
|
24aef8ea90 | ||
|
|
e540e5e381 | ||
|
|
51aebcd061 | ||
|
|
df7b752c7a | ||
|
|
6f74b139cc | ||
|
|
e49609cbc2 | ||
|
|
2e655a91bc | ||
|
|
1e927b2e53 | ||
|
|
21963a1cad | ||
|
|
87b291debe | ||
|
|
cce1cdcb6d | ||
|
|
03e003c828 | ||
|
|
ad9d11ba3f | ||
|
|
5c2ed99dab | ||
|
|
eaec80b7f3 | ||
|
|
d6ef8a807b | ||
|
|
c0318a84f0 | ||
|
|
5a056321af |
@@ -10,7 +10,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/configwatcher"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmagent/csvimport"
|
||||
@@ -113,7 +112,6 @@ func main() {
|
||||
flag.Usage = usage
|
||||
envflag.Parse()
|
||||
remotewrite.InitSecretFlags()
|
||||
configwatcher.Init()
|
||||
buildinfo.Init()
|
||||
logger.Init()
|
||||
timeserieslimits.Init(*maxLabelsPerTimeseries, *maxLabelNameLen, *maxLabelValueLen)
|
||||
@@ -201,7 +199,6 @@ func main() {
|
||||
}
|
||||
protoparserutil.StopUnmarshalWorkers()
|
||||
remotewrite.Stop()
|
||||
configwatcher.Stop()
|
||||
|
||||
logger.Infof("successfully stopped vmagent in %.3f seconds", time.Since(startTime).Seconds())
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmagent/common"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmagent/remotewrite"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/auth"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prommetadata"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/opentelemetry/firehose"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/opentelemetry/stream"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/protoparserutil"
|
||||
@@ -68,7 +68,7 @@ func insertRows(at *auth.Token, tss []prompb.TimeSeries, mms []prompb.MetricMeta
|
||||
ctx.WriteRequest.Timeseries = tssDst
|
||||
|
||||
var metadataTotal int
|
||||
if promscrape.IsMetadataEnabled() {
|
||||
if prommetadata.IsEnabled() {
|
||||
var accountID, projectID uint32
|
||||
if at != nil {
|
||||
accountID = at.AccountID
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmagent/remotewrite"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/auth"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prommetadata"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/prometheus"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/prometheus/stream"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/protoparserutil"
|
||||
@@ -36,7 +36,7 @@ func InsertHandler(at *auth.Token, req *http.Request) error {
|
||||
return err
|
||||
}
|
||||
encoding := req.Header.Get("Content-Encoding")
|
||||
return stream.Parse(req.Body, defaultTimestamp, encoding, true, promscrape.IsMetadataEnabled(), func(rows []prometheus.Row, mms []prometheus.Metadata) error {
|
||||
return stream.Parse(req.Body, defaultTimestamp, encoding, true, prommetadata.IsEnabled(), func(rows []prometheus.Row, mms []prometheus.Metadata) error {
|
||||
return insertRows(at, rows, mms, extraLabels)
|
||||
}, func(s string) {
|
||||
httpserver.LogError(req, s)
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmagent/common"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmagent/remotewrite"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/auth"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prommetadata"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/promremotewrite/stream"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/protoparserutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/tenantmetrics"
|
||||
@@ -71,7 +71,7 @@ func insertRows(at *auth.Token, timeseries []prompb.TimeSeries, mms []prompb.Met
|
||||
ctx.WriteRequest.Timeseries = tssDst
|
||||
|
||||
var metadataTotal int
|
||||
if promscrape.IsMetadataEnabled() {
|
||||
if prommetadata.IsEnabled() {
|
||||
var accountID, projectID uint32
|
||||
if at != nil {
|
||||
accountID = at.AccountID
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/common"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/relabel"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prommetadata"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/prometheus"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/prometheus/stream"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/protoparserutil"
|
||||
@@ -30,7 +30,7 @@ func InsertHandler(req *http.Request) error {
|
||||
return err
|
||||
}
|
||||
encoding := req.Header.Get("Content-Encoding")
|
||||
return stream.Parse(req.Body, defaultTimestamp, encoding, true, promscrape.IsMetadataEnabled(), func(rows []prometheus.Row, _ []prometheus.Metadata) error {
|
||||
return stream.Parse(req.Body, defaultTimestamp, encoding, true, prommetadata.IsEnabled(), func(rows []prometheus.Row, _ []prometheus.Metadata) error {
|
||||
return insertRows(rows, extraLabels)
|
||||
}, func(s string) {
|
||||
httpserver.LogError(req, s)
|
||||
|
||||
@@ -142,6 +142,12 @@ func (s *series) summarize(aggrFunc aggrFunc, startTime, endTime, step int64, xF
|
||||
}
|
||||
|
||||
func execExpr(ec *evalConfig, query string) (nextSeriesFunc, error) {
|
||||
// Validate query length to prevent memory exhaustion
|
||||
maxLen := searchutil.GetMaxQueryLen()
|
||||
if len(query) > maxLen {
|
||||
return nil, fmt.Errorf("too long query; got %d bytes; mustn't exceed `-search.maxQueryLen=%d` bytes", len(query), maxLen)
|
||||
}
|
||||
|
||||
expr, err := graphiteql.Parse(query)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse %q: %w", query, err)
|
||||
|
||||
@@ -4070,6 +4070,9 @@ func TestExecExprFailure(t *testing.T) {
|
||||
|
||||
f(`holtWintersConfidenceArea(group(time("foo.baz",15),time("foo.baz",15)))`)
|
||||
f(`holtWintersConfidenceArea()`)
|
||||
|
||||
// too long query
|
||||
f(`sumSeries(` + strings.Repeat("metric.very.long.name.that.takes.space,", 500) + `metric.final)`)
|
||||
}
|
||||
|
||||
func compareSeries(ss, ssExpected []*series, expr graphiteql.Expr) error {
|
||||
|
||||
@@ -2,6 +2,7 @@ package vmselect
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -67,6 +68,7 @@ func Init() {
|
||||
prometheus.InitMaxUniqueTimeseries(*maxConcurrentRequests)
|
||||
|
||||
concurrencyLimitCh = make(chan struct{}, *maxConcurrentRequests)
|
||||
initVMUIConfig()
|
||||
initVMAlertProxy()
|
||||
}
|
||||
|
||||
@@ -460,6 +462,11 @@ func handleStaticAndSimpleRequests(w http.ResponseWriter, r *http.Request, path
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(path, "/vmui/") {
|
||||
if path == "/vmui/config.json" {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprint(w, vmuiConfig)
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(path, "/vmui/static/") {
|
||||
// Allow clients caching static contents for long period of time, since it shouldn't change over time.
|
||||
// Path to static contents (such as js and css) must be changed whenever its contents is changed.
|
||||
@@ -734,8 +741,34 @@ func proxyVMAlertRequests(w http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
vmalertProxyHost string
|
||||
vmalertProxy *nethttputil.ReverseProxy
|
||||
vmuiConfig string
|
||||
)
|
||||
|
||||
func initVMUIConfig() {
|
||||
var cfg struct {
|
||||
License struct {
|
||||
Type string `json:"type"`
|
||||
} `json:"license"`
|
||||
VMAlert struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
} `json:"vmalert"`
|
||||
}
|
||||
data, err := vmuiFiles.ReadFile("vmui/config.json")
|
||||
if err != nil {
|
||||
logger.Fatalf("cannot read vmui default config: %s", err)
|
||||
}
|
||||
err = json.Unmarshal(data, &cfg)
|
||||
if err != nil {
|
||||
logger.Fatalf("cannot parse vmui default config: %s", err)
|
||||
}
|
||||
cfg.VMAlert.Enabled = len(*vmalertProxyURL) != 0
|
||||
data, err = json.Marshal(&cfg)
|
||||
if err != nil {
|
||||
logger.Fatalf("cannot create vmui config: %s", err)
|
||||
}
|
||||
vmuiConfig = string(data)
|
||||
}
|
||||
|
||||
// initVMAlertProxy must be called after flag.Parse(), since it uses command-line flags.
|
||||
func initVMAlertProxy() {
|
||||
if len(*vmalertProxyURL) == 0 {
|
||||
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httputil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
@@ -38,7 +37,6 @@ var (
|
||||
latencyOffset = flag.Duration("search.latencyOffset", time.Second*30, "The time when data points become visible in query results after the collection. "+
|
||||
"It can be overridden on per-query basis via latency_offset arg. "+
|
||||
"Too small value can result in incomplete last points for query results")
|
||||
maxQueryLen = flagutil.NewBytes("search.maxQueryLen", 16*1024, "The maximum search query length in bytes")
|
||||
maxLookback = flag.Duration("search.maxLookback", 0, "Synonym to -query.lookback-delta from Prometheus. "+
|
||||
"The value is dynamically detected from interval between time series datapoints if not set. It can be overridden on per-query basis via max_lookback arg. "+
|
||||
"See also '-search.maxStalenessInterval' flag, which has the same meaning due to historical reasons")
|
||||
@@ -733,8 +731,9 @@ func QueryHandler(qt *querytracer.Tracer, startTime time.Time, w http.ResponseWr
|
||||
step = defaultStep
|
||||
}
|
||||
|
||||
if len(query) > maxQueryLen.IntN() {
|
||||
return fmt.Errorf("too long query; got %d bytes; mustn't exceed `-search.maxQueryLen=%d` bytes", len(query), maxQueryLen.N)
|
||||
maxLen := searchutil.GetMaxQueryLen()
|
||||
if len(query) > maxLen {
|
||||
return fmt.Errorf("too long query; got %d bytes; mustn't exceed `-search.maxQueryLen=%d` bytes", len(query), maxLen)
|
||||
}
|
||||
etfs, err := searchutil.GetExtraTagFilters(r)
|
||||
if err != nil {
|
||||
@@ -904,8 +903,9 @@ func queryRangeHandler(qt *querytracer.Tracer, startTime time.Time, w http.Respo
|
||||
}
|
||||
|
||||
// Validate input args.
|
||||
if len(query) > maxQueryLen.IntN() {
|
||||
return fmt.Errorf("too long query; got %d bytes; mustn't exceed `-search.maxQueryLen=%d` bytes", len(query), maxQueryLen.N)
|
||||
maxLen := searchutil.GetMaxQueryLen()
|
||||
if len(query) > maxLen {
|
||||
return fmt.Errorf("too long query; got %d bytes; mustn't exceed `-search.maxQueryLen=%d` bytes", len(query), maxLen)
|
||||
}
|
||||
if start > end {
|
||||
end = start + defaultStep
|
||||
|
||||
@@ -7,10 +7,12 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/metricsql"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httputil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
|
||||
"github.com/VictoriaMetrics/metricsql"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -20,6 +22,7 @@ var (
|
||||
maxStatusRequestDuration = flag.Duration("search.maxStatusRequestDuration", time.Minute*5, "The maximum duration for /api/v1/status/* requests")
|
||||
maxLabelsAPIDuration = flag.Duration("search.maxLabelsAPIDuration", time.Second*5, "The maximum duration for /api/v1/labels, /api/v1/label/.../values and /api/v1/series requests. "+
|
||||
"See also -search.maxLabelsAPISeries and -search.ignoreExtraFiltersAtLabelsAPI")
|
||||
maxQueryLen = flagutil.NewBytes("search.maxQueryLen", 16*1024, "The maximum search query length in bytes")
|
||||
)
|
||||
|
||||
// GetMaxQueryDuration returns the maximum duration for query from r.
|
||||
@@ -227,3 +230,8 @@ func toTagFilter(dst *storage.TagFilter, src *metricsql.LabelFilter) {
|
||||
dst.IsRegexp = src.IsRegexp
|
||||
dst.IsNegative = src.IsNegative
|
||||
}
|
||||
|
||||
// GetMaxQueryLen returns the current value of the search.maxQueryLen flag.
|
||||
func GetMaxQueryLen() int {
|
||||
return maxQueryLen.IntN()
|
||||
}
|
||||
|
||||
209
app/vmselect/vmui/assets/index-DY3sj68d.js
Normal file
1
app/vmselect/vmui/assets/index-XlRqIMog.css
Normal file
@@ -36,10 +36,10 @@
|
||||
<meta property="og:title" content="UI for VictoriaMetrics">
|
||||
<meta property="og:url" content="https://victoriametrics.com/">
|
||||
<meta property="og:description" content="Explore and troubleshoot your VictoriaMetrics data">
|
||||
<script type="module" crossorigin src="./assets/index-SqjehVXD.js"></script>
|
||||
<script type="module" crossorigin src="./assets/index-DY3sj68d.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="./assets/vendor-DBOs1yKE.js">
|
||||
<link rel="stylesheet" crossorigin href="./assets/vendor-D1GxaB_c.css">
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-B7vIex3g.css">
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-XlRqIMog.css">
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
@@ -683,7 +683,7 @@ func writeStorageMetrics(w io.Writer, strg *storage.Storage) {
|
||||
metrics.WriteCounterUint64(w, `vm_cache_eviction_bytes_total{type="storage/metricIDs", reason="miss_percentage"}`, m.MetricIDCacheMissEvictionBytes)
|
||||
metrics.WriteCounterUint64(w, `vm_cache_eviction_bytes_total{type="storage/metricIDs", reason="expiration"}`, m.MetricIDCacheExpireEvictionBytes)
|
||||
|
||||
metrics.WriteCounterUint64(w, `vm_deleted_metrics_total{type="indexdb"}`, idbm.DeletedMetricsCount)
|
||||
metrics.WriteCounterUint64(w, `vm_deleted_metrics_total{type="indexdb"}`, m.DeletedMetricsCount)
|
||||
|
||||
metrics.WriteCounterUint64(w, `vm_cache_collisions_total{type="storage/tsid"}`, m.TSIDCacheCollisions)
|
||||
metrics.WriteCounterUint64(w, `vm_cache_collisions_total{type="storage/metricName"}`, m.MetricNameCacheCollisions)
|
||||
|
||||
@@ -6,12 +6,3 @@ export enum AppType {
|
||||
export const APP_TYPE = import.meta.env.VITE_APP_TYPE;
|
||||
export const APP_TYPE_VM = APP_TYPE === AppType.victoriametrics;
|
||||
export const APP_TYPE_ANOMALY = APP_TYPE === AppType.vmanomaly;
|
||||
|
||||
export const isDefaultDatasourceType = (datasourceType: string): boolean => {
|
||||
switch (APP_TYPE) {
|
||||
case AppType.victoriametrics:
|
||||
return datasourceType == "prometheus" || datasourceType == "";
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useEffect, useState } from "preact/compat";
|
||||
import { ErrorTypes } from "../types";
|
||||
import { APP_TYPE_VM } from "../constants/appType";
|
||||
|
||||
const useFetchFlags = () => {
|
||||
const useFetchAppConfig = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
@@ -31,5 +31,5 @@ const useFetchFlags = () => {
|
||||
return { isLoading, error };
|
||||
};
|
||||
|
||||
export default useFetchFlags;
|
||||
export default useFetchAppConfig;
|
||||
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import { useAppDispatch, useAppState } from "../state/common/StateContext";
|
||||
import { useEffect, useState } from "preact/compat";
|
||||
import { ErrorTypes } from "../types";
|
||||
import { APP_TYPE_VM } from "../constants/appType";
|
||||
import { getUrlWithoutTenant } from "../utils/tenants";
|
||||
|
||||
const useFetchFlags = () => {
|
||||
const { serverUrl } = useAppState();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<ErrorTypes | string>("");
|
||||
|
||||
useEffect(() => {
|
||||
const fetchFlags = async () => {
|
||||
if (!serverUrl || !APP_TYPE_VM) return;
|
||||
setError("");
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const url = getUrlWithoutTenant(serverUrl);
|
||||
const response = await fetch(`${url}/flags`);
|
||||
const data = await response.text();
|
||||
const flags = data.split("\n").filter(flag => flag.trim() !== "")
|
||||
.reduce((acc, flag) => {
|
||||
const [keyRaw, valueRaw] = flag.split("=");
|
||||
const key = keyRaw.trim().replace(/^-/, "");
|
||||
acc[key.trim()] = valueRaw ? valueRaw.trim().replace(/^"(.*)"$/, "$1") : null;
|
||||
return acc;
|
||||
}, {} as Record<string, string|null>);
|
||||
dispatch({ type: "SET_FLAGS", payload: flags });
|
||||
} catch (e) {
|
||||
setIsLoading(false);
|
||||
if (e instanceof Error) setError(`${e.name}: ${e.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
fetchFlags();
|
||||
}, [serverUrl]);
|
||||
|
||||
return { isLoading, error };
|
||||
};
|
||||
|
||||
export default useFetchFlags;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import Header from "../Header/Header";
|
||||
import { FC, useEffect } from "preact/compat";
|
||||
import { Outlet, useLocation, useSearchParams } from "react-router-dom";
|
||||
import { Outlet, useSearchParams } from "react-router-dom";
|
||||
import qs from "qs";
|
||||
import "../MainLayout/style.scss";
|
||||
import { getAppModeEnable } from "../../utils/app-mode";
|
||||
import classNames from "classnames";
|
||||
import Footer from "../Footer/Footer";
|
||||
import { routerOptions } from "../../router";
|
||||
import useFetchDefaultTimezone from "../../hooks/useFetchDefaultTimezone";
|
||||
import useDeviceDetect from "../../hooks/useDeviceDetect";
|
||||
import ControlsAnomalyLayout from "./ControlsAnomalyLayout";
|
||||
@@ -14,17 +13,10 @@ import ControlsAnomalyLayout from "./ControlsAnomalyLayout";
|
||||
const AnomalyLayout: FC = () => {
|
||||
const appModeEnable = getAppModeEnable();
|
||||
const { isMobile } = useDeviceDetect();
|
||||
const { pathname } = useLocation();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
useFetchDefaultTimezone();
|
||||
|
||||
const setDocumentTitle = () => {
|
||||
const defaultTitle = "vmui for vmanomaly";
|
||||
const routeTitle = routerOptions[pathname]?.title;
|
||||
document.title = routeTitle ? `${routeTitle} - ${defaultTitle}` : defaultTitle;
|
||||
};
|
||||
|
||||
// for support old links with search params
|
||||
const redirectSearchToHashParams = () => {
|
||||
const { search, href } = window.location;
|
||||
@@ -38,7 +30,6 @@ const AnomalyLayout: FC = () => {
|
||||
if (newHref !== href) window.location.replace(newHref);
|
||||
};
|
||||
|
||||
useEffect(setDocumentTitle, [pathname]);
|
||||
useEffect(redirectSearchToHashParams, []);
|
||||
|
||||
return <section className="vm-container">
|
||||
|
||||
@@ -11,7 +11,6 @@ import { useFetchDashboards } from "../../pages/PredefinedPanels/hooks/useFetchD
|
||||
import useDeviceDetect from "../../hooks/useDeviceDetect";
|
||||
import ControlsMainLayout from "./ControlsMainLayout";
|
||||
import useFetchDefaultTimezone from "../../hooks/useFetchDefaultTimezone";
|
||||
import useFetchFlags from "../../hooks/useFetchFlags";
|
||||
import useFetchAppConfig from "../../hooks/useFetchAppConfig";
|
||||
|
||||
const MainLayout: FC = () => {
|
||||
@@ -23,7 +22,6 @@ const MainLayout: FC = () => {
|
||||
useFetchDashboards();
|
||||
useFetchDefaultTimezone();
|
||||
useFetchAppConfig();
|
||||
useFetchFlags();
|
||||
|
||||
const setDocumentTitle = () => {
|
||||
const defaultTitle = "vmui";
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
align-items: stretch;
|
||||
gap: $padding-medium;
|
||||
max-width: calc(100vw - var(--scrollbar-width));
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
align-items: stretch;
|
||||
gap: $padding-medium;
|
||||
|
||||
@media (max-width: 500px) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { APP_TYPE, AppType } from "../constants/appType";
|
||||
|
||||
const router = {
|
||||
home: "/",
|
||||
metrics: "/metrics",
|
||||
@@ -15,7 +17,6 @@ const router = {
|
||||
rawQuery: "/raw-query",
|
||||
downsamplingDebug: "/downsampling-filters-debug",
|
||||
retentionDebug: "/retention-filters-debug",
|
||||
alerts: "/alerts",
|
||||
rules: "/rules",
|
||||
notifiers: "/notifiers",
|
||||
};
|
||||
@@ -51,11 +52,23 @@ const routerOptionsDefault = {
|
||||
},
|
||||
};
|
||||
|
||||
const getDefaultOptions = (appType: AppType) => {
|
||||
switch (appType) {
|
||||
case AppType.vmanomaly:
|
||||
return {
|
||||
title: "Anomaly exploration",
|
||||
...routerOptionsDefault,
|
||||
};
|
||||
default:
|
||||
return {
|
||||
title: "Query",
|
||||
...routerOptionsDefault,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const routerOptions: { [key: string]: RouterOptions } = {
|
||||
[router.home]: {
|
||||
title: "Query",
|
||||
...routerOptionsDefault,
|
||||
},
|
||||
[router.home]: getDefaultOptions(APP_TYPE),
|
||||
[router.rawQuery]: {
|
||||
title: "Raw query",
|
||||
...routerOptionsDefault,
|
||||
@@ -127,10 +140,7 @@ export const routerOptions: { [key: string]: RouterOptions } = {
|
||||
title: "Icons",
|
||||
header: {},
|
||||
},
|
||||
[router.anomaly]: {
|
||||
title: "Anomaly exploration",
|
||||
...routerOptionsDefault,
|
||||
},
|
||||
[router.anomaly]: getDefaultOptions(AppType.vmanomaly),
|
||||
[router.query]: {
|
||||
title: "Query",
|
||||
...routerOptionsDefault,
|
||||
|
||||
@@ -17,7 +17,7 @@ interface NavigationConfig {
|
||||
serverUrl: string,
|
||||
isEnterpriseLicense: boolean,
|
||||
showPredefinedDashboards: boolean,
|
||||
showAlertLink: boolean,
|
||||
showAlerting: boolean,
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -43,7 +43,7 @@ const getExploreNav = () => [
|
||||
];
|
||||
|
||||
/**
|
||||
* Submenu for Alerts tab
|
||||
* Submenu for Alerting tab
|
||||
*/
|
||||
|
||||
const getAlertingNav = () => [
|
||||
@@ -57,14 +57,14 @@ const getAlertingNav = () => [
|
||||
export const getDefaultNavigation = ({
|
||||
isEnterpriseLicense,
|
||||
showPredefinedDashboards,
|
||||
showAlertLink,
|
||||
showAlerting,
|
||||
}: NavigationConfig): NavigationItem[] => [
|
||||
{ value: router.home },
|
||||
{ value: router.rawQuery },
|
||||
{ label: "Explore", submenu: getExploreNav() },
|
||||
{ label: "Tools", submenu: getToolsNav(isEnterpriseLicense) },
|
||||
{ value: router.dashboards, hide: !showPredefinedDashboards },
|
||||
{ value: "Alerting", submenu: getAlertingNav(), hide: !showAlertLink },
|
||||
{ value: "Alerting", submenu: getAlertingNav(), hide: !showAlerting },
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,17 +9,17 @@ import { APP_TYPE, AppType } from "../constants/appType";
|
||||
const useNavigationMenu = () => {
|
||||
const appModeEnable = getAppModeEnable();
|
||||
const { dashboardsSettings } = useDashboardsState();
|
||||
const { serverUrl, flags, appConfig } = useAppState();
|
||||
const { serverUrl, appConfig } = useAppState();
|
||||
const isEnterpriseLicense = appConfig.license?.type === "enterprise";
|
||||
const showAlertLink = Boolean(flags["vmalert.proxyURL"]);
|
||||
const showAlerting = appConfig?.vmalert?.enabled || false;
|
||||
const showPredefinedDashboards = Boolean(!appModeEnable && dashboardsSettings.length);
|
||||
|
||||
const navigationConfig = useMemo(() => ({
|
||||
serverUrl,
|
||||
isEnterpriseLicense,
|
||||
showAlertLink,
|
||||
showAlerting,
|
||||
showPredefinedDashboards
|
||||
}), [serverUrl, isEnterpriseLicense, showAlertLink, showPredefinedDashboards]);
|
||||
}), [serverUrl, isEnterpriseLicense, showAlerting, showPredefinedDashboards]);
|
||||
|
||||
|
||||
const menu = useMemo(() => {
|
||||
|
||||
@@ -10,7 +10,6 @@ export interface AppState {
|
||||
tenantId: string;
|
||||
theme: Theme;
|
||||
isDarkTheme: boolean | null;
|
||||
flags: Record<string, string | null>;
|
||||
appConfig: AppConfig
|
||||
}
|
||||
|
||||
@@ -18,7 +17,6 @@ export type Action =
|
||||
| { type: "SET_SERVER", payload: string }
|
||||
| { type: "SET_THEME", payload: Theme }
|
||||
| { type: "SET_TENANT_ID", payload: string }
|
||||
| { type: "SET_FLAGS", payload: Record<string, string | null> }
|
||||
| { type: "SET_APP_CONFIG", payload: AppConfig }
|
||||
| { type: "SET_DARK_THEME" }
|
||||
|
||||
@@ -29,7 +27,6 @@ export const initialState: AppState = {
|
||||
tenantId,
|
||||
theme: (getFromStorage("THEME") || Theme.system) as Theme,
|
||||
isDarkTheme: null,
|
||||
flags: {},
|
||||
appConfig: {}
|
||||
};
|
||||
|
||||
@@ -56,11 +53,6 @@ export function reducer(state: AppState, action: Action): AppState {
|
||||
...state,
|
||||
isDarkTheme: isDarkTheme(state.theme)
|
||||
};
|
||||
case "SET_FLAGS":
|
||||
return {
|
||||
...state,
|
||||
flags: action.payload
|
||||
};
|
||||
case "SET_APP_CONFIG":
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -180,6 +180,9 @@ export interface AppConfig {
|
||||
license?: {
|
||||
type?: "enterprise" | "opensource";
|
||||
};
|
||||
vmalert?: {
|
||||
enabled: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface Group {
|
||||
|
||||
@@ -7,7 +7,8 @@ ROOT_IMAGE ?= alpine:3.22.1
|
||||
ROOT_IMAGE_SCRATCH ?= scratch
|
||||
CERTS_IMAGE := alpine:3.22.1
|
||||
|
||||
GO_BUILDER_IMAGE := golang:1.25.0-alpine
|
||||
GO_BUILDER_IMAGE := golang:1.25.0
|
||||
|
||||
BUILDER_IMAGE := local/builder:2.0.0-$(shell echo $(GO_BUILDER_IMAGE) | tr :/ __)-1
|
||||
BASE_IMAGE := local/base:1.1.4-$(shell echo $(ROOT_IMAGE) | tr :/ __)-$(shell echo $(CERTS_IMAGE) | tr :/ __)
|
||||
DOCKER ?= docker
|
||||
@@ -43,7 +44,7 @@ app-via-docker: package-builder
|
||||
$(BUILDER_IMAGE) \
|
||||
go build $(RACE) -trimpath -buildvcs=false \
|
||||
-ldflags "-extldflags '-static' $(GO_BUILDINFO)" \
|
||||
-tags 'netgo osusergo musl $(EXTRA_GO_BUILD_TAGS)' \
|
||||
-tags 'netgo osusergo $(EXTRA_GO_BUILD_TAGS)' \
|
||||
-o bin/$(APP_NAME)$(APP_SUFFIX)-prod $(PKG_PREFIX)/app/$(APP_NAME)
|
||||
|
||||
app-via-docker-windows: package-builder
|
||||
@@ -158,11 +159,11 @@ app-via-docker-pure:
|
||||
APP_SUFFIX='-pure' DOCKER_OPTS='--env CGO_ENABLED=0' $(MAKE) app-via-docker
|
||||
|
||||
app-via-docker-linux-amd64:
|
||||
EXTRA_DOCKER_ENVS='CC=/opt/cross-builder/x86_64-linux-musl-cross/bin/x86_64-linux-musl-gcc' \
|
||||
EXTRA_DOCKER_ENVS='CC=x86_64-linux-gnu-gcc' \
|
||||
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 $(MAKE) app-via-docker-goos-goarch
|
||||
|
||||
app-via-docker-linux-arm64:
|
||||
EXTRA_DOCKER_ENVS='CC=/opt/cross-builder/aarch64-linux-musl-cross/bin/aarch64-linux-musl-gcc' \
|
||||
EXTRA_DOCKER_ENVS='CC=aarch64-linux-gnu-gcc' \
|
||||
CGO_ENABLED=1 GOOS=linux GOARCH=arm64 $(MAKE) app-via-docker-goos-goarch
|
||||
|
||||
app-via-docker-linux-arm:
|
||||
@@ -201,11 +202,11 @@ package-via-docker-pure:
|
||||
APP_SUFFIX='-pure' DOCKER_OPTS='--env CGO_ENABLED=0' $(MAKE) package-via-docker
|
||||
|
||||
package-via-docker-amd64:
|
||||
EXTRA_DOCKER_ENVS='CC=/opt/cross-builder/x86_64-linux-musl-cross/bin/x86_64-linux-musl-gcc' \
|
||||
EXTRA_DOCKER_ENVS='CC=x86_64-linux-gnu-gcc' \
|
||||
CGO_ENABLED=1 GOARCH=amd64 $(MAKE) package-via-docker-goarch
|
||||
|
||||
package-via-docker-arm64:
|
||||
EXTRA_DOCKER_ENVS='CC=/opt/cross-builder/aarch64-linux-musl-cross/bin/aarch64-linux-musl-gcc' \
|
||||
EXTRA_DOCKER_ENVS='CC=aarch64-linux-gnu-gcc' \
|
||||
CGO_ENABLED=1 GOARCH=arm64 $(MAKE) package-via-docker-goarch
|
||||
|
||||
package-via-docker-arm:
|
||||
|
||||
@@ -40,7 +40,11 @@ The communication scheme between components is the following:
|
||||
and recording rules results back to `vmagent`;
|
||||
* [alertmanager](#alertmanager) is configured to receive notifications from `vmalert`.
|
||||
|
||||
<img alt="VictoriaMetrics single-server deployment" width="500" src="assets/vm-single-server.png">
|
||||
<picture>
|
||||
<source srcset="assets/vm-single-server-dark.png" media="(prefers-color-scheme: dark)">
|
||||
<source srcset="assets/vm-single-server-light.png" media="(prefers-color-scheme: light)">
|
||||
<img src="assets/vm-single-server-light.png" alt="VictoriaMetrics single-server deployment" width="500" >
|
||||
</picture>
|
||||
|
||||
To access Grafana use link [http://localhost:3000](http://localhost:3000).
|
||||
|
||||
@@ -78,7 +82,11 @@ The communication scheme between components is the following:
|
||||
and recording rules to `vmagent`;
|
||||
* [alertmanager](#alertmanager) is configured to receive notifications from `vmalert`.
|
||||
|
||||
<img alt="VictoriaMetrics cluster deployment" width="500" src="assets/vm-cluster.png">
|
||||
<picture>
|
||||
<source srcset="assets/vm-cluster-dark.png" media="(prefers-color-scheme: dark)">
|
||||
<source srcset="assets/vm-cluster-light.png" media="(prefers-color-scheme: light)">
|
||||
<img src="assets/vm-cluster-light.png" alt="VictoriaMetrics cluster deployment" width="500" src="assets/vm-cluster-light.png" >
|
||||
</picture>
|
||||
|
||||
To access Grafana use link [http://localhost:3000](http://localhost:3000).
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 99 KiB |
BIN
deployment/docker/assets/vm-cluster-dark.png
Normal file
|
After Width: | Height: | Size: 156 KiB |
BIN
deployment/docker/assets/vm-cluster-light.png
Normal file
|
After Width: | Height: | Size: 154 KiB |
|
Before Width: | Height: | Size: 152 KiB |
BIN
deployment/docker/assets/vm-single-server-dark.png
Normal file
|
After Width: | Height: | Size: 93 KiB |
BIN
deployment/docker/assets/vm-single-server-light.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 93 KiB |
@@ -1,5 +1,5 @@
|
||||
# balance load among vmselects
|
||||
# see https://docs.victoriametrics.com/victoriametrics/vmauth/#load-balancing
|
||||
# balance load among vminsert and vmselect instances,
|
||||
# see https://docs.victoriametrics.com/victoriametrics/vmauth/#load-balancing.
|
||||
# Note: if username and password are changes, please update the Grafana datasource configuration
|
||||
# and check other places where these credentials are used.
|
||||
users:
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
ARG go_builder_image=non-existing
|
||||
FROM $go_builder_image
|
||||
STOPSIGNAL SIGINT
|
||||
RUN apk add git gcc musl-dev make wget --no-cache && \
|
||||
mkdir /opt/cross-builder && \
|
||||
cd /opt/cross-builder && \
|
||||
for arch in aarch64 x86_64; do \
|
||||
wget \
|
||||
https://github.com/VictoriaMetrics/muslcc-mirror/releases/download/v1.0.0/${arch}-linux-musl-cross.tgz \
|
||||
-O /opt/cross-builder/${arch}-musl.tgz \
|
||||
--no-verbose && \
|
||||
tar zxf ${arch}-musl.tgz -C ./ && \
|
||||
rm /opt/cross-builder/${arch}-musl.tgz; \
|
||||
done
|
||||
RUN apt update && apt install -y \
|
||||
gcc-x86-64-linux-gnu \
|
||||
gcc-aarch64-linux-gnu
|
||||
|
||||
@@ -14,10 +14,12 @@ services:
|
||||
command:
|
||||
- "--promscrape.config=/etc/prometheus/prometheus.yml"
|
||||
- "--remoteWrite.url=http://vmauth:8427/insert/0/prometheus/api/v1/write"
|
||||
- "--remoteWrite.basicAuth.username=foo"
|
||||
- "--remoteWrite.basicAuth.password=bar"
|
||||
restart: always
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana:12.0.2
|
||||
image: grafana/grafana:12.1.1
|
||||
depends_on:
|
||||
- "vmauth"
|
||||
ports:
|
||||
|
||||
@@ -38,7 +38,7 @@ services:
|
||||
restart: always
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana:12.0.2
|
||||
image: grafana/grafana:12.1.1
|
||||
depends_on:
|
||||
- "victoriametrics"
|
||||
ports:
|
||||
|
||||
@@ -17,7 +17,8 @@ scrape_configs:
|
||||
- job_name: vminsert
|
||||
static_configs:
|
||||
- targets:
|
||||
- vminsert:8480
|
||||
- vminsert-1:8480
|
||||
- vminsert-2:8480
|
||||
- job_name: vmselect
|
||||
static_configs:
|
||||
- targets:
|
||||
|
||||
@@ -27,7 +27,7 @@ services:
|
||||
restart: always
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana:12.0.2
|
||||
image: grafana/grafana:12.1.1
|
||||
depends_on:
|
||||
- "victoriametrics"
|
||||
ports:
|
||||
|
||||
@@ -58,7 +58,7 @@ services:
|
||||
- ./vmsingle/promscrape.yml:/promscrape.yml
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana:12.0.2
|
||||
image: grafana/grafana:12.1.1
|
||||
depends_on: [vmsingle]
|
||||
ports:
|
||||
- 3000:3000
|
||||
|
||||
@@ -97,6 +97,7 @@ See also [case studies](https://docs.victoriametrics.com/victoriametrics/casestu
|
||||
* [Building a Scalable and Cost-Effective Monitoring Stack on AWS with Victoria Metrics](https://medium.com/@heliodevhub/construindo-uma-stack-de-monitoramento-escal%C3%A1vel-e-econ%C3%B4mica-na-aws-com-victoria-metrics-532d535dcfb6)
|
||||
* [Optimizing Datadog Costs with VictoriaMetrics: A Practical Step-by-Step Guide](https://medium.com/p/optimizing-datadog-costs-with-victoriametrics-a-practical-step-by-step-guide-c984d32c7423)
|
||||
* [Kubernetes Monitoring — A Complete Solution](https://itnext.io/kubernetes-monitoring-a-complete-solution-part-1-architecture-eb5b998658d5)
|
||||
* [Why VictoriaMetrics? A Practical Guide to Scalable and Faster Monitoring Than Prometheus](https://amir-shams.medium.com/why-victoriametrics-a-practical-guide-to-scalable-and-faster-monitoring-than-prometheus-54ef21f10465)
|
||||
|
||||
## Third-party articles and slides about VictoriaLogs
|
||||
|
||||
|
||||
@@ -24,15 +24,23 @@ See also [LTS releases](https://docs.victoriametrics.com/victoriametrics/lts-rel
|
||||
|
||||
## tip
|
||||
|
||||
## [v1.125.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.125.0)
|
||||
|
||||
Released at 2025-08-29
|
||||
|
||||
* FEATURE: [vmsingle](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/) and [vmselect](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/) in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): apply `-search.maxQueryLen` limit to Graphite queries. Previously, this limit was only applied to Prometheus queries.
|
||||
* FEATURE: upgrade Go builder from Go1.24.6 to Go1.25. See [Go1.25 release notes](https://tip.golang.org/doc/go1.25).
|
||||
* FEATURE: [vmui](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#vmui): add export functionality for Query (Table view) and RawQuery tabs in CSV/JSON format. See [#9332](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9332).
|
||||
* FEATURE: [vmui](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#vmui): replace `Alerts` tab with `Alerting` tab in vmui. The new `Alerting` tab displays vmalert groups and rules directly in vmui interface without redirecting user to vmalert's WEB UI. Links of format `.*/prometheus/vmalert/.*` will continue working by redirecting to vmalert's UI. This functionality is available only if `-vmalert.proxyURL` is set on vmselect. Some functionality of the new `Alerting` tab requires vmalert to be of the same version as vmselect, or higher.
|
||||
* FEATURE: [vmalert](https://docs.victoriametrics.com/victoriametrics/vmalert/): add `/api/v1/group?group_id=<id>` API endpoint for viewing details of a specific [rules group](https://docs.victoriametrics.com/vmalert.html#groups). The new handler is used by new `Alerting` tab in vmselect.
|
||||
* FEATURE: [vmalert](https://docs.victoriametrics.com/victoriametrics/vmalert/): add `lastError` field to `/api/v1/notifiers` response. The new field contains an error message if the last attempt to send data to the notifier failed. The error can be also viewed in `Alerting. Notifiers` tab.
|
||||
* FEATURE: [vmsingle](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/) and `vmstorage` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): optimize `/api/v1/labels` and `/api/v1/label/TAG/values` requests with a single `match` or `extra_filters` filter containing timeseries metric name. See this PR [#9489](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/9489) for details.
|
||||
|
||||
* BUGFIX: [vmagent](https://docs.victoriametrics.com/victoriametrics/vmagent/): prevent remote write ingestion stop on push error for [Google Pub/Sub](https://docs.victoriametrics.com/victoriametrics/vmagent/#writing-metrics-to-pubsub) integration.
|
||||
* BUGFIX: [vmauth](https://docs.victoriametrics.com/victoriametrics/vmauth/): properly handle [mTLS authorization and routing](https://docs.victoriametrics.com/victoriametrics/vmauth/#mtls-based-request-routing). Previously it didn't work. See [#29](https://github.com/VictoriaMetrics/VictoriaLogs/issues/29).
|
||||
* BUGFIX: [MetricsQL](https://docs.victoriametrics.com/victoriametrics/metricsql/): fix `timestamp` function compatibility with Prometheus when used with sub-expressions such as `timestamp(sum(foo))`. The fix applies only when `-search.disableImplicitConversion` flag is set. See more in [#9527-comment](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9527#issuecomment-3200646020) and [metricsql#55](https://github.com/VictoriaMetrics/metricsql/pull/55).
|
||||
* BUGFIX: [vmsingle](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/) and `vmstorage` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): optimize subtract operation on uint64 sets. This should potentially improve index search with huge number of deleted series. See this issue [9602](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9602) for details.
|
||||
* BUGFIX: all VictoriaMetrics [enterprise](https://docs.victoriametrics.com/enterprise/) components: fix support for automatic issuing of TLS certificates for HTTPS server at `-httpListenAddr` via [Let's Encrypt service](https://letsencrypt.org/). See [these docs](https://docs.victoriametrics.com/#automatic-issuing-of-tls-certificates).
|
||||
|
||||
## [v1.124.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.124.0)
|
||||
|
||||
|
||||
@@ -515,9 +515,9 @@ scrape_configs:
|
||||
|
||||
One of the following roles can be configured to discover targets:
|
||||
|
||||
* `role: services`
|
||||
### Dockerswarm role: services
|
||||
|
||||
The `services` role discovers all Swarm services.
|
||||
The `role: services` discovers all Swarm services.
|
||||
|
||||
Each discovered target has an [`__address__`](https://docs.victoriametrics.com/victoriametrics/relabeling/#how-to-modify-scrape-urls-in-targets) label set
|
||||
to `<ip>:<port>`, where `<ip>` is the endpoint's virtual IP, while the `<port>` is the published port of the service.
|
||||
@@ -542,9 +542,9 @@ One of the following roles can be configured to discover targets:
|
||||
* `__meta_dockerswarm_network_label_<labelname>`: each label of the network
|
||||
* `__meta_dockerswarm_network_scope`: the scope of the network
|
||||
|
||||
* `role: tasks`
|
||||
### Dockerswarm role: tasks
|
||||
|
||||
The `tasks` role discovers all Swarm tasks.
|
||||
The `role: tasks` discovers all Swarm tasks.
|
||||
|
||||
Each discovered target has an [`__address__`](https://docs.victoriametrics.com/victoriametrics/relabeling/#how-to-modify-scrape-urls-in-targets) label set
|
||||
to `<ip>:<port>`, where the `<ip>` is the node IP, while the `<port>` is the published port of the task.
|
||||
@@ -583,9 +583,9 @@ One of the following roles can be configured to discover targets:
|
||||
|
||||
The `__meta_dockerswarm_network_*` meta labels are not populated for ports which are published with `mode=host`.
|
||||
|
||||
* `role: nodes`
|
||||
### Dockerswarm role: nodes
|
||||
|
||||
The `nodes` role is used to discover Swarm nodes.
|
||||
The `role: nodes` is used to discover Swarm nodes.
|
||||
|
||||
Each discovered target has an [`__address__`](https://docs.victoriametrics.com/victoriametrics/relabeling/#how-to-modify-scrape-urls-in-targets) label set
|
||||
to `<ip>:<port>`, where `<ip>` is the node IP, while the `<port>` is the `port` value obtained from the `dockerswarm_sd_configs`.
|
||||
@@ -1072,7 +1072,7 @@ See [these examples](https://docs.victoriametrics.com/victoriametrics/scrape_con
|
||||
|
||||
One of the following `role` types can be configured to discover targets:
|
||||
|
||||
* `role: node`
|
||||
### Kubernetes role: node
|
||||
|
||||
The `role: node` discovers one target per cluster node.
|
||||
|
||||
@@ -1093,7 +1093,7 @@ One of the following `role` types can be configured to discover targets:
|
||||
|
||||
In addition, the `instance` label for the node will be set to the node name as retrieved from the API server.
|
||||
|
||||
* `role: service`
|
||||
### Kubernetes role: service
|
||||
|
||||
The `role: service` discovers Kubernetes services.
|
||||
|
||||
@@ -1120,7 +1120,7 @@ One of the following `role` types can be configured to discover targets:
|
||||
* `__meta_kubernetes_service_port_protocol`: Protocol of the service port for the target.
|
||||
* `__meta_kubernetes_service_type`: The type of the service.
|
||||
|
||||
* `role: pod`
|
||||
### Kubernetes role: pod
|
||||
|
||||
The `role: pod` discovers all pods and exposes their containers as targets.
|
||||
|
||||
@@ -1153,8 +1153,7 @@ One of the following `role` types can be configured to discover targets:
|
||||
* `__meta_kubernetes_pod_controller_kind`: Object kind of the pod controller.
|
||||
* `__meta_kubernetes_pod_controller_name`: Name of the pod controller.
|
||||
|
||||
|
||||
* `role: endpoints`
|
||||
### Kubernetes role: endpoints
|
||||
|
||||
The `role: endpoints` discovers targets from listed endpoints of a service.
|
||||
|
||||
@@ -1180,10 +1179,10 @@ One of the following `role` types can be configured to discover targets:
|
||||
* `__meta_kubernetes_endpoint_address_target_kind`: Kind of the endpoint address target.
|
||||
* `__meta_kubernetes_endpoint_address_target_name`: Name of the endpoint address target.
|
||||
|
||||
If the endpoints belong to a service, all labels of the `role: service` are attached.
|
||||
For all targets backed by a pod, all labels of the `role: pod` are attached.
|
||||
If the endpoints belong to a service, all labels of the [`role: service`](#kubernetes-role-service) are attached.
|
||||
For all targets backed by a pod, all labels of the [`role: pod`](#kubernetes-role-pod) are attached.
|
||||
|
||||
* `role: endpointslice`
|
||||
### Kubernetes role: endpointslice
|
||||
|
||||
The `role: endpointslice` discovers targets from existing endpointslices.
|
||||
|
||||
@@ -1209,10 +1208,10 @@ One of the following `role` types can be configured to discover targets:
|
||||
* `__meta_kubernetes_endpointslice_port_name`: Named port of the referenced endpoint.
|
||||
* `__meta_kubernetes_endpointslice_port_protocol`: Protocol of the referenced endpoint.
|
||||
|
||||
If the endpoints belong to a service, all labels of the `role: service` are attached.
|
||||
For all targets backed by a pod, all labels of the `role: pod` are attached.
|
||||
If the endpoints belong to a service, all labels of the [`role: service`](#kubernetes-role-service) are attached.
|
||||
For all targets backed by a pod, all labels of the [`role: pod`](#kubernetes-role-pod) are attached.
|
||||
|
||||
* `role: ingress`
|
||||
### Kubernetes role: ingress
|
||||
|
||||
The `role: ingress` discovers a target for each path of each ingress.
|
||||
|
||||
@@ -1469,9 +1468,9 @@ scrape_configs:
|
||||
# ...
|
||||
```
|
||||
|
||||
One of the following `role` types can be configured to discover targets:
|
||||
One of the following `role` types can be configured to discover OpenStack targets:
|
||||
|
||||
* `role: hypervisor`
|
||||
### OpenStack role: hypervisor
|
||||
|
||||
The `role: hypervisor` discovers one target per Nova hypervisor node.
|
||||
|
||||
@@ -1487,7 +1486,7 @@ One of the following `role` types can be configured to discover targets:
|
||||
* `__meta_openstack_hypervisor_status`: the hypervisor node's status.
|
||||
* `__meta_openstack_hypervisor_type`: the hypervisor node's type.
|
||||
|
||||
* `role: instance`
|
||||
### OpenStack role: instance
|
||||
|
||||
The `role: instance` discovers one target per network interface of Nova instance.
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/common"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/fscommon"
|
||||
libfs "github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
)
|
||||
|
||||
@@ -103,7 +102,7 @@ func (fs *FS) CopyPart(srcFS common.OriginFS, p common.Part) error {
|
||||
}
|
||||
// Attempt to create hardlink from srcPath to dstPath.
|
||||
if err := os.Link(srcPath, dstPath); err == nil {
|
||||
libfs.MustSyncPath(dstPath)
|
||||
// There is no need in fsync for dstPath, since its contents must be already synced at srcPath.
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
package configwatcher
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/procutil"
|
||||
)
|
||||
|
||||
// TBD: migrate /-/reload handler to the package
|
||||
// TBD: print registered configs in 10s after the start
|
||||
// TBD: print what reload methods enabled
|
||||
|
||||
type handler struct {
|
||||
flag string
|
||||
handler func()
|
||||
}
|
||||
|
||||
var configCheckInterval = flag.Duration("configCheckInterval", 0, "TBD")
|
||||
|
||||
var signalHandlers []handler
|
||||
|
||||
var checkIntervalHandlers []handler
|
||||
|
||||
var mux = sync.Mutex{}
|
||||
|
||||
func RegisterHandler(flag string, handlerFn func()) {
|
||||
RegisterSignalHandler(flag, handlerFn)
|
||||
RegisterCheckIntervalHandler(flag, handlerFn)
|
||||
}
|
||||
|
||||
func RegisterSignalHandler(flag string, handlerFn func()) {
|
||||
mux.Lock()
|
||||
defer mux.Unlock()
|
||||
|
||||
signalHandlers = append(signalHandlers, handler{
|
||||
flag: flag,
|
||||
handler: handlerFn,
|
||||
})
|
||||
}
|
||||
|
||||
func RegisterCheckIntervalHandler(flag string, handlerFn func()) {
|
||||
mux.Lock()
|
||||
defer mux.Unlock()
|
||||
|
||||
checkIntervalHandlers = append(checkIntervalHandlers, handler{
|
||||
flag: flag,
|
||||
handler: handlerFn,
|
||||
})
|
||||
}
|
||||
|
||||
func UnregisterHandler(flag string) {
|
||||
mux.Lock()
|
||||
defer mux.Unlock()
|
||||
|
||||
newCheckIntervalHandlers := make([]handler, 0, len(checkIntervalHandlers))
|
||||
for _, h := range checkIntervalHandlers {
|
||||
if h.flag != flag {
|
||||
newCheckIntervalHandlers = append(newCheckIntervalHandlers, h)
|
||||
}
|
||||
}
|
||||
newSignalHandlers := make([]handler, 0, len(signalHandlers))
|
||||
for _, h := range signalHandlers {
|
||||
if h.flag != flag {
|
||||
newSignalHandlers = append(newSignalHandlers, h)
|
||||
}
|
||||
}
|
||||
|
||||
checkIntervalHandlers = newCheckIntervalHandlers
|
||||
}
|
||||
|
||||
var stopChan chan struct{}
|
||||
|
||||
func Init() {
|
||||
stopChan = make(chan struct{})
|
||||
go func() {
|
||||
sighupCh := procutil.NewSighupChan()
|
||||
|
||||
var tickerCh <-chan time.Time
|
||||
if *configCheckInterval > 0 {
|
||||
ticker := time.NewTicker(*configCheckInterval)
|
||||
tickerCh = ticker.C
|
||||
defer ticker.Stop()
|
||||
}
|
||||
for {
|
||||
|
||||
select {
|
||||
case <-sighupCh:
|
||||
mux.Lock()
|
||||
for _, h := range signalHandlers {
|
||||
h.handler()
|
||||
}
|
||||
mux.Unlock()
|
||||
case <-tickerCh:
|
||||
mux.Lock()
|
||||
for _, h := range checkIntervalHandlers {
|
||||
h.handler()
|
||||
}
|
||||
mux.Unlock()
|
||||
case <-stopChan:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Method for BC
|
||||
func EnableCheckInterval(dur time.Duration) {
|
||||
mux.Lock()
|
||||
defer mux.Unlock()
|
||||
|
||||
if dur > *configCheckInterval {
|
||||
*configCheckInterval = dur
|
||||
}
|
||||
}
|
||||
|
||||
// Stop stops Prometheus scraper.
|
||||
func Stop() {
|
||||
close(stopChan)
|
||||
}
|
||||
33
lib/fs/fs.go
@@ -107,31 +107,32 @@ func IsTemporaryFileName(fn string) bool {
|
||||
var tmpFileNameRe = regexp.MustCompile(`\.tmp\.\d+$`)
|
||||
|
||||
// MustMkdirIfNotExist creates the given path dir if it isn't exist.
|
||||
//
|
||||
// The caller is responsible for MustSyncPath() call for the parent directory for the path.
|
||||
func MustMkdirIfNotExist(path string) {
|
||||
if IsPathExist(path) {
|
||||
return
|
||||
}
|
||||
mustMkdirSync(path)
|
||||
mustMkdir(path)
|
||||
}
|
||||
|
||||
// MustMkdirFailIfExist creates the given path dir if it isn't exist.
|
||||
//
|
||||
// If the directory at the given path already exists, then the function logs the error and exits.
|
||||
// If the directory at the given path already exists, then the function logs the fatal error and exits the process.
|
||||
//
|
||||
// The caller is responsible for MustSyncPath() call for the parent directory for the path.
|
||||
func MustMkdirFailIfExist(path string) {
|
||||
if IsPathExist(path) {
|
||||
logger.Panicf("FATAL: the %q already exists", path)
|
||||
}
|
||||
mustMkdirSync(path)
|
||||
mustMkdir(path)
|
||||
}
|
||||
|
||||
func mustMkdirSync(path string) {
|
||||
func mustMkdir(path string) {
|
||||
if err := os.MkdirAll(path, 0755); err != nil {
|
||||
logger.Panicf("FATAL: cannot create directory: %s", err)
|
||||
}
|
||||
// Sync the parent directory, so the created directory becomes visible
|
||||
// in the fs after power loss.
|
||||
parentDirPath := filepath.Dir(path)
|
||||
MustSyncPath(parentDirPath)
|
||||
// Do not sync the parent directory - this is the responsibility of the caller.
|
||||
}
|
||||
|
||||
// MustClose must close the given file f.
|
||||
@@ -174,9 +175,11 @@ func MustReadDir(dir string) []os.DirEntry {
|
||||
return des
|
||||
}
|
||||
|
||||
// MustHardLinkFiles makes hard links for all the files from srcDir in dstDir.
|
||||
// MustHardLinkFiles creates dstDir and makes hard links for all the files from srcDir in dstDir.
|
||||
//
|
||||
// The caller is responsible for calling MustSyncPath for the parent directory of dstDir.
|
||||
func MustHardLinkFiles(srcDir, dstDir string) {
|
||||
mustMkdirSync(dstDir)
|
||||
mustMkdir(dstDir)
|
||||
|
||||
des := MustReadDir(srcDir)
|
||||
for _, de := range des {
|
||||
@@ -196,6 +199,8 @@ func MustHardLinkFiles(srcDir, dstDir string) {
|
||||
}
|
||||
|
||||
// MustSymlinkRelative creates relative symlink for srcPath in dstPath.
|
||||
//
|
||||
// The caller is responsible for calling MustSyncPath() for the parent directory of dstPath.
|
||||
func MustSymlinkRelative(srcPath, dstPath string) {
|
||||
baseDir := filepath.Dir(dstPath)
|
||||
srcPathRel, err := filepath.Rel(baseDir, srcPath)
|
||||
@@ -207,10 +212,13 @@ func MustSymlinkRelative(srcPath, dstPath string) {
|
||||
}
|
||||
}
|
||||
|
||||
// MustCopyDirectory copies all the files in srcPath to dstPath.
|
||||
// MustCopyDirectory creates dstPath and copies all the files in srcPath to dstPath.
|
||||
//
|
||||
// The caller is responsible for calling MustSyncPath() for the parent directory of dstPath.
|
||||
func MustCopyDirectory(srcPath, dstPath string) {
|
||||
mustMkdir(dstPath)
|
||||
|
||||
des := MustReadDir(srcPath)
|
||||
MustMkdirIfNotExist(dstPath)
|
||||
for _, de := range des {
|
||||
if !de.Type().IsRegular() {
|
||||
// Skip non-files
|
||||
@@ -220,6 +228,7 @@ func MustCopyDirectory(srcPath, dstPath string) {
|
||||
dst := filepath.Join(dstPath, de.Name())
|
||||
MustCopyFile(src, dst)
|
||||
}
|
||||
|
||||
MustSyncPath(dstPath)
|
||||
}
|
||||
|
||||
|
||||
@@ -351,12 +351,15 @@ func MustOpenTable(path string, flushInterval time.Duration, flushCallback func(
|
||||
flushInterval = pendingItemsFlushInterval
|
||||
}
|
||||
|
||||
// Create a directory for the table if it doesn't exist yet.
|
||||
// Create a directory at the path if it doesn't exist yet.
|
||||
fs.MustMkdirIfNotExist(path)
|
||||
|
||||
// Open table parts.
|
||||
pws := mustOpenParts(path)
|
||||
|
||||
// Sync the path and the parent dir, so the path becomes visible in the parent dir.
|
||||
fs.MustSyncPathAndParentDir(path)
|
||||
|
||||
tb := &Table{
|
||||
path: path,
|
||||
flushInterval: flushInterval,
|
||||
@@ -1481,9 +1484,6 @@ func (tb *Table) nextMergeIdx() uint64 {
|
||||
}
|
||||
|
||||
func mustOpenParts(path string) []*partWrapper {
|
||||
// The path can be missing after restoring from backup, so create it if needed.
|
||||
fs.MustMkdirIfNotExist(path)
|
||||
|
||||
// Remove txn and tmp directories, which may be left after the upgrade
|
||||
// to v1.90.0 and newer versions.
|
||||
fs.MustRemoveDir(filepath.Join(path, "txn"))
|
||||
@@ -1522,7 +1522,6 @@ func mustOpenParts(path string) []*partWrapper {
|
||||
fs.MustRemoveDir(deletePath)
|
||||
}
|
||||
}
|
||||
fs.MustSyncPath(path)
|
||||
|
||||
// Open parts
|
||||
var pws []*partWrapper
|
||||
@@ -1594,9 +1593,7 @@ func (tb *Table) MustCreateSnapshotAt(dstDir string) {
|
||||
fs.MustHardLinkFiles(srcPartPath, dstPartPath)
|
||||
}
|
||||
|
||||
fs.MustSyncPath(dstDir)
|
||||
parentDir := filepath.Dir(dstDir)
|
||||
fs.MustSyncPath(parentDir)
|
||||
fs.MustSyncPathAndParentDir(dstDir)
|
||||
|
||||
logger.Infof("created Table snapshot of %q at %q in %.3f seconds", srcDir, dstDir, time.Since(startTime).Seconds())
|
||||
}
|
||||
|
||||
@@ -178,6 +178,7 @@ func tryOpeningQueue(path, name string, chunkFileSize, maxBlockSize, maxPendingB
|
||||
fs.MustClose(q.flockF)
|
||||
}
|
||||
}()
|
||||
fs.MustSyncPathAndParentDir(path)
|
||||
|
||||
// Read metainfo.
|
||||
var mi metainfo
|
||||
@@ -643,7 +644,6 @@ func (mi *metainfo) WriteToFile(path string) error {
|
||||
return fmt.Errorf("cannot marshal persistent queue metainfo %#v: %w", mi, err)
|
||||
}
|
||||
fs.MustWriteSync(path, data)
|
||||
fs.MustSyncPath(path)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
19
lib/prommetadata/metadata.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package prommetadata
|
||||
|
||||
import "flag"
|
||||
|
||||
var enableMetadata = flag.Bool("enableMetadata", false, "Whether to enable metadata processing for metrics scraped from targets, received via VictoriaMetrics remote write, Prometheus remote write v1 or OpenTelemetry protocol. "+
|
||||
"See also remoteWrite.maxMetadataPerBlock")
|
||||
|
||||
// IsEnabled reports whether metadata processing is enabled.
|
||||
func IsEnabled() bool {
|
||||
return *enableMetadata
|
||||
}
|
||||
|
||||
// SetEnabled sets enableMetadata to v and returns the previous value of enableMetadata.
|
||||
// This function is intended for promscrape tests.
|
||||
func SetEnabled(v bool) bool {
|
||||
prev := *enableMetadata
|
||||
*enableMetadata = v
|
||||
return prev
|
||||
}
|
||||
@@ -52,8 +52,6 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
enableMetadata = flag.Bool("enableMetadata", false, "Whether to enable metadata processing for metrics scraped from targets, received via VictoriaMetrics remote write, Prometheus remote write v1 or OpenTelemetry protocol. "+
|
||||
"See also remoteWrite.maxMetadataPerBlock")
|
||||
noStaleMarkers = flag.Bool("promscrape.noStaleMarkers", false, "Whether to disable sending Prometheus stale markers for metrics when scrape target disappears. This option may reduce memory usage if stale markers aren't needed for your setup. This option also disables populating the scrape_series_added metric. See https://prometheus.io/docs/concepts/jobs_instances/#automatically-generated-labels-and-time-series")
|
||||
seriesLimitPerTarget = flag.Int("promscrape.seriesLimitPerTarget", 0, "Optional limit on the number of unique time series a single scrape target can expose. See https://docs.victoriametrics.com/victoriametrics/vmagent/#cardinality-limiter for more info")
|
||||
strictParse = flag.Bool("promscrape.config.strictParse", true, "Whether to deny unsupported fields in -promscrape.config . Set to false in order to silently skip unsupported fields")
|
||||
@@ -90,11 +88,6 @@ var (
|
||||
|
||||
var clusterMemberID int
|
||||
|
||||
// IsMetadataEnabled returns true if metadata is enabled.
|
||||
func IsMetadataEnabled() bool {
|
||||
return *enableMetadata
|
||||
}
|
||||
|
||||
func mustInitClusterMemberID() {
|
||||
s := *clusterMemberNum
|
||||
// special case for kubernetes deployment, where pod-name formatted at some-pod-name-1
|
||||
|
||||
@@ -9,12 +9,12 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/configwatcher"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/auth"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/procutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/azure"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/consul"
|
||||
@@ -113,7 +113,7 @@ func runScraper(configFile string, pushData func(at *auth.Token, wr *prompb.Writ
|
||||
// Register SIGHUP handler for config reload before loadConfig.
|
||||
// This guarantees that the config will be re-read if the signal arrives just after loadConfig.
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1240
|
||||
//sighupCh := procutil.NewSighupChan()
|
||||
sighupCh := procutil.NewSighupChan()
|
||||
|
||||
logger.Infof("reading scrape configs from %q", configFile)
|
||||
cfg, err := loadConfig(configFile)
|
||||
@@ -152,69 +152,61 @@ func runScraper(configFile string, pushData func(at *auth.Token, wr *prompb.Writ
|
||||
scs.add("yandexcloud_sd_configs", *yandexcloud.SDCheckInterval, func(cfg *Config, swsPrev []*ScrapeWork) []*ScrapeWork { return cfg.getYandexCloudSDScrapeWork(swsPrev) })
|
||||
scs.add("static_configs", 0, func(cfg *Config, _ []*ScrapeWork) []*ScrapeWork { return cfg.getStaticScrapeWork() })
|
||||
|
||||
scs.updateConfig(cfg)
|
||||
|
||||
var tickerCh <-chan time.Time
|
||||
if *configCheckInterval > 0 {
|
||||
// TBD print notice that deprecated -promscrape.configCheckInterval is used
|
||||
configwatcher.EnableCheckInterval(*configCheckInterval)
|
||||
|
||||
configwatcher.RegisterCheckIntervalHandler("-promscrape.config", func() {
|
||||
ticker := time.NewTicker(*configCheckInterval)
|
||||
tickerCh = ticker.C
|
||||
defer ticker.Stop()
|
||||
}
|
||||
for {
|
||||
scs.updateConfig(cfg)
|
||||
waitForChans:
|
||||
select {
|
||||
case <-sighupCh:
|
||||
logger.Infof("SIGHUP received; reloading Prometheus configs from %q", configFile)
|
||||
cfgNew, err := loadConfig(configFile)
|
||||
if err != nil {
|
||||
configReloadErrors.Inc()
|
||||
configSuccess.Set(0)
|
||||
logger.Errorf("cannot read %q: %s; continuing with the previous config", configFile, err)
|
||||
return
|
||||
logger.Errorf("cannot read %q on SIGHUP: %s; continuing with the previous config", configFile, err)
|
||||
goto waitForChans
|
||||
}
|
||||
configSuccess.Set(1)
|
||||
if !cfgNew.mustRestart(cfg) {
|
||||
return
|
||||
logger.Infof("nothing changed in %q", configFile)
|
||||
goto waitForChans
|
||||
}
|
||||
cfg = cfgNew
|
||||
marshaledData = cfg.marshal()
|
||||
configData.Store(&marshaledData)
|
||||
configReloads.Inc()
|
||||
configTimestamp.Set(fasttime.UnixTimestamp())
|
||||
|
||||
scs.updateConfig(cfg)
|
||||
})
|
||||
case <-tickerCh:
|
||||
cfgNew, err := loadConfig(configFile)
|
||||
if err != nil {
|
||||
configReloadErrors.Inc()
|
||||
configSuccess.Set(0)
|
||||
logger.Errorf("cannot read %q: %s; continuing with the previous config", configFile, err)
|
||||
goto waitForChans
|
||||
}
|
||||
configSuccess.Set(1)
|
||||
if !cfgNew.mustRestart(cfg) {
|
||||
goto waitForChans
|
||||
}
|
||||
cfg = cfgNew
|
||||
marshaledData = cfg.marshal()
|
||||
configData.Store(&marshaledData)
|
||||
configReloads.Inc()
|
||||
configTimestamp.Set(fasttime.UnixTimestamp())
|
||||
case <-globalStopCh:
|
||||
cfg.mustStop()
|
||||
logger.Infof("stopping Prometheus scrapers")
|
||||
startTime := time.Now()
|
||||
scs.stop()
|
||||
logger.Infof("stopped Prometheus scrapers in %.3f seconds", time.Since(startTime).Seconds())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
configwatcher.RegisterSignalHandler("-promscrape.config", func() {
|
||||
logger.Infof("SIGHUP received; reloading Prometheus configs from %q", configFile)
|
||||
cfgNew, err := loadConfig(configFile)
|
||||
if err != nil {
|
||||
configReloadErrors.Inc()
|
||||
configSuccess.Set(0)
|
||||
logger.Errorf("cannot read %q on SIGHUP: %s; continuing with the previous config", configFile, err)
|
||||
return
|
||||
}
|
||||
configSuccess.Set(1)
|
||||
if !cfgNew.mustRestart(cfg) {
|
||||
logger.Infof("nothing changed in %q", configFile)
|
||||
return
|
||||
}
|
||||
cfg = cfgNew
|
||||
marshaledData = cfg.marshal()
|
||||
configData.Store(&marshaledData)
|
||||
configReloads.Inc()
|
||||
configTimestamp.Set(fasttime.UnixTimestamp())
|
||||
|
||||
scs.updateConfig(cfg)
|
||||
})
|
||||
|
||||
go func() {
|
||||
<-globalStopCh
|
||||
|
||||
configwatcher.UnregisterHandler("-promscrape.config")
|
||||
|
||||
cfg.mustStop()
|
||||
logger.Infof("stopping Prometheus scrapers")
|
||||
startTime := time.Now()
|
||||
scs.stop()
|
||||
logger.Infof("stopped Prometheus scrapers in %.3f seconds", time.Since(startTime).Seconds())
|
||||
return
|
||||
}()
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/leveledbytebufferpool"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prommetadata"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutil"
|
||||
@@ -520,7 +521,7 @@ func (sw *scrapeWork) processDataOneShot(scrapeTimestamp, realTimestamp int64, b
|
||||
up = 0
|
||||
scrapesFailed.Inc()
|
||||
} else {
|
||||
if IsMetadataEnabled() {
|
||||
if prommetadata.IsEnabled() {
|
||||
wc.rows, wc.metadataRows = parser.UnmarshalWithMetadata(wc.rows, wc.metadataRows, bodyString, sw.logError)
|
||||
} else {
|
||||
wc.rows.UnmarshalWithErrLogger(bodyString, sw.logError)
|
||||
@@ -616,7 +617,7 @@ func (sw *scrapeWork) processDataInStreamMode(scrapeTimestamp, realTimestamp int
|
||||
areIdenticalSeries := areIdenticalSeries(cfg, lastScrapeStr, bodyString)
|
||||
|
||||
r := body.NewReader()
|
||||
err := stream.Parse(r, scrapeTimestamp, "", false, IsMetadataEnabled(), func(rows []parser.Row, mms []parser.Metadata) error {
|
||||
err := stream.Parse(r, scrapeTimestamp, "", false, prommetadata.IsEnabled(), func(rows []parser.Row, mms []parser.Metadata) error {
|
||||
labelsLen := maxLabelsLen.Load()
|
||||
wc := writeRequestCtxPool.Get(int(labelsLen))
|
||||
defer func() {
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/auth"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/chunkedbuffer"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prommetadata"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutil"
|
||||
@@ -144,11 +145,10 @@ func TestScrapeWorkScrapeInternalSuccess(t *testing.T) {
|
||||
}
|
||||
|
||||
func testScrapeWorkScrapeInternalSuccess(t *testing.T, streamParse bool) {
|
||||
oldIsmetadataEnabled := *enableMetadata
|
||||
oldMetadataEnabled := prommetadata.SetEnabled(true)
|
||||
defer func() {
|
||||
*enableMetadata = oldIsmetadataEnabled
|
||||
prommetadata.SetEnabled(oldMetadataEnabled)
|
||||
}()
|
||||
*enableMetadata = true
|
||||
f := func(data string, cfg *ScrapeWork, dataExpected string, metaDataExpected []prompb.MetricMetadata) {
|
||||
t.Helper()
|
||||
|
||||
@@ -599,11 +599,10 @@ func testScrapeWorkScrapeInternalSuccess(t *testing.T, streamParse bool) {
|
||||
//
|
||||
// The core parsing functionality is validated separately in TestScrapeWorkScrapeInternalSuccess.
|
||||
func TestScrapeWorkScrapeInternalStreamConcurrency(t *testing.T) {
|
||||
oldIsmetadataEnabled := *enableMetadata
|
||||
oldMetadataEnabled := prommetadata.SetEnabled(true)
|
||||
defer func() {
|
||||
*enableMetadata = oldIsmetadataEnabled
|
||||
prommetadata.SetEnabled(oldMetadataEnabled)
|
||||
}()
|
||||
*enableMetadata = true
|
||||
f := func(data string, cfg *ScrapeWork, pushDataCallsExpected int64, timeseriesExpected, timeseriesExpectedDelta, metadataExpected int64) {
|
||||
t.Helper()
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/auth"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/chunkedbuffer"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prommetadata"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/protoparserutil"
|
||||
)
|
||||
@@ -130,11 +131,10 @@ func BenchmarkScrapeWorkScrapeInternalStreamBigData(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkScrapeWorkScrapeInternalOneShotWithMetadata(b *testing.B) {
|
||||
oldIsmetadataEnabled := *enableMetadata
|
||||
oldMetadataEnabled := prommetadata.SetEnabled(true)
|
||||
defer func() {
|
||||
*enableMetadata = oldIsmetadataEnabled
|
||||
prommetadata.SetEnabled(oldMetadataEnabled)
|
||||
}()
|
||||
*enableMetadata = true
|
||||
data := `
|
||||
# TYPE vm_tcplistener_accepts_total counter
|
||||
# HELP vm_tcplistener_accepts_total some useless help message
|
||||
|
||||
@@ -199,8 +199,6 @@ type IndexDBMetrics struct {
|
||||
TagFiltersToMetricIDsCacheRequests uint64
|
||||
TagFiltersToMetricIDsCacheMisses uint64
|
||||
|
||||
DeletedMetricsCount uint64
|
||||
|
||||
IndexDBRefCount uint64
|
||||
|
||||
MissingTSIDsForMetricID uint64
|
||||
@@ -231,8 +229,6 @@ func (db *indexDB) scheduleToDrop() {
|
||||
// UpdateMetrics updates m with metrics from the db.
|
||||
func (db *indexDB) UpdateMetrics(m *IndexDBMetrics) {
|
||||
// global index metrics
|
||||
m.DeletedMetricsCount += uint64(db.s.getDeletedMetricIDs().Len())
|
||||
|
||||
m.IndexBlocksWithMetricIDsProcessed = indexBlocksWithMetricIDsProcessed.Load()
|
||||
m.IndexBlocksWithMetricIDsIncorrectOrder = indexBlocksWithMetricIDsIncorrectOrder.Load()
|
||||
|
||||
@@ -623,18 +619,21 @@ func (is *indexSearch) searchLabelNamesWithFiltersOnTimeRange(qt *querytracer.Tr
|
||||
}
|
||||
|
||||
func (is *indexSearch) searchLabelNamesWithFiltersOnDate(qt *querytracer.Tracer, tfss []*TagFilters, date uint64, maxLabelNames, maxMetrics int) (map[string]struct{}, error) {
|
||||
filter, err := is.searchMetricIDsWithFiltersOnDate(qt, tfss, date, maxMetrics)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if filter != nil && filter.Len() <= 100e3 {
|
||||
// It is faster to obtain label names by metricIDs from the filter
|
||||
// instead of scanning the inverted index for the matching filters.
|
||||
// This should help https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2978
|
||||
metricIDs := filter.AppendTo(nil)
|
||||
qt.Printf("sort %d metricIDs", len(metricIDs))
|
||||
lns := is.getLabelNamesForMetricIDs(qt, metricIDs, maxLabelNames)
|
||||
return lns, nil
|
||||
var filter *uint64set.Set
|
||||
if !isSingleMetricNameFilter(tfss) {
|
||||
filter, err := is.searchMetricIDsWithFiltersOnDate(qt, tfss, date, maxMetrics)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if filter != nil && filter.Len() <= 100e3 {
|
||||
// It is faster to obtain label names by metricIDs from the filter
|
||||
// instead of scanning the inverted index for the matching filters.
|
||||
// This should help https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2978
|
||||
metricIDs := filter.AppendTo(nil)
|
||||
qt.Printf("sort %d metricIDs", len(metricIDs))
|
||||
lns := is.getLabelNamesForMetricIDs(qt, metricIDs, maxLabelNames)
|
||||
return lns, nil
|
||||
}
|
||||
}
|
||||
|
||||
var prevLabelName []byte
|
||||
@@ -885,23 +884,27 @@ func (is *indexSearch) searchLabelValuesOnTimeRange(qt *querytracer.Tracer, labe
|
||||
}
|
||||
|
||||
func (is *indexSearch) searchLabelValuesOnDate(qt *querytracer.Tracer, labelName string, tfss []*TagFilters, date uint64, maxLabelValues, maxMetrics int) (map[string]struct{}, error) {
|
||||
filter, err := is.searchMetricIDsWithFiltersOnDate(qt, tfss, date, maxMetrics)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if filter != nil && filter.Len() <= 100e3 {
|
||||
// It is faster to obtain label values by metricIDs from the filter
|
||||
// instead of scanning the inverted index for the matching filters.
|
||||
// This should help https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2978
|
||||
metricIDs := filter.AppendTo(nil)
|
||||
qt.Printf("sort %d metricIDs", len(metricIDs))
|
||||
lvs := is.getLabelValuesForMetricIDs(qt, labelName, metricIDs, maxLabelValues)
|
||||
return lvs, nil
|
||||
}
|
||||
if labelName == "__name__" {
|
||||
// __name__ label is encoded as empty string in indexdb.
|
||||
labelName = ""
|
||||
}
|
||||
useCompositeScan := labelName != "" && isSingleMetricNameFilter(tfss)
|
||||
var filter *uint64set.Set
|
||||
if !useCompositeScan {
|
||||
filter, err := is.searchMetricIDsWithFiltersOnDate(qt, tfss, date, maxMetrics)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if filter != nil && filter.Len() <= 100e3 {
|
||||
// It is faster to obtain label values by metricIDs from the filter
|
||||
// instead of scanning the inverted index for the matching filters.
|
||||
// This should help https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2978
|
||||
metricIDs := filter.AppendTo(nil)
|
||||
qt.Printf("sort %d metricIDs", len(metricIDs))
|
||||
lvs := is.getLabelValuesForMetricIDs(qt, labelName, metricIDs, maxLabelValues)
|
||||
return lvs, nil
|
||||
}
|
||||
}
|
||||
|
||||
labelNameBytes := bytesutil.ToUnsafeBytes(labelName)
|
||||
if name := getCommonMetricNameForTagFilterss(tfss); len(name) > 0 && labelName != "" {
|
||||
@@ -1350,6 +1353,7 @@ func (is *indexSearch) getTSDBStatus(qt *querytracer.Tracer, tfss []*TagFilters,
|
||||
qt.Printf("no matching series for filter=%s", tfss)
|
||||
return &TSDBStatus{}, nil
|
||||
}
|
||||
|
||||
ts := &is.ts
|
||||
kb := &is.kb
|
||||
mp := &is.mp
|
||||
@@ -2190,6 +2194,11 @@ func matchTagFilters(mn *MetricName, tfs []*tagFilter, kb *bytesutil.ByteBuffer)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func isSingleMetricNameFilter(tfss []*TagFilters) bool {
|
||||
// We check if tfss contain only single filter which is __name__
|
||||
return len(tfss) == 1 && len(tfss[0].tfs) == 1 && getMetricNameFilter(tfss[0]) != nil
|
||||
}
|
||||
|
||||
func (is *indexSearch) searchMetricIDsWithFiltersOnDate(qt *querytracer.Tracer, tfss []*TagFilters, date uint64, maxMetrics int) (*uint64set.Set, error) {
|
||||
if len(tfss) == 0 {
|
||||
return nil, nil
|
||||
|
||||
@@ -1704,6 +1704,10 @@ func TestSearchTSIDWithTimeRange(t *testing.T) {
|
||||
if err := tfsMetricName.Add(nil, []byte("testMetric"), false, false); err != nil {
|
||||
t.Fatalf("cannot add filter on metric name: %s", err)
|
||||
}
|
||||
tfsComposite := NewTagFilters()
|
||||
if err := tfsComposite.Add(nil, []byte("testMetric"), false, false); err != nil {
|
||||
t.Fatalf("cannot add filter: %s", err)
|
||||
}
|
||||
|
||||
// Perform a search within a day.
|
||||
// This should return the metrics for the day
|
||||
@@ -1749,6 +1753,16 @@ func TestSearchTSIDWithTimeRange(t *testing.T) {
|
||||
t.Fatalf("unexpected labelNames; got\n%s\nwant\n%s", got, labelNames)
|
||||
}
|
||||
|
||||
// Check SearchLabelNames with filters on composite key and time range.
|
||||
lns, err = idbCurr.SearchLabelNames(nil, []*TagFilters{tfsComposite}, tr, 10000, 1e9, noDeadline)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error in SearchLabelNames(filters=%s, timeRange=%s): %s", tfs, &tr, err)
|
||||
}
|
||||
got = sortedSlice(lns)
|
||||
if !reflect.DeepEqual(got, labelNames) {
|
||||
t.Fatalf("unexpected labelNames; got\n%s\nwant\n%s", got, labelNames)
|
||||
}
|
||||
|
||||
// Check SearchLabelValues with the specified filter.
|
||||
lvs, err = idbCurr.SearchLabelValues(nil, "", []*TagFilters{tfs}, TimeRange{}, 10000, 1e9, noDeadline)
|
||||
if err != nil {
|
||||
@@ -1779,6 +1793,17 @@ func TestSearchTSIDWithTimeRange(t *testing.T) {
|
||||
t.Fatalf("unexpected labelValues; got\n%s\nwant\n%s", got, labelValues)
|
||||
}
|
||||
|
||||
// Check SearchLabelValues with filters on composite key and time range.
|
||||
lvs, err = idbCurr.SearchLabelValues(nil, "constant", []*TagFilters{tfsComposite}, tr, 10000, 1e9, noDeadline)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error in SearchLabelValues(filters=%s, timeRange=%s): %s", tfs, &tr, err)
|
||||
}
|
||||
got = sortedSlice(lvs)
|
||||
labelValues = []string{"const"}
|
||||
if !reflect.DeepEqual(got, labelValues) {
|
||||
t.Fatalf("unexpected labelValues; got\n%s\nwant\n%s", got, labelValues)
|
||||
}
|
||||
|
||||
// Perform a search across all the days, should match all metrics
|
||||
tr = TimeRange{
|
||||
MinTimestamp: int64(timestamp - msecPerDay*days),
|
||||
|
||||
@@ -192,8 +192,7 @@ func (pw *partWrapper) decRef() {
|
||||
}
|
||||
}
|
||||
|
||||
// mustCreatePartition creates new partition for the given timestamp and the given paths
|
||||
// to small and big partitions.
|
||||
// mustCreatePartition creates new partition for the given timestamp and the given paths to small and big partitions.
|
||||
func mustCreatePartition(timestamp int64, smallPartitionsPath, bigPartitionsPath string, s *Storage) *partition {
|
||||
name := timestampToPartitionName(timestamp)
|
||||
smallPartsPath := filepath.Join(filepath.Clean(smallPartitionsPath), name)
|
||||
@@ -208,6 +207,9 @@ func mustCreatePartition(timestamp int64, smallPartitionsPath, bigPartitionsPath
|
||||
|
||||
pt := newPartition(name, smallPartsPath, bigPartsPath, tr, s)
|
||||
|
||||
fs.MustSyncPathAndParentDir(smallPartsPath)
|
||||
fs.MustSyncPathAndParentDir(bigPartsPath)
|
||||
|
||||
pt.startBackgroundWorkers()
|
||||
|
||||
logger.Infof("partition %q has been created", name)
|
||||
@@ -242,6 +244,10 @@ func mustOpenPartition(smallPartsPath, bigPartsPath string, s *Storage) *partiti
|
||||
smallPartsPath = filepath.Clean(smallPartsPath)
|
||||
bigPartsPath = filepath.Clean(bigPartsPath)
|
||||
|
||||
// Create paths to parts if they are missing.
|
||||
fs.MustMkdirIfNotExist(smallPartsPath)
|
||||
fs.MustMkdirIfNotExist(bigPartsPath)
|
||||
|
||||
name := filepath.Base(smallPartsPath)
|
||||
var tr TimeRange
|
||||
if err := tr.fromPartitionName(name); err != nil {
|
||||
@@ -268,6 +274,9 @@ func mustOpenPartition(smallPartsPath, bigPartsPath string, s *Storage) *partiti
|
||||
pt.smallParts = smallParts
|
||||
pt.bigParts = bigParts
|
||||
|
||||
fs.MustSyncPathAndParentDir(smallPartsPath)
|
||||
fs.MustSyncPathAndParentDir(bigPartsPath)
|
||||
|
||||
pt.startBackgroundWorkers()
|
||||
|
||||
return pt
|
||||
@@ -1917,9 +1926,6 @@ func getPartsSize(pws []*partWrapper) uint64 {
|
||||
}
|
||||
|
||||
func mustOpenParts(partsFile, path string, partNames []string) []*partWrapper {
|
||||
// The path can be missing after restoring from backup, so create it if needed.
|
||||
fs.MustMkdirIfNotExist(path)
|
||||
|
||||
// Remove txn and tmp directories, which may be left after the upgrade
|
||||
// to v1.90.0 and newer versions.
|
||||
fs.MustRemoveDir(filepath.Join(path, "txn"))
|
||||
@@ -1955,7 +1961,6 @@ func mustOpenParts(partsFile, path string, partNames []string) []*partWrapper {
|
||||
fs.MustRemoveDir(deletePath)
|
||||
}
|
||||
}
|
||||
fs.MustSyncPath(path)
|
||||
|
||||
// Open parts
|
||||
var pws []*partWrapper
|
||||
@@ -2003,6 +2008,9 @@ func (pt *partition) MustCreateSnapshotAt(smallPath, bigPath string) {
|
||||
pt.mustCreateSnapshot(pt.smallPartsPath, smallPath, pwsSmall)
|
||||
pt.mustCreateSnapshot(pt.bigPartsPath, bigPath, pwsBig)
|
||||
|
||||
fs.MustSyncPathAndParentDir(smallPath)
|
||||
fs.MustSyncPathAndParentDir(bigPath)
|
||||
|
||||
logger.Infof("created partition snapshot of %q and %q at %q and %q in %.3f seconds",
|
||||
pt.smallPartsPath, pt.bigPartsPath, smallPath, bigPath, time.Since(startTime).Seconds())
|
||||
}
|
||||
@@ -2025,10 +2033,6 @@ func (pt *partition) mustCreateSnapshot(srcDir, dstDir string, pws []*partWrappe
|
||||
dstPath := filepath.Join(dstDir, filepath.Base(srcPath))
|
||||
fs.MustCopyFile(srcPath, dstPath)
|
||||
}
|
||||
|
||||
fs.MustSyncPath(dstDir)
|
||||
parentDir := filepath.Dir(dstDir)
|
||||
fs.MustSyncPath(parentDir)
|
||||
}
|
||||
|
||||
type partNamesJSON struct {
|
||||
|
||||
@@ -456,7 +456,7 @@ func (s *Storage) MustCreateSnapshot() string {
|
||||
dstIdbDir := filepath.Join(dstDir, indexdbDirname)
|
||||
fs.MustSymlinkRelative(idbSnapshot, dstIdbDir)
|
||||
|
||||
fs.MustSyncPath(dstDir)
|
||||
fs.MustSyncPathAndParentDir(dstDir)
|
||||
|
||||
logger.Infof("created Storage snapshot for %q at %q in %.3f seconds", srcDir, dstDir, time.Since(startTime).Seconds())
|
||||
return snapshotName
|
||||
@@ -655,6 +655,8 @@ type Metrics struct {
|
||||
MetricNamesUsageTrackerSizeBytes uint64
|
||||
MetricNamesUsageTrackerSizeMaxBytes uint64
|
||||
|
||||
DeletedMetricsCount uint64
|
||||
|
||||
IndexDBMetrics IndexDBMetrics
|
||||
TableMetrics TableMetrics
|
||||
}
|
||||
@@ -763,6 +765,8 @@ func (s *Storage) UpdateMetrics(m *Metrics) {
|
||||
}
|
||||
m.NextRetentionSeconds = uint64(d)
|
||||
|
||||
m.DeletedMetricsCount += uint64(s.getDeletedMetricIDs().Len())
|
||||
|
||||
idbPrev, idbCurr := s.getPrevAndCurrIndexDBs()
|
||||
defer s.putPrevAndCurrIndexDBs(idbPrev, idbCurr)
|
||||
idbCurr.UpdateMetrics(&m.IndexDBMetrics)
|
||||
|
||||
@@ -1439,6 +1439,55 @@ func TestStorageDeleteSeries_CachesAreUpdatedOrReset(t *testing.T) {
|
||||
assertDeletedMetricIDsCacheSize(3)
|
||||
}
|
||||
|
||||
func TestStorageDeleteSeriesFromPrevAndCurrIndexDB(t *testing.T) {
|
||||
defer testRemoveAll(t)
|
||||
|
||||
rng := rand.New(rand.NewSource(1))
|
||||
const numSeries = 100
|
||||
trPrev := TimeRange{
|
||||
MinTimestamp: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC).UnixMilli(),
|
||||
MaxTimestamp: time.Date(2020, 1, 1, 23, 59, 59, 999_999_999, time.UTC).UnixMilli(),
|
||||
}
|
||||
mrsPrev := testGenerateMetricRowsWithPrefix(rng, numSeries, "prev", trPrev)
|
||||
trCurr := TimeRange{
|
||||
MinTimestamp: time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC).UnixMilli(),
|
||||
MaxTimestamp: time.Date(2020, 1, 2, 23, 59, 59, 999_999_999, time.UTC).UnixMilli(),
|
||||
}
|
||||
mrsCurr := testGenerateMetricRowsWithPrefix(rng, numSeries, "curr", trCurr)
|
||||
deleteSeries := func(s *Storage, want, wantTotal int) {
|
||||
t.Helper()
|
||||
tfs := NewTagFilters()
|
||||
if err := tfs.Add(nil, []byte(".*"), false, true); err != nil {
|
||||
t.Fatalf("unexpected error in TagFilters.Add: %v", err)
|
||||
}
|
||||
got, err := s.DeleteSeries(nil, []*TagFilters{tfs}, 1e9)
|
||||
if err != nil {
|
||||
t.Fatalf("could not delete series unexpectedly: %v", err)
|
||||
}
|
||||
if got != want {
|
||||
t.Fatalf("unexpected number of deleted series: got %d, want %d", got, want)
|
||||
}
|
||||
var m Metrics
|
||||
s.UpdateMetrics(&m)
|
||||
if got, want := m.DeletedMetricsCount, uint64(wantTotal); got != want {
|
||||
t.Fatalf("unexpected number of total deleted series: got %d, want %d", got, want)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
s := MustOpenStorage(t.Name(), OpenOptions{})
|
||||
defer s.MustClose()
|
||||
s.AddRows(mrsPrev, defaultPrecisionBits)
|
||||
s.DebugFlush()
|
||||
deleteSeries(s, numSeries, numSeries)
|
||||
|
||||
s.mustRotateIndexDB(time.Now())
|
||||
|
||||
s.AddRows(mrsCurr, defaultPrecisionBits)
|
||||
s.DebugFlush()
|
||||
deleteSeries(s, numSeries, 2*numSeries)
|
||||
}
|
||||
|
||||
func TestStorageRegisterMetricNamesSerial(t *testing.T) {
|
||||
path := "TestStorageRegisterMetricNamesSerial"
|
||||
s := MustOpenStorage(path, OpenOptions{})
|
||||
|
||||
@@ -108,6 +108,9 @@ func mustOpenTable(path string, s *Storage) *table {
|
||||
// Open partitions.
|
||||
pts := mustOpenPartitions(smallPartitionsPath, bigPartitionsPath, s)
|
||||
|
||||
// Make sure all the directories inside the path are properly synced.
|
||||
fs.MustSyncPathAndParentDir(path)
|
||||
|
||||
tb := &table{
|
||||
path: path,
|
||||
smallPartitionsPath: smallPartitionsPath,
|
||||
@@ -144,10 +147,8 @@ func (tb *table) MustCreateSnapshot(snapshotName string) (string, string) {
|
||||
ptw.pt.MustCreateSnapshotAt(smallPath, bigPath)
|
||||
}
|
||||
|
||||
fs.MustSyncPath(dstSmallDir)
|
||||
fs.MustSyncPath(dstBigDir)
|
||||
fs.MustSyncPath(filepath.Dir(dstSmallDir))
|
||||
fs.MustSyncPath(filepath.Dir(dstBigDir))
|
||||
fs.MustSyncPathAndParentDir(dstSmallDir)
|
||||
fs.MustSyncPathAndParentDir(dstBigDir)
|
||||
|
||||
logger.Infof("created table snapshot for %q at (%q, %q) in %.3f seconds", tb.path, dstSmallDir, dstBigDir, time.Since(startTime).Seconds())
|
||||
return dstSmallDir, dstBigDir
|
||||
|
||||
@@ -359,12 +359,23 @@ func (s *Set) Subtract(a *Set) {
|
||||
// Fast path - nothing to subtract.
|
||||
return
|
||||
}
|
||||
a.ForEach(func(part []uint64) bool {
|
||||
for _, x := range part {
|
||||
s.Del(x)
|
||||
}
|
||||
return true
|
||||
})
|
||||
if s.Len() >= a.Len() {
|
||||
a.ForEach(func(part []uint64) bool {
|
||||
for _, x := range part {
|
||||
s.Del(x)
|
||||
}
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
s.ForEach(func(part []uint64) bool {
|
||||
for _, x := range part {
|
||||
if a.Has(x) {
|
||||
s.Del(x)
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Equal returns true if s contains the same items as a.
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
func TestSetOps(t *testing.T) {
|
||||
@@ -758,3 +760,141 @@ func TestAddMulti(t *testing.T) {
|
||||
}
|
||||
f(a)
|
||||
}
|
||||
|
||||
func TestSubtract(t *testing.T) {
|
||||
f := func(a, b, want *Set) {
|
||||
t.Helper()
|
||||
bBefore := b.AppendTo(nil)
|
||||
a.Subtract(b)
|
||||
bAfter := b.AppendTo(nil)
|
||||
gotValues := []uint64{}
|
||||
gotValues = a.AppendTo(gotValues)
|
||||
wantValues := []uint64{}
|
||||
wantValues = want.AppendTo(wantValues)
|
||||
if diff := cmp.Diff(wantValues, gotValues); diff != "" {
|
||||
t.Fatalf("unexpected a set (-want, +got):\n%s", diff)
|
||||
}
|
||||
if diff := cmp.Diff(bBefore, bAfter); diff != "" {
|
||||
t.Fatalf("unexpected b set (-want, +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
s := func(start, end uint64, se ...uint64) *Set {
|
||||
s := &Set{}
|
||||
for i := start; i <= end; i++ {
|
||||
s.Add(i)
|
||||
}
|
||||
if len(se)%2 != 0 {
|
||||
t.Fatalf("the number of additional starts and ends must be an even number since they go in pairs")
|
||||
}
|
||||
for i := 0; i < len(se); i += 2 {
|
||||
start := se[i]
|
||||
end := se[i+1]
|
||||
for j := start; j <= end; j++ {
|
||||
s.Add(j)
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
var a, b, want *Set
|
||||
|
||||
// - no overlap
|
||||
// - a values before b values
|
||||
// - len(a) > len(b)
|
||||
a = s(1, 500_000)
|
||||
b = s(500_001, 600_000)
|
||||
want = s(1, 500_000)
|
||||
f(a, b, want)
|
||||
|
||||
// - no overlap
|
||||
// - a values after b values
|
||||
// - len(a) > len(b)
|
||||
a = s(500_001, 1_000_000)
|
||||
b = s(400_000, 500_000)
|
||||
want = s(500_001, 1_000_000)
|
||||
f(a, b, want)
|
||||
|
||||
// - no overlap
|
||||
// - a values before b values
|
||||
// - len(a) < len(b)
|
||||
a = s(400_000, 500_000)
|
||||
b = s(500_001, 1_000_000)
|
||||
want = s(400_000, 500_000)
|
||||
f(a, b, want)
|
||||
|
||||
// - no overlap
|
||||
// - a values after b values
|
||||
// - len(a) < len(b)
|
||||
a = s(500_001, 600_000)
|
||||
b = s(1, 500_000)
|
||||
want = s(500_001, 600_000)
|
||||
f(a, b, want)
|
||||
|
||||
// - overlap on the left side
|
||||
// - len(a) > len(b)
|
||||
a = s(500_000, 1_000_000)
|
||||
b = s(400_000, 600_000)
|
||||
want = s(600_001, 1_000_000)
|
||||
f(a, b, want)
|
||||
|
||||
// - overlap on the right side
|
||||
// - len(a) > len(b)
|
||||
a = s(1, 500_000)
|
||||
b = s(400_001, 600_000)
|
||||
want = s(1, 400_000)
|
||||
f(a, b, want)
|
||||
|
||||
// - overlap on the left side
|
||||
// - len(a) < len(b)
|
||||
a = s(400_000, 600_000)
|
||||
b = s(500_001, 1_000_000)
|
||||
want = s(400_000, 500_000)
|
||||
f(a, b, want)
|
||||
|
||||
// - overlap on the right side
|
||||
// - len(a) < len(b)
|
||||
a = s(400_000, 600_000)
|
||||
b = s(1, 500_000)
|
||||
want = s(500_001, 600_000)
|
||||
f(a, b, want)
|
||||
|
||||
// same
|
||||
a = s(1, 500_000)
|
||||
b = s(1, 500_000)
|
||||
want = &Set{}
|
||||
f(a, b, want)
|
||||
|
||||
// b is a subset of a
|
||||
a = s(1, 500_000)
|
||||
b = s(100_001, 400_000)
|
||||
want = s(1, 100_000, 400_001, 500_000)
|
||||
f(a, b, want)
|
||||
|
||||
// a is a subset of b
|
||||
a = s(100_001, 400_000)
|
||||
b = s(1, 500_000)
|
||||
want = &Set{}
|
||||
f(a, b, want)
|
||||
|
||||
// a with intervals
|
||||
a = s(1, 200_000, 400_000, 500_000)
|
||||
b = s(150_001, 450_000)
|
||||
want = s(1, 150_000, 450_001, 500_000)
|
||||
f(a, b, want)
|
||||
|
||||
// b with intervals
|
||||
a = s(1, 500_000)
|
||||
b = s(200_001, 300_000, 400_001, 500_000)
|
||||
want = s(1, 200_000, 300_001, 400_000)
|
||||
f(a, b, want)
|
||||
|
||||
// subtract more than once.
|
||||
a = s(1, 500_000)
|
||||
b1 := s(100_001, 200_000)
|
||||
want1 := s(1, 100_000, 200_001, 500_000)
|
||||
b2 := s(300_001, 400_000)
|
||||
want2 := s(1, 100_000, 200_001, 300_000, 400_001, 500_000)
|
||||
f(a, b1, want1)
|
||||
f(a, b2, want2)
|
||||
}
|
||||
|
||||
@@ -154,6 +154,66 @@ func benchmarkIntersect(b *testing.B, sa, sb *Set) {
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkSubtract(b *testing.B) {
|
||||
f := func(b *testing.B, startA, itemsCountA, startB, itemsCountB uint64) {
|
||||
sa := createRangeSet(startA, int(itemsCountA))
|
||||
sb := createRangeSet(startB, int(itemsCountB))
|
||||
b.ReportAllocs()
|
||||
b.SetBytes(int64(sa.Len() + sb.Len()))
|
||||
for b.Loop() {
|
||||
saCopy := sa.Clone()
|
||||
saCopy.Subtract(sb)
|
||||
}
|
||||
}
|
||||
|
||||
start := uint64(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC).UnixNano())
|
||||
itemsACounts := []uint64{1e3, 1e4, 1e5, 1e6, 1e7}
|
||||
itemsBCounts := []uint64{1e7, 1e6, 1e5, 1e4, 1e3}
|
||||
for i := range len(itemsACounts) {
|
||||
itemsCountA := itemsACounts[i]
|
||||
itemsCountB := itemsBCounts[i]
|
||||
|
||||
b.Run(fmt.Sprintf("-----NoOverlap-AbeforeB-A%d-B%d", itemsCountA, itemsCountB), func(b *testing.B) {
|
||||
f(b, start, itemsCountA, start+itemsCountA, itemsCountB)
|
||||
})
|
||||
b.Run(fmt.Sprintf("-----NoOverlap-AbeforeB-B%d-A%d", itemsCountB, itemsCountA), func(b *testing.B) {
|
||||
f(b, start+itemsCountA, itemsCountB, start, itemsCountA)
|
||||
})
|
||||
b.Run(fmt.Sprintf("-----NoOverlap-BbeforeA-A%d-B%d", itemsCountA, itemsCountB), func(b *testing.B) {
|
||||
f(b, start-itemsCountA, itemsCountA, start, itemsCountB)
|
||||
})
|
||||
b.Run(fmt.Sprintf("-----NoOverlap-BbeforeA-B%d-A%d", itemsCountB, itemsCountA), func(b *testing.B) {
|
||||
f(b, start, itemsCountB, start-itemsCountA, itemsCountA)
|
||||
})
|
||||
|
||||
b.Run(fmt.Sprintf("PartialOverlap-AbeforeB-A%d-B%d", itemsCountA, itemsCountB), func(b *testing.B) {
|
||||
f(b, start, itemsCountA, start+itemsCountA-itemsCountB/2, itemsCountB)
|
||||
})
|
||||
b.Run(fmt.Sprintf("PartialOverlap-AbeforeB-B%d-A%d", itemsCountB, itemsCountA), func(b *testing.B) {
|
||||
f(b, start+itemsCountA-itemsCountB/2, itemsCountB, start, itemsCountA)
|
||||
})
|
||||
b.Run(fmt.Sprintf("PartialOverlap-BbeforeA-A%d-B%d", itemsCountA, itemsCountB), func(b *testing.B) {
|
||||
f(b, start+itemsCountB/2, itemsCountA, start, itemsCountB)
|
||||
})
|
||||
b.Run(fmt.Sprintf("PartialOverlap-BbeforeA-B%d-A%d", itemsCountB, itemsCountA), func(b *testing.B) {
|
||||
f(b, start, itemsCountB, start+itemsCountB/2, itemsCountA)
|
||||
})
|
||||
|
||||
b.Run(fmt.Sprintf("---FullOverlap-AbeforeB-A%d-B%d", itemsCountA, itemsCountB), func(b *testing.B) {
|
||||
f(b, start, itemsCountA, start+itemsCountA-itemsCountB, itemsCountB)
|
||||
})
|
||||
b.Run(fmt.Sprintf("---FullOverlap-AbeforeB-B%d-A%d", itemsCountB, itemsCountA), func(b *testing.B) {
|
||||
f(b, start+itemsCountA-itemsCountB, itemsCountB, start, itemsCountA)
|
||||
})
|
||||
b.Run(fmt.Sprintf("---FullOverlap-BbeforeA-A%d-B%d", itemsCountA, itemsCountB), func(b *testing.B) {
|
||||
f(b, start+itemsCountB, itemsCountA, start, itemsCountB)
|
||||
})
|
||||
b.Run(fmt.Sprintf("---FullOverlap-BbeforeA-B%d-A%d", itemsCountB, itemsCountA), func(b *testing.B) {
|
||||
f(b, start, itemsCountB, start+itemsCountB, itemsCountA)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func createRangeSet(start uint64, itemsCount int) *Set {
|
||||
var s Set
|
||||
for i := 0; i < itemsCount; i++ {
|
||||
|
||||