/**
* Copyright 2015 The OpenZipkin Authors
*
* 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 io.zipkin.query;
import io.zipkin.Annotation;
import io.zipkin.BinaryAnnotation;
import io.zipkin.Span;
import io.zipkin.Trace;
import io.zipkin.internal.Nullable;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
public final class InMemoryZipkinQuery implements ZipkinQuery {
private static final Charset UTF_8 = Charset.forName("UTF-8");
private final Multimap<Long, Span> traceIdToSpans = new Multimap<>(LinkedList::new);
private final Multimap<String, Long> serviceToTraceIds = new Multimap<>(LinkedHashSet::new);
private final Multimap<String, String> serviceToSpanNames = new Multimap<>(LinkedHashSet::new);
public synchronized void accept(Iterable<Span> spans) {
spans.forEach(span -> {
long traceId = span.traceId();
traceIdToSpans.put(span.traceId(), span);
span.annotations().stream().filter(a -> a.host() != null)
.map(annotation -> annotation.host().serviceName().toLowerCase())
.forEach(serviceName -> {
serviceToTraceIds.put(serviceName, traceId);
serviceToSpanNames.put(serviceName, span.name());
});
});
}
synchronized void clear() {
traceIdToSpans.clear();
serviceToTraceIds.clear();
}
@Override
public synchronized List<Trace> getTraces(QueryRequest request) {
Collection<Long> traceIds = serviceToTraceIds.get(request.serviceName());
if (traceIds == null || traceIds.isEmpty()) return Collections.emptyList();
Predicate<Span> finalPredicate = spanPredicate(request);
return traceIds.stream().map(traceIdToSpans::get)
.filter(spans -> spans.stream().anyMatch(finalPredicate))
.limit(request.limit())
.map(spans -> Trace.create(new ArrayList<>(spans)))
.collect(Collectors.toList());
}
@Override
public synchronized List<Trace> getTracesByIds(List<Long> traceIds, boolean adjustClockSkew) {
if (traceIds.isEmpty()) return Collections.emptyList();
return traceIds.stream().map(traceIdToSpans::get)
.filter(spans -> spans != null)
.map(spans -> Trace.create(new ArrayList<>(spans)))
.collect(Collectors.toList());
}
@Override
public synchronized Set<String> getServiceNames() {
return new LinkedHashSet<>(serviceToTraceIds.keySet());
}
@Override
public synchronized Set<String> getSpanNames(@Nullable String service) {
if (service == null) return Collections.emptySet();
Collection<String> result = serviceToSpanNames.get(service);
return result != null ? new LinkedHashSet<>(result) : Collections.emptySet();
}
static final class Multimap<K, V> {
private final Map<K, Collection<V>> delegate = new LinkedHashMap<>();
private final Supplier<Collection<V>> collectionFunction;
public Multimap(Supplier<Collection<V>> collectionFunction) {
this.collectionFunction = collectionFunction;
}
public Set<K> keySet() {
return delegate.keySet();
}
void put(K key, V value) {
delegate.computeIfAbsent(key, (k) -> collectionFunction.get()).add(value);
}
void clear() {
delegate.clear();
}
Collection<V> get(K key) {
return delegate.get(key);
}
}
static Predicate<Span> spanPredicate(QueryRequest request) {
long endTs = (request.endTs() > 0 && request.endTs() != Long.MAX_VALUE) ? request.endTs()
: System.currentTimeMillis() / 1000;
Predicate<Span> spanPredicate =
s -> s.annotations().stream().map(Annotation::timestamp).allMatch(ts -> ts < endTs);
if (request.spanName() != null) {
spanPredicate = spanPredicate.and(s -> s.name().equals(request.spanName()));
}
if (request.annotations() != null && !request.annotations().isEmpty()) {
spanPredicate = spanPredicate.and(s -> s.annotations().stream().map(
Annotation::value).allMatch(v -> request.annotations().contains(v)));
}
if (request.binaryAnnotations() != null && !request.binaryAnnotations().isEmpty()) {
spanPredicate = spanPredicate.and(s -> s.binaryAnnotations().stream()
.filter(b -> b.type() == BinaryAnnotation.Type.STRING)
.filter(b -> request.binaryAnnotations().containsKey(b.key()))
.allMatch(
b -> request.binaryAnnotations().get(b.key()).equals(new String(b.value(), UTF_8))));
}
return spanPredicate;
}
}