fix: split queries for topology to avoid page crash (#485)

This commit is contained in:
Fine0830
2025-07-30 20:52:33 +08:00
committed by GitHub
parent faf475d82f
commit ad4b0639cd
6 changed files with 55 additions and 45 deletions

View File

@@ -113,4 +113,6 @@ export const LightChartColors = [
"#c4ccd3", "#c4ccd3",
]; ];
export const MaxQueryLength = 120; export const TopologyMaxQueryEntities = 20;
export const TopologyMaxQueryExpressions = 10;
export const DashboardMaxQueryWidgets = 6;

View File

@@ -14,7 +14,13 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { RespFields, MaximumEntities, MaxQueryLength } from "./data"; import {
RespFields,
MaximumEntities,
TopologyMaxQueryEntities,
TopologyMaxQueryExpressions,
DashboardMaxQueryWidgets,
} from "./data";
import { EntityType, ExpressionResultType } from "@/views/dashboard/data"; import { EntityType, ExpressionResultType } from "@/views/dashboard/data";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { useTopologyStore } from "@/store/modules/topology"; import { useTopologyStore } from "@/store/modules/topology";
@@ -26,10 +32,17 @@ import type { Instance, Endpoint, Service } from "@/types/selector";
import type { Node, Call } from "@/types/topology"; import type { Node, Call } from "@/types/topology";
function chunkArray(array: any[], chunkSize: number) { function chunkArray(array: any[], chunkSize: number) {
if (chunkSize <= 0) {
return [array];
}
if (chunkSize > array.length) {
return [array];
}
const result = []; const result = [];
for (let i = 0; i < array.length; i += chunkSize) { for (let i = 0; i < array.length; i += chunkSize) {
result.push(array.slice(i, i + chunkSize)); result.push(array.slice(i, i + chunkSize));
} }
return result; return result;
} }
@@ -191,7 +204,7 @@ export async function useDashboardQueryProcessor(configList: Indexable[]) {
} }
} }
const partArr = chunkArray(configList, 6); const partArr = chunkArray(configList, DashboardMaxQueryWidgets);
const promiseArr = partArr.map((d: Array<Indexable>) => fetchMetrics(d)); const promiseArr = partArr.map((d: Array<Indexable>) => fetchMetrics(d));
const responseList = await Promise.all(promiseArr); const responseList = await Promise.all(promiseArr);
let resp = {}; let resp = {};
@@ -201,7 +214,6 @@ export async function useDashboardQueryProcessor(configList: Indexable[]) {
...item, ...item,
}; };
} }
return resp; return resp;
} }
@@ -395,13 +407,14 @@ export async function useExpressionsQueryPodsMetrics(
export function useQueryTopologyExpressionsProcessor(metrics: string[], instances: (Call | Node)[]) { export function useQueryTopologyExpressionsProcessor(metrics: string[], instances: (Call | Node)[]) {
const appStore = useAppStoreWithOut(); const appStore = useAppStoreWithOut();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
const topologyStore = useTopologyStore();
function getExpressionQuery(partMetrics?: string[]) { function getExpressionQuery(partMetrics: string[] = [], entities: (Call | Node)[] = []) {
const conditions: { [key: string]: unknown } = { const conditions: { [key: string]: unknown } = {
duration: appStore.durationTime, duration: appStore.durationTime,
}; };
const variables: string[] = [`$duration: Duration!`]; const variables: string[] = [`$duration: Duration!`];
const fragmentList = instances.map((d: any, index: number) => { const fragmentList = entities.map((d: Indexable, index: number) => {
let serviceName; let serviceName;
let destServiceName; let destServiceName;
let endpointName; let endpointName;
@@ -482,23 +495,27 @@ export function useQueryTopologyExpressionsProcessor(metrics: string[], instance
} }
} }
} }
return obj; return obj;
} }
async function fetchMetrics(partMetrics: string[]) { async function fetchMetrics(partMetrics: string[], entities: (Call | Node)[]) {
const topologyStore = useTopologyStore(); const param = getExpressionQuery(partMetrics, entities);
const param = getExpressionQuery(partMetrics);
const res = await topologyStore.getTopologyExpressionValue(param); const res = await topologyStore.getTopologyExpressionValue(param);
if (res.errors) { if (res.errors) {
ElMessage.error(res.errors); ElMessage.error(res.errors);
return; return;
} }
return handleExpressionValues(partMetrics, res.data); return handleExpressionValues(partMetrics, res.data);
} }
async function getMetrics() { async function getMetrics() {
const count = Math.floor(MaxQueryLength / instances.length); const metricsArr = chunkArray(metrics, TopologyMaxQueryExpressions);
const metricsArr = chunkArray(metrics, count); const entities = chunkArray(instances, TopologyMaxQueryEntities);
const promiseArr = metricsArr.map((d: string[]) => fetchMetrics(d));
const promiseArr = metricsArr
.map((d: string[]) => entities.map((e: (Call | Node)[]) => fetchMetrics(d, e)))
.flat(1);
const responseList = await Promise.all(promiseArr); const responseList = await Promise.all(promiseArr);
let resp = {}; let resp = {};
for (const item of responseList) { for (const item of responseList) {
@@ -507,8 +524,9 @@ export function useQueryTopologyExpressionsProcessor(metrics: string[], instance
...item, ...item,
}; };
} }
return resp; return resp;
} }
return { getMetrics, getExpressionQuery }; return { getMetrics };
} }

