From d608bd69b7d79a9c333f924af791020e593033e8 Mon Sep 17 00:00:00 2001 From: StyleTang Date: Fri, 30 Jul 2021 19:52:37 +0800 Subject: [PATCH] [ISSUE #756] [RocketMQ-Console] Improve message trace UI (#757) * Trace UI improvement 1. Change Send Message Info UI 2. Handle edge case: consume trace subBefore or subAfter trace is missing (if subAfter trace is missing, the status will be unknown) 3. Fix timestamp typo 4. Time format for ms 5. Change costTime color from white to black 6. Remove graph and data format button * improve costTime display --- .../console/model/MessageTraceView.java | 5 +- .../model/trace/MessageTraceStatusEnum.java | 32 +++ .../console/model/trace/TraceNode.java | 4 +- .../service/impl/MessageTraceServiceImpl.java | 35 ++- src/main/resources/static/src/messageTrace.js | 89 ++++---- .../static/view/pages/messageTrace.html | 200 +++++++++--------- 6 files changed, 206 insertions(+), 159 deletions(-) create mode 100644 src/main/java/org/apache/rocketmq/console/model/trace/MessageTraceStatusEnum.java diff --git a/src/main/java/org/apache/rocketmq/console/model/MessageTraceView.java b/src/main/java/org/apache/rocketmq/console/model/MessageTraceView.java index e6ac404..a3e4e65 100644 --- a/src/main/java/org/apache/rocketmq/console/model/MessageTraceView.java +++ b/src/main/java/org/apache/rocketmq/console/model/MessageTraceView.java @@ -21,6 +21,7 @@ import com.google.common.base.Charsets; import org.apache.rocketmq.client.trace.TraceBean; import org.apache.rocketmq.client.trace.TraceContext; import org.apache.rocketmq.common.message.MessageExt; +import org.apache.rocketmq.console.model.trace.MessageTraceStatusEnum; import org.apache.rocketmq.console.util.MsgTraceDecodeUtil; import java.util.ArrayList; @@ -66,9 +67,9 @@ public class MessageTraceView { messageTraceView.setCostTime(context.getCostTime()); messageTraceView.setGroupName(context.getGroupName()); if (context.isSuccess()) { - messageTraceView.setStatus("success"); + messageTraceView.setStatus(MessageTraceStatusEnum.SUCCESS.getStatus()); } else { - messageTraceView.setStatus("failed"); + messageTraceView.setStatus(MessageTraceStatusEnum.FAILED.getStatus()); } messageTraceView.setKeys(traceBean.getKeys()); messageTraceView.setMsgId(traceBean.getMsgId()); diff --git a/src/main/java/org/apache/rocketmq/console/model/trace/MessageTraceStatusEnum.java b/src/main/java/org/apache/rocketmq/console/model/trace/MessageTraceStatusEnum.java new file mode 100644 index 0000000..9b70e7a --- /dev/null +++ b/src/main/java/org/apache/rocketmq/console/model/trace/MessageTraceStatusEnum.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.console.model.trace; + +import lombok.Getter; + +@Getter +public enum MessageTraceStatusEnum { + SUCCESS("success"), + FAILED("failed"), + UNKNOWN("unknown"); + private final String status; + + MessageTraceStatusEnum(String status) { + this.status = status; + } +} diff --git a/src/main/java/org/apache/rocketmq/console/model/trace/TraceNode.java b/src/main/java/org/apache/rocketmq/console/model/trace/TraceNode.java index 97bf20d..caf3de6 100644 --- a/src/main/java/org/apache/rocketmq/console/model/trace/TraceNode.java +++ b/src/main/java/org/apache/rocketmq/console/model/trace/TraceNode.java @@ -25,8 +25,8 @@ public class TraceNode { private String storeHost; private String clientHost; private int costTime; - private long beginTimeStamp; - private long endTimeStamp; + private long beginTimestamp; + private long endTimestamp; private int retryTimes; private String status; private String transactionState; diff --git a/src/main/java/org/apache/rocketmq/console/service/impl/MessageTraceServiceImpl.java b/src/main/java/org/apache/rocketmq/console/service/impl/MessageTraceServiceImpl.java index 49e3630..8d482ae 100644 --- a/src/main/java/org/apache/rocketmq/console/service/impl/MessageTraceServiceImpl.java +++ b/src/main/java/org/apache/rocketmq/console/service/impl/MessageTraceServiceImpl.java @@ -40,6 +40,7 @@ import org.apache.rocketmq.console.model.trace.ProducerNode; import org.apache.rocketmq.console.model.trace.MessageTraceGraph; import org.apache.rocketmq.console.model.trace.SubscriptionNode; import org.apache.rocketmq.console.model.trace.TraceNode; +import org.apache.rocketmq.console.model.trace.MessageTraceStatusEnum; import org.apache.rocketmq.console.service.MessageTraceService; import org.apache.rocketmq.tools.admin.MQAdminExt; import org.slf4j.Logger; @@ -125,13 +126,16 @@ public class MessageTraceServiceImpl implements MessageTraceService { } private TraceNode buildTransactionNode(MessageTraceView messageTraceView) { - return buildTraceNode(messageTraceView); + TraceNode transactionNode = buildTraceNode(messageTraceView); + transactionNode.setCostTime(-1); + return transactionNode; } private List buildSubscriptionNodeList( Map> requestIdTracePairMap) { Map> subscriptionTraceNodeMap = Maps.newHashMap(); for (Pair traceNodePair : requestIdTracePairMap.values()) { + traceNodePair = makeBeforeOrAfterMissCompatible(traceNodePair); MessageTraceView subBeforeTrace = traceNodePair.getObject1(); MessageTraceView subAfterTrace = traceNodePair.getObject2(); List traceNodeList = subscriptionTraceNodeMap.computeIfAbsent(subBeforeTrace.getGroupName(), @@ -141,9 +145,9 @@ public class MessageTraceServiceImpl implements MessageTraceService { consumeNode.setStoreHost(subBeforeTrace.getStoreHost()); consumeNode.setClientHost(subBeforeTrace.getClientHost()); consumeNode.setRetryTimes(subBeforeTrace.getRetryTimes()); - consumeNode.setBeginTimeStamp(subBeforeTrace.getTimeStamp()); + consumeNode.setBeginTimestamp(subBeforeTrace.getTimeStamp()); consumeNode.setCostTime(subAfterTrace.getCostTime()); - consumeNode.setEndTimeStamp(subBeforeTrace.getTimeStamp() + subAfterTrace.getCostTime()); + consumeNode.setEndTimestamp(subBeforeTrace.getTimeStamp() + Math.max(0, subAfterTrace.getCostTime())); consumeNode.setStatus(subAfterTrace.getStatus()); traceNodeList.add(consumeNode); } @@ -157,6 +161,25 @@ public class MessageTraceServiceImpl implements MessageTraceService { }).collect(Collectors.toList()); } + private Pair makeBeforeOrAfterMissCompatible(Pair traceNodePair) { + if (traceNodePair.getObject1() != null && traceNodePair.getObject2() != null) { + return traceNodePair; + } + MessageTraceView subBeforeTrace = traceNodePair.getObject1(); + MessageTraceView subAfterTrace = traceNodePair.getObject2(); + if (subBeforeTrace == null) { + subBeforeTrace = new MessageTraceView(); + BeanUtils.copyProperties(subAfterTrace, subBeforeTrace); + } + if (subAfterTrace == null) { + subAfterTrace = new MessageTraceView(); + BeanUtils.copyProperties(subBeforeTrace, subAfterTrace); + subAfterTrace.setStatus(MessageTraceStatusEnum.UNKNOWN.getStatus()); + subAfterTrace.setCostTime(-1); + } + return new Pair<>(subBeforeTrace, subAfterTrace); + } + private void putIntoMessageTraceViewGroupMap(MessageTraceView messageTraceView, Map> messageTraceViewGroupMap) { Pair messageTracePair = messageTraceViewGroupMap @@ -183,13 +206,13 @@ public class MessageTraceServiceImpl implements MessageTraceService { private TraceNode buildTraceNode(MessageTraceView messageTraceView) { TraceNode traceNode = new TraceNode(); BeanUtils.copyProperties(messageTraceView, traceNode); - traceNode.setBeginTimeStamp(messageTraceView.getTimeStamp()); - traceNode.setEndTimeStamp(messageTraceView.getTimeStamp() + messageTraceView.getCostTime()); + traceNode.setBeginTimestamp(messageTraceView.getTimeStamp()); + traceNode.setEndTimestamp(messageTraceView.getTimeStamp() + messageTraceView.getCostTime()); return traceNode; } private List sortTraceNodeListByBeginTimestamp(List traceNodeList) { - traceNodeList.sort((o1, o2) -> -Long.compare(o1.getBeginTimeStamp(), o2.getBeginTimeStamp())); + traceNodeList.sort((o1, o2) -> -Long.compare(o1.getBeginTimestamp(), o2.getBeginTimestamp())); return traceNodeList; } } diff --git a/src/main/resources/static/src/messageTrace.js b/src/main/resources/static/src/messageTrace.js index f53cbb3..56e5088 100644 --- a/src/main/resources/static/src/messageTrace.js +++ b/src/main/resources/static/src/messageTrace.js @@ -18,17 +18,14 @@ var module = app; const SUCCESS_COLOR = '#75d874'; const ERROR_COLOR = 'red'; +const UNKNOWN_COLOR = 'yellow'; const TRANSACTION_COMMIT_COLOR = SUCCESS_COLOR; const TRANSACTION_ROLLBACK_COLOR = ERROR_COLOR; -const SHOW_GRAPH = 'Show Graph'; -const HIDE_GRAPH = 'Hide Graph'; -const SHOW_GRAPH_TRACE_DATA = 'Show Graph Trace Data'; -const SHOW_ORIGINAL_TRACE_DATA = 'Show Original Trace Data'; const TRANSACTION_UNKNOWN_COLOR = 'grey' const TIME_FORMAT_PATTERN = "YYYY-MM-DD HH:mm:ss.SSS"; const DEFAULT_DISPLAY_DURATION = 10 * 1000 // transactionTraceNode do not have costTime, assume it cost 50ms -const transactionCheckCostTime = 50; +const TRANSACTION_CHECK_COST_TIME = 50; module.controller('messageTraceController', ['$scope', '$routeParams', 'ngDialog', '$http', 'Notification', function ($scope, $routeParams, ngDialog, $http, Notification) { $scope.allTopicList = []; $scope.selectedTopic = []; @@ -120,10 +117,6 @@ module.controller('messageTraceController', ['$scope', '$routeParams', 'ngDialog }]); module.controller('messageTraceDetailViewDialogController', ['$scope', '$timeout', 'ngDialog', '$http', 'Notification', function ($scope, $timeout, ngDialog, $http, Notification) { - $scope.displayGraph = false; - $scope.showGraphData = true; - $scope.graphButtonName = SHOW_GRAPH; - $scope.traceDataButtonName = SHOW_ORIGINAL_TRACE_DATA; $scope.displayMessageTraceGraph = function (messageTraceGraph) { let dom = document.getElementById("messageTraceGraph"); $scope.messageTraceGraph = echarts.init(dom); @@ -134,18 +127,17 @@ module.controller('messageTraceDetailViewDialogController', ['$scope', '$timeout let endTime = 0; let messageGroups = []; if (messageTraceGraph.producerNode) { - startTime = +messageTraceGraph.producerNode.traceNode.beginTimeStamp; - endTime = +messageTraceGraph.producerNode.traceNode.endTimeStamp; + startTime = +messageTraceGraph.producerNode.traceNode.beginTimestamp; + endTime = +messageTraceGraph.producerNode.traceNode.endTimestamp; } else { messageTraceGraph.subscriptionNodeList.forEach(subscriptionNode => { subscriptionNode.consumeNodeList.forEach(consumeNode => { - startTime = Math.min(startTime, consumeNode.beginTimeStamp); + startTime = Math.min(startTime, consumeNode.beginTimestamp); }) }) } function buildNodeColor(traceNode) { - let nodeColor = SUCCESS_COLOR; if (traceNode.transactionState != null) { switch (traceNode.transactionState) { case 'COMMIT_MESSAGE': @@ -158,16 +150,20 @@ module.controller('messageTraceDetailViewDialogController', ['$scope', '$timeout return ERROR_COLOR; } } - if (traceNode.status !== 'success') { - nodeColor = ERROR_COLOR; + switch (traceNode.status) { + case 'failed': + return ERROR_COLOR; + case 'unknown': + return UNKNOWN_COLOR; + default: + return SUCCESS_COLOR; } - return nodeColor; } function formatXAxisTime(value) { let duration = Math.max(0, value - startTime); if (duration < 1000) - return duration + 'ms'; + return timeFormat(duration, 'ms'); duration /= 1000; if (duration < 60) return timeFormat(duration, 's'); @@ -190,13 +186,31 @@ module.controller('messageTraceDetailViewDialogController', ['$scope', '$timeout return ""; } + function formatCostTimeStr(costTime) { + if (costTime < 0) { + return ""; + } + let costTimeStr = costTime; + if (costTime === 0) { + costTimeStr = '<1' + } + return `${costTimeStr}ms`; + } + + function buildCostTimeInfo(costTime) { + if (costTime < 0) { + return ""; + } + return `costTime: ${formatCostTimeStr(costTime)}
` + } + function formatNodeToolTip(params) { let traceNode = params.data.traceData.traceNode; return ` - costTime: ${traceNode.costTime}ms
+ ${buildCostTimeInfo(traceNode.costTime)} status: ${traceNode.status}
- beginTimeStamp: ${new moment(traceNode.beginTimeStamp).format(TIME_FORMAT_PATTERN)}
- endTimeStamp: ${new moment(traceNode.endTimeStamp).format(TIME_FORMAT_PATTERN)}
+ beginTimestamp: ${new moment(traceNode.beginTimestamp).format(TIME_FORMAT_PATTERN)}
+ endTimestamp: ${new moment(traceNode.endTimestamp).format(TIME_FORMAT_PATTERN)}
clientHost: ${traceNode.clientHost}
storeHost: ${traceNode.storeHost}
retryTimes: ${traceNode.retryTimes}
@@ -211,8 +225,8 @@ module.controller('messageTraceDetailViewDialogController', ['$scope', '$timeout data.push({ value: [ index, - traceNode.beginTimeStamp, - traceNode.endTimeStamp, + traceNode.beginTimestamp, + traceNode.endTimestamp === traceNode.beginTimestamp ? traceNode.beginTimestamp + 1 : traceNode.endTimestamp, traceNode.costTime ], itemStyle: { @@ -225,7 +239,7 @@ module.controller('messageTraceDetailViewDialogController', ['$scope', '$timeout traceNode: traceNode } }); - endTime = Math.max(traceNode.endTimeStamp, endTime); + endTime = Math.max(traceNode.endTimestamp, endTime); } messageTraceGraph.subscriptionNodeList.forEach(item => { @@ -239,13 +253,12 @@ module.controller('messageTraceDetailViewDialogController', ['$scope', '$timeout let producerNodeIndex = messageGroups.length - 1; addTraceData(messageTraceGraph.producerNode.traceNode, producerNodeIndex); messageTraceGraph.producerNode.transactionNodeList.forEach(transactionNode => { - transactionNode.beginTimeStamp = Math.max(messageTraceGraph.producerNode.traceNode.endTimeStamp, - transactionNode.endTimeStamp - transactionCheckCostTime); + transactionNode.beginTimestamp = Math.max(messageTraceGraph.producerNode.traceNode.endTimestamp, + transactionNode.endTimestamp - TRANSACTION_CHECK_COST_TIME); addTraceData(transactionNode, producerNodeIndex) - endTime = Math.max(endTime, transactionNode.endTimeStamp); + endTime = Math.max(endTime, transactionNode.endTimestamp); }) } - let totalDuration = endTime - startTime; if (totalDuration > DEFAULT_DISPLAY_DURATION) { dataZoomEnd = DEFAULT_DISPLAY_DURATION / totalDuration * 100 @@ -274,8 +287,8 @@ module.controller('messageTraceDetailViewDialogController', ['$scope', '$timeout transition: ['shape'], shape: rectShape, style: api.style({ - text: `${api.value(3)}ms`, - textFill: '#fff' + text: formatCostTimeStr(api.value(3)), + textFill: '#000' }) }; } @@ -334,25 +347,9 @@ module.controller('messageTraceDetailViewDialogController', ['$scope', '$timeout $scope.messageTraceGraph.setOption(option); } $scope.showGraph = function () { - $scope.displayGraph = !$scope.displayGraph; - if ($scope.displayGraph) { - $scope.graphButtonName = HIDE_GRAPH; - $scope.displayMessageTraceGraph($scope.ngDialogData); - } else { - $scope.messageTraceGraph.dispose(); - $scope.graphButtonName = SHOW_GRAPH; - } + $scope.displayMessageTraceGraph($scope.ngDialogData); }; - $scope.changeTraceDataFormat = function () { - $scope.showGraphData = !$scope.showGraphData; - if ($scope.showGraphData) { - $scope.traceDataButtonName = SHOW_ORIGINAL_TRACE_DATA; - } else { - $scope.traceDataButtonName = SHOW_GRAPH_TRACE_DATA; - } - } - function initGraph() { $timeout(function () { if (document.getElementById('messageTraceGraph') == null) { diff --git a/src/main/resources/static/view/pages/messageTrace.html b/src/main/resources/static/view/pages/messageTrace.html index 9bfb8dd..4e4a754 100644 --- a/src/main/resources/static/view/pages/messageTrace.html +++ b/src/main/resources/static/view/pages/messageTrace.html @@ -132,15 +132,16 @@