/* * Copyright 2014 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.service; import com.navercorp.pinpoint.common.trace.ServiceType; import com.navercorp.pinpoint.web.applicationmap.histogram.TimeHistogram; import com.navercorp.pinpoint.web.applicationmap.rawdata.*; import com.navercorp.pinpoint.web.dao.HostApplicationMapDao; import com.navercorp.pinpoint.web.dao.MapStatisticsCalleeDao; import com.navercorp.pinpoint.web.dao.MapStatisticsCallerDao; import com.navercorp.pinpoint.web.security.ServerMapDataFilter; import com.navercorp.pinpoint.web.service.map.AcceptApplication; import com.navercorp.pinpoint.web.service.map.AcceptApplicationLocalCache; import com.navercorp.pinpoint.web.service.map.RpcApplication; import com.navercorp.pinpoint.web.vo.Application; import com.navercorp.pinpoint.web.vo.LinkKey; import com.navercorp.pinpoint.web.vo.Range; import com.navercorp.pinpoint.web.vo.SearchOption; import org.apache.commons.collections.CollectionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; /** * Breadth-first link search * not thread safe * @author emeroad * @author minwoo.jung */ public class BFSLinkSelector implements LinkSelector { private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final LinkVisitChecker linkVisitChecker = new LinkVisitChecker(); private final MapStatisticsCalleeDao mapStatisticsCalleeDao; private final MapStatisticsCallerDao mapStatisticsCallerDao; private final HostApplicationMapDao hostApplicationMapDao; private final AcceptApplicationLocalCache acceptApplicationLocalCache = new AcceptApplicationLocalCache(); private final Set<LinkData> emulationLinkMarker = new HashSet<>(); private final Queue nextQueue = new Queue(); private ServerMapDataFilter serverMapDataFilter; public BFSLinkSelector(MapStatisticsCallerDao mapStatisticsCallerDao, MapStatisticsCalleeDao mapStatisticsCalleeDao, HostApplicationMapDao hostApplicationMapDao, ServerMapDataFilter serverMapDataFilter) { if (mapStatisticsCalleeDao == null) { throw new NullPointerException("mapStatisticsCalleeDao must not be null"); } if (mapStatisticsCallerDao == null) { throw new NullPointerException("mapStatisticsCallerDao must not be null"); } if (hostApplicationMapDao == null) { throw new NullPointerException("hostApplicationMapDao must not be null"); } this.mapStatisticsCalleeDao = mapStatisticsCalleeDao; this.mapStatisticsCallerDao = mapStatisticsCallerDao; this.hostApplicationMapDao = hostApplicationMapDao; this.serverMapDataFilter = serverMapDataFilter; } /** * Queries for all applications(caller&callee) called by the targetApplicationList * * @param targetApplicationList * @param range * @return */ private LinkDataDuplexMap selectLink(List<Application> targetApplicationList, Range range, SearchDepth callerDepth, SearchDepth calleeDepth) { final LinkDataDuplexMap searchResult = new LinkDataDuplexMap(); for (Application targetApplication : targetApplicationList) { final boolean searchCallerNode = checkNextCaller(targetApplication, callerDepth); if (searchCallerNode) { final LinkDataMap caller = mapStatisticsCallerDao.selectCaller(targetApplication, range); if (logger.isDebugEnabled()) { logger.debug("Found Caller. count={}, caller={}, depth={}", caller.size(), targetApplication, callerDepth.getDepth()); } final LinkDataMap replaceRpcCaller = replaceRpcCaller(caller, range); for (LinkData link : replaceRpcCaller.getLinkDataList()) { searchResult.addSourceLinkData(link); final Application toApplication = link.getToApplication(); // skip if nextApplication is a terminal or an unknown cloud if (toApplication.getServiceType().isTerminal() || toApplication.getServiceType().isUnknown()) { continue; } addNextNode(toApplication); } } final boolean searchCalleeNode = checkNextCallee(targetApplication, calleeDepth); if (searchCalleeNode) { final LinkDataMap callee = mapStatisticsCalleeDao.selectCallee(targetApplication, range); if (logger.isInfoEnabled()) { logger.debug("Found Callee. count={}, callee={}, depth={}", callee.size(), targetApplication, calleeDepth.getDepth()); } for (LinkData stat : callee.getLinkDataList()) { searchResult.addTargetLinkData(stat); final Application fromApplication = stat.getFromApplication(); addNextNode(fromApplication); } } } logger.debug("{} depth search end", callerDepth.getDepth()); return searchResult; } private void addNextNode(Application sourceApplication) { final boolean add = this.nextQueue.addNextNode(sourceApplication); if (!add) { logger.debug("already visited. nextNode:{}", sourceApplication); } } private boolean checkNextCaller(Application targetApplication, SearchDepth depth) { if (depth.isDepthOverflow()) { logger.debug("caller depth overflow application:{} depth:{}", targetApplication, depth.getDepth()); return false; } if (linkVisitChecker.visitCaller(targetApplication)) { logger.debug("already visited caller:{}", targetApplication); return false; } return filter(targetApplication); } private boolean filter(Application targetApplication) { if (serverMapDataFilter != null && serverMapDataFilter.filter(targetApplication)) { return false; } return true; } private boolean checkNextCallee(Application targetApplication, SearchDepth depth) { if (depth.isDepthOverflow()) { logger.debug("callee depth overflow application:{} depth:{}", targetApplication, depth.getDepth()); return false; } if (linkVisitChecker.visitCallee(targetApplication)) { logger.debug("already visited callee:{}", targetApplication); return false; } return filter(targetApplication); } private List<LinkData> checkRpcCallAccepted(LinkData linkData, Range range) { // replace if the rpc client's destination has an agent installed and thus has an application name final Application toApplication = linkData.getToApplication(); if (!toApplication.getServiceType().isRpcClient() && !toApplication.getServiceType().isQueue()) { return Collections.singletonList(linkData); } logger.debug("checkRpcCallAccepted(). Find applicationName:{} {}", toApplication, range); final Set<AcceptApplication> acceptApplicationList = findAcceptApplication(linkData.getFromApplication(), toApplication.getName(), range); logger.debug("find accept application:{}", acceptApplicationList); if (CollectionUtils.isNotEmpty(acceptApplicationList)) { if (acceptApplicationList.size() == 1) { logger.debug("Application info replaced. {} => {}", linkData, acceptApplicationList); AcceptApplication first = acceptApplicationList.iterator().next(); final LinkData acceptedLinkData = new LinkData(linkData.getFromApplication(), first.getApplication()); acceptedLinkData.setLinkCallDataMap(linkData.getLinkCallDataMap()); return Collections.singletonList(acceptedLinkData); } else { // special case - there are more than 2 nodes grouped by a single url return createVirtualLinkData(linkData, toApplication, acceptApplicationList); } } else { // for queues, accept application may not exist if no consumers have an agent installed if (toApplication.getServiceType().isQueue()) { return Collections.singletonList(linkData); } else { final Application unknown = new Application(toApplication.getName(), ServiceType.UNKNOWN); final LinkData unknownLinkData = new LinkData(linkData.getFromApplication(), unknown); unknownLinkData.setLinkCallDataMap(linkData.getLinkCallDataMap()); return Collections.singletonList(unknownLinkData); } } } private List<LinkData> createVirtualLinkData(LinkData linkData, Application toApplication, Set<AcceptApplication> acceptApplicationList) { logger.warn("one to N replaced. node:{}->host:{} accept:{}", linkData.getFromApplication(), toApplication.getName(), acceptApplicationList); List<LinkData> emulationLink = new ArrayList<>(); for (AcceptApplication acceptApplication : acceptApplicationList) { // linkCallData needs to be modified - remove callHistogram on purpose final LinkData acceptedLinkData = new LinkData(linkData.getFromApplication(), acceptApplication.getApplication()); acceptedLinkData.setLinkCallDataMap(linkData.getLinkCallDataMap()); emulationLink.add(acceptedLinkData); traceEmulationLink(acceptedLinkData); } return emulationLink; } private void traceEmulationLink(LinkData acceptApplication) { final boolean add = emulationLinkMarker.add(acceptApplication); if (!add) { logger.warn("emulationLink add error. {}", acceptApplication); } } private Set<AcceptApplication> findAcceptApplication(Application fromApplication, String host, Range range) { logger.debug("findAcceptApplication {} {}", fromApplication, host); final RpcApplication rpcApplication = new RpcApplication(host, fromApplication); final Set<AcceptApplication> hit = this.acceptApplicationLocalCache.get(rpcApplication); if (CollectionUtils.isNotEmpty(hit)) { logger.debug("acceptApplicationLocalCache hit {}", rpcApplication); return hit; } final Set<AcceptApplication> acceptApplicationSet= hostApplicationMapDao.findAcceptApplicationName(fromApplication, range); this.acceptApplicationLocalCache.put(rpcApplication, acceptApplicationSet); Set<AcceptApplication> acceptApplication = this.acceptApplicationLocalCache.get(rpcApplication); logger.debug("findAcceptApplication {}->{} result:{}", fromApplication, host, acceptApplication); return acceptApplication; } private void fillEmulationLink(LinkDataDuplexMap linkDataDuplexMap, Range range) { // TODO need to be reimplemented - virtual node creation logic needs an overhaul. // Currently, only the reversed relationship node is displayed. We need to create a virtual node and convert the rpc data appropriately. logger.debug("this.emulationLinkMarker:{}", this.emulationLinkMarker); List<LinkData> emulationLinkDataList = findEmulationLinkData(linkDataDuplexMap); for (LinkData emulationLinkData : emulationLinkDataList) { LinkCallDataMap beforeImage = emulationLinkData.getLinkCallDataMap(); logger.debug("beforeImage:{}", beforeImage); emulationLinkData.resetLinkData(); LinkKey findLinkKey = new LinkKey(emulationLinkData.getFromApplication(), emulationLinkData.getToApplication()); LinkData targetLinkData = linkDataDuplexMap.getTargetLinkData(findLinkKey); if (targetLinkData == null) { // This is a case where the emulation target node has been only "partially" visited, (ie. does not have a target link data) // Most likely due to the limit imposed by inbound search depth. // Must go fetch the target link data here. final Application targetApplication = emulationLinkData.getToApplication(); final LinkDataMap callee = mapStatisticsCalleeDao.selectCallee(targetApplication, range); targetLinkData = callee.getLinkData(findLinkKey); if (targetLinkData == null) { // There has been a case where targetLinkData was null, but exact event could not be captured for analysis. // Logging the case for further analysis should it happen again in the future. logger.error("targetLinkData not found findLinkKey:{}", findLinkKey); continue; } } // create reversed link data - convert data accepted by the target to target's call data LinkCallDataMap targetList = targetLinkData.getLinkCallDataMap(); Collection<LinkCallData> beforeLinkDataList = beforeImage.getLinkDataList(); LinkCallData beforeLinkCallData = beforeLinkDataList.iterator().next(); for (LinkCallData agentHistogram : targetList.getLinkDataList()) { Collection<TimeHistogram> timeHistogramList = agentHistogram.getTimeHistogram(); LinkCallDataMap linkCallDataMap = emulationLinkData.getLinkCallDataMap(); if (logger.isDebugEnabled()) { logger.debug("emulationLink BEFORE:{}", beforeLinkCallData); logger.debug("emulationLink agent:{}", agentHistogram); logger.debug("emulationLink link:{}/{} -> {}/{}", agentHistogram.getTarget(), agentHistogram.getTargetServiceType(), beforeLinkCallData.getTarget(), beforeLinkCallData.getTargetServiceType().getCode()); } linkCallDataMap.addCallData(agentHistogram.getTarget(), agentHistogram.getTargetServiceType(), beforeLinkCallData.getTarget(), beforeLinkCallData.getTargetServiceType(), timeHistogramList); } } } private List<LinkData> findEmulationLinkData(LinkDataDuplexMap linkDataDuplexMap) { // LinkDataDuplexMap already has a copy of the data - modifying emulationLinkMarker's data has no effect. // We must get the data from LinkDataDuplexMap again. List<LinkData> searchList = new ArrayList<>(); for (LinkData emulationLinkData : this.emulationLinkMarker) { LinkKey search = getLinkKey(emulationLinkData); for (LinkData linkData : linkDataDuplexMap.getSourceLinkDataList()) { LinkKey linkKey = getLinkKey(linkData); if (linkKey.equals(search)) { searchList.add(linkData); } } } return searchList; } private LinkKey getLinkKey(LinkData emulationLinkData) { Application fromApplication = emulationLinkData.getFromApplication(); Application toApplication = emulationLinkData.getToApplication(); return new LinkKey(fromApplication, toApplication); } public LinkDataDuplexMap select(Application sourceApplication, Range range, SearchOption searchOption) { if (searchOption == null) { throw new NullPointerException("searchOption must not be null"); } SearchDepth callerDepth = new SearchDepth(searchOption.getCallerSearchDepth()); SearchDepth calleeDepth = new SearchDepth(searchOption.getCalleeSearchDepth()); logger.debug("ApplicationMap select {}", sourceApplication); addNextNode(sourceApplication); LinkDataDuplexMap linkDataDuplexMap = new LinkDataDuplexMap(); while (!this.nextQueue.isEmpty()) { final List<Application> currentNode = this.nextQueue.copyAndClear(); logger.debug("size:{} depth caller:{} callee:{} node:{}", currentNode.size(), callerDepth.getDepth(), calleeDepth.getDepth(), currentNode); LinkDataDuplexMap levelData = selectLink(currentNode, range, callerDepth, calleeDepth); linkDataDuplexMap.addLinkDataDuplexMap(levelData); callerDepth = callerDepth.nextDepth(); calleeDepth = calleeDepth.nextDepth(); } if (!emulationLinkMarker.isEmpty()) { logger.debug("Link emulation size:{}", emulationLinkMarker.size()); // special case checkUnsearchEmulationCalleeNode(linkDataDuplexMap, range); fillEmulationLink(linkDataDuplexMap, range); } return linkDataDuplexMap; } private void checkUnsearchEmulationCalleeNode(LinkDataDuplexMap searchResult, Range range) { List<Application> unvisitedList = getUnvisitedEmulationNode(); if (unvisitedList.isEmpty()) { logger.debug("unvisited callee node not found"); return; } logger.info("unvisited callee node {}", unvisitedList); final LinkDataMap calleeLinkData = new LinkDataMap(); for (Application application : unvisitedList) { LinkDataMap callee = mapStatisticsCalleeDao.selectCallee(application, range); logger.debug("calleeNode:{}", callee); calleeLinkData.addLinkDataMap(callee); } LinkDataMap unvisitedNodeFilter = new LinkDataMap(); for (LinkData linkData : calleeLinkData.getLinkDataList()) { Application fromApplication = linkData.getFromApplication(); if (!fromApplication.getServiceType().isWas()) { continue; } Application emulatedApplication = linkData.getToApplication(); boolean unvisitedNode = isUnVisitedNode(unvisitedList, emulatedApplication, fromApplication); if (unvisitedNode) { logger.debug("EmulationCalleeNode:{}", linkData); unvisitedNodeFilter.addLinkData(linkData); } } logger.debug("UnVisitedNode:{}", unvisitedNodeFilter); for (LinkData linkData : unvisitedNodeFilter.getLinkDataList()) { searchResult.addTargetLinkData(linkData); } } private boolean isUnVisitedNode(List<Application> unvisitedList, Application toApplication, Application fromApplication) { for (Application unvisitedApplication : unvisitedList) { if (toApplication.equals(unvisitedApplication) && linkVisitChecker.isVisitedCaller(fromApplication)) { return true; } } return false; } private List<Application> getUnvisitedEmulationNode() { Set<Application> unvisitedList = new HashSet<>(); for (LinkData linkData : this.emulationLinkMarker) { Application toApplication = linkData.getToApplication(); boolean isVisited = this.linkVisitChecker.isVisitedCaller(toApplication); if (!isVisited) { unvisitedList.add(toApplication); } } return new ArrayList<>(unvisitedList); } private LinkDataMap replaceRpcCaller(LinkDataMap caller, Range range) { final LinkDataMap replaceRpcCaller = new LinkDataMap(); for (LinkData callerLink : caller.getLinkDataList()) { final List<LinkData> checkedLink = checkRpcCallAccepted(callerLink, range); for (LinkData linkData : checkedLink) { replaceRpcCaller.addLinkData(linkData); } } return replaceRpcCaller; } static class Queue { private final Set<Application> nextNode = new HashSet<>(); public boolean addNextNode(Application application) { return this.nextNode.add(application); } public List<Application> copyAndClear() { List<Application> copyList = new ArrayList<>(this.nextNode); this.nextNode.clear(); return copyList; } public boolean isEmpty() { return this.nextNode.isEmpty(); } } }