From c27e0310e4188f062679ec87f5762e3757ff4963 Mon Sep 17 00:00:00 2001 From: zzl Date: Tue, 11 Jun 2024 11:30:18 +0800 Subject: [PATCH] [#ISSUE 205] Support query consumer's stack from dashboard --- .../controller/ConsumerController.java | 7 ++ .../dashboard/controller/OpsController.java | 1 - .../rocketmq/dashboard/model/StackResult.java | 32 +++++++++ .../dashboard/service/ConsumerService.java | 3 + .../service/impl/ConsumerServiceImpl.java | 68 +++++++++++++++++-- src/main/resources/static/src/consumer.js | 31 ++++++++- src/main/resources/static/src/i18n/en.js | 1 + src/main/resources/static/src/i18n/zh.js | 1 + .../resources/static/view/pages/consumer.html | 29 +++++++- 9 files changed, 166 insertions(+), 7 deletions(-) create mode 100644 src/main/java/org/apache/rocketmq/dashboard/model/StackResult.java diff --git a/src/main/java/org/apache/rocketmq/dashboard/controller/ConsumerController.java b/src/main/java/org/apache/rocketmq/dashboard/controller/ConsumerController.java index 96fc056..5e39fdc 100644 --- a/src/main/java/org/apache/rocketmq/dashboard/controller/ConsumerController.java +++ b/src/main/java/org/apache/rocketmq/dashboard/controller/ConsumerController.java @@ -117,4 +117,11 @@ public class ConsumerController { @RequestParam boolean jstack) { return consumerService.getConsumerRunningInfo(consumerGroup, clientId, jstack); } + + @RequestMapping(value = "/consumerStack.query") + @ResponseBody + public Object getConsumerStack(@RequestParam String consumerGroup, @RequestParam String clientId, + @RequestParam boolean jstack) { + return consumerService.getConsumerStack(consumerGroup, clientId, jstack); + } } diff --git a/src/main/java/org/apache/rocketmq/dashboard/controller/OpsController.java b/src/main/java/org/apache/rocketmq/dashboard/controller/OpsController.java index 6a56447..ed5ff64 100644 --- a/src/main/java/org/apache/rocketmq/dashboard/controller/OpsController.java +++ b/src/main/java/org/apache/rocketmq/dashboard/controller/OpsController.java @@ -56,7 +56,6 @@ public class OpsController { opsService.addNameSvrAddr(newNamesrvAddr); return true; } - @RequestMapping(value = "/updateIsVIPChannel.do", method = RequestMethod.POST) @ResponseBody public Object updateIsVIPChannel(@RequestParam String useVIPChannel) { diff --git a/src/main/java/org/apache/rocketmq/dashboard/model/StackResult.java b/src/main/java/org/apache/rocketmq/dashboard/model/StackResult.java new file mode 100644 index 0000000..9ead835 --- /dev/null +++ b/src/main/java/org/apache/rocketmq/dashboard/model/StackResult.java @@ -0,0 +1,32 @@ +/* + * 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.rocketmq.dashboard.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +import java.util.Map; + +@Builder +@Data +@AllArgsConstructor +public class StackResult { + + private Map stackMap; +} \ No newline at end of file diff --git a/src/main/java/org/apache/rocketmq/dashboard/service/ConsumerService.java b/src/main/java/org/apache/rocketmq/dashboard/service/ConsumerService.java index e284c44..f8d3b74 100644 --- a/src/main/java/org/apache/rocketmq/dashboard/service/ConsumerService.java +++ b/src/main/java/org/apache/rocketmq/dashboard/service/ConsumerService.java @@ -17,6 +17,7 @@ package org.apache.rocketmq.dashboard.service; +import org.apache.rocketmq.dashboard.model.StackResult; import org.apache.rocketmq.remoting.protocol.body.ConsumerConnection; import org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo; import org.apache.rocketmq.dashboard.model.ConsumerGroupRollBackStat; @@ -55,4 +56,6 @@ public interface ConsumerService { ConsumerConnection getConsumerConnection(String consumerGroup, String address); ConsumerRunningInfo getConsumerRunningInfo(String consumerGroup, String clientId, boolean jstack); + + StackResult getConsumerStack(String consumerGroup, String clientId, boolean jstack); } diff --git a/src/main/java/org/apache/rocketmq/dashboard/service/impl/ConsumerServiceImpl.java b/src/main/java/org/apache/rocketmq/dashboard/service/impl/ConsumerServiceImpl.java index 9bc37ab..b30c04b 100644 --- a/src/main/java/org/apache/rocketmq/dashboard/service/impl/ConsumerServiceImpl.java +++ b/src/main/java/org/apache/rocketmq/dashboard/service/impl/ConsumerServiceImpl.java @@ -18,11 +18,14 @@ package org.apache.rocketmq.dashboard.service.impl; import com.google.common.base.Predicate; +import com.google.common.base.Splitter; import com.google.common.base.Throwables; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; +import java.util.ArrayList; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -42,10 +45,16 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; import javax.annotation.Resource; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.common.MixAll; +import org.apache.rocketmq.dashboard.model.ConsumerGroupRollBackStat; +import org.apache.rocketmq.dashboard.model.GroupConsumeInfo; +import org.apache.rocketmq.dashboard.model.QueueStatInfo; +import org.apache.rocketmq.dashboard.model.StackResult; +import org.apache.rocketmq.dashboard.model.TopicConsumerInfo; import org.apache.rocketmq.dashboard.service.client.ProxyAdmin; import org.apache.rocketmq.remoting.protocol.admin.ConsumeStats; import org.apache.rocketmq.remoting.protocol.admin.RollbackStats; @@ -61,10 +70,6 @@ import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.dashboard.config.RMQConfigure; -import org.apache.rocketmq.dashboard.model.ConsumerGroupRollBackStat; -import org.apache.rocketmq.dashboard.model.GroupConsumeInfo; -import org.apache.rocketmq.dashboard.model.QueueStatInfo; -import org.apache.rocketmq.dashboard.model.TopicConsumerInfo; import org.apache.rocketmq.dashboard.model.request.ConsumerConfigInfo; import org.apache.rocketmq.dashboard.model.request.DeleteSubGroupRequest; import org.apache.rocketmq.dashboard.model.request.ResetOffsetRequest; @@ -482,4 +487,59 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum throw new RuntimeException(e); } } + @Override + public StackResult getConsumerStack(String consumerGroup, String clientId, boolean jstack) { + ConsumerRunningInfo consumerRunningInfo = getConsumerRunningInfo(consumerGroup, clientId, jstack); + Map stackMap = new HashMap<>(); + Map> map = formatThreadStack(consumerRunningInfo.getJstack()); + if (MapUtils.isNotEmpty(map)) { + Set threads = map.keySet(); + for (String thread : threads) { + StringBuilder result = new StringBuilder(); + map.get(thread).forEach(s -> result.append(s).append("\n")); + stackMap.put(thread, result.toString()); + } + } + return new StackResult(stackMap); + } + + private Map> formatThreadStack(String stack) { + Map> threadStackMap = new HashMap<>(); + List stackList = Splitter.on("\n\n").splitToList(stack); + for (String threadStack : stackList) { + List stacks = Splitter.on("\n").splitToList(threadStack); + if (CollectionUtils.isNotEmpty(stacks)) { + List elements = new ArrayList<>(); + String threadName = null; + for (String s : stacks) { + List stackItem = Splitter.on(" ") + .omitEmptyStrings() + .trimResults() + .splitToList(s); + if (stackItem.size() == 1) { + String stackStr = stackItem.get(0); + if (threadName == null) { + int index = stackStr.indexOf("TID"); + if (index != -1) { + threadName = stackStr.substring(0, index); + } + } else { + elements.add(stackStr.substring(threadName.length(), stackStr.length())); + } + } + if (stackItem.size() == 2) { + if (threadName == null) { + threadName = stackItem.get(0); + } + elements.add(stackItem.get(stackItem.size() - 1)); + } + } + if (threadName != null) { + threadStackMap.put(threadName, elements); + } + } + } + return threadStackMap; + } + } diff --git a/src/main/resources/static/src/consumer.js b/src/main/resources/static/src/consumer.js index d192334..db76fde 100644 --- a/src/main/resources/static/src/consumer.js +++ b/src/main/resources/static/src/consumer.js @@ -273,7 +273,7 @@ module.controller('consumerController', ['$scope', 'ngDialog', '$http', 'Notific console.log(resp); ngDialog.open({ template: 'clientInfoDialog', - // controller: 'addTopicDialogController', + controller: 'consumerStackDialogController', data: {data: resp.data, consumerGroupName: consumerGroupName} }); } else { @@ -421,4 +421,33 @@ module.controller('consumerTopicViewDialogController', ['$scope', 'ngDialog', '$ }); }; }] +); + +module.controller('consumerStackDialogController', ['$scope', 'ngDialog', '$http', 'Notification', function ($scope, ngDialog, $http, Notification) { + $scope.consumerStack = function (consumerGroup, clientId, jstack) { + $http({ + method: "GET", + url: "consumer/consumerStack.query", + params: { + consumerGroup: consumerGroup + , + clientId: clientId, + jstack: jstack + } + }).success(function (resp) { + if (resp.status == 0) { + console.log(resp); + ngDialog.open({ + template: 'consumerStackDialog', + data: { + consumerStack: resp.data, + clientId: clientId + } + }); + } else { + Notification.error({message: resp.errMsg, delay: 2000}); + } + }); + }; + }] ); \ No newline at end of file diff --git a/src/main/resources/static/src/i18n/en.js b/src/main/resources/static/src/i18n/en.js index 83083d7..87e779b 100644 --- a/src/main/resources/static/src/i18n/en.js +++ b/src/main/resources/static/src/i18n/en.js @@ -134,4 +134,5 @@ var en = { "MESSAGE_TYPE_FIFO": "FIFO", "MESSAGE_TYPE_DELAY": "DELAY", "MESSAGE_TYPE_TRANSACTION": "TRANSACTION", + "STACK": "STACK" } diff --git a/src/main/resources/static/src/i18n/zh.js b/src/main/resources/static/src/i18n/zh.js index f8c3c1d..de813c3 100644 --- a/src/main/resources/static/src/i18n/zh.js +++ b/src/main/resources/static/src/i18n/zh.js @@ -135,4 +135,5 @@ var zh = { "MESSAGE_TYPE_FIFO": "顺序消息", "MESSAGE_TYPE_DELAY": "定时/延时消息", "MESSAGE_TYPE_TRANSACTION": "事务消息", + "STACK": "堆栈" } \ No newline at end of file diff --git a/src/main/resources/static/view/pages/consumer.html b/src/main/resources/static/view/pages/consumer.html index d883afc..82253f1 100644 --- a/src/main/resources/static/view/pages/consumer.html +++ b/src/main/resources/static/view/pages/consumer.html @@ -160,7 +160,7 @@ - + + + + \ No newline at end of file