mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-07-18 10:24:10 +00:00
feat: set node metrics
This commit is contained in:
parent
9f77830a1a
commit
72080f7bd5
37
src/graphql/fetch.ts
Normal file
37
src/graphql/fetch.ts
Normal 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;
|
@ -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();
|
||||
|
@ -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 };
|
||||
}
|
||||
|
@ -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;
|
||||
},
|
||||
},
|
||||
|
@ -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;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user