/** * Copyright 2015-2016 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 zipkin.storage; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; 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.TreeSet; import zipkin.DependencyLink; import zipkin.Span; import zipkin.internal.CorrectForClockSkew; import zipkin.internal.DependencyLinker; import zipkin.internal.GroupByTraceId; import zipkin.internal.MergeById; import zipkin.internal.Nullable; import zipkin.internal.Pair; import static zipkin.internal.ApplyTimestampAndDuration.guessTimestamp; import static zipkin.internal.GroupByTraceId.TRACE_DESCENDING; import static zipkin.internal.Util.sortedList; /** Internally, spans are indexed on 64-bit trace ID */ public final class InMemorySpanStore implements SpanStore { private final Multimap<Long, Span> traceIdToSpans = new LinkedListMultimap<>(); private final Set<Pair<Long>> traceIdTimeStamps = new TreeSet<>(VALUE_2_DESCENDING); private final Multimap<String, Pair<Long>> serviceToTraceIdTimeStamp = new SortedByValue2Descending<>(); private final Multimap<String, String> serviceToSpanNames = new LinkedHashSetMultimap<>(); private final boolean strictTraceId; volatile int acceptedSpanCount; // Historical constructor public InMemorySpanStore() { this(new InMemoryStorage.Builder()); } InMemorySpanStore(InMemoryStorage.Builder builder) { this.strictTraceId = builder.strictTraceId; } final StorageAdapters.SpanConsumer spanConsumer = new StorageAdapters.SpanConsumer() { @Override public void accept(List<Span> spans) { for (Span span : spans) { Long timestamp = guessTimestamp(span); Pair<Long> traceIdTimeStamp = Pair.create(span.traceId, timestamp == null ? Long.MIN_VALUE : timestamp); String spanName = span.name; synchronized (InMemorySpanStore.this) { traceIdTimeStamps.add(traceIdTimeStamp); traceIdToSpans.put(span.traceId, span); acceptedSpanCount++; for (String serviceName : span.serviceNames()) { serviceToTraceIdTimeStamp.put(serviceName, traceIdTimeStamp); serviceToSpanNames.put(serviceName, spanName); } } } } @Override public String toString() { return "InMemorySpanConsumer"; } }; /** * @deprecated use {@link #getRawTraces()} */ @Deprecated public synchronized List<Long> traceIds() { return sortedList(traceIdToSpans.keySet()); } synchronized void clear() { acceptedSpanCount = 0; traceIdToSpans.clear(); serviceToTraceIdTimeStamp.clear(); } /** * Used for testing. Returns all traces unconditionally. */ public synchronized List<List<Span>> getRawTraces() { List<List<Span>> result = new ArrayList<>(); for (long traceId : traceIdToSpans.keySet()) { Collection<Span> sameTraceId = traceIdToSpans.get(traceId); for (List<Span> next : GroupByTraceId.apply(sameTraceId, strictTraceId, false)) { result.add(next); } } Collections.sort(result, TRACE_DESCENDING); return result; } @Override public synchronized List<List<Span>> getTraces(QueryRequest request) { Set<Long> traceIdsInTimerange = traceIdsDescendingByTimestamp(request); if (traceIdsInTimerange.isEmpty()) return Collections.emptyList(); List<List<Span>> result = new ArrayList<>(); for (Iterator<Long> traceId = traceIdsInTimerange.iterator(); traceId.hasNext() && result.size() < request.limit; ) { Collection<Span> sameTraceId = traceIdToSpans.get(traceId.next()); for (List<Span> next : GroupByTraceId.apply(sameTraceId, strictTraceId, true)) { if (request.test(next)) { result.add(next); } } } Collections.sort(result, TRACE_DESCENDING); return result; } Set<Long> traceIdsDescendingByTimestamp(QueryRequest request) { Collection<Pair<Long>> traceIdTimestamps = request.serviceName != null ? serviceToTraceIdTimeStamp.get(request.serviceName) : traceIdTimeStamps; long endTs = request.endTs * 1000; long startTs = endTs - request.lookback * 1000; if (traceIdTimestamps == null || traceIdTimestamps.isEmpty()) return Collections.emptySet(); Set<Long> result = new LinkedHashSet<>(); for (Pair<Long> traceIdTimestamp : traceIdTimestamps) { if (traceIdTimestamp._2 >= startTs || traceIdTimestamp._2 <= endTs) { result.add(traceIdTimestamp._1); } } return result; } @Override public List<Span> getTrace(long traceId) { return getTrace(0L, traceId); } @Override public List<Span> getTrace(long traceIdHigh, long traceIdLow) { List<Span> result = getRawTrace(traceIdHigh, traceIdLow); if (result == null) return null; return CorrectForClockSkew.apply(MergeById.apply(result)); } @Override public List<Span> getRawTrace(long traceId) { return getRawTrace(0L, traceId); } @Override public synchronized List<Span> getRawTrace(long traceIdHigh, long traceId) { List<Span> spans = (List<Span>) traceIdToSpans.get(traceId); if (spans == null || spans.isEmpty()) return null; if (!strictTraceId) return sortedList(spans); List<Span> filtered = new ArrayList<>(spans); Iterator<Span> iterator = filtered.iterator(); while (iterator.hasNext()) { if (iterator.next().traceIdHigh != traceIdHigh) { iterator.remove(); } } return filtered.isEmpty() ? null : filtered; } @Override public synchronized List<String> getServiceNames() { return sortedList(serviceToTraceIdTimeStamp.keySet()); } @Override public synchronized List<String> getSpanNames(String service) { if (service == null) return Collections.emptyList(); service = service.toLowerCase(); // service names are always lowercase! return sortedList(serviceToSpanNames.get(service)); } @Override public List<DependencyLink> getDependencies(long endTs, @Nullable Long lookback) { QueryRequest request = QueryRequest.builder() .endTs(endTs) .lookback(lookback) .limit(Integer.MAX_VALUE).build(); DependencyLinker linksBuilder = new DependencyLinker(); for (Collection<Span> trace : getTraces(request)) { linksBuilder.putTrace(trace); } return linksBuilder.link(); } static final class LinkedListMultimap<K, V> extends Multimap<K, V> { @Override Collection<V> valueContainer() { return new LinkedList<>(); } } static final Comparator<Pair<Long>> VALUE_2_DESCENDING = (left, right) -> { int result = right._2.compareTo(left._2); if (result != 0) return result; return right._1.compareTo(left._1); }; /** QueryRequest.limit needs trace ids are returned in timestamp descending order. */ static final class SortedByValue2Descending<K> extends Multimap<K, Pair<Long>> { @Override Set<Pair<Long>> valueContainer() { return new TreeSet<>(VALUE_2_DESCENDING); } } static final class LinkedHashSetMultimap<K, V> extends Multimap<K, V> { @Override Collection<V> valueContainer() { return new LinkedHashSet<>(); } } static abstract class Multimap<K, V> { private final Map<K, Collection<V>> delegate = new LinkedHashMap<>(); abstract Collection<V> valueContainer(); Set<K> keySet() { return delegate.keySet(); } void put(K key, V value) { Collection<V> valueContainer = delegate.get(key); if (valueContainer == null) { synchronized (delegate) { if (!delegate.containsKey(key)) { valueContainer = valueContainer(); delegate.put(key, valueContainer); } } } valueContainer.add(value); } // not synchronized as only used for for testing void clear() { delegate.clear(); } Collection<V> get(K key) { return delegate.get(key); } } }