/*
* 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.calltree.span;
import com.navercorp.pinpoint.common.server.bo.SpanBo;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* @author emeroad
*/
public class SpanIdMatcher {
private List<SpanBo> nextSpanBoList;
private static final long MAX_EXCLUDE_WEIGHT = 1000 * 5;
public SpanIdMatcher(List<SpanBo> nextSpanBoList) {
if (nextSpanBoList == null) {
throw new NullPointerException("nextSpanBoList must not be null");
}
this.nextSpanBoList = nextSpanBoList;
}
public SpanBo approximateMatch(long spanEventBoStartTime) {
// TODO: need algorithm for matching
List<WeightSpanBo> weightSpanList = computeWeight(spanEventBoStartTime);
if (weightSpanList.isEmpty()) {
return null;
}
Collections.sort(weightSpanList, new Comparator<WeightSpanBo>() {
@Override
public int compare(WeightSpanBo wSpan1, WeightSpanBo wSpan2) {
final long spanWeight1 = wSpan1.getWeight();
final long spanWeight2 = wSpan2.getWeight();
if (spanWeight1 < spanWeight2) {
return -1;
} else {
if (spanWeight1 == spanWeight2) {
return 0;
} else {
return 1;
}
}
}
});
SpanBo minWeight = getMinWeight(weightSpanList);
if (minWeight != null) {
nextSpanBoList.remove(minWeight);
}
return minWeight;
}
private SpanBo getMinWeight(List<WeightSpanBo> weightSpanList) {
long min = Long.MAX_VALUE;
final List<SpanBo> minValue = new ArrayList<>();
for (WeightSpanBo weightSpanBo : weightSpanList) {
long weight = weightSpanBo.getWeight();
if (weight <= min) {
minValue.add(weightSpanBo.getSpanBo());
min = weight;
}
}
if (minValue.size() == 1) {
return minValue.get(0);
}
// returns the first data when more than one
// TODO: we probably need to log this
return minValue.get(0);
}
private List<WeightSpanBo> computeWeight(long spanEventBoStartTime) {
List<WeightSpanBo> weightSpanList = new ArrayList<>();
for (SpanBo next : nextSpanBoList) {
long startTime = next.getStartTime();
long distance = startTime - spanEventBoStartTime;
long weightDistance = getWeightDistance(distance);
if (weightDistance > MAX_EXCLUDE_WEIGHT) {
// if higher than MAX WEIGHT, most likely missing case. just drop it
continue;
}
weightSpanList.add(new WeightSpanBo(weightDistance, next));
}
return weightSpanList;
}
private long getWeightDistance(long distance) {
if (distance >= 0) {
// positive number
return distance;
} else {
// give a penalty when negative
// if time skew due to network time sync problem is not big, it is highly unlikely to match a negative number
// it actually is more likely to get higher positive number diff due to JVM GC.
// TODO: need to adjust for network sync time diff. penalty is just set to 1 second
distance = Math.abs(distance);
return (distance * 2) + 1000;
}
}
public List<SpanBo> other() {
if (nextSpanBoList.isEmpty()) {
return null;
}
return nextSpanBoList;
}
private static class WeightSpanBo {
private long weight;
private SpanBo spanBo;
private WeightSpanBo(long weight, SpanBo spanBo) {
this.weight = weight;
this.spanBo = spanBo;
}
private long getWeight() {
return weight;
}
private SpanBo getSpanBo() {
return spanBo;
}
}
}