vmui: limit number of plotted series (#3229)

* feat: add maximum display series by tabs

* feat: add warning on PredefinedPanels.tsx

* docs/CHANGELOG.md: vmui limit number of plotted series

* docs/CHANGELOG.md: vmui limit number of plotted series

* wip

Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
This commit is contained in:
Yury Molodov 2022-10-13 11:13:47 +02:00 committed by GitHub
parent 92f7fe306e
commit ff6151fa49
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 35 additions and 12 deletions

View file

@ -1,12 +1,12 @@
{ {
"files": { "files": {
"main.css": "./static/css/main.ba692000.css", "main.css": "./static/css/main.ba692000.css",
"main.js": "./static/js/main.dd4b1276.js", "main.js": "./static/js/main.c0e3dc67.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.ba692000.css", "static/css/main.ba692000.css",
"static/js/main.dd4b1276.js" "static/js/main.c0e3dc67.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.dd4b1276.js"></script><link href="./static/css/main.ba692000.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.c0e3dc67.js"></script><link href="./static/css/main.ba692000.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

@ -12,13 +12,13 @@ import {ErrorTypes} from "../../../../types";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import usePrevious from "../../../../hooks/usePrevious"; import usePrevious from "../../../../hooks/usePrevious";
import {MAX_QUERY_FIELDS} from "../../../../config";
export interface QueryConfiguratorProps { export interface QueryConfiguratorProps {
error?: ErrorTypes | string; error?: ErrorTypes | string;
queryOptions: string[] queryOptions: string[]
} }
export const MAX_QUERY_FIELDS = 4;
const QueryConfigurator: FC<QueryConfiguratorProps> = ({error, queryOptions}) => { const QueryConfigurator: FC<QueryConfiguratorProps> = ({error, queryOptions}) => {

View file

@ -40,7 +40,7 @@ const CustomPanel: FC = () => {
}; };
const {queryOptions} = useFetchQueryOptions(); const {queryOptions} = useFetchQueryOptions();
const {isLoading, liveData, graphData, error, traces} = useFetchQuery({ const {isLoading, liveData, graphData, error, warning, traces} = useFetchQuery({
visible: true, visible: true,
customStep customStep
}); });
@ -83,6 +83,7 @@ const CustomPanel: FC = () => {
</Box> </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>}
{warning && <Alert color="warning" severity="warning" sx={{whiteSpace: "pre-wrap", my: 2}}>{warning}</Alert>}
{graphData && period && (displayType === "chart") && <> {graphData && period && (displayType === "chart") && <>
{isTracingEnabled && <TracingsView {isTracingEnabled && <TracingsView
traces={tracesState} traces={tracesState}

View file

@ -46,7 +46,7 @@ const PredefinedPanels: FC<PredefinedPanelsProps> = ({
const validExpr = useMemo(() => Array.isArray(expr) && expr.every(q => q), [expr]); const validExpr = useMemo(() => Array.isArray(expr) && expr.every(q => q), [expr]);
const {isLoading, graphData, error} = useFetchQuery({ const {isLoading, graphData, error, warning} = useFetchQuery({
predefinedQuery: validExpr ? expr : [], predefinedQuery: validExpr ? expr : [],
display: "chart", display: "chart",
visible, visible,
@ -115,6 +115,7 @@ const PredefinedPanels: FC<PredefinedPanelsProps> = ({
<Box px={2} pb={2}> <Box px={2} pb={2}>
{isLoading && <Spinner isLoading={true} height={"500px"}/>} {isLoading && <Spinner isLoading={true} height={"500px"}/>}
{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>}
{warning && <Alert color="warning" severity="warning" sx={{whiteSpace: "pre-wrap", my: 2}}>{warning}</Alert>}
{graphData && <GraphView {graphData && <GraphView
data={graphData} data={graphData}
period={period} period={period}

View file

@ -0,0 +1,6 @@
export const MAX_QUERY_FIELDS = 4;
export const MAX_SERIES = {
table: 100,
chart: 20,
code: Infinity,
};

View file

@ -11,6 +11,7 @@ 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"; import Trace from "../components/CustomPanel/Trace/Trace";
import {MAX_SERIES} from "../config";
interface FetchQueryParams { interface FetchQueryParams {
predefinedQuery?: string[] predefinedQuery?: string[]
@ -28,6 +29,7 @@ export const useFetchQuery = ({predefinedQuery, visible, display, customStep}: F
graphData?: MetricResult[], graphData?: MetricResult[],
liveData?: InstantMetricResult[], liveData?: InstantMetricResult[],
error?: ErrorTypes | string, error?: ErrorTypes | string,
warning?: string,
traces?: Trace[], traces?: Trace[],
} => { } => {
const {query, displayType, serverUrl, time: {period}, queryControls: {nocache, isTracingEnabled}} = useAppState(); const {query, displayType, serverUrl, time: {period}, queryControls: {nocache, isTracingEnabled}} = useAppState();
@ -37,6 +39,7 @@ export const useFetchQuery = ({predefinedQuery, visible, display, customStep}: F
const [liveData, setLiveData] = useState<InstantMetricResult[]>(); const [liveData, setLiveData] = useState<InstantMetricResult[]>();
const [traces, setTraces] = useState<Trace[]>(); const [traces, setTraces] = useState<Trace[]>();
const [error, setError] = useState<ErrorTypes | string>(); const [error, setError] = useState<ErrorTypes | string>();
const [warning, setWarning] = useState<string>();
const [fetchQueue, setFetchQueue] = useState<AbortController[]>([]); const [fetchQueue, setFetchQueue] = useState<AbortController[]>([]);
useEffect(() => { useEffect(() => {
@ -56,6 +59,7 @@ export const useFetchQuery = ({predefinedQuery, visible, display, customStep}: F
const tempData: MetricBase[] = []; const tempData: MetricBase[] = [];
const tempTraces: Trace[] = []; const tempTraces: Trace[] = [];
let counter = 1; let counter = 1;
for await (const response of responses) { for await (const response of responses) {
const resp = await response.json(); const resp = await response.json();
if (response.ok) { if (response.ok) {
@ -73,7 +77,14 @@ export const useFetchQuery = ({predefinedQuery, visible, display, customStep}: F
setError(`${resp.errorType}\r\n${resp?.error}`); setError(`${resp.errorType}\r\n${resp?.error}`);
} }
} }
isDisplayChart ? setGraphData(tempData as MetricResult[]) : setLiveData(tempData as InstantMetricResult[]);
const length = tempData.length;
const seriesLimit = MAX_SERIES[displayType];
const result = tempData.slice(0, seriesLimit);
const limitText = `Showing ${seriesLimit} series out of ${length} series due to performance reasons. Please narrow down the query, so it returns less series`;
setWarning(length > seriesLimit ? limitText : "");
isDisplayChart ? setGraphData(result as MetricResult[]) : setLiveData(result as InstantMetricResult[]);
setTraces(tempTraces); setTraces(tempTraces);
} catch (e) { } catch (e) {
if (e instanceof Error && e.name !== "AbortError") { if (e instanceof Error && e.name !== "AbortError") {
@ -107,9 +118,12 @@ export const useFetchQuery = ({predefinedQuery, visible, display, customStep}: F
[serverUrl, period, displayType, customStep]); [serverUrl, period, displayType, customStep]);
const prevFetchUrl = usePrevious(fetchUrl); const prevFetchUrl = usePrevious(fetchUrl);
const prevDisplayType = usePrevious(displayType);
useEffect(() => { useEffect(() => {
if (!visible || (fetchUrl && prevFetchUrl && arrayEquals(fetchUrl, prevFetchUrl)) || !fetchUrl?.length) return; const equalFetchUrl = fetchUrl && prevFetchUrl && arrayEquals(fetchUrl, prevFetchUrl);
const changedDisplayType = displayType !== prevDisplayType;
if (!visible || (equalFetchUrl && !changedDisplayType) || !fetchUrl?.length) return;
setIsLoading(true); setIsLoading(true);
const expr = predefinedQuery ?? query; const expr = predefinedQuery ?? query;
throttledFetchData(fetchUrl, fetchQueue, (display || displayType), expr); throttledFetchData(fetchUrl, fetchQueue, (display || displayType), expr);
@ -122,5 +136,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, traces}; return {fetchUrl, isLoading, graphData, liveData, error, warning, traces};
}; };

View file

@ -1,7 +1,7 @@
import qs from "qs"; import qs from "qs";
import get from "lodash.get"; import get from "lodash.get";
import router from "../router"; import router from "../router";
import {MAX_QUERY_FIELDS} from "../components/CustomPanel/Configurator/Query/QueryConfigurator"; import {MAX_QUERY_FIELDS} from "../config";
const graphStateToUrlParams = { const graphStateToUrlParams = {
"time.duration": "range_input", "time.duration": "range_input",

View file

@ -38,6 +38,7 @@ The following tip changes can be tested by building VictoriaMetrics components f
See [the corresponding issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3208). See [the corresponding issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3208).
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): allow controlling staleness tracking on a per-[scrape_config](https://docs.victoriametrics.com/sd_configs.html#scrape_configs) basis by specifying `no_stale_markers: true` or `no_stale_markers: false` option in the corresponding [scrape_config](https://docs.victoriametrics.com/sd_configs.html#scrape_configs). * FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): allow controlling staleness tracking on a per-[scrape_config](https://docs.victoriametrics.com/sd_configs.html#scrape_configs) basis by specifying `no_stale_markers: true` or `no_stale_markers: false` option in the corresponding [scrape_config](https://docs.victoriametrics.com/sd_configs.html#scrape_configs).
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): limit the number of plotted series. This should prevent from browser crashes or hands when the query returns big number of time series. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3155).
* BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui): automatically update graph, legend and url after the removal of query field. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3169) and [this comment](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3196#issuecomment-1269765205). * BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui): automatically update graph, legend and url after the removal of query field. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3169) and [this comment](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3196#issuecomment-1269765205).
* BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert.html): remove duplicate `alertname` JSON entry from generated alerts. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3053). Thanks to @Howie59 for [the fix](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3182)! * BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert.html): remove duplicate `alertname` JSON entry from generated alerts. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3053). Thanks to @Howie59 for [the fix](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3182)!