feat: set node metrics

This commit is contained in:
Qiuxia Fan 2022-02-14 16:33:29 +08:00
parent 9f77830a1a
commit 72080f7bd5
8 changed files with 134 additions and 116 deletions

37
src/graphql/fetch.ts Normal file
View File

@ -0,0 +1,37 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import axios, { AxiosResponse } from "axios";
import { cancelToken } from "@/utils/cancelToken";
async function query(param: {
queryStr: string;
conditions: { [key: string]: unknown };
}) {
const res: AxiosResponse = await axios.post(
"/graphql",
{ query: param.queryStr, variables: { ...param.conditions } },
{ cancelToken: cancelToken() }
);
if (res.data.errors) {
res.data.errors = res.data.errors
.map((e: { message: string }) => e.message)
.join(" ");
}
return res;
}
export default query;

View File

@ -27,7 +27,7 @@ const query: { [key: string]: string } = {
...dashboard,
...topology,
};
class Graph {
class Graphql {
private queryData = "";
public query(queryData: string) {
this.queryData = queryData;
@ -57,4 +57,4 @@ class Graph {
}
}
export default new Graph();
export default new Graphql();

View File

@ -258,3 +258,28 @@ export function usePodsSource(
});
return data;
}
export function useQueryNodesMetrics(metrics: string[], ids: string[]) {
const appStore = useAppStoreWithOut();
const conditions: { [key: string]: unknown } = {
duration: appStore.durationTime,
ids,
};
const variables: string[] = [`$duration: Duration!`, `$ids: [ID!]!`];
const fragmentList = metrics.map((d: string, index: number) => {
conditions[`m${index}`] = d;
variables.push(`$m${index}: String!`);
return `${d}: getValues(metric: {
name: $m${index}
ids: $ids
}, duration: $duration) {
values {
id
value
}
}`;
});
const queryStr = `query queryData(${variables}) {${fragmentList.join(" ")}}`;
return { queryStr, conditions };
}

View File

