All Downloads are FREE. Search and download functionalities are using the official Maven repository.

zipkin2.storage.cassandra.CassandraSpanConsumer Maven / Gradle / Ivy

There is a newer version: 3.4.2
Show newest version
/*
 * Copyright The OpenZipkin Authors
 * SPDX-License-Identifier: Apache-2.0
 */
package zipkin2.storage.cassandra;

import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import zipkin2.Annotation;
import zipkin2.Call;
import zipkin2.Span;
import zipkin2.internal.AggregateCall;
import zipkin2.internal.Nullable;
import zipkin2.storage.SpanConsumer;
import zipkin2.storage.cassandra.internal.call.InsertEntry;

import static zipkin2.storage.cassandra.CassandraUtil.durationIndexBucket;
import static zipkin2.storage.cassandra.Schema.TABLE_AUTOCOMPLETE_TAGS;
import static zipkin2.storage.cassandra.Schema.TABLE_SERVICE_REMOTE_SERVICES;
import static zipkin2.storage.cassandra.Schema.TABLE_SERVICE_SPANS;

class CassandraSpanConsumer implements SpanConsumer { // not final for testing
  final boolean searchEnabled;
  final InsertSpan.Factory insertSpan;
  final Set autocompleteKeys;

  // Everything below here is null when search is disabled
  @Nullable final InsertTraceByServiceRemoteService.Factory insertTraceByServiceRemoteService;
  @Nullable final InsertTraceByServiceSpan.Factory insertTraceByServiceSpan;
  @Nullable final InsertEntry.Factory insertServiceSpan;
  @Nullable final InsertEntry.Factory insertServiceRemoteService;
  @Nullable final InsertEntry.Factory insertAutocompleteValue;

  void clear() {
    if (insertServiceSpan != null) insertServiceSpan.clear();
    if (insertServiceRemoteService != null) insertServiceRemoteService.clear();
    if (insertAutocompleteValue != null) insertAutocompleteValue.clear();
  }

  CassandraSpanConsumer(CassandraStorage storage) {
    this(
      storage.session(), storage.metadata(),
      storage.strictTraceId, storage.searchEnabled,
      storage.autocompleteKeys, storage.autocompleteTtl, storage.autocompleteCardinality
    );
  }

  CassandraSpanConsumer(CqlSession session, Schema.Metadata metadata, boolean strictTraceId,
    boolean searchEnabled, Set autocompleteKeys, int autocompleteTtl,
    int autocompleteCardinality) {
    this.searchEnabled = searchEnabled;
    this.autocompleteKeys = autocompleteKeys;

    insertSpan = new InsertSpan.Factory(session, strictTraceId, searchEnabled);

    if (!searchEnabled) {
      insertTraceByServiceRemoteService = null;
      insertTraceByServiceSpan = null;
      insertServiceRemoteService = null;
      insertServiceSpan = null;
      insertAutocompleteValue = null;
      return;
    }

    insertTraceByServiceSpan = new InsertTraceByServiceSpan.Factory(session, strictTraceId);
    if (metadata.hasRemoteService) {
      insertTraceByServiceRemoteService =
        new InsertTraceByServiceRemoteService.Factory(session, strictTraceId);
      insertServiceRemoteService = new InsertEntry.Factory(
        "INSERT INTO " + TABLE_SERVICE_REMOTE_SERVICES + " (service, remote_service) VALUES (?,?)",
        session, autocompleteTtl, autocompleteCardinality
      );
    } else {
      insertTraceByServiceRemoteService = null;
      insertServiceRemoteService = null;
    }
    insertServiceSpan = new InsertEntry.Factory(
      "INSERT INTO " + TABLE_SERVICE_SPANS + " (service, span) VALUES (?,?)",
      session, autocompleteTtl, autocompleteCardinality
    );
    if (metadata.hasAutocompleteTags && !autocompleteKeys.isEmpty()) {
      insertAutocompleteValue = new InsertEntry.Factory(
        "INSERT INTO " + TABLE_AUTOCOMPLETE_TAGS + " (key, value) VALUES (?,?)",
        session, autocompleteTtl, autocompleteCardinality
      );
    } else {
      insertAutocompleteValue = null;
    }
  }

