mirror of
https://github.com/apache/rocketmq-dashboard.git
synced 2025-09-10 19:48:29 +08:00
[#ISSUE 205] Support query consumer's stack from dashboard
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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) {
|
||||
|
@@ -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<String, String> stackMap;
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
@@ -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<String, String> stackMap = new HashMap<>();
|
||||
Map<String, List<String>> map = formatThreadStack(consumerRunningInfo.getJstack());
|
||||
if (MapUtils.isNotEmpty(map)) {
|
||||
Set<String> 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<String, List<String>> formatThreadStack(String stack) {
|
||||
Map<String, List<String>> threadStackMap = new HashMap<>();
|
||||
List<String> stackList = Splitter.on("\n\n").splitToList(stack);
|
||||
for (String threadStack : stackList) {
|
||||
List<String> stacks = Splitter.on("\n").splitToList(threadStack);
|
||||
if (CollectionUtils.isNotEmpty(stacks)) {
|
||||
List<String> elements = new ArrayList<>();
|
||||
String threadName = null;
|
||||
for (String s : stacks) {
|
||||
List<String> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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 {
|
||||
@@ -422,3 +422,32 @@ 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});
|
||||
}
|
||||
});
|
||||
};
|
||||
}]
|
||||
);
|
@@ -134,4 +134,5 @@ var en = {
|
||||
"MESSAGE_TYPE_FIFO": "FIFO",
|
||||
"MESSAGE_TYPE_DELAY": "DELAY",
|
||||
"MESSAGE_TYPE_TRANSACTION": "TRANSACTION",
|
||||
"STACK": "STACK"
|
||||
}
|
||||
|
@@ -135,4 +135,5 @@ var zh = {
|
||||
"MESSAGE_TYPE_FIFO": "顺序消息",
|
||||
"MESSAGE_TYPE_DELAY": "定时/延时消息",
|
||||
"MESSAGE_TYPE_TRANSACTION": "事务消息",
|
||||
"STACK": "堆栈"
|
||||
}
|
@@ -160,7 +160,7 @@
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">[{{ngDialogData.consumerGroupName}}]{{'CLIENT'|translate}}</h4>
|
||||
</div>
|
||||
<div class="modal-body ">
|
||||
<div class="modal-body " style="overflow: auto">
|
||||
<form class="form-horizontal" novalidate>
|
||||
<table class="table table-bordered">
|
||||
<tr>
|
||||
@@ -168,12 +168,15 @@
|
||||
<th class="text-center">ClientAddr</th>
|
||||
<th class="text-center">Language</th>
|
||||
<th class="text-center">Version</th>
|
||||
<th class="text-center">Operation</th>
|
||||
</tr>
|
||||
<tr ng-repeat="conn in ngDialogData.data.connectionSet">
|
||||
<td class="text-center">{{conn.clientId}}</td>
|
||||
<td class="text-center">{{conn.clientAddr}}</td>
|
||||
<td class="text-center">{{conn.language}}</td>
|
||||
<td class="text-center">{{conn.versionDesc}}</td>
|
||||
<td class="text-center"><a
|
||||
ng-click="consumerStack(ngDialogData.consumerGroupName, conn.clientId, true)">{{'STACK' | translate}}</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
<p>Below is subscription:</p>
|
||||
@@ -562,3 +565,27 @@
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<!--consumer stack-->
|
||||
<script type="text/ng-template" id="consumerStackDialog">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ngDialogData.clientId}}</h4>
|
||||
</div>
|
||||
<div class="modal-body ">
|
||||
<div ng-repeat="(thread, stackItem) in ngDialogData.consumerStack.stackMap">
|
||||
<p style="white-space: pre-line;">
|
||||
<label style="color: #0da6e3;">Thread: {{thread}}</label>
|
||||
{{stackItem}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="ngdialog-buttons">
|
||||
<button type="button" class="ngdialog-button ngdialog-button-secondary"
|
||||
ng-click="closeThisDialog('Cancel')">{{ 'CLOSE' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
Reference in New Issue
Block a user