View File

@@ -223,14 +223,14 @@ export const topologyStore = defineStore({
setNodeMetricValue(m: MetricVal) { setNodeMetricValue(m: MetricVal) {
this.nodeMetricValue = m; this.nodeMetricValue = m;
}, },
setLegendValues(expressions: string, data: { [key: string]: any }) { setLegendValues(expression: string, data: Indexable) {
const nodeArr = this.nodes.filter((d: Node) => d.isReal); const nodeArr = this.nodes.filter((d: Node) => d.isReal);
for (let idx = 0; idx < nodeArr.length; idx++) { for (let idx = 0; idx < nodeArr.length; idx++) {
for (let index = 0; index < expressions.length; index++) { if (expression) {
const k = "expression" + idx + index; nodeArr[idx][expression] = Number(
if (expressions[index]) { data[expression]?.values?.find((d: { id: string; value: string }) => d.id === nodeArr[idx].id)?.value,
nodeArr[idx][expressions[index]] = Number(data[k].results[0].values[0].value); );
}
} }
} }
}, },

View File

@@ -158,7 +158,6 @@ limitations under the License. -->
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
import { useTopologyStore } from "@/store/modules/topology"; import { useTopologyStore } from "@/store/modules/topology";
import { ElMessage } from "element-plus";
import { ScopeType, EntityType, CallTypes } from "@/views/dashboard/data"; import { ScopeType, EntityType, CallTypes } from "@/views/dashboard/data";
import type { Option } from "@/types/app"; import type { Option } from "@/types/app";
import { useQueryTopologyExpressionsProcessor } from "@/hooks/useExpressionsProcessor"; import { useQueryTopologyExpressionsProcessor } from "@/hooks/useExpressionsProcessor";
@@ -239,17 +238,12 @@ limitations under the License. -->
async function setLegend() { async function setLegend() {
updateSettings(); updateSettings();
const expression = dashboardStore.selectedGrid.legendMQE && dashboardStore.selectedGrid.legendMQE.expression; const expression = dashboardStore.selectedGrid.legendMQE && dashboardStore.selectedGrid.legendMQE.expression;
const { getExpressionQuery } = useQueryTopologyExpressionsProcessor( const { getMetrics } = useQueryTopologyExpressionsProcessor(
[expression], [expression],
topologyStore.nodes.filter((d: Node) => d.isReal), topologyStore.nodes.filter((d: Node) => d.isReal),
); );
const param = getExpressionQuery(); const metrics = await getMetrics();
const res = await topologyStore.getTopologyExpressionValue(param); topologyStore.setLegendValues(expression, metrics);
if (res.errors) {
ElMessage.error(res.errors);
} else {
topologyStore.setLegendValues([expression], res.data);
}
} }
function changeNodeDashboard(opt: any) { function changeNodeDashboard(opt: any) {
states.nodeDashboard = opt[0].value; states.nodeDashboard = opt[0].value;

View File

@@ -90,7 +90,7 @@ limitations under the License. -->
const htmlServer = serverMetrics.map((m, index) => { const htmlServer = serverMetrics.map((m, index) => {
const metric = const metric =
topologyStore.linkServerMetrics[m].values.find((val: { id: string; value: unknown }) => val.id === data.id) || topologyStore.linkServerMetrics[m]?.values?.find((val: { id: string; value: unknown }) => val.id === data.id) ||
{}; {};
if (metric) { if (metric) {
const opt: MetricConfigOpt = linkServerMetricConfig[index] || {}; const opt: MetricConfigOpt = linkServerMetricConfig[index] || {};
@@ -103,7 +103,7 @@ limitations under the License. -->
const htmlClient = clientMetrics.map((m, index) => { const htmlClient = clientMetrics.map((m, index) => {
const opt: MetricConfigOpt = linkClientMetricConfig[index] || {}; const opt: MetricConfigOpt = linkClientMetricConfig[index] || {};
const metric = const metric =
topologyStore.linkClientMetrics[m].values.find((val: { id: string; value: unknown }) => val.id === data.id) || topologyStore.linkClientMetrics[m]?.values?.find((val: { id: string; value: unknown }) => val.id === data.id) ||
{}; {};
return ` <div class="mb-5"><span class="grey">${opt.label || m}: </span>${metric.value} ${opt.unit || ""}</div>`; return ` <div class="mb-5"><span class="grey">${opt.label || m}: </span>${metric.value} ${opt.unit || ""}</div>`;
@@ -122,7 +122,8 @@ limitations under the License. -->
const nodeMetricConfig = props.settings.nodeMetricConfig || []; const nodeMetricConfig = props.settings.nodeMetricConfig || [];
const html = nodeMetrics.map((m, index) => { const html = nodeMetrics.map((m, index) => {
const metric = const metric =
topologyStore.nodeMetricValue[m].values.find((val: { id: string; value: unknown }) => val.id === data.id) || {}; topologyStore.nodeMetricValue[m]?.values?.find((val: { id: string; value: unknown }) => val.id === data.id) ||
{};
const opt: MetricConfigOpt = nodeMetricConfig[index] || {}; const opt: MetricConfigOpt = nodeMetricConfig[index] || {};
return ` <div class="mb-5"><span class="grey">${opt.label || m}: </span>${metric.value} ${opt.unit || ""}</div>`; return ` <div class="mb-5"><span class="grey">${opt.label || m}: </span>${metric.value} ${opt.unit || ""}</div>`;

View File

@@ -159,7 +159,7 @@ limitations under the License. -->
import zoom from "@/views/dashboard/related/components/utils/zoom"; import zoom from "@/views/dashboard/related/components/utils/zoom";
import { useQueryTopologyExpressionsProcessor } from "@/hooks/useExpressionsProcessor"; import { useQueryTopologyExpressionsProcessor } from "@/hooks/useExpressionsProcessor";
import { ConfigFieldTypes } from "@/views/dashboard/data"; import { ConfigFieldTypes } from "@/views/dashboard/data";
/*global Nullable, defineProps */ /*global Nullable, defineProps, Indexable */
const props = defineProps({ const props = defineProps({
config: { config: {
type: Object as PropType<any>, type: Object as PropType<any>,
@@ -285,20 +285,16 @@ limitations under the License. -->
if (!expression) { if (!expression) {
return; return;
} }
const { getExpressionQuery } = useQueryTopologyExpressionsProcessor( const { getMetrics } = useQueryTopologyExpressionsProcessor(
[expression], [expression],
topologyStore.nodes.filter((d: Node) => d.isReal), topologyStore.nodes.filter((d: Node) => d.isReal),
); );
const param = getExpressionQuery(); const metrics = await getMetrics();
const res = await topologyStore.getTopologyExpressionValue(param);
if (res.errors) { topologyStore.setLegendValues(expression, metrics);
ElMessage.error(res.errors);
} else {
topologyStore.setLegendValues([expression], res.data);
}
} }
function getNodeStatus(d: any) { function getNodeStatus(d: Indexable) {
const { legendMQE } = settings.value; const { legendMQE } = settings.value;
if (!legendMQE) { if (!legendMQE) {
return icons.CUBE; return icons.CUBE;
@@ -313,8 +309,7 @@ limitations under the License. -->
const nodeMetricConfig = settings.value.nodeMetricConfig || []; const nodeMetricConfig = settings.value.nodeMetricConfig || [];
const html = nodeMetrics.map((m, index) => { const html = nodeMetrics.map((m, index) => {
const metric = const metric =
(topologyStore.nodeMetricValue[m] && topologyStore.nodeMetricValue[m]?.values?.find((val: { id: string; value: string }) => val.id === data.id) ||
topologyStore.nodeMetricValue[m].values.find((val: { id: string; value: string }) => val.id === data.id)) ||
{}; {};
const opt: MetricConfigOpt = nodeMetricConfig[index] || {}; const opt: MetricConfigOpt = nodeMetricConfig[index] || {};
return ` <div class="mb-5"><span class="grey">${opt.label || m}: </span>${metric.value || NaN} ${ return ` <div class="mb-5"><span class="grey">${opt.label || m}: </span>${metric.value || NaN} ${
@@ -339,7 +334,7 @@ limitations under the License. -->
const linkClientMetricConfig: MetricConfigOpt[] = settings.value.linkClientMetricConfig || []; const linkClientMetricConfig: MetricConfigOpt[] = settings.value.linkClientMetricConfig || [];
const linkServerMetrics: string[] = settings.value.linkServerExpressions || []; const linkServerMetrics: string[] = settings.value.linkServerExpressions || [];
const htmlServer = linkServerMetrics.map((m, index) => { const htmlServer = linkServerMetrics.map((m, index) => {
const metric = topologyStore.linkServerMetrics[m].values.find( const metric = topologyStore.linkServerMetrics[m]?.values?.find(
(val: { id: string; value: unknown }) => val.id === data.id, (val: { id: string; value: unknown }) => val.id === data.id,
); );
if (metric) { if (metric) {
@@ -351,7 +346,7 @@ limitations under the License. -->
}); });
const htmlClient = linkClientMetrics.map((m: string, index: number) => { const htmlClient = linkClientMetrics.map((m: string, index: number) => {
const opt: MetricConfigOpt = linkClientMetricConfig[index] || {}; const opt: MetricConfigOpt = linkClientMetricConfig[index] || {};
const metric = topologyStore.linkClientMetrics[m].values.find( const metric = topologyStore.linkClientMetrics[m]?.values?.find(
(val: { id: string; value: unknown }) => val.id === data.id, (val: { id: string; value: unknown }) => val.id === data.id,
); );
if (metric) { if (metric) {