@ -18,6 +18,7 @@ import { defineStore } from "pinia";
import { store } from "@/store";
import { LayoutConfig } from "@/types/dashboard";
import graphql from "@/graphql";
import query from "@/graphql/fetch";
import {
ConfigData,
ConfigData1,
@ -29,8 +30,7 @@ import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import { NewControl } from "../data";
import { Duration } from "@/types/app";
import axios, { AxiosResponse } from "axios";
import { cancelToken } from "@/utils/cancelToken";
import { AxiosResponse } from "axios";
interface DashboardState {
showConfig: boolean;
layout: LayoutConfig[];
@ -212,11 +212,7 @@ export const dashboardStore = defineStore({
queryStr: string;
conditions: { [key: string]: unknown };
}) {
const res: AxiosResponse = await axios.post(
"/graphql",
{ query: param.queryStr, variables: { ...param.conditions } },
{ cancelToken: cancelToken() }
);
const res: AxiosResponse = await query(param);
return res.data;
},
},

View File

@ -18,15 +18,17 @@ import { defineStore } from "pinia";
import { store } from "@/store";
import { Node, Call } from "@/types/topology";
import graphql from "@/graphql";
import { AxiosResponse } from "axios";
import { useSelectorStore } from "@/store/modules/selectors";
import { useAppStoreWithOut } from "@/store/modules/app";
import { AxiosResponse } from "axios";
import query from "@/graphql/fetch";
interface TopologyState {
node: Node | null;
call: Call | null;
calls: Call[];
nodes: Node[];
nodeMetrics: { id: string; value: unknown }[];
}
export const topologyStore = defineStore({
@ -36,6 +38,7 @@ export const topologyStore = defineStore({
nodes: [],
node: null,
call: null,
nodeMetrics: [],
}),
actions: {
setNode(node: Node) {
@ -48,75 +51,6 @@ export const topologyStore = defineStore({
this.nodes = data.nodes;
this.calls = data.calls;
},
async setMetrics(data: { nodes: Node[]; calls: Call[] }, params: any) {
const ids = data.nodes.map((i: Node) => i.id);
const idsC = data.calls
.filter((i: Call) => i.detectPoints.includes("CLIENT"))
.map((b: any) => b.id);
const idsS = data.calls
.filter((i: Call) => i.detectPoints.includes("SERVER"))
.map((b: any) => b.id);
const res: AxiosResponse = await graphql
.query("queryTopoInfo")
.params({ ...params, ids, idsC, idsS });
const resInfo = res.data.data;
if (!resInfo.sla) {
return this.setTopology(data);
}
for (let i = 0; i < resInfo.sla.values.length; i += 1) {
for (let j = 0; j < data.nodes.length; j += 1) {
if (data.nodes[j].id === resInfo.sla.values[i].id) {
data.nodes[j] = {
...data.nodes[j],
isGroupActive: true,
sla: resInfo.sla.values[i].value
? resInfo.sla.values[i].value / 100
: -1,
cpm: resInfo.nodeCpm.values[i]
? resInfo.nodeCpm.values[i].value
: -1,
latency: resInfo.nodeLatency.values[i]
? resInfo.nodeLatency.values[i].value
: -1,
};
}
}
}
if (!resInfo.cpmC) {
return this.setTopology(data);
}
for (let i = 0; i < resInfo.cpmC.values.length; i += 1) {
for (let j = 0; j < data.calls.length; j += 1) {
if (data.calls[j].id === resInfo.cpmC.values[i].id) {
data.calls[j] = {
...data.calls[j],
isGroupActive: true,
cpm: resInfo.cpmC.values[i] ? resInfo.cpmC.values[i].value : "",
latency: resInfo.latencyC.values[i]
? resInfo.latencyC.values[i].value
: "",
};
}
}
}
if (!resInfo.cpmS) {
return this.setTopology(data);
}
for (let i = 0; i < resInfo.cpmS.values.length; i += 1) {
for (let j = 0; j < data.calls.length; j += 1) {
if (data.calls[j].id === resInfo.cpmS.values[i].id) {
data.calls[j] = {
...data.calls[j],
cpm: resInfo.cpmS.values[i] ? resInfo.cpmS.values[i].value : "",
latency: resInfo.latencyS.values[i]
? resInfo.latencyS.values[i].value
: "",
};
}
}
}
this.setTopology(data);
},
async getServiceTopology() {
const serviceId = useSelectorStore().currentService.id;
const duration = useAppStoreWithOut().durationTime;
@ -127,7 +61,7 @@ export const topologyStore = defineStore({
duration,
});
if (!res.data.errors) {
this.setMetrics(res.data.data.topology, { duration });
this.setTopology(res.data.data.topology);
}
return res.data;
},
@ -139,7 +73,7 @@ export const topologyStore = defineStore({
duration,
});
if (!res.data.errors) {
this.setMetrics(res.data.data.topology, { duration });
this.setTopology(res.data.data.topology);
}
return res.data;
},
@ -173,6 +107,18 @@ export const topologyStore = defineStore({
}
return res.data;
},
async getNodeMetrics(param: {
queryStr: string;
conditions: { [key: string]: unknown };
}) {
const res: AxiosResponse = await query(param);
if (res.data.errors) {
return res.data;
}
this.nodeMetrics = res.data.data;
return res.data;
},
},
});

View File

@ -122,8 +122,9 @@ onMounted(async () => {
event.stopPropagation();
event.preventDefault();
topologyStore.setNode(null);
showSetting.value = false;
// showSetting.value = false;
});
update();
});
function ticked() {
link.value.attr(
@ -182,6 +183,9 @@ function handleLinkClick(event: any, d: Call) {
}
function update() {
// node element
if (!node.value || !link.value) {
return;
}
node.value = node.value.data(topologyStore.nodes, (d: Node) => d.id);
node.value.exit().remove();
node.value = nodeElement(
@ -192,9 +196,25 @@ function update() {
dragged: dragged,
dragended: dragended,
handleNodeClick: handleNodeClick,
tipHtml: (data: Node) => {
const nodeMetrics: string[] = settings.value.nodeMetrics;
if (!nodeMetrics) {
return;
}
const html = nodeMetrics.map((m) => {
const metric =
topologyStore.nodeMetrics[m].values.filter(
(val: { id: string; value: unknown }) => val.id === data.id
)[0] || {};
return ` <div class="mb-5"><span class="grey">${m}: </span>${metric.value}</div>`;
});
return [
` <div class="mb-5"><span class="grey">name: </span>${data.name}</div>`,
...html,
].join(" ");
},
},
tip.value,
t
tip.value
).merge(node.value);
// line element
link.value = link.value.data(topologyStore.calls, (d: Call) => d.id);
@ -207,7 +227,7 @@ function update() {
anchor.value.enter(),
{
handleLinkClick: handleLinkClick,
$tip: (data: any) =>
$tip: (data: Call) =>
`
<div class="mb-5"><span class="grey">${t("cpm")}: </span>${
data.cpm
@ -254,22 +274,19 @@ function update() {
}
}
function handleGoEndpoint() {
const node = topologyStore.node;
const path = `/dashboard/${dashboardStore.layerId}/Endpoint/${node.id}/${settings.value.endpointDashboard}`;
const path = `/dashboard/${dashboardStore.layerId}/Endpoint/${topologyStore.node.id}/${settings.value.endpointDashboard}`;
const routeUrl = router.resolve({ path });
window.open(routeUrl.href, "_blank");
}
function handleGoInstance() {
const node = topologyStore.node;
const path = `/dashboard/${dashboardStore.layerId}/ServiceInstance/${node.id}/${settings.value.instanceDashboard}`;
const path = `/dashboard/${dashboardStore.layerId}/ServiceInstance/${topologyStore.node.id}/${settings.value.instanceDashboard}`;
const routeUrl = router.resolve({ path });
window.open(routeUrl.href, "_blank");
}
function handleGoDashboard() {
const node = topologyStore.node;
const path = `/dashboard/${dashboardStore.layerId}/Service/${node.id}/${settings.value.nodeDashboard}`;
const path = `/dashboard/${dashboardStore.layerId}/Service/${topologyStore.node.id}/${settings.value.nodeDashboard}`;
const routeUrl = router.resolve({ path });
window.open(routeUrl.href, "_blank");
@ -312,12 +329,12 @@ function updateSettings(config: any) {
onBeforeUnmount(() => {
window.removeEventListener("resize", resize);
});
watch(
() => [topologyStore.calls, topologyStore.nodes],
() => {
update();
}
);
// watch(
// () => [topologyStore.calls, topologyStore.nodes],
// () => {
// update();
// }
// );
</script>
<style lang="scss">
.micro-topo-chart {

View File

@ -76,14 +76,18 @@ limitations under the License. -->
import { reactive } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useTopologyStore } from "@/store/modules/topology";
import { ElMessage } from "element-plus";
import { MetricCatalog } from "../../data";
import { Option } from "@/types/app";
import { useQueryNodesMetrics } from "@/hooks/useProcessor";
import { Node, Call } from "@/types/topology";
/*global defineEmits */
const emit = defineEmits(["update"]);
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const topologyStore = useTopologyStore();
const states = reactive<{
linkDashboard: string;
nodeDashboard: string;
@ -130,13 +134,21 @@ function updateSettings() {
nodeMetrics: states.nodeMetrics,
});
}
function changeLinkMetrics(options: Option[]) {
async function changeLinkMetrics(options: Option[]) {
states.linkMetrics = options.map((d: Option) => d.value);
updateSettings();
}
function changeNodeMetrics(options: Option[]) {
async function changeNodeMetrics(options: Option[]) {
states.nodeMetrics = options.map((d: Option) => d.value);
updateSettings();
const ids = topologyStore.nodes.map((d: Node) => d.id);
const param = await useQueryNodesMetrics(states.nodeMetrics, ids);
const res = await topologyStore.getNodeMetrics(param);
if (res.errors) {
ElMessage.error(res.errors);
}
}
</script>
<style lang="scss" scoped>

View File

@ -18,7 +18,7 @@ import icons from "@/assets/img/icons";
import { Node } from "@/types/topology";
icons["KAFKA-CONSUMER"] = icons.KAFKA;
export default (d3: any, graph: any, funcs: any, tip: any, t: any) => {
export default (d3: any, graph: any, funcs: any, tip: any) => {
const nodeEnter = graph
.append("g")
.call(
@ -29,22 +29,7 @@ export default (d3: any, graph: any, funcs: any, tip: any, t: any) => {
.on("end", funcs.dragended)
)
.on("mouseover", function (event: any, d: Node) {
tip
.html(() => {
return `
<div class="mb-5"><span class="grey">${t("name")}: </span>${
d.name
}</div>
<div class="mb-5"><span class="grey">${t("cpm")}: </span>${
d.cpm
}</div>
<div class="mb-5"><span class="grey">${t("latency")}: </span>${
d.latency
}</div>
<div><span class="grey">${t("sla")}: </span>${d.sla}</div>
`;
})
.show(d, this);
tip.html(funcs.tipHtml).show(d, this);
})
.on("mouseout", function () {
tip.hide(this);