mirror of
https://github.com/wahyd4/pinpoint.git
synced 2026-08-17 08:46:22 +10:00
[강운덕] [LUCYSUS-1744] 한개의 트랜잭션이 진행중인 상태일 경우 개별 transaction을 조회 했을때 부분 조회가 정확하게 동작하도록 개선함.
git-svn-id: http://svn.bds.nhncorp.com/pe/hippo-web/trunk@2647 84d0f5b1-2673-498c-a247-62c4ff18d310
This commit is contained in:
@@ -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<Integer, SpanBo> spanMap;
|
||||
private Integer rootSpanId = null;
|
||||
private int matchType = FAIL_MATCH;
|
||||
|
||||
public SpanAligner2(List<SpanBo> spans) {
|
||||
spanMap = new HashMap<Integer, SpanBo>(spans.size());
|
||||
public SpanAligner2(List<SpanBo> spans, long selectedSpanStartTime) {
|
||||
this.spanMap = buildSpanMap(spans);
|
||||
this.rootSpanId = findRootSpanId(spans, selectedSpanStartTime);
|
||||
}
|
||||
|
||||
long rootSpanStartTime = Long.MAX_VALUE;
|
||||
private int findRootSpanId(List<SpanBo> spans, long selectedSpanStartTime) {
|
||||
final List<SpanBo> root = new ArrayList<SpanBo>();
|
||||
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<SpanBo> startTimeMatcher = new ArrayList<SpanBo>();
|
||||
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<Integer, SpanBo> buildSpanMap(List<SpanBo> spans) {
|
||||
Map<Integer, SpanBo> spanMap = new HashMap<Integer, SpanBo>();
|
||||
for (SpanBo span : spans) {
|
||||
spanMap.put(span.getSpanId(), span);
|
||||
}
|
||||
return spanMap;
|
||||
}
|
||||
|
||||
public List<SpanAlign> sort() {
|
||||
public List<SpanAlign> sort() {
|
||||
List<SpanAlign> list = new ArrayList<SpanAlign>();
|
||||
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<SpanAlign> container) {
|
||||
public int getMatchType() {
|
||||
return matchType;
|
||||
}
|
||||
|
||||
private void populate(SpanBo span, int spanDepth, List<SpanAlign> container) {
|
||||
logger.debug("populate start");
|
||||
int currentDepth = spanDepth;
|
||||
if (logger.isDebugEnabled()) {
|
||||
|
||||
@@ -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<SpanAlign> spanAligns = this.spanService.selectSpan(traceId);
|
||||
try {
|
||||
// select spans
|
||||
final SpanResult spanResult = this.spanService.selectSpan(traceId, focusTimestamp);
|
||||
List<SpanAlign> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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> spanAlign;
|
||||
private Object completeTypeString;
|
||||
|
||||
public SpanResult(int completeType, List<SpanAlign> 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<SpanAlign> 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";
|
||||
}
|
||||
}
|
||||
@@ -9,5 +9,5 @@ import com.nhn.pinpoint.web.vo.TransactionId;
|
||||
*
|
||||
*/
|
||||
public interface SpanService {
|
||||
List<SpanAlign> selectSpan(TransactionId transactionId);
|
||||
SpanResult selectSpan(TransactionId transactionId, long selectedSpanHint);
|
||||
}
|
||||
|
||||
@@ -46,22 +46,22 @@ public class SpanServiceImpl implements SpanService {
|
||||
private OutputParameterParser outputParameterParser = new OutputParameterParser();
|
||||
|
||||
@Override
|
||||
public List<SpanAlign> selectSpan(TransactionId transactionId) {
|
||||
public SpanResult selectSpan(TransactionId transactionId, long selectedSpanHint) {
|
||||
|
||||
List<SpanBo> spans = traceDao.selectSpanAndAnnotation(transactionId);
|
||||
if (spans == null || spans.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
return new SpanResult(SpanAligner2.FAIL_MATCH, Collections.<SpanAlign>emptyList());
|
||||
}
|
||||
|
||||
List<SpanAlign> order = order(spans);
|
||||
SpanResult result = order(spans, selectedSpanHint);
|
||||
List<SpanAlign> 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<AnnotationBo> annotationBoList);
|
||||
}
|
||||
|
||||
private List<SpanAlign> order(List<SpanBo> spans) {
|
||||
SpanAligner2 spanAligner = new SpanAligner2(spans);
|
||||
private SpanResult order(List<SpanBo> spans, long selectedSpanHint) {
|
||||
SpanAligner2 spanAligner = new SpanAligner2(spans, selectedSpanHint);
|
||||
List<SpanAlign> sort = spanAligner.sort();
|
||||
|
||||
logger.trace("SpanAlignList:{}", sort);
|
||||
return sort;
|
||||
return new SpanResult(spanAligner.getMatchType(), sort);
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class AgentKey {
|
||||
|
||||
private final String agentId;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"applicationId" : "${recordSet.applicationId}",
|
||||
"callStackStart" : ${callstackStart},
|
||||
"callStackEnd" : ${callstackEnd},
|
||||
"completeState" : "${completeState}",
|
||||
"callStackIndex" : {
|
||||
"depth":0,
|
||||
"begin":1,
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
"callStackStart" : ${callstackStart},
|
||||
"callStackEnd" : ${callstackEnd},
|
||||
"completeState" : "${completeState}",
|
||||
|
||||
"callStack" : [
|
||||
<c:set var="startTime" scope="page" value="${callstackStart}"/>
|
||||
|
||||
@@ -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<SpanAlign> sort = spanService.selectSpan(traceId);
|
||||
// selectedHint를 좀더 정확히 수정할것.
|
||||
SpanResult spanResult = spanService.selectSpan(traceId, System.currentTimeMillis());
|
||||
List<SpanAlign> 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<TAnnotation> ano = Collections.emptyList();
|
||||
long time = System.currentTimeMillis();
|
||||
int andIncrement = id.getAndIncrement();
|
||||
|
||||
Reference in New Issue
Block a user