package com.nhn.pinpoint.web.service; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StopWatch; import com.nhn.pinpoint.common.ServiceType; import com.nhn.pinpoint.common.bo.AgentInfoBo; import com.nhn.pinpoint.web.applicationmap.ApplicationMap; import com.nhn.pinpoint.web.applicationmap.rawdata.TransactionFlowStatistics; import com.nhn.pinpoint.web.dao.AgentInfoDao; import com.nhn.pinpoint.web.dao.ApplicationIndexDao; import com.nhn.pinpoint.web.dao.ApplicationMapStatisticsCalleeDao; import com.nhn.pinpoint.web.dao.ApplicationMapStatisticsCallerDao; import com.nhn.pinpoint.web.dao.HostApplicationMapDao; import com.nhn.pinpoint.web.vo.Application; import com.nhn.pinpoint.web.vo.LinkStatistics; /** * * @author netspider */ @Service public class ApplicationMapServiceImpl implements ApplicationMapService { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired private ApplicationIndexDao applicationIndexDao; @Autowired private AgentInfoDao agentInfoDao; @Autowired private ApplicationMapStatisticsCallerDao applicationMapStatisticsCallerDao; @Autowired private ApplicationMapStatisticsCalleeDao applicationMapStatisticsCalleeDao; @Autowired private HostApplicationMapDao hostApplicationMapDao; private Set selectAgents(String applicationId) { if (applicationId == null) { throw new NullPointerException("applicationId must not be null"); } String[] agentIds = applicationIndexDao.selectAgentIds(applicationId); Set agentSet = new HashSet(); for (String agentId : agentIds) { // TODO 조회 시간대에 따라서 agent info row timestamp를 변경하여 조회해야하는지는 모르겠음. AgentInfoBo info = agentInfoDao.findAgentInfoBeforeStartTime(agentId, System.currentTimeMillis()); agentSet.add(info); } return agentSet; } /** * callerApplicationName이 호출한 callee를 모두 조회 * * @param callerApplicationName * @param callerServiceType * @param from * @param to * @param calleeFoundApplications * @param callerFoundApplications * @return */ private Set selectCallee(String callerApplicationName, short callerServiceType, long from, long to, Set calleeFoundApplications, Set callerFoundApplications) { // 이미 조회된 구간이면 skip if (calleeFoundApplications.contains(callerApplicationName + callerServiceType)) { logger.debug("ApplicationStatistics exists. Skip finding callee. " + callerApplicationName + callerServiceType); return new HashSet(0); } calleeFoundApplications.add(callerApplicationName + callerServiceType); logger.debug("Find Callee. caller=" + callerApplicationName + ", serviceType=" + ServiceType.findServiceType(callerServiceType)); final Set calleeSet = new HashSet(); Map callee = applicationMapStatisticsCalleeDao.selectCallee(callerApplicationName, callerServiceType, from, to); logger.debug(" Found Callee. count=" + callee.size() + ", caller=" + callerApplicationName); for (Entry entry : callee.entrySet()) { boolean replaced = replaceApplicationInfo(entry.getValue(), from, to); // replaced된 녀석은 CLIENT이기 때문에 callee검색용도로만 사용하고 map에 추가하지 않는다. if (!replaced) { fillAdditionalInfo(entry.getValue(), from, to); calleeSet.add(entry.getValue()); } // terminal, unknowncloud 인 경우에는 skip if (entry.getValue().getToServiceType().isTerminal() || entry.getValue().getToServiceType().isUnknown()) { continue; } TransactionFlowStatistics stat = entry.getValue(); logger.debug(" Find subCallee of " + stat.getTo()); Set calleeSub = selectCallee(stat.getTo(), stat.getToServiceType().getCode(), from, to, calleeFoundApplications, callerFoundApplications); logger.debug(" Found subCallee. count=" + calleeSub.size() + ", caller=" + stat.getTo()); calleeSet.addAll(calleeSub); // 찾아진 녀석들에 대한 caller도 찾는다. for (TransactionFlowStatistics eachCallee : calleeSub) { logger.debug(" Find caller of " + eachCallee.getFrom()); Set callerSub = selectCaller(eachCallee.getFrom(), eachCallee.getFromServiceType().getCode(), from, to, calleeFoundApplications, callerFoundApplications); logger.debug(" Found subCaller. count=" + callerSub.size() + ", callee=" + eachCallee.getFrom()); calleeSet.addAll(callerSub); } } return calleeSet; } /** * callee applicationname을 호출한 caller 조회. * * @param calleeApplicationName * @param from * @param to * @param foundApplications * @return */ private Set selectCaller(String calleeApplicationName, short calleeServiceType, long from, long to, Set calleeFoundApplications, Set callerFoundApplications) { // 이미 조회된 구간이면 skip if (callerFoundApplications.contains(calleeApplicationName + calleeServiceType)) { logger.debug("ApplicationStatistics exists. Skip finding caller. " + calleeApplicationName + calleeServiceType); return new HashSet(0); } callerFoundApplications.add(calleeApplicationName + calleeServiceType); logger.debug("Find Caller. callee=" + calleeApplicationName + ", serviceType=" + ServiceType.findServiceType(calleeServiceType)); final Set callerSet = new HashSet(); Map caller = applicationMapStatisticsCallerDao.selectCaller(calleeApplicationName, calleeServiceType, from, to); logger.debug(" Found Caller. count=" + caller.size() + ", callee=" + calleeApplicationName); for (Entry entry : caller.entrySet()) { fillAdditionalInfo(entry.getValue(), from, to); callerSet.add(entry.getValue()); TransactionFlowStatistics stat = entry.getValue(); // 나를 부른 application을 찾아야 하기 떄문에 to를 입력. Set callerSub = selectCaller(stat.getFrom(), stat.getFromServiceType().getCode(), from, to, calleeFoundApplications, callerFoundApplications); callerSet.addAll(callerSub); // 찾아진 녀석들에 대한 callee도 찾는다. for (TransactionFlowStatistics eachCallee : callerSub) { // terminal이면 skip if (eachCallee.getToServiceType().isTerminal() || eachCallee.getToServiceType().isUnknown()) { continue; } Set calleeSub = selectCallee(eachCallee.getTo(), eachCallee.getToServiceType().getCode(), from, to, calleeFoundApplications, callerFoundApplications); callerSet.addAll(calleeSub); } } return callerSet; } private boolean replaceApplicationInfo(TransactionFlowStatistics stat, long from, long to) { // rpc client의 목적지가 agent가 설치되어 application name이 존재한다면 replace. if (stat.getToServiceType().isRpcClient()) { logger.debug("Find applicationName {} {} {}", stat.getTo(), from, to); Application app = hostApplicationMapDao.findApplicationName(stat.getTo(), from, to); if (app != null) { logger.debug("Application info replaced. {} => {}", stat, app); stat.setTo(app.getApplicationName()); stat.setToServiceType(app.getServiceType()); return true; } else { stat.setToServiceType(ServiceType.UNKNOWN_CLOUD); } } return false; } private void fillAdditionalInfo(TransactionFlowStatistics stat, long from, long to) { if (stat.getToServiceType().isTerminal() || stat.getToServiceType().isUnknown()) { return; } Set agentSet = selectAgents(stat.getTo()); if (agentSet.isEmpty()) { return; } // destination이 WAS이고 agent가 설치되어있으면 agentSet이 존재한다. stat.addToAgentSet(agentSet); logger.debug("fill agent info. {}, {}", stat.getTo(), agentSet); } /** * 메인화면에서 사용. 시간별로 TimeSlot을 조회하여 서버 맵을 그릴 때 사용한다. */ @Override public ApplicationMap selectApplicationMap(String applicationName, short serviceType, long from, long to) { if (applicationName == null) { throw new NullPointerException("applicationName must not be null"); } logger.debug("SelectApplicationMap"); StopWatch watch = new StopWatch("applicationMapWatch"); watch.start(); // 무한 탐색을 방지하기 위한 용도. final Set callerFoundApplications = new HashSet(); final Set calleeFoundApplications = new HashSet(); Set callee = selectCallee(applicationName, serviceType, from, to, calleeFoundApplications, callerFoundApplications); Set caller = selectCaller(applicationName, serviceType, from, to, calleeFoundApplications, callerFoundApplications); Set data = new HashSet(callee.size() + caller.size()); data.addAll(callee); data.addAll(caller); ApplicationMap map = new ApplicationMap(data).build(); watch.stop(); logger.info("Fetch applicationmap elapsed. {}ms", watch.getLastTaskTimeMillis()); return map; } @Override public LinkStatistics linkStatistics(long from, long to, String srcApplicationName, short srcServiceType, String destApplicationName, short destServiceType) { if (srcApplicationName == null) { throw new NullPointerException("srcApplicationName must not be null"); } if (destApplicationName == null) { throw new NullPointerException("destApplicationName must not be null"); } List>> list; if (ServiceType.findServiceType(srcServiceType) == ServiceType.CLIENT) { logger.debug("Find 'client -> any' link statistics"); // client는 applicatinname + servicetype.client로 기록된다. // 그래서 src, dest가 둘 다 dest로 같음. list = applicationMapStatisticsCalleeDao.selectCalleeStatistics(destApplicationName, srcServiceType, destApplicationName, destServiceType, from, to); } else if (ServiceType.findServiceType(destServiceType).isWas()) { logger.debug("Find 'any -> was' link statistics"); // destination이 was인 경우에는 중간에 client event가 끼어있기 때문에 callee에서 // caller가 // 같은녀석을 찾아야 한다. list = applicationMapStatisticsCallerDao.selectCallerStatistics(srcApplicationName, srcServiceType, destApplicationName, destServiceType, from, to); } else { logger.debug("Find 'was -> terminal' link statistics"); // 일반적으로 was -> terminal 간의 통계정보 조회. list = applicationMapStatisticsCalleeDao.selectCalleeStatistics(srcApplicationName, srcServiceType, destApplicationName, destServiceType, from, to); } LinkStatistics statistics = new LinkStatistics(from, to); // 조회가 안되는 histogram slot이 있으면 UI에 모두 보이지 않기 때문에 미리 정의된 slot을 모두 할당한다. statistics.setDefaultHistogramSlotList(ServiceType.findServiceType(destServiceType).getHistogram().getHistogramSlotList()); logger.debug("Fetched statistics data=" + list); for (Map> map : list) { for (Entry> entry : map.entrySet()) { long timestamp = entry.getKey(); Map histogramMap = entry.getValue(); for (Entry histogram : histogramMap.entrySet()) { if (histogram.getKey() == -1) { statistics.addSample(timestamp, histogram.getKey(), histogram.getValue(), true); } else { statistics.addSample(timestamp, histogram.getKey(), histogram.getValue(), false); } } } } return statistics; } }