  /**
   * This fans out into many requests, last count was 2 * spans.size. If any of these fail, the
   * returned future will fail. Most callers drop or log the result.
   */
  @Override public Call accept(List input) {
    if (input.isEmpty()) return Call.create(null);

    Set spans = new LinkedHashSet<>();
    Set> serviceRemoteServices = new LinkedHashSet<>();
    Set> serviceSpans = new LinkedHashSet<>();
    Set traceByServiceRemoteServices =
      new LinkedHashSet<>();
    Set traceByServiceSpans = new LinkedHashSet<>();
    Set> autocompleteTags = new LinkedHashSet<>();

    for (Span s : input) {
      // indexing occurs by timestamp, so derive one if not present.
      long ts_micro = s.timestampAsLong();
      if (ts_micro == 0L) ts_micro = guessTimestamp(s);

      // fallback to current time on the ts_uuid for span data, so we know when it was inserted
      UUID ts_uuid =
        new UUID(
          Uuids.startOf(ts_micro != 0L ? (ts_micro / 1000L) : System.currentTimeMillis())
            .getMostSignificantBits(),
          Uuids.random().getLeastSignificantBits());

      spans.add(insertSpan.newInput(s, ts_uuid));

      if (!searchEnabled) continue;

      // Empty values allow for api queries with blank service or span name
      String service = s.localServiceName() != null ? s.localServiceName() : "";
      String span =
        null != s.name() ? s.name() : ""; // Empty value allows for api queries without span name

      if (null == s.localServiceName()) continue; // don't index further w/o a service name

      // service span and remote service indexes is refreshed regardless of timestamp
      String remoteService = s.remoteServiceName();
      if (insertServiceRemoteService != null && remoteService != null) {
        serviceRemoteServices.add(Map.entry(service, remoteService));
      }
      serviceSpans.add(Map.entry(service, span));

      if (ts_micro == 0L) continue; // search is only valid with a timestamp, don't index w/o it!
      int bucket = durationIndexBucket(ts_micro); // duration index is milliseconds not microseconds
      long duration = s.durationAsLong() / 1000L;
      traceByServiceSpans.add(
        insertTraceByServiceSpan.newInput(service, span, bucket, ts_uuid, s.traceId(), duration));
      if (span.isEmpty()) continue;

      if (insertServiceRemoteService != null && remoteService != null) {
        traceByServiceRemoteServices.add(
          insertTraceByServiceRemoteService.newInput(service, remoteService, bucket, ts_uuid,
            s.traceId()));
      }
      traceByServiceSpans.add( // Allows lookup without the span name
        insertTraceByServiceSpan.newInput(service, "", bucket, ts_uuid, s.traceId(), duration));

      if (insertAutocompleteValue != null) {
        for (Map.Entry entry : s.tags().entrySet()) {
          if (autocompleteKeys.contains(entry.getKey())) autocompleteTags.add(entry);
        }
      }
    }
    List> calls = new ArrayList<>();
    for (InsertSpan.Input span : spans) {
      calls.add(insertSpan.create(span));
    }
    for (Map.Entry serviceSpan : serviceSpans) {
      insertServiceSpan.maybeAdd(serviceSpan, calls);
    }
    for (Map.Entry serviceRemoteService : serviceRemoteServices) {
      insertServiceRemoteService.maybeAdd(serviceRemoteService, calls);
    }
    for (InsertTraceByServiceSpan.Input serviceSpan : traceByServiceSpans) {
      calls.add(insertTraceByServiceSpan.create(serviceSpan));
    }
    for (InsertTraceByServiceRemoteService.Input serviceRemoteService : traceByServiceRemoteServices) {
      calls.add(insertTraceByServiceRemoteService.create(serviceRemoteService));
    }
    for (Map.Entry autocompleteTag : autocompleteTags) {
      insertAutocompleteValue.maybeAdd(autocompleteTag, calls);
    }
    return calls.isEmpty() ? Call.create(null) : AggregateCall.newVoidCall(calls);
  }

  static long guessTimestamp(Span span) {
    assert 0L == span.timestampAsLong() : "method only for when span has no timestamp";
    for (Annotation annotation : span.annotations()) {
      if (0L < annotation.timestamp()) return annotation.timestamp();
    }
    return 0L; // return a timestamp that won't match a query
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy