mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-12-01 14:47:38 +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
ee5c502446
commit
f28cbcc7b5
19 changed files with 300 additions and 41 deletions
|
@ -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.fdf5a65f.js",
|
"main.js": "./static/js/main.645fe611.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.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";
|
import {TimeParams} from "../types";
|
||||||
|
|
||||||
export const getQueryRangeUrl = (server: string, query: string, period: TimeParams, nocache: boolean): string =>
|
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" : ""}`;
|
`${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 =>
|
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}`;
|
`${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`;
|
export const getQueryOptions = (server: string) => `${server}/api/v1/label/__name__/values`;
|
||||||
|
|
|
@ -14,10 +14,8 @@ export interface InstantMetricResult extends MetricBase {
|
||||||
value: [number, string]
|
value: [number, string]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QueryRangeResponse {
|
export interface TracingData {
|
||||||
status: string;
|
message: string;
|
||||||
data: {
|
duration_msec: number;
|
||||||
result: MetricResult[];
|
children: TracingData[];
|
||||||
resultType: "matrix";
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -12,7 +12,7 @@ const AdditionalSettings: FC = () => {
|
||||||
const {customStep} = useGraphState();
|
const {customStep} = useGraphState();
|
||||||
const graphDispatch = useGraphDispatch();
|
const graphDispatch = useGraphDispatch();
|
||||||
|
|
||||||
const {queryControls: {autocomplete, nocache}, time: {period: {step}}} = useAppState();
|
const {queryControls: {autocomplete, nocache, isTracingEnabled}, time: {period: {step}}} = useAppState();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const onChangeAutocomplete = () => {
|
const onChangeAutocomplete = () => {
|
||||||
|
@ -25,6 +25,11 @@ const AdditionalSettings: FC = () => {
|
||||||
saveToStorage("NO_CACHE", !nocache);
|
saveToStorage("NO_CACHE", !nocache);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onChangeQueryTracing = () => {
|
||||||
|
dispatch({type: "TOGGLE_QUERY_TRACING"});
|
||||||
|
saveToStorage("QUERY_TRACING", !isTracingEnabled);
|
||||||
|
};
|
||||||
|
|
||||||
return <Box display="flex" alignItems="center">
|
return <Box display="flex" alignItems="center">
|
||||||
<Box>
|
<Box>
|
||||||
<FormControlLabel label="Enable autocomplete"
|
<FormControlLabel label="Enable autocomplete"
|
||||||
|
@ -36,6 +41,11 @@ const AdditionalSettings: FC = () => {
|
||||||
control={<BasicSwitch checked={!nocache} onChange={onChangeCache}/>}
|
control={<BasicSwitch checked={!nocache} onChange={onChangeCache}/>}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
<Box ml={2}>
|
||||||
|
<FormControlLabel label="Enable query tracing"
|
||||||
|
control={<BasicSwitch checked={isTracingEnabled} onChange={onChangeQueryTracing} />}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
<Box ml={2}>
|
<Box ml={2}>
|
||||||
<StepConfigurator defaultStep={step} customStepEnable={customStep.enable}
|
<StepConfigurator defaultStep={step} customStepEnable={customStep.enable}
|
||||||
setStep={(value) => {
|
setStep={(value) => {
|
||||||
|
|
|
@ -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 Alert from "@mui/material/Alert";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import GraphView from "./Views/GraphView";
|
import GraphView from "./Views/GraphView";
|
||||||
|
@ -13,10 +13,13 @@ import {useGraphDispatch, useGraphState} from "../../state/graph/GraphStateConte
|
||||||
import {AxisRange} from "../../state/graph/reducer";
|
import {AxisRange} from "../../state/graph/reducer";
|
||||||
import Spinner from "../common/Spinner";
|
import Spinner from "../common/Spinner";
|
||||||
import {useFetchQueryOptions} from "../../hooks/useFetchQueryOptions";
|
import {useFetchQueryOptions} from "../../hooks/useFetchQueryOptions";
|
||||||
|
import TracingsView from "./Views/TracingsView";
|
||||||
|
import Trace from "./Trace/Trace";
|
||||||
|
|
||||||
const CustomPanel: FC = () => {
|
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 { customStep, yaxis } = useGraphState();
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
@ -35,11 +38,26 @@ const CustomPanel: FC = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const {queryOptions} = useFetchQueryOptions();
|
const {queryOptions} = useFetchQueryOptions();
|
||||||
const {isLoading, liveData, graphData, error} = useFetchQuery({
|
const {isLoading, liveData, graphData, error, tracingData} = useFetchQuery({
|
||||||
visible: true,
|
visible: true,
|
||||||
customStep
|
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 (
|
return (
|
||||||
<Box p={4} display="grid" gridTemplateRows="auto 1fr" style={{minHeight: "calc(100vh - 64px)"}}>
|
<Box p={4} display="grid" gridTemplateRows="auto 1fr" style={{minHeight: "calc(100vh - 64px)"}}>
|
||||||
<QueryConfigurator error={error} queryOptions={queryOptions}/>
|
<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}
|
<Box display="grid" gridTemplateColumns="1fr auto" alignItems="center" mx={-4} px={4} mb={2}
|
||||||
borderBottom={1} borderColor="divider">
|
borderBottom={1} borderColor="divider">
|
||||||
<DisplayTypeSwitch/>
|
<DisplayTypeSwitch/>
|
||||||
|
<Box display={"flex"}>
|
||||||
{displayType === "chart" && <GraphSettings
|
{displayType === "chart" && <GraphSettings
|
||||||
yaxis={yaxis}
|
yaxis={yaxis}
|
||||||
setYaxisLimits={setYaxisLimits}
|
setYaxisLimits={setYaxisLimits}
|
||||||
toggleEnableLimits={toggleEnableLimits}
|
toggleEnableLimits={toggleEnableLimits}
|
||||||
/>}
|
/>}
|
||||||
</Box>
|
</Box>
|
||||||
|
</Box>
|
||||||
{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
|
||||||
|
tracingsData={tracingsData}
|
||||||
|
onDeleteClick={handleTraceDelete}
|
||||||
|
/>}
|
||||||
<GraphView data={graphData} period={period} customStep={customStep} query={query} yaxis={yaxis}
|
<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 === "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>
|
</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 {getQueryRangeUrl, getQueryUrl} from "../api/query-range";
|
||||||
import {useAppState} from "../state/common/StateContext";
|
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 {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";
|
||||||
|
@ -10,6 +10,7 @@ import {DisplayType} from "../components/CustomPanel/Configurator/DisplayTypeSwi
|
||||||
import {CustomStep} from "../state/graph/reducer";
|
import {CustomStep} from "../state/graph/reducer";
|
||||||
import usePrevious from "./usePrevious";
|
import usePrevious from "./usePrevious";
|
||||||
import {arrayEquals} from "../utils/array";
|
import {arrayEquals} from "../utils/array";
|
||||||
|
import Trace from "../components/CustomPanel/Trace/Trace";
|
||||||
|
|
||||||
interface FetchQueryParams {
|
interface FetchQueryParams {
|
||||||
predefinedQuery?: string[]
|
predefinedQuery?: string[]
|
||||||
|
@ -27,12 +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,
|
||||||
} => {
|
} => {
|
||||||
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 [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 [error, setError] = useState<ErrorTypes | string>();
|
const [error, setError] = useState<ErrorTypes | string>();
|
||||||
const [fetchQueue, setFetchQueue] = useState<AbortController[]>([]);
|
const [fetchQueue, setFetchQueue] = useState<AbortController[]>([]);
|
||||||
|
|
||||||
|
@ -40,10 +43,22 @@ export const useFetchQuery = ({predefinedQuery, visible, display, customStep}: F
|
||||||
if (error) {
|
if (error) {
|
||||||
setGraphData(undefined);
|
setGraphData(undefined);
|
||||||
setLiveData(undefined);
|
setLiveData(undefined);
|
||||||
|
setTracingData(undefined);
|
||||||
}
|
}
|
||||||
}, [error]);
|
}, [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();
|
const controller = new AbortController();
|
||||||
setFetchQueue([...fetchQueue, controller]);
|
setFetchQueue([...fetchQueue, controller]);
|
||||||
try {
|
try {
|
||||||
|
@ -54,6 +69,7 @@ export const useFetchQuery = ({predefinedQuery, visible, display, customStep}: F
|
||||||
const resp = await response.json();
|
const resp = await response.json();
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
|
updateTracingData(resp.trace, query);
|
||||||
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;
|
||||||
|
@ -87,8 +103,8 @@ export const useFetchQuery = ({predefinedQuery, visible, display, customStep}: F
|
||||||
const updatedPeriod = {...period};
|
const updatedPeriod = {...period};
|
||||||
if (customStep.enable) updatedPeriod.step = customStep.value;
|
if (customStep.enable) updatedPeriod.step = customStep.value;
|
||||||
return expr.filter(q => q.trim()).map(q => displayChart
|
return expr.filter(q => q.trim()).map(q => displayChart
|
||||||
? getQueryRangeUrl(server, q, updatedPeriod, nocache)
|
? getQueryRangeUrl(server, q, updatedPeriod, nocache, isTracingEnabled)
|
||||||
: getQueryUrl(server, q, updatedPeriod));
|
: getQueryUrl(server, q, updatedPeriod, isTracingEnabled));
|
||||||
} else {
|
} else {
|
||||||
setError(ErrorTypes.validServer);
|
setError(ErrorTypes.validServer);
|
||||||
}
|
}
|
||||||
|
@ -100,7 +116,8 @@ export const useFetchQuery = ({predefinedQuery, visible, display, customStep}: F
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!visible || (fetchUrl && prevFetchUrl && arrayEquals(fetchUrl, prevFetchUrl)) || !fetchUrl?.length) return;
|
if (!visible || (fetchUrl && prevFetchUrl && arrayEquals(fetchUrl, prevFetchUrl)) || !fetchUrl?.length) return;
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
throttledFetchData(fetchUrl, fetchQueue, (display || displayType));
|
const expr = predefinedQuery ?? query;
|
||||||
|
throttledFetchData(fetchUrl, fetchQueue, (display || displayType), expr);
|
||||||
}, [fetchUrl, visible]);
|
}, [fetchUrl, visible]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -110,5 +127,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 };
|
return {fetchUrl, isLoading, graphData, liveData, error, tracingData};
|
||||||
};
|
};
|
||||||
|
|
|
@ -34,8 +34,9 @@ export interface AppState {
|
||||||
queryHistory: QueryHistory[],
|
queryHistory: QueryHistory[],
|
||||||
queryControls: {
|
queryControls: {
|
||||||
autoRefresh: boolean;
|
autoRefresh: boolean;
|
||||||
autocomplete: boolean,
|
autocomplete: boolean;
|
||||||
nocache: boolean
|
nocache: boolean;
|
||||||
|
isTracingEnabled: boolean;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +56,7 @@ export type Action =
|
||||||
| { type: "TOGGLE_AUTOREFRESH"}
|
| { type: "TOGGLE_AUTOREFRESH"}
|
||||||
| { type: "TOGGLE_AUTOCOMPLETE"}
|
| { type: "TOGGLE_AUTOCOMPLETE"}
|
||||||
| { type: "NO_CACHE"}
|
| { type: "NO_CACHE"}
|
||||||
|
| { type: "TOGGLE_QUERY_TRACING" }
|
||||||
|
|
||||||
|
|
||||||
const {duration, endInput, relativeTimeId} = getRelativeTime({
|
const {duration, endInput, relativeTimeId} = getRelativeTime({
|
||||||
|
@ -79,6 +81,7 @@ export const initialState: AppState = {
|
||||||
autoRefresh: false,
|
autoRefresh: false,
|
||||||
autocomplete: getFromStorage("AUTOCOMPLETE") as boolean || false,
|
autocomplete: getFromStorage("AUTOCOMPLETE") as boolean || false,
|
||||||
nocache: getFromStorage("NO_CACHE") 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
|
autocomplete: !state.queryControls.autocomplete
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
case "TOGGLE_QUERY_TRACING":
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
queryControls: {
|
||||||
|
...state.queryControls,
|
||||||
|
isTracingEnabled: !state.queryControls.isTracingEnabled,
|
||||||
|
}
|
||||||
|
};
|
||||||
case "NO_CACHE":
|
case "NO_CACHE":
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|
|
@ -3,6 +3,7 @@ export type StorageKeys = "BASIC_AUTH_DATA"
|
||||||
| "AUTH_TYPE"
|
| "AUTH_TYPE"
|
||||||
| "AUTOCOMPLETE"
|
| "AUTOCOMPLETE"
|
||||||
| "NO_CACHE"
|
| "NO_CACHE"
|
||||||
|
| "QUERY_TRACING"
|
||||||
|
|
||||||
export const saveToStorage = (key: StorageKeys, value: string | boolean | Record<string, unknown>): void => {
|
export const saveToStorage = (key: StorageKeys, value: string | boolean | Record<string, unknown>): void => {
|
||||||
if (value) {
|
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: 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: [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
|
```yaml
|
||||||
scrape_configs:
|
scrape_configs:
|
||||||
|
|
|
@ -243,7 +243,8 @@ Prometheus doesn't drop data during VictoriaMetrics restart. See [this article](
|
||||||
## vmui
|
## vmui
|
||||||
|
|
||||||
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. 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:
|
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
|
## Query tracing
|
||||||
|
|
||||||
VictoriaMetrics supports query tracing, which can be used for determining bottlenecks during query processing.
|
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.
|
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.
|
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.
|
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
|
## Cardinality limiter
|
||||||
|
|
||||||
|
|
|
@ -247,7 +247,8 @@ Prometheus doesn't drop data during VictoriaMetrics restart. See [this article](
|
||||||
## vmui
|
## vmui
|
||||||
|
|
||||||
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. 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:
|
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
|
## Query tracing
|
||||||
|
|
||||||
VictoriaMetrics supports query tracing, which can be used for determining bottlenecks during query processing.
|
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.
|
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.
|
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.
|
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
|
## Cardinality limiter
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue