[강운덕] [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:
Woonduk Kang
2013-10-29 09:01:31 +00:00
parent fee6d6104b
commit 403c6b32a6
8 changed files with 200 additions and 89 deletions
@@ -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();