/* * 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 java.util.ArrayList; import java.util.List; import java.util.Objects; import com.navercorp.pinpoint.common.server.bo.AnnotationBo; import com.navercorp.pinpoint.common.server.bo.Event; import com.navercorp.pinpoint.common.server.bo.SpanBo; import com.navercorp.pinpoint.common.service.AnnotationKeyRegistryService; import com.navercorp.pinpoint.common.service.ServiceTypeRegistryService; import com.navercorp.pinpoint.common.trace.AnnotationKeyMatcher; import com.navercorp.pinpoint.common.trace.LoggingInfo; import com.navercorp.pinpoint.common.util.TransactionId; import com.navercorp.pinpoint.web.calltree.span.CallTreeIterator; import com.navercorp.pinpoint.web.calltree.span.CallTreeNode; import com.navercorp.pinpoint.web.calltree.span.SpanAlign; import com.navercorp.pinpoint.web.dao.TraceDao; import com.navercorp.pinpoint.web.filter.Filter; import com.navercorp.pinpoint.web.security.MetaDataFilter; import com.navercorp.pinpoint.web.security.MetaDataFilter.MetaData; import com.navercorp.pinpoint.web.vo.BusinessTransactions; import com.navercorp.pinpoint.web.vo.Range; import com.navercorp.pinpoint.web.vo.callstacks.Record; import com.navercorp.pinpoint.web.vo.callstacks.RecordFactory; import com.navercorp.pinpoint.web.vo.callstacks.RecordSet; import org.apache.commons.collections.CollectionUtils; 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; /** * * @author jaehong.kim */ @Service public class TransactionInfoServiceImpl implements TransactionInfoService { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired @Qualifier("hbaseTraceDaoFactory") private TraceDao traceDao; @Autowired private AnnotationKeyMatcherService annotationKeyMatcherService; @Autowired private ServiceTypeRegistryService registry; @Autowired private AnnotationKeyRegistryService annotationKeyRegistryService; @Autowired(required=false) private MetaDataFilter metaDataFilter; // Temporarily disabled Because We need to solve authentication problem inter system. // @Value("#{pinpointWebProps['log.enable'] ?: false}") // private boolean logLinkEnable; // @Value("#{pinpointWebProps['log.button.name'] ?: ''}") // private String logButtonName; // @Value("#{pinpointWebProps['log.page.url'] ?: ''}") // private String logPageUrl; @Override public BusinessTransactions selectBusinessTransactions(List<TransactionId> transactionIdList, String applicationName, Range range, Filter filter) { if (transactionIdList == null) { throw new NullPointerException("transactionIdList must not be null"); } if (applicationName == null) { throw new NullPointerException("applicationName must not be null"); } if (filter == null) { throw new NullPointerException("filter must not be null"); } if (range == null) { // TODO range is not used - check the logic again throw new NullPointerException("range must not be null"); } List<List<SpanBo>> traceList; if (filter == Filter.NONE) { traceList = this.traceDao.selectSpans(transactionIdList); } else { traceList = this.traceDao.selectAllSpans(transactionIdList); } BusinessTransactions businessTransactions = new BusinessTransactions(); for (List<SpanBo> trace : traceList) { if (!filter.include(trace)) { continue; } for (SpanBo spanBo : trace) { // show application's incoming requests if (applicationName.equals(spanBo.getApplicationId())) { businessTransactions.add(spanBo); } } } return businessTransactions; } @Override public RecordSet createRecordSet(CallTreeIterator callTreeIterator, long focusTimestamp, String agentId, long spanId) { if (callTreeIterator == null) { throw new NullPointerException("callTreeIterator must not be null"); } RecordSet recordSet = new RecordSet(); final List<SpanAlign> spanAlignList = callTreeIterator.values(); // finds and marks the viewPoint.base on focusTimestamp. // focusTimestamp is needed to determine which span to use as reference when there are more than 2 spans making up a transaction. // for cases where focus cannot be found due to an error, a separate marker is needed. // TODO potential error - because server time is used, there may be more than 2 focusTime due to differences in server times. SpanAlign viewPointSpanAlign = findViewPoint(spanAlignList, focusTimestamp, agentId, spanId); // FIXME patched temporarily for cases where focusTimeSpanBo is not found. Need a more complete solution. if (viewPointSpanAlign != null) { recordSet.setAgentId(viewPointSpanAlign.getAgentId()); recordSet.setApplicationId(viewPointSpanAlign.getApplicationId()); final String applicationName = getRpcArgument(viewPointSpanAlign); recordSet.setApplicationName(applicationName); } // find the startTime to use as reference long startTime = getStartTime(spanAlignList); recordSet.setStartTime(startTime); // find the endTime to use as reference long endTime = getEndTime(spanAlignList); recordSet.setEndTime(endTime); recordSet.setLoggingTransactionInfo(findIsLoggingTransactionInfo(spanAlignList)); final SpanAlignPopulate spanAlignPopulate = new SpanAlignPopulate(); List<Record> recordList = spanAlignPopulate.populateSpanRecord(callTreeIterator); logger.debug("RecordList:{}", recordList); if (viewPointSpanAlign != null) { // mark the record to be used as focus long beginTimeStamp = viewPointSpanAlign.getStartTime(); markFocusRecord(recordList, viewPointSpanAlign); recordSet.setBeginTimestamp(beginTimeStamp); } recordSet.setRecordList(recordList); return recordSet; } private boolean findIsLoggingTransactionInfo(List<SpanAlign> spanAlignList) { for (SpanAlign spanAlign : spanAlignList) { if (spanAlign.isSpan()) { if (spanAlign.getLoggingTransactionInfo() == LoggingInfo.LOGGED.getCode()) { return true; } } } return false; } private void markFocusRecord(List<Record> recordList, final SpanAlign viewPointTimeSpanAlign) { final String agentId = viewPointTimeSpanAlign.getAgentId(); for (Record record : recordList) { if (viewPointTimeSpanAlign.getSpanId() == record.getSpanId() && record.getBegin() == viewPointTimeSpanAlign.getStartTime()) { if (agentId == null) { if (record.getAgent() == null) { record.setFocused(true); break; } } else { if (record.getAgent() != null && agentId.equals(record.getAgent())) { record.setFocused(true); break; } } } } } // private void addlogLink(RecordSet recordSet) { // List<Record> records = recordSet.getRecordList(); // List<TransactionInfo> transactionInfoes = new LinkedList<TransactionInfo>(); // // for (Iterator<Record> iterator = records.iterator(); iterator.hasNext();) { // Record record = (Record) iterator.next(); // // if(record.getTransactionId() == null) { // continue; // } // // TransactionInfo transactionInfo = new TransactionInfo(record.getTransactionId(), record.getSpanId()); // // if (transactionInfoes.contains(transactionInfo)) { // continue; // }; // // record.setLogPageUrl(logPageUrl); // record.setLogButtonName(logButtonName); // // transactionInfoes.add(transactionInfo); // } // } private long getStartTime(List<SpanAlign> spanAlignList) { if (CollectionUtils.isEmpty(spanAlignList)) { return 0; } SpanAlign spanAlign = spanAlignList.get(0); return spanAlign.getStartTime(); } private long getEndTime(List<SpanAlign> spanAlignList) { if (CollectionUtils.isEmpty(spanAlignList)) { return 0; } SpanAlign spanAlign = spanAlignList.get(0); return spanAlign.getLastTime(); } private SpanAlign findViewPoint(List<SpanAlign> spanAlignList, long focusTimestamp, String agentId, long spanId) { SpanAlign firstSpan = null; for (SpanAlign spanAlign : spanAlignList) { if (spanAlign.isSpan()) { if (isViewPoint(spanAlign, focusTimestamp, agentId, spanId)) { return spanAlign; } if (firstSpan == null) { firstSpan = spanAlign; } } } // return firstSpan when focus Span could not be found. return firstSpan; } private boolean isViewPoint(final SpanAlign spanAlign, long focusTimestamp, String agentId, long spanId) { if (spanAlign.getCollectorAcceptTime() != focusTimestamp) { return false; } if (logger.isDebugEnabled()) { logger.debug("Matched focusTimestamp of view point. focusTimestamp={}, spanAlign={focusTimestamp={}, agentId={}, spanId={}}", focusTimestamp, spanAlign.getCollectorAcceptTime(), spanAlign.getAgentId(), spanAlign.getSpanId()); } // agentId if (agentId != null) { if (spanAlign.getAgentId() == null || !spanAlign.getAgentId().equals(agentId)) { return false; } if (logger.isDebugEnabled()) { logger.debug("Matched agentId of view point. agentId={}, spanAlign={focusTimestamp={}, agentId={}, spanId={}}", agentId, spanAlign.getCollectorAcceptTime(), spanAlign.getAgentId(), spanAlign.getSpanId()); } } // spanId if (spanId != -1) { if (spanAlign.getSpanId() != spanId) { return false; } if (logger.isDebugEnabled()) { logger.debug("Matched spanId of view point. spanId={}, spanAlign={focusTimestamp={}, agentId={}, spanId={}}", spanId, spanAlign.getCollectorAcceptTime(), spanAlign.getAgentId(), spanAlign.getSpanId()); } } return true; } private String getArgument(final SpanAlign spanAlign) { if (spanAlign.isSpan()) { return getRpcArgument(spanAlign); } return getDisplayArgument(spanAlign.getSpanEventBo()); } private String getRpcArgument(SpanAlign spanAlign) { SpanBo spanBo = spanAlign.getSpanBo(); String rpc = spanBo.getRpc(); if (rpc != null) { return rpc; } return getDisplayArgument(spanBo); } private String getDisplayArgument(Event event) { AnnotationBo displayArgument = getDisplayArgument0(event); if (displayArgument == null) { return ""; } return Objects.toString(displayArgument.getValue(), ""); } private AnnotationBo getDisplayArgument0(Event event) { // TODO needs a more generalized implementation for Arcus List<AnnotationBo> list = event.getAnnotationBoList(); if (list == null) { return null; } final AnnotationKeyMatcher matcher = annotationKeyMatcherService.findAnnotationKeyMatcher(event.getServiceType()); if (matcher == null) { return null; } for (AnnotationBo annotation : list) { int key = annotation.getKey(); if (matcher.matches(key)) { return annotation; } } return null; } private class SpanAlignPopulate { private List<Record> populateSpanRecord(CallTreeIterator callTreeIterator) { if (callTreeIterator == null) { throw new NullPointerException("callTreeIterator must not be null"); } final List<Record> recordList = new ArrayList<>(callTreeIterator.size() * 2); final RecordFactory factory = new RecordFactory(registry, annotationKeyRegistryService); // annotation id has nothing to do with spanAlign's seq and thus may be incremented as long as they don't overlap. while (callTreeIterator.hasNext()) { final CallTreeNode node = callTreeIterator.next(); if (node == null) { logger.warn("Corrupt CallTree found : {}", callTreeIterator.toString()); throw new IllegalStateException("CallTree corrupted"); } final SpanAlign align = node.getValue(); if (metaDataFilter != null && metaDataFilter.filter(align, MetaData.API)) { if (align.isSpan()) { Record record = metaDataFilter.createRecord(node, factory); recordList.add(record); } continue; } if (metaDataFilter != null && metaDataFilter.filter(align, MetaData.PARAM)) { metaDataFilter.replaceAnnotationBo(align, MetaData.PARAM); } final String argument = getArgument(align); final Record record = factory.get(node, argument); recordList.add(record); // add exception record. if (align.hasException()) { final Record exceptionRecord = factory.getException(record.getTab() + 1, record.getId(), align); if(exceptionRecord != null) { recordList.add(exceptionRecord); } } // add annotation record. if (!align.getAnnotationBoList().isEmpty()) { final List<Record> annotations = factory.getAnnotations(record.getTab() + 1, record.getId(), align); recordList.addAll(annotations); } // add remote record.(span only) if (align.getRemoteAddr() != null) { final Record remoteAddressRecord = factory.getParameter(record.getTab() + 1, record.getId(), "REMOTE_ADDRESS", align.getRemoteAddr()); recordList.add(remoteAddressRecord); } } return recordList; } } }