/*
* 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.profiler.receiver.service;
import com.navercorp.pinpoint.profiler.context.active.ActiveTraceHistogramFactory;
import com.navercorp.pinpoint.profiler.context.active.ActiveTraceHistogramFactory.ActiveTraceHistogram;
import com.navercorp.pinpoint.profiler.context.active.ActiveTraceRepository;
import com.navercorp.pinpoint.profiler.receiver.CommandSerializer;
import com.navercorp.pinpoint.profiler.receiver.ProfilerRequestCommandService;
import com.navercorp.pinpoint.profiler.receiver.ProfilerStreamCommandService;
import com.navercorp.pinpoint.rpc.packet.stream.StreamCode;
import com.navercorp.pinpoint.rpc.stream.ServerStreamChannel;
import com.navercorp.pinpoint.rpc.stream.ServerStreamChannelContext;
import com.navercorp.pinpoint.rpc.stream.StreamChannelStateChangeEventHandler;
import com.navercorp.pinpoint.rpc.stream.StreamChannelStateCode;
import com.navercorp.pinpoint.rpc.util.TimerFactory;
import com.navercorp.pinpoint.thrift.dto.command.TCmdActiveThreadCount;
import com.navercorp.pinpoint.thrift.dto.command.TCmdActiveThreadCountRes;
import com.navercorp.pinpoint.thrift.util.SerializationUtils;
import org.apache.thrift.TBase;
import org.jboss.netty.util.HashedWheelTimer;
import org.jboss.netty.util.Timeout;
import org.jboss.netty.util.TimerTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* @author Taejin Koo
*/
public class ActiveThreadCountService implements ProfilerRequestCommandService, ProfilerStreamCommandService {
private static final long DEFAULT_FLUSH_DELAY = 1000;
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final Object lock = new Object();
private final StreamChannelStateChangeEventHandler stateChangeEventHandler = new ActiveThreadCountStreamChannelStateChangeEventHandler();
private final HashedWheelTimer timer = TimerFactory.createHashedWheelTimer("ActiveThreadCountService-Timer", 100, TimeUnit.MILLISECONDS, 512);
private final long flushDelay;
private final AtomicBoolean onTimerTask = new AtomicBoolean(false);
private final List<ServerStreamChannel> streamChannelRepository = new CopyOnWriteArrayList<ServerStreamChannel>();
private final ActiveTraceHistogramFactory activeTraceHistogramFactory;
public ActiveThreadCountService(ActiveTraceRepository activeTraceRepository) {
this(activeTraceRepository, DEFAULT_FLUSH_DELAY);
}
public ActiveThreadCountService(ActiveTraceRepository activeTraceRepository, long flushDelay) {
if (activeTraceRepository == null) {
throw new NullPointerException("activeTraceRepository");
}
this.activeTraceHistogramFactory = new ActiveTraceHistogramFactory(activeTraceRepository);
this.flushDelay = flushDelay;
}
@Override
public Class<? extends TBase> getCommandClazz() {
return TCmdActiveThreadCount.class;
}
@Override
public TBase<?, ?> requestCommandService(TBase activeThreadCountObject) {
if (activeThreadCountObject == null) {
throw new NullPointerException("activeThreadCountObject may not be null.");
}
return getActiveThreadCountResponse();
}
@Override
public StreamCode streamCommandService(TBase tBase, ServerStreamChannelContext streamChannelContext) {
logger.info("streamCommandService object:{}, streamChannelContext:{}", tBase, streamChannelContext);
streamChannelContext.getStreamChannel().addStateChangeEventHandler(stateChangeEventHandler);
return StreamCode.OK;
}
private TCmdActiveThreadCountRes getActiveThreadCountResponse() {
ActiveTraceHistogram activeTraceHistogram = this.activeTraceHistogramFactory.createHistogram();
TCmdActiveThreadCountRes response = new TCmdActiveThreadCountRes();
response.setHistogramSchemaType(activeTraceHistogram.getHistogramSchema().getTypeCode());
response.setActiveThreadCount(activeTraceHistogram.getActiveTraceCounts());
response.setTimeStamp(System.currentTimeMillis());
return response;
}
private class ActiveThreadCountStreamChannelStateChangeEventHandler implements StreamChannelStateChangeEventHandler<ServerStreamChannel> {
@Override
public void eventPerformed(ServerStreamChannel streamChannel, StreamChannelStateCode updatedStateCode) throws Exception {
logger.info("eventPerformed. ServerStreamChannel:{}, StreamChannelStateCode:{}.", streamChannel, updatedStateCode);
synchronized (lock) {
switch (updatedStateCode) {
case CONNECTED:
streamChannelRepository.add(streamChannel);
boolean turnOn = onTimerTask.compareAndSet(false, true);
if (turnOn) {
logger.info("turn on ActiveThreadCountTimerTask.");
timer.newTimeout(new ActiveThreadCountTimerTask(), flushDelay, TimeUnit.MILLISECONDS);
}
break;
case CLOSED:
case ILLEGAL_STATE:
boolean removed = streamChannelRepository.remove(streamChannel);
if (removed && streamChannelRepository.isEmpty()) {
boolean turnOff = onTimerTask.compareAndSet(true, false);
if (turnOff) {
logger.info("turn off ActiveThreadCountTimerTask.");
}
}
break;
}
}
}
@Override
public void exceptionCaught(ServerStreamChannel streamChannel, StreamChannelStateCode updatedStateCode, Throwable e) {
logger.warn("exceptionCaught caused:{}. ServerStreamChannel:{}, StreamChannelStateCode:{}.", e.getMessage(), streamChannel, updatedStateCode, e);
}
}
private class ActiveThreadCountTimerTask implements TimerTask {
@Override
public void run(Timeout timeout) throws Exception {
logger.debug("ActiveThreadCountTimerTask started. target-streams:{}", streamChannelRepository);
try {
TCmdActiveThreadCountRes activeThreadCountResponse = getActiveThreadCountResponse();
for (ServerStreamChannel serverStreamChannel : streamChannelRepository) {
byte[] payload = SerializationUtils.serialize(activeThreadCountResponse, CommandSerializer.SERIALIZER_FACTORY, null);
if (payload != null) {
serverStreamChannel.sendData(payload);
}
}
} finally {
if (timer != null && onTimerTask.get()) {
timer.newTimeout(this, flushDelay, TimeUnit.MILLISECONDS);
}
}
}
}
}