mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
vmui: added query tracing (#2748)
* vmui: added query tracing * vmui: updated ui * vmui: update tracing logic, fix bugs, disable tracing by default * vmui: use empty message as props * vmui: fixed ui, added delete for each tacing data, show query in header * vmui: added timelines * vmui: speedup render * vmui: use memo for sorting * vmui: use Trace model, remove unused functions, simplify part of code * vmui: update recursive logic * vmui: fix set query to header * vmui: code cleanup, remove unused code * vmui: remove unused type, rename component * wip * wip Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
This commit is contained in:
parent
7f1c73bdaf
commit
bb7f31541f
20 changed files with 305 additions and 42 deletions
|
@ -243,7 +243,8 @@ Prometheus doesn't drop data during VictoriaMetrics restart. See [this article](
|
|||
## 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. It also provides support for [cardinality explorer](#cardinality-explorer).
|
||||
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).
|
||||
|
||||
Graphs in vmui support scrolling and zooming:
|
||||
|
||||
|
@ -1470,6 +1471,7 @@ VictoriaMetrics provides an UI on top of `/api/v1/status/tsdb` - see [cardinalit
|
|||
## Query tracing
|
||||
|
||||
VictoriaMetrics supports query tracing, which can be used for determining bottlenecks during query processing.
|
||||
This is like `EXPLAIN ANALYZE` from Postgresql.
|
||||
|
||||
Query tracing can be enabled for a specific query by passing `trace=1` query arg.
|
||||
In this case VictoriaMetrics puts query trace into `trace` field in the output JSON.
|
||||
|
@ -1529,6 +1531,8 @@ All the durations and timestamps in traces are in milliseconds.
|
|||
|
||||
Query tracing is allowed by default. It can be denied by passing `-denyQueryTracing` command-line flag to VictoriaMetrics.
|
||||
|
||||
[VMUI](#vmui) provides an UI for query tracing - just click `Enable query tracing` checkbox and re-run the query in order to investigate its' trace.
|
||||
|
||||
|
||||
## Cardinality limiter
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"files": {
|
||||
"main.css": "./static/css/main.7e6d0c89.css",
|
||||
"main.js": "./static/js/main.fdf5a65f.js",
|
||||
"main.js": "./static/js/main.645fe611.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.fdf5a65f.js"
|
||||
"static/js/main.645fe611.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 src="./dashboards/index.js" type="module"></script><script defer="defer" src="./static/js/main.fdf5a65f.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.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>
|
2
app/vmselect/vmui/static/js/main.645fe611.js
Normal file
2
app/vmselect/vmui/static/js/main.645fe611.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
|
@ -1,9 +1,9 @@
|
|||
import {TimeParams} from "../types";
|
||||
|
||||
export const getQueryRangeUrl = (server: string, query: string, period: TimeParams, nocache: boolean): string =>
|
||||
`${server}/api/v1/query_range?query=${encodeURIComponent(query)}&start=${period.start}&end=${period.end}&step=${period.step}${nocache ? "&nocache=1" : ""}`;
|
||||
export const getQueryRangeUrl = (server: string, query: string, period: TimeParams, nocache: boolean, queryTracing: boolean): string =>
|
||||
`${server}/api/v1/query_range?query=${encodeURIComponent(query)}&start=${period.start}&end=${period.end}&step=${period.step}${nocache ? "&nocache=1" : ""}${queryTracing ? "&trace=1" : ""}`;
|
||||
|
||||
export const getQueryUrl = (server: string, query: string, period: TimeParams): string =>
|
||||
`${server}/api/v1/query?query=${encodeURIComponent(query)}&start=${period.start}&end=${period.end}&step=${period.step}`;
|
||||
export const getQueryUrl = (server: string, query: string, period: TimeParams, queryTracing: boolean): string =>
|
||||
`${server}/api/v1/query?query=${encodeURIComponent(query)}&start=${period.start}&end=${period.end}&step=${period.step}${queryTracing ? "&trace=1" : ""}`;
|
||||
|
||||
export const getQueryOptions = (server: string) => `${server}/api/v1/label/__name__/values`;
|
||||
|
|
|
@ -14,10 +14,8 @@ export interface InstantMetricResult extends MetricBase {
|
|||
value: [number, string]
|
||||
}
|
||||
|
||||
export interface QueryRangeResponse {
|
||||
status: string;
|
||||
data: {
|
||||
result: MetricResult[];
|
||||
resultType: "matrix";
|
||||
}
|
||||
}
|
||||
export interface TracingData {
|
||||
message: string;
|
||||
duration_msec: number;
|
||||
children: TracingData[];
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ const AdditionalSettings: FC = () => {
|
|||
const {customStep} = useGraphState();
|
||||
const graphDispatch = useGraphDispatch();
|
||||
|
||||
const {queryControls: {autocomplete, nocache}, time: {period: {step}}} = useAppState();
|
||||
const {queryControls: {autocomplete, nocache, isTracingEnabled}, time: {period: {step}}} = useAppState();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const onChangeAutocomplete = () => {
|
||||
|
@ -25,6 +25,11 @@ const AdditionalSettings: FC = () => {
|
|||
saveToStorage("NO_CACHE", !nocache);
|
||||
};
|
||||
|
||||
const onChangeQueryTracing = () => {
|
||||
dispatch({type: "TOGGLE_QUERY_TRACING"});
|
||||
saveToStorage("QUERY_TRACING", !isTracingEnabled);
|
||||
};
|
||||
|
||||
return <Box display="flex" alignItems="center">
|
||||
<Box>
|
||||
<FormControlLabel label="Enable autocomplete"
|
||||
|
@ -36,6 +41,11 @@ const AdditionalSettings: FC = () => {
|
|||
control={<BasicSwitch checked={!nocache} onChange={onChangeCache}/>}
|
||||
/>
|
||||
</Box>
|
||||
<Box ml={2}>
|
||||
<FormControlLabel label="Enable query tracing"
|
||||
control={<BasicSwitch checked={isTracingEnabled} onChange={onChangeQueryTracing} />}
|
||||
/>
|
||||
</Box>
|
||||
<Box ml={2}>
|
||||
<StepConfigurator defaultStep={step} customStepEnable={customStep.enable}
|
||||
setStep={(value) => {
|
||||
|
@ -48,4 +58,4 @@ const AdditionalSettings: FC = () => {
|
|||
</Box>;
|
||||
};
|
||||
|
||||
export default AdditionalSettings;
|
||||
export default AdditionalSettings;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, {FC} from "preact/compat";
|
||||
import React, {FC, useState, useEffect} from "preact/compat";
|
||||
import Alert from "@mui/material/Alert";
|
||||
import Box from "@mui/material/Box";
|
||||
import GraphView from "./Views/GraphView";
|
||||
|
@ -13,10 +13,13 @@ import {useGraphDispatch, useGraphState} from "../../state/graph/GraphStateConte
|
|||
import {AxisRange} from "../../state/graph/reducer";
|
||||
import Spinner from "../common/Spinner";
|
||||
import {useFetchQueryOptions} from "../../hooks/useFetchQueryOptions";
|
||||
import TracingsView from "./Views/TracingsView";
|
||||
import Trace from "./Trace/Trace";
|
||||
|
||||
const CustomPanel: FC = () => {
|
||||
|
||||
const {displayType, time: {period}, query} = useAppState();
|
||||
const [tracingsData, setTracingData] = useState<Trace[]>([]);
|
||||
const {displayType, time: {period}, query, queryControls: {isTracingEnabled}} = useAppState();
|
||||
const { customStep, yaxis } = useGraphState();
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
@ -35,11 +38,26 @@ const CustomPanel: FC = () => {
|
|||
};
|
||||
|
||||
const {queryOptions} = useFetchQueryOptions();
|
||||
const {isLoading, liveData, graphData, error} = useFetchQuery({
|
||||
const {isLoading, liveData, graphData, error, tracingData} = useFetchQuery({
|
||||
visible: true,
|
||||
customStep
|
||||
});
|
||||
|
||||
const handleTraceDelete = (tracingData: Trace) => {
|
||||
const updatedTracings = tracingsData.filter((data) => data.idValue !== tracingData.idValue);
|
||||
setTracingData([...updatedTracings]);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (tracingData) {
|
||||
setTracingData([...tracingsData, tracingData]);
|
||||
}
|
||||
}, [tracingData]);
|
||||
|
||||
useEffect(() => {
|
||||
setTracingData([]);
|
||||
}, [displayType]);
|
||||
|
||||
return (
|
||||
<Box p={4} display="grid" gridTemplateRows="auto 1fr" style={{minHeight: "calc(100vh - 64px)"}}>
|
||||
<QueryConfigurator error={error} queryOptions={queryOptions}/>
|
||||
|
@ -49,18 +67,31 @@ const CustomPanel: FC = () => {
|
|||
<Box display="grid" gridTemplateColumns="1fr auto" alignItems="center" mx={-4} px={4} mb={2}
|
||||
borderBottom={1} borderColor="divider">
|
||||
<DisplayTypeSwitch/>
|
||||
{displayType === "chart" && <GraphSettings
|
||||
yaxis={yaxis}
|
||||
setYaxisLimits={setYaxisLimits}
|
||||
toggleEnableLimits={toggleEnableLimits}
|
||||
/>}
|
||||
<Box display={"flex"}>
|
||||
{displayType === "chart" && <GraphSettings
|
||||
yaxis={yaxis}
|
||||
setYaxisLimits={setYaxisLimits}
|
||||
toggleEnableLimits={toggleEnableLimits}
|
||||
/>}
|
||||
</Box>
|
||||
</Box>
|
||||
{error && <Alert color="error" severity="error" sx={{whiteSpace: "pre-wrap", mt: 2}}>{error}</Alert>}
|
||||
{graphData && period && (displayType === "chart") &&
|
||||
{graphData && period && (displayType === "chart") && <>
|
||||
{isTracingEnabled && <TracingsView
|
||||
tracingsData={tracingsData}
|
||||
onDeleteClick={handleTraceDelete}
|
||||
/>}
|
||||
<GraphView data={graphData} period={period} customStep={customStep} query={query} yaxis={yaxis}
|
||||
setYaxisLimits={setYaxisLimits} setPeriod={setPeriod}/>}
|
||||
setYaxisLimits={setYaxisLimits} setPeriod={setPeriod}/>
|
||||
</>}
|
||||
{liveData && (displayType === "code") && <JsonView data={liveData}/>}
|
||||
{liveData && (displayType === "table") && <TableView data={liveData}/>}
|
||||
{liveData && (displayType === "table") && <>
|
||||
{isTracingEnabled && <TracingsView
|
||||
tracingsData={tracingsData}
|
||||
onDeleteClick={handleTraceDelete}
|
||||
/>}
|
||||
<TableView data={liveData}/>
|
||||
</>}
|
||||
</Box>}
|
||||
</Box>
|
||||
</Box>
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
import React, {FC} from "preact/compat";
|
||||
import Box from "@mui/material/Box";
|
||||
import ListItem from "@mui/material/ListItem";
|
||||
import ListItemText from "@mui/material/ListItemText";
|
||||
import ListItemButton from "@mui/material/ListItemButton";
|
||||
import ListItemIcon from "@mui/material/ListItemIcon";
|
||||
import ExpandLess from "@mui/icons-material/ExpandLess";
|
||||
import AddCircleRoundedIcon from "@mui/icons-material/AddCircleRounded";
|
||||
import Collapse from "@mui/material/Collapse";
|
||||
import List from "@mui/material/List";
|
||||
import {BorderLinearProgressWithLabel} from "../../BorderLineProgress/BorderLinearProgress";
|
||||
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);
|
||||
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}}>
|
||||
<ListItemButton alignItems={"flex-start"} sx={{ pt: 0, pb: 0}}>
|
||||
{hasChildren ? <ListItemIcon>
|
||||
{openLevels[trace.duration] ?
|
||||
<ExpandLess fontSize={"large"} color={"info"} /> :
|
||||
<AddCircleRoundedIcon fontSize={"large"} color={"info"} />}
|
||||
</ListItemIcon>: null}
|
||||
<Box display="flex" flexDirection="column" flexGrow={0.5} sx={{ ml: 4, mr: 4, width: "100%" }}>
|
||||
<ListItemText
|
||||
primary={`duration: ${trace.duration} ms`}
|
||||
secondary={trace.message}
|
||||
/>
|
||||
<ListItemText>
|
||||
<BorderLinearProgressWithLabel variant="determinate" value={progress} />
|
||||
</ListItemText>
|
||||
</Box>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<>
|
||||
<Collapse in={openLevels[trace.duration]} 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>
|
||||
</>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default NestedNav;
|
|
@ -0,0 +1,49 @@
|
|||
import {TracingData} from "../../../api/types";
|
||||
|
||||
export default class Trace {
|
||||
private readonly tracing: TracingData;
|
||||
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, "");
|
||||
}
|
||||
|
||||
get queryValue(): string {
|
||||
return this.query;
|
||||
}
|
||||
get idValue(): number {
|
||||
return this.id;
|
||||
}
|
||||
get children(): Trace[] {
|
||||
const arr: Trace[] = [];
|
||||
return this.recursiveMap(this.tracing.children, this.createTrace, arr);
|
||||
}
|
||||
get message(): string {
|
||||
return this.tracing.message;
|
||||
}
|
||||
get duration(): number {
|
||||
return this.tracing.duration_msec;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
import React, {FC, useState} from "preact/compat";
|
||||
import List from "@mui/material/List";
|
||||
import NestedNav from "../NestedNav/NestedNav";
|
||||
import Trace from "../Trace/Trace";
|
||||
|
||||
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}
|
||||
/>
|
||||
</List>);
|
||||
};
|
||||
|
||||
export default TraceView;
|
|
@ -0,0 +1,40 @@
|
|||
import React, {FC} from "preact/compat";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import TraceView from "./TraceView";
|
||||
import Alert from "@mui/material/Alert";
|
||||
import RemoveCircleIcon from "@mui/icons-material/RemoveCircle";
|
||||
import Button from "@mui/material/Button";
|
||||
import Trace from "../Trace/Trace";
|
||||
|
||||
interface TraceViewProps {
|
||||
tracingsData: Trace[];
|
||||
onDeleteClick: (tracingData: 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) {
|
||||
return (
|
||||
<Alert color={"info"} severity="info" sx={{whiteSpace: "pre-wrap", mt: 2}}>
|
||||
{EMPTY_MESSAGE}
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
const handleDeleteClick = (tracingData: Trace) => () => {
|
||||
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"} />
|
||||
</Button>
|
||||
</Typography>
|
||||
<TraceView trace={tracingData} />
|
||||
</>)}</>;
|
||||
};
|
||||
|
||||
export default TracingsView;
|
|
@ -1,7 +1,7 @@
|
|||
import {useEffect, useMemo, useCallback, useState} from "preact/compat";
|
||||
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} from "../api/types";
|
||||
import {InstantMetricResult, MetricBase, MetricResult, TracingData} from "../api/types";
|
||||
import {isValidHttpUrl} from "../utils/url";
|
||||
import {ErrorTypes} from "../types";
|
||||
import {getAppModeEnable, getAppModeParams} from "../utils/app-mode";
|
||||
|
@ -10,6 +10,7 @@ import {DisplayType} from "../components/CustomPanel/Configurator/DisplayTypeSwi
|
|||
import {CustomStep} from "../state/graph/reducer";
|
||||
import usePrevious from "./usePrevious";
|
||||
import {arrayEquals} from "../utils/array";
|
||||
import Trace from "../components/CustomPanel/Trace/Trace";
|
||||
|
||||
interface FetchQueryParams {
|
||||
predefinedQuery?: string[]
|
||||
|
@ -27,12 +28,14 @@ export const useFetchQuery = ({predefinedQuery, visible, display, customStep}: F
|
|||
graphData?: MetricResult[],
|
||||
liveData?: InstantMetricResult[],
|
||||
error?: ErrorTypes | string,
|
||||
tracingData?: Trace,
|
||||
} => {
|
||||
const {query, displayType, serverUrl, time: {period}, queryControls: {nocache}} = useAppState();
|
||||
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 [error, setError] = useState<ErrorTypes | string>();
|
||||
const [fetchQueue, setFetchQueue] = useState<AbortController[]>([]);
|
||||
|
||||
|
@ -40,10 +43,22 @@ export const useFetchQuery = ({predefinedQuery, visible, display, customStep}: F
|
|||
if (error) {
|
||||
setGraphData(undefined);
|
||||
setLiveData(undefined);
|
||||
setTracingData(undefined);
|
||||
}
|
||||
}, [error]);
|
||||
|
||||
const fetchData = async (fetchUrl: string[], fetchQueue: AbortController[], displayType: DisplayType) => {
|
||||
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 {
|
||||
|
@ -54,6 +69,7 @@ export const useFetchQuery = ({predefinedQuery, visible, display, customStep}: F
|
|||
const resp = await response.json();
|
||||
if (response.ok) {
|
||||
setError(undefined);
|
||||
updateTracingData(resp.trace, query);
|
||||
tempData.push(...resp.data.result.map((d: MetricBase) => {
|
||||
d.group = counter;
|
||||
return d;
|
||||
|
@ -87,8 +103,8 @@ export const useFetchQuery = ({predefinedQuery, visible, display, customStep}: F
|
|||
const updatedPeriod = {...period};
|
||||
if (customStep.enable) updatedPeriod.step = customStep.value;
|
||||
return expr.filter(q => q.trim()).map(q => displayChart
|
||||
? getQueryRangeUrl(server, q, updatedPeriod, nocache)
|
||||
: getQueryUrl(server, q, updatedPeriod));
|
||||
? getQueryRangeUrl(server, q, updatedPeriod, nocache, isTracingEnabled)
|
||||
: getQueryUrl(server, q, updatedPeriod, isTracingEnabled));
|
||||
} else {
|
||||
setError(ErrorTypes.validServer);
|
||||
}
|
||||
|
@ -100,7 +116,8 @@ export const useFetchQuery = ({predefinedQuery, visible, display, customStep}: F
|
|||
useEffect(() => {
|
||||
if (!visible || (fetchUrl && prevFetchUrl && arrayEquals(fetchUrl, prevFetchUrl)) || !fetchUrl?.length) return;
|
||||
setIsLoading(true);
|
||||
throttledFetchData(fetchUrl, fetchQueue, (display || displayType));
|
||||
const expr = predefinedQuery ?? query;
|
||||
throttledFetchData(fetchUrl, fetchQueue, (display || displayType), expr);
|
||||
}, [fetchUrl, visible]);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -110,5 +127,5 @@ export const useFetchQuery = ({predefinedQuery, visible, display, customStep}: F
|
|||
setFetchQueue(fetchQueue.filter(f => !f.signal.aborted));
|
||||
}, [fetchQueue]);
|
||||
|
||||
return { fetchUrl, isLoading, graphData, liveData, error };
|
||||
return {fetchUrl, isLoading, graphData, liveData, error, tracingData};
|
||||
};
|
||||
|
|
|
@ -34,8 +34,9 @@ export interface AppState {
|
|||
queryHistory: QueryHistory[],
|
||||
queryControls: {
|
||||
autoRefresh: boolean;
|
||||
autocomplete: boolean,
|
||||
nocache: boolean
|
||||
autocomplete: boolean;
|
||||
nocache: boolean;
|
||||
isTracingEnabled: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,6 +56,7 @@ export type Action =
|
|||
| { type: "TOGGLE_AUTOREFRESH"}
|
||||
| { type: "TOGGLE_AUTOCOMPLETE"}
|
||||
| { type: "NO_CACHE"}
|
||||
| { type: "TOGGLE_QUERY_TRACING" }
|
||||
|
||||
|
||||
const {duration, endInput, relativeTimeId} = getRelativeTime({
|
||||
|
@ -79,6 +81,7 @@ export const initialState: AppState = {
|
|||
autoRefresh: false,
|
||||
autocomplete: getFromStorage("AUTOCOMPLETE") as boolean || false,
|
||||
nocache: getFromStorage("NO_CACHE") as boolean || false,
|
||||
isTracingEnabled: getFromStorage("QUERY_TRACING") as boolean || false,
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -187,6 +190,14 @@ export function reducer(state: AppState, action: Action): AppState {
|
|||
autocomplete: !state.queryControls.autocomplete
|
||||
}
|
||||
};
|
||||
case "TOGGLE_QUERY_TRACING":
|
||||
return {
|
||||
...state,
|
||||
queryControls: {
|
||||
...state.queryControls,
|
||||
isTracingEnabled: !state.queryControls.isTracingEnabled,
|
||||
}
|
||||
};
|
||||
case "NO_CACHE":
|
||||
return {
|
||||
...state,
|
||||
|
|
|
@ -3,6 +3,7 @@ export type StorageKeys = "BASIC_AUTH_DATA"
|
|||
| "AUTH_TYPE"
|
||||
| "AUTOCOMPLETE"
|
||||
| "NO_CACHE"
|
||||
| "QUERY_TRACING"
|
||||
|
||||
export const saveToStorage = (key: StorageKeys, value: string | boolean | Record<string, unknown>): void => {
|
||||
if (value) {
|
||||
|
|
|
@ -17,6 +17,7 @@ The following tip changes can be tested by building VictoriaMetrics components f
|
|||
|
||||
* FEATURE: add `-search.setLookbackToStep` command-line flag, which enables InfluxDB-like gap filling during querying. See [these docs](https://docs.victoriametrics.com/guides/migrate-from-influx.html) for details.
|
||||
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): add ability to specify additional HTTP headers to send to scrape targets via `headers` section in `scrape_configs`. This can be used when the scrape target requires custom authorization and authentication like in [this stackoverflow question](https://stackoverflow.com/questions/66032498/prometheus-scrape-metric-with-custom-header). For example, the following config instructs sending `My-Auth: top-secret` and `TenantID: FooBar` headers with each request to `http://host123:8080/metrics`:
|
||||
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add an UI for [query tracing](https://docs.victoriametrics.com/#query-tracing). It can be enabled by clicking `enable query tracing` checkbox and re-running the query. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2703).
|
||||
|
||||
```yaml
|
||||
scrape_configs:
|
||||
|
|
|
@ -243,7 +243,8 @@ Prometheus doesn't drop data during VictoriaMetrics restart. See [this article](
|
|||
## 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. It also provides support for [cardinality explorer](#cardinality-explorer).
|
||||
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).
|
||||
|
||||
Graphs in vmui support scrolling and zooming:
|
||||
|
||||
|
@ -1470,6 +1471,7 @@ VictoriaMetrics provides an UI on top of `/api/v1/status/tsdb` - see [cardinalit
|
|||
## Query tracing
|
||||
|
||||
VictoriaMetrics supports query tracing, which can be used for determining bottlenecks during query processing.
|
||||
This is like `EXPLAIN ANALYZE` from Postgresql.
|
||||
|
||||
Query tracing can be enabled for a specific query by passing `trace=1` query arg.
|
||||
In this case VictoriaMetrics puts query trace into `trace` field in the output JSON.
|
||||
|
@ -1529,6 +1531,8 @@ All the durations and timestamps in traces are in milliseconds.
|
|||
|
||||
Query tracing is allowed by default. It can be denied by passing `-denyQueryTracing` command-line flag to VictoriaMetrics.
|
||||
|
||||
[VMUI](#vmui) provides an UI for query tracing - just click `Enable query tracing` checkbox and re-run the query in order to investigate its' trace.
|
||||
|
||||
|
||||
## Cardinality limiter
|
||||
|
||||
|
|
|
@ -247,7 +247,8 @@ Prometheus doesn't drop data during VictoriaMetrics restart. See [this article](
|
|||
## 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. It also provides support for [cardinality explorer](#cardinality-explorer).
|
||||
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).
|
||||
|
||||
Graphs in vmui support scrolling and zooming:
|
||||
|
||||
|
@ -1474,6 +1475,7 @@ VictoriaMetrics provides an UI on top of `/api/v1/status/tsdb` - see [cardinalit
|
|||
## Query tracing
|
||||
|
||||
VictoriaMetrics supports query tracing, which can be used for determining bottlenecks during query processing.
|
||||
This is like `EXPLAIN ANALYZE` from Postgresql.
|
||||
|
||||
Query tracing can be enabled for a specific query by passing `trace=1` query arg.
|
||||
In this case VictoriaMetrics puts query trace into `trace` field in the output JSON.
|
||||
|
@ -1533,6 +1535,8 @@ All the durations and timestamps in traces are in milliseconds.
|
|||
|
||||
Query tracing is allowed by default. It can be denied by passing `-denyQueryTracing` command-line flag to VictoriaMetrics.
|
||||
|
||||
[VMUI](#vmui) provides an UI for query tracing - just click `Enable query tracing` checkbox and re-run the query in order to investigate its' trace.
|
||||
|
||||
|
||||
## Cardinality limiter
|
||||
|
||||
|
|
Loading…
Reference in a new issue