This is an automated email from the ASF dual-hosted git repository. iluo pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/dubbo-admin.git
The following commit(s) were added to refs/heads/develop by this push: new 368d9dd Feature#23 (#437) 368d9dd is described below commit 368d9dd17ea61b759fabb0e0c0f7bd5f08c092d7 Author: 孙不服 <sunyoub...@163.com> AuthorDate: Thu Sep 19 16:27:29 2019 +0800 Feature#23 (#437) * ace 行号栏 z-index 过高 #358 * Add a graph to display the call relationship of all applications #23 * code style --- .../admin/controller/MetricsCollectController.java | 12 +- .../apache/dubbo/admin/model/dto/RelationDTO.java | 216 +++++++++++++++++++++ .../apache/dubbo/admin/service/MetricsService.java | 24 +++ .../admin/service/impl/MetricsServiceImpl.java | 119 ++++++++++++ dubbo-admin-ui/src/api/menu.js | 12 +- .../src/components/metrics/ServiceRelation.vue | 118 +++++++++++ dubbo-admin-ui/src/lang/en.js | 3 + dubbo-admin-ui/src/lang/zh.js | 3 + dubbo-admin-ui/src/router/index.js | 8 +- 9 files changed, 510 insertions(+), 5 deletions(-) diff --git a/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/controller/MetricsCollectController.java b/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/controller/MetricsCollectController.java index 9dfc43c..9c061a2 100644 --- a/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/controller/MetricsCollectController.java +++ b/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/controller/MetricsCollectController.java @@ -24,7 +24,9 @@ import org.apache.dubbo.admin.common.util.Tool; import org.apache.dubbo.admin.model.domain.Consumer; import org.apache.dubbo.admin.model.domain.Provider; import org.apache.dubbo.admin.model.dto.MetricDTO; +import org.apache.dubbo.admin.model.dto.RelationDTO; import org.apache.dubbo.admin.service.ConsumerService; +import org.apache.dubbo.admin.service.MetricsService; import org.apache.dubbo.admin.service.ProviderService; import org.apache.dubbo.admin.service.impl.MetrcisCollectServiceImpl; import org.apache.dubbo.metadata.definition.model.FullServiceDefinition; @@ -41,8 +43,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; - - @RestController @RequestMapping("/api/{env}/metrics") public class MetricsCollectController { @@ -53,6 +53,9 @@ public class MetricsCollectController { @Autowired private ConsumerService consumerService; + @Autowired + private MetricsService metricsService; + @RequestMapping(method = RequestMethod.POST) public String metricsCollect(@RequestParam String group, @PathVariable String env) { MetrcisCollectServiceImpl service = new MetrcisCollectServiceImpl(); @@ -61,6 +64,11 @@ public class MetricsCollectController { return service.invoke(group).toString(); } + @RequestMapping(value = "/relation", method = RequestMethod.GET) + public RelationDTO getApplicationRelation(){ + return metricsService.getApplicationRelation(); + } + private String getOnePortMessage(String group, String ip, String port, String protocol) { MetrcisCollectServiceImpl metrcisCollectService = new MetrcisCollectServiceImpl(); metrcisCollectService.setUrl(protocol + "://" + ip + ":" + port +"?scope=remote&cache=true"); diff --git a/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/model/dto/RelationDTO.java b/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/model/dto/RelationDTO.java new file mode 100644 index 0000000..90edcf1 --- /dev/null +++ b/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/model/dto/RelationDTO.java @@ -0,0 +1,216 @@ +/* + * 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. + */ +package org.apache.dubbo.admin.model.dto; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +/** + * relation about node for relation graph + */ +public class RelationDTO { + + private List<Categories> categories; + private List<Node> nodes; + private List<Link> links; + + public static final Categories CONSUMER_CATEGORIES = new RelationDTO.Categories(0, "consumer", "consumer");; + public static final Categories PROVIDER_CATEGORIES = new RelationDTO.Categories(1, "provider", "provider"); + public static final Categories CONSUMER_AND_PROVIDER_CATEGORIES = new RelationDTO.Categories(2, "consumer and provider", "consumer and provider"); + + public static final List<RelationDTO.Categories> CATEGORIES_LIST = Arrays.asList(CONSUMER_CATEGORIES, PROVIDER_CATEGORIES, CONSUMER_AND_PROVIDER_CATEGORIES); + + public RelationDTO() { + } + + public RelationDTO(List<Node> nodes, List<Link> links) { + this.categories = CATEGORIES_LIST; + this.nodes = nodes; + this.links = links; + } + + public static class Categories { + private Integer index; + private String name; + private String base; + + public Categories() { + } + + public Categories(Integer index, String name, String base) { + this.index = index; + this.name = name; + this.base = base; + } + + public Integer getIndex() { + return index; + } + + public void setIndex(Integer index) { + this.index = index; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getBase() { + return base; + } + + public void setBase(String base) { + this.base = base; + } + } + + public static class Node { + + private Integer index; + private String name; + private int category; + + public Node() { + } + + public Node(Integer index, String name, int category) { + this.index = index; + this.name = name; + this.category = category; + } + + public Integer getIndex() { + return index; + } + + public void setIndex(Integer index) { + this.index = index; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getCategory() { + return category; + } + + public void setCategory(int category) { + this.category = category; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Node node = (Node) o; + return category == node.category && + index.equals(node.index) && + name.equals(node.name); + } + + @Override + public int hashCode() { + return Objects.hash(index, name, category); + } + } + + public static class Link { + + private int source; + private int target; + + public Link() { + } + + public Link(int source, int target) { + this.source = source; + this.target = target; + } + + public int getSource() { + return source; + } + + public void setSource(int source) { + this.source = source; + } + + public int getTarget() { + return target; + } + + public void setTarget(int target) { + this.target = target; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Link link = (Link) o; + return source == link.source && + target == link.target; + } + + @Override + public int hashCode() { + return Objects.hash(source, target); + } + + @Override + public String toString() { + return "Link{" + + "source=" + source + + ", target=" + target + + '}'; + } + } + + public List<Categories> getCategories() { + return categories; + } + + public void setCategories(List<Categories> categories) { + this.categories = categories; + } + + public List<Node> getNodes() { + return nodes; + } + + public void setNodes(List<Node> nodes) { + this.nodes = nodes; + } + + public List<Link> getLinks() { + return links; + } + + public void setLinks(List<Link> links) { + this.links = links; + } +} diff --git a/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/service/MetricsService.java b/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/service/MetricsService.java new file mode 100644 index 0000000..6da25e0 --- /dev/null +++ b/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/service/MetricsService.java @@ -0,0 +1,24 @@ +/* + * 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. + */ +package org.apache.dubbo.admin.service; + +import org.apache.dubbo.admin.model.dto.RelationDTO; + +public interface MetricsService { + + RelationDTO getApplicationRelation(); +} diff --git a/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/service/impl/MetricsServiceImpl.java b/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/service/impl/MetricsServiceImpl.java new file mode 100644 index 0000000..dee5f9f --- /dev/null +++ b/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/service/impl/MetricsServiceImpl.java @@ -0,0 +1,119 @@ +/* + * 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. + */ +package org.apache.dubbo.admin.service.impl; + +import org.apache.dubbo.admin.model.domain.Consumer; +import org.apache.dubbo.admin.model.domain.Provider; +import org.apache.dubbo.admin.model.dto.RelationDTO; +import org.apache.dubbo.admin.service.ConsumerService; +import org.apache.dubbo.admin.service.MetricsService; +import org.apache.dubbo.admin.service.ProviderService; + +import org.apache.dubbo.common.utils.CollectionUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@Component +public class MetricsServiceImpl implements MetricsService { + + @Autowired + private ConsumerService consumerService; + @Autowired + private ProviderService providerService; + + @Override + public RelationDTO getApplicationRelation() { + + List<Consumer> consumerList = consumerService.findAll(); + List<Provider> providerList = providerService.findAll(); + + int index = 0; + // collect all service + Set<String> serviceSet = new HashSet<>(); + + // collect consumer's nodes map <application, node> + Map<String, RelationDTO.Node> consumerNodeMap = new HashMap<>(); + // collect consumer's service and applications map <service, set<application>> + Map<String, Set<String>> consumerServiceApplicationMap = new HashMap<>(); + for (Consumer consumer : consumerList) { + String application = consumer.getApplication(); + if (!consumerNodeMap.keySet().contains(application)) { + RelationDTO.Node node = new RelationDTO.Node(index, application, RelationDTO.CONSUMER_CATEGORIES.getIndex()); + consumerNodeMap.put(application, node); + index++; + } + String service = consumer.getService(); + serviceSet.add(service); + consumerServiceApplicationMap.computeIfAbsent(service, s -> new HashSet<>()); + consumerServiceApplicationMap.get(service).add(application); + } + // collect provider's nodes + Map<String, RelationDTO.Node> providerNodeMap = new HashMap<>(); + // collect provider's service and applications map <service, set<application>> + Map<String, Set<String>> providerServiceApplicationMap = new HashMap<>(); + for (Provider provider : providerList) { + String application = provider.getApplication(); + if (!providerNodeMap.keySet().contains(application)) { + RelationDTO.Node node = new RelationDTO.Node(index, application, RelationDTO.PROVIDER_CATEGORIES.getIndex()); + providerNodeMap.put(application, node); + index++; + } + String service = provider.getService(); + serviceSet.add(service); + providerServiceApplicationMap.computeIfAbsent(service, s -> new HashSet<>()); + providerServiceApplicationMap.get(service).add(application); + } + // merge provider's nodes and consumer's nodes + Map<String, RelationDTO.Node> nodeMap = new HashMap<>(consumerNodeMap); + for (Map.Entry<String, RelationDTO.Node> entry : providerNodeMap.entrySet()) { + if (nodeMap.get(entry.getKey()) != null) { + nodeMap.get(entry.getKey()).setCategory(RelationDTO.CONSUMER_AND_PROVIDER_CATEGORIES.getIndex()); + } else { + nodeMap.put(entry.getKey(), entry.getValue()); + } + } + // build link by same service + Set<RelationDTO.Link> linkSet = new HashSet<>(); + for (String service : serviceSet) { + Set<String> consumerApplicationSet = consumerServiceApplicationMap.get(service); + Set<String> providerApplicationSet = providerServiceApplicationMap.get(service); + if (CollectionUtils.isNotEmpty(consumerApplicationSet) && CollectionUtils.isNotEmpty(providerApplicationSet)) { + for (String providerApplication : providerApplicationSet) { + for (String consumerApplication : consumerApplicationSet) { + if (nodeMap.get(consumerApplication) != null && nodeMap.get(providerApplication) != null) { + Integer consumerIndex = nodeMap.get(consumerApplication).getIndex(); + Integer providerIndex = nodeMap.get(providerApplication).getIndex(); + linkSet.add(new RelationDTO.Link(consumerIndex, providerIndex)); + } + } + } + } + } + // sort node by index + List<RelationDTO.Node> nodeList = nodeMap.values().stream().sorted(Comparator.comparingInt(RelationDTO.Node::getIndex)).collect(Collectors.toList()); + return new RelationDTO(nodeList, new ArrayList<>(linkSet)); + } +} diff --git a/dubbo-admin-ui/src/api/menu.js b/dubbo-admin-ui/src/api/menu.js index fba2a05..3403f64 100644 --- a/dubbo-admin-ui/src/api/menu.js +++ b/dubbo-admin-ui/src/api/menu.js @@ -23,7 +23,7 @@ const Menu = [ group: 'governance', items: [ { title: 'routingRule', path: '/governance/routingRule' }, - {title: 'tagRule', path: '/governance/tagRule', badge: 'new'}, + { title: 'tagRule', path: '/governance/tagRule', badge: 'new' }, { title: 'accessControl', path: '/governance/access' }, { title: 'dynamicConfig', path: '/governance/config' }, { title: 'weightAdjust', path: '/governance/weight' }, @@ -32,7 +32,15 @@ const Menu = [ }, { title: 'serviceTest', path: '/test', icon: 'code' }, { title: 'serviceMock', path: '/mock', icon: 'build', badge: 'feature' }, - { title: 'metrics', path: '/metrics', icon: 'show_chart', badge: 'feature' }, + { + title: 'serviceMetrics', + path: 'metrics', + icon: 'show_chart', + items: [ + { title: 'serviceMetrics', path: '/metrics/index', badge: 'feature' }, + { title: 'serviceRelation', path: '/metrics/relation', badge: 'new' } + ] + }, { title: 'configManage', path: '/management', icon: 'build' } ] diff --git a/dubbo-admin-ui/src/components/metrics/ServiceRelation.vue b/dubbo-admin-ui/src/components/metrics/ServiceRelation.vue new file mode 100644 index 0000000..55dee6c --- /dev/null +++ b/dubbo-admin-ui/src/components/metrics/ServiceRelation.vue @@ -0,0 +1,118 @@ +<!-- + - 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> + <v-container grid-list-xl fluid > + <v-layout row wrap> + <v-flex lg12> + <breadcrumb title="serviceRelation" :items="breads"></breadcrumb> + </v-flex> + </v-layout> + + <v-flex lg12> + <v-card> + <div id="chartContent" style="width:100%;height:500%;"/> + </v-card> + </v-flex> + + </v-container> + +</template> +<script> + import Breadcrumb from '@/components/public/Breadcrumb' + import axios from 'axios' + export default { + components: { + Breadcrumb, + axios + }, + data: () => ({ + baseURL: '/api/dev', + success: null, + breads: [ + { + text: 'serviceMetrics', + href: '' + }, + { + text: 'serviceRelation', + href: '' + } + ], + responseData: null + }), + methods: { + initData: function () { + // eslint-disable-next-line no-undef + this.chartContent = echarts.init(document.getElementById('chartContent')) + this.chartContent.showLoading() + axios.get(this.baseURL + '/metrics/relation') + .then(response => { + if (response && response.status === 200) { + this.success = true + this.responseData = response.data + this.responseData.type = 'force' + this.initChart(this.responseData) + } + }) + .catch(error => { + this.success = false + this.responseData = error.response.data + }) + }, + initChart: function (data) { + this.chartContent.hideLoading() + + const option = { + legend: { + top: 'bottom', + data: data.categories.map(i => i.name) + }, + series: [{ + type: 'graph', + layout: 'force', + animation: false, + label: { + normal: { + show: true, + position: 'right' + } + }, + draggable: true, + data: data.nodes.map(function (node, idx) { + node.id = idx + return node + }), + categories: this.responseData.categories, + force: { + edgeLength: 100, + repulsion: 10 + }, + edges: data.links, + edgeSymbol: ['', 'arrow'], + edgeSymbolSize: 7 + }] + } + this.chartContent.setOption(option) + } + }, + mounted: function () { + this.initData() + } + + } +</script> diff --git a/dubbo-admin-ui/src/lang/en.js b/dubbo-admin-ui/src/lang/en.js index adc826b..d8f44ef 100644 --- a/dubbo-admin-ui/src/lang/en.js +++ b/dubbo-admin-ui/src/lang/en.js @@ -26,7 +26,10 @@ export default { loadBalance: 'Load Balance', serviceTest: 'Service Test', serviceMock: 'Service Mock', + serviceMetrics: 'Service Metrics', + serviceRelation: 'Service Relation', metrics: 'Metrics', + relation: 'Relation', group: 'Group', serviceInfo: 'Service Info', providers: 'Providers', diff --git a/dubbo-admin-ui/src/lang/zh.js b/dubbo-admin-ui/src/lang/zh.js index e1abcc5..d13f7b6 100644 --- a/dubbo-admin-ui/src/lang/zh.js +++ b/dubbo-admin-ui/src/lang/zh.js @@ -18,6 +18,8 @@ export default { service: '服务', serviceSearch: '服务查询', serviceGovernance: '服务治理', + serviceMetrics: '服务统计', + serviceRelation: '服务关系', routingRule: '条件路由', tagRule: '标签路由', dynamicConfig: '动态配置', @@ -29,6 +31,7 @@ export default { providers: '提供者', consumers: '消费者', metrics: '统计', + relation: '关系', group: '组', version: '版本', app: '应用', diff --git a/dubbo-admin-ui/src/router/index.js b/dubbo-admin-ui/src/router/index.js index 35f8a90..4b1d318 100644 --- a/dubbo-admin-ui/src/router/index.js +++ b/dubbo-admin-ui/src/router/index.js @@ -29,6 +29,7 @@ import Overrides from '@/components/governance/Overrides' import ServiceTest from '@/components/test/ServiceTest' import ServiceMock from '@/components/test/ServiceMock' import ServiceMetrics from '@/components/metrics/ServiceMetrics' +import ServiceRelation from '@/components/metrics/ServiceRelation' import Management from '@/components/Management' Vue.use(Router) @@ -90,11 +91,16 @@ export default new Router({ component: ServiceMock }, { - path: '/metrics', + path: '/metrics/index', name: 'ServiceMetrics', component: ServiceMetrics }, { + path: '/metrics/relation', + name: 'ServiceRelation', + component: ServiceRelation + }, + { path: '/management', name: 'Management', component: Management