/*
* Copyright 2017 NAVER Corp.
*
* Licensed 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 com.navercorp.pinpoint.web.websocket;
import com.navercorp.pinpoint.rpc.packet.stream.StreamClosePacket;
import com.navercorp.pinpoint.rpc.packet.stream.StreamCode;
import com.navercorp.pinpoint.rpc.packet.stream.StreamCreateFailPacket;
import com.navercorp.pinpoint.rpc.packet.stream.StreamResponsePacket;
import com.navercorp.pinpoint.rpc.stream.ClientStreamChannel;
import com.navercorp.pinpoint.rpc.stream.ClientStreamChannelContext;
import com.navercorp.pinpoint.rpc.stream.ClientStreamChannelMessageListener;
import com.navercorp.pinpoint.rpc.stream.LoggingStreamChannelMessageListener;
import com.navercorp.pinpoint.rpc.stream.StreamChannel;
import com.navercorp.pinpoint.rpc.stream.StreamChannelStateChangeEventHandler;
import com.navercorp.pinpoint.rpc.stream.StreamChannelStateCode;
import com.navercorp.pinpoint.thrift.dto.command.TCmdActiveThreadCount;
import com.navercorp.pinpoint.thrift.dto.command.TCommandTransferResponse;
import com.navercorp.pinpoint.thrift.dto.command.TRouteResult;
import com.navercorp.pinpoint.web.service.AgentService;
import com.navercorp.pinpoint.web.vo.AgentActiveThreadCount;
import com.navercorp.pinpoint.web.vo.AgentActiveThreadCountFactory;
import com.navercorp.pinpoint.web.vo.AgentInfo;
import org.apache.thrift.TBase;
import org.apache.thrift.TException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @Author Taejin Koo
*/
public class ActiveThreadCountWorker implements PinpointWebSocketHandlerWorker {
private static final ClientStreamChannelMessageListener LOGGING = LoggingStreamChannelMessageListener.CLIENT_LISTENER;
private static final TCmdActiveThreadCount COMMAND_INSTANCE = new TCmdActiveThreadCount();
private static final ActiveThreadCountErrorType INTERNAL_ERROR = ActiveThreadCountErrorType.PINPOINT_INTERNAL_ERROR;
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final Object lock = new Object();
private final AgentService agentService;
private final String applicationName;
private final String agentId;
private final PinpointWebSocketResponseAggregator responseAggregator;
private final WorkerActiveManager workerActiveManager;
private final AgentActiveThreadCountFactory failResponseFactory;
private volatile AgentActiveThreadCount defaultFailResponse;
private final MessageListener messageListener;
private final StateChangeListener stateChangeListener;
private volatile boolean started = false;
private volatile boolean active = false;
private volatile boolean stopped = false;
private StreamChannel streamChannel;
public ActiveThreadCountWorker(AgentService agentService, AgentInfo agentInfo, PinpointWebSocketResponseAggregator webSocketResponseAggregator, WorkerActiveManager workerActiveManager) {
this(agentService, agentInfo.getApplicationName(), agentInfo.getAgentId(), webSocketResponseAggregator, workerActiveManager);
}
public ActiveThreadCountWorker(AgentService agentService, String applicationName, String agentId, PinpointWebSocketResponseAggregator webSocketResponseAggregator, WorkerActiveManager workerActiveManager) {
this.agentService = agentService;
this.applicationName = applicationName;
this.agentId = agentId;
this.responseAggregator = webSocketResponseAggregator;
this.workerActiveManager = workerActiveManager;
AgentActiveThreadCountFactory failResponseFactory = new AgentActiveThreadCountFactory();
failResponseFactory.setAgentId(agentId);
this.failResponseFactory = failResponseFactory;
this.defaultFailResponse = failResponseFactory.createFail(INTERNAL_ERROR.getMessage());
this.messageListener = new MessageListener();
this.stateChangeListener = new StateChangeListener();
}
@Override
public void start(AgentInfo agentInfo) {
if (!applicationName.equals(agentInfo.getApplicationName())) {
return;
}
if (!agentId.equals(agentInfo.getAgentId())) {
return;
}
synchronized (lock) {
if (!started) {
started = true;
logger.info("ActiveThreadCountWorker start. applicationName:{}, agentId:{}", applicationName, agentId);
this.active = active0(agentInfo);
}
}
}
@Override
public boolean reactive(AgentInfo agentInfo) {
synchronized (lock) {
if (isTurnOn()) {
if (active) {
return true;
}
logger.info("ActiveThreadCountWorker reactive. applicationName:{}, agentId:{}", applicationName, agentId);
active = active0(agentInfo);
return active;
}
}
return false;
}
@Override
public void stop() {
synchronized (lock) {
if (isTurnOn()) {
stopped = true;
logger.info("ActiveThreadCountWorker stop. applicationName:{}, agentId:{}, streamChannel:{}", applicationName, agentId, streamChannel);
try {
closeStreamChannel();
} catch (Exception ignored) {
}
return;
}
}
}
private boolean active0(AgentInfo agentInfo) {
synchronized (lock) {
boolean active = false;
try {
ClientStreamChannelContext clientStreamChannelContext = agentService.openStream(agentInfo, COMMAND_INSTANCE, messageListener, stateChangeListener);
if (clientStreamChannelContext == null) {
setDefaultErrorMessage(StreamCode.CONNECTION_NOT_FOUND.name());
workerActiveManager.addReactiveWorker(agentInfo);
} else {
if (clientStreamChannelContext.getCreateFailPacket() == null) {
streamChannel = clientStreamChannelContext.getStreamChannel();
setDefaultErrorMessage(TRouteResult.TIMEOUT.name());
active = true;
} else {
StreamCreateFailPacket createFailPacket = clientStreamChannelContext.getCreateFailPacket();
setDefaultErrorMessage(createFailPacket.getCode().name());
}
}
} catch (TException exception) {
setDefaultErrorMessage(TRouteResult.NOT_SUPPORTED_REQUEST.name());
}
return active;
}
}
private boolean isTurnOn() {
if (started && !stopped) {
return true;
} else {
return false;
}
}
private void closeStreamChannel() {
if (streamChannel != null) {
streamChannel.close();
}
setDefaultErrorMessage(StreamCode.STATE_CLOSED.name());
}
private void setDefaultErrorMessage(String message) {
ActiveThreadCountErrorType errorType = ActiveThreadCountErrorType.getType(message);
AgentActiveThreadCount failResponse = failResponseFactory.createFail(errorType.getCode(), errorType.getMessage());
defaultFailResponse = failResponse;
}
public String getAgentId() {
return agentId;
}
public AgentActiveThreadCount getDefaultFailResponse() {
return defaultFailResponse;
}
private class MessageListener implements ClientStreamChannelMessageListener {
@Override
public void handleStreamData(ClientStreamChannelContext streamChannelContext, StreamResponsePacket packet) {
LOGGING.handleStreamData(streamChannelContext, packet);
TBase response = agentService.deserializeResponse(packet.getPayload(), null);
AgentActiveThreadCount activeThreadCount = getAgentActiveThreadCount(response);
responseAggregator.response(activeThreadCount);
}
@Override
public void handleStreamClose(ClientStreamChannelContext streamChannelContext, StreamClosePacket packet) {
LOGGING.handleStreamClose(streamChannelContext, packet);
setDefaultErrorMessage(StreamCode.STATE_CLOSED.name());
}
private AgentActiveThreadCount getAgentActiveThreadCount(TBase routeResponse) {
if (routeResponse instanceof TCommandTransferResponse) {
byte[] payload = ((TCommandTransferResponse) routeResponse).getPayload();
TBase<?, ?> activeThreadCountResponse = agentService.deserializeResponse(payload, null);
AgentActiveThreadCountFactory factory = new AgentActiveThreadCountFactory();
factory.setAgentId(agentId);
return factory.create(activeThreadCountResponse);
} else {
logger.warn("getAgentActiveThreadCount failed. applicationName:{}, agentId:{}", applicationName, agentId);
AgentActiveThreadCountFactory factory = new AgentActiveThreadCountFactory();
factory.setAgentId(agentId);
return factory.createFail(INTERNAL_ERROR.getMessage());
}
}
}
private class StateChangeListener implements StreamChannelStateChangeEventHandler<ClientStreamChannel> {
@Override
public void eventPerformed(ClientStreamChannel streamChannel, StreamChannelStateCode updatedStateCode) throws Exception {
logger.info("eventPerformed streamChannel:{}, stateCode:{}", streamChannel, updatedStateCode);
switch (updatedStateCode) {
case CLOSED:
case ILLEGAL_STATE:
if (isTurnOn()) {
active = false;
workerActiveManager.addReactiveWorker(agentId);
setDefaultErrorMessage(StreamCode.STATE_CLOSED.name());
}
break;
}
}
@Override
public void exceptionCaught(ClientStreamChannel streamChannel, StreamChannelStateCode updatedStateCode, Throwable e) {
logger.warn("exceptionCaught message:{}, streamChannel:{}, stateCode:{}", e.getMessage(), streamChannel, updatedStateCode, e);
}
}
}