fix: optimize monitor data fetching for large time ranges in overview detail (#10)

Co-authored-by: yaojiping <yaojiping@infini.ltd>
This commit is contained in:
yaojp123 2024-12-06 16:27:38 +08:00 committed by GitHub
parent 7dd3808942
commit eedcda0fec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 124 additions and 93 deletions

View File

@ -48,11 +48,8 @@ export default (props) => {
}, },
}; };
return ( return (
<div className={styles.lineWrapper}> <div className={styles.chartBody}>
<div className={styles.chartTitle}>{props.title}</div> <Line {...config} />
<div className={styles.chartBody}>
<Line {...config} />
</div>
</div> </div>
); );
}; };

View File

@ -3,53 +3,66 @@ import { Line } from "@ant-design/plots";
import styles from "./index.scss"; import styles from "./index.scss";
import MetricLine from "./MetricLine"; import MetricLine from "./MetricLine";
import { formatMessage } from "umi/locale"; import { formatMessage } from "umi/locale";
import { useState } from "react";
import request from "@/utils/request";
import MetricChart from "@/pages/Platform/Overview/components/MetricChart";
export default ({ metrics = {}, renderExtraMetric }) => { export default (props) => {
let newMetrics = Object.values(metrics).map((item) => { const { action, timeRange, timezone, timeout, overview, renderExtraMetric, metrics = [], queryParams } = props
let key = item.key;
let units = "";
let newData = [];
item.lines.map((line) => {
let category = line.metric.label;
if (!units) {
if (line.metric.formatType.toLowerCase() == "bytes") {
units = line.metric.formatType;
} else {
units = line.metric.units;
}
}
return line.data.map((ld) => {
newData.push({
category: category,
x: formatter.dateUserDefined(parseInt(ld[0])),
y: ld[1],
});
});
});
return { key: key, data: newData, units: units, order: item.order };
});
newMetrics = newMetrics.sort((a, b) => a.order - b.order);
return ( return (
<div className={styles.metricChart}> <div className={styles.metricChart}>
{newMetrics.map((item) => { {metrics.map((metricKey) => {
if(["node_health", "parent_breaker", "index_health"].includes(item.key)){ return (
return null; <MetricChart
} key={metricKey}
let config = { timezone={timezone}
data: item.data, timeRange={timeRange}
xField: "x", fetchUrl={action}
yField: "y", metricKey={metricKey}
seriesField: "category", title={formatMessage({id: "cluster.metrics.axis." + metricKey + ".title"})}
title: `${formatMessage({ queryParams={queryParams}
id: "cluster.metrics.axis." + item.key + ".title", timeout={timeout}
})} ${item.units ? "(" + item.units + ")" : ""}`, className={styles.lineWrapper}
yUnits: item.units, formatMetric={(metric) => {
}; if (!metric) return metric;
return <MetricLine {...config} key={item.key} />; let units = "";
const newData = [];
(metric.lines || []).map((line) => {
let category = line.metric.label;
if (!units) {
if (line.metric.formatType.toLowerCase() == "bytes") {
units = line.metric.formatType;
} else {
units = line.metric.units;
}
}
return line.data.map((ld) => {
newData.push({
category: category,
x: formatter.dateUserDefined(parseInt(ld[0])),
y: ld[1],
});
});
});
return { ...metric, data: newData, units };
}}
customRenderChart={(metric) => {
if (!metric) return null
const config = {
data: metric.data,
xField: "x",
yField: "y",
seriesField: "category",
yUnits: metric.units,
};
return <MetricLine {...config} key={metric.key} />
}}
/>
)
})} })}
{renderExtraMetric ? renderExtraMetric() : null} {renderExtraMetric ? renderExtraMetric() : null}
</div> </div>
); );
}; };

View File

