diff --git a/src/main/java/com/nhn/pinpoint/web/calltree/span/SpanAligner2.java b/src/main/java/com/nhn/pinpoint/web/calltree/span/SpanAligner2.java index a0ab948e2..703de5b89 100644 --- a/src/main/java/com/nhn/pinpoint/web/calltree/span/SpanAligner2.java +++ b/src/main/java/com/nhn/pinpoint/web/calltree/span/SpanAligner2.java @@ -15,33 +15,88 @@ import org.slf4j.LoggerFactory; public class SpanAligner2 { private final Logger logger = LoggerFactory.getLogger(this.getClass()); + // 매치가 안됨. + public static final int FAIL_MATCH = 0; + // transaction이 완벽하게 끝남. + public static final int BEST_MATCH = 1; + // transaction이 진행중이거나. 일부 분실된 데이터가 있음. + public static final int START_TIME_MATCH = 2; + + private static final Integer ROOT = -1; private final Map spanMap; private Integer rootSpanId = null; + private int matchType = FAIL_MATCH; - public SpanAligner2(List spans) { - spanMap = new HashMap(spans.size()); + public SpanAligner2(List spans, long selectedSpanStartTime) { + this.spanMap = buildSpanMap(spans); + this.rootSpanId = findRootSpanId(spans, selectedSpanStartTime); + } - long rootSpanStartTime = Long.MAX_VALUE; + private int findRootSpanId(List spans, long selectedSpanStartTime) { + final List root = new ArrayList(); + for (SpanBo span : spans) { + if (span.getParentSpanId() == ROOT) { + root.add(span); + } + } + // 최상 조건의 best매치. 완벽 조건의 매치. + final int matchSize = root.size(); + if (matchSize == 1) { + final SpanBo spanBo = root.get(0); + logger.debug("root span found best match:{}", spanBo); + matchType = BEST_MATCH; + return spanBo.getSpanId(); + } + // 버그 rootspan이 2개 이상인 경우는 로직 버그이다. 아무거나 잡아서 데이터를 뿌려줘야 되나? + if (matchSize > 1) { + logger.warn("parentSpanId(-1) collision. size:{} root span:{} allSpan:{}", matchSize, root, spans); + throw new IllegalStateException("parentSpanId(-1) collision. size:" + matchSize); + } - for (SpanBo span : spans) { - if (spanMap.containsKey(span.getSpanId())) { - throw new IllegalStateException("duplicated spanId. id:" + span.getSpanId()); - } + // root 분실. 혹은 아직 도착하지 않아 root가 완성 되지 않음. 즉 진행중인 process일 수 있음. + // 차선책으로 자신이 조회한 span의 시작 시간을 기준으로 span을 조회한다. + // span에서 데이터를 추출하는 것이기 때문에, 왠간하면 데이터는 존재함. hbase insert시 data insert를 실패할 경우 없을수 있음. + final List startTimeMatcher = new ArrayList(); + for(SpanBo span : spans) { + // collectorTime이 힌트로 들어온다. + if (span.getCollectorAcceptTime() == selectedSpanStartTime) { + startTimeMatcher.add(span); + } + } + // startTime 기반 match. 아래 추가 정보가 제공 되면 더 정확하게 매치가 가능하다. + // 이중에서 어느 정보를 얻으면 가장 쉽고 정확하게 매치가 가능한가? agentId가 제일 무난하지 않나 함. + // "applicationName" : "/httpclient4/post.pinpoint", + // "transactionId" : "emeroad-pc^1382955966412^16", + // "agentId" : "emeroad-pc", + // "applicationId" : "emeroad-app", + // "callStackStart" : 1383024213315, + // "callStackEnd" : 2010, + final int startMatchSize = startTimeMatcher.size(); + if (startMatchSize == 1) { + final SpanBo spanBo = startTimeMatcher.get(0); + logger.info("startTime span found startTime match:{}", spanBo); + matchType = START_TIME_MATCH; + return spanBo.getSpanId(); + } + if (startMatchSize > 1) { + logger.warn("startTime match collision. size:{} selectedSpanStartTime:{} span:{} allSpan:{}", startMatchSize, selectedSpanStartTime, startMatchSize, spans); + throw new IllegalStateException("startTime match collision size:" + startMatchSize + " selectedSpanStartTime:" + selectedSpanStartTime); + } + // 여기서 다음상황으로 더 정확하게 매치가 가능한가? 마땅히 call stack을 랜더링 할수 있는 방법 없음 + logger.warn("startTime match not found. size:{} selectedSpanStartTime:{} span:{} allSpan:{}", startMatchSize, selectedSpanStartTime, startMatchSize, spans); + throw new IllegalStateException("startTime match not found startTime size:" + startMatchSize + " selectedSpanStartTime:" + selectedSpanStartTime); + } - if (span.getParentSpanId() == ROOT) { - rootSpanId = ROOT; - spanMap.put(ROOT, span); - continue; - } else if ((rootSpanId == null || !rootSpanId.equals(ROOT)) && span.getStartTime() < rootSpanStartTime) { - rootSpanId = (span.getParentSpanId() == ROOT) ? ROOT : span.getSpanId(); - rootSpanStartTime = span.getStartTime(); - } - spanMap.put(span.getSpanId(), span); - } - } + private Map buildSpanMap(List spans) { + Map spanMap = new HashMap(); + for (SpanBo span : spans) { + spanMap.put(span.getSpanId(), span); + } + return spanMap; + } - public List sort() { + public List sort() { List list = new ArrayList(); final SpanBo root = spanMap.get(rootSpanId); if (root == null) { @@ -53,7 +108,11 @@ public class SpanAligner2 { return list; } - private void populate(SpanBo span, int spanDepth, List container) { + public int getMatchType() { + return matchType; + } + + private void populate(SpanBo span, int spanDepth, List container) { logger.debug("populate start"); int currentDepth = spanDepth; if (logger.isDebugEnabled()) { diff --git a/src/main/java/com/nhn/pinpoint/web/controller/BusinessTransactionController.java b/src/main/java/com/nhn/pinpoint/web/controller/BusinessTransactionController.java index fe000677f..4c4d402ca 100644 --- a/src/main/java/com/nhn/pinpoint/web/controller/BusinessTransactionController.java +++ b/src/main/java/com/nhn/pinpoint/web/controller/BusinessTransactionController.java @@ -7,6 +7,7 @@ import java.util.Set; import javax.servlet.http.HttpServletResponse; +import com.nhn.pinpoint.web.service.SpanResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -47,7 +48,7 @@ public class BusinessTransactionController { @Autowired private FlowChartService flow; - /** + /** * applicationname에서 from ~ to 시간대에 수행된 URL을 조회한다. * * @param model @@ -97,69 +98,72 @@ public class BusinessTransactionController { return getBusinessTransactionsData(model, response, applicationName, from, to, filterText, limit); } - /** - * 선택한 하나의 Transaction 정보 조회. - * - * @param traceIdParam - * @param focusTimestamp - * @return - */ - @RequestMapping(value = "/transactionInfo", method = RequestMethod.GET) - public ModelAndView transactionInfo(@RequestParam("traceId") String traceIdParam, @RequestParam("focusTimestamp") long focusTimestamp, - // FIXME jsonResult는 UI 개발 편의를 위해 임시로 추가된 변수 임. 나중에 제거. - // 기존 html view에서 json을 넘어가는 중임. - @RequestParam(value="jsonResult", required=false, defaultValue="false") boolean jsonResult, - @RequestParam(value="v", required=false, defaultValue="0") int viewVersion) { - logger.debug("traceId:{}", traceIdParam); + /** + * 선택한 하나의 Transaction 정보 조회. + * + * @param traceIdParam + * @param focusTimestamp + * @return + */ + @RequestMapping(value = "/transactionInfo", method = RequestMethod.GET) + public ModelAndView transactionInfo(@RequestParam("traceId") String traceIdParam, @RequestParam("focusTimestamp") long focusTimestamp, + // FIXME jsonResult는 UI 개발 편의를 위해 임시로 추가된 변수 임. 나중에 제거. + // 기존 html view에서 json을 넘어가는 중임. + @RequestParam(value = "jsonResult", required = false, defaultValue = "false") boolean jsonResult, + @RequestParam(value = "v", required = false, defaultValue = "0") int viewVersion) { + logger.debug("traceId:{}", traceIdParam); - final TransactionId traceId = new TransactionId(traceIdParam); + final TransactionId traceId = new TransactionId(traceIdParam); - ModelAndView mv = new ModelAndView("transactionInfo"); + ModelAndView mv = new ModelAndView("transactionInfo"); - try { - // select spans - List spanAligns = this.spanService.selectSpan(traceId); + try { + // select spans + final SpanResult spanResult = this.spanService.selectSpan(traceId, focusTimestamp); + List spanAligns = spanResult.getSpanAlign(); - if (spanAligns.isEmpty()) { - mv.addObject("errorCode", 9); - mv.setViewName("error"); - return mv; - } + if (spanAligns.isEmpty()) { + mv.addObject("errorCode", 9); + mv.setViewName("error"); + return mv; + } - // debug - mv.addObject("spanList", spanAligns); + // debug + mv.addObject("spanList", spanAligns); - mv.addObject("traceId", traceId); + mv.addObject("traceId", traceId); - // call tree - ServerCallTree callTree = this.flow.selectServerCallTree(traceId); - mv.addObject("nodes", callTree.getNodes()); - mv.addObject("links", callTree.getLinks()); + // call tree + ServerCallTree callTree = this.flow.selectServerCallTree(traceId); + mv.addObject("nodes", callTree.getNodes()); + mv.addObject("links", callTree.getLinks()); - // call stacks - RecordSet recordSet = this.recordSetService.createRecordSet(spanAligns, focusTimestamp); - mv.addObject("recordSet", recordSet); + // call stacks + RecordSet recordSet = this.recordSetService.createRecordSet(spanAligns, focusTimestamp); + mv.addObject("recordSet", recordSet); - mv.addObject("applicationName", recordSet.getApplicationName()); - mv.addObject("callstack", recordSet.getRecordList()); - mv.addObject("timeline", recordSet.getRecordList()); - mv.addObject("callstackStart", recordSet.getStartTime()); - mv.addObject("callstackEnd", recordSet.getEndTime()); - } catch (Exception e) { - logger.warn("BusinessTransactionController Error Cause" + e.getMessage(), e); - // TODO 아무래도 다시 던져야 될듯한데. Exception처리 정책을 생각해봐야 한다. - // throw e; - } + mv.addObject("applicationName", recordSet.getApplicationName()); + mv.addObject("callstack", recordSet.getRecordList()); + mv.addObject("timeline", recordSet.getRecordList()); + mv.addObject("callstackStart", recordSet.getStartTime()); + mv.addObject("callstackEnd", recordSet.getEndTime()); + mv.addObject("completeState", spanResult.getCompleteTypeString()); + } catch (Exception e) { + logger.warn("BusinessTransactionController Error Cause" + e.getMessage(), e); + // TODO 아무래도 다시 던져야 될듯한데. Exception처리 정책을 생각해봐야 한다. + // throw e; + } + + // FIXME jsonResult는 UI 개발 편의를 위해 임시로 추가된 변수 임. 나중에 제거. + if (jsonResult) { + if (viewVersion == 2) { + mv.setViewName("transactionInfoJsonHash"); + } else { + mv.setViewName("transactionInfoJson"); + } + } + + return mv; + } - // FIXME jsonResult는 UI 개발 편의를 위해 임시로 추가된 변수 임. 나중에 제거. - if (jsonResult) { - if (viewVersion == 2) { - mv.setViewName("transactionInfoJsonHash"); - } else { - mv.setViewName("transactionInfoJson"); - } - } - - return mv; - } } diff --git a/src/main/java/com/nhn/pinpoint/web/service/SpanResult.java b/src/main/java/com/nhn/pinpoint/web/service/SpanResult.java new file mode 100644 index 000000000..555f3f3bb --- /dev/null +++ b/src/main/java/com/nhn/pinpoint/web/service/SpanResult.java @@ -0,0 +1,45 @@ +package com.nhn.pinpoint.web.service; + +import com.nhn.pinpoint.web.calltree.span.SpanAlign; +import com.nhn.pinpoint.web.calltree.span.SpanAligner2; + +import java.util.List; + +/** + * + */ +public class SpanResult { + private int completeType; + private List spanAlign; + private Object completeTypeString; + + public SpanResult(int completeType, List spanAlign) { + if (spanAlign == null) { + throw new NullPointerException("spanAlign must not be null"); + } + this.completeType = completeType; + this.spanAlign = spanAlign; + } + + + + public int getCompleteType() { + return completeType; + } + + public List getSpanAlign() { + return spanAlign; + } + + public String getCompleteTypeString() { + switch (completeType) { + case SpanAligner2.BEST_MATCH: + return "Complete"; + case SpanAligner2.START_TIME_MATCH: + return "Progress"; + case SpanAligner2.FAIL_MATCH: + return "Error"; + } + return "Error"; + } +} diff --git a/src/main/java/com/nhn/pinpoint/web/service/SpanService.java b/src/main/java/com/nhn/pinpoint/web/service/SpanService.java index 2a902f078..d47a104e3 100644 --- a/src/main/java/com/nhn/pinpoint/web/service/SpanService.java +++ b/src/main/java/com/nhn/pinpoint/web/service/SpanService.java @@ -9,5 +9,5 @@ import com.nhn.pinpoint.web.vo.TransactionId; * */ public interface SpanService { - List selectSpan(TransactionId transactionId); + SpanResult selectSpan(TransactionId transactionId, long selectedSpanHint); } diff --git a/src/main/java/com/nhn/pinpoint/web/service/SpanServiceImpl.java b/src/main/java/com/nhn/pinpoint/web/service/SpanServiceImpl.java index bdfb80276..32fd662a0 100644 --- a/src/main/java/com/nhn/pinpoint/web/service/SpanServiceImpl.java +++ b/src/main/java/com/nhn/pinpoint/web/service/SpanServiceImpl.java @@ -46,22 +46,22 @@ public class SpanServiceImpl implements SpanService { private OutputParameterParser outputParameterParser = new OutputParameterParser(); @Override - public List selectSpan(TransactionId transactionId) { + public SpanResult selectSpan(TransactionId transactionId, long selectedSpanHint) { List spans = traceDao.selectSpanAndAnnotation(transactionId); if (spans == null || spans.isEmpty()) { - return Collections.emptyList(); + return new SpanResult(SpanAligner2.FAIL_MATCH, Collections.emptyList()); } - List order = order(spans); + SpanResult result = order(spans, selectedSpanHint); + List order = result.getSpanAlign(); // transitionApiId(order); transitionDynamicApiId(order); transitionSqlId(order); transitionCachedString(order); transitionException(order); // TODO root span not found시 row data라도 보여줘야 됨. - - return order; + return result; } @@ -365,14 +365,16 @@ public class SpanServiceImpl implements SpanService { void replacement(SpanAlign spanAlign, List annotationBoList); } - private List order(List spans) { - SpanAligner2 spanAligner = new SpanAligner2(spans); + private SpanResult order(List spans, long selectedSpanHint) { + SpanAligner2 spanAligner = new SpanAligner2(spans, selectedSpanHint); List sort = spanAligner.sort(); + logger.trace("SpanAlignList:{}", sort); - return sort; + return new SpanResult(spanAligner.getMatchType(), sort); } + private static class AgentKey { private final String agentId; diff --git a/src/main/webapp/WEB-INF/views/transactionInfoJson.jsp b/src/main/webapp/WEB-INF/views/transactionInfoJson.jsp index 9bbeb9718..7ad8b984f 100644 --- a/src/main/webapp/WEB-INF/views/transactionInfoJson.jsp +++ b/src/main/webapp/WEB-INF/views/transactionInfoJson.jsp @@ -9,6 +9,7 @@ "applicationId" : "${recordSet.applicationId}", "callStackStart" : ${callstackStart}, "callStackEnd" : ${callstackEnd}, + "completeState" : "${completeState}", "callStackIndex" : { "depth":0, "begin":1, diff --git a/src/main/webapp/WEB-INF/views/transactionInfoJsonHash.jsp b/src/main/webapp/WEB-INF/views/transactionInfoJsonHash.jsp index 2ee73254f..7e88d351d 100644 --- a/src/main/webapp/WEB-INF/views/transactionInfoJsonHash.jsp +++ b/src/main/webapp/WEB-INF/views/transactionInfoJsonHash.jsp @@ -10,6 +10,7 @@ "callStackStart" : ${callstackStart}, "callStackEnd" : ${callstackEnd}, + "completeState" : "${completeState}", "callStack" : [ diff --git a/src/test/java/com/nhn/pinpoint/web/service/SpanServiceTest.java b/src/test/java/com/nhn/pinpoint/web/service/SpanServiceTest.java index 64a8dc05b..ac090ada1 100644 --- a/src/test/java/com/nhn/pinpoint/web/service/SpanServiceTest.java +++ b/src/test/java/com/nhn/pinpoint/web/service/SpanServiceTest.java @@ -4,7 +4,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; -import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; @@ -108,8 +107,9 @@ public class SpanServiceTest { private void doRead(TSpan span) { com.nhn.pinpoint.common.util.TransactionId id = TransactionIdUtils.parseTransactionId(span.getTransactionId()); TransactionId traceId = new TransactionId(id.getAgentId(), id.getAgentStartTime(), id.getTransactionSequence()); - - List sort = spanService.selectSpan(traceId); + // selectedHint를 좀더 정확히 수정할것. + SpanResult spanResult = spanService.selectSpan(traceId, System.currentTimeMillis()); + List sort = spanResult.getSpanAlign(); for (SpanAlign spanAlign : sort) { logger.info("depth:{} {}", spanAlign.getDepth(), spanAlign.getSpanBo()); } @@ -124,7 +124,6 @@ public class SpanServiceTest { private TSpan createRootSpan() { // 별도 생성기로 뽑을것. - UUID uuid = UUID.randomUUID(); List ano = Collections.emptyList(); long time = System.currentTimeMillis(); int andIncrement = id.getAndIncrement();