/*
* 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.mapper;
import com.google.common.annotations.Beta;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.navercorp.pinpoint.common.buffer.Buffer;
import com.navercorp.pinpoint.common.buffer.OffsetFixedBuffer;
import com.navercorp.pinpoint.common.hbase.HBaseTables;
import com.navercorp.pinpoint.common.hbase.RowMapper;
import com.navercorp.pinpoint.common.server.bo.BasicSpan;
import com.navercorp.pinpoint.common.server.bo.SpanBo;
import com.navercorp.pinpoint.common.server.bo.SpanChunkBo;
import com.navercorp.pinpoint.common.server.bo.SpanEventBo;
import com.navercorp.pinpoint.common.server.bo.SpanEventComparator;
import com.navercorp.pinpoint.common.server.bo.serializer.RowKeyDecoder;
import com.navercorp.pinpoint.common.server.bo.serializer.trace.v2.SpanDecoder;
import com.navercorp.pinpoint.common.server.bo.serializer.trace.v2.SpanDecoderV0;
import com.navercorp.pinpoint.common.server.bo.serializer.trace.v2.SpanDecodingContext;
import com.navercorp.pinpoint.common.util.TransactionId;
import org.apache.commons.lang3.StringUtils;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.util.Bytes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author emeroad
*/
@Beta
@Component
public class SpanMapperV2 implements RowMapper<List<SpanBo>> {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final SpanDecoder spanDecoder = new SpanDecoderV0();
private final RowKeyDecoder<TransactionId> rowKeyDecoder;
@Autowired
public SpanMapperV2(@Qualifier("traceRowKeyDecoderV2") RowKeyDecoder<TransactionId> rowKeyDecoder) {
if (rowKeyDecoder == null) {
throw new NullPointerException("rowKeyDecoder must not be null");
}
this.rowKeyDecoder = rowKeyDecoder;
}
@Override
public List<SpanBo> mapRow(Result result, int rowNum) throws Exception {
if (result.isEmpty()) {
return Collections.emptyList();
}
byte[] rowKey = result.getRow();
final TransactionId transactionId = this.rowKeyDecoder.decodeRowKey(rowKey);
final Cell[] rawCells = result.rawCells();
ListMultimap<AgentKey, SpanBo> spanMap = LinkedListMultimap.create();
List<SpanChunkBo> spanChunkList = new ArrayList<>();
final SpanDecodingContext decodingContext = new SpanDecodingContext();
decodingContext.setTransactionId(transactionId);
for (Cell cell : rawCells) {
SpanDecoder spanDecoder = null;
// only if family name is "span"
if (CellUtil.matchingFamily(cell, HBaseTables.TRACE_V2_CF_SPAN)) {
decodingContext.setCollectorAcceptedTime(cell.getTimestamp());
final Buffer qualifier = new OffsetFixedBuffer(cell.getQualifierArray(), cell.getQualifierOffset(), cell.getQualifierLength());
final Buffer columnValue = new OffsetFixedBuffer(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength());
spanDecoder = resolveDecoder(columnValue);
final Object decodeObject = spanDecoder.decode(qualifier, columnValue, decodingContext);
if (decodeObject instanceof SpanBo) {
SpanBo spanBo = (SpanBo) decodeObject;
if (logger.isDebugEnabled()) {
logger.debug("spanBo:{}", spanBo);
}
AgentKey agentKey = newAgentKey(spanBo);
spanMap.put(agentKey, spanBo);
} else if (decodeObject instanceof SpanChunkBo) {
SpanChunkBo spanChunkBo = (SpanChunkBo) decodeObject;
if (logger.isDebugEnabled()) {
logger.debug("spanChunkBo:{}", spanChunkBo);
}
spanChunkList.add(spanChunkBo);
}
} else {
logger.warn("Unknown ColumnFamily :{}", Bytes.toStringBinary(CellUtil.cloneFamily(cell)));
}
nextCell(spanDecoder, decodingContext);
}
decodingContext.finish();
return buildSpanBoList(spanMap, spanChunkList);
}
private void nextCell(SpanDecoder spanDecoder, SpanDecodingContext decodingContext) {
if (spanDecoder != null) {
spanDecoder.next(decodingContext);
} else {
decodingContext.next();
}
}
private SpanDecoder resolveDecoder(Buffer columnValue) {
final byte version = columnValue.getByte(0);
if (version == 0) {
return this.spanDecoder;
} else {
throw new IllegalStateException("unsupported version");
}
}
private List<SpanBo> buildSpanBoList(ListMultimap<AgentKey, SpanBo> spanMap, List<SpanChunkBo> spanChunkList) {
List<SpanBo> spanBoList = bindSpanChunk(spanMap, spanChunkList);
sortSpanEvent(spanBoList);
return spanBoList;
}
private void sortSpanEvent(List<SpanBo> spanBoList) {
for (SpanBo spanBo : spanBoList) {
List<SpanEventBo> spanEventBoList = spanBo.getSpanEventBoList();
Collections.sort(spanEventBoList, SpanEventComparator.INSTANCE);
}
}
private List<SpanBo> bindSpanChunk(ListMultimap<AgentKey, SpanBo> spanMap, List<SpanChunkBo> spanChunkList) {
for (SpanChunkBo spanChunkBo : spanChunkList) {
AgentKey agentKey = newAgentKey(spanChunkBo);
List<SpanBo> matchedSpanBoList = spanMap.get(agentKey);
if (matchedSpanBoList != null) {
final int spanIdCollisionSize = matchedSpanBoList.size();
if (spanIdCollisionSize > 1) {
// exceptional case dump
logger.warn("spanIdCollision {}", matchedSpanBoList);
}
int agentLevelCollisionCount = 0;
for (SpanBo spanBo : matchedSpanBoList) {
if (StringUtils.equals(spanBo.getAgentId(), spanChunkBo.getAgentId())) {
spanBo.addSpanEventBoList(spanChunkBo.getSpanEventBoList());
agentLevelCollisionCount++;
}
}
if (agentLevelCollisionCount > 1) {
// exceptional case dump
logger.warn("agentLevelCollision {}", matchedSpanBoList);
}
} else {
if (logger.isInfoEnabled()) {
logger.info("Span not exist spanId:{} spanChunk:{}", agentKey, spanChunkBo);
}
}
}
return Lists.newArrayList(spanMap.values());
}
private AgentKey newAgentKey(BasicSpan basicSpan) {
return new AgentKey(basicSpan.getApplicationId(), basicSpan.getAgentId(), basicSpan.getAgentStartTime(), basicSpan.getSpanId());
}
public static class AgentKey {
private final long spanId;
private final String applicationId;
private final String agentId;
private final long agentStartTime;
public AgentKey(String applicationId, String agentId, long agentStartTime, long spanId) {
if (applicationId == null) {
throw new NullPointerException("applicationId must not be null");
}
if (agentId == null) {
throw new NullPointerException("agentId must not be null");
}
this.applicationId = applicationId;
this.agentId = agentId;
this.agentStartTime = agentStartTime;
this.spanId = spanId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AgentKey agentKey = (AgentKey) o;
if (spanId != agentKey.spanId) return false;
if (agentStartTime != agentKey.agentStartTime) return false;
if (!applicationId.equals(agentKey.applicationId)) return false;
return agentId.equals(agentKey.agentId);
}
@Override
public int hashCode() {
int result = (int) (spanId ^ (spanId >>> 32));
result = 31 * result + applicationId.hashCode();
result = 31 * result + agentId.hashCode();
result = 31 * result + (int) (agentStartTime ^ (agentStartTime >>> 32));
return result;
}
@Override
public String toString() {
return "AgentKey{" +
"spanId=" + spanId +
", applicationId='" + applicationId + '\'' +
", agentId='" + agentId + '\'' +
", agentStartTime=" + agentStartTime +
'}';
}
}
}