@ -1,38 +1,21 @@
import React, { useMemo, useEffect } from "react"; import React, { useMemo, useEffect } from "react";
import { ESPrefix } from "@/services/common";
import useFetch from "@/lib/hooks/use_fetch";
import { calculateBounds } from "@/components/vendor/data/common/query/timefilter";
import MetricLineList from "./MetricLineList"; import MetricLineList from "./MetricLineList";
import { formatTimeRange } from "@/lib/elasticsearch/util";
export default ({ action, timeRange, overview, setSpinning, renderExtraMetric }) => { export default (props) => {
const { action, bucketSize, timeRange, overview, setSpinning, renderExtraMetric, metrics = [] } = props
const queryParams = useMemo(() => { const queryParams = useMemo(() => {
const bounds = calculateBounds({ const newParams = formatTimeRange(timeRange);
from: timeRange.min, if (overview) {
to: timeRange.max, newParams.overview = overview;
}); }
let params = { if (bucketSize) {
min: bounds.min.valueOf(), newParams.bucket_size = bucketSize
max: bounds.max.valueOf(), }
}; return newParams;
params.overview = overview; }, [timeRange, bucketSize]);
return params;
}, [timeRange]);
const { loading, error, value } = useFetch( return <MetricLineList {...props} queryParams={queryParams} />;
action,
{ queryParams },
[action, queryParams]
);
useEffect(() => {
setSpinning(loading);
}, [loading]);
const metrics = useMemo(() => {
const { metrics = {} } = value || {};
return metrics;
}, [value]);
return <MetricLineList metrics={metrics} renderExtraMetric={renderExtraMetric}/>;
}; };

View File

@ -15,6 +15,7 @@ import { formatMessage } from "umi/locale";
import DatePicker from "@/common/src/DatePicker"; import DatePicker from "@/common/src/DatePicker";
import { getLocale } from "umi/locale"; import { getLocale } from "umi/locale";
import { getTimezone } from "@/utils/utils"; import { getTimezone } from "@/utils/utils";
import { TIMEOUT_CACHE_KEY } from "../../Monitor";
const { TabPane } = Tabs; const { TabPane } = Tabs;
@ -26,6 +27,7 @@ export default (props) => {
linkMore, linkMore,
overviews, overviews,
extra, extra,
metrics = [],
} = props; } = props;
const [spinning, setSpinning] = useState(false); const [spinning, setSpinning] = useState(false);
@ -35,12 +37,14 @@ export default (props) => {
max: "now", max: "now",
timeFormatter: formatter.dates(1), timeFormatter: formatter.dates(1),
}, },
timeInterval: '',
timeout: localStorage.getItem(TIMEOUT_CACHE_KEY) || '120s',
}); });
const [refresh, setRefresh] = useState({ isRefreshPaused: false }); const [refresh, setRefresh] = useState({ isRefreshPaused: false });
const [timeZone, setTimeZone] = useState(() => getTimezone()); const [timeZone, setTimeZone] = useState(() => getTimezone());
const handleTimeChange = ({ start, end }) => { const handleTimeChange = ({ start, end, timeInterval, timeout }) => {
const bounds = calculateBounds({ const bounds = calculateBounds({
from: start, from: start,
to: end, to: end,
@ -55,6 +59,8 @@ export default (props) => {
max: end, max: end,
timeFormatter: formatter.dates(intDay), timeFormatter: formatter.dates(intDay),
}, },
timeInterval: timeInterval || state.timeInterval,
timeout: timeout || state.timeout
}); });
setSpinning(true); setSpinning(true);
}; };
@ -90,23 +96,35 @@ export default (props) => {
{...refresh} {...refresh}
onRefreshChange={setRefresh} onRefreshChange={setRefresh}
onRefresh={handleTimeChange} onRefresh={handleTimeChange}
showTimeSetting={true}
showTimeInterval={true}
showTimeout={true}
timeout={state.timeout}
onTimeSettingChange={(timeSetting) => {
localStorage.setItem(TIMEOUT_CACHE_KEY, timeSetting.timeout)
setState({
...state,
timeInterval: timeSetting.timeInterval,
timeout: timeSetting.timeout
});
}}
timeZone={timeZone} timeZone={timeZone}
onTimeZoneChange={setTimeZone} onTimeZoneChange={setTimeZone}
recentlyUsedRangesKey={'overview-detail'} recentlyUsedRangesKey={'overview-detail'}
/> />
</div> </div>
<Button onClick={() => {
handleTimeChange({ start: state.timeRange.min, end: state.timeRange.max})
}} loading={spinning} icon={"reload"} type="primary" />
</div> </div>
<div className={styles.metricWrapper}> <div className={styles.metricWrapper}>
<MetricSeries <MetricSeries
action={metricAction} action={metricAction}
timeRange={state.timeRange} timeZone={timeZone}
overview={1} overview={1}
setSpinning={setSpinning} setSpinning={setSpinning}
renderExtraMetric={renderExtraMetric} renderExtraMetric={renderExtraMetric}
metrics={metrics}
{...state}
bucketSize={state.timeInterval}
/> />
</div> </div>

View File

@ -39,7 +39,7 @@ const formatTimeout = (timeout) => {
return timeout return timeout
} }
const TIMEOUT_CACHE_KEY = "monitor-timeout" export const TIMEOUT_CACHE_KEY = "monitor-timeout"
const Monitor = (props) => { const Monitor = (props) => {
const { const {
@ -187,7 +187,6 @@ const Monitor = (props) => {
setSpinning={setSpinning} setSpinning={setSpinning}
{...extraParams} {...extraParams}
bucketSize={state.timeInterval} bucketSize={state.timeInterval}
timeout={state.timeout}
/> />
) )
) : null} ) : null}

