mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-12-11 14:53:49 +00:00
Merge branch 'public-single-node' into pmm-6401-read-prometheus-data-files
This commit is contained in:
commit
c2e9be96a7
29 changed files with 1563 additions and 1536 deletions
|
@ -1177,7 +1177,9 @@ See [these docs](https://docs.victoriametrics.com/guides/guide-vmcluster-multipl
|
|||
|
||||
* `-downsampling.period=30d:5m,180d:1h` instructs VictoriaMetrics to deduplicate samples older than 30 days with 5 minutes interval and to deduplicate samples older than 180 days with 1 hour interval.
|
||||
|
||||
Downsampling is applied independently per each time series. It can reduce disk space usage and improve query performance if it is applied to time series with big number of samples per each series. The downsampling doesn't improve query performance if the database contains big number of time series with small number of samples per each series (aka [high churn rate](https://docs.victoriametrics.com/FAQ.html#what-is-high-churn-rate)), since downsamlping doesn't reduce the number of time series. So the majority of time is spent on searching for the matching time series.
|
||||
Downsampling is applied independently per each time series. It can reduce disk space usage and improve query performance if it is applied to time series with big number of samples per each series. The downsampling doesn't improve query performance if the database contains big number of time series with small number of samples per each series (aka [high churn rate](https://docs.victoriametrics.com/FAQ.html#what-is-high-churn-rate)), since downsampling doesn't reduce the number of time series. So the majority of time is spent on searching for the matching time series.
|
||||
|
||||
The downsampling can be evaluated for free by downloading and using enterprise binaries from [the releases page](https://github.com/VictoriaMetrics/VictoriaMetrics/releases).
|
||||
|
||||
|
||||
## Multi-tenancy
|
||||
|
|
|
@ -254,7 +254,7 @@ func (c *client) sendBlockHTTP(block []byte) bool {
|
|||
again:
|
||||
req, err := http.NewRequest("POST", c.remoteWriteURL, bytes.NewBuffer(block))
|
||||
if err != nil {
|
||||
logger.Panicf("BUG: unexected error from http.NewRequest(%q): %s", c.sanitizedURL, err)
|
||||
logger.Panicf("BUG: unexpected error from http.NewRequest(%q): %s", c.sanitizedURL, err)
|
||||
}
|
||||
h := req.Header
|
||||
h.Set("User-Agent", "vmagent")
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"files": {
|
||||
"main.css": "./static/css/main.a33903a8.css",
|
||||
"main.js": "./static/js/main.4305bd17.js",
|
||||
"main.js": "./static/js/main.23f635e5.js",
|
||||
"static/js/27.85f0e2b0.chunk.js": "./static/js/27.85f0e2b0.chunk.js",
|
||||
"index.html": "./index.html"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.a33903a8.css",
|
||||
"static/js/main.4305bd17.js"
|
||||
"static/js/main.23f635e5.js"
|
||||
]
|
||||
}
|
|
@ -1 +1 @@
|
|||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="VM-UI is a metric explorer for Victoria Metrics"/><link rel="apple-touch-icon" href="./apple-touch-icon.png"/><link rel="icon" type="image/png" sizes="32x32" href="./favicon-32x32.png"><link rel="manifest" href="./manifest.json"/><title>VM UI</title><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"/><script defer="defer" src="./static/js/main.4305bd17.js"></script><link href="./static/css/main.a33903a8.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.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="VM-UI is a metric explorer for Victoria Metrics"/><link rel="apple-touch-icon" href="./apple-touch-icon.png"/><link rel="icon" type="image/png" sizes="32x32" href="./favicon-32x32.png"><link rel="manifest" href="./manifest.json"/><title>VM UI</title><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"/><script defer="defer" src="./static/js/main.23f635e5.js"></script><link href="./static/css/main.a33903a8.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
2
app/vmselect/vmui/static/js/main.23f635e5.js
Normal file
2
app/vmselect/vmui/static/js/main.23f635e5.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -15,6 +15,7 @@ const AxesLimitsConfigurator: FC = () => {
|
|||
const onChangeLimit = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>, axis: string, index: number) => {
|
||||
const newLimits = yaxis.limits.range;
|
||||
newLimits[axis][index] = +e.target.value;
|
||||
if (newLimits[axis][0] === newLimits[axis][1] || newLimits[axis][0] > newLimits[axis][1]) return;
|
||||
graphDispatch({type: "SET_YAXIS_LIMITS", payload: newLimits});
|
||||
};
|
||||
const debouncedOnChangeLimit = useCallback(debounce(onChangeLimit, 500), [yaxis.limits.range]);
|
||||
|
|
|
@ -9,7 +9,7 @@ const StepConfigurator: FC = () => {
|
|||
const {customStep} = useGraphState();
|
||||
const graphDispatch = useGraphDispatch();
|
||||
const [error, setError] = useState(false);
|
||||
const {time: {period: {step}, duration}} = useAppState();
|
||||
const {time: {period: {step}}} = useAppState();
|
||||
|
||||
const onChangeStep = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
const value = +e.target.value;
|
||||
|
@ -28,10 +28,6 @@ const StepConfigurator: FC = () => {
|
|||
graphDispatch({type: "TOGGLE_CUSTOM_STEP"});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (customStep.enable) onChangeEnableStep();
|
||||
}, [duration]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!customStep.enable) graphDispatch({type: "SET_CUSTOM_STEP", payload: step || 1});
|
||||
}, [step]);
|
||||
|
|
|
@ -18,7 +18,7 @@ export const useFetchQuery = (): {
|
|||
liveData?: InstantMetricResult[],
|
||||
error?: ErrorTypes | string,
|
||||
} => {
|
||||
const {query, displayType, serverUrl, time: {period}, queryControls: {nocache}} = useAppState();
|
||||
const {query, displayType, serverUrl, time: {period}, queryControls: {nocache, autoRefresh}} = useAppState();
|
||||
|
||||
const {basicData, bearerData, authMethod} = useAuthState();
|
||||
const {customStep} = useGraphState();
|
||||
|
@ -37,7 +37,7 @@ export const useFetchQuery = (): {
|
|||
}, [error]);
|
||||
|
||||
const needUpdateData = useMemo(() => {
|
||||
if (!prevPeriod) return true;
|
||||
if (!prevPeriod || autoRefresh) return true;
|
||||
const duration = (prevPeriod.end - prevPeriod.start) / 3;
|
||||
const factorLimit = duration / (period.end - period.start) >= 0.7;
|
||||
const maxLimit = period.end > (prevPeriod.end + duration);
|
||||
|
|
|
@ -16,7 +16,7 @@ const HomeLayout: FC = () => {
|
|||
const {isLoading, liveData, graphData, error} = useFetchQuery();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box id="homeLayout">
|
||||
<AppBar position="static">
|
||||
<Toolbar>
|
||||
<Box display="flex">
|
||||
|
@ -78,7 +78,7 @@ const HomeLayout: FC = () => {
|
|||
</Box>}
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import React, {FC, useEffect, useState} from "react";
|
||||
import React, {FC, useEffect, useMemo, useState} from "react";
|
||||
import {MetricResult} from "../../../api/types";
|
||||
import LineChart from "../../LineChart/LineChart";
|
||||
import {AlignedData as uPlotData, Series as uPlotSeries} from "uplot";
|
||||
import Legend from "../../Legend/Legend";
|
||||
import {useGraphDispatch} from "../../../state/graph/GraphStateContext";
|
||||
import {useGraphDispatch, useGraphState} from "../../../state/graph/GraphStateContext";
|
||||
import {getHideSeries, getLegendItem, getSeriesItem} from "../../../utils/uplot/series";
|
||||
import {getLimitsYAxis, getTimeSeries} from "../../../utils/uplot/axes";
|
||||
import {LegendItem} from "../../../utils/uplot/types";
|
||||
import {AxisRange} from "../../../state/graph/reducer";
|
||||
import GraphSettings from "../Configurator/Graph/GraphSettings";
|
||||
import {useAppState} from "../../../state/common/StateContext";
|
||||
|
||||
export interface GraphViewProps {
|
||||
data?: MetricResult[];
|
||||
|
@ -16,16 +16,17 @@ export interface GraphViewProps {
|
|||
|
||||
const GraphView: FC<GraphViewProps> = ({data = []}) => {
|
||||
const graphDispatch = useGraphDispatch();
|
||||
const {time: {period}} = useAppState();
|
||||
const { customStep } = useGraphState();
|
||||
const currentStep = useMemo(() => customStep.enable ? customStep.value : period.step || 1, [period.step, customStep]);
|
||||
|
||||
const [dataChart, setDataChart] = useState<uPlotData>([[]]);
|
||||
const [series, setSeries] = useState<uPlotSeries[]>([]);
|
||||
const [legend, setLegend] = useState<LegendItem[]>([]);
|
||||
const [hideSeries, setHideSeries] = useState<string[]>([]);
|
||||
const [valuesLimit, setValuesLimit] = useState<AxisRange>({"1": [0, 1]});
|
||||
|
||||
const setLimitsYaxis = (values: {[key: string]: number[]}) => {
|
||||
const limits = getLimitsYAxis(values);
|
||||
setValuesLimit(limits);
|
||||
graphDispatch({type: "SET_YAXIS_LIMITS", payload: limits});
|
||||
};
|
||||
|
||||
|
@ -50,9 +51,12 @@ const GraphView: FC<GraphViewProps> = ({data = []}) => {
|
|||
});
|
||||
});
|
||||
|
||||
const timeSeries = getTimeSeries(tempTimes);
|
||||
const timeSeries = getTimeSeries(tempTimes, currentStep, period);
|
||||
setDataChart([timeSeries, ...data.map(d => {
|
||||
return new Array(timeSeries.length).fill(1).map((v, i) => d.values[i] ? +d.values[i][1] : null);
|
||||
return timeSeries.map(t => {
|
||||
const value = d.values.find(v => v[0] === t);
|
||||
return value ? +value[1] : null;
|
||||
});
|
||||
})] as uPlotData);
|
||||
setLimitsYaxis(tempValues);
|
||||
|
||||
|
@ -79,7 +83,7 @@ const GraphView: FC<GraphViewProps> = ({data = []}) => {
|
|||
{(data.length > 0)
|
||||
? <div>
|
||||
<GraphSettings/>
|
||||
<LineChart data={dataChart} series={series} metrics={data} limits={valuesLimit}/>
|
||||
<LineChart data={dataChart} series={series} metrics={data}/>
|
||||
<Legend labels={legend} onChange={onChangeLegend}/>
|
||||
</div>
|
||||
: <div style={{textAlign: "center"}}>No data to show</div>}
|
||||
|
|
|
@ -11,29 +11,28 @@ import {limitsDurations} from "../../utils/time";
|
|||
import throttle from "lodash.throttle";
|
||||
import "uplot/dist/uPlot.min.css";
|
||||
import "./tooltip.css";
|
||||
import {AxisRange} from "../../state/graph/reducer";
|
||||
import useResize from "../../hooks/useResize";
|
||||
|
||||
export interface LineChartProps {
|
||||
metrics: MetricResult[];
|
||||
data: uPlotData;
|
||||
series: uPlotSeries[];
|
||||
limits: AxisRange;
|
||||
}
|
||||
enum typeChartUpdate {xRange = "xRange", yRange = "yRange", data = "data"}
|
||||
|
||||
const LineChart: FC<LineChartProps> = ({data, series, metrics = [], limits}) => {
|
||||
const LineChart: FC<LineChartProps> = ({data, series, metrics = []}) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const {time: {period}} = useAppState();
|
||||
const {yaxis} = useGraphState();
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const uPlotRef = useRef<HTMLDivElement>(null);
|
||||
const [isPanning, setPanning] = useState(false);
|
||||
const [xRange, setXRange] = useState({min: period.start, max: period.end});
|
||||
const [uPlotInst, setUPlotInst] = useState<uPlot>();
|
||||
const layoutSize = useResize(document.getElementById("homeLayout"));
|
||||
|
||||
const tooltip = document.createElement("div");
|
||||
tooltip.className = "u-tooltip";
|
||||
const tooltipIdx = {seriesIdx: 1, dataIdx: 0};
|
||||
const tooltipIdx: {seriesIdx: number | null, dataIdx: number | undefined} = {seriesIdx: null, dataIdx: undefined};
|
||||
const tooltipOffset = {left: 0, top: 0};
|
||||
|
||||
const setScale = ({min, max}: { min: number, max: number }): void => {
|
||||
|
@ -73,22 +72,22 @@ const LineChart: FC<LineChartProps> = ({data, series, metrics = [], limits}) =>
|
|||
const setCursor = (u: uPlot) => {
|
||||
if (tooltipIdx.dataIdx === u.cursor.idx) return;
|
||||
tooltipIdx.dataIdx = u.cursor.idx || 0;
|
||||
if (tooltipIdx.seriesIdx && tooltipIdx.dataIdx) {
|
||||
if (tooltipIdx.seriesIdx !== null && tooltipIdx.dataIdx !== undefined) {
|
||||
setTooltip({u, tooltipIdx, metrics, series, tooltip, tooltipOffset});
|
||||
}
|
||||
};
|
||||
|
||||
const seriesFocus = (u: uPlot, sidx: (number | null)) => {
|
||||
if (tooltipIdx.seriesIdx === sidx) return;
|
||||
tooltipIdx.seriesIdx = sidx || 0;
|
||||
sidx && tooltipIdx.dataIdx
|
||||
tooltipIdx.seriesIdx = sidx;
|
||||
sidx && tooltipIdx.dataIdx !== undefined
|
||||
? setTooltip({u, tooltipIdx, metrics, series, tooltip, tooltipOffset})
|
||||
: tooltip.style.display = "none";
|
||||
};
|
||||
const getRangeX = (): Range.MinMax => [xRange.min, xRange.max];
|
||||
const getRangeY = (u: uPlot, min = 0, max = 1, axis: string): Range.MinMax => {
|
||||
if (yaxis.limits.enable) return yaxis.limits.range[axis];
|
||||
return min && max ? [min - (min * 0.05), max + (max * 0.05)] : limits[axis];
|
||||
return min && max ? [min - (min * 0.25), max + (max * 0.25)] : [-1, 1];
|
||||
};
|
||||
|
||||
const getScales = (): Scales => {
|
||||
|
@ -104,7 +103,7 @@ const LineChart: FC<LineChartProps> = ({data, series, metrics = [], limits}) =>
|
|||
series,
|
||||
axes: getAxes(series),
|
||||
scales: {...getScales()},
|
||||
width: containerRef.current ? containerRef.current.offsetWidth : 400,
|
||||
width: layoutSize.width ? layoutSize.width - 64 : 400,
|
||||
plugins: [{hooks: {ready: onReadyChart, setCursor, setSeries: seriesFocus}}],
|
||||
};
|
||||
|
||||
|
@ -135,13 +134,13 @@ const LineChart: FC<LineChartProps> = ({data, series, metrics = [], limits}) =>
|
|||
setUPlotInst(u);
|
||||
setXRange({min: period.start, max: period.end});
|
||||
return u.destroy;
|
||||
}, [uPlotRef.current, series]);
|
||||
}, [uPlotRef.current, series, layoutSize]);
|
||||
|
||||
useEffect(() => updateChart(typeChartUpdate.data), [data]);
|
||||
useEffect(() => updateChart(typeChartUpdate.xRange), [xRange]);
|
||||
useEffect(() => updateChart(typeChartUpdate.yRange), [yaxis]);
|
||||
|
||||
return <div ref={containerRef} style={{pointerEvents: isPanning ? "none" : "auto", height: "500px"}}>
|
||||
return <div style={{pointerEvents: isPanning ? "none" : "auto", height: "500px"}}>
|
||||
<div ref={uPlotRef}/>
|
||||
</div>;
|
||||
};
|
||||
|
|
23
app/vmui/packages/vmui/src/hooks/useResize.ts
Normal file
23
app/vmui/packages/vmui/src/hooks/useResize.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { useState, useEffect } from "react";
|
||||
|
||||
const useResize = (node: HTMLElement | null): {width: number, height: number} => {
|
||||
const [windowSize, setWindowSize] = useState({
|
||||
width: 0,
|
||||
height: 0,
|
||||
});
|
||||
useEffect(() => {
|
||||
if (!node) return;
|
||||
const handleResize = () => {
|
||||
setWindowSize({
|
||||
width: node.offsetWidth,
|
||||
height: node.offsetHeight,
|
||||
});
|
||||
};
|
||||
window.addEventListener("resize", handleResize);
|
||||
handleResize();
|
||||
return () => window.removeEventListener("resize", handleResize);
|
||||
}, []);
|
||||
return windowSize;
|
||||
};
|
||||
|
||||
export default useResize;
|
|
@ -21,7 +21,7 @@ export interface GraphState {
|
|||
|
||||
export type GraphAction =
|
||||
| { type: "TOGGLE_ENABLE_YAXIS_LIMITS" }
|
||||
| { type: "SET_YAXIS_LIMITS", payload: { [key: string]: [number, number] } }
|
||||
| { type: "SET_YAXIS_LIMITS", payload: AxisRange }
|
||||
| { type: "TOGGLE_CUSTOM_STEP" }
|
||||
| { type: "SET_CUSTOM_STEP", payload: number}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import {getMaxFromArray, getMinFromArray} from "../math";
|
|||
import {roundTimeSeconds} from "../time";
|
||||
import {AxisRange} from "../../state/graph/reducer";
|
||||
import {formatTicks} from "./helpers";
|
||||
import {TimeParams} from "../../types";
|
||||
|
||||
export const getAxes = (series: Series[]): Axis[] => Array.from(new Set(series.map(s => s.scale))).map(a => {
|
||||
const axis = {scale: a, show: true, font: "10px Arial", values: formatTicks};
|
||||
|
@ -11,11 +12,11 @@ export const getAxes = (series: Series[]): Axis[] => Array.from(new Set(series.m
|
|||
return axis;
|
||||
});
|
||||
|
||||
export const getTimeSeries = (times: number[]): number[] => {
|
||||
export const getTimeSeries = (times: number[], defaultStep: number, period: TimeParams): number[] => {
|
||||
const allTimes = Array.from(new Set(times)).sort((a, b) => a - b);
|
||||
const step = getMinFromArray(allTimes.map((t, i) => allTimes[i + 1] - t));
|
||||
const length = Math.ceil((period.end - period.start)/defaultStep);
|
||||
const startTime = allTimes[0] || 0;
|
||||
return new Array(allTimes.length).fill(startTime).map((d, i) => roundTimeSeconds(d + (step * i)));
|
||||
return new Array(length*2).fill(startTime).map((d, i) => roundTimeSeconds(d + (defaultStep * i)));
|
||||
};
|
||||
|
||||
export const getLimitsYAxis = (values: { [key: string]: number[] }): AxisRange => {
|
||||
|
@ -24,7 +25,7 @@ export const getLimitsYAxis = (values: { [key: string]: number[] }): AxisRange =
|
|||
const numbers = values[key];
|
||||
const min = getMinFromArray(numbers);
|
||||
const max = getMaxFromArray(numbers);
|
||||
result[key] = [min - (min * 0.05), max + (max * 0.05)];
|
||||
result[key] = min && max ? [min - (min * 0.25), max + (max * 0.25)] : [-1, 1];
|
||||
}
|
||||
return result;
|
||||
};
|
|
@ -15,6 +15,10 @@ export const defaultOptions = {
|
|||
focus: {
|
||||
prox: 30
|
||||
},
|
||||
points: {
|
||||
size: 5.6,
|
||||
width: 1.4
|
||||
},
|
||||
bind: {
|
||||
mouseup: (): null => null,
|
||||
mousedown: (): null => null,
|
||||
|
|
|
@ -10,10 +10,14 @@ export const getSeriesItem = (d: MetricResult, hideSeries: string[]): Series =>
|
|||
return {
|
||||
label,
|
||||
dash: getDashLine(d.group),
|
||||
width: 1.5,
|
||||
width: 1.4,
|
||||
stroke: getColorLine(d.group, label),
|
||||
show: !includesHideSeries(label, d.group, hideSeries),
|
||||
scale: String(d.group)
|
||||
scale: String(d.group),
|
||||
points: {
|
||||
size: 4.2,
|
||||
width: 1.4
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import {getColorLine} from "./helpers";
|
|||
|
||||
export const setTooltip = ({u, tooltipIdx, metrics, series, tooltip, tooltipOffset}: SetupTooltip): void => {
|
||||
const {seriesIdx, dataIdx} = tooltipIdx;
|
||||
if (seriesIdx === null || dataIdx === undefined) return;
|
||||
const dataSeries = u.data[seriesIdx][dataIdx];
|
||||
const dataTime = u.data[0][dataIdx];
|
||||
const metric = metrics[seriesIdx - 1]?.metric || {};
|
||||
|
|
|
@ -11,8 +11,8 @@ export interface SetupTooltip {
|
|||
top: number
|
||||
},
|
||||
tooltipIdx: {
|
||||
seriesIdx: number,
|
||||
dataIdx: number
|
||||
seriesIdx: number | null,
|
||||
dataIdx: number | undefined
|
||||
}
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1605,7 +1605,7 @@
|
|||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_DBAAS-TEST-T3-MEDIUM-INST}"
|
||||
"uid": "$ds"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "sum(rate(vm_promscrape_scrapes_total{job=~\"$job\", instance=~\"$instance\"}[$__interval]))",
|
||||
|
@ -1616,7 +1616,7 @@
|
|||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_DBAAS-TEST-T3-MEDIUM-INST}"
|
||||
"uid": "$ds"
|
||||
},
|
||||
"expr": "sum(rate(vm_promscrape_scraped_samples_sum{job=~\"$job\", instance=~\"$instance\"}[$__interval]))",
|
||||
"interval": "",
|
||||
|
@ -3429,7 +3429,7 @@
|
|||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_DBAAS-TEST-T3-MEDIUM-INST}"
|
||||
"uid": "$ds"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "sum(rate(vm_persistentqueue_write_duration_seconds_total{job=~\"$job\", instance=~\"$instance\", url=~\"$url\"}[$__rate_interval])) by (instance)",
|
||||
|
@ -3538,7 +3538,7 @@
|
|||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_DBAAS-TEST-T3-MEDIUM-INST}"
|
||||
"uid": "$ds"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "sum(rate(vm_persistentqueue_read_duration_seconds_total{job=~\"$job\", instance=~\"$instance\", url=~\"$url\"}[$__rate_interval])) by (instance)",
|
||||
|
|
|
@ -6,6 +6,9 @@ sort: 15
|
|||
|
||||
## tip
|
||||
|
||||
|
||||
## [v1.71.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.71.0)
|
||||
|
||||
* FEATURE: [VictoriaMetrics enterprise](https://victoriametrics.com/enterprise.html): add multi-level downsampling support. See [these docs](https://docs.victoriametrics.com/#downsampling) and [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/36).
|
||||
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add ability to analyze the correlation between two queries on a single graph. Just click `+Query` button, enter the second query in the newly appeared input field and press `Ctrl+Enter`. Results for both queries should be displayed simultaneously on the same graph. Every query has its own vertical scale, which is displayed on the left and the right side of the graph. Lines for the second query are dashed. See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/1916).
|
||||
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add ability to override the interval between returned datapoints. By default it is automatically calculated depending on the selected time range and horizontal resolution of the graph. Now it is possible to override it with custom values. This may be useful during data exploration and debugging.
|
||||
|
|
|
@ -1177,7 +1177,9 @@ See [these docs](https://docs.victoriametrics.com/guides/guide-vmcluster-multipl
|
|||
|
||||
* `-downsampling.period=30d:5m,180d:1h` instructs VictoriaMetrics to deduplicate samples older than 30 days with 5 minutes interval and to deduplicate samples older than 180 days with 1 hour interval.
|
||||
|
||||
Downsampling is applied independently per each time series. It can reduce disk space usage and improve query performance if it is applied to time series with big number of samples per each series. The downsampling doesn't improve query performance if the database contains big number of time series with small number of samples per each series (aka [high churn rate](https://docs.victoriametrics.com/FAQ.html#what-is-high-churn-rate)), since downsamlping doesn't reduce the number of time series. So the majority of time is spent on searching for the matching time series.
|
||||
Downsampling is applied independently per each time series. It can reduce disk space usage and improve query performance if it is applied to time series with big number of samples per each series. The downsampling doesn't improve query performance if the database contains big number of time series with small number of samples per each series (aka [high churn rate](https://docs.victoriametrics.com/FAQ.html#what-is-high-churn-rate)), since downsampling doesn't reduce the number of time series. So the majority of time is spent on searching for the matching time series.
|
||||
|
||||
The downsampling can be evaluated for free by downloading and using enterprise binaries from [the releases page](https://github.com/VictoriaMetrics/VictoriaMetrics/releases).
|
||||
|
||||
|
||||
## Multi-tenancy
|
||||
|
|
|
@ -1181,7 +1181,9 @@ See [these docs](https://docs.victoriametrics.com/guides/guide-vmcluster-multipl
|
|||
|
||||
* `-downsampling.period=30d:5m,180d:1h` instructs VictoriaMetrics to deduplicate samples older than 30 days with 5 minutes interval and to deduplicate samples older than 180 days with 1 hour interval.
|
||||
|
||||
Downsampling is applied independently per each time series. It can reduce disk space usage and improve query performance if it is applied to time series with big number of samples per each series. The downsampling doesn't improve query performance if the database contains big number of time series with small number of samples per each series (aka [high churn rate](https://docs.victoriametrics.com/FAQ.html#what-is-high-churn-rate)), since downsamlping doesn't reduce the number of time series. So the majority of time is spent on searching for the matching time series.
|
||||
Downsampling is applied independently per each time series. It can reduce disk space usage and improve query performance if it is applied to time series with big number of samples per each series. The downsampling doesn't improve query performance if the database contains big number of time series with small number of samples per each series (aka [high churn rate](https://docs.victoriametrics.com/FAQ.html#what-is-high-churn-rate)), since downsampling doesn't reduce the number of time series. So the majority of time is spent on searching for the matching time series.
|
||||
|
||||
The downsampling can be evaluated for free by downloading and using enterprise binaries from [the releases page](https://github.com/VictoriaMetrics/VictoriaMetrics/releases).
|
||||
|
||||
|
||||
## Multi-tenancy
|
||||
|
|
|
@ -113,6 +113,9 @@ func (q *queue) mustResetFiles() {
|
|||
|
||||
// GetPendingBytes returns the number of pending bytes in the queue.
|
||||
func (q *queue) GetPendingBytes() uint64 {
|
||||
if q.readerOffset > q.writerOffset {
|
||||
logger.Panicf("BUG: readerOffset=%d cannot exceed writerOffset=%d", q.readerOffset, q.writerOffset)
|
||||
}
|
||||
n := q.writerOffset - q.readerOffset
|
||||
return n
|
||||
}
|
||||
|
@ -555,6 +558,9 @@ func (q *queue) nextChunkFileForRead() error {
|
|||
if n := q.readerOffset % q.chunkFileSize; n > 0 {
|
||||
q.readerOffset += q.chunkFileSize - n
|
||||
}
|
||||
if err := q.checkReaderWriterOffsets(); err != nil {
|
||||
return err
|
||||
}
|
||||
q.readerLocalOffset = 0
|
||||
q.readerPath = q.chunkFilePath(q.readerOffset)
|
||||
r, err := filestream.Open(q.readerPath, true)
|
||||
|
@ -598,6 +604,14 @@ func (q *queue) readFull(buf []byte) error {
|
|||
}
|
||||
q.readerLocalOffset += bufLen
|
||||
q.readerOffset += bufLen
|
||||
return q.checkReaderWriterOffsets()
|
||||
}
|
||||
|
||||
func (q *queue) checkReaderWriterOffsets() error {
|
||||
if q.readerOffset > q.writerOffset {
|
||||
return fmt.Errorf("readerOffset=%d cannot exceed writerOffset=%d; it is likely persistent queue files were corrupted on unclean shutdown",
|
||||
q.readerOffset, q.writerOffset)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -82,7 +82,7 @@ func TestParseRelabelConfigsSuccess(t *testing.T) {
|
|||
t.Helper()
|
||||
pcs, err := ParseRelabelConfigs(rcs, false)
|
||||
if err != nil {
|
||||
t.Fatalf("unexected error: %s", err)
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
if !reflect.DeepEqual(pcs, pcsExpected) {
|
||||
t.Fatalf("unexpected pcs; got\n%#v\nwant\n%#v", pcs, pcsExpected)
|
||||
|
|
|
@ -126,13 +126,14 @@ func (sw *ScrapeWork) canSwitchToStreamParseMode() bool {
|
|||
|
||||
// key returns unique identifier for the given sw.
|
||||
//
|
||||
// it can be used for comparing for equality for two ScrapeWork objects.
|
||||
// It can be used for comparing for equality for two ScrapeWork objects.
|
||||
func (sw *ScrapeWork) key() string {
|
||||
// Do not take into account OriginalLabels.
|
||||
key := fmt.Sprintf("ScrapeURL=%s, ScrapeInterval=%s, ScrapeTimeout=%s, HonorLabels=%v, HonorTimestamps=%v, DenyRedirects=%v, Labels=%s, "+
|
||||
// Do not take into account OriginalLabels, since they can be changed with relabeling.
|
||||
// Take into account JobNameOriginal in order to capture the case when the original job_name is changed via relabeling.
|
||||
key := fmt.Sprintf("JobNameOriginal=%s, ScrapeURL=%s, ScrapeInterval=%s, ScrapeTimeout=%s, HonorLabels=%v, HonorTimestamps=%v, DenyRedirects=%v, Labels=%s, "+
|
||||
"ProxyURL=%s, ProxyAuthConfig=%s, AuthConfig=%s, MetricRelabelConfigs=%s, SampleLimit=%d, DisableCompression=%v, DisableKeepAlive=%v, StreamParse=%v, "+
|
||||
"ScrapeAlignInterval=%s, ScrapeOffset=%s, SeriesLimit=%d",
|
||||
sw.ScrapeURL, sw.ScrapeInterval, sw.ScrapeTimeout, sw.HonorLabels, sw.HonorTimestamps, sw.DenyRedirects, sw.LabelsString(),
|
||||
sw.jobNameOriginal, sw.ScrapeURL, sw.ScrapeInterval, sw.ScrapeTimeout, sw.HonorLabels, sw.HonorTimestamps, sw.DenyRedirects, sw.LabelsString(),
|
||||
sw.ProxyURL.String(), sw.ProxyAuthConfig.String(),
|
||||
sw.AuthConfig.String(), sw.MetricRelabelConfigs.String(), sw.SampleLimit, sw.DisableCompression, sw.DisableKeepAlive, sw.StreamParse,
|
||||
sw.ScrapeAlignInterval, sw.ScrapeOffset, sw.SeriesLimit)
|
||||
|
|
|
@ -362,7 +362,7 @@ func TestNextRetentionDuration(t *testing.T) {
|
|||
if d <= 0 {
|
||||
currTime := time.Now().UTC()
|
||||
nextTime := time.Now().UTC().Add(d)
|
||||
t.Fatalf("unexected retention duration for retentionMonths=%f; got %s; must be %s + %f months", retentionMonths, nextTime, currTime, retentionMonths)
|
||||
t.Fatalf("unexpected retention duration for retentionMonths=%f; got %s; must be %s + %f months", retentionMonths, nextTime, currTime, retentionMonths)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue