zipkin2.storage.StrictTraceId Maven / Gradle / Ivy
/*
* Copyright The OpenZipkin Authors
* SPDX-License-Identifier: Apache-2.0
*/
package zipkin2.storage;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import zipkin2.Call.Mapper;
import zipkin2.Span;
import zipkin2.internal.FilterTraces;
/**
* Storage implementation often need to re-check query results when {@link
* StorageComponent.Builder#strictTraceId(boolean) strict trace ID} is disabled.
*/
public final class StrictTraceId {
public static Mapper, List> filterSpans(String traceId) {
return new FilterSpans(traceId);
}
/**
* Filters the mutable input client-side when there's a clash on lower 64-bits of a trace ID.
*
* @see FilterTraces
*/
public static Mapper>, List>> filterTraces(QueryRequest request) {
return new FilterTracesIfClashOnLowerTraceId(request);
}
static final class FilterTracesIfClashOnLowerTraceId
implements Mapper>, List>> {
final QueryRequest request;
FilterTracesIfClashOnLowerTraceId(QueryRequest request) {
this.request = request;
}
@Override public List> map(List> input) {
if (hasClashOnLowerTraceId(input)) {
return FilterTraces.create(request).map(input);
}
return input;
}
@Override public String toString() {
return "FilterTracesIfClashOnLowerTraceId{request=" + request + "}";
}
}
/** Returns true if any trace clashes on the right-most 16 characters of the trace ID */
// Concretely, Netflix have a special index template for a multi-tag, "fit.sessionId". If we
// blindly filtered without seeing if we had to, a match that works on the server side would
// fail client side. Normally, we wouldn't special case like this, but not filtering unless
// necessary is also more efficient.
static boolean hasClashOnLowerTraceId(List> input) {
int traceCount = input.size();
if (traceCount <= 1) return false;
// NOTE: It is probably more efficient to do clever sorting and peeking here, but the call site
// is query side, which is not in the critical path of user code. A set is much easier to grok.
Set traceIdLows = new LinkedHashSet<>();
boolean clash = false;
for (List spans : input) {
String traceId = lowerTraceId(spans.get(0).traceId());
if (!traceIdLows.add(traceId)) {
clash = true;
break;
}
}
return clash;
}
static String lowerTraceId(String traceId) {
return traceId.length() == 16 ? traceId : traceId.substring(16);
}
static final class FilterSpans implements Mapper, List> {
final String traceId;
FilterSpans(String traceId) {
this.traceId = traceId;
}
@Override public List map(List input) {
Iterator i = input.iterator();
while (i.hasNext()) {
Span next = i.next();
if (!next.traceId().equals(traceId)) i.remove();
}
return input;
}
@Override public String toString() {
return "FilterSpans{traceId=" + traceId + "}";
}
}
/**
* Returns a function that filters its mutable input when it contains a trace not matching the
* specified trace IDs.
*
* Make sure the input IDs are unique and {@link Span#normalizeTraceId(String) normalized}.
*/
public static Mapper>, List>> filterTraces(Iterable traceIds) {
return new FilterTracesByIds(traceIds);
}
static final class FilterTracesByIds implements Mapper>, List>> {
final Set traceIds;
FilterTracesByIds(Iterable sanitizedIds) {
traceIds = new LinkedHashSet<>();
for (String traceId : sanitizedIds) {
traceIds.add(traceId);
}
}
@Override
public List> map(List> input) {
Iterator> i = input.iterator();
while (i.hasNext()) { // Not using removeIf as that's java 8+
List next = i.next();
if (!traceIds.contains(next.get(0).traceId())) {
i.remove();
}
}
return input;
}
@Override
public String toString() {
return "FilterTracesByIds{traceIds=" + traceIds + "}";
}
}
StrictTraceId() {
}
}