package rocks.inspectit.shared.cs.cmr.service.cache; import java.util.Collection; import java.util.Date; import java.util.Objects; import java.util.concurrent.ExecutionException; import org.apache.commons.collections.CollectionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import rocks.inspectit.shared.all.tracing.data.AbstractSpan; import rocks.inspectit.shared.all.tracing.data.Span; import rocks.inspectit.shared.all.tracing.data.SpanIdent; import rocks.inspectit.shared.cs.cmr.service.ISpanService; import rocks.inspectit.shared.cs.communication.comparator.ResultComparator; /** * Wrapper for the {@link ISpanService} that caches spans so that we don't need to have constant * round-trips to the server to load span by span. * * @author Ivan Senic * */ public class CachedSpanService implements ISpanService { /** * Logger of this class. */ private static final Logger LOG = LoggerFactory.getLogger(CachedSpanService.class); /** * Real {@link ISpanService} to load data from. */ private ISpanService service; /** * Span cache for avoiding reloading. */ private LoadingCache<SpanIdent, Span> cache = CacheBuilder.newBuilder().maximumSize(1000).build(new CacheLoader<SpanIdent, Span>() { @Override public Span load(SpanIdent ident) throws Exception { // collect all from same trace as we expect them to be asked in same point of time // this way we don't do round-trips to only get one span // example: // - you ask for span id 1 and trace id 2 // - I load all spans with trace id 2, which might be few of them // - the spans not having id 1 I add to the cache manually // - i "load" the one with id 1, which will effectively add it also to the cache.. Collection<? extends Span> spans = service.getSpans(ident.getTraceId()); // add other spans from the trace to the cache and returned the one we tried to load if (CollectionUtils.isNotEmpty(spans)) { Span value = null; for (Span span : spans) { if (Objects.equals(ident, span.getSpanIdent())) { value = span; } else { cache.put(span.getSpanIdent(), span); } } // return asked one if (null != value) { return value; } } // if we can not locate, throw exception as per loading cache api throw new Exception("Span with ident " + ident + " can not be found."); } }); /** * Default constructor. * * @param service * Real {@link ISpanService} to load data from. */ public CachedSpanService(ISpanService service) { this.service = service; } /** * {@inheritDoc} */ @Override public Collection<? extends Span> getRootSpans(int limit, Date fromDate, Date toDate, ResultComparator<AbstractSpan> resultComparator) { // call service Collection<? extends Span> spans = service.getRootSpans(limit, fromDate, toDate, resultComparator); // cache results for (Span span : spans) { cache.put(span.getSpanIdent(), span); } // then return return spans; } /** * {@inheritDoc} */ @Override public Collection<? extends Span> getSpans(long traceId) { // call service // note we can not go via the cache here as we are not sure that cache holds all spans // belonging to the trace (due to eviction and/or spans coming to the server from several // agents in different time) Collection<? extends Span> spans = service.getSpans(traceId); // cache results for (Span span : spans) { cache.put(span.getSpanIdent(), span); } // then return return spans; } /** * Returns the span with given span ident. This method will look into the cache first and only * if span is not cached try to load it from the original span service. * * @param spanIdent * {@link SpanIdent} that identifies the span. * @return {@link Span} or <code>null</code> if span is not in a cache and not found on the * server. */ @Override public Span get(SpanIdent spanIdent) { try { return cache.get(spanIdent); } catch (ExecutionException e) { if (LOG.isDebugEnabled()) { LOG.debug("Span with span ident " + spanIdent.toString() + " can not be loaded.", e); } return null; } } }