/*
* 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.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.*;
/**
* @author emeroad
*/
@Deprecated
public class DFSLinkSelector 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<>();
public DFSLinkSelector(MapStatisticsCalleeDao mapStatisticsCalleeDao, MapStatisticsCallerDao mapStatisticsCallerDao, HostApplicationMapDao hostApplicationMapDao) {
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;
}
/**
* Queries for all applications(callee) called by the callerApplication
*
* @param callerApplication
* @param range
* @return
*/
private LinkDataDuplexMap selectCaller(Application callerApplication, Range range, SearchDepth searchDepth) {
// skip if the callerApplication has already been checked
if (linkVisitChecker.visitCaller(callerApplication)) {
return new LinkDataDuplexMap();
}
LinkDataMap caller = mapStatisticsCallerDao.selectCaller(callerApplication, range);
if (logger.isDebugEnabled()) {
logger.debug("Found Caller. count={}, caller={}", caller.size(), callerApplication);
}
final LinkDataMap replaceRpcCaller = new LinkDataMap();
for (LinkData callerLink : caller.getLinkDataList()) {
final List<LinkData> checkedLink = checkRpcCallAccepted(callerLink, range);
for (LinkData linkData : checkedLink) {
replaceRpcCaller.addLinkData(linkData);
}
}
final LinkDataDuplexMap resultCaller = new LinkDataDuplexMap();
for (LinkData link : replaceRpcCaller.getLinkDataList()) {
resultCaller.addSourceLinkData(link);
final Application toApplication = link.getToApplication();
// skip if toApplication is a terminal or an unknown cloud
if (toApplication.getServiceType().isTerminal() || toApplication.getServiceType().isUnknown()) {
continue;
}
// search depth check
final SearchDepth nextLevel = searchDepth.nextDepth();
if (nextLevel.isDepthOverflow()) {
continue;
}
logger.debug(" Find subCaller of {}", toApplication);
LinkDataDuplexMap callerSub = selectCaller(toApplication, range, nextLevel);
logger.debug(" Found subCaller. count={}, caller={}", callerSub.size(), toApplication);
resultCaller.addLinkDataDuplexMap(callerSub);
// find all callers of queried subCallers as well
for (LinkData eachCaller : callerSub.getSourceLinkDataList()) {
logger.debug(" Find callee of {}", eachCaller.getFromApplication());
LinkDataDuplexMap calleeSub = selectCallee(eachCaller.getFromApplication(), range, nextLevel);
logger.debug(" Found subCallee. count={}, callee={}", calleeSub.size(), eachCaller.getFromApplication());
resultCaller.addLinkDataDuplexMap(calleeSub);
}
}
return resultCaller;
}
/**
* Queries for all applications(caller) that called calleeApplication
*
* @param calleeApplication
* @param range
* @return
*/
private LinkDataDuplexMap selectCallee(Application calleeApplication, Range range, SearchDepth searchDepth) {
// skip if the calleeApplication has already been checked
if (linkVisitChecker.visitCallee(calleeApplication)) {
return new LinkDataDuplexMap();
}
final LinkDataMap callee = mapStatisticsCalleeDao.selectCallee(calleeApplication, range);
logger.debug("Found Callee. count={}, callee={}", callee.size(), calleeApplication);
final LinkDataDuplexMap calleeSet = new LinkDataDuplexMap();
for (LinkData stat : callee.getLinkDataList()) {
calleeSet.addTargetLinkData(stat);
// search depth check
final SearchDepth nextLevel = searchDepth.nextDepth();
if (nextLevel.isDepthOverflow()) {
continue;
}
// need to find the applications that called me
LinkDataDuplexMap calleeSub = selectCallee(stat.getFromApplication(), range, nextLevel);
calleeSet.addLinkDataDuplexMap(calleeSub);
// find all callees of queried subCallees as well
for (LinkData eachCallee : calleeSub.getTargetLinkDataList()) {
// skip if terminal node
final Application eachCalleeToApplication = eachCallee.getToApplication();
if (eachCalleeToApplication.getServiceType().isTerminal() || eachCalleeToApplication.getServiceType().isUnknown()) {
continue;
}
LinkDataDuplexMap callerSub = selectCaller(eachCalleeToApplication, range, nextLevel);
calleeSet.addLinkDataDuplexMap(callerSub);
}
}
return calleeSet;
}
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()) {
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 {
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("ono 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) {
// 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) {
// 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);
}
@Override
public LinkDataDuplexMap select(Application sourceApplication, Range range, SearchOption searchOption) {
if (searchOption == null) {
throw new NullPointerException("searchOption must not be null");
}
final SearchDepth callerLevel = new SearchDepth(searchOption.getCallerSearchDepth());
LinkDataDuplexMap caller = selectCaller(sourceApplication, range, callerLevel);
logger.debug("Result of finding caller {}", caller);
final SearchDepth calleeLevel = new SearchDepth(searchOption.getCalleeSearchDepth());
LinkDataDuplexMap callee = selectCallee(sourceApplication, range, calleeLevel);
logger.debug("Result of finding callee {}", callee);
LinkDataDuplexMap linkDataDuplexMap = new LinkDataDuplexMap();
linkDataDuplexMap.addLinkDataDuplexMap(caller);
linkDataDuplexMap.addLinkDataDuplexMap(callee);
fillEmulationLink(linkDataDuplexMap);
return linkDataDuplexMap;
}
}