blob: a8491ce0f5f32081883997825f6019a573687281 [file] [log] [blame]
/*
* 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.servicecomb.pack.alpha.ui.controller;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.apache.servicecomb.pack.alpha.core.api.APIv1;
import org.apache.servicecomb.pack.alpha.core.fsm.repository.model.GlobalTransaction;
import org.apache.servicecomb.pack.alpha.core.fsm.repository.model.PagingGlobalTransactions;
import org.apache.servicecomb.pack.alpha.core.metrics.AlphaMetrics;
import org.apache.servicecomb.pack.alpha.ui.vo.DataTablesRequestDTO;
import org.apache.servicecomb.pack.alpha.ui.vo.DataTablesResponseDTO;
import org.apache.servicecomb.pack.alpha.ui.vo.EventDTO;
import org.apache.servicecomb.pack.alpha.ui.vo.SubTransactionDTO;
import org.apache.servicecomb.pack.alpha.ui.vo.TransactionRowDTO;
import org.apache.servicecomb.pack.alpha.ui.vo.TransactionStatisticsDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Base64.Decoder;
@Controller
@EnableScheduling
public class TransactionController {
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private static final String WEBSOCKET_BROKER_METRICES_TOPIC = "/topic/metrics";
@Autowired
SimpMessagingTemplate template;
@Autowired
APIv1 apiv1;
boolean applicationReady = false;
@PostMapping("/ui/transaction/sagalist")
@ResponseBody
public DataTablesResponseDTO sagaList(@ModelAttribute DataTablesRequestDTO dataTablesRequestDTO)
throws Exception {
List<TransactionRowDTO> data = new ArrayList<>();
PagingGlobalTransactions pagingGlobalTransactions = apiv1
.getTransactions(dataTablesRequestDTO.getState(),
dataTablesRequestDTO.getStart() / dataTablesRequestDTO.getLength(),
dataTablesRequestDTO.getLength());
pagingGlobalTransactions.getGlobalTransactions().forEach(globalTransaction -> {
data.add(TransactionRowDTO.builder()
.serviceName(globalTransaction.getServiceName())
.instanceId(globalTransaction.getInstanceId())
.globalTxId(globalTransaction.getGlobalTxId())
.state(globalTransaction.getState())
.beginTime(globalTransaction.getBeginTime())
.endTime(globalTransaction.getEndTime())
.subTxSize(globalTransaction.getSubTxSize())
.durationTime(globalTransaction.getDurationTime())
.build());
});
return DataTablesResponseDTO.builder()
.draw(dataTablesRequestDTO.getDraw())
.recordsTotal(pagingGlobalTransactions.getTotal())
.recordsFiltered(pagingGlobalTransactions.getTotal())
.data(data)
.build();
}
// TODO The state machine is not yet supported
@PostMapping("/ui/transaction/tcclist")
@ResponseBody
public DataTablesResponseDTO tccList(@ModelAttribute DataTablesRequestDTO dataTablesRequestDTO) {
List<TransactionRowDTO> data = new ArrayList<>();
return DataTablesResponseDTO.builder()
.draw(dataTablesRequestDTO.getDraw())
.recordsTotal(0)
.recordsFiltered(0)
.data(data)
.build();
}
@PostMapping("/ui/transaction/search")
@ResponseBody
public DataTablesResponseDTO searchList(
@ModelAttribute DataTablesRequestDTO dataTablesRequestDTO) throws Exception {
if (dataTablesRequestDTO.getQuery() != null) {
List<TransactionRowDTO> data = new ArrayList<>();
GlobalTransaction globalTransaction = findGlobalTransactionByGlobalTxId(
dataTablesRequestDTO.getQuery());
if (globalTransaction != null) {
data.add(TransactionRowDTO.builder()
.serviceName(globalTransaction.getServiceName())
.instanceId(globalTransaction.getInstanceId())
.globalTxId(globalTransaction.getGlobalTxId())
.state(globalTransaction.getState())
.beginTime(globalTransaction.getBeginTime())
.endTime(globalTransaction.getEndTime())
.subTxSize(globalTransaction.getSubTxSize())
.durationTime(globalTransaction.getDurationTime())
.build());
}
return DataTablesResponseDTO.builder()
.draw(dataTablesRequestDTO.getDraw())
.recordsTotal(1)
.recordsFiltered(1)
.data(data)
.build();
} else {
return this.sagaList(dataTablesRequestDTO);
}
}
@GetMapping("/ui/transaction/{globalTxId}")
public String getGlobalTransaction(ModelMap map, @PathVariable("globalTxId") String globalTxId)
throws Exception {
List<EventDTO> events = new ArrayList<>();
List<SubTransactionDTO> subTransactions = new ArrayList<>();
GlobalTransaction globalTransaction = findGlobalTransactionByGlobalTxId(globalTxId);
globalTransaction.getEvents().forEach(event -> {
EventDTO eventDTO = EventDTO.builder()
// Common Event properties
.type(event.get("type").toString())
.serviceName(event.get("serviceName").toString())
.instanceId(event.get("instanceId").toString())
.timeout(event.get("timeout") != null ? Integer.valueOf(event.get("timeout").toString()) : 0)
.globalTxId(event.get("globalTxId").toString())
.parentTxId(event.get("parentTxId") != null ? event.get("parentTxId").toString() : null)
.localTxId(event.get("localTxId") != null ? event.get("localTxId").toString() : null)
.createTime(new Date(Long.valueOf(event.get("createTime").toString())))
.build();
if (eventDTO.getType().equals("TxStartedEvent")) {
// TxStartedEvent properties
if (event.containsKey("compensationMethod")) {
eventDTO.setCompensationMethod(event.get("compensationMethod").toString());
}
if (event.containsKey("reverseRetries")) {
eventDTO.setReverseRetries(Long.valueOf(event.get("reverseRetries").toString()));
}
if (event.containsKey("forwardRetries")) {
eventDTO.setForwardRetries(Long.valueOf(event.get("forwardRetries").toString()));
}
if (event.containsKey("reverseTimeout")) {
eventDTO.setReverseTimeout(Long.valueOf(event.get("reverseTimeout").toString()));
}
if (event.containsKey("forwardTimeout")) {
eventDTO.setForwardTimeout(Long.valueOf(event.get("forwardTimeout").toString()));
}
if (event.containsKey("timeout")) {
eventDTO.setTimeout(Long.valueOf(event.get("timeout").toString()));
}
if (event.containsKey("retryDelayInMilliseconds")) {
eventDTO.setRetryDelayInMilliseconds(Long.valueOf(event.get("retryDelayInMilliseconds").toString()));
}
}
if (eventDTO.getType().equals("TxAbortedEvent") || eventDTO.getType()
.equals("SagaAbortedEvent") || eventDTO.getType()
.equals("TxCompensateAckFailedEvent")) {
if (event.containsKey("payloads")) {
Decoder decoder = Base64.getDecoder();
String exception;
try {
exception = new String(decoder.decode(event.get("payloads").toString()), "UTF-8");
} catch (IOException e) {
exception = "BASE64Decoder error";
LOG.error(e.getMessage(), e);
}
eventDTO.setException(exception);
}
}
events.add(eventDTO);
});
globalTransaction.getSubTransactions().forEach(sub -> {
subTransactions.add(
SubTransactionDTO.builder().parentTxId(globalTxId).localTxId(sub.getLocalTxId())
.beginTime(sub.getBeginTime()).endTime(sub.getEndTime())
.durationTime(sub.getDurationTime()).state(sub.getState().name()).build());
});
map.put("events", events);
map.put("globalTxId", globalTransaction.getGlobalTxId());
map.put("state", globalTransaction.getState());
map.put("endTime", globalTransaction.getEndTime());
map.put("suspendedType", globalTransaction.getSuspendedType());
map.put("subTransactions", subTransactions);
return "transaction_details";
}
@GetMapping("/ui/transaction/statistics")
@ResponseBody
public TransactionStatisticsDTO getGlobalTransactionStatistics() {
TransactionStatisticsDTO statisticsDTO = new TransactionStatisticsDTO();
Map<String, Long> statistics = apiv1.getTransactionStatistics();
if (statistics.containsKey("COMMITTED")) {
statisticsDTO.setSuccessful(statistics.get("COMMITTED").longValue());
}
if (statistics.containsKey("SUSPENDED")) {
statisticsDTO.setFailed(statistics.get("SUSPENDED").longValue());
}
if (statistics.containsKey("COMPENSATED")) {
statisticsDTO.setCompensated(statistics.get("COMPENSATED").longValue());
}
return statisticsDTO;
}
@GetMapping("/ui/transaction/slow")
@ResponseBody
public List<TransactionRowDTO> getSlowGlobalTransactionTopN() {
List<TransactionRowDTO> transactionRowDTOS = new ArrayList<>();
List<GlobalTransaction> transactions = apiv1.getSlowTransactions(10);
transactions.stream().forEach(globalTransaction -> {
transactionRowDTOS.add(TransactionRowDTO.builder()
.serviceName(globalTransaction.getServiceName())
.instanceId(globalTransaction.getInstanceId())
.globalTxId(globalTransaction.getGlobalTxId())
.state(globalTransaction.getState())
.beginTime(globalTransaction.getBeginTime())
.endTime(globalTransaction.getEndTime())
.subTxSize(globalTransaction.getSubTxSize())
.durationTime(globalTransaction.getDurationTime())
.build());
});
return transactionRowDTOS;
}
@GetMapping("/ui/transaction/metrics")
@ResponseBody
public AlphaMetrics getMetrics() {
return getAlphaMetrics();
}
@Scheduled(fixedDelay = 1000)
public void publishUpdates() {
if (applicationReady) {
template.convertAndSend(WEBSOCKET_BROKER_METRICES_TOPIC, getAlphaMetrics());
}
}
private GlobalTransaction findGlobalTransactionByGlobalTxId(String globalTxId) throws Exception {
GlobalTransaction globalTransaction = apiv1.getTransactionByGlobalTxId(globalTxId);
return globalTransaction;
}
private AlphaMetrics getAlphaMetrics() {
AlphaMetrics alphaMetrics = apiv1.getMetrics();
return alphaMetrics;
}
@EventListener(ApplicationReadyEvent.class)
public void startUp() {
applicationReady = true;
}
}