/*
* 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.google.common.collect.Lists;
import com.navercorp.pinpoint.common.server.bo.SpanBo;
import com.navercorp.pinpoint.common.server.bo.SpanEventBo;
import com.navercorp.pinpoint.common.service.ServiceTypeRegistryService;
import com.navercorp.pinpoint.common.trace.HistogramSchema;
import com.navercorp.pinpoint.common.trace.HistogramSlot;
import com.navercorp.pinpoint.common.trace.ServiceType;
import com.navercorp.pinpoint.common.util.TransactionId;
import com.navercorp.pinpoint.web.applicationmap.ApplicationMap;
import com.navercorp.pinpoint.web.applicationmap.ApplicationMapBuilder;
import com.navercorp.pinpoint.web.applicationmap.ApplicationMapWithScatterData;
import com.navercorp.pinpoint.web.applicationmap.ApplicationMapWithScatterScanResult;
import com.navercorp.pinpoint.web.applicationmap.rawdata.LinkDataDuplexMap;
import com.navercorp.pinpoint.web.applicationmap.rawdata.LinkDataMap;
import com.navercorp.pinpoint.web.dao.ApplicationTraceIndexDao;
import com.navercorp.pinpoint.web.dao.TraceDao;
import com.navercorp.pinpoint.web.filter.Filter;
import com.navercorp.pinpoint.web.security.ServerMapDataFilter;
import com.navercorp.pinpoint.web.util.TimeWindow;
import com.navercorp.pinpoint.web.util.TimeWindowDownSampler;
import com.navercorp.pinpoint.web.vo.Application;
import com.navercorp.pinpoint.web.vo.LimitedScanResult;
import com.navercorp.pinpoint.web.vo.LoadFactor;
import com.navercorp.pinpoint.web.vo.Range;
import com.navercorp.pinpoint.web.vo.ResponseHistogramBuilder;
import com.navercorp.pinpoint.web.vo.SelectedScatterArea;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.util.StopWatch;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author netspider
* @author emeroad
* @author minwoo.jung
*/
@Service
public class FilteredMapServiceImpl implements FilteredMapService {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private AgentInfoService agentInfoService;
@Autowired
@Qualifier("hbaseTraceDaoFactory")
private TraceDao traceDao;
@Autowired
private ApplicationTraceIndexDao applicationTraceIndexDao;
@Autowired
private ServiceTypeRegistryService registry;
@Autowired
private ApplicationFactory applicationFactory;
@Autowired(required=false)
private ServerMapDataFilter serverMapDataFilter;
private static final Object V = new Object();
@Override
public LimitedScanResult<List<TransactionId>> selectTraceIdsFromApplicationTraceIndex(String applicationName, Range range, int limit) {
return selectTraceIdsFromApplicationTraceIndex(applicationName, range, limit, true);
}
@Override
public LimitedScanResult<List<TransactionId>> selectTraceIdsFromApplicationTraceIndex(String applicationName, Range range, int limit, boolean backwardDirection) {
if (applicationName == null) {
throw new NullPointerException("applicationName must not be null");
}
if (range == null) {
throw new NullPointerException("range must not be null");
}
if (logger.isTraceEnabled()) {
logger.trace("scan(selectTraceIdsFromApplicationTraceIndex) {}, {}", applicationName, range);
}
return this.applicationTraceIndexDao.scanTraceIndex(applicationName, range, limit, backwardDirection);
}
@Override
public LimitedScanResult<List<TransactionId>> selectTraceIdsFromApplicationTraceIndex(String applicationName, SelectedScatterArea area, int limit) {
if (applicationName == null) {
throw new NullPointerException("applicationName must not be null");
}
if (area == null) {
throw new NullPointerException("area must not be null");
}
if (logger.isTraceEnabled()) {
logger.trace("scan(selectTraceIdsFromApplicationTraceIndex) {}, {}", applicationName, area);
}
return this.applicationTraceIndexDao.scanTraceIndex(applicationName, area, limit);
}
@Override
@Deprecated
public LoadFactor linkStatistics(Range range, List<TransactionId> traceIdSet, Application sourceApplication, Application destinationApplication, Filter filter) {
if (sourceApplication == null) {
throw new NullPointerException("sourceApplication must not be null");
}
if (destinationApplication == null) {
throw new NullPointerException("destApplicationName must not be null");
}
if (filter == null) {
throw new NullPointerException("filter must not be null");
}
StopWatch watch = new StopWatch();
watch.start();
List<List<SpanBo>> originalList = this.traceDao.selectAllSpans(traceIdSet);
List<SpanBo> filteredTransactionList = filterList(originalList, filter);
LoadFactor statistics = new LoadFactor(range);
// TODO need to handle these separately by node type (like fromToFilter)
// scan transaction list
for (SpanBo span : filteredTransactionList) {
if (sourceApplication.equals(span.getApplicationId(), registry.findServiceType(span.getApplicationServiceType()))) {
List<SpanEventBo> spanEventBoList = span.getSpanEventBoList();
if (spanEventBoList == null) {
continue;
}
// find dest elapsed time
for (SpanEventBo spanEventBo : spanEventBoList) {
if (destinationApplication.equals(spanEventBo.getDestinationId(), registry.findServiceType(spanEventBo.getServiceType()))) {
// find exception
boolean hasException = spanEventBo.hasException();
// add sample
// TODO : need timeslot value instead of the actual value
statistics.addSample(span.getStartTime() + spanEventBo.getStartElapsed(), spanEventBo.getEndElapsed(), 1, hasException);
break;
}
}
}
}
watch.stop();
logger.info("Fetch link statistics elapsed. {}ms", watch.getLastTaskTimeMillis());
return statistics;
}
private List<SpanBo> filterList(List<List<SpanBo>> transactionList, Filter filter) {
final List<SpanBo> filteredResult = new ArrayList<>();
for (List<SpanBo> transaction : transactionList) {
if (filter.include(transaction)) {
filteredResult.addAll(transaction);
}
}
return filteredResult;
}
private List<List<SpanBo>> filterList2(List<List<SpanBo>> transactionList, Filter filter) {
final List<List<SpanBo>> filteredResult = new ArrayList<>();
for (List<SpanBo> transaction : transactionList) {
if (filter.include(transaction)) {
filteredResult.add(transaction);
}
}
return filteredResult;
}
@Override
public ApplicationMap selectApplicationMap(TransactionId transactionId) {
if (transactionId == null) {
throw new NullPointerException("transactionId must not be null");
}
List<TransactionId> transactionIdList = new ArrayList<>();
transactionIdList.add(transactionId);
// FIXME from,to -1
Range range = new Range(-1, -1);
return selectApplicationMap(transactionIdList, range, range, Filter.NONE);
}
/**
* filtered application map
*/
@Override
public ApplicationMap selectApplicationMap(List<TransactionId> transactionIdList, Range originalRange, Range scanRange, Filter filter) {
if (transactionIdList == null) {
throw new NullPointerException("transactionIdList must not be null");
}
if (filter == null) {
throw new NullPointerException("filter must not be null");
}
StopWatch watch = new StopWatch();
watch.start();
final List<List<SpanBo>> filterList = selectFilteredSpan(transactionIdList, filter);
DotExtractor dotExtractor = createDotExtractor(scanRange, filterList);
ApplicationMap map = createMap(originalRange, scanRange, filterList);
ApplicationMapWithScatterScanResult applicationMapWithScatterScanResult = new ApplicationMapWithScatterScanResult(map, dotExtractor.getApplicationScatterScanResult());
watch.stop();
logger.debug("Select filtered application map elapsed. {}ms", watch.getTotalTimeMillis());
return applicationMapWithScatterScanResult;
}
@Override
public ApplicationMap selectApplicationMapWithScatterData(List<TransactionId> transactionIdList, Range originalRange, Range scanRange, int xGroupUnit, int yGroupUnit, Filter filter) {
if (transactionIdList == null) {
throw new NullPointerException("transactionIdList must not be null");
}
if (filter == null) {
throw new NullPointerException("filter must not be null");
}
StopWatch watch = new StopWatch();
watch.start();
final List<List<SpanBo>> filterList = selectFilteredSpan(transactionIdList, filter);
DotExtractor dotExtractor = createDotExtractor(scanRange, filterList);
ApplicationMap map = createMap(originalRange, scanRange, filterList);
ApplicationMapWithScatterData applicationMapWithScatterData = new ApplicationMapWithScatterData(map, dotExtractor.getApplicationScatterData(originalRange.getFrom(), originalRange.getTo(), xGroupUnit, yGroupUnit));
watch.stop();
logger.debug("Select filtered application map elapsed. {}ms", watch.getTotalTimeMillis());
return applicationMapWithScatterData;
}
private List<List<SpanBo>> selectFilteredSpan(List<TransactionId> transactionIdList, Filter filter) {
// filters out recursive calls by looking at each objects
// do not filter here if we change to a tree-based collision check in the future.
final List<TransactionId> recursiveFilterList = recursiveCallFilter(transactionIdList);
// FIXME might be better to simply traverse the List<Span> and create a process chain for execution
final List<List<SpanBo>> originalList = this.traceDao.selectAllSpans(recursiveFilterList);
return filterList2(originalList, filter);
}
private DotExtractor createDotExtractor(Range scanRange, List<List<SpanBo>> filterList) {
final DotExtractor dotExtractor = new DotExtractor(scanRange, applicationFactory);
for (List<SpanBo> transaction : filterList) {
for (SpanBo span : transaction) {
final Application spanApplication = this.applicationFactory.createApplication(span.getApplicationId(), span.getApplicationServiceType());
if (!spanApplication.getServiceType().isRecordStatistics() || spanApplication.getServiceType().isRpcClient()) {
continue;
}
dotExtractor.addDot(span);
}
}
return dotExtractor;
}
private ApplicationMap createMap(Range range, Range scanRange, List<List<SpanBo>> filterList) {
// TODO inject TimeWindow from elsewhere
final TimeWindow window = new TimeWindow(range, TimeWindowDownSampler.SAMPLER);
final LinkDataDuplexMap linkDataDuplexMap = new LinkDataDuplexMap();
final ResponseHistogramBuilder mapHistogramSummary = new ResponseHistogramBuilder(range);
/*
* Convert to statistical data
*/
for (List<SpanBo> transaction : filterList) {
final Map<Long, SpanBo> transactionSpanMap = checkDuplicatedSpanId(transaction);
for (SpanBo span : transaction) {
final Application parentApplication = createParentApplication(span, transactionSpanMap);
final Application spanApplication = this.applicationFactory.createApplication(span.getApplicationId(), span.getApplicationServiceType());
// records the Span's response time statistics
recordSpanResponseTime(spanApplication, span, mapHistogramSummary, span.getCollectorAcceptTime());
if (!spanApplication.getServiceType().isRecordStatistics() || spanApplication.getServiceType().isRpcClient()) {
// span's serviceType is probably not set correctly
logger.warn("invalid span application:{}", spanApplication);
continue;
}
final short slotTime = getHistogramSlotTime(span, spanApplication.getServiceType());
// might need to reconsider using collector's accept time for link statistics.
// we need to convert to time window's timestamp. If not, it may lead to OOM due to mismatch in timeslots.
long timestamp = window.refineTimestamp(span.getCollectorAcceptTime());
if (parentApplication.getServiceType() == ServiceType.USER) {
// Outbound data
if (logger.isTraceEnabled()) {
logger.trace("span user:{} {} -> span:{} {}", parentApplication, span.getAgentId(), spanApplication, span.getAgentId());
}
final LinkDataMap sourceLinkData = linkDataDuplexMap.getSourceLinkDataMap();
sourceLinkData.addLinkData(parentApplication, span.getAgentId(), spanApplication, span.getAgentId(), timestamp, slotTime, 1);
if (logger.isTraceEnabled()) {
logger.trace("span target user:{} {} -> span:{} {}", parentApplication, span.getAgentId(), spanApplication, span.getAgentId());
}
// Inbound data
final LinkDataMap targetLinkDataMap = linkDataDuplexMap.getTargetLinkDataMap();
targetLinkDataMap.addLinkData(parentApplication, span.getAgentId(), spanApplication, span.getAgentId(), timestamp, slotTime, 1);
} else {
// Inbound data
if (logger.isTraceEnabled()) {
logger.trace("span target parent:{} {} -> span:{} {}", parentApplication, span.getAgentId(), spanApplication, span.getAgentId());
}
final LinkDataMap targetLinkDataMap = linkDataDuplexMap.getTargetLinkDataMap();
targetLinkDataMap.addLinkData(parentApplication, span.getAgentId(), spanApplication, span.getAgentId(), timestamp, slotTime, 1);
}
if (serverMapDataFilter != null && serverMapDataFilter.filter(spanApplication)) {
continue;
}
addNodeFromSpanEvent(span, window, linkDataDuplexMap, transactionSpanMap);
}
}
ApplicationMapBuilder applicationMapBuilder = new ApplicationMapBuilder(range);
mapHistogramSummary.build();
ApplicationMap map = applicationMapBuilder.build(linkDataDuplexMap, agentInfoService, mapHistogramSummary);
if(serverMapDataFilter != null) {
map = serverMapDataFilter.dataFiltering(map);
}
return map;
}
private Map<Long, SpanBo> checkDuplicatedSpanId(List<SpanBo> transaction) {
final Map<Long, SpanBo> transactionSpanMap = new HashMap<>();
for (SpanBo span : transaction) {
final SpanBo old = transactionSpanMap.put(span.getSpanId(), span);
if (old != null) {
logger.warn("duplicated span found:{}", old);
}
}
return transactionSpanMap;
}
private void recordSpanResponseTime(Application application, SpanBo span, ResponseHistogramBuilder responseHistogramBuilder, long timeStamp) {
responseHistogramBuilder.addHistogram(application, span, timeStamp);
}
private void addNodeFromSpanEvent(SpanBo span, TimeWindow window, LinkDataDuplexMap linkDataDuplexMap, Map<Long, SpanBo> transactionSpanMap) {
/*
* add span event statistics
*/
final List<SpanEventBo> spanEventBoList = span.getSpanEventBoList();
if (CollectionUtils.isEmpty(spanEventBoList)) {
return;
}
final Application srcApplication = applicationFactory.createApplication(span.getApplicationId(), span.getApplicationServiceType());
LinkDataMap sourceLinkDataMap = linkDataDuplexMap.getSourceLinkDataMap();
for (SpanEventBo spanEvent : spanEventBoList) {
ServiceType destServiceType = registry.findServiceType(spanEvent.getServiceType());
if (!destServiceType.isRecordStatistics()) {
// internal method
continue;
}
// convert to Unknown if destServiceType is a rpc client and there is no acceptor.
// acceptor exists if there is a span with spanId identical to the current spanEvent's next spanId.
// logic for checking acceptor
if (destServiceType.isRpcClient()) {
if (!transactionSpanMap.containsKey(spanEvent.getNextSpanId())) {
destServiceType = ServiceType.UNKNOWN;
}
}
String dest = spanEvent.getDestinationId();
if (dest == null) {
dest = "Unknown";
}
final Application destApplication = this.applicationFactory.createApplication(dest, destServiceType);
final short slotTime = getHistogramSlotTime(spanEvent, destServiceType);
// FIXME
final long spanEventTimeStamp = window.refineTimestamp(span.getStartTime() + spanEvent.getStartElapsed());
if (logger.isTraceEnabled()) {
logger.trace("spanEvent src:{} {} -> dest:{} {}", srcApplication, span.getAgentId(), destApplication, spanEvent.getEndPoint());
}
// endPoint may be null
final String destinationAgentId = StringUtils.defaultString(spanEvent.getEndPoint());
sourceLinkDataMap.addLinkData(srcApplication, span.getAgentId(), destApplication, destinationAgentId, spanEventTimeStamp, slotTime, 1);
}
}
private Application createParentApplication(SpanBo span, Map<Long, SpanBo> transactionSpanMap) {
final SpanBo parentSpan = transactionSpanMap.get(span.getParentSpanId());
if (span.isRoot() || parentSpan == null) {
ServiceType spanServiceType = this.registry.findServiceType(span.getServiceType());
if (spanServiceType.isQueue()) {
String applicationName = span.getAcceptorHost();
ServiceType serviceType = spanServiceType;
return this.applicationFactory.createApplication(applicationName, serviceType);
} else {
String applicationName = span.getApplicationId();
ServiceType serviceType = ServiceType.USER;
return this.applicationFactory.createApplication(applicationName, serviceType);
}
} else {
// create virtual queue node if current' span's service type is a queue AND :
// 1. parent node's application service type is not a queue (it may have come from a queue that is traced)
// 2. current node's application service type is not a queue (current node may be a queue that is traced)
ServiceType spanServiceType = this.registry.findServiceType(span.getServiceType());
if (spanServiceType.isQueue()) {
ServiceType parentApplicationServiceType = this.registry.findServiceType(parentSpan.getApplicationServiceType());
ServiceType spanApplicationServiceType = this.registry.findServiceType(span.getApplicationServiceType());
if (!parentApplicationServiceType.isQueue() && !spanApplicationServiceType.isQueue()) {
String parentApplicationName = span.getAcceptorHost();
if (parentApplicationName == null) {
parentApplicationName = span.getRemoteAddr();
}
short parentServiceType = span.getServiceType();
return this.applicationFactory.createApplication(parentApplicationName, parentServiceType);
}
}
String parentApplicationName = parentSpan.getApplicationId();
short parentServiceType = parentSpan.getApplicationServiceType();
return this.applicationFactory.createApplication(parentApplicationName, parentServiceType);
}
}
private short getHistogramSlotTime(SpanEventBo spanEvent, ServiceType serviceType) {
return getHistogramSlotTime(spanEvent.hasException(), spanEvent.getEndElapsed(), serviceType);
}
private short getHistogramSlotTime(SpanBo span, ServiceType serviceType) {
boolean allException = span.getErrCode() != 0;
return getHistogramSlotTime(allException, span.getElapsed(), serviceType);
}
private short getHistogramSlotTime(boolean hasException, int elapsedTime, ServiceType serviceType) {
final HistogramSchema schema = serviceType.getHistogramSchema();
final HistogramSlot histogramSlot = schema.findHistogramSlot(elapsedTime, hasException);
return histogramSlot.getSlotTime();
}
private List<TransactionId> recursiveCallFilter(List<TransactionId> transactionIdList) {
if (transactionIdList == null) {
throw new NullPointerException("transactionIdList must not be null");
}
List<TransactionId> crashKey = new ArrayList<>();
Map<TransactionId, Object> filterMap = new LinkedHashMap<>(transactionIdList.size());
for (TransactionId transactionId : transactionIdList) {
Object old = filterMap.put(transactionId, V);
if (old != null) {
crashKey.add(transactionId);
}
}
if (!crashKey.isEmpty()) {
Set<TransactionId> filteredTransactionId = filterMap.keySet();
logger.info("transactionId crash found. original:{} filter:{} crashKey:{}", transactionIdList.size(), filteredTransactionId.size(), crashKey);
return Lists.newArrayList(filteredTransactionId);
}
return transactionIdList;
}
}