/* * 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 com.navercorp.pinpoint.common.server.bo.AnnotationBo; import com.navercorp.pinpoint.common.server.bo.ApiMetaDataBo; import com.navercorp.pinpoint.common.server.bo.MethodTypeEnum; import com.navercorp.pinpoint.common.server.bo.SpanBo; import com.navercorp.pinpoint.common.server.bo.SqlMetaDataBo; import com.navercorp.pinpoint.common.server.bo.StringMetaDataBo; import com.navercorp.pinpoint.common.server.util.AnnotationUtils; import com.navercorp.pinpoint.common.trace.AnnotationKey; import com.navercorp.pinpoint.common.util.AnnotationKeyUtils; import com.navercorp.pinpoint.common.util.DefaultSqlParser; import com.navercorp.pinpoint.common.util.IntStringStringValue; import com.navercorp.pinpoint.common.util.OutputParameterParser; import com.navercorp.pinpoint.common.util.SqlParser; import com.navercorp.pinpoint.common.util.TransactionId; import com.navercorp.pinpoint.web.calltree.span.CallTree; import com.navercorp.pinpoint.web.calltree.span.CallTreeIterator; import com.navercorp.pinpoint.web.calltree.span.SpanAlign; import com.navercorp.pinpoint.web.calltree.span.SpanAligner2; import com.navercorp.pinpoint.web.dao.ApiMetaDataDao; import com.navercorp.pinpoint.web.dao.SqlMetaDataDao; import com.navercorp.pinpoint.web.dao.StringMetaDataDao; import com.navercorp.pinpoint.web.dao.TraceDao; import com.navercorp.pinpoint.web.security.MetaDataFilter; import com.navercorp.pinpoint.web.security.MetaDataFilter.MetaData; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; /** * @author emeroad * @author jaehong.kim * @author minwoo.jung */ //@Service public class SpanServiceImpl implements SpanService { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired @Qualifier("hbaseTraceDaoFactory") private TraceDao traceDao; // @Autowired private SqlMetaDataDao sqlMetaDataDao; @Autowired(required=false) private MetaDataFilter metaDataFilter; @Autowired private ApiMetaDataDao apiMetaDataDao; @Autowired private StringMetaDataDao stringMetaDataDao; private final SqlParser sqlParser = new DefaultSqlParser(); private final OutputParameterParser outputParameterParser = new OutputParameterParser(); public void setSqlMetaDataDao(SqlMetaDataDao sqlMetaDataDao) { this.sqlMetaDataDao = sqlMetaDataDao; } @Override public SpanResult selectSpan(TransactionId transactionId, long selectedSpanHint) { if (transactionId == null) { throw new NullPointerException("transactionId must not be null"); } final List<SpanBo> spans = traceDao.selectSpan(transactionId); if (CollectionUtils.isEmpty(spans)) { return new SpanResult(SpanAligner2.FAIL_MATCH, new CallTreeIterator(null)); } final SpanResult result = order(spans, selectedSpanHint); final CallTreeIterator callTreeIterator = result.getCallTree(); final List<SpanAlign> values = callTreeIterator.values(); transitionDynamicApiId(values); transitionSqlId(values); transitionCachedString(values); transitionException(values); // TODO need to at least show the row data when root span is not found. return result; } private void transitionAnnotation(List<SpanAlign> spans, AnnotationReplacementCallback annotationReplacementCallback) { for (SpanAlign spanAlign : spans) { List<AnnotationBo> annotationBoList = spanAlign.getAnnotationBoList(); if (annotationBoList == null) { annotationBoList = new ArrayList<>(); spanAlign.setAnnotationBoList(annotationBoList); } annotationReplacementCallback.replacement(spanAlign, annotationBoList); } } private void transitionSqlId(final List<SpanAlign> spans) { this.transitionAnnotation(spans, new AnnotationReplacementCallback() { @Override public void replacement(SpanAlign spanAlign, List<AnnotationBo> annotationBoList) { AnnotationBo sqlIdAnnotation = findAnnotation(annotationBoList, AnnotationKey.SQL_ID.getCode()); if (sqlIdAnnotation == null) { return; } if (metaDataFilter != null && metaDataFilter.filter(spanAlign, MetaData.SQL)) { AnnotationBo annotationBo = metaDataFilter.createAnnotationBo(spanAlign, MetaData.SQL); annotationBoList.add(annotationBo); return; } // value of sqlId's annotation contains multiple values. final IntStringStringValue sqlValue = (IntStringStringValue) sqlIdAnnotation.getValue(); final int sqlId = sqlValue.getIntValue(); final String sqlParam = sqlValue.getStringValue1(); final List<SqlMetaDataBo> sqlMetaDataList = sqlMetaDataDao.getSqlMetaData(spanAlign.getAgentId(), spanAlign.getAgentStartTime(), sqlId); final int size = sqlMetaDataList.size(); if (size == 0) { AnnotationBo api = new AnnotationBo(); api.setKey(AnnotationKey.SQL.getCode()); api.setValue("SQL-ID not found sqlId:" + sqlId); annotationBoList.add(api); } else if (size == 1) { final SqlMetaDataBo sqlMetaDataBo = sqlMetaDataList.get(0); if (StringUtils.isEmpty(sqlParam)) { AnnotationBo sqlMeta = new AnnotationBo(); sqlMeta.setKey(AnnotationKey.SQL_METADATA.getCode()); sqlMeta.setValue(sqlMetaDataBo.getSql()); annotationBoList.add(sqlMeta); // AnnotationBo checkFail = checkIdentifier(spanAlign, sqlMetaDataBo); // if (checkFail != null) { // // fail // annotationBoList.add(checkFail); // return; // } AnnotationBo sql = new AnnotationBo(); sql.setKey(AnnotationKey.SQL.getCode()); sql.setValue(sqlMetaDataBo.getSql().trim()); annotationBoList.add(sql); } else { logger.debug("sqlMetaDataBo:{}", sqlMetaDataBo); final String outputParams = sqlParam; List<String> parsedOutputParams = outputParameterParser.parseOutputParameter(outputParams); logger.debug("outputPrams:{}, parsedOutputPrams:{}", outputParams, parsedOutputParams); String originalSql = sqlParser.combineOutputParams(sqlMetaDataBo.getSql(), parsedOutputParams); logger.debug("outputPrams{}, originalSql:{}", outputParams, originalSql); AnnotationBo sqlMeta = new AnnotationBo(); sqlMeta.setKey(AnnotationKey.SQL_METADATA.getCode()); sqlMeta.setValue(sqlMetaDataBo.getSql()); annotationBoList.add(sqlMeta); AnnotationBo sql = new AnnotationBo(); sql.setKey(AnnotationKey.SQL.getCode()); sql.setValue(originalSql.trim()); annotationBoList.add(sql); } } else { // TODO need improvement AnnotationBo api = new AnnotationBo(); api.setKey(AnnotationKey.SQL.getCode()); api.setValue(collisionSqlIdCodeMessage(sqlId, sqlMetaDataList)); annotationBoList.add(api); } // add if bindValue exists final String bindValue = sqlValue.getStringValue2(); if (StringUtils.isNotEmpty(bindValue)) { AnnotationBo bindValueAnnotation = new AnnotationBo(); bindValueAnnotation.setKey(AnnotationKey.SQL_BINDVALUE.getCode()); bindValueAnnotation.setValue(bindValue); annotationBoList.add(bindValueAnnotation); } } }); } private AnnotationBo findAnnotation(List<AnnotationBo> annotationBoList, int key) { for (AnnotationBo annotationBo : annotationBoList) { if (key == annotationBo.getKey()) { return annotationBo; } } return null; } private String collisionSqlIdCodeMessage(int sqlId, List<SqlMetaDataBo> sqlMetaDataList) { // TODO need a separate test case to test for hashCode collision (probability way too low for easy replication) StringBuilder sb = new StringBuilder(64); sb.append("Collision Sql sqlId:"); sb.append(sqlId); sb.append('\n'); for (int i = 0; i < sqlMetaDataList.size(); i++) { if (i != 0) { sb.append("or\n"); } SqlMetaDataBo sqlMetaDataBo = sqlMetaDataList.get(i); sb.append(sqlMetaDataBo.getSql()); } return sb.toString(); } private void transitionDynamicApiId(List<SpanAlign> spans) { this.transitionAnnotation(spans, new AnnotationReplacementCallback() { @Override public void replacement(SpanAlign spanAlign, List<AnnotationBo> annotationBoList) { final int apiId = spanAlign.getApiId(); if (apiId == 0) { String apiString = AnnotationUtils.findApiAnnotation(annotationBoList); // annotation base api if (apiString != null) { ApiMetaDataBo apiMetaDataBo = new ApiMetaDataBo(spanAlign.getAgentId(), spanAlign.getStartTime(), apiId); apiMetaDataBo.setApiInfo(apiString); apiMetaDataBo.setLineNumber(-1); apiMetaDataBo.setMethodTypeEnum(MethodTypeEnum.DEFAULT); AnnotationBo apiAnnotation = new AnnotationBo(); apiAnnotation.setKey(AnnotationKey.API_METADATA.getCode()); apiAnnotation.setValue(apiMetaDataBo); annotationBoList.add(apiAnnotation); return; } } // may be able to get a more accurate data using agentIdentifier. List<ApiMetaDataBo> apiMetaDataList = apiMetaDataDao.getApiMetaData(spanAlign.getAgentId(), spanAlign.getAgentStartTime(), apiId); int size = apiMetaDataList.size(); if (size == 0) { AnnotationBo api = new AnnotationBo(); api.setKey(AnnotationKey.ERROR_API_METADATA_NOT_FOUND.getCode()); api.setValue("API-DynamicID not found. api:" + apiId); annotationBoList.add(api); } else if (size == 1) { ApiMetaDataBo apiMetaDataBo = apiMetaDataList.get(0); AnnotationBo apiMetaData = new AnnotationBo(); apiMetaData.setKey(AnnotationKey.API_METADATA.getCode()); apiMetaData.setValue(apiMetaDataBo); annotationBoList.add(apiMetaData); if (apiMetaDataBo.getMethodTypeEnum() == MethodTypeEnum.DEFAULT) { AnnotationBo apiAnnotation = new AnnotationBo(); apiAnnotation.setKey(AnnotationKey.API.getCode()); String apiInfo = getApiInfo(apiMetaDataBo); apiAnnotation.setValue(apiInfo); annotationBoList.add(apiAnnotation); } else { AnnotationBo apiAnnotation = new AnnotationBo(); apiAnnotation.setKey(AnnotationKey.API_TAG.getCode()); apiAnnotation.setValue(getApiTagInfo(apiMetaDataBo)); annotationBoList.add(apiAnnotation); } } else { AnnotationBo apiAnnotation = new AnnotationBo(); apiAnnotation.setKey(AnnotationKey.ERROR_API_METADATA_DID_COLLSION.getCode()); String collisionMessage = collisionApiDidMessage(apiId, apiMetaDataList); apiAnnotation.setValue(collisionMessage); annotationBoList.add(apiAnnotation); } } }); } private void transitionCachedString(List<SpanAlign> spans) { this.transitionAnnotation(spans, new AnnotationReplacementCallback() { @Override public void replacement(SpanAlign spanAlign, List<AnnotationBo> annotationBoList) { List<AnnotationBo> cachedStringAnnotation = findCachedStringAnnotation(annotationBoList); if (cachedStringAnnotation.isEmpty()) { return; } for (AnnotationBo annotationBo : cachedStringAnnotation) { final int cachedArgsKey = annotationBo.getKey(); int stringMetaDataId = (Integer) annotationBo.getValue(); List<StringMetaDataBo> stringMetaList = stringMetaDataDao.getStringMetaData(spanAlign.getAgentId(), spanAlign.getAgentStartTime(), stringMetaDataId); int size = stringMetaList.size(); if (size == 0) { logger.warn("StringMetaData not Found {}/{}/{}", spanAlign.getAgentId(), stringMetaDataId, spanAlign.getAgentStartTime()); AnnotationBo api = new AnnotationBo(); api.setKey(AnnotationKey.ERROR_API_METADATA_NOT_FOUND.getCode()); api.setValue("CACHED-STRING-ID not found. stringId:" + cachedArgsKey); annotationBoList.add(api); } else if (size >= 1) { // key collision shouldn't really happen (probability too low) StringMetaDataBo stringMetaDataBo = stringMetaList.get(0); AnnotationBo stringMetaData = new AnnotationBo(); stringMetaData.setKey(AnnotationKeyUtils.cachedArgsToArgs(cachedArgsKey)); stringMetaData.setValue(stringMetaDataBo.getStringValue()); annotationBoList.add(stringMetaData); if (size > 1) { logger.warn("stringMetaData size not 1 :{}", stringMetaList); } } } } }); } private List<AnnotationBo> findCachedStringAnnotation(List<AnnotationBo> annotationBoList) { List<AnnotationBo> findAnnotationBoList = new ArrayList<>(annotationBoList.size()); for (AnnotationBo annotationBo : annotationBoList) { if (AnnotationKeyUtils.isCachedArgsKey(annotationBo.getKey())) { findAnnotationBoList.add(annotationBo); } } return findAnnotationBoList; } private void transitionException(List<SpanAlign> spanAlignList) { for (SpanAlign spanAlign : spanAlignList) { if (spanAlign.hasException()) { StringMetaDataBo stringMetaData = selectStringMetaData(spanAlign.getAgentId(), spanAlign.getExceptionId(), spanAlign.getAgentStartTime()); spanAlign.setExceptionClass(stringMetaData.getStringValue()); } } } private StringMetaDataBo selectStringMetaData(String agentId, int cacheId, long agentStartTime) { final List<StringMetaDataBo> metaDataList = stringMetaDataDao.getStringMetaData(agentId, agentStartTime, cacheId); if (CollectionUtils.isEmpty(metaDataList)) { logger.warn("StringMetaData not Found agent:{}, cacheId{}, agentStartTime:{}", agentId, cacheId, agentStartTime); StringMetaDataBo stringMetaDataBo = new StringMetaDataBo(agentId, agentStartTime, cacheId); stringMetaDataBo.setStringValue("STRING-META-DATA-NOT-FOUND"); return stringMetaDataBo; } if (metaDataList.size() == 1) { return metaDataList.get(0); } else { logger.warn("stringMetaData size not 1 :{}", metaDataList); return metaDataList.get(0); } } private String collisionApiDidMessage(int apidId, List<ApiMetaDataBo> apiMetaDataList) { // TODO need a separate test case to test for apidId collision (probability way too low for easy replication) StringBuilder sb = new StringBuilder(64); sb.append("Collision Api DynamicId:"); sb.append(apidId); sb.append('\n'); for (int i = 0; i < apiMetaDataList.size(); i++) { if (i != 0) { sb.append("or\n"); } ApiMetaDataBo apiMetaDataBo = apiMetaDataList.get(i); sb.append(getApiInfo(apiMetaDataBo)); } return sb.toString(); } private String getApiInfo(ApiMetaDataBo apiMetaDataBo) { if (apiMetaDataBo.getLineNumber() != -1) { return apiMetaDataBo.getApiInfo() + ":" + apiMetaDataBo.getLineNumber(); } else { return apiMetaDataBo.getApiInfo(); } } private String getApiTagInfo(ApiMetaDataBo apiMetaDataBo) { return apiMetaDataBo.getApiInfo(); } public interface AnnotationReplacementCallback { void replacement(SpanAlign spanAlign, List<AnnotationBo> annotationBoList); } private SpanResult order(List<SpanBo> spans, long selectedSpanHint) { SpanAligner2 spanAligner = new SpanAligner2(spans, selectedSpanHint); final CallTree callTree = spanAligner.sort(); return new SpanResult(spanAligner.getMatchType(), callTree.iterator()); } }