vmui: add trace analyzer (#3310)

* refactor: change structure project

* refactor: change structure project

* fix: add hooks for set query params

* refactor: add index for pages

* docs: add TESTCASES.md

* refactor: restructure components

* feat: add page with trace analyzer

* fix: change detect trace data

* Update app/vmui/packages/vmui/src/pages/TracePage/index.tsx

Co-authored-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>

* Update app/vmui/packages/vmui/src/pages/TracePage/index.tsx

Co-authored-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>

* fix: change descriptions on trace page

* Update app/vmui/packages/vmui/src/pages/TracePage/index.tsx

Co-authored-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>

* feat: add base components

* feat: add reset styles

* docs: add description about trace analyzer

* feat: add styles for custom panel page

* feat: add styles for predefined panels

* feat: add style for TracingsView.tsx

* feat: add Alerts

* feat: add Tooltip.tsx

* fix: correct styles

* feat: add DatePicker.tsx

* feat: add tables

* feat: add theme provider

* fix: replace using callbacks as props to handlers

* fix: correct update time

* fix: change TimePicker.tsx

* fix: correct styles

* fix: update packages

* vmui: refactor code, remove material-ui

* feat: add paste json for trace analyzer

* vmui: update trace analyzer docs

* app/vmselect/vmui: `make vmui-update`

Co-authored-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
This commit is contained in:
Yury Molodov 2022-11-17 21:22:01 +01:00 committed by GitHub
parent e79bfdf4b8
commit 519bd2af7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 580 additions and 54 deletions

View File

@ -1644,7 +1644,9 @@ 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 `Trace query` checkbox and re-run the query in order to investigate its' trace.
[VMUI](#vmui) provides an UI:
- for query tracing - just click `Trace query` checkbox and re-run the query in order to investigate its' trace.
- for exploring custom trace - go to the tab `Trace analyzer` and upload or paste JSON with trace information.
## Cardinality limiter

View File

@ -1,12 +1,12 @@
{
"files": {
"main.css": "./static/css/main.f2cbb582.css",
"main.js": "./static/js/main.d0509bc3.js",
"main.css": "./static/css/main.44a8ea12.css",
"main.js": "./static/js/main.da9caa97.js",
"static/js/27.c1ccfd29.chunk.js": "./static/js/27.c1ccfd29.chunk.js",
"index.html": "./index.html"
},
"entrypoints": [
"static/css/main.f2cbb582.css",
"static/js/main.d0509bc3.js"
"static/css/main.44a8ea12.css",
"static/js/main.da9caa97.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="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono&family=Lato:wght@300;400;700&display=swap" rel="stylesheet"><script src="./dashboards/index.js" type="module"></script><script defer="defer" src="./static/js/main.d0509bc3.js"></script><link href="./static/css/main.f2cbb582.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="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono&family=Lato:wght@300;400;700&display=swap" rel="stylesheet"><script src="./dashboards/index.js" type="module"></script><script defer="defer" src="./static/js/main.da9caa97.js"></script><link href="./static/css/main.44a8ea12.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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -9,6 +9,7 @@ import CardinalityPanel from "./pages/CardinalityPanel";
import TopQueries from "./pages/TopQueries";
import ThemeProvider from "./components/Main/ThemeProvider/ThemeProvider";
import Spinner from "./components/Main/Spinner/Spinner";
import TracePage from "./pages/TracePage";
const App: FC = () => {
@ -45,6 +46,10 @@ const App: FC = () => {
path={router.topQueries}
element={<TopQueries/>}
/>
<Route
path={router.trace}
element={<TracePage/>}
/>
</Route>
</Routes>
</AppContextProvider>

View File

@ -43,6 +43,10 @@ const Header: FC = () => {
{
label: "Top queries",
value: router.topQueries,
},
{
label: "Trace analyzer",
value: router.trace,
}
]), [appModeEnable]);

View File

@ -4,7 +4,7 @@
position: relative;
display: grid;
grid-template-columns: 20px 1fr;
align-items: center;
align-items: flex-start;
gap: $padding-small;
padding: $padding-global;
background-color: $color-background-block;
@ -13,7 +13,7 @@
font-size: $font-size-medium;
font-weight: 500;
color: $color-text;
line-height: 1.3;
line-height: 20px;
&:after {
position: absolute;
@ -41,6 +41,7 @@
&__content {
filter: brightness(0.6);
white-space: pre-line;
}
&_success {

View File

@ -279,7 +279,6 @@ export const VisibilityIcon = () => (
</svg>
);
export const VisibilityOffIcon = () => (
<svg
viewBox="0 0 24 24"
@ -290,3 +289,14 @@ export const VisibilityOffIcon = () => (
></path>
</svg>
);
export const CopyIcon = () => (
<svg
viewBox="0 0 24 24"
fill="currentColor"
>
<path
d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"
></path>
</svg>
);

View File

@ -16,11 +16,9 @@ $padding-modal: 22px;
&-content {
padding: $padding-modal;
max-height: 85vh;
background: $color-white;
box-shadow: 0 0 24px rgba($color-black, 0.07);
border-radius: $border-radius-small;
overflow: hidden;
&-header {
display: grid;

View File

@ -64,7 +64,10 @@ const TextField: FC<TextFieldProps> = ({
}, [fieldRef, autofocus]);
return <label
className="vm-text-field"
className={classNames({
"vm-text-field": true,
"vm-text-field_textarea": type === "textarea",
})}
data-replicated-value={value}
>
{startIcon && <div className="vm-text-field__icon-start">{startIcon}</div>}

View File

@ -6,7 +6,7 @@
margin: 6px 0;
width: 100%;
&::after {
&_textarea:after {
content: attr(data-replicated-value) " ";
white-space: pre-wrap;
visibility: hidden;

View File

@ -3,12 +3,14 @@ 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 tracing: TracingData;
private query: string;
private tracingChildren: Trace[];
private readonly originalTracing: TracingData;
private readonly id: number;
constructor(tracingData: TracingData, query: string) {
this.tracing = tracingData;
this.originalTracing = JSON.parse(JSON.stringify(tracingData));
this.query = query;
this.id = traceId++;
const children = tracingData.children || [];
@ -30,4 +32,21 @@ export default class Trace {
get duration(): number {
return this.tracing.duration_msec;
}
get JSON(): string {
return JSON.stringify(this.tracing, null, 2);
}
get originalJSON(): string {
return JSON.stringify(this.originalTracing, null, 2);
}
setTracing (tracingData: TracingData) {
this.tracing = tracingData;
const children = tracingData.children || [];
this.tracingChildren = children.map((x: TracingData) => new Trace(x, this.query));
}
setQuery (query: string) {
this.query = query;
}
resetTracing () {
this.tracing = this.originalTracing;
}
}

View File

@ -1,18 +1,38 @@
import React, { FC } from "preact/compat";
import React, { FC, useState } from "preact/compat";
import Trace from "./Trace";
import Button from "../Main/Button/Button";
import { DeleteIcon } from "../Main/Icons";
import { CodeIcon, DeleteIcon } from "../Main/Icons";
import "./style.scss";
import NestedNav from "./NestedNav/NestedNav";
import Alert from "../Main/Alert/Alert";
import Tooltip from "../Main/Tooltip/Tooltip";
import Modal from "../Main/Modal/Modal";
import JsonForm from "../../pages/TracePage/JsonForm/JsonForm";
interface TraceViewProps {
traces: Trace[];
jsonEditor?: boolean;
onDeleteClick: (trace: Trace) => void;
}
const TracingsView: FC<TraceViewProps> = ({ traces, onDeleteClick }) => {
const TracingsView: FC<TraceViewProps> = ({ traces, jsonEditor = false, onDeleteClick }) => {
const [openTrace, setOpenTrace] = useState<Trace | null>(null);
const handleCloseJson = () => {
setOpenTrace(null);
};
const handleUpdateTrace = (val: string, title: string) => {
if (!jsonEditor || !openTrace) return;
try {
openTrace.setTracing(JSON.parse(val));
openTrace.setQuery(title);
setOpenTrace(null);
} catch (e) {
console.error(e);
}
};
if (!traces.length) {
return (
<Alert variant="info">
@ -25,34 +45,66 @@ const TracingsView: FC<TraceViewProps> = ({ traces, onDeleteClick }) => {
onDeleteClick(tracingData);
};
return <div className="vm-tracings-view">
{traces.map((trace: Trace) => (
<div
className="vm-tracings-view-trace vm-block vm-block_empty-padding"
key={trace.idValue}
>
<div className="vm-tracings-view-trace-header">
<h3 className="vm-tracings-view-trace-header-title">
Trace for <b className="vm-tracings-view-trace-header-title__query">{trace.queryValue}</b>
</h3>
<Tooltip title={"Remove trace"}>
<Button
variant="text"
color="error"
startIcon={<DeleteIcon/>}
onClick={handleDeleteClick(trace)}
/>
</Tooltip>
</div>
<nav className="vm-tracings-view-trace__nav">
<NestedNav
trace={trace}
totalMsec={trace.duration}
/>
</nav>
const handleJsonClick = (tracingData: Trace) => () => {
setOpenTrace(tracingData);
};
return (
<>
<div className="vm-tracings-view">
{traces.map((trace: Trace) => (
<div
className="vm-tracings-view-trace vm-block vm-block_empty-padding"
key={trace.idValue}
>
<div className="vm-tracings-view-trace-header">
<h3 className="vm-tracings-view-trace-header-title">
Trace for <b className="vm-tracings-view-trace-header-title__query">{trace.queryValue}</b>
</h3>
<Tooltip title={"Open JSON"}>
<Button
variant="text"
startIcon={<CodeIcon/>}
onClick={handleJsonClick(trace)}
/>
</Tooltip>
<Tooltip title={"Remove trace"}>
<Button
variant="text"
color="error"
startIcon={<DeleteIcon/>}
onClick={handleDeleteClick(trace)}
/>
</Tooltip>
</div>
<nav className="vm-tracings-view-trace__nav">
<NestedNav
trace={trace}
totalMsec={trace.duration}
/>
</nav>
</div>
))}
</div>
))}
</div>;
{openTrace && (
<Modal
title={openTrace.queryValue}
onClose={handleCloseJson}
>
<JsonForm
editable={jsonEditor}
displayTitle={jsonEditor}
defaultTile={openTrace.queryValue}
defaultJson={openTrace.JSON}
resetValue={openTrace.originalJSON}
onClose={handleCloseJson}
onUpload={handleUpdateTrace}
/>
</Modal>
)}
</>
);
};
export default TracingsView;

View File

@ -13,6 +13,7 @@
padding: $padding-small $padding-small $padding-small $padding-medium;
&-title {
flex-grow: 1;
font-size: $font-size-medium;
margin-right: $padding-small;

View File

@ -24,7 +24,6 @@ const JsonView: FC<JsonViewProps> = ({ data }) => {
<div className="vm-json-view__copy">
<Button
variant="outlined"
fullWidth={false}
onClick={handlerCopy}
>
Copy JSON

View File

@ -0,0 +1,140 @@
import React, { FC, useState, useMemo } from "preact/compat";
import TextField from "../../../components/Main/TextField/TextField";
import "./style.scss";
import Button from "../../../components/Main/Button/Button";
import Trace from "../../../components/TraceQuery/Trace";
import { ErrorTypes } from "../../../types";
import classNames from "classnames";
import { useSnack } from "../../../contexts/Snackbar";
import { CopyIcon, RestartIcon } from "../../../components/Main/Icons";
interface JsonFormProps {
defaultJson?: string
defaultTile?: string
displayTitle?: boolean
editable?: boolean
resetValue?: string
onUpload: (json: string, title: string) => void
onClose: () => void
}
const JsonForm: FC<JsonFormProps> = ({
editable = false,
defaultTile = "JSON",
displayTitle = true,
defaultJson = "",
resetValue= "",
onClose,
onUpload,
}) => {
const { showInfoMessage } = useSnack();
const [json, setJson] = useState(defaultJson);
const [title, setTitle] = useState(defaultTile);
const [errorTitle, setErrorTitle] = useState("");
const [error, setError] = useState("");
const errorJson = useMemo(() => {
try {
const resp = JSON.parse(json);
const traceData = resp.trace || resp;
if (!traceData.duration_msec) return ErrorTypes.traceNotFound;
new Trace(traceData, "");
return "";
} catch (e) {
return e instanceof Error ? e.message : "Unknown error";
}
}, [json]);
const handleChangeTitle = (val: string) => {
setTitle(val);
};
const handleChangeJson = (val: string) => {
setError("");
setJson(val);
};
const handlerCopy = async () => {
await navigator.clipboard.writeText(json);
showInfoMessage({ text: "Formatted JSON has been copied", type: "success" });
};
const handleReset = () => {
setJson(resetValue);
};
const handleApply = () => {
setError(errorJson);
const titleTrim = title.trim();
if (!titleTrim) setErrorTitle(ErrorTypes.emptyTitle);
if (errorJson || errorTitle) return;
onUpload(json, title);
onClose();
};
return (
<div
className={classNames({
"vm-json-form": true,
"vm-json-form_one-field": !displayTitle
})}
>
{displayTitle && (
<TextField
value={title}
label="Title"
error={errorTitle}
onEnter={handleApply}
onChange={handleChangeTitle}
/>
)}
<TextField
value={json}
label="JSON"
type="textarea"
error={error}
autofocus
onChange={handleChangeJson}
disabled={!editable}
/>
<div className="vm-json-form-footer">
<div className="vm-json-form-footer__controls">
<Button
variant="outlined"
startIcon={<CopyIcon/>}
onClick={handlerCopy}
>
Copy JSON
</Button>
{resetValue && (
<Button
variant="text"
startIcon={<RestartIcon/>}
onClick={handleReset}
>
Reset JSON
</Button>
)}
</div>
<div className="vm-json-form-footer__controls vm-json-form-footer__controls_right">
<Button
variant="outlined"
color="error"
onClick={onClose}
>
Cancel
</Button>
<Button
variant="contained"
onClick={handleApply}
>
apply
</Button>
</div>
</div>
</div>
);
};
export default JsonForm;

View File

@ -0,0 +1,41 @@
@use "src/styles/variables" as *;
.vm-json-form {
display: grid;
grid-template-rows: auto calc(90vh - 78px - ($padding-medium*3)) auto;
gap: $padding-global;
width: 70vw;
max-width: 1000px;
max-height: 900px;
&_one-field {
grid-template-rows: calc(90vh - 78px - ($padding-medium*3)) auto;
}
textarea {
overflow: auto;
width: 100%;
height: 100%;
}
&-footer {
display: flex;
align-items: center;
justify-content: space-between;
gap: $padding-small;
&__controls {
flex-grow: 1;
display: flex;
align-items: center;
justify-content: flex-start;
gap: $padding-small;
&_right {
display: grid;
grid-template-columns: repeat(2, 90px);
justify-content: flex-end;
}
}
}
}

View File

@ -0,0 +1,177 @@
import React, { FC, useMemo, useState } from "preact/compat";
import { ChangeEvent } from "react";
import Trace from "../../components/TraceQuery/Trace";
import TracingsView from "../../components/TraceQuery/TracingsView";
import Tooltip from "../../components/Main/Tooltip/Tooltip";
import Button from "../../components/Main/Button/Button";
import Alert from "../../components/Main/Alert/Alert";
import "./style.scss";
import { CloseIcon } from "../../components/Main/Icons";
import Modal from "../../components/Main/Modal/Modal";
import JsonForm from "./JsonForm/JsonForm";
import { ErrorTypes } from "../../types";
const TracePage: FC = () => {
const [openModal, setOpenModal] = useState(false);
const [tracesState, setTracesState] = useState<Trace[]>([]);
const [errors, setErrors] = useState<{filename: string, text: string}[]>([]);
const hasTraces = useMemo(() => !!tracesState.length, [tracesState]);
const handleOpenModal = () => {
setOpenModal(true);
};
const handleCloseModal = () => {
setOpenModal(false);
};
const handleError = (e: Error, filename = "") => {
setErrors(prev => [{ filename, text: `: ${e.message}` }, ...prev]);
};
const handleOnload = (result: string, filename: string) => {
try {
const resp = JSON.parse(result);
const traceData = resp.trace || resp;
if (!traceData.duration_msec) {
handleError(new Error(ErrorTypes.traceNotFound), filename);
return;
}
const trace = new Trace(traceData, filename);
setTracesState(prev => [trace, ...prev]);
} catch (e) {
if (e instanceof Error) handleError(e, filename);
}
};
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
setErrors([]);
const files = Array.from(e.target.files || []);
files.map(f => {
const reader = new FileReader();
const filename = f?.name || "";
reader.onload = (e) => {
const result = String(e.target?.result);
handleOnload(result, filename);
};
reader.readAsText(f);
});
e.target.value = "";
};
const handleTraceDelete = (trace: Trace) => {
const updatedTraces = tracesState.filter((data) => data.idValue !== trace.idValue);
setTracesState([...updatedTraces]);
};
const handleCloseError = (index: number) => {
setErrors(prev => prev.filter((e,i) => i !== index));
};
const createHandlerCloseError = (index: number) => () => {
handleCloseError(index);
};
const UploadButtons = () => (
<div className="vm-trace-page-controls">
<Button
variant="outlined"
onClick={handleOpenModal}
>
Paste JSON
</Button>
<Tooltip title="The file must contain tracing information in JSON format">
<Button>
Upload Files
<input
id="json"
type="file"
accept="application/json"
multiple
title=" "
onChange={handleChange}
/>
</Button>
</Tooltip>
</div>
);
return (
<div className="vm-trace-page">
<div className="vm-trace-page-header">
<div className="vm-trace-page-header-errors">
{errors.map((error, i) => (
<div
className="vm-trace-page-header-errors-item"
key={`${error}_${i}`}
>
<Alert variant="error">
<b className="vm-trace-page-header-errors-item__filename">{error.filename}</b>
<span>{error.text}</span>
</Alert>
<Button
className="vm-trace-page-header-errors-item__close"
startIcon={<CloseIcon/>}
variant="text"
color="error"
onClick={createHandlerCloseError(i)}
/>
</div>
))}
</div>
<div>
{hasTraces && <UploadButtons/>}
</div>
</div>
{hasTraces && (
<div>
<TracingsView
jsonEditor={true}
traces={tracesState}
onDeleteClick={handleTraceDelete}
/>
</div>
)}
{!hasTraces && (
<div className="vm-trace-page-preview">
<p className="vm-trace-page-preview__text">
Please, upload file with JSON response content.
{"\n"}
The file must contain tracing information in JSON format.
{"\n"}
In order to use tracing please refer to the doc:&nbsp;
<a
href="https://docs.victoriametrics.com/#query-tracing"
target="_blank"
rel="noreferrer"
>
https://docs.victoriametrics.com/#query-tracing
</a>
{"\n"}
Tracing graph will be displayed after file upload.
</p>
<UploadButtons/>
</div>
)}
{openModal && (
<Modal
title="Paste JSON"
onClose={handleCloseModal}
>
<JsonForm
editable
displayTitle
defaultTile={`JSON ${tracesState.length + 1}`}
onClose={handleCloseModal}
onUpload={handleOnload}
/>
</Modal>
)}
</div>
);
};
export default TracePage;

View File

@ -0,0 +1,66 @@
@use "src/styles/variables" as *;
.vm-trace-page {
display: flex;
flex-direction: column;
padding: $padding-global;
min-height: 100%;
&-controls {
display: grid;
grid-template-columns: 1fr 1fr;
gap: $padding-global;
align-items: center;
justify-content: center;
}
&-header {
display: grid;
grid-template-columns: 1fr auto;
align-items: start;
gap: $padding-global;
margin-bottom: $padding-medium;
&-errors {
display: grid;
align-items: flex-start;
justify-content: stretch;
grid-template-columns: 1fr;
gap: $padding-medium;
&-item {
position: relative;
display: grid;
align-items: center;
justify-content: stretch;
&__filename {
min-height: 20px;
}
&__close {
position: absolute;
top: auto;
right: $padding-small;
z-index: 2;
}
}
}
}
&-preview {
flex-grow: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
&__text {
margin-bottom: $padding-small;
font-size: $font-size-medium;
white-space: pre-line;
text-align: center;
line-height: 1.8;
}
}
}

View File

@ -3,6 +3,7 @@ const router = {
dashboards: "/dashboards",
cardinality: "/cardinality",
topQueries: "/top-queries",
trace: "/trace"
};
export interface RouterOptions {

View File

@ -38,5 +38,5 @@ input[type=number]::-webkit-outer-spin-button {
position: fixed;
left: $padding-global;
bottom: $padding-global;
z-index: 9;
z-index: 999;
}

View File

@ -40,7 +40,9 @@ export interface InstantDataSeries {
export enum ErrorTypes {
emptyServer = "Please enter Server URL",
validServer = "Please provide a valid Server URL",
validQuery = "Please enter a valid Query and execute it"
validQuery = "Please enter a valid Query and execute it",
traceNotFound = "Not found the tracing information",
emptyTitle = "Please enter title",
}
export interface PanelSettings {

View File

@ -17,6 +17,7 @@ The following tip changes can be tested by building VictoriaMetrics components f
* FEATURE: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): add [range_linear_regression](https://docs.victoriametrics.com/MetricsQL.html#range_linear_regression) function for calculating [simple linear regression](https://en.wikipedia.org/wiki/Simple_linear_regression) over the input time series on the selected time range. This function is useful for predictions and capacity planning. For example, `range_linear_regression(process_resident_memory_bytes)` can predict future memory usage based on the past memory usage.
* FEATURE: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): add [range_stddev](https://docs.victoriametrics.com/MetricsQL.html#range_stddev) and [range_stdvar](https://docs.victoriametrics.com/MetricsQL.html#range_stdvar) functions.
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add the ability to upload/paste JSON to investigate the trace. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3308) and [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3310).
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): reduce JS bundle size from 200Kb to 100Kb. See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3298).
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add the ability to hide results of a particular query by clicking the `eye` icon. See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3359).

View File

@ -1645,7 +1645,9 @@ 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 `Trace query` checkbox and re-run the query in order to investigate its' trace.
[VMUI](#vmui) provides an UI:
- for query tracing - just click `Trace query` checkbox and re-run the query in order to investigate its' trace.
- for exploring custom trace - go to the tab `Trace analyzer` and upload or paste JSON with trace information.
## Cardinality limiter

View File

@ -1648,7 +1648,9 @@ 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 `Trace query` checkbox and re-run the query in order to investigate its' trace.
[VMUI](#vmui) provides an UI:
- for query tracing - just click `Trace query` checkbox and re-run the query in order to investigate its' trace.
- for exploring custom trace - go to the tab `Trace analyzer` and upload or paste JSON with trace information.
## Cardinality limiter