From 536df8c052c68820947de55f82dc2e873a69e828 Mon Sep 17 00:00:00 2001 From: Fine0830 Date: Thu, 14 Nov 2024 11:38:13 +0800 Subject: [PATCH] feat: Split topology metric query to avoid exceeding the maximum query complexity (#429) --- src/hooks/data.ts | 2 + src/hooks/useExpressionsProcessor.ts | 63 +++++++++++++------ src/store/modules/topology.ts | 40 +++--------- .../related/topology/config/Settings.vue | 2 +- .../related/topology/service/ServiceMap.vue | 2 +- 5 files changed, 58 insertions(+), 51 deletions(-) diff --git a/src/hooks/data.ts b/src/hooks/data.ts index 1ad8867e..f797fba6 100644 --- a/src/hooks/data.ts +++ b/src/hooks/data.ts @@ -112,3 +112,5 @@ export const LightChartColors = [ "#546570", "#c4ccd3", ]; + +export const MaxQueryLength = 120; diff --git a/src/hooks/useExpressionsProcessor.ts b/src/hooks/useExpressionsProcessor.ts index c086b520..ecd74cfc 100644 --- a/src/hooks/useExpressionsProcessor.ts +++ b/src/hooks/useExpressionsProcessor.ts @@ -14,9 +14,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { RespFields, MaximumEntities } from "./data"; +import { RespFields, MaximumEntities, MaxQueryLength } from "./data"; import { EntityType, ExpressionResultType } from "@/views/dashboard/data"; import { ElMessage } from "element-plus"; +import { useTopologyStore } from "@/store/modules/topology"; import { useDashboardStore } from "@/store/modules/dashboard"; import { useSelectorStore } from "@/store/modules/selectors"; import { useAppStoreWithOut } from "@/store/modules/app"; @@ -24,6 +25,14 @@ import type { MetricConfigOpt } from "@/types/dashboard"; import type { Instance, Endpoint, Service } from "@/types/selector"; import type { Node, Call } from "@/types/topology"; +function chunkArray(array: any[], chunkSize: number) { + const result = []; + for (let i = 0; i < array.length; i += chunkSize) { + result.push(array.slice(i, i + chunkSize)); + } + return result; +} + export async function useDashboardQueryProcessor(configList: Indexable[]) { function expressionsGraphql(config: Indexable, idx: number) { if (!(config.metrics && config.metrics[0])) { @@ -181,13 +190,6 @@ export async function useDashboardQueryProcessor(configList: Indexable[]) { return { 0: { source: {}, tips: [], typesOfMQE: [] } }; } } - function chunkArray(array: any[], chunkSize: number) { - const result = []; - for (let i = 0; i < array.length; i += chunkSize) { - result.push(array.slice(i, i + chunkSize)); - } - return result; - } const partArr = chunkArray(configList, 6); const promiseArr = partArr.map((d: Array) => fetchMetrics(d)); @@ -394,7 +396,7 @@ export function useQueryTopologyExpressionsProcessor(metrics: string[], instance const appStore = useAppStoreWithOut(); const dashboardStore = useDashboardStore(); - function getExpressionQuery() { + function getExpressionQuery(partMetrics?: string[]) { const conditions: { [key: string]: unknown } = { duration: appStore.durationTime, }; @@ -448,7 +450,7 @@ export function useQueryTopologyExpressionsProcessor(metrics: string[], instance }; variables.push(`$entity${index}: Entity!`); conditions[`entity${index}`] = entity; - const f = metrics.map((name: string, idx: number) => { + const f = (partMetrics || metrics).map((name: string, idx: number) => { if (index === 0) { variables.push(`$expression${idx}: String!`); conditions[`expression${idx}`] = name; @@ -462,19 +464,19 @@ export function useQueryTopologyExpressionsProcessor(metrics: string[], instance return { queryStr, conditions }; } - function handleExpressionValues(resp: { [key: string]: any }) { + function handleExpressionValues(partMetrics: string[], resp: { [key: string]: any }) { const obj: any = {}; for (let idx = 0; idx < instances.length; idx++) { - for (let index = 0; index < metrics.length; index++) { + for (let index = 0; index < partMetrics.length; index++) { const k = "expression" + idx + index; - if (metrics[index]) { - if (!obj[metrics[index]]) { - obj[metrics[index]] = { + if (partMetrics[index]) { + if (!obj[partMetrics[index]]) { + obj[partMetrics[index]] = { values: [], }; } - obj[metrics[index]].values.push({ - value: resp[k].results[0] && resp[k].results[0].values[0].value, + obj[partMetrics[index]].values.push({ + value: resp[k] && resp[k].results[0] && resp[k].results[0].values[0].value, id: instances[idx].id, }); } @@ -482,6 +484,31 @@ export function useQueryTopologyExpressionsProcessor(metrics: string[], instance } return obj; } + async function fetchMetrics(partMetrics: string[]) { + const topologyStore = useTopologyStore(); + const param = getExpressionQuery(partMetrics); + const res = await topologyStore.getTopologyExpressionValue(param); + if (res.errors) { + ElMessage.error(res.errors); + return; + } + return handleExpressionValues(partMetrics, res.data); + } - return { getExpressionQuery, handleExpressionValues }; + async function getMetrics() { + const count = Math.floor(MaxQueryLength / instances.length); + const metricsArr = chunkArray(metrics, count); + const promiseArr = metricsArr.map((d: string[]) => fetchMetrics(d)); + const responseList = await Promise.all(promiseArr); + let resp = {}; + for (const item of responseList) { + resp = { + ...resp, + ...item, + }; + } + return resp; + } + + return { getMetrics, getExpressionQuery }; } diff --git a/src/store/modules/topology.ts b/src/store/modules/topology.ts index 66354f01..c5379cfd 100644 --- a/src/store/modules/topology.ts +++ b/src/store/modules/topology.ts @@ -24,7 +24,6 @@ import { useAppStoreWithOut } from "@/store/modules/app"; import type { AxiosResponse } from "axios"; import query from "@/graphql/fetch"; import { useQueryTopologyExpressionsProcessor } from "@/hooks/useExpressionsProcessor"; -import { ElMessage } from "element-plus"; interface MetricVal { [key: string]: { values: { id: string; value: unknown }[] }; @@ -443,7 +442,7 @@ export const topologyStore = defineStore({ return { calls, nodes }; }, - async getNodeExpressionValue(param: { queryStr: string; conditions: { [key: string]: unknown } }) { + async getTopologyExpressionValue(param: { queryStr: string; conditions: { [key: string]: unknown } }) { const res: AxiosResponse = await query(param); if (res.data.errors) { @@ -461,14 +460,8 @@ export const topologyStore = defineStore({ if (!calls.length) { return; } - const { getExpressionQuery, handleExpressionValues } = useQueryTopologyExpressionsProcessor(expressions, calls); - const param = getExpressionQuery(); - const res = await this.getNodeExpressionValue(param); - if (res.errors) { - ElMessage.error(res.errors); - return; - } - const metrics = handleExpressionValues(res.data); + const { getMetrics } = useQueryTopologyExpressionsProcessor(expressions, calls); + const metrics = await getMetrics(); if (type === "SERVER") { this.setLinkServerMetrics(metrics); } else { @@ -484,17 +477,11 @@ export const topologyStore = defineStore({ this.setNodeMetricValue({}); return; } - const { getExpressionQuery, handleExpressionValues } = useQueryTopologyExpressionsProcessor( + const { getMetrics } = useQueryTopologyExpressionsProcessor( expressions, this.nodes.filter((d: Node) => d.isReal), ); - const param = getExpressionQuery(); - const res = await this.getNodeExpressionValue(param); - if (res.errors) { - ElMessage.error(res.errors); - return; - } - const metrics = handleExpressionValues(res.data); + const metrics = await getMetrics(); this.setNodeMetricValue(metrics); }, async getHierarchyServiceTopology() { @@ -550,17 +537,6 @@ export const topologyStore = defineStore({ this.setHierarchyInstanceTopology(res.data.data.hierarchyInstanceTopology || {}, levels); return res.data; }, - async queryHierarchyExpressions(expressions: string[], nodes: Node[]) { - const { getExpressionQuery, handleExpressionValues } = useQueryTopologyExpressionsProcessor(expressions, nodes); - const param = getExpressionQuery(); - const res = await this.getNodeExpressionValue(param); - if (res.errors) { - ElMessage.error(res.errors); - return; - } - const metrics = handleExpressionValues(res.data); - return metrics; - }, async queryHierarchyNodeExpressions(expressions: string[], layer: string) { const nodes = this.hierarchyServiceNodes.filter((n: HierarchyNode) => n.layer === layer); if (!nodes.length) { @@ -571,7 +547,8 @@ export const topologyStore = defineStore({ this.setHierarchyNodeMetricValue({}, layer); return; } - const metrics = await this.queryHierarchyExpressions(expressions, nodes); + const { getMetrics } = useQueryTopologyExpressionsProcessor(expressions, nodes); + const metrics = await getMetrics(); this.setHierarchyNodeMetricValue(metrics, layer); }, async queryHierarchyInstanceNodeExpressions(expressions: string[], layer: string) { @@ -585,7 +562,8 @@ export const topologyStore = defineStore({ this.setHierarchyInstanceNodeMetricValue({}, layer); return; } - const metrics = await this.queryHierarchyExpressions(expressions, nodes); + const { getMetrics } = useQueryTopologyExpressionsProcessor(expressions, nodes); + const metrics = await getMetrics(); this.setHierarchyInstanceNodeMetricValue(metrics, layer); }, }, diff --git a/src/views/dashboard/related/topology/config/Settings.vue b/src/views/dashboard/related/topology/config/Settings.vue index 6c0e0841..d8929b5b 100644 --- a/src/views/dashboard/related/topology/config/Settings.vue +++ b/src/views/dashboard/related/topology/config/Settings.vue @@ -249,7 +249,7 @@ limitations under the License. --> topologyStore.nodes.filter((d: Node) => d.isReal), ); const param = getExpressionQuery(); - const res = await topologyStore.getNodeExpressionValue(param); + const res = await topologyStore.getTopologyExpressionValue(param); if (res.errors) { ElMessage.error(res.errors); } else { diff --git a/src/views/dashboard/related/topology/service/ServiceMap.vue b/src/views/dashboard/related/topology/service/ServiceMap.vue index eba245d6..947b927c 100644 --- a/src/views/dashboard/related/topology/service/ServiceMap.vue +++ b/src/views/dashboard/related/topology/service/ServiceMap.vue @@ -290,7 +290,7 @@ limitations under the License. --> topologyStore.nodes.filter((d: Node) => d.isReal), ); const param = getExpressionQuery(); - const res = await topologyStore.getNodeExpressionValue(param); + const res = await topologyStore.getTopologyExpressionValue(param); if (res.errors) { ElMessage.error(res.errors); } else {