vmui: update render logic for nested component (#2795)

* vmui: update render logic for nested component, avoid rerender, remove local storage usage for tracing flag

* docs/url-examples.md: fix various documentation issues there

* docs: add Troubleshooting doc

This doc contains troubleshooting guides for typical problems with VictoriaMetrics.

* docs/Troubleshooting.md: add troubleshooting guide for cluster instability

* wip

* wip

Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
This commit is contained in:
Dmytro Kozlov 2022-06-30 15:47:12 +03:00 committed by Aliaksandr Valialkin
parent 2a70a9296e
commit 67753cc6f0
No known key found for this signature in database
GPG key ID: A72BEC6CD3D0DED1
17 changed files with 74 additions and 107 deletions

View file

@ -1,12 +1,12 @@
{ {
"files": { "files": {
"main.css": "./static/css/main.7e6d0c89.css", "main.css": "./static/css/main.7e6d0c89.css",
"main.js": "./static/js/main.645fe611.js", "main.js": "./static/js/main.84a0d8f8.js",
"static/js/27.939f971b.chunk.js": "./static/js/27.939f971b.chunk.js", "static/js/27.939f971b.chunk.js": "./static/js/27.939f971b.chunk.js",
"index.html": "./index.html" "index.html": "./index.html"
}, },
"entrypoints": [ "entrypoints": [
"static/css/main.7e6d0c89.css", "static/css/main.7e6d0c89.css",
"static/js/main.645fe611.js" "static/js/main.84a0d8f8.js"
] ]
} }

View file

@ -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 src="./dashboards/index.js" type="module"></script><script defer="defer" src="./static/js/main.645fe611.js"></script><link href="./static/css/main.7e6d0c89.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 src="./dashboards/index.js" type="module"></script><script defer="defer" src="./static/js/main.84a0d8f8.js"></script><link href="./static/css/main.7e6d0c89.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -81,7 +81,7 @@ const CardinalityConfigurator: FC<CardinalityConfiguratorProps> = ({
onChange={onFocusLabelChange} /> onChange={onFocusLabelChange} />
</Box> </Box>
<Box> <Box>
<FormControlLabel label="Enable autocomplete" <FormControlLabel label="Autocomplete"
control={<BasicSwitch checked={autocomplete} onChange={onChangeAutocomplete}/>} control={<BasicSwitch checked={autocomplete} onChange={onChangeAutocomplete}/>}
/> />
</Box> </Box>

View file

@ -32,17 +32,17 @@ const AdditionalSettings: FC = () => {
return <Box display="flex" alignItems="center"> return <Box display="flex" alignItems="center">
<Box> <Box>
<FormControlLabel label="Enable autocomplete" <FormControlLabel label="Autocomplete"
control={<BasicSwitch checked={autocomplete} onChange={onChangeAutocomplete}/>} control={<BasicSwitch checked={autocomplete} onChange={onChangeAutocomplete}/>}
/> />
</Box> </Box>
<Box ml={2}> <Box ml={2}>
<FormControlLabel label="Enable cache" <FormControlLabel label="Disable cache"
control={<BasicSwitch checked={!nocache} onChange={onChangeCache}/>} control={<BasicSwitch checked={nocache} onChange={onChangeCache}/>}
/> />
</Box> </Box>
<Box ml={2}> <Box ml={2}>
<FormControlLabel label="Enable query tracing" <FormControlLabel label="Trace query"
control={<BasicSwitch checked={isTracingEnabled} onChange={onChangeQueryTracing} />} control={<BasicSwitch checked={isTracingEnabled} onChange={onChangeQueryTracing} />}
/> />
</Box> </Box>

View file

@ -18,7 +18,7 @@ import Trace from "./Trace/Trace";
const CustomPanel: FC = () => { const CustomPanel: FC = () => {
const [tracingsData, setTracingData] = useState<Trace[]>([]); const [tracesState, setTracesState] = useState<Trace[]>([]);
const {displayType, time: {period}, query, queryControls: {isTracingEnabled}} = useAppState(); const {displayType, time: {period}, query, queryControls: {isTracingEnabled}} = useAppState();
const { customStep, yaxis } = useGraphState(); const { customStep, yaxis } = useGraphState();
@ -38,24 +38,24 @@ const CustomPanel: FC = () => {
}; };
const {queryOptions} = useFetchQueryOptions(); const {queryOptions} = useFetchQueryOptions();
const {isLoading, liveData, graphData, error, tracingData} = useFetchQuery({ const {isLoading, liveData, graphData, error, traces} = useFetchQuery({
visible: true, visible: true,
customStep customStep
}); });
const handleTraceDelete = (tracingData: Trace) => { const handleTraceDelete = (trace: Trace) => {
const updatedTracings = tracingsData.filter((data) => data.idValue !== tracingData.idValue); const updatedTraces = tracesState.filter((data) => data.idValue !== trace.idValue);
setTracingData([...updatedTracings]); setTracesState([...updatedTraces]);
}; };
useEffect(() => { useEffect(() => {
if (tracingData) { if (traces) {
setTracingData([...tracingsData, tracingData]); setTracesState([...tracesState, ...traces]);
} }
}, [tracingData]); }, [traces]);
useEffect(() => { useEffect(() => {
setTracingData([]); setTracesState([]);
}, [displayType]); }, [displayType]);
return ( return (
@ -78,7 +78,7 @@ const CustomPanel: FC = () => {
{error && <Alert color="error" severity="error" sx={{whiteSpace: "pre-wrap", mt: 2}}>{error}</Alert>} {error && <Alert color="error" severity="error" sx={{whiteSpace: "pre-wrap", mt: 2}}>{error}</Alert>}
{graphData && period && (displayType === "chart") && <> {graphData && period && (displayType === "chart") && <>
{isTracingEnabled && <TracingsView {isTracingEnabled && <TracingsView
tracingsData={tracingsData} traces={tracesState}
onDeleteClick={handleTraceDelete} onDeleteClick={handleTraceDelete}
/>} />}
<GraphView data={graphData} period={period} customStep={customStep} query={query} yaxis={yaxis} <GraphView data={graphData} period={period} customStep={customStep} query={query} yaxis={yaxis}
@ -87,7 +87,7 @@ const CustomPanel: FC = () => {
{liveData && (displayType === "code") && <JsonView data={liveData}/>} {liveData && (displayType === "code") && <JsonView data={liveData}/>}
{liveData && (displayType === "table") && <> {liveData && (displayType === "table") && <>
{isTracingEnabled && <TracingsView {isTracingEnabled && <TracingsView
tracingsData={tracingsData} traces={tracesState}
onDeleteClick={handleTraceDelete} onDeleteClick={handleTraceDelete}
/>} />}
<TableView data={liveData}/> <TableView data={liveData}/>

View file

@ -1,4 +1,4 @@
import React, {FC} from "preact/compat"; import React, {FC, useState} from "preact/compat";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import ListItem from "@mui/material/ListItem"; import ListItem from "@mui/material/ListItem";
import ListItemText from "@mui/material/ListItemText"; import ListItemText from "@mui/material/ListItemText";
@ -14,20 +14,28 @@ import Trace from "../Trace/Trace";
interface RecursiveProps { interface RecursiveProps {
trace: Trace; trace: Trace;
totalMsec: number; totalMsec: number;
openLevels: Record<number, boolean>;
onChange: (level: number) => void;
} }
const NestedNav: FC<RecursiveProps> = ({ trace, openLevels, totalMsec, onChange}) => { interface OpenLevels {
const handleListClick = (traceID: number) => () => onChange(traceID); [x: number]: boolean
}
const NestedNav: FC<RecursiveProps> = ({ trace, totalMsec}) => {
const [openLevels, setOpenLevels] = useState({} as OpenLevels);
const handleListClick = (level: number) => () => {
setOpenLevels((prevState:OpenLevels) => {
return {...prevState, [level]: !prevState[level]};
});
};
const hasChildren = trace.children && trace.children.length; const hasChildren = trace.children && trace.children.length;
const progress = trace.duration / totalMsec * 100; const progress = trace.duration / totalMsec * 100;
return ( return (
<Box sx={{ bgcolor: "rgba(201, 227, 246, 0.4)" }}> <Box sx={{ bgcolor: "rgba(201, 227, 246, 0.4)" }}>
<ListItem onClick={handleListClick(trace.duration)} sx={!hasChildren ? {p:0, pl: 7} : {p:0}}> <ListItem onClick={handleListClick(trace.idValue)} sx={!hasChildren ? {p:0, pl: 7} : {p:0}}>
<ListItemButton alignItems={"flex-start"} sx={{ pt: 0, pb: 0}}> <ListItemButton alignItems={"flex-start"} sx={{ pt: 0, pb: 0}}>
{hasChildren ? <ListItemIcon> {hasChildren ? <ListItemIcon>
{openLevels[trace.duration] ? {openLevels[trace.idValue] ?
<ExpandLess fontSize={"large"} color={"info"} /> : <ExpandLess fontSize={"large"} color={"info"} /> :
<AddCircleRoundedIcon fontSize={"large"} color={"info"} />} <AddCircleRoundedIcon fontSize={"large"} color={"info"} />}
</ListItemIcon>: null} </ListItemIcon>: null}
@ -43,15 +51,13 @@ const NestedNav: FC<RecursiveProps> = ({ trace, openLevels, totalMsec, onChange}
</ListItemButton> </ListItemButton>
</ListItem> </ListItem>
<> <>
<Collapse in={openLevels[trace.duration]} timeout="auto" unmountOnExit> <Collapse in={openLevels[trace.idValue]} timeout="auto" unmountOnExit>
<List component="div" disablePadding sx={{ pl: 4 }}> <List component="div" disablePadding sx={{ pl: 4 }}>
{hasChildren ? {hasChildren ?
trace.children.map((trace) => <NestedNav trace.children.map((trace) => <NestedNav
key={trace.duration} key={trace.duration}
trace={trace} trace={trace}
openLevels={openLevels}
totalMsec={totalMsec} totalMsec={totalMsec}
onChange={onChange}
/>) : null} />) : null}
</List> </List>
</Collapse> </Collapse>

View file

@ -1,33 +1,18 @@
import {TracingData} from "../../../api/types"; import {TracingData} from "../../../api/types";
let traceId = 0;
export default class Trace { export default class Trace {
private readonly tracing: TracingData; private readonly tracing: TracingData;
private readonly tracingChildren: Trace[];
private readonly query: string; private readonly query: string;
private readonly id: number; private readonly id: number;
constructor(tracingData: TracingData, query: string) { constructor(tracingData: TracingData, query: string) {
this.tracing = tracingData; this.tracing = tracingData;
this.query = query; this.query = query;
this.id = new Date().getTime(); this.id = traceId++;
} const children = tracingData.children || [];
this.tracingChildren = children.map((x: TracingData) => new Trace(x, query));
recursiveMap(oldArray: TracingData[], callback: (tr: TracingData) => Trace, newArray: Trace[]): Trace[] {
if (!oldArray) return [];
//base case: check if there are any items left in the original array to process
if (oldArray && oldArray.length <= 0){
//if all items have been processed return the new array
return newArray;
} else {
//destructure the first item from old array and put remaining in a separate array
const [item, ...theRest] = oldArray;
// create an array of the current new array and the result of the current item and the callback function
const interimArray = [...newArray, callback(item)];
// return a recursive call to to map to process the next item.
return this.recursiveMap(theRest, callback, interimArray);
}
}
createTrace(traceData: TracingData) {
return new Trace(traceData, "");
} }
get queryValue(): string { get queryValue(): string {
@ -37,8 +22,7 @@ export default class Trace {
return this.id; return this.id;
} }
get children(): Trace[] { get children(): Trace[] {
const arr: Trace[] = []; return this.tracingChildren;
return this.recursiveMap(this.tracing.children, this.createTrace, arr);
} }
get message(): string { get message(): string {
return this.tracing.message; return this.tracing.message;

View file

@ -1,4 +1,4 @@
import React, {FC, useState} from "preact/compat"; import React, {FC} from "preact/compat";
import List from "@mui/material/List"; import List from "@mui/material/List";
import NestedNav from "../NestedNav/NestedNav"; import NestedNav from "../NestedNav/NestedNav";
import Trace from "../Trace/Trace"; import Trace from "../Trace/Trace";
@ -7,23 +7,10 @@ interface TraceViewProps {
trace: Trace; trace: Trace;
} }
interface OpenLevels {
[x: number]: boolean
}
const TraceView: FC<TraceViewProps> = ({trace}) => { const TraceView: FC<TraceViewProps> = ({trace}) => {
const [openLevels, setOpenLevels] = useState({} as OpenLevels);
const handleClick = (level: number) => {
setOpenLevels((prevState:OpenLevels) => ({...prevState, [level]: !prevState[level]}));
};
return (<List sx={{ width: "100%" }} component="nav"> return (<List sx={{ width: "100%" }} component="nav">
<NestedNav <NestedNav trace={trace} totalMsec={trace.duration} />
trace={trace}
openLevels={openLevels}
totalMsec={trace.duration}
onChange={handleClick}
/>
</List>); </List>);
}; };

View file

@ -7,17 +7,15 @@ import Button from "@mui/material/Button";
import Trace from "../Trace/Trace"; import Trace from "../Trace/Trace";
interface TraceViewProps { interface TraceViewProps {
tracingsData: Trace[]; traces: Trace[];
onDeleteClick: (tracingData: Trace) => void; onDeleteClick: (trace: Trace) => void;
} }
const EMPTY_MESSAGE = "Please re-run the query to see results of the tracing"; const TracingsView: FC<TraceViewProps> = ({traces, onDeleteClick}) => {
if (!traces.length) {
const TracingsView: FC<TraceViewProps> = ({tracingsData, onDeleteClick}) => {
if (!tracingsData.length) {
return ( return (
<Alert color={"info"} severity="info" sx={{whiteSpace: "pre-wrap", mt: 2}}> <Alert color={"info"} severity="info" sx={{whiteSpace: "pre-wrap", mt: 2}}>
{EMPTY_MESSAGE} Please re-run the query to see results of the tracing
</Alert> </Alert>
); );
} }
@ -26,14 +24,14 @@ const TracingsView: FC<TraceViewProps> = ({tracingsData, onDeleteClick}) => {
onDeleteClick(tracingData); onDeleteClick(tracingData);
}; };
return <>{tracingsData.map((tracingData) => <> return <>{traces.map((trace: Trace) => <>
<Typography variant="h4" gutterBottom component="div"> <Typography variant="h5" component="div">
{"Tracing for"} {tracingData.queryValue} Trace for <b>{trace.queryValue}</b>
<Button onClick={handleDeleteClick(tracingData)}> <Button onClick={handleDeleteClick(trace)}>
<RemoveCircleIcon fontSize={"large"} color={"error"} /> <RemoveCircleIcon fontSize={"medium"} color={"error"} />
</Button> </Button>
</Typography> </Typography>
<TraceView trace={tracingData} /> <TraceView trace={trace} />
</>)}</>; </>)}</>;
}; };

View file

@ -1,7 +1,7 @@
import {useCallback, useEffect, useMemo, useState} from "preact/compat"; import {useCallback, useEffect, useMemo, useState} from "preact/compat";
import {getQueryRangeUrl, getQueryUrl} from "../api/query-range"; import {getQueryRangeUrl, getQueryUrl} from "../api/query-range";
import {useAppState} from "../state/common/StateContext"; import {useAppState} from "../state/common/StateContext";
import {InstantMetricResult, MetricBase, MetricResult, TracingData} from "../api/types"; import {InstantMetricResult, MetricBase, MetricResult} from "../api/types";
import {isValidHttpUrl} from "../utils/url"; import {isValidHttpUrl} from "../utils/url";
import {ErrorTypes} from "../types"; import {ErrorTypes} from "../types";
import {getAppModeEnable, getAppModeParams} from "../utils/app-mode"; import {getAppModeEnable, getAppModeParams} from "../utils/app-mode";
@ -28,14 +28,14 @@ export const useFetchQuery = ({predefinedQuery, visible, display, customStep}: F
graphData?: MetricResult[], graphData?: MetricResult[],
liveData?: InstantMetricResult[], liveData?: InstantMetricResult[],
error?: ErrorTypes | string, error?: ErrorTypes | string,
tracingData?: Trace, traces?: Trace[],
} => { } => {
const {query, displayType, serverUrl, time: {period}, queryControls: {nocache, isTracingEnabled}} = useAppState(); const {query, displayType, serverUrl, time: {period}, queryControls: {nocache, isTracingEnabled}} = useAppState();
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [graphData, setGraphData] = useState<MetricResult[]>(); const [graphData, setGraphData] = useState<MetricResult[]>();
const [liveData, setLiveData] = useState<InstantMetricResult[]>(); const [liveData, setLiveData] = useState<InstantMetricResult[]>();
const [tracingData, setTracingData] = useState<Trace>(); const [traces, setTraces] = useState<Trace[]>();
const [error, setError] = useState<ErrorTypes | string>(); const [error, setError] = useState<ErrorTypes | string>();
const [fetchQueue, setFetchQueue] = useState<AbortController[]>([]); const [fetchQueue, setFetchQueue] = useState<AbortController[]>([]);
@ -43,33 +43,26 @@ export const useFetchQuery = ({predefinedQuery, visible, display, customStep}: F
if (error) { if (error) {
setGraphData(undefined); setGraphData(undefined);
setLiveData(undefined); setLiveData(undefined);
setTracingData(undefined); setTraces(undefined);
} }
}, [error]); }, [error]);
const updateTracingData = (tracing: TracingData, queries: string[]) => {
if (tracing) {
queries.forEach((query) => {
const {message} = tracing;
if (message.includes(query)) {
setTracingData(new Trace(tracing, query));
}
});
}
};
const fetchData = async (fetchUrl: string[], fetchQueue: AbortController[], displayType: DisplayType, query: string[]) => { const fetchData = async (fetchUrl: string[], fetchQueue: AbortController[], displayType: DisplayType, query: string[]) => {
const controller = new AbortController(); const controller = new AbortController();
setFetchQueue([...fetchQueue, controller]); setFetchQueue([...fetchQueue, controller]);
try { try {
const responses = await Promise.all(fetchUrl.map(url => fetch(url, {signal: controller.signal}))); const responses = await Promise.all(fetchUrl.map(url => fetch(url, {signal: controller.signal})));
const tempData = []; const tempData = [];
const tempTraces: Trace[] = [];
let counter = 1; let counter = 1;
for await (const response of responses) { for await (const response of responses) {
const resp = await response.json(); const resp = await response.json();
if (response.ok) { if (response.ok) {
setError(undefined); setError(undefined);
updateTracingData(resp.trace, query); if (resp.trace) {
const trace = new Trace(resp.trace, query[counter-1]);
tempTraces.push(trace);
}
tempData.push(...resp.data.result.map((d: MetricBase) => { tempData.push(...resp.data.result.map((d: MetricBase) => {
d.group = counter; d.group = counter;
return d; return d;
@ -80,6 +73,7 @@ export const useFetchQuery = ({predefinedQuery, visible, display, customStep}: F
} }
} }
displayType === "chart" ? setGraphData(tempData) : setLiveData(tempData); displayType === "chart" ? setGraphData(tempData) : setLiveData(tempData);
setTraces(tempTraces);
} catch (e) { } catch (e) {
if (e instanceof Error && e.name !== "AbortError") { if (e instanceof Error && e.name !== "AbortError") {
setError(`${e.name}: ${e.message}`); setError(`${e.name}: ${e.message}`);
@ -127,5 +121,5 @@ export const useFetchQuery = ({predefinedQuery, visible, display, customStep}: F
setFetchQueue(fetchQueue.filter(f => !f.signal.aborted)); setFetchQueue(fetchQueue.filter(f => !f.signal.aborted));
}, [fetchQueue]); }, [fetchQueue]);
return {fetchUrl, isLoading, graphData, liveData, error, tracingData}; return {fetchUrl, isLoading, graphData, liveData, error, traces};
}; };

View file

@ -80,8 +80,8 @@ export const initialState: AppState = {
queryControls: { queryControls: {
autoRefresh: false, autoRefresh: false,
autocomplete: getFromStorage("AUTOCOMPLETE") as boolean || false, autocomplete: getFromStorage("AUTOCOMPLETE") as boolean || false,
nocache: getFromStorage("NO_CACHE") as boolean || false, nocache: false,
isTracingEnabled: getFromStorage("QUERY_TRACING") as boolean || false, isTracingEnabled: false,
} }
}; };

View file

@ -244,7 +244,7 @@ Prometheus doesn't drop data during VictoriaMetrics restart. See [this article](
VictoriaMetrics provides UI for query troubleshooting and exploration. The UI is available at `http://victoriametrics:8428/vmui`. VictoriaMetrics provides UI for query troubleshooting and exploration. The UI is available at `http://victoriametrics:8428/vmui`.
The UI allows exploring query results via graphs and tables. The UI allows exploring query results via graphs and tables.
It also provides the ability to [explore cardinality](#cardinality-explorer) and to [investigate query tracec](#query-tracing). It also provides the ability to [explore cardinality](#cardinality-explorer) and to [investigate query traces](#query-tracing).
Graphs in vmui support scrolling and zooming: Graphs in vmui support scrolling and zooming:
@ -255,7 +255,7 @@ Query history can be navigated by holding `Ctrl` (or `Cmd` on MacOS) and pressin
Multi-line queries can be entered by pressing `Shift-Enter` in query input field. Multi-line queries can be entered by pressing `Shift-Enter` in query input field.
When querying the [backfilled data](https://docs.victoriametrics.com/#backfilling), it may be useful disabling response cache by clicking `Enable cache` checkbox. When querying the [backfilled data](https://docs.victoriametrics.com/#backfilling), it may be useful disabling response cache by clicking `Disable cache` checkbox.
VMUI automatically adjusts the interval between datapoints on the graph depending on the horizontal resolution and on the selected time range. The step value can be customized by clickhing `Override step value` checkbox. VMUI automatically adjusts the interval between datapoints on the graph depending on the horizontal resolution and on the selected time range. The step value can be customized by clickhing `Override step value` checkbox.

View file

@ -248,7 +248,7 @@ Prometheus doesn't drop data during VictoriaMetrics restart. See [this article](
VictoriaMetrics provides UI for query troubleshooting and exploration. The UI is available at `http://victoriametrics:8428/vmui`. VictoriaMetrics provides UI for query troubleshooting and exploration. The UI is available at `http://victoriametrics:8428/vmui`.
The UI allows exploring query results via graphs and tables. The UI allows exploring query results via graphs and tables.
It also provides the ability to [explore cardinality](#cardinality-explorer) and to [investigate query tracec](#query-tracing). It also provides the ability to [explore cardinality](#cardinality-explorer) and to [investigate query traces](#query-tracing).
Graphs in vmui support scrolling and zooming: Graphs in vmui support scrolling and zooming:
@ -259,7 +259,7 @@ Query history can be navigated by holding `Ctrl` (or `Cmd` on MacOS) and pressin
Multi-line queries can be entered by pressing `Shift-Enter` in query input field. Multi-line queries can be entered by pressing `Shift-Enter` in query input field.
When querying the [backfilled data](https://docs.victoriametrics.com/#backfilling), it may be useful disabling response cache by clicking `Enable cache` checkbox. When querying the [backfilled data](https://docs.victoriametrics.com/#backfilling), it may be useful disabling response cache by clicking `Disable cache` checkbox.
VMUI automatically adjusts the interval between datapoints on the graph depending on the horizontal resolution and on the selected time range. The step value can be customized by clickhing `Override step value` checkbox. VMUI automatically adjusts the interval between datapoints on the graph depending on the horizontal resolution and on the selected time range. The step value can be customized by clickhing `Override step value` checkbox.

View file

@ -273,5 +273,3 @@ have enough free resources for graceful processing the increased workload.
See [capacity planning docs](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#capacity-planning) See [capacity planning docs](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#capacity-planning)
and [cluster resizing and scalability docs](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#cluster-resizing-and-scalability) and [cluster resizing and scalability docs](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#cluster-resizing-and-scalability)
for details. for details.