/* * 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.calltree.span; import java.util.*; import com.navercorp.pinpoint.common.server.bo.SpanBo; import com.navercorp.pinpoint.common.server.bo.SpanEventBo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author netspider * @author emeroad * @author jaehong.kim */ public class SpanAligner2 { private final Logger logger = LoggerFactory.getLogger(this.getClass()); // not matched public static final int FAIL_MATCH = 0; // transaction completed successfully public static final int BEST_MATCH = 1; // transaction in-flight or missing data public static final int START_TIME_MATCH = 2; private static final Long ROOT = -1L; private final Map<Long, List<SpanBo>> spanIdMap; private Long rootSpanId = null; private int matchType = FAIL_MATCH; public SpanAligner2(List<SpanBo> spans, long collectorAcceptTime) { this.spanIdMap = buildSpanMap(spans); this.rootSpanId = findRootSpanId(spans, collectorAcceptTime); } private long findRootSpanId(List<SpanBo> spanList, long collectorAcceptTime) { if (spanList == null) { throw new NullPointerException("spanList must not be null"); } final List<SpanBo> root = new ArrayList<>(); for (SpanBo span : spanList) { if (span.getParentSpanId() == ROOT) { root.add(span); } } // perfect match condition final int rootSpanBoSize = root.size(); if (rootSpanBoSize == 1) { final SpanBo spanBo = root.get(0); logger.debug("root span found. best match:{}", spanBo); matchType = BEST_MATCH; // XXX in case where root exist but no span queried. additional logic needed return spanBo.getSpanId(); } // XXX: a bug in the logic if rootspan is more than 2. should we display randomly? if (rootSpanBoSize > 1) { logger.warn("parentSpanId(-1) collision. size:{} root span:{} allSpan:{}", rootSpanBoSize, root, spanList); throw new IllegalStateException("parentSpanId(-1) collision. size:" + rootSpanBoSize); } // missing root or incomplete root (not arrived yet): meaning on-going process // next best thing is to lookup span based on the beginning of time of span it looked up // most likely data exist since the data gets extracted from span. non-existent data possible due to hbase insertion failure final List<SpanBo> collectorAcceptTimeMatcher = new ArrayList<>(); for (SpanBo span : spanList) { // collectorTime is a hint if (span.getCollectorAcceptTime() == collectorAcceptTime) { collectorAcceptTimeMatcher.add(span); } } // a match based on startTime. a more accurate matching is possible when additional information is given (see below) // which one of these leads to a best match? probably agentId. // "applicationName" : "/httpclient4/post.pinpoint", // "transactionId" : "emeroad-pc^1382955966412^16", // "agentId" : "emeroad-pc", // "applicationId" : "emeroad-app", // "callStackStart" : 1383024213315, // "callStackEnd" : 2010, final int startMatchSize = collectorAcceptTimeMatcher.size(); if (startMatchSize == 1) { final SpanBo spanBo = collectorAcceptTimeMatcher.get(0); logger.info("collectorAcceptTime span found startTime match:{}", spanBo); matchType = START_TIME_MATCH; return spanBo.getSpanId(); } if (startMatchSize > 1) { logger.warn("collectorAcceptTime match collision. size:{} collectorAcceptTime:{} allSpan:{}", startMatchSize, collectorAcceptTime, spanList); throw new IllegalStateException("startTime match collision size:" + startMatchSize + " collectorAcceptTime:" + collectorAcceptTime); } // can we do better? There doesn't seem to be a definitive answer for rendering the call stack logger.warn("collectorAcceptTime match not found. size:{} collectorAcceptTime:{} allSpan:{}", startMatchSize, collectorAcceptTime, spanList); throw new IllegalStateException("startTime match not found startTime size:" + startMatchSize + " collectorAcceptTime:" + collectorAcceptTime); } private Map<Long, List<SpanBo>> buildSpanMap(List<SpanBo> spans) { final Map<Long, List<SpanBo>> spanMap = new HashMap<>(); for (SpanBo span : spans) { final long spanId = span.getSpanId(); List<SpanBo> spanBoList = spanMap.get(spanId); if (spanBoList == null) { spanBoList = new ArrayList<>(); spanBoList.add(span); spanMap.put(spanId, spanBoList); } else { spanBoList.add(span); } } return spanMap; } public CallTree sort() { final List<SpanBo> rootList = spanIdMap.remove(rootSpanId); if (rootList == null || rootList.isEmpty()) { throw new IllegalStateException("rootList span not found. rootSpanId=" + rootSpanId + ", map=" + spanIdMap.keySet()); } if (rootList.size() > 1) { throw new IllegalStateException("duplicate rootList span found. rootSpanId=" + rootSpanId + ", map=" + spanIdMap.keySet()); } final SpanBo rootSpanBo = rootList.get(0); final CallTree tree = createSpanCallTree(rootSpanBo); tree.sort(); return tree; } public int getMatchType() { return matchType; } private void populateSubTree(final CallTree tree, final SpanBo span, final List<SpanEventBo> spanEventBoList, SpanAsyncEventMap asyncSpanEventMap) { if (spanEventBoList == null) { return; } for (SpanEventBo spanEventBo : spanEventBoList) { if (logger.isDebugEnabled()) { logger.debug("Align seq={}, depth={}, async={}, event={}", spanEventBo.getSequence(), spanEventBo.getDepth(), spanEventBo.isAsync(), spanEventBo); } final SpanAlign spanEventAlign = new SpanAlign(span, spanEventBo); try { tree.add(spanEventBo.getDepth(), spanEventAlign); } catch (CorruptedSpanCallTreeNodeException e) { logger.warn("Find corrupted span event.", e); CorruptedSpanAlignFactory factory = new CorruptedSpanAlignFactory(); final CallTree subTree = new SpanCallTree(factory.get(e.getTitle(), span, spanEventBo)); tree.add(subTree); return; } final long nextSpanId = spanEventBo.getNextSpanId(); final List<SpanBo> nextSpanBoList = spanIdMap.remove(nextSpanId); if (nextSpanId != ROOT && nextSpanBoList != null) { final SpanBo nextSpanBo = getNextSpan(span, spanEventBo, nextSpanBoList); if (nextSpanBo != null) { final CallTree subTree = createSpanCallTree(nextSpanBo); tree.add(subTree); } else { logger.debug("nextSpanId not found. {}", nextSpanId); } } final int nextAsyncId = spanEventBo.getNextAsyncId(); for (List<SpanEventBo> list : asyncSpanEventMap.get(nextAsyncId)) { final CallTree subTree = createAsyncSpanCallTree(span, list, asyncSpanEventMap); tree.add(subTree); } } } private CallTree createSpanCallTree(final SpanBo span) { logger.debug("Populate start. span={}", span); final List<SpanEventBo> spanEventBoList = span.getSpanEventBoList(); final SpanAlign spanAlign = new SpanAlign(span); final CallTree tree = new SpanCallTree(spanAlign); final SpanAsyncEventMap asyncSpanEventMap = extractAsyncSpanEvent(span.getSpanEventBoList()); populateSubTree(tree, span, spanEventBoList, asyncSpanEventMap); logger.debug("populate end. span={}", span); return tree; } private CallTree createAsyncSpanCallTree(final SpanBo span, final List<SpanEventBo> asyncSpanEventBoList, final SpanAsyncEventMap asyncSpanEventMap) { final SpanAlign spanAlign = new SpanAlign(span); final CallTree tree = new SpanAsyncCallTree(spanAlign); populateSubTree(tree, span, asyncSpanEventBoList, asyncSpanEventMap); return tree; } // fix nextSpan collision problem private SpanBo getNextSpan(SpanBo span, SpanEventBo beforeSpanEventBo, List<SpanBo> nextSpanBoList) { if (logger.isDebugEnabled()) { logger.debug("beforeSpanEvent:{}, nextSpanBoList:{}", beforeSpanEventBo, nextSpanBoList); } if (nextSpanBoList.size() == 1) { return nextSpanBoList.get(0); } else if (nextSpanBoList.size() > 1) { // attempt matching based on similarity // return spanBos.get(0); long spanEventBoStartTime = span.getStartTime() + beforeSpanEventBo.getStartElapsed(); SpanIdMatcher spanIdMatcher = new SpanIdMatcher(nextSpanBoList); // very susceptible to things like packet loss due to similarity match based on restricted set of data // TODO: need to find a better way to calc similarity based on entire data SpanBo matched = spanIdMatcher.approximateMatch(spanEventBoStartTime); if (matched == null) { // no matching span return null; } List<SpanBo> other = spanIdMatcher.other(); if (other != null) { spanIdMap.put(matched.getSpanId(), other); } return matched; } else { throw new IllegalStateException("error"); } } SpanAsyncEventMap extractAsyncSpanEvent(final List<SpanEventBo> spanEventBoList) { final SpanAsyncEventMap spanAsyncEventMap = new SpanAsyncEventMap(); if (spanEventBoList == null) { return spanAsyncEventMap; } final List<SpanEventBo> removeList = new ArrayList<>(); for (SpanEventBo spanEvent : spanEventBoList) { if (spanAsyncEventMap.add(spanEvent)) { removeList.add(spanEvent); } } spanAsyncEventMap.sort(); // clear spanEventBoList.removeAll(removeList); return spanAsyncEventMap; } }