mirror of
https://github.com/apache/rocketmq-dashboard.git
synced 2025-09-11 03:49:06 +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) {
|
@RequestParam boolean jstack) {
|
||||||
return consumerService.getConsumerRunningInfo(consumerGroup, clientId, 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);
|
opsService.addNameSvrAddr(newNamesrvAddr);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping(value = "/updateIsVIPChannel.do", method = RequestMethod.POST)
|
@RequestMapping(value = "/updateIsVIPChannel.do", method = RequestMethod.POST)
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public Object updateIsVIPChannel(@RequestParam String useVIPChannel) {
|
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;
|
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.ConsumerConnection;
|
||||||
import org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo;
|
import org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo;
|
||||||
import org.apache.rocketmq.dashboard.model.ConsumerGroupRollBackStat;
|
import org.apache.rocketmq.dashboard.model.ConsumerGroupRollBackStat;
|
||||||
@@ -55,4 +56,6 @@ public interface ConsumerService {
|
|||||||
ConsumerConnection getConsumerConnection(String consumerGroup, String address);
|
ConsumerConnection getConsumerConnection(String consumerGroup, String address);
|
||||||
|
|
||||||
ConsumerRunningInfo getConsumerRunningInfo(String consumerGroup, String clientId, boolean jstack);
|
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;
|
package org.apache.rocketmq.dashboard.service.impl;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
|
import com.google.common.base.Splitter;
|
||||||
import com.google.common.base.Throwables;
|
import com.google.common.base.Throwables;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -42,10 +45,16 @@ import java.util.concurrent.atomic.AtomicLong;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import org.apache.commons.collections.CollectionUtils;
|
import org.apache.commons.collections.CollectionUtils;
|
||||||
|
import org.apache.commons.collections.MapUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.rocketmq.client.exception.MQClientException;
|
import org.apache.rocketmq.client.exception.MQClientException;
|
||||||
import org.apache.rocketmq.common.MQVersion;
|
import org.apache.rocketmq.common.MQVersion;
|
||||||
import org.apache.rocketmq.common.MixAll;
|
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.dashboard.service.client.ProxyAdmin;
|
||||||
import org.apache.rocketmq.remoting.protocol.admin.ConsumeStats;
|
import org.apache.rocketmq.remoting.protocol.admin.ConsumeStats;
|
||||||
import org.apache.rocketmq.remoting.protocol.admin.RollbackStats;
|
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.remoting.protocol.subscription.SubscriptionGroupConfig;
|
||||||
import org.apache.rocketmq.common.utils.ThreadUtils;
|
import org.apache.rocketmq.common.utils.ThreadUtils;
|
||||||
import org.apache.rocketmq.dashboard.config.RMQConfigure;
|
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.ConsumerConfigInfo;
|
||||||
import org.apache.rocketmq.dashboard.model.request.DeleteSubGroupRequest;
|
import org.apache.rocketmq.dashboard.model.request.DeleteSubGroupRequest;
|
||||||
import org.apache.rocketmq.dashboard.model.request.ResetOffsetRequest;
|
import org.apache.rocketmq.dashboard.model.request.ResetOffsetRequest;
|
||||||
@@ -482,4 +487,59 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
|
|||||||
throw new RuntimeException(e);
|
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);
|
console.log(resp);
|
||||||
ngDialog.open({
|
ngDialog.open({
|
||||||
template: 'clientInfoDialog',
|
template: 'clientInfoDialog',
|
||||||
// controller: 'addTopicDialogController',
|
controller: 'consumerStackDialogController',
|
||||||
data: {data: resp.data, consumerGroupName: consumerGroupName}
|
data: {data: resp.data, consumerGroupName: consumerGroupName}
|
||||||
});
|
});
|
||||||
} else {
|
} 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_FIFO": "FIFO",
|
||||||
"MESSAGE_TYPE_DELAY": "DELAY",
|
"MESSAGE_TYPE_DELAY": "DELAY",
|
||||||
"MESSAGE_TYPE_TRANSACTION": "TRANSACTION",
|
"MESSAGE_TYPE_TRANSACTION": "TRANSACTION",
|
||||||
|
"STACK": "STACK"
|
||||||
}
|
}
|
||||||
|
@@ -135,4 +135,5 @@ var zh = {
|
|||||||
"MESSAGE_TYPE_FIFO": "顺序消息",
|
"MESSAGE_TYPE_FIFO": "顺序消息",
|
||||||
"MESSAGE_TYPE_DELAY": "定时/延时消息",
|
"MESSAGE_TYPE_DELAY": "定时/延时消息",
|
||||||
"MESSAGE_TYPE_TRANSACTION": "事务消息",
|
"MESSAGE_TYPE_TRANSACTION": "事务消息",
|
||||||
|
"STACK": "堆栈"
|
||||||
}
|
}
|
@@ -160,7 +160,7 @@
|
|||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h4 class="modal-title">[{{ngDialogData.consumerGroupName}}]{{'CLIENT'|translate}}</h4>
|
<h4 class="modal-title">[{{ngDialogData.consumerGroupName}}]{{'CLIENT'|translate}}</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body ">
|
<div class="modal-body " style="overflow: auto">
|
||||||
<form class="form-horizontal" novalidate>
|
<form class="form-horizontal" novalidate>
|
||||||
<table class="table table-bordered">
|
<table class="table table-bordered">
|
||||||
<tr>
|
<tr>
|
||||||
@@ -168,12 +168,15 @@
|
|||||||
<th class="text-center">ClientAddr</th>
|
<th class="text-center">ClientAddr</th>
|
||||||
<th class="text-center">Language</th>
|
<th class="text-center">Language</th>
|
||||||
<th class="text-center">Version</th>
|
<th class="text-center">Version</th>
|
||||||
|
<th class="text-center">Operation</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr ng-repeat="conn in ngDialogData.data.connectionSet">
|
<tr ng-repeat="conn in ngDialogData.data.connectionSet">
|
||||||
<td class="text-center">{{conn.clientId}}</td>
|
<td class="text-center">{{conn.clientId}}</td>
|
||||||
<td class="text-center">{{conn.clientAddr}}</td>
|
<td class="text-center">{{conn.clientAddr}}</td>
|
||||||
<td class="text-center">{{conn.language}}</td>
|
<td class="text-center">{{conn.language}}</td>
|
||||||
<td class="text-center">{{conn.versionDesc}}</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>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<p>Below is subscription:</p>
|
<p>Below is subscription:</p>
|
||||||
@@ -562,3 +565,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</script>
|
</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