View File

@ -33,6 +33,7 @@ export default (props) => {
params={{ clusterID, clusterName }} params={{ clusterID, clusterName }}
linkMore={`/cluster/monitor/elasticsearch/${clusterID}`} linkMore={`/cluster/monitor/elasticsearch/${clusterID}`}
overviews={overviews} overviews={overviews}
metrics={['index_throughput', 'search_throughput', 'index_latency', 'search_latency']}
/> />
) )

View File

@ -6,6 +6,7 @@ import IndexMetric from "../../components/index_metric";
import ClusterMetric from "../../components/cluster_metric"; import ClusterMetric from "../../components/cluster_metric";
import QueueMetric from "../../components/queue_metric"; import QueueMetric from "../../components/queue_metric";
import { ESPrefix } from "@/services/common"; import { ESPrefix } from "@/services/common";
import { SearchEngines } from "@/lib/search_engines";
const timezone = "local"; const timezone = "local";
@ -19,6 +20,7 @@ export default ({
}) => { }) => {
const isVersionGTE6 = useMemo(() => { const isVersionGTE6 = useMemo(() => {
if ([SearchEngines.Easysearch, SearchEngines.Opensearch].includes(selectedCluster?.distribution)) return true;
const main = selectedCluster?.version?.split('.')[0] const main = selectedCluster?.version?.split('.')[0]
if (main && parseInt(main) >= 6) { if (main && parseInt(main) >= 6) {
return true return true

View File

@ -26,6 +26,12 @@ export default (props) => {
params={{ clusterID, clusterName }} params={{ clusterID, clusterName }}
linkMore={`/cluster/monitor/${clusterID}/indices/${indexName}?_g={"cluster_name":"${clusterName}"}`} linkMore={`/cluster/monitor/${clusterID}/indices/${indexName}?_g={"cluster_name":"${clusterName}"}`}
overviews={overviews} overviews={overviews}
metrics={[
"index_throughput",
"search_throughput",
"index_latency",
"search_latency",
]}
/> />
) )
}; };

View File

@ -27,6 +27,14 @@ export default (props) => {
params={{ clusterID, clusterName }} params={{ clusterID, clusterName }}
linkMore={`/cluster/monitor/${clusterID}/nodes/${nodeID}?_g={"cluster_name":"${clusterName}","node_name":"${nodeName}"}`} linkMore={`/cluster/monitor/${clusterID}/nodes/${nodeID}?_g={"cluster_name":"${clusterName}","node_name":"${nodeName}"}`}
overviews={overviews} overviews={overviews}
metrics={[
"cpu",
"jvm",
"index_throughput",
"search_throughput",
"index_latency",
"search_latency",
]}
/> />
); );
}; };

View File

@ -3,6 +3,7 @@ import { Tabs } from "antd";
import NodeMetric from "../../components/node_metric"; import NodeMetric from "../../components/node_metric";
import QueueMetric from "../../components/queue_metric"; import QueueMetric from "../../components/queue_metric";
import { formatMessage } from "umi/locale"; import { formatMessage } from "umi/locale";
import { SearchEngines } from "@/lib/search_engines";
const timezone = "local"; const timezone = "local";
@ -17,12 +18,13 @@ export default ({
}) => { }) => {
const isVersionGTE6 = useMemo(() => { const isVersionGTE6 = useMemo(() => {
if ([SearchEngines.Easysearch, SearchEngines.Opensearch].includes(selectedCluster?.distribution)) return true;
const main = selectedCluster?.version?.split('.')[0] const main = selectedCluster?.version?.split('.')[0]
if (main && parseInt(main) >= 6) { if (main && parseInt(main) >= 6) {
return true return true
} }
return false return false
}, [selectedCluster?.version]) }, [selectedCluster])
const [param, setParam] = useState({ const [param, setParam] = useState({
show_top: false, show_top: false,

View File

@ -34,7 +34,9 @@ export default (props) => {
queryParams, queryParams,
className, className,
style, style,
formatMetric formatMetric,
height = 200,
customRenderChart
} = props; } = props;
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
@ -71,7 +73,7 @@ export default (props) => {
} else if (res && !res.error) { } else if (res && !res.error) {
const { metrics = {} } = res || {}; const { metrics = {} } = res || {};
const metric = metrics[metricKey] const metric = metrics[metricKey]
setMetric(formatMetric ? formatMetric(metric) : metric); setMetric(formatMetric && metric ? formatMetric(metric) : metric);
} }
if (firstFetchRef.current || showLoading) { if (firstFetchRef.current || showLoading) {
setLoading(false) setLoading(false)
@ -142,10 +144,10 @@ export default (props) => {
}; };
const renderChart = () => { const renderChart = () => {
if (loading) return <div style={{ height: 200 }}></div> if (loading) return <div style={{ height }}></div>
if (error) { if (error) {
return ( return (
<div style={{ height: 200, padding: 10 }}> <div style={{ height, padding: 10 }}>
<Alert style={{ maxHeight: '100%', overflow: 'auto', wordBreak: 'break-all'}} message={error} type="error"/> <Alert style={{ maxHeight: '100%', overflow: 'auto', wordBreak: 'break-all'}} message={error} type="error"/>
</div> </div>
) )
@ -153,11 +155,11 @@ export default (props) => {
const axis = metric?.axis || []; const axis = metric?.axis || [];
const lines = metric?.lines || []; const lines = metric?.lines || [];
if (lines.every((item) => !item.data || item.data.length === 0)) { if (lines.every((item) => !item.data || item.data.length === 0)) {
return <Empty style={{ height: 200, margin: 0, paddingTop: 64 }} image={Empty.PRESENTED_IMAGE_SIMPLE} /> return <Empty style={{ height, margin: 0, paddingTop: 64 }} image={Empty.PRESENTED_IMAGE_SIMPLE} />
} }
return ( return (
<Chart <Chart
size={[, 200]} size={[, height]}
className={styles.vizChartItem} className={styles.vizChartItem}
ref={chartRef} ref={chartRef}
> >
@ -312,7 +314,7 @@ export default (props) => {
</span> </span>
} }
</div> </div>
{renderChart()} {customRenderChart ? customRenderChart(metric) : renderChart()}
</Spin> </Spin>
</div> </div>
); );