Feat: Implement endpoint dependency and metrics (#321)
diff --git a/src/assets/lang/en.ts b/src/assets/lang/en.ts
index 66d52ab..05ce744 100644
--- a/src/assets/lang/en.ts
+++ b/src/assets/lang/en.ts
@@ -52,9 +52,9 @@
cpm: 'Cpm',
sla: 'SLA',
latency: 'Latency',
- avgResponseTime: 'Avg Response Time',
- avgThroughput: 'Avg Throughput',
- avgSLA: 'Avg SLA',
+ avgResponseTime: 'Avg Response Time ( ms )',
+ avgThroughput: 'Load (CPM - calls per minute)',
+ avgSLA: 'Successful Rate ( % )',
all: 'All',
success: 'Success',
error: 'Error',
@@ -100,7 +100,7 @@
weekCutTip: 'Last 1 week',
monthCutTip: 'Last 1 month',
serverZone: 'Server Zone',
- percentResponse: 'Response Time Percentile',
+ percentResponse: 'Response Time Percentile ( ms )',
exportImage: 'Export image',
queryData: 'Query',
previousService: 'Previous Service',
@@ -148,6 +148,7 @@
descendOrder: 'Descend Order',
increaseOrder: 'Increase Order',
chartType: 'Chart Type',
+ currentDepth: 'Current Depth',
};
export default m;
diff --git a/src/assets/lang/zh.ts b/src/assets/lang/zh.ts
index 6ca18a8..abeb7fb 100644
--- a/src/assets/lang/zh.ts
+++ b/src/assets/lang/zh.ts
@@ -148,6 +148,7 @@
descendOrder: '递减顺序',
increaseOrder: '递增顺序',
chartType: '图表类型',
+ currentDepth: '当前深度',
};
export default m;
diff --git a/src/graph/fragments/topology.ts b/src/graph/fragments/topology.ts
index 3075faf..18288f9 100644
--- a/src/graph/fragments/topology.ts
+++ b/src/graph/fragments/topology.ts
@@ -228,6 +228,26 @@
}
}`,
};
+export const endpointTopology = {
+ variable: ['$endpointId: ID!', '$duration: Duration!'],
+ query: `
+ endpointTopology: getEndpointDependencies(endpointId: $endpointId, duration: $duration) {
+ nodes {
+ id
+ name
+ serviceId
+ serviceName
+ type
+ isReal
+ }
+ calls {
+ id
+ source
+ target
+ detectPoints
+ }
+ }`,
+};
export const TopoMetric = {
variable: '$ids: [ID!]!',
query: `
@@ -371,3 +391,82 @@
}
`,
};
+export const TopoEndpointDependencyMetrics = {
+ variable: [
+ '$serviceName: String',
+ '$endpointName: String',
+ '$destServiceName: String',
+ '$destEndpointName: String',
+ '$duration: Duration!',
+ ],
+ query: `
+ endpointRelationPercentile: readLabeledMetricsValues(condition: {
+ name: "endpoint_relation_percentile"
+ entity: {
+ scope: EndpointRelation
+ serviceName: $serviceName
+ normal: true
+ endpointName: $endpointName
+ destNormal: true
+ destServiceName: $destServiceName
+ destEndpointName: $destEndpointName
+ }
+ }, labels: ["0", "1", "2", "3", "4"], duration: $duration) {
+ label
+ values {
+ values {value}
+ }
+ }
+ endpointRelationCpm: readMetricsValues(condition: {
+ name: "endpoint_relation_cpm"
+ entity: {
+ scope: EndpointRelation
+ serviceName: $serviceName
+ normal: true
+ endpointName: $endpointName
+ destNormal: true
+ destServiceName: $destServiceName
+ destEndpointName: $destEndpointName
+ }
+ }, duration: $duration) {
+ label
+ values {
+ values {value}
+ }
+ }
+ endpointRelationRespTime: readMetricsValues(condition: {
+ name: "endpoint_relation_resp_time"
+ entity: {
+ scope: EndpointRelation
+ serviceName: $serviceName
+ normal: true
+ endpointName: $endpointName
+ destNormal: true
+ destServiceName: $destServiceName
+ destEndpointName: $destEndpointName
+ }
+ }, duration: $duration) {
+ label
+ values {
+ values {value}
+ }
+ }
+ endpointRelationSla: readMetricsValues(condition: {
+ name: "endpoint_relation_sla"
+ entity: {
+ scope: EndpointRelation
+ serviceName: $serviceName
+ normal: true
+ endpointName: $endpointName
+ destNormal: true
+ destServiceName: $destServiceName
+ destEndpointName: $destEndpointName
+ }
+ }, duration: $duration) {
+ label
+ values {
+ values {value}
+ }
+ }
+`,
+};
diff --git a/src/graph/query/topology.ts b/src/graph/query/topology.ts
index e92126a..d1bf631 100644
--- a/src/graph/query/topology.ts
+++ b/src/graph/query/topology.ts
@@ -30,6 +30,8 @@
DependencyInstanceServerMetric,
DependencyInstanceClientMetric,
TopoServiceDetail,
+ endpointTopology,
+ TopoEndpointDependencyMetrics,
} from '../fragments/topology';
export const queryTopo = `query queryTopo(${Topo.variable}) {${Topo.query}}`;
@@ -72,3 +74,8 @@
export const queryTopoServiceDetail = `query queryTopoServiceDetail(
${TopoServiceDetail.variable}) {${TopoServiceDetail.query}}`;
+
+export const queryEndpointTopology = `query queryEndpointTopology(${endpointTopology.variable}) {${endpointTopology.query}}`;
+
+export const queryTopoEndpointDependencyMetrics = `query queryTopoEndpointDependencyMetrics(
+ ${TopoEndpointDependencyMetrics.variable}) {${TopoEndpointDependencyMetrics.query}}`;
diff --git a/src/store/modules/topology/index.ts b/src/store/modules/topology/index.ts
index bdf4b22..28d4ecb 100644
--- a/src/store/modules/topology/index.ts
+++ b/src/store/modules/topology/index.ts
@@ -18,13 +18,19 @@
import { Commit, ActionTree, Dispatch } from 'vuex';
import graph from '@/graph';
import * as types from '../../mutation-types';
-import { AxiosResponse } from 'axios';
+import axios, { AxiosPromise, AxiosResponse } from 'axios';
+import { cancelToken } from '@/utils/cancelToken';
interface Option {
key: string;
label: string;
}
-interface Call {
+export interface Duration {
+ start: string;
+ end: string;
+ step: string;
+}
+export interface Call {
avgResponseTime: number;
cpm: number;
isAlert: boolean;
@@ -47,12 +53,18 @@
type: string;
}
+export interface EndpointDependencyConidition {
+ serviceName: string;
+ endpointName: string;
+ destServiceName: string;
+ destEndpointName: string;
+ duration: Duration;
+}
+
export interface State {
callback: any;
calls: Call[];
nodes: Node[];
- _calls: Call[];
- _nodes: Node[];
detectPoints: string[];
selectedServiceCall: Call | null;
currentNode: any;
@@ -67,8 +79,14 @@
calls: Call[];
nodes: Node[];
};
+ endpointDependency: {
+ calls: Call[];
+ nodes: Node[];
+ };
selectedInstanceCall: Call | null;
instanceDependencyMetrics: { [key: string]: any };
+ endpointDependencyMetrics: { [key: string]: any };
+ currentEndpointDepth: { key: number; label: string };
queryInstanceMetricsType: string;
serviceThroughput: { Throughput: number[] };
serviceSLA: { SLA: number[] };
@@ -88,8 +106,6 @@
selectedServiceCall: null,
calls: [],
nodes: [],
- _calls: [],
- _nodes: [],
currentNode: {},
currentLink: {},
current: {
@@ -104,8 +120,14 @@
calls: [],
nodes: [],
},
+ endpointDependency: {
+ calls: [],
+ nodes: [],
+ },
selectedInstanceCall: null,
instanceDependencyMetrics: {},
+ endpointDependencyMetrics: {},
+ currentEndpointDepth: { key: 2, label: '2' },
queryInstanceMetricsType: '',
serviceThroughput: { Throughput: [] },
serviceSLA: { SLA: [] },
@@ -150,10 +172,6 @@
state.calls = data.calls;
state.nodes = data.nodes;
},
- [types.SET_TOPO_COPY](state: State, data: any) {
- state._calls = data.calls;
- state._nodes = data.nodes;
- },
[types.SET_SELECTED_CALL](state: State, data: any) {
state.selectedServiceCall = data;
},
@@ -196,6 +214,26 @@
state.instanceDependencyMetrics.percentResponse[PercentileItem[index]] = item.values.map((i: any) => i.value);
});
},
+ [types.SET_ENDPOINT_DEPENDENCY_METRICS](state: State, data: { [key: string]: any }) {
+ state.endpointDependencyMetrics.cpm = data.endpointRelationCpm
+ ? data.endpointRelationCpm.values.values.map((i: any) => i.value)
+ : [];
+ state.endpointDependencyMetrics.respTime = data.endpointRelationRespTime
+ ? data.endpointRelationRespTime.values.values.map((i: any) => i.value)
+ : [];
+ state.endpointDependencyMetrics.sla = data.endpointRelationSla
+ ? data.endpointRelationSla.values.values.map((i: any) => i.value)
+ : [];
+ state.endpointDependencyMetrics.percentile = {};
+ if (!data.endpointRelationPercentile) {
+ return;
+ }
+ for (const item of data.endpointRelationPercentile) {
+ state.endpointDependencyMetrics.percentile[PercentileItem[Number(item.label)]] = item.values.values.map(
+ (i: any) => i.value,
+ );
+ }
+ },
[types.SET_INSTANCE_DEPEDENCE_TYPE](state: State, data: string) {
state.queryInstanceMetricsType = data;
},
@@ -263,41 +301,43 @@
state.topoEndpoints.push(comp);
window.localStorage.setItem('topologyEndpoints', JSON.stringify(state.topoEndpoints));
},
+ [types.SET_ENDPOINT_DEPENDENCY](state: State, data: { calls: Call[]; nodes: Node[] }) {
+ state.endpointDependency = data;
+ },
+ [types.SET_ENDPOINT_DEPTH](state: State, data: { key: number; label: string }) {
+ state.currentEndpointDepth = data;
+ },
};
// actions
const actions: ActionTree<State, any> = {
- FILTER_TOPO(context: { commit: Commit; state: State }, params: { services: string[]; group: string }) {
- const tempCalls = [...context.state._calls];
- const tempNodes = [...context.state._nodes];
- if (params.group === 'all') {
- context.commit(types.SET_TOPO, { calls: context.state._calls, nodes: context.state._nodes });
- return;
+ GET_SERVICES(context: { commit: Commit }, params: { duration: Duration; keyword: string }) {
+ if (!params.keyword) {
+ params.keyword = '';
}
- const nodeInCalls: string[] = [];
- const resultNodes: Node[] = [];
- const resultCalls: Call[] = [];
- tempCalls.forEach((call: any) => {
- if (
- params.services.some((i: string) => call.source.id === i) ||
- params.services.some((i: string) => call.target.id === i)
- ) {
- nodeInCalls.push(call.source.id);
- nodeInCalls.push(call.target.id);
- resultCalls.push(call);
- }
- });
- const setNodes: string[] = Array.from(new Set(nodeInCalls));
- tempNodes.forEach((node: any) => {
- if (setNodes.some((i: string) => node.id === i)) {
- resultNodes.push(node);
- }
- });
- context.commit(types.SET_TOPO, { calls: resultCalls, nodes: resultNodes });
+ return graph
+ .query('queryServices')
+ .params(params)
+ .then((res: AxiosResponse) => {
+ return res.data.data.services || [];
+ });
+ },
+ GET_SERVICE_ENDPOINTS(context: { commit: Commit }, params: { serviceId: string; keyword: string }) {
+ if (!params.serviceId) {
+ return new Promise((resolve) => resolve());
+ }
+ if (!params.keyword) {
+ params.keyword = '';
+ }
+ return graph
+ .query('queryEndpoints')
+ .params(params)
+ .then((res: AxiosResponse) => {
+ return res.data.data.getEndpoints || [];
+ });
},
CLEAR_TOPO(context: { commit: Commit; state: State }) {
context.commit(types.SET_TOPO, { calls: [], nodes: [] });
- context.commit(types.SET_TOPO_COPY, { calls: [], nodes: [] });
},
CLEAR_TOPO_INFO(context: { commit: Commit; state: State }) {
context.commit(types.SET_TOPO_RELATION, {});
@@ -316,7 +356,7 @@
context.dispatch('INSTANCE_RELATION_INFO', params);
}
},
- GET_TOPO_SERVICE_INFO(context: { commit: Commit; state: State }, params: any) {
+ GET_TOPO_SERVICE_INFO(context: { commit: Commit; state: State }, params: { id: string; duration: Duration }) {
if (!params.id) {
return;
}
@@ -346,7 +386,10 @@
context.commit(types.SET_SELECTED_CALL, params);
});
},
- GET_TOPO_SERVICE_DETAIL(context: { commit: Commit; state: State }, params: any) {
+ GET_TOPO_SERVICE_DETAIL(
+ context: { commit: Commit; state: State },
+ params: { serviceId: string; duration: Duration },
+ ) {
return graph
.query('queryTopoServiceDetail')
.params({
@@ -387,7 +430,6 @@
.then((info: AxiosResponse) => {
const resInfo = info.data.data;
if (!resInfo.sla) {
- context.commit(types.SET_TOPO_COPY, { calls, nodes });
return context.commit(types.SET_TOPO, { calls, nodes });
}
for (let i = 0; i < resInfo.sla.values.length; i += 1) {
@@ -404,7 +446,6 @@
}
}
if (!resInfo.cpmC) {
- context.commit(types.SET_TOPO_COPY, { calls, nodes });
return context.commit(types.SET_TOPO, { calls, nodes });
}
for (let i = 0; i < resInfo.cpmC.values.length; i += 1) {
@@ -420,7 +461,6 @@
}
}
if (!resInfo.cpmS) {
- context.commit(types.SET_TOPO_COPY, { calls, nodes });
return context.commit(types.SET_TOPO, { calls, nodes });
}
for (let i = 0; i < resInfo.cpmS.values.length; i += 1) {
@@ -434,17 +474,153 @@
}
}
}
- context.commit(types.SET_TOPO_COPY, { calls, nodes });
context.commit(types.SET_TOPO, { calls, nodes });
});
});
},
+ // todo sync
+ GET_ALL_ENDPOINT_DEPENDENCY(
+ context: { commit: Commit; state: State; dispatch: Dispatch },
+ params: { endpointIds: string[]; duration: Duration },
+ ) {
+ context.dispatch('GET_ENDPOINT_TOPO', params).then((res) => {
+ if (context.state.currentEndpointDepth.key > 1) {
+ const endpointIds = res.nodes.map((item: Node) => item.id);
+
+ context.dispatch('GET_ENDPOINT_TOPO', { endpointIds, duration: params.duration }).then((json) => {
+ if (context.state.currentEndpointDepth.key > 2) {
+ const ids = json.nodes.map((item: Node) => item.id);
+
+ context.dispatch('GET_ENDPOINT_TOPO', { endpointIds: ids, duration: params.duration }).then((topo) => {
+ if (context.state.currentEndpointDepth.key > 3) {
+ const endpoints = topo.nodes.map((item: Node) => item.id);
+ context
+ .dispatch('GET_ENDPOINT_TOPO', { endpointIds: endpoints, duration: params.duration })
+ .then((data) => {
+ if (context.state.currentEndpointDepth.key > 4) {
+ context
+ .dispatch('GET_ENDPOINT_TOPO', { endpointIds: endpoints, duration: params.duration })
+ .then((topos) => {
+ context.commit(types.SET_ENDPOINT_DEPENDENCY, topos);
+ });
+ } else {
+ context.commit(types.SET_ENDPOINT_DEPENDENCY, data);
+ }
+ });
+ } else {
+ context.commit(types.SET_ENDPOINT_DEPENDENCY, topo);
+ }
+ });
+ } else {
+ context.commit(types.SET_ENDPOINT_DEPENDENCY, json);
+ }
+ });
+ } else {
+ context.commit(types.SET_ENDPOINT_DEPENDENCY, res);
+ }
+ });
+ },
+ GET_ENDPOINT_TOPO(context: { commit: Commit; state: State }, params: { endpointIds: string[]; duration: Duration }) {
+ const variables = ['$duration: Duration!'];
+ const fragment = params.endpointIds.map((id: string, index: number) => {
+ return `endpointTopology${index}: getEndpointDependencies(endpointId: "${id}", duration: $duration) {
+ nodes {
+ id
+ name
+ serviceId
+ serviceName
+ type
+ isReal
+ }
+ calls {
+ id
+ source
+ target
+ detectPoints
+ }
+ }`;
+ });
+ const querys = `query queryData(${variables}) {${fragment}}`;
+ return axios
+ .post('/graphql', { query: querys, variables: { duration: params.duration } }, { cancelToken: cancelToken() })
+ .then((res: AxiosResponse) => {
+ if (res.data.errors) {
+ context.commit(types.SET_ENDPOINT_DEPENDENCY, { calls: [], nodes: [] });
+ return;
+ }
+ const topo = res.data.data;
+ const calls = [] as any;
+ let nodes = [] as any;
+ for (const key of Object.keys(topo)) {
+ calls.push(...topo[key].calls);
+ nodes.push(...topo[key].nodes);
+ }
+ const obj = {} as any;
+ nodes = nodes.reduce((prev: Node[], next: Node) => {
+ if (!obj[next.id]) {
+ obj[next.id] = true;
+ prev.push(next);
+ }
+ return prev;
+ }, []);
+ const queryVariables = ['$duration: Duration!'];
+ const fragments = calls
+ .map((call: Call & EndpointDependencyConidition, index: number) => {
+ let source = {} as any;
+ let target = {} as any;
+ for (const node of nodes) {
+ if (node.id === call.source) {
+ source = node;
+ call.serviceName = node.serviceName;
+ call.endpointName = node.name;
+ }
+ if (node.id === call.target) {
+ target = node;
+ call.destServiceName = node.serviceName;
+ call.destEndpointName = node.name;
+ }
+ }
+ return `cpm_${index}: readMetricsValue(condition: {
+ name: "endpoint_relation_cpm"
+ entity: {
+ scope: EndpointRelation
+ serviceName: "${source.serviceName}"
+ normal: true
+ endpointName: "${source.name}"
+ destNormal: true
+ destServiceName: "${target.serviceName}"
+ destEndpointName: "${target.name}"
+ }
+ }, duration: $duration)`;
+ })
+ .join(' ');
+ const query = `query queryData(${queryVariables}) {${fragments}}`;
+ return axios
+ .post('/graphql', { query, variables: { duration: params.duration } }, { cancelToken: cancelToken() })
+ .then((json: AxiosResponse<any>) => {
+ if (json.data.errors) {
+ context.commit(types.SET_ENDPOINT_DEPENDENCY, { calls: [], nodes: [] });
+ return;
+ }
+ const cpms = json.data.data;
+ const keys = Object.keys(cpms);
+ for (const key of keys) {
+ const index = Number(key.split('_')[1]);
+ calls[index].value = cpms[key] || 0.01;
+ }
+ return { calls, nodes };
+ })
+ .catch(() => {
+ context.commit(types.SET_ENDPOINT_DEPENDENCY, { calls: [], nodes: [] });
+ });
+ });
+ },
async GET_TOPO_INSTANCE_DEPENDENCY(
context: { commit: Commit; state: State },
params: {
clientServiceId: string;
serverServiceId: string;
- duration: string;
+ duration: Duration;
},
) {
graph
@@ -511,7 +687,7 @@
},
INSTANCE_RELATION_INFO(
context: { commit: Commit; state: State },
- params: Call & { mode: string; queryType: string; durationTime: string },
+ params: Call & { mode: string; queryType: string; durationTime: Duration },
) {
graph
.query(params.queryType)
@@ -528,6 +704,17 @@
context.commit(types.SET_INSTANCE_DEPEDENCE_METRICS, res.data.data);
});
},
+ GET_ENDPOINT_DEPENDENCY_METRICS(context: { commit: Commit; state: State }, params: EndpointDependencyConidition) {
+ return graph
+ .query('queryTopoEndpointDependencyMetrics')
+ .params(params)
+ .then((res: AxiosResponse) => {
+ if (!(res.data && res.data.data)) {
+ return;
+ }
+ context.commit(types.SET_ENDPOINT_DEPENDENCY_METRICS, res.data.data);
+ });
+ },
};
export default {
diff --git a/src/store/mutation-types.ts b/src/store/mutation-types.ts
index 0d7d7b0..dc05d2d 100644
--- a/src/store/mutation-types.ts
+++ b/src/store/mutation-types.ts
@@ -79,7 +79,6 @@
export const SET_HONEYCOMB_NODE = 'SET_HONEYCOMB_NODE';
export const SET_SHOW_DIALOG = 'SET_SHOW_DIALOG';
export const SET_INSTANCE_DEPENDENCY = 'SET_INSTANCE_DEPENDENCY';
-export const SET_TOPO_COPY = 'SET_TOPO_COPY';
export const SET_LINK = 'SET_LINK';
export const SET_SERVICE_DETAIL = 'SET_SERVICE_DETAIL';
export const SET_SERVICE_TOPOLOGY = 'GET_SERVICE_TOPOLOGY';
@@ -95,6 +94,9 @@
export const DELETE_TOPO_ENDPOINT = 'DELETE_TOPO_ENDPOINT';
export const ADD_TOPO_INSTANCE_COMP = 'ADD_TOPO_INSTANCE_COMP';
export const ADD_TOPO_ENDPOINT_COMP = 'ADD_TOPO_ENDPOINT_COMP';
+export const SET_ENDPOINT_DEPENDENCY = 'SET_ENDPOINT_DEPENDENCY';
+export const SET_ENDPOINT_DEPENDENCY_METRICS = 'SET_ENDPOINT_DEPENDENCY_METRICS';
+export const SET_ENDPOINT_DEPTH = 'SET_ENDPOINT_DEPTH';
// profile
export const SET_TASK_OPTIONS = 'SET_TASK_OPTIONS';
diff --git a/src/views/components/dashboard/charts/chart-line.vue b/src/views/components/dashboard/charts/chart-line.vue
index 3527efe..407dac7 100644
--- a/src/views/components/dashboard/charts/chart-line.vue
+++ b/src/views/components/dashboard/charts/chart-line.vue
@@ -43,7 +43,9 @@
},
};
if (this.type === 'areaChart') {
- serie.areaStyle = {};
+ serie.areaStyle = {
+ opacity: 0.4,
+ };
}
return serie;
});
diff --git a/src/views/components/topology/chart-line.vue b/src/views/components/topology/chart-line.vue
index 7a9bf61..eb9cd80 100644
--- a/src/views/components/topology/chart-line.vue
+++ b/src/views/components/topology/chart-line.vue
@@ -16,7 +16,7 @@
<template>
<div>
<div class="grey sm mb-5">{{ title }}</div>
- <RkEcharts height="90px" :option="option" />
+ <RkEcharts height="120px" :option="option" />
</div>
</template>
<script lang="ts">
diff --git a/src/views/components/topology/chart/topo.vue b/src/views/components/topology/chart/topo.vue
index a2a0762..6fc604b 100644
--- a/src/views/components/topology/chart/topo.vue
+++ b/src/views/components/topology/chart/topo.vue
@@ -69,7 +69,7 @@
{icon: 'INSTANCE', click: this.handleGoInstance},
{icon: 'TRACE', click: this.handleGoTrace},
{icon: 'ALARM', click: this.handleGoAlarm},
- {icon: ''},
+ {icon: 'ENDPOINT', click: this.handleGoEndpointDependency},
{icon: ''},
]);
this.svg.on('click', (d, i) => {
@@ -106,6 +106,10 @@
});
this.$emit('setDialog', 'endpoint');
},
+ // endpoint dependency hexagon
+ handleGoEndpointDependency() {
+ this.$emit('setDialog', 'endpoint_dependency');
+ },
handleNodeClick(d) {
this.$emit('setCurrent', { key: d.id, label: d.name });
const {x, y, vx, vy, fx, fy, index, ...rest} = d;
diff --git a/src/views/components/topology/chart/utils/tool/ENDPOINT.png b/src/views/components/topology/chart/utils/tool/ENDPOINT.png
new file mode 100644
index 0000000..de26135
--- /dev/null
+++ b/src/views/components/topology/chart/utils/tool/ENDPOINT.png
Binary files differ
diff --git a/src/views/components/topology/dependency-sankey.vue b/src/views/components/topology/dependency-sankey.vue
index 22a88ab..406242f 100644
--- a/src/views/components/topology/dependency-sankey.vue
+++ b/src/views/components/topology/dependency-sankey.vue
@@ -63,11 +63,7 @@
private clickLinks(params: any) {
if (params.dataType === 'edge' && params.data) {
- this.GET_INSTANCE_DEPENDENCY_METRICS({
- ...params.data,
- durationTime: this.durationTime,
- mode: params.data.detectPoints[0],
- });
+ this.$emit('showMetrics', params.data);
}
}
}
diff --git a/src/views/components/topology/topo-chart.vue b/src/views/components/topology/topo-chart.vue
index 9b18346..8e017f8 100644
--- a/src/views/components/topology/topo-chart.vue
+++ b/src/views/components/topology/topo-chart.vue
@@ -15,7 +15,7 @@
<template>
<div>
<div class="grey sm mb-5">{{ title }}</div>
- <h5 class="mt-0 mb-0">{{ content.toFixed(2) }} {{ unit }}</h5>
+ <h5 class="grey mt-0 mb-0">{{ content.toFixed(2) }} {{ unit }}</h5>
<RkEcharts height="100px" :option="responseConfig" />
</div>
</template>
@@ -78,8 +78,9 @@
},
series: [
{
- data: this.data.map((i: any, index: number) => [this.intervalTime[index], i]),
- type: this.precent ? 'bar' : 'line',
+ data: this.data.map((i: any, index: number) => [this.intervalTime[index], this.precent ? i / 100 : i]),
+ // type: this.precent ? 'bar' : 'line',
+ type: 'line',
symbol: 'none',
barMaxWidth: 5,
lineStyle: {
diff --git a/src/views/components/topology/topo-endpoint-dependency.vue b/src/views/components/topology/topo-endpoint-dependency.vue
new file mode 100644
index 0000000..3bcb97d
--- /dev/null
+++ b/src/views/components/topology/topo-endpoint-dependency.vue
@@ -0,0 +1,126 @@
+<!-- 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. -->
+<template>
+ <div class="rk-endpoint-dependency">
+ <div class="endpoint-dependency-chart">
+ <DependencySankey
+ v-if="stateTopo.endpointDependency.nodes.length"
+ :data="stateTopo.endpointDependency"
+ @showMetrics="showEndpointMetrics"
+ />
+ <div v-else class="endpoint-dependency-empty">
+ No Endpoint Dependency
+ </div>
+ </div>
+ <div class="endpoint-dependency-metrics">
+ <div v-if="respTime.length">
+ <TopoChart :data="respTime" :intervalTime="intervalTime" :title="$t('avgResponseTime')" unit="ms" />
+ </div>
+ <div v-if="cpm.length">
+ <TopoChart :data="cpm" :intervalTime="intervalTime" :title="$t('avgThroughput')" unit="cpm" />
+ </div>
+ <div v-if="sla.length">
+ <TopoChart :data="sla" :intervalTime="intervalTime" :precent="true" :title="$t('avgSLA')" unit="%" />
+ </div>
+ <div v-if="percentile.p50">
+ <ChartLine :data="percentile" :intervalTime="intervalTime" :title="$t('percentResponse')" />
+ </div>
+ </div>
+ </div>
+</template>
+<script lang="ts">
+ import { Vue, Component, Prop, Watch } from 'vue-property-decorator';
+ import { State, Action, Getter, Mutation } from 'vuex-class';
+ import { State as topoState, EndpointDependencyConidition, Call, Duration } from '@/store/modules/topology';
+ import TopoChart from './topo-chart.vue';
+ import DependencySankey from './dependency-sankey.vue';
+ import ChartLine from './chart-line.vue';
+
+ @Component({
+ components: {
+ ChartLine,
+ TopoChart,
+ DependencySankey,
+ },
+ })
+ export default class TopoEndpointDependency extends Vue {
+ @Getter('durationTime') private durationTime!: Duration;
+ @Getter('intervalTime') private intervalTime: any;
+ @State('rocketTopo') private stateTopo!: topoState;
+ @Action('rocketTopo/GET_ENDPOINT_DEPENDENCY_METRICS') private GET_ENDPOINT_DEPENDENCY_METRICS: any;
+
+ private respTime: number[] = [];
+ private cpm: number[] = [];
+ private sla: number[] = [];
+ private percentile: { [key: string]: number[] } = {};
+
+ private showEndpointMetrics(data: EndpointDependencyConidition & Call) {
+ this.GET_ENDPOINT_DEPENDENCY_METRICS({
+ serviceName: data.serviceName,
+ endpointName: data.endpointName,
+ destServiceName: data.destServiceName,
+ destEndpointName: data.destEndpointName,
+ duration: this.durationTime,
+ }).then(() => {
+ this.updateMetrics();
+ });
+ }
+
+ @Watch('stateTopo.endpointDependency.nodes')
+ private updateMetrics() {
+ this.respTime = this.stateTopo.endpointDependencyMetrics.respTime;
+ this.cpm = this.stateTopo.endpointDependencyMetrics.cpm;
+ this.sla = this.stateTopo.endpointDependencyMetrics.sla;
+ this.percentile = this.stateTopo.endpointDependencyMetrics.percentile;
+ }
+
+ private beforeDestroy() {
+ this.stateTopo.endpointDependency = {
+ calls: [],
+ nodes: [],
+ };
+ }
+ }
+</script>
+<style lang="scss">
+ .rk-endpoint-dependency {
+ background: #333840;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ overflow: scroll;
+ .endpoint-dependency-chart {
+ height: 80%;
+ min-height: 500px;
+ }
+ .endpoint-dependency-metrics {
+ height: 20%;
+ min-height: 100px;
+ display: flex;
+ flex-direction: row;
+ padding-left: 10px;
+ > div {
+ width: 25%;
+ height: 100%;
+ }
+ }
+ .endpoint-dependency-empty {
+ color: #fff;
+ text-align: center;
+ height: 500px;
+ line-height: 500px;
+ }
+ }
+</style>
diff --git a/src/views/components/topology/topo-group/index.vue b/src/views/components/topology/topo-group/index.vue
index 2f4fb23..6ace529 100644
--- a/src/views/components/topology/topo-group/index.vue
+++ b/src/views/components/topology/topo-group/index.vue
@@ -1,24 +1,15 @@
-/**
- * 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.
- */
+/** * 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. */
<template>
<div class="topo-group">
<div class="topo-group-wrapper">
- <div v-for="i in pagedData">
+ <div v-for="i in pagedData" :key="i.id">
<GroupItem
:servicesMap="servicesMap"
:active="rocketTopoGroup.groupId === i.id"
@@ -62,7 +53,6 @@
@Mutation('rocketTopoGroup/DELETE_GROUP') private DELETE_GROUP: any;
@Mutation('rocketTopoGroup/SELECT_GROUP') private SELECT_GROUP: any;
@Mutation('SET_EVENTS') private SET_EVENTS: any;
- @Action('rocketTopo/FILTER_TOPO') private FILTER_TOPO: any;
@Action('rocketTopo/GET_TOPO') private GET_TOPO: any;
private servicesMap = [];
private currentPage = 1;
diff --git a/src/views/components/topology/topo-instance-dependency.vue b/src/views/components/topology/topo-instance-dependency.vue
index ef706f4..b4858b7 100644
--- a/src/views/components/topology/topo-instance-dependency.vue
+++ b/src/views/components/topology/topo-instance-dependency.vue
@@ -15,7 +15,7 @@
<template>
<div class="rk-topo-instance-dependency">
<div class="rk-dependency-chart">
- <DependencySankey :data="stateTopo.instanceDependency" />
+ <DependencySankey :data="stateTopo.instanceDependency" @showMetrics="showDependencyMetrics" />
</div>
<div class="rk-instance-metric-box">
<div v-if="!stateTopo.instanceDependency.nodes.length">
@@ -108,6 +108,13 @@
mode,
});
}
+ private showDependencyMetrics(data: any) {
+ this.GET_INSTANCE_DEPENDENCY_METRICS({
+ ...data,
+ durationTime: this.durationTime,
+ mode: data.detectPoints[0],
+ });
+ }
}
</script>
<style lang="scss">
diff --git a/src/views/components/topology/topo-services.vue b/src/views/components/topology/topo-services.vue
index 0e31b7e..c9b0f2c 100644
--- a/src/views/components/topology/topo-services.vue
+++ b/src/views/components/topology/topo-services.vue
@@ -20,7 +20,6 @@
<script lang="ts">
import { DurationTime } from '@/types/global';
import compareObj from '@/utils/comparison';
- import Axios, { AxiosResponse } from 'axios';
import { Component, Vue, Watch } from 'vue-property-decorator';
import { Action, Getter, Mutation } from 'vuex-class';
import TopoSelect from './topo-select.vue';
@@ -29,26 +28,14 @@
export default class TopoServices extends Vue {
@Getter('durationTime') public durationTime: any;
@Action('rocketTopo/GET_TOPO') public GET_TOPO: any;
+ @Action('rocketTopo/GET_SERVICES') private GET_SERVICES: any;
@Mutation('rocketTopoGroup/UNSELECT_GROUP') private UNSELECT_GROUP: any;
private services = [{ key: 0, label: 'All services' }];
private service = { key: 0, label: 'All services' };
private fetchData() {
- Axios.post('/graphql', {
- query: `
- query queryServices($duration: Duration!) {
- services: getAllServices(duration: $duration) {
- key: id
- label: name
- }
- }`,
- variables: {
- duration: this.durationTime,
- },
- }).then((res: AxiosResponse) => {
- this.services = res.data.data.services
- ? [{ key: 0, label: 'All services' }, ...res.data.data.services]
- : [{ key: 0, label: 'All services' }];
+ this.GET_SERVICES({ duration: this.durationTime }).then((json: any[]) => {
+ this.services = [...this.services, ...json];
});
}
diff --git a/src/views/containers/topology/endpoint-dependency/index.vue b/src/views/containers/topology/endpoint-dependency/index.vue
new file mode 100644
index 0000000..51a5a43
--- /dev/null
+++ b/src/views/containers/topology/endpoint-dependency/index.vue
@@ -0,0 +1,109 @@
+<!-- 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. -->
+<template>
+ <div class="endpoint-dependency-page">
+ <div class="rk-dashboard-bar flex-h">
+ <ToolBarSelect :selectable="false" :title="this.$t('currentService')" :current="current" icon="package" />
+ <ToolBarEndpointSelect
+ @onChoose="selectEndpoint"
+ :title="$t('currentEndpoint')"
+ :current="stateDashboardOption.currentEndpoint"
+ :data="stateDashboardOption.endpoints"
+ icon="code"
+ />
+ <ToolBarEndpointSelect
+ @onChoose="selectDepth"
+ :title="$t('currentDepth')"
+ :current="rocketTopo.currentEndpointDepth"
+ :data="depths"
+ icon="code"
+ />
+ </div>
+ <TopoEndpointDependency />
+ </div>
+</template>
+
+<script lang="ts">
+ import Vue from 'vue';
+ import { Component, Watch, Prop } from 'vue-property-decorator';
+ import { Action, Getter, State, Mutation } from 'vuex-class';
+ import ToolBarSelect from '@/views/components/dashboard/tool-bar-select.vue';
+ import ToolBarEndpointSelect from '@/views/components/dashboard/tool-bar-endpoint-select.vue';
+ import TopoEndpointDependency from '@/views/components/topology/topo-endpoint-dependency.vue';
+
+ @Component({
+ components: {
+ ToolBarSelect,
+ ToolBarEndpointSelect,
+ TopoEndpointDependency,
+ },
+ })
+ export default class WindowEndpointDependency extends Vue {
+ @State('rocketOption') private stateDashboardOption!: any;
+ @State('rocketTopo') private rocketTopo!: any;
+ @Getter('durationTime') private durationTime: any;
+ @Mutation('SET_CURRENT_SERVICE') private SET_CURRENT_SERVICE: any;
+ @Mutation('SET_EDIT') private SET_EDIT: any;
+ @Mutation('rocketTopo/SET_ENDPOINT_DEPENDENCY_METRICS') private SET_ENDPOINT_DEPENDENCY_METRICS: any;
+ @Mutation('rocketTopo/SET_ENDPOINT_DEPTH') private SET_ENDPOINT_DEPTH: any;
+ @Action('GET_SERVICE_ENDPOINTS') private GET_SERVICE_ENDPOINTS: any;
+ @Action('MIXHANDLE_CHANGE_GROUP_WITH_CURRENT') private MIXHANDLE_CHANGE_GROUP_WITH_CURRENT: any;
+ @Action('SELECT_ENDPOINT') private SELECT_ENDPOINT: any;
+ @Action('rocketTopo/GET_ALL_ENDPOINT_DEPENDENCY') private GET_ALL_ENDPOINT_DEPENDENCY: any;
+ @Prop() private current!: { key: number | string; label: string };
+
+ private depths: Array<{ key: number; label: string }> = [{ key: 2, label: '2' }];
+
+ private beforeMount() {
+ this.SET_CURRENT_SERVICE(this.current);
+ this.MIXHANDLE_CHANGE_GROUP_WITH_CURRENT({ index: 0, current: 2 });
+ this.GET_SERVICE_ENDPOINTS({ duration: this.durationTime, serviceId: this.current.key, keyword: '' }).then(() => {
+ this.selectEndpoint(this.stateDashboardOption.endpoints[0]);
+ });
+ this.depths = [1, 2, 3, 4, 5].map((item: number) => ({ key: item, label: String(item) }));
+ }
+
+ private selectEndpoint(i: any) {
+ this.SELECT_ENDPOINT({ endpoint: i, duration: this.durationTime });
+ this.GET_ALL_ENDPOINT_DEPENDENCY({ endpointIds: [i.key], duration: this.durationTime });
+ this.SET_ENDPOINT_DEPENDENCY_METRICS({ respTime: [], sla: [], cpm: [], percentile: {} });
+ }
+
+ private selectDepth(i: { key: number; label: string }) {
+ this.SET_ENDPOINT_DEPTH(i);
+ this.GET_ALL_ENDPOINT_DEPENDENCY({
+ endpointIds: [this.stateDashboardOption.currentEndpoint.key],
+ duration: this.durationTime,
+ });
+
+ this.SET_ENDPOINT_DEPENDENCY_METRICS({ respTime: [], sla: [], cpm: [], percentile: {} });
+ }
+
+ private beforeDestroy() {
+ this.SET_EDIT(false);
+ this.SET_ENDPOINT_DEPTH({ key: 2, label: '2' });
+ this.$emit('changeEndpointComps', { type: '' });
+ }
+ }
+</script>
+
+<style lang="scss">
+ .endpoint-dependency-page {
+ height: calc(100% - 48px);
+ .rk-dashboard-bar {
+ border-bottom: 1px solid #252a2f;
+ }
+ }
+</style>
diff --git a/src/views/containers/topology/topology.vue b/src/views/containers/topology/topology.vue
index 0271a66..1da0430 100644
--- a/src/views/containers/topology/topology.vue
+++ b/src/views/containers/topology/topology.vue
@@ -24,7 +24,7 @@
/>
<TopoAside />
<TopoGroup />
- <rk-sidebox :show="dialog.length" @update:show="dialog = ''" :fixed="true" width="80%">
+ <rk-sidebox :show="dialog.length" @update:show="dialog = ''" :fixed="true" width="100%">
<window-endpoint
v-if="dialog === 'endpoint'"
:current="this.current"
@@ -41,6 +41,11 @@
/>
<window-trace v-if="dialog === 'trace'" :current="this.current" />
<window-alarm v-if="dialog === 'alarm'" :current="this.current" />
+ <window-endpoint-dependency
+ v-if="dialog === 'endpoint_dependency'"
+ @changeEndpointComps="changeEndpointComps"
+ :current="this.current"
+ />
</rk-sidebox>
</div>
</template>
@@ -57,6 +62,7 @@
import Topo from '../../components/topology/chart/topo.vue';
import TopoAside from '../../components/topology/topo-aside.vue';
import TopoGroup from '../../components/topology/topo-group/index.vue';
+ import WindowEndpointDependency from '@/views/containers/topology/endpoint-dependency/index.vue';
@Component({
components: {
@@ -67,6 +73,7 @@
WindowInstance,
WindowTrace,
WindowAlarm,
+ WindowEndpointDependency,
},
})
export default class Topology extends Vue {