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 GitHub
parent ee42f18dcb
commit 4d9715f5a8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 76 additions and 109 deletions

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`.
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:
@ -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.
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.

View file

@ -1,12 +1,12 @@
{
"files": {
"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",
"index.html": "./index.html"
},
"entrypoints": [
"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} />
</Box>
<Box>
<FormControlLabel label="Enable autocomplete"
<FormControlLabel label="Autocomplete"
control={<BasicSwitch checked={autocomplete} onChange={onChangeAutocomplete}/>}
/>
</Box>

View file

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

View file

@ -18,7 +18,7 @@ import Trace from "./Trace/Trace";
const CustomPanel: FC = () => {
const [tracingsData, setTracingData] = useState<Trace[]>([]);
const [tracesState, setTracesState] = useState<Trace[]>([]);
const {displayType, time: {period}, query, queryControls: {isTracingEnabled}} = useAppState();
const { customStep, yaxis } = useGraphState();
@ -38,24 +38,24 @@ const CustomPanel: FC = () => {
};
const {queryOptions} = useFetchQueryOptions();
const {isLoading, liveData, graphData, error, tracingData} = useFetchQuery({
const {isLoading, liveData, graphData, error, traces} = useFetchQuery({
visible: true,
customStep
});
const handleTraceDelete = (tracingData: Trace) => {
const updatedTracings = tracingsData.filter((data) => data.idValue !== tracingData.idValue);
setTracingData([...updatedTracings]);
const handleTraceDelete = (trace: Trace) => {
const updatedTraces = tracesState.filter((data) => data.idValue !== trace.idValue);
setTracesState([...updatedTraces]);
};
useEffect(() => {
if (tracingData) {
setTracingData([...tracingsData, tracingData]);
if (traces) {
setTracesState([...tracesState, ...traces]);
}
}, [tracingData]);
}, [traces]);
useEffect(() => {
setTracingData([]);
setTracesState([]);
}, [displayType]);
return (
@ -78,7 +78,7 @@ const CustomPanel: FC = () => {
{error && <Alert color="error" severity="error" sx={{whiteSpace: "pre-wrap", mt: 2}}>{error}</Alert>}
{graphData && period && (displayType === "chart") && <>
{isTracingEnabled && <TracingsView
tracingsData={tracingsData}
traces={tracesState}
onDeleteClick={handleTraceDelete}
/>}
<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 === "table") && <>
{isTracingEnabled && <TracingsView
tracingsData={tracingsData}
traces={tracesState}
onDeleteClick={handleTraceDelete}
/>}
<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 ListItem from "@mui/material/ListItem";
import ListItemText from "@mui/material/ListItemText";
@ -14,20 +14,28 @@ import Trace from "../Trace/Trace";
interface RecursiveProps {
trace: Trace;
totalMsec: number;
openLevels: Record<number, boolean>;
onChange: (level: number) => void;
}
const NestedNav: FC<RecursiveProps> = ({ trace, openLevels, totalMsec, onChange}) => {
const handleListClick = (traceID: number) => () => onChange(traceID);
interface OpenLevels {
[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 progress = trace.duration / totalMsec * 100;
return (
<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}}>
{hasChildren ? <ListItemIcon>
{openLevels[trace.duration] ?
{openLevels[trace.idValue] ?
<ExpandLess fontSize={"large"} color={"info"} /> :
<AddCircleRoundedIcon fontSize={"large"} color={"info"} />}
</ListItemIcon>: null}
@ -43,15 +51,13 @@ const NestedNav: FC<RecursiveProps> = ({ trace, openLevels, totalMsec, onChange}
</ListItemButton>
</ListItem>
<>
<Collapse in={openLevels[trace.duration]} timeout="auto" unmountOnExit>
<Collapse in={openLevels[trace.idValue]} timeout="auto" unmountOnExit>
<List component="div" disablePadding sx={{ pl: 4 }}>
{hasChildren ?
trace.children.map((trace) => <NestedNav
key={trace.duration}
trace={trace}
openLevels={openLevels}
totalMsec={totalMsec}
onChange={onChange}
/>) : null}
</List>
</Collapse>

View file

@ -1,33 +1,18 @@
import {TracingData} from "../../../api/types";
let traceId = 0;
export default class Trace {
private readonly tracing: TracingData;
private readonly tracingChildren: Trace[];
private readonly query: string;
private readonly id: number;
constructor(tracingData: TracingData, query: string) {
this.tracing = tracingData;
this.query = query;
this.id = new Date().getTime();
}
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, "");
this.id = traceId++;
const children = tracingData.children || [];
this.tracingChildren = children.map((x: TracingData) => new Trace(x, query));
}
get queryValue(): string {
@ -37,8 +22,7 @@ export default class Trace {
return this.id;
}
get children(): Trace[] {
const arr: Trace[] = [];
return this.recursiveMap(this.tracing.children, this.createTrace, arr);
return this.tracingChildren;
}
get message(): string {
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 NestedNav from "../NestedNav/NestedNav";
import Trace from "../Trace/Trace";
@ -7,23 +7,10 @@ interface TraceViewProps {
trace: Trace;
}
interface OpenLevels {
[x: number]: boolean
}
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">
<NestedNav
trace={trace}
openLevels={openLevels}
totalMsec={trace.duration}
onChange={handleClick}
/>
<NestedNav trace={trace} totalMsec={trace.duration} />
</List>);
};

View file

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

View file

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

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`.
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:
@ -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.
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.

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)
and [cluster resizing and scalability docs](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#cluster-resizing-and-scalability)
for details.