diff --git a/dd-trace-ext/README.md b/dd-trace-ext/README.md deleted file mode 100644 index 54c177619f..0000000000 --- a/dd-trace-ext/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Convenience Utils for Datadog Tracer Internal API -- Trace Scopes -- Global Tracer -- Async controls diff --git a/dd-trace-ext/dd-trace-ext.gradle b/dd-trace-ext/dd-trace-ext.gradle deleted file mode 100644 index bd6e0d7315..0000000000 --- a/dd-trace-ext/dd-trace-ext.gradle +++ /dev/null @@ -1,6 +0,0 @@ -description = 'dd-trace-ext' -apply from: "${rootDir}/gradle/java.gradle" - -dependencies { - compile project(':dd-trace') -} diff --git a/dd-trace-ext/src/main/java/datadog/trace/tracer/ext/Examples.java b/dd-trace-ext/src/main/java/datadog/trace/tracer/ext/Examples.java deleted file mode 100644 index e9ccdc66cc..0000000000 --- a/dd-trace-ext/src/main/java/datadog/trace/tracer/ext/Examples.java +++ /dev/null @@ -1,75 +0,0 @@ -package datadog.trace.tracer.ext; - -import datadog.trace.tracer.Continuation; -import datadog.trace.tracer.Span; -import datadog.trace.tracer.Tracer; - -// Keeping in PR for potential discussions. Will eventually remove. -// TODO: remove -class Examples { - private Examples() {} - - public static void test() { - final Throwable someThrowable = null; - // registration - TracerContext.registerGlobalContext(new TracerContext(Tracer.builder().build()), false); - - // scope - final TracerContext ctx = TracerContext.getGlobalContext(); - // without try-with-resources - { - final Span rootSpan = ctx.getTracer().buildTrace(null); - final Scope scope = ctx.pushScope(rootSpan); - rootSpan.attachThrowable(someThrowable); - scope.close(); - rootSpan.finish(); - } - - /* - // with try-with-resources finishOnClose=true - { - Span rootSpan = ctx.getTracer().buildTrace(null); - try (Scope scope = ctx.pushScope(rootSpan)) { - try { - // the body - } catch (Throwable t) { - rootSpan.setError(true); - rootSpan.attachThrowable(t); - throw t; - } - } - } - */ - - // with try-with-resources finishOnClose=false - { - final Span rootSpan = ctx.getTracer().buildTrace(null); - try (final Scope scope = ctx.pushScope(rootSpan)) { - // the body - } catch (final Throwable t) { - rootSpan.attachThrowable(t); - throw t; - } finally { - rootSpan.finish(); - } - } - - // continuations - { - final Span rootSpan = ctx.getTracer().buildTrace(null); - final Continuation cont = rootSpan.getTrace().createContinuation(rootSpan); - { // on another thread - final Span parent = cont.getSpan(); - try { - // body - } finally { - cont.close(); - } - } - } - - // create a span as a child of the currently active span - final Span childSpan = - ctx.peekScope().span().getTrace().createSpan(ctx.peekScope().span().getContext()); - } -} diff --git a/dd-trace-ext/src/main/java/datadog/trace/tracer/ext/Scope.java b/dd-trace-ext/src/main/java/datadog/trace/tracer/ext/Scope.java deleted file mode 100644 index d74615a57b..0000000000 --- a/dd-trace-ext/src/main/java/datadog/trace/tracer/ext/Scope.java +++ /dev/null @@ -1,27 +0,0 @@ -package datadog.trace.tracer.ext; - -import datadog.trace.tracer.Continuation; -import datadog.trace.tracer.Span; - -/** - * A scope holds a single span or trace continuation and may optionally finish its span or - * continuation. - * - *

To create a scope, see {@link TracerContext#pushScope(Span)} and {@link - * TracerContext#pushScope(Continuation)}. - * - *

All created scopes must be closed with {@link Scope#close()} - */ -public interface Scope extends AutoCloseable { - /** Get the span held by this scope. */ - Span span(); - - /** - * Close this scope. This method must be invoked on all created scopes. - * - *

Attempting to close a scope which is not on the top of its TracerContext's scope-stack is an - * error. See {@link TracerContext#peekScope()}. - */ - @Override - void close(); -} diff --git a/dd-trace-ext/src/main/java/datadog/trace/tracer/ext/TracerContext.java b/dd-trace-ext/src/main/java/datadog/trace/tracer/ext/TracerContext.java deleted file mode 100644 index e8d78b7b3f..0000000000 --- a/dd-trace-ext/src/main/java/datadog/trace/tracer/ext/TracerContext.java +++ /dev/null @@ -1,86 +0,0 @@ -package datadog.trace.tracer.ext; - -import datadog.trace.tracer.Continuation; -import datadog.trace.tracer.Span; -import datadog.trace.tracer.Tracer; - -/** - * Provides a place to store a shared global tracer with convenient span-building helper methods. - * - *

Also maintains a stack of active scopes (or scope-stack). The span of the scope on the top of - * the scope stack is used as the parent for newly created spans. - * - *

This class is thread safe. - */ -public final class TracerContext { - // global TracerContext - /** Get the global TracerContext */ - public static TracerContext getGlobalContext() { - return null; - } - - /** - * Register the global TracerContext. - * - * @param context The context to register. - * @param replaceExisting If true, the existing global TracerContext will be replaced - * @return The old global TracerContext, or null if no previous context ws registered - */ - public static TracerContext registerGlobalContext( - final TracerContext context, final boolean replaceExisting) { - return null; - } - - /** @return True if a global TracerContext has been registered */ - public static boolean isRegistered() { - return false; - } - - private final Tracer tracer; - - public TracerContext(final Tracer tracer) { - this.tracer = tracer; - } - - /** @return The tracer associated with this TracerContext */ - public Tracer getTracer() { - return tracer; - } - - // TODO: convenience APIs like buildSpan, etc. - - /** - * Push a new scope to the top of this scope-stack. The scope's span will be the given span. - * - * @param span - * @return - */ - public Scope pushScope(final Span span) { - return null; - } - - /** - * Push a new scope to the top of this scope-stack. The scope's span will be the continuation's - * span. - * - * @param continuation - * @return - */ - public Scope pushScope(final Continuation continuation) { - return null; - } - - /** - * Pop the given scope off the top of the scope stack. - * - *

If the given scope is not the topmost scope on the stack an error will be thrown. - * - * @param scope the topmost scope in the scope stack. - */ - public void popScope(final Scope scope) {} - - /** @return The scope on the top of this scope-stack or null if there is no active scope. */ - public Scope peekScope() { - return null; - } -} diff --git a/dd-trace/README.md b/dd-trace/README.md deleted file mode 100644 index 7043db7a19..0000000000 --- a/dd-trace/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Datadog Tracer Internal API -Contains the core elements needed to create and report APM traces to Datadog. - -It's recommended to use `:dd-trace-ext` in addition to this api. diff --git a/dd-trace/dd-trace.gradle b/dd-trace/dd-trace.gradle deleted file mode 100644 index d39ef8b9db..0000000000 --- a/dd-trace/dd-trace.gradle +++ /dev/null @@ -1,27 +0,0 @@ -description = 'dd-trace' -apply from: "${rootDir}/gradle/java.gradle" - -minimumBranchCoverage = 0.9 -minimumInstructionCoverage = 0.9 -excludedClassesCoverage += [ - 'datadog.trace.tracer.Tracer.TracerBuilder', - 'datadog.trace.decorator.*', // TODO: remove when ready to write tests. -] - -dependencies { - annotationProcessor deps.autoservice - implementation deps.autoservice - - compile project(':dd-trace-api') - - compile deps.slf4j - compile deps.jackson - - compile project(':utils:gc-utils') - - // Spock uses that for mocking - testCompile deps.bytebuddy - testCompile group: 'org.objenesis', name: 'objenesis', version: '2.6' // Last version to support Java7 - testCompile group: 'nl.jqno.equalsverifier', name: 'equalsverifier', version: '2.5.2' // Last version to support Java7 - testCompile group: 'com.github.tomakehurst', name: 'wiremock', 'version': '2.20.0' -} diff --git a/dd-trace/src/main/java/datadog/trace/decorator/BaseDecorator.java b/dd-trace/src/main/java/datadog/trace/decorator/BaseDecorator.java deleted file mode 100644 index e1db97b0f6..0000000000 --- a/dd-trace/src/main/java/datadog/trace/decorator/BaseDecorator.java +++ /dev/null @@ -1,28 +0,0 @@ -package datadog.trace.decorator; - -import datadog.trace.tracer.Span; - -public abstract class BaseDecorator { - - protected abstract String component(); - - public Span afterStart(final Span span) { - assert span != null; - span.setMeta("component", component()); - return span; - } - - public Span beforeFinish(final Span span) { - assert span != null; - return span; - } - - public Span onError(final Span span, final Throwable throwable) { - assert span != null; - if (throwable != null) { - span.setErrored(true); - span.attachThrowable(throwable); - } - return span; - } -} diff --git a/dd-trace/src/main/java/datadog/trace/decorator/ClientDecorator.java b/dd-trace/src/main/java/datadog/trace/decorator/ClientDecorator.java deleted file mode 100644 index 221d86d3df..0000000000 --- a/dd-trace/src/main/java/datadog/trace/decorator/ClientDecorator.java +++ /dev/null @@ -1,21 +0,0 @@ -package datadog.trace.decorator; - -import datadog.trace.tracer.Span; - -public abstract class ClientDecorator extends BaseDecorator { - - protected abstract String service(); - - protected abstract String spanType(); - - @Override - public Span afterStart(final Span span) { - assert span != null; - if (service() != null) { - span.setService(service()); - } - span.setMeta("span.kind", "client"); - span.setMeta("span.type", spanType()); - return super.afterStart(span); - } -} diff --git a/dd-trace/src/main/java/datadog/trace/decorator/HttpClientDecorator.java b/dd-trace/src/main/java/datadog/trace/decorator/HttpClientDecorator.java deleted file mode 100644 index f7210f47ec..0000000000 --- a/dd-trace/src/main/java/datadog/trace/decorator/HttpClientDecorator.java +++ /dev/null @@ -1,45 +0,0 @@ -package datadog.trace.decorator; - -import datadog.trace.tracer.Span; - -public abstract class HttpClientDecorator extends ClientDecorator { - - protected abstract String method(REQUEST request); - - protected abstract String url(REQUEST request); - - protected abstract String hostname(RESPONSE request); - - protected abstract int port(RESPONSE request); - - protected abstract int status(RESPONSE response); - - @Override - protected String spanType() { - return "http"; - } - - public Span onRequest(final Span span, final REQUEST request) { - assert span != null; - if (request != null) { - span.setMeta("http.method", method(request)); - span.setMeta("http.url", url(request)); - } - return span; - } - - public Span onResponse(final Span span, final RESPONSE response) { - assert span != null; - if (response != null) { - span.setMeta("peer.hostname", hostname(response)); - span.setMeta("peer.port", port(response)); - - final int status = status(response); - span.setMeta("http.status", status); - if (400 <= status && status < 500) { - span.setErrored(true); - } - } - return span; - } -} diff --git a/dd-trace/src/main/java/datadog/trace/decorator/HttpServerDecorator.java b/dd-trace/src/main/java/datadog/trace/decorator/HttpServerDecorator.java deleted file mode 100644 index 691de60ed5..0000000000 --- a/dd-trace/src/main/java/datadog/trace/decorator/HttpServerDecorator.java +++ /dev/null @@ -1,55 +0,0 @@ -package datadog.trace.decorator; - -import datadog.trace.tracer.Span; - -public abstract class HttpServerDecorator extends ServerDecorator { - - protected abstract String method(REQUEST request); - - protected abstract String url(REQUEST request); - - protected abstract String hostname(REQUEST request); - - protected abstract int port(REQUEST request); - - protected abstract int status(RESPONSE response); - - @Override - protected String spanType() { - return "web"; - } - - public Span onRequest(final Span span, final REQUEST request) { - assert span != null; - if (request != null) { - span.setMeta("http.method", method(request)); - span.setMeta("http.url", url(request)); - span.setMeta("peer.hostname", hostname(request)); - span.setMeta("peer.port", port(request)); - // TODO set resource name from URL. - } - return span; - } - - public Span onResponse(final Span span, final RESPONSE response) { - assert span != null; - if (response != null) { - span.setMeta("http.status", status(response)); - if (status(response) >= 500) { - span.setErrored(true); - } - } - return span; - } - - @Override - public Span onError(final Span span, final Throwable throwable) { - assert span != null; - final Object status = span.getMeta("http.status"); - if (status == null || status.equals(200)) { - // Ensure status set correctly - span.setMeta("http.status", 500); - } - return super.onError(span, throwable); - } -} diff --git a/dd-trace/src/main/java/datadog/trace/decorator/ServerDecorator.java b/dd-trace/src/main/java/datadog/trace/decorator/ServerDecorator.java deleted file mode 100644 index 8af0a8533f..0000000000 --- a/dd-trace/src/main/java/datadog/trace/decorator/ServerDecorator.java +++ /dev/null @@ -1,16 +0,0 @@ -package datadog.trace.decorator; - -import datadog.trace.tracer.Span; - -public abstract class ServerDecorator extends BaseDecorator { - - protected abstract String spanType(); - - @Override - public Span afterStart(final Span span) { - assert span != null; - span.setMeta("span.kind", "server"); - span.setMeta("span.type", spanType()); - return super.afterStart(span); - } -} diff --git a/dd-trace/src/main/java/datadog/trace/tracer/Clock.java b/dd-trace/src/main/java/datadog/trace/tracer/Clock.java deleted file mode 100644 index 1aca0cb0db..0000000000 --- a/dd-trace/src/main/java/datadog/trace/tracer/Clock.java +++ /dev/null @@ -1,108 +0,0 @@ -package datadog.trace.tracer; - -import java.util.concurrent.TimeUnit; -import lombok.EqualsAndHashCode; - -/** - * This is a wrapper to System clock that provides an easier way to get nanosecond precision. - * - *

The JDK provides two clocks: - *

  • one in nanoseconds, for precision, but it can only use to measure durations - *
  • one in milliseconds, for accuracy, useful to provide epoch time - * - *

    Once created this class captures current time with millisecond presition and current - * nanosecond counter. - * - *

    It provides an API to create {@link Timestamp} that can be used to measure durations with - * nanosecond precision. - */ -@EqualsAndHashCode -class Clock { - - /** Tracer that created this clock */ - private final Tracer tracer; - - /** Trace start time in nano seconds measured up to a millisecond accuracy */ - private final long startTimeNano; - /** Nano ticks counter when clock is created */ - private final long startNanoTicks; - - Clock(final Tracer tracer) { - this.tracer = tracer; - startTimeNano = epochTimeNano(); - startNanoTicks = nanoTicks(); - } - - /** @return {@link Tracer} that created this clock. */ - public Tracer getTracer() { - return tracer; - } - - /** - * Create new timestamp instance for current time. - * - * @return new timestamp capturing current time. - */ - public Timestamp createCurrentTimestamp() { - return new Timestamp(this); - } - - /** - * Create new timestamp instance for current time. - * - * @return new timestamp capturing current time. - */ - public Timestamp createTimestampForTime(final long time, final TimeUnit unit) { - return new Timestamp(this, time, unit); - } - - /** - * Get the current nanos ticks (i.e. System.nanoTime()), this method can't be use for date - * accuracy (only duration calculations). - * - * @return The current nanos ticks. - */ - long nanoTicks() { - return System.nanoTime(); - } - - /** - * Get the current epoch time in micros. - * - *

    Note: The actual precision is the millis. - * - * @return the current epoch time in micros. - */ - long epochTimeMicro() { - return TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis()); - } - - /** - * Get the current epoch time in nanos. - * - *

    Note: The actual precision is the millis. This will overflow ~290 years after epoch. - * - * @return the current epoch time in nanos. - */ - long epochTimeNano() { - return TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()); - } - - /** - * Get time this clock instance was created in nanos. - * - * @return the time this clock instance was created in nanos. - */ - long getStartTimeNano() { - return startTimeNano; - } - - /** - * Get nano ticks counter value when this clock instance was created. - * - * @return nano ticks counter value when this clock instance was created. - */ - long getStartNanoTicks() { - return startNanoTicks; - } -} diff --git a/dd-trace/src/main/java/datadog/trace/tracer/Continuation.java b/dd-trace/src/main/java/datadog/trace/tracer/Continuation.java deleted file mode 100644 index 823a108558..0000000000 --- a/dd-trace/src/main/java/datadog/trace/tracer/Continuation.java +++ /dev/null @@ -1,26 +0,0 @@ -package datadog.trace.tracer; - -/** - * Continuations are used to prevent a trace from reporting without creating a span. - * - *

    All spans are thread safe. - * - *

    To create a Span, see {@link Trace#createContinuation(Span parentSpan)} - */ -public interface Continuation { - - /** @return parent span used to create this continuation. */ - Span getSpan(); - - /** @return trace used to create this continuation. */ - Trace getTrace(); - - /** @return true iff continuation has been closed. */ - boolean isClosed(); - - /** - * Close the continuation. Continuation's trace will not block reporting on account of this - * continuation. - */ - void close(); -} diff --git a/dd-trace/src/main/java/datadog/trace/tracer/ContinuationImpl.java b/dd-trace/src/main/java/datadog/trace/tracer/ContinuationImpl.java deleted file mode 100644 index c004abadb3..0000000000 --- a/dd-trace/src/main/java/datadog/trace/tracer/ContinuationImpl.java +++ /dev/null @@ -1,72 +0,0 @@ -package datadog.trace.tracer; - -import lombok.extern.slf4j.Slf4j; - -/** Concrete implementation of a continuation */ -@Slf4j -class ContinuationImpl implements Continuation { - - private final TraceInternal trace; - private final Span span; - private volatile boolean closed = false; - - ContinuationImpl(final TraceInternal trace, final Span span) { - this.trace = trace; - this.span = span; - } - - @Override - public Trace getTrace() { - return trace; - } - - @Override - public Span getSpan() { - return span; - } - - @Override - public boolean isClosed() { - return closed; - } - - @Override - public synchronized void close() { - if (closed) { - reportUsageError("Attempted to close continuation that is already closed: %s", this); - } else { - closeContinuation(false); - } - } - - @Override - protected synchronized void finalize() { - try { - if (!closed) { - log.debug( - "Closing continuation due to GC, this will prevent trace from being reported: {}", - this); - closeContinuation(true); - } - } catch (final Throwable t) { - // Exceptions thrown in finalizer are eaten up and ignored, so log them instead - log.debug("Span finalizer had thrown an exception: ", t); - } - } - - /** - * Helper method to perform operations needed to close the continuation. - * - *

    Note: This has to be called under object lock. - * - * @param invalid true iff continuation is being closed due to GC, this will make trace invalid. - */ - private void closeContinuation(final boolean invalid) { - closed = true; - trace.closeContinuation(this, invalid); - } - - private void reportUsageError(final String message, final Object... args) { - trace.getTracer().reportError(message, args); - } -} diff --git a/dd-trace/src/main/java/datadog/trace/tracer/Interceptor.java b/dd-trace/src/main/java/datadog/trace/tracer/Interceptor.java deleted file mode 100644 index 77271f9dca..0000000000 --- a/dd-trace/src/main/java/datadog/trace/tracer/Interceptor.java +++ /dev/null @@ -1,29 +0,0 @@ -package datadog.trace.tracer; - -/** - * An Interceptor allows adding hooks to particular events of a span starting and finishing and also - * trace being written to backend. - */ -public interface Interceptor { - /** - * Called after a span is started. - * - * @param span the started span. - */ - void afterSpanStarted(Span span); - - /** - * Called before a span is finished. - * - * @param span the span to be finished. - */ - void beforeSpanFinished(Span span); - - /** - * Invoked when a trace is eligible for writing but hasn't been handed off to its writer yet. - * - * @param trace The intercepted trace. - * @return modified trace. Null if trace is to be dropped. - */ - Trace beforeTraceWritten(Trace trace); -} diff --git a/dd-trace/src/main/java/datadog/trace/tracer/LogRateLimiter.java b/dd-trace/src/main/java/datadog/trace/tracer/LogRateLimiter.java deleted file mode 100644 index ae957d3698..0000000000 --- a/dd-trace/src/main/java/datadog/trace/tracer/LogRateLimiter.java +++ /dev/null @@ -1,51 +0,0 @@ -package datadog.trace.tracer; - -import java.util.concurrent.TimeUnit; -import org.slf4j.Logger; - -/** - * Helper class to limit errors logged into application's error log. - * - *

    TODO: can we make this class not public? - * - *

    TODO: once we drop 1.7 support we should be able to use {@code java.time.Clock} instead of - * {@code System.currentTimeMillis} to simplify testing. - */ -public class LogRateLimiter { - - private final Logger log; - private final long millisecondsBetweenLog; - - private long nextAllowedLogTime = 0; - - public LogRateLimiter(final Logger log, final long millisecondsBetweenLog) { - this.log = log; - this.millisecondsBetweenLog = millisecondsBetweenLog; - } - - public synchronized void warn(String message, final Object... arguments) { - if (log.isDebugEnabled()) { - log.debug(message, arguments); - } else if (nextAllowedLogTime <= System.currentTimeMillis()) { - message += - " (going silent for " - + TimeUnit.MILLISECONDS.toMinutes(millisecondsBetweenLog) - + " minutes)"; - nextAllowedLogTime = System.currentTimeMillis() + millisecondsBetweenLog; - log.warn(message, arguments); - } - } - - public synchronized void error(String message, final Object... arguments) { - if (log.isDebugEnabled()) { - log.debug(message, arguments); - } else if (nextAllowedLogTime <= System.currentTimeMillis()) { - message += - " (going silent for " - + TimeUnit.MILLISECONDS.toMinutes(millisecondsBetweenLog) - + " minutes)"; - nextAllowedLogTime = System.currentTimeMillis() + millisecondsBetweenLog; - log.error(message, arguments); - } - } -} diff --git a/dd-trace/src/main/java/datadog/trace/tracer/Span.java b/dd-trace/src/main/java/datadog/trace/tracer/Span.java deleted file mode 100644 index 04f9630e88..0000000000 --- a/dd-trace/src/main/java/datadog/trace/tracer/Span.java +++ /dev/null @@ -1,141 +0,0 @@ -package datadog.trace.tracer; - -import java.util.Map; - -/** - * A single measurement of time with arbitrary key-value attributes. - * - *

    All spans are thread safe. - * - *

    To create a Span, see {@link Trace#createSpan(SpanContext parentContext, Timestamp - * startTimestamp)} - */ -public interface Span { - - /** @return The trace this span is associated with. */ - Trace getTrace(); - - /** @return start timestamp of this span */ - public Timestamp getStartTimestamp(); - - /** @return duration of this span in nanoseconds or null if span is not finished */ - public Long getDuration(); - - /** @return true if a finish method has been invoked on this span. */ - boolean isFinished(); - - /** - * Get the span context for this span - required attributes to report to datadog. - * - *

    See https://docs.datadoghq.com/api/?lang=python#tracing - * - * @return the span context. - */ - SpanContext getContext(); - - /** @return the span's service. */ - String getService(); - - /** - * Set the span's service. - * - *

    May not exceed 100 characters. - * - * @param service the new service for the span. - */ - void setService(String service); - - /** @return the span's resource. */ - String getResource(); - - /** - * Span the span's resource (e.g. http endpoint). - * - *

    May not exceed 5000 characters. - * - * @param resource the new resource for the span. - */ - void setResource(String resource); - - /** @return the span's type. */ - String getType(); - - /** - * Set the span's type (web, db, etc). {@see DDSpanTypes}. - * - * @param type the new type of the span. - */ - void setType(String type); - - /** @return the span's name. */ - String getName(); - - /** - * Set the span's name. - * - *

    May not exceed 100 characters. - * - * @param name the new name for the span. - */ - void setName(String name); - - /** @return true iff span was marked as error span. */ - boolean isErrored(); - - /** - * Mark the span as having an error. - * - * @param errored true if the span has an error. - */ - void setErrored(boolean errored); - - /** - * Attach a throwable to this span. - * - * @param throwable throwable to attach - */ - void attachThrowable(Throwable throwable); - - /** - * Get all the metadata attached to this span. - * - * @return immutable map of span metadata. - */ - Map getMeta(); - - /** - * Get a meta value on a span. - * - * @param key The meta key - * @return The value currently associated with the given key. Null if no associated. - */ - Object getMeta(String key); - - /** - * Set key-value metadata on the span. - * - * @param key to set - * @param value to set - */ - void setMeta(String key, String value); - - /** {@link Span#setMeta(String, String)} for boolean values */ - void setMeta(String key, Boolean value); - - /** {@link Span#setMeta(String, String)} for number values */ - void setMeta(String key, Number value); - - /** Stop the span's timer. Has no effect if the span is already finished. */ - void finish(); - - /** - * Stop the span's timer. Has no effect if the span is already finished. - * - *

    It's undefined behavior to specify a finish timestamp which occurred before this span's - * start timestamp. - * - * @param finishTimestampNanoseconds Epoch time in nanoseconds. - */ - // FIXME: This should take a Timestamp object instead. - void finish(long finishTimestampNanoseconds); -} diff --git a/dd-trace/src/main/java/datadog/trace/tracer/SpanContext.java b/dd-trace/src/main/java/datadog/trace/tracer/SpanContext.java deleted file mode 100644 index d71137058f..0000000000 --- a/dd-trace/src/main/java/datadog/trace/tracer/SpanContext.java +++ /dev/null @@ -1,40 +0,0 @@ -package datadog.trace.tracer; - -/** - * All attributes of a {@link Span} which propagate for distributed tracing. - * - *

    All Spans must have a SpanContext, but not all SpanContexts require a span. - * - *

    All SpanContexts are thread safe. - */ -public interface SpanContext { - - /** - * Get this context's trace id. - * - * @return 64 bit unsigned integer in String format. - */ - String getTraceId(); - - /** - * Get this context's parent span id. - * - * @return 64 bit unsigned integer in String format. - */ - String getParentId(); - - /** - * Get this context's span id. - * - * @return 64 bit unsigned integer in String format. - */ - String getSpanId(); - - /** - * Get the sampling flag for this context. - * - * @return sampling flag for null if no sampling flags are set. - */ - // TODO: should we add a @Nullable annotation to our project? - Integer getSamplingFlags(); -} diff --git a/dd-trace/src/main/java/datadog/trace/tracer/SpanContextImpl.java b/dd-trace/src/main/java/datadog/trace/tracer/SpanContextImpl.java deleted file mode 100644 index 3ee4f10d37..0000000000 --- a/dd-trace/src/main/java/datadog/trace/tracer/SpanContextImpl.java +++ /dev/null @@ -1,69 +0,0 @@ -package datadog.trace.tracer; - -import datadog.trace.api.sampling.PrioritySampling; -import java.math.BigInteger; -import java.util.concurrent.ThreadLocalRandom; -import lombok.EqualsAndHashCode; - -@EqualsAndHashCode -class SpanContextImpl implements SpanContext { - - public static final String ZERO = "0"; - - private final String traceId; - private final String parentId; - private final String spanId; - - SpanContextImpl(final String traceId, final String parentId, final String spanId) { - this.traceId = traceId; - this.parentId = parentId; - this.spanId = spanId; - } - - @Override - public String getTraceId() { - return traceId; - } - - @Override - public String getParentId() { - return parentId; - } - - @Override - public String getSpanId() { - return spanId; - } - - // TODO: Implement proper priority handling methods - @Override - public Integer getSamplingFlags() { - return PrioritySampling.SAMPLER_KEEP; - } - - static SpanContext fromParent(final SpanContext parent) { - final String traceId; - final String parentId; - if (parent == null) { - traceId = generateNewId(); - parentId = ZERO; - } else { - traceId = parent.getTraceId(); - parentId = parent.getSpanId(); - } - return new SpanContextImpl(traceId, parentId, generateNewId()); - } - - /** @return Random 64bit unsigned number strictly greater than zero */ - static String generateNewId() { - // Note: we can probably optimize this using {@link ThreadLocalRandom#nextLong()} - // and {@link Long#toUnsignedString} but {@link Long#toUnsignedString} is only - // available in Java8+ and {@link ThreadLocalRandom#nextLong()} cannot - // generate negative numbers before Java8. - BigInteger result = BigInteger.ZERO; - while (result.equals(BigInteger.ZERO)) { - result = new BigInteger(64, ThreadLocalRandom.current()); - } - return result.toString(); - } -} diff --git a/dd-trace/src/main/java/datadog/trace/tracer/SpanImpl.java b/dd-trace/src/main/java/datadog/trace/tracer/SpanImpl.java deleted file mode 100644 index a311aa11d0..0000000000 --- a/dd-trace/src/main/java/datadog/trace/tracer/SpanImpl.java +++ /dev/null @@ -1,345 +0,0 @@ -package datadog.trace.tracer; - -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; -import com.fasterxml.jackson.annotation.JsonFormat; -import com.fasterxml.jackson.annotation.JsonGetter; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; -import datadog.trace.api.DDTags; -import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.math.BigInteger; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import lombok.extern.slf4j.Slf4j; - -/** Concrete implementation of a span */ -@Slf4j -// Disable autodetection of fields and accessors -@JsonAutoDetect( - fieldVisibility = Visibility.NONE, - setterVisibility = Visibility.NONE, - getterVisibility = Visibility.NONE, - isGetterVisibility = Visibility.NONE, - creatorVisibility = Visibility.NONE) -class SpanImpl implements Span { - - private final TraceInternal trace; - - private final SpanContext context; - private final Timestamp startTimestamp; - - /* Note: some fields are volatile so we could make getters non synchronized. - Alternatively we could make getters synchronized, but this may create more contention. - */ - private volatile Long duration = null; - - private volatile String service; - private volatile String resource; - private volatile String type; - private volatile String name; - private volatile boolean errored = false; - - private final Map meta = new HashMap<>(); - - private final List interceptors; - - /** - * Create a span with the a specific startTimestamp timestamp. - * - * @param trace The trace to associate this span with. - * @param parentContext identifies the parent of this span. May be null. - * @param startTimestamp timestamp when this span was started. - */ - SpanImpl( - final TraceInternal trace, final SpanContext parentContext, final Timestamp startTimestamp) { - this.trace = trace; - - context = SpanContextImpl.fromParent(parentContext); - - if (startTimestamp == null) { - reportUsageError("Cannot create span without timestamp: %s", trace); - throw new TraceException(String.format("Cannot create span without timestamp: %s", trace)); - } - this.startTimestamp = startTimestamp; - service = trace.getTracer().getDefaultServiceName(); - interceptors = trace.getTracer().getInterceptors(); - - for (final Interceptor interceptor : interceptors) { - interceptor.afterSpanStarted(this); - } - } - - @Override - public Trace getTrace() { - return trace; - } - - @Override - public SpanContext getContext() { - return context; - } - - @JsonGetter("trace_id") - @JsonSerialize(using = UInt64IDStringSerializer.class) - public String getTraceId() { - return context.getTraceId(); - } - - @JsonGetter("span_id") - @JsonSerialize(using = UInt64IDStringSerializer.class) - public String getSpanId() { - return context.getSpanId(); - } - - @JsonGetter("parent_id") - @JsonSerialize(using = UInt64IDStringSerializer.class) - public String getParentId() { - return context.getParentId(); - } - - @Override - @JsonGetter("start") - public Timestamp getStartTimestamp() { - return startTimestamp; - } - - @Override - @JsonGetter("duration") - public Long getDuration() { - return duration; - } - - @Override - public boolean isFinished() { - return duration != null; - } - - @Override - @JsonGetter("service") - public String getService() { - return service; - } - - @Override - public synchronized void setService(final String service) { - if (isFinished()) { - reportSetterUsageError("service"); - } else { - this.service = service; - } - } - - @Override - @JsonGetter("resource") - public String getResource() { - return resource; - } - - @Override - public synchronized void setResource(final String resource) { - if (isFinished()) { - reportSetterUsageError("resource"); - } else { - this.resource = resource; - } - } - - @Override - @JsonGetter("type") - public String getType() { - return type; - } - - @Override - public synchronized void setType(final String type) { - if (isFinished()) { - reportSetterUsageError("type"); - } else { - this.type = type; - } - } - - @Override - @JsonGetter("name") - public String getName() { - return name; - } - - @Override - public synchronized void setName(final String name) { - if (isFinished()) { - reportSetterUsageError("name"); - } else { - this.name = name; - } - } - - @Override - @JsonGetter("error") - @JsonFormat(shape = JsonFormat.Shape.NUMBER) - public boolean isErrored() { - return errored; - } - - @Override - public synchronized void attachThrowable(final Throwable throwable) { - if (isFinished()) { - reportSetterUsageError("throwable"); - } else { - setErrored(true); - - setMeta(DDTags.ERROR_MSG, throwable.getMessage()); - setMeta(DDTags.ERROR_TYPE, throwable.getClass().getName()); - - final StringWriter errorString = new StringWriter(); - throwable.printStackTrace(new PrintWriter(errorString)); - setMeta(DDTags.ERROR_STACK, errorString.toString()); - } - } - - @Override - public synchronized void setErrored(final boolean errored) { - if (isFinished()) { - reportSetterUsageError("errored"); - } else { - this.errored = errored; - } - } - - @Override - public synchronized Map getMeta() { - return Collections.unmodifiableMap(meta); - } - - /** - * The agent expects meta's values to be strings. - * - * @return a copy of meta with all values converted to strings. - */ - @JsonGetter("meta") - synchronized Map getMetaString() { - final Map result = new HashMap<>(meta.size()); - for (final Map.Entry entry : meta.entrySet()) { - result.put(entry.getKey(), String.valueOf(entry.getValue())); - } - return result; - } - - @Override - public synchronized Object getMeta(final String key) { - return meta.get(key); - } - - protected synchronized void setMeta(final String key, final Object value) { - if (isFinished()) { - reportSetterUsageError("meta value " + key); - } else { - if (value == null) { - meta.remove(key); - } else { - meta.put(key, value); - } - } - } - - @Override - public void setMeta(final String key, final String value) { - setMeta(key, (Object) value); - } - - @Override - public void setMeta(final String key, final Boolean value) { - setMeta(key, (Object) value); - } - - @Override - public void setMeta(final String key, final Number value) { - setMeta(key, (Object) value); - } - - // FIXME: Add metrics support and json rendering for metrics - - @Override - public synchronized void finish() { - if (isFinished()) { - reportUsageError("Attempted to finish span that is already finished: %s", this); - } else { - finishSpan(startTimestamp.getDuration(), false); - } - } - - // FIXME: This should take a Timestamp object instead. - @Override - public synchronized void finish(final long finishTimestampNanoseconds) { - if (isFinished()) { - reportUsageError("Attempted to finish span that is already finish: %s", this); - } else { - finishSpan(startTimestamp.getDuration(finishTimestampNanoseconds), false); - } - } - - @Override - protected synchronized void finalize() { - try { - // Note: according to docs finalize is only called once for a given instance - even if - // instance is 'revived' from the dead by passing reference to some other object and - // then dies again. - if (!isFinished()) { - log.debug( - "Finishing span due to GC, this will prevent trace from being reported: {}", this); - finishSpan(startTimestamp.getDuration(), true); - } - } catch (final Throwable t) { - // Exceptions thrown in finalizer are eaten up and ignored, so log them instead - log.debug("Span finalizer had thrown an exception: ", t); - } - } - - /** - * Helper method to perform operations to finish the span. - * - *

    Note: This has to be called under object lock. - * - * @param duration duration of the span. - * @param fromGC true iff we are closing span because it is being GCed, this will make trace - * invalid. - */ - private void finishSpan(final long duration, final boolean fromGC) { - // Run interceptors in 'reverse' order - for (int i = interceptors.size() - 1; i >= 0; i--) { - interceptors.get(i).beforeSpanFinished(this); - } - this.duration = duration; - trace.finishSpan(this, fromGC); - } - - private void reportUsageError(final String message, final Object... args) { - trace.getTracer().reportError(message, args); - } - - private void reportSetterUsageError(final String fieldName) { - reportUsageError("Attempted to set '%s' when span is already finished: %s", fieldName, this); - } - - /** Helper to serialize string value as 64 bit unsigned integer */ - private static class UInt64IDStringSerializer extends StdSerializer { - - public UInt64IDStringSerializer() { - super(String.class); - } - - @Override - public void serialize( - final String value, final JsonGenerator jsonGenerator, final SerializerProvider provider) - throws IOException { - jsonGenerator.writeNumber(new BigInteger(value)); - } - } -} diff --git a/dd-trace/src/main/java/datadog/trace/tracer/Timestamp.java b/dd-trace/src/main/java/datadog/trace/tracer/Timestamp.java deleted file mode 100644 index a619bd8ccb..0000000000 --- a/dd-trace/src/main/java/datadog/trace/tracer/Timestamp.java +++ /dev/null @@ -1,99 +0,0 @@ -package datadog.trace.tracer; - -import static java.lang.Math.max; - -import com.fasterxml.jackson.annotation.JsonValue; -import java.util.concurrent.TimeUnit; -import lombok.EqualsAndHashCode; - -/** - * Class that encapsulations notion of a given timestamp, or instant in time. - * - *

    Timestamps are created by a [@link Clock} instance. - */ -@EqualsAndHashCode -public class Timestamp { - - private final Clock clock; - private final long nanoTicks; - - /** - * Create timestamp for a given clock and given nanoTicks state - * - * @param clock clock instance - */ - Timestamp(final Clock clock) { - this.clock = clock; - nanoTicks = clock.nanoTicks(); - } - - /** - * Create timestamp for a given clock and given start time and precision - * - * @param clock clock instance - */ - Timestamp(final Clock clock, final long time, final TimeUnit unit) { - this.clock = clock; - final long currentTime = clock.epochTimeNano(); - final long currentTick = clock.nanoTicks(); - final long desiredTime = unit.toNanos(time); - final long offset = currentTime - desiredTime; - nanoTicks = currentTick - offset; - } - - /** @return clock instance used by this timestamp */ - Clock getClock() { - return clock; - } - - /** @return time since epoch in nanoseconds */ - @JsonValue - public long getTime() { - return clock.getStartTimeNano() + startTicksOffset(); - } - - /** @return duration in nanoseconds from this time stamp to current time. */ - public long getDuration() { - return getDuration(clock.createCurrentTimestamp()); - } - - /** - * Get duration in nanoseconds from this time stamp to provided finish timestamp. - * - * @param finishTimestamp finish timestamp to use as period's end. - * @return duration in nanoseconds. - */ - public long getDuration(final Timestamp finishTimestamp) { - if (clock != finishTimestamp.clock) { - clock - .getTracer() - .reportError( - "Trying to find duration between two timestamps created by different clocks. Current clock: %s, finish timestamp clock: %s", - clock, finishTimestamp.clock); - // Do our best to try to calculate nano-second time using millisecond clock start time and - // nanosecond offset. - return max(0, finishTimestamp.getTime() - getTime()); - } - return max(0, finishTimestamp.nanoTicks - nanoTicks); - } - - /** - * Duration in nanoseconds for external finish time. - * - *

    Note: since we can only get time with millisecond precision in Java this ends up being - * effectively millisecond precision duration converted to nanoseconds. - * - * @param finishTimeNanoseconds - * @return duration in nanoseconds (with millisecond precision). - */ - public long getDuration(final long finishTimeNanoseconds) { - // This is calculated as the difference between finish time and clock start time and then - // subtracting difference between timestamp nanoticks and clock start nanoticks to account for - // time after clock has been created and before timestamp has been created. - return max(0, finishTimeNanoseconds - clock.getStartTimeNano() - startTicksOffset()); - } - - private long startTicksOffset() { - return nanoTicks - clock.getStartNanoTicks(); - } -} diff --git a/dd-trace/src/main/java/datadog/trace/tracer/Trace.java b/dd-trace/src/main/java/datadog/trace/tracer/Trace.java deleted file mode 100644 index 0fe5c1eb5f..0000000000 --- a/dd-trace/src/main/java/datadog/trace/tracer/Trace.java +++ /dev/null @@ -1,59 +0,0 @@ -package datadog.trace.tracer; - -import java.util.List; - -/** - * A tree of {@link Span}s with a single root node plus logic to determine when to report said tree - * to the backend. - * - *

    A trace will be written when all of its spans are finished and all trace continuations are - * closed. - * - *

    To create a Trace, see {@link Tracer#buildTrace(SpanContext parentContext)} - */ -public interface Trace { - /** @return the tracer which created this trace. */ - Tracer getTracer(); - - /** @return the root span for this trace. This will never be null. */ - Span getRootSpan(); - - /** - * @return list of spans for this trace. Note: if trace is not finished this will report error. - */ - List getSpans(); - - /** @return true iff trace is valid (invalid traces should not be reported). */ - boolean isValid(); - - /** @return current timestamp using this trace's clock */ - Timestamp createCurrentTimestamp(); - - /** - * Create a new span in this trace as a child of the given parent context. - * - * @param parentContext the parent to use. Must be a span in this trace. - * @return the new span. It is the caller's responsibility to ensure {@link Span#finish()} is - * eventually invoked on this span. - */ - Span createSpan(final SpanContext parentContext); - - /** - * Create a new span in this trace as a child of the given parent context. - * - * @param parentContext the parent to use. Must be a span in this trace. - * @param startTimestamp timestamp to use as start timestamp for a new span. - * @return the new span. It is the caller's responsibility to ensure {@link Span#finish()} is - * eventually invoked on this span. - */ - Span createSpan(final SpanContext parentContext, final Timestamp startTimestamp); - - /** - * Create a new continuation for this trace - * - * @param parentSpan the parent to use. Must be a span in this trace. - * @return the new continuation. It is the caller's responsibility to ensure {@link - * Continuation#close()} is eventually invoked on this continuation. - */ - Continuation createContinuation(Span parentSpan); -} diff --git a/dd-trace/src/main/java/datadog/trace/tracer/TraceException.java b/dd-trace/src/main/java/datadog/trace/tracer/TraceException.java deleted file mode 100644 index f2cb0a1789..0000000000 --- a/dd-trace/src/main/java/datadog/trace/tracer/TraceException.java +++ /dev/null @@ -1,54 +0,0 @@ -package datadog.trace.tracer; - -/** Tracer-specific runtime exception. */ -public class TraceException extends RuntimeException { - - /** - * Constructs a new trace exception with {@code null} as its detail message. The cause is not - * initialized, and may subsequently be initialized by a call to {@link #initCause}. - */ - public TraceException() { - super(); - } - - /** - * Constructs a new trace exception with the specified detail message. The cause is not - * initialized, and may subsequently be initialized by a call to {@link #initCause}. - * - * @param message the detail message. The detail message is saved for later retrieval by the - * {@link #getMessage()} method. - */ - public TraceException(final String message) { - super(message); - } - - /** - * Constructs a new trace exception with the specified detail message and cause. - * - *

    Note that the detail message associated with {@code cause} is not automatically - * incorporated in this runtime exception's detail message. - * - * @param message the detail message (which is saved for later retrieval by the {@link - * #getMessage()} method). - * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method). - * (A null value is permitted, and indicates that the cause is nonexistent or - * unknown.) - */ - public TraceException(final String message, final Throwable cause) { - super(message, cause); - } - - /** - * Constructs a new runtime exception with the specified cause and a detail message of - * (cause==null ? null : cause.toString()) (which typically contains the class and detail - * message of cause). This constructor is useful for runtime exceptions that are little - * more than wrappers for other throwables. - * - * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method). - * (A null value is permitted, and indicates that the cause is nonexistent or - * unknown.) - */ - public TraceException(final Throwable cause) { - super(cause); - } -} diff --git a/dd-trace/src/main/java/datadog/trace/tracer/TraceImpl.java b/dd-trace/src/main/java/datadog/trace/tracer/TraceImpl.java deleted file mode 100644 index a79ce3722f..0000000000 --- a/dd-trace/src/main/java/datadog/trace/tracer/TraceImpl.java +++ /dev/null @@ -1,199 +0,0 @@ -package datadog.trace.tracer; - -import com.fasterxml.jackson.annotation.JsonValue; -import datadog.trace.tracer.writer.Writer; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.WeakHashMap; - -class TraceImpl implements TraceInternal { - - /* We use weakly referenced sets to track 'in-flight' spans and continuations. We use span/continuation's - finalizer to notify trace that span/continuation is being GCed. - If any part of the trace (span or continuation) has been was finished (closed) via GC then trace would be - marked as 'invalid' and will not be reported the the backend. Instead only writer's counter would be incremented. - This allows us not to report traces that have wrong timing information. - Note: instead of using {@link WeakHashMap} we may want to consider using more fancy implementations from - {@link datadog.trace.agent.tooling.WeakMapSuppliers}. If we do this care should be taken to avoid creating - cleanup threads per trace. - */ - private final Set inFlightSpans = - Collections.newSetFromMap(new WeakHashMap()); - private final Set inFlightContinuations = - Collections.newSetFromMap(new WeakHashMap()); - - /** Strong refs to spans which are closed */ - private final List finishedSpans = new ArrayList(); - - private final Tracer tracer; - private final Clock clock; - private final Span rootSpan; - private boolean valid = true; - private boolean finished = false; - - /** - * Create a new Trace. - * - * @param tracer the Tracer to apply settings from. - */ - TraceImpl( - final Tracer tracer, - final SpanContext rootSpanParentContext, - final Timestamp rootSpanStartTimestamp) { - this.tracer = tracer; - clock = rootSpanStartTimestamp.getClock(); - rootSpan = new SpanImpl(this, rootSpanParentContext, rootSpanStartTimestamp); - inFlightSpans.add(rootSpan); - } - - @Override - public Tracer getTracer() { - return tracer; - } - - @Override - public Span getRootSpan() { - return rootSpan; - } - - @Override - @JsonValue - public synchronized List getSpans() { - if (!finished) { - tracer.reportError("Cannot get spans, trace is not finished yet: %s", this); - return Collections.EMPTY_LIST; - } - return Collections.unmodifiableList(finishedSpans); - } - - @Override - public synchronized boolean isValid() { - return valid; - } - - @Override - public Timestamp createCurrentTimestamp() { - return clock.createCurrentTimestamp(); - } - - @Override - public Span createSpan(final SpanContext parentContext) { - return createSpan(parentContext, createCurrentTimestamp()); - } - - @Override - public synchronized Span createSpan( - final SpanContext parentContext, final Timestamp startTimestamp) { - checkTraceFinished("create span"); - if (parentContext == null) { - throw new TraceException("Got null parent context, trace: " + this); - } - if (!parentContext.getTraceId().equals(rootSpan.getContext().getTraceId())) { - throw new TraceException( - String.format( - "Wrong trace id when creating a span. Got %s, expected %s", - parentContext.getTraceId(), rootSpan.getContext().getTraceId())); - } - final Span span = new SpanImpl(this, parentContext, startTimestamp); - inFlightSpans.add(span); - return span; - } - - @Override - public synchronized Continuation createContinuation(final Span span) { - checkTraceFinished("create continuation"); - if (span == null) { - throw new TraceException("Got null parent span, trace: " + this); - } - if (!span.getContext().getTraceId().equals(rootSpan.getContext().getTraceId())) { - throw new TraceException( - String.format( - "Wrong trace id when creating a span. Got %s, expected %s", - span.getContext().getTraceId(), rootSpan.getContext().getTraceId())); - } - final Continuation continuation = new ContinuationImpl(this, span); - inFlightContinuations.add(continuation); - return continuation; - } - - @Override - public synchronized void finishSpan(final Span span, final boolean invalid) { - checkTraceFinished("finish span"); - if (!inFlightSpans.contains(span)) { - tracer.reportError("Trace doesn't contain continuation to finish: %s, trace: %s", span, this); - return; - } - if (invalid) { - valid = false; - } - inFlightSpans.remove(span); - finishedSpans.add(span); - checkAndWriteTrace(); - } - - @Override - public synchronized void closeContinuation( - final Continuation continuation, final boolean invalid) { - checkTraceFinished("close continuation"); - if (!inFlightContinuations.contains(continuation)) { - tracer.reportError( - "Trace doesn't contain continuation to finish: %s, trace: %s", continuation, this); - return; - } - if (invalid) { - valid = false; - } - inFlightContinuations.remove(continuation); - checkAndWriteTrace(); - } - - /** - * Helper to check if trace is ready to be written and write it if it is. - * - *

    Note: This has to be called under object lock. - */ - private void checkAndWriteTrace() { - if (inFlightSpans.isEmpty() && inFlightContinuations.isEmpty()) { - final Writer writer = tracer.getWriter(); - writer.incrementTraceCount(); - final Trace trace = runInterceptorsBeforeTraceWritten(this); - if (trace != null && tracer.getSampler().sample(trace)) { - writer.write(trace); - } - finished = true; - } - } - - /** - * Helper to run interceptor hooks before trace is finished. - * - *

    Note: This has to be called under object lock. - */ - private Trace runInterceptorsBeforeTraceWritten(Trace trace) { - final List interceptors = tracer.getInterceptors(); - // Run interceptors in 'reverse' order - for (int i = interceptors.size() - 1; i >= 0; i--) { - // TODO: we probably should handle exceptions in interceptors more or less gracefully - trace = interceptors.get(i).beforeTraceWritten(trace); - if (trace == null) { - break; - } - } - return trace; - } - - /** - * Helper to check if trace is finished and report an error if it is. - * - *

    This has to be called under object lock - * - * @param action action to report error with. - */ - private void checkTraceFinished(final String action) { - if (finished) { - tracer.reportError("Cannot %s, trace has already been finished: %s", action, this); - } - } -} diff --git a/dd-trace/src/main/java/datadog/trace/tracer/TraceInternal.java b/dd-trace/src/main/java/datadog/trace/tracer/TraceInternal.java deleted file mode 100644 index ab0b694748..0000000000 --- a/dd-trace/src/main/java/datadog/trace/tracer/TraceInternal.java +++ /dev/null @@ -1,21 +0,0 @@ -package datadog.trace.tracer; - -/** Trace interface that provides additional methods used internally */ -interface TraceInternal extends Trace { - - /** - * Called by the span to inform trace that span is finished. - * - * @param span span to finish. - * @param invalid true iff span is 'invalid'. - */ - void finishSpan(final Span span, final boolean invalid); - - /** - * Called by the continuation to inform trace that span is closed. - * - * @param continuation continuation to close. - * @param invalid true iff span is 'invalid'. - */ - void closeContinuation(final Continuation continuation, final boolean invalid); -} diff --git a/dd-trace/src/main/java/datadog/trace/tracer/Tracer.java b/dd-trace/src/main/java/datadog/trace/tracer/Tracer.java deleted file mode 100644 index 0a5c9a5274..0000000000 --- a/dd-trace/src/main/java/datadog/trace/tracer/Tracer.java +++ /dev/null @@ -1,171 +0,0 @@ -package datadog.trace.tracer; - -import datadog.trace.api.Config; -import datadog.trace.tracer.sampling.AllSampler; -import datadog.trace.tracer.sampling.Sampler; -import datadog.trace.tracer.writer.Writer; -import java.io.Closeable; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.TimeUnit; -import lombok.Builder; -import lombok.extern.slf4j.Slf4j; - -/** A Tracer creates {@link Trace}s and holds common settings across traces. */ -@Slf4j -public class Tracer implements Closeable { - - /** Writer is an charge of reporting traces and spans to the desired endpoint */ - private final Writer writer; - - /** Sampler defines the sampling policy in order to reduce the number of traces for instance */ - private final Sampler sampler; - - /** Settings for this tracer. */ - private final Config config; - - /** Interceptors to be called on certain trace and span events */ - private final List interceptors; - - /** - * JVM shutdown callback, keeping a reference to it to remove this if Tracer gets destroyed - * earlier - */ - private final Thread shutdownCallback; - - @Builder - private Tracer( - final Config config, - final Writer writer, - final Sampler sampler, - final List interceptors) { - // Apply defaults: - this.config = config != null ? config : Config.get(); - this.writer = writer != null ? writer : Writer.Builder.forConfig(this.config); - this.sampler = sampler != null ? sampler : new AllSampler(); - - // TODO: implement and include "standard" interceptors - this.interceptors = - interceptors != null - ? Collections.unmodifiableList(new ArrayList<>(interceptors)) - : Collections.emptyList(); - - shutdownCallback = new ShutdownHook(this); - try { - Runtime.getRuntime().addShutdownHook(shutdownCallback); - } catch (final IllegalStateException ex) { - // The JVM is already shutting down. - } - } - - /** @return {@link Writer} used by this tracer */ - public Writer getWriter() { - return writer; - } - - /** @return {@link Sampler} used by this tracer. */ - public Sampler getSampler() { - return sampler; - } - - /** @return unmodifiable list of trace/span interceptors. */ - public List getInterceptors() { - return interceptors; - } - - /** @return service name to use on span by default. */ - String getDefaultServiceName() { - return config.getServiceName(); - } - - /** - * @return timestamp for current time. Note: this is mainly useful when there is no 'current' - * trace. If there is 'current' trace already then one should use it to get timestamps. - */ - public Timestamp createCurrentTimestamp() { - return new Clock(this).createCurrentTimestamp(); - } - - /** - * @return timestamp for given time. Note: this is mainly useful when there is no 'current' trace. - * If there is 'current' trace already then one should use it to get timestamps. - */ - public Timestamp createTimestampForTime(final long time, final TimeUnit unit) { - return new Clock(this).createTimestampForTime(time, unit); - } - - /** - * Construct a new trace using this tracer's settings and return the root span. - * - * @param parentContext parent context of a root span in this trace. May be null. - * @return The root span of the new trace. - */ - public Span buildTrace(final SpanContext parentContext) { - return buildTrace(parentContext, createCurrentTimestamp()); - } - - /** - * Construct a new trace using this tracer's settings and return the root span. - * - * @param parentContext parent context of a root span in this trace. May be null. - * @param timestamp root span start timestamp. - * @return The root span of the new trace. - */ - public Span buildTrace(final SpanContext parentContext, final Timestamp timestamp) { - final Trace trace = new TraceImpl(this, parentContext, timestamp); - return trace.getRootSpan(); - } - - // TODO: doc inject and extract - // TODO: inject and extract helpers on span context? - public void inject(final SpanContext spanContext, final Object format, final T carrier) {} - - public SpanContext extract(final Object format, final T carrier) { - return null; - } - - /* - Report error to the log/console. This may throw an exception - */ - void reportError(final String message, final Object... args) { - // TODO: Provide way to do logging or throwing an exception according to config? - final String completeMessage = String.format(message, args); - log.debug(completeMessage); - throw new TraceException(completeMessage); - } - - @Override - public void finalize() { - try { - Runtime.getRuntime().removeShutdownHook(shutdownCallback); - shutdownCallback.run(); - } catch (final Exception e) { - log.error("Error while finalizing Tracer.", e); - } - } - - @Override - public void close() { - // FIXME: Handle the possibility of close being called more than once or not at all. - // FIXME: Depends on order of execution between finalize, GC, and the shutdown hook. - writer.close(); - } - - private static class ShutdownHook extends Thread { - private final WeakReference reference; - - private ShutdownHook(final Tracer tracer) { - reference = new WeakReference<>(tracer); - } - - @Override - public void run() { - final Tracer tracer = reference.get(); - if (tracer != null) { - tracer.close(); - } - } - } -} diff --git a/dd-trace/src/main/java/datadog/trace/tracer/sampling/AllSampler.java b/dd-trace/src/main/java/datadog/trace/tracer/sampling/AllSampler.java deleted file mode 100644 index 2c1153551c..0000000000 --- a/dd-trace/src/main/java/datadog/trace/tracer/sampling/AllSampler.java +++ /dev/null @@ -1,11 +0,0 @@ -package datadog.trace.tracer.sampling; - -import datadog.trace.tracer.Trace; - -/** Sampler that samples all traces. */ -public class AllSampler implements Sampler { - @Override - public boolean sample(final Trace trace) { - return true; - } -} diff --git a/dd-trace/src/main/java/datadog/trace/tracer/sampling/Sampler.java b/dd-trace/src/main/java/datadog/trace/tracer/sampling/Sampler.java deleted file mode 100644 index 3479ef9c51..0000000000 --- a/dd-trace/src/main/java/datadog/trace/tracer/sampling/Sampler.java +++ /dev/null @@ -1,19 +0,0 @@ -package datadog.trace.tracer.sampling; - -import datadog.trace.tracer.Trace; - -/** - * Keeps or discards traces. - * - *

    Note that in most cases the sampler will keep all traces. Most of the sampling logic is done - * downstream by the trace-agent or dd-backend. - */ -public interface Sampler { - /** - * Run tracer sampling logic on the trace. - * - * @param trace - * @return true if the trace should be kept/written/reported. - */ - boolean sample(Trace trace); -} diff --git a/dd-trace/src/main/java/datadog/trace/tracer/writer/AgentClient.java b/dd-trace/src/main/java/datadog/trace/tracer/writer/AgentClient.java deleted file mode 100644 index 1a22c5f147..0000000000 --- a/dd-trace/src/main/java/datadog/trace/tracer/writer/AgentClient.java +++ /dev/null @@ -1,147 +0,0 @@ -package datadog.trace.tracer.writer; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonValue; -import com.fasterxml.jackson.databind.ObjectMapper; -import datadog.trace.tracer.LogRateLimiter; -import datadog.trace.tracer.Trace; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.TimeUnit; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.msgpack.jackson.dataformat.MessagePackFactory; - -// FIXME: this has to be migrated to okhttp before we can use this. -// Otherwise http requests coming from here will get traced. -@Slf4j -class AgentClient { - - static final String TRACES_ENDPOINT = "/v0.4/traces"; - - static final String CONTENT_TYPE = "Content-Type"; - static final String MSGPACK = "application/msgpack"; - static final String DATADOG_META_LANG = "Datadog-Meta-Lang"; - static final String DATADOG_META_LANG_VERSION = "Datadog-Meta-Lang-Version"; - static final String DATADOG_META_LANG_INTERPRETER = "Datadog-Meta-Lang-Interpreter"; - static final String DATADOG_META_TRACER_VERSION = "Datadog-Meta-Tracer-Version"; - static final String X_DATADOG_TRACE_COUNT = "X-Datadog-Trace-Count"; - - static final int CONNECT_TIMEOUT = (int) TimeUnit.SECONDS.toMillis(1); - static final int READ_TIMEOUT = (int) TimeUnit.SECONDS.toMillis(1); - - private static final long MILLISECONDS_BETWEEN_ERROR_LOG = TimeUnit.MINUTES.toMillis(5); - - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(new MessagePackFactory()); - private static final LogRateLimiter LOG_RATE_LIMITER = - new LogRateLimiter(log, MILLISECONDS_BETWEEN_ERROR_LOG); - - @Getter private final URL agentUrl; - - AgentClient(final String host, final int port) { - final String url = "http://" + host + ":" + port + TRACES_ENDPOINT; - try { - agentUrl = new URL(url); - } catch (final MalformedURLException e) { - // This should essentially mean agent should bail out from installing, we cannot meaningfully - // recover from this. - throw new RuntimeException("Cannot parse agent url: " + url, e); - } - } - - /** - * Send traces to the Datadog agent - * - * @param traces the traces to be sent - * @param traceCount total number of traces - */ - public SampleRateByService sendTraces(final List traces, final int traceCount) { - final TracesRequest request = new TracesRequest(traces); - try { - final HttpURLConnection connection = createHttpConnection(); - connection.setRequestProperty(X_DATADOG_TRACE_COUNT, String.valueOf(traceCount)); - - try (final OutputStream out = connection.getOutputStream()) { - OBJECT_MAPPER.writeValue(out, request); - } - - final int responseCode = connection.getResponseCode(); - if (responseCode != 200) { - throw new IOException( - String.format( - "Error while sending %d of %d traces to the DD agent. Status: %d, ResponseMessage: %s", - traces.size(), traceCount, responseCode, connection.getResponseMessage())); - } - - try (final InputStream in = connection.getInputStream()) { - final TracesResponse response = OBJECT_MAPPER.readValue(in, TracesResponse.class); - return response.getSampleRateByService(); - } - } catch (final IOException e) { - LOG_RATE_LIMITER.warn( - "Error while sending {} of {} traces to the DD agent.", traces.size(), traceCount, e); - } - return null; - } - - private HttpURLConnection createHttpConnection() throws IOException { - final HttpURLConnection connection = (HttpURLConnection) agentUrl.openConnection(); - connection.setDoOutput(true); - connection.setDoInput(true); - - // It is important to have timeout for agent request here: we need to finish request in some - // reasonable amount - // of time to allow following requests to be run. - connection.setConnectTimeout(CONNECT_TIMEOUT); - connection.setReadTimeout(READ_TIMEOUT); - - connection.setRequestMethod("PUT"); - connection.setRequestProperty(CONTENT_TYPE, MSGPACK); - connection.setRequestProperty(DATADOG_META_LANG, "java"); - - // TODO: set these variables properly!!! - connection.setRequestProperty(DATADOG_META_LANG_VERSION, "TODO: DDTraceOTInfo.JAVA_VERSION"); - connection.setRequestProperty( - DATADOG_META_LANG_INTERPRETER, "TODO: DDTraceOTInfo.JAVA_VM_NAME"); - connection.setRequestProperty(DATADOG_META_TRACER_VERSION, "TODO: DDTraceOTInfo.VERSION"); - - return connection; - } - - private static class TracesRequest { - - private final List traces; - - TracesRequest(final List traces) { - this.traces = Collections.unmodifiableList(traces); - } - - @JsonValue - public List getTraces() { - return traces; - } - } - - @JsonIgnoreProperties(ignoreUnknown = true) - private static class TracesResponse { - - private final SampleRateByService sampleRateByService; - - @JsonCreator - TracesResponse(@JsonProperty("rate_by_service") final SampleRateByService sampleRateByService) { - this.sampleRateByService = sampleRateByService; - } - - public SampleRateByService getSampleRateByService() { - return sampleRateByService; - } - } -} diff --git a/dd-trace/src/main/java/datadog/trace/tracer/writer/AgentWriter.java b/dd-trace/src/main/java/datadog/trace/tracer/writer/AgentWriter.java deleted file mode 100644 index aab3314e71..0000000000 --- a/dd-trace/src/main/java/datadog/trace/tracer/writer/AgentWriter.java +++ /dev/null @@ -1,168 +0,0 @@ -package datadog.trace.tracer.writer; - -import datadog.trace.tracer.Trace; -import java.net.URL; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -public class AgentWriter implements Writer { - - /** Maximum number of traces kept in memory */ - static final int DEFAULT_QUEUE_SIZE = 7000; - /** Flush interval for the API in seconds */ - static final long FLUSH_TIME_SECONDS = 1; - /** Maximum amount of time to await for scheduler to shutdown */ - static final long SHUTDOWN_TIMEOUT_SECONDS = 1; - - private static final ThreadFactory THREAD_FACTORY = - new ThreadFactory() { - @Override - public Thread newThread(final Runnable r) { - final Thread thread = new Thread(r, "dd-agent-writer"); - thread.setDaemon(true); - return thread; - } - }; - - /** Scheduled thread pool, acting like a cron */ - private final ScheduledExecutorService executorService = - Executors.newScheduledThreadPool(1, THREAD_FACTORY); - - private final TracesSendingTask task; - private final ShutdownCallback shutdownCallback; - - public AgentWriter(final AgentClient client) { - this(client, DEFAULT_QUEUE_SIZE); - } - - AgentWriter(final AgentClient client, final int queueSize) { - task = new TracesSendingTask(client, queueSize); - shutdownCallback = new ShutdownCallback(executorService); - } - - /** @return Datadog agent URL. Visible for testing. */ - URL getAgentUrl() { - return task.getClient().getAgentUrl(); - } - - @Override - public void write(final Trace trace) { - if (trace.isValid()) { - if (!task.getQueue().offer(trace)) { - log.debug("Writer queue is full, dropping trace {}", trace); - } - } - } - - @Override - public void incrementTraceCount() { - task.getTraceCount().incrementAndGet(); - } - - @Override - public SampleRateByService getSampleRateByService() { - return task.getSampleRateByService().get(); - } - - @Override - public void start() { - executorService.scheduleAtFixedRate(task, 0, FLUSH_TIME_SECONDS, TimeUnit.SECONDS); - try { - Runtime.getRuntime().addShutdownHook(shutdownCallback); - } catch (final IllegalStateException ex) { - // The JVM is already shutting down. - } - } - - @Override - public void close() { - // Perform actions needed to shutdown this writer - shutdownCallback.run(); - } - - @Override - public void finalize() { - close(); - } - - /** Infinite tasks blocking until some spans come in the queue. */ - private static final class TracesSendingTask implements Runnable { - - /** The Datadog agent client */ - @Getter private final AgentClient client; - /** Queue size */ - private final int queueSize; - /** In memory collection of traces waiting for departure */ - @Getter private final BlockingQueue queue; - /** Number of traces to be written */ - @Getter private final AtomicInteger traceCount = new AtomicInteger(0); - /** Sample rate by service returned by Datadog agent */ - @Getter - private final AtomicReference sampleRateByService = - new AtomicReference<>(SampleRateByService.EMPTY_INSTANCE); - - TracesSendingTask(final AgentClient client, final int queueSize) { - this.client = client; - this.queueSize = queueSize; - queue = new ArrayBlockingQueue<>(queueSize); - } - - @Override - public void run() { - try { - final List tracesToWrite = new ArrayList<>(queueSize); - queue.drainTo(tracesToWrite); - if (tracesToWrite.size() > 0) { - sampleRateByService.set(client.sendTraces(tracesToWrite, traceCount.getAndSet(0))); - } - } catch (final Throwable e) { - log.debug("Failed to send traces to the API: {}", e.getMessage()); - } - } - } - - /** - * Helper to handle shutting down of the Writer because JVM is shutting down or Writer is closed. - */ - // Visible for testing - static final class ShutdownCallback extends Thread { - - private final ExecutorService executorService; - - public ShutdownCallback(final ExecutorService executorService) { - this.executorService = executorService; - } - - @Override - public void run() { - // We use this logic in two cases: - // * When JVM is shutting down - // * When Writer is closed manually/via GC - // In latter case we need to remove shutdown hook. - try { - Runtime.getRuntime().removeShutdownHook(this); - } catch (final IllegalStateException ex) { - // The JVM may be shutting down. - } - - try { - executorService.shutdownNow(); - executorService.awaitTermination(SHUTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS); - } catch (final InterruptedException e) { - log.info("Writer properly closed and async writer interrupted."); - } - } - } -} diff --git a/dd-trace/src/main/java/datadog/trace/tracer/writer/LoggingWriter.java b/dd-trace/src/main/java/datadog/trace/tracer/writer/LoggingWriter.java deleted file mode 100644 index 38db25dabd..0000000000 --- a/dd-trace/src/main/java/datadog/trace/tracer/writer/LoggingWriter.java +++ /dev/null @@ -1,34 +0,0 @@ -package datadog.trace.tracer.writer; - -import datadog.trace.tracer.Trace; -import lombok.extern.slf4j.Slf4j; - -/** Writer implementation that just logs traces as they are being written */ -@Slf4j -public class LoggingWriter implements Writer { - @Override - public void write(final Trace trace) { - log.debug("Trace written: {}", trace); - } - - @Override - public void incrementTraceCount() { - // Nothing to do here. - } - - @Override - public SampleRateByService getSampleRateByService() { - return SampleRateByService.EMPTY_INSTANCE; - } - - @Override - public void start() { - // TODO: do we really need this? and if we do - who is responsible for calling this? - log.debug("{} started", getClass().getSimpleName()); - } - - @Override - public void close() { - log.debug("{} closed", getClass().getSimpleName()); - } -} diff --git a/dd-trace/src/main/java/datadog/trace/tracer/writer/SampleRateByService.java b/dd-trace/src/main/java/datadog/trace/tracer/writer/SampleRateByService.java deleted file mode 100644 index 652017e750..0000000000 --- a/dd-trace/src/main/java/datadog/trace/tracer/writer/SampleRateByService.java +++ /dev/null @@ -1,28 +0,0 @@ -package datadog.trace.tracer.writer; - -import com.fasterxml.jackson.annotation.JsonCreator; -import java.util.Collections; -import java.util.Map; -import lombok.EqualsAndHashCode; - -/** - * Holds sample rate for all known services. This is reported by Dadadog agent in response to - * writing traces. - */ -@EqualsAndHashCode -class SampleRateByService { - - static final SampleRateByService EMPTY_INSTANCE = new SampleRateByService(Collections.EMPTY_MAP); - - private final Map rateByService; - - @JsonCreator - SampleRateByService(final Map rateByService) { - this.rateByService = Collections.unmodifiableMap(rateByService); - } - - public Double getRate(final String service) { - // TODO: improve logic in this class to handle default value better - return rateByService.get(service); - } -} diff --git a/dd-trace/src/main/java/datadog/trace/tracer/writer/Writer.java b/dd-trace/src/main/java/datadog/trace/tracer/writer/Writer.java deleted file mode 100644 index e26b86b815..0000000000 --- a/dd-trace/src/main/java/datadog/trace/tracer/writer/Writer.java +++ /dev/null @@ -1,75 +0,0 @@ -package datadog.trace.tracer.writer; - -import datadog.trace.api.Config; -import datadog.trace.tracer.Trace; -import java.util.Properties; -import lombok.extern.slf4j.Slf4j; - -/** A writer sends traces to some place. */ -public interface Writer { - - /** - * Write a trace represented by the entire list of all the finished spans. - * - *

    It is up to the tracer to decide if the trace should be written (e.g. for invalid traces). - * - *

    This call doesn't increment trace counter, see {@code incrementTraceCount} for that - * - * @param trace the trace to write - */ - void write(Trace trace); - - /** - * Inform the writer that a trace occurred but will not be written. Used by tracer-side sampling. - */ - void incrementTraceCount(); - - /** @return Most up to date {@link SampleRateByService} instance. */ - SampleRateByService getSampleRateByService(); - - /** Start the writer */ - void start(); - - /** - * Indicates to the writer that no future writing will come and it should terminates all - * connections and tasks - */ - void close(); - - @Slf4j - final class Builder { - - public static Writer forConfig(final Config config) { - if (config == null) { - // There is no way config is not create so getting here must be a code bug - throw new NullPointerException("Config is required to create writer"); - } - - final Writer writer; - - final String configuredType = config.getWriterType(); - if (Config.DD_AGENT_WRITER_TYPE.equals(configuredType)) { - writer = createAgentWriter(config); - } else if (Config.LOGGING_WRITER_TYPE.equals(configuredType)) { - writer = new LoggingWriter(); - } else { - log.warn( - "Writer type not configured correctly: Type {} not recognized. Defaulting to AgentWriter.", - configuredType); - writer = createAgentWriter(config); - } - - return writer; - } - - public static Writer forConfig(final Properties config) { - return forConfig(Config.get(config)); - } - - private static Writer createAgentWriter(final Config config) { - return new AgentWriter(new AgentClient(config.getAgentHost(), config.getAgentPort())); - } - - private Builder() {} - } -} diff --git a/dd-trace/src/test/groovy/datadog/trace/tracer/ClockTest.groovy b/dd-trace/src/test/groovy/datadog/trace/tracer/ClockTest.groovy deleted file mode 100644 index a19472de12..0000000000 --- a/dd-trace/src/test/groovy/datadog/trace/tracer/ClockTest.groovy +++ /dev/null @@ -1,64 +0,0 @@ -package datadog.trace.tracer - -import nl.jqno.equalsverifier.EqualsVerifier -import nl.jqno.equalsverifier.Warning -import spock.lang.Shared -import spock.lang.Specification - -import java.util.concurrent.TimeUnit - -class ClockTest extends Specification { - - // Assume it takes less than a minute to run this test - public static final long MINUTE = TimeUnit.MINUTES.toNanos(1) - - @Shared - def tracer = Mock(Tracer) - - def "test getters"() { - setup: - def currentTimeNano = TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()) - def nanoTicks = System.nanoTime() - - when: - def clock = new Clock(tracer) - - then: - clock.getTracer() == tracer - clock.getStartTimeNano() - currentTimeNano <= MINUTE - clock.getStartNanoTicks() - nanoTicks <= MINUTE - clock.epochTimeNano() - currentTimeNano <= MINUTE - TimeUnit.MICROSECONDS.toNanos(clock.epochTimeMicro()) - currentTimeNano <= MINUTE - clock.nanoTicks() - nanoTicks <= MINUTE - } - - def "test timestamp creation"() { - setup: - def clock = new Clock(tracer) - - when: - def timestamp = clock.createCurrentTimestamp() - - then: - timestamp.getClock() == clock - } - - def "test timestamp creation with custom time"() { - setup: - def clock = new Clock(tracer) - - when: - def timestamp = clock.createTimestampForTime(1, TimeUnit.SECONDS) - - then: - timestamp.getClock() == clock - } - - def "test equals"() { - when: - EqualsVerifier.forClass(Clock).suppress(Warning.STRICT_INHERITANCE).verify() - - then: - noExceptionThrown() - } -} diff --git a/dd-trace/src/test/groovy/datadog/trace/tracer/ContinuationImplTest.groovy b/dd-trace/src/test/groovy/datadog/trace/tracer/ContinuationImplTest.groovy deleted file mode 100644 index 8169a8b1cf..0000000000 --- a/dd-trace/src/test/groovy/datadog/trace/tracer/ContinuationImplTest.groovy +++ /dev/null @@ -1,103 +0,0 @@ -package datadog.trace.tracer - - -import spock.lang.Specification - -class ContinuationImplTest extends Specification { - - def tracer = Mock(Tracer) - def trace = Mock(TraceImpl) { - getTracer() >> tracer - } - def span = Mock(Span) - - def "test getters"() { - when: - def continuation = new ContinuationImpl(trace, span) - - then: - continuation.getTrace() == trace - continuation.getSpan() == span - } - - def "happy lifecycle"() { - when: "continuation is created" - def continuation = new ContinuationImpl(trace, span) - - then: "continuation is opened" - !continuation.isClosed() - - when: "close continuation" - continuation.close() - - then: "continuation is closed and no errors are reported" - continuation.isClosed() - 0 * tracer.reportError(*_) - and: "continuation is reported as closed to a trace" - 1 * trace.closeContinuation(continuation, false) - - when: "continuation is finalized" - continuation.finalize() - - then: "continuation is still closed and no errors are reported" - continuation.isClosed() - 0 * tracer.reportError(*_) - and: "continuation is not reported as closed to a trace again" - 0 * trace.closeContinuation(_, _) - } - - def "double close"() { - setup: - def continuation = new ContinuationImpl(trace, span) - - when: "close continuation" - continuation.close() - - then: "continuation is closed" - continuation.isClosed() - - when: "close continuation again" - continuation.close() - - then: "error is reported" - 1 * tracer.reportError(_, [continuation]) - and: "continuation is not reported as closed to a trace again" - 0 * trace.closeContinuation(_, _) - } - - def "finalize"() { - setup: - def continuation = new ContinuationImpl(trace, span) - - when: "finalize continuation" - continuation.finalize() - - then: "continuation is closed" - continuation.isClosed() - and: "continuation is reported as closed to a trace" - 1 * trace.closeContinuation(continuation, true) - - when: "finalize continuation again" - continuation.finalize() - - then: "continuation is still closed" - continuation.isClosed() - and: "continuation is not reported as closed to a trace again" - 0 * trace.closeContinuation(_, _) - and: "no error is reported" - 0 * tracer.reportError(_, *_) - } - - def "finalize catches all exceptions"() { - setup: - def continuation = new ContinuationImpl(trace, span) - - when: - continuation.finalize() - - then: - 1 * trace.closeContinuation(_, _) >> { throw new Throwable() } - noExceptionThrown() - } - -} diff --git a/dd-trace/src/test/groovy/datadog/trace/tracer/JsonSpan.groovy b/dd-trace/src/test/groovy/datadog/trace/tracer/JsonSpan.groovy deleted file mode 100644 index b569ff3035..0000000000 --- a/dd-trace/src/test/groovy/datadog/trace/tracer/JsonSpan.groovy +++ /dev/null @@ -1,63 +0,0 @@ -package datadog.trace.tracer - -import com.fasterxml.jackson.annotation.JsonCreator -import com.fasterxml.jackson.annotation.JsonProperty -import groovy.transform.EqualsAndHashCode -import groovy.transform.ToString - -/** - * Helper class to parse serialized span to verify serialization logic - */ -@EqualsAndHashCode -@ToString -class JsonSpan { - @JsonProperty("trace_id") - BigInteger traceId - @JsonProperty("parent_id") - BigInteger parentId - @JsonProperty("span_id") - BigInteger spanId - - @JsonProperty("start") - long start - @JsonProperty("duration") - long duration - - @JsonProperty("service") - String service - @JsonProperty("resource") - String resource - @JsonProperty("type") - String type - @JsonProperty("name") - String name - - @JsonProperty("error") - // Somehow MsgPack formatter can not convert number to boolean with newer jackson so we have to do this manually - //@JsonFormat(shape = JsonFormat.Shape.NUMBER) - int error - - @JsonProperty("meta") - Map meta - - @JsonCreator - JsonSpan() {} - - JsonSpan(SpanImpl span) { - traceId = new BigInteger(span.getContext().getTraceId()) - parentId = new BigInteger(span.getContext().getParentId()) - spanId = new BigInteger(span.getContext().getSpanId()) - - start = span.getStartTimestamp().getTime() - duration = span.getDuration() - - service = span.getService() - resource = span.getResource() - type = span.getType() - name = span.getName() - - error = span.isErrored() ? 1 : 0 - - meta = span.getMetaString() - } -} diff --git a/dd-trace/src/test/groovy/datadog/trace/tracer/LogRateLimiterTest.groovy b/dd-trace/src/test/groovy/datadog/trace/tracer/LogRateLimiterTest.groovy deleted file mode 100644 index 29a00fc53c..0000000000 --- a/dd-trace/src/test/groovy/datadog/trace/tracer/LogRateLimiterTest.groovy +++ /dev/null @@ -1,70 +0,0 @@ -package datadog.trace.tracer - -import org.slf4j.Logger -import spock.lang.Specification - - -class LogRateLimiterTest extends Specification { - - private static final String MESSAGE = "message" - private static final int REPEAT_COUNT = 10 - - def log = Mock(Logger) - def object = new Object() - - def "test debugging enabled: #method"() { - setup: - log.isDebugEnabled() >> true - def logRateLimiter = new LogRateLimiter(log, 10) - - when: "message is logged" - logRateLimiter."${method}"(MESSAGE, object) - - then: "debug message is output" - 1 * log.debug(MESSAGE, object) - - where: - method | _ - "warn" | _ - "error" | _ - } - - def "test debugging disabled, no delay: #method"() { - setup: "debug is disabled and delay between log is zero" - log.isDebugEnabled() >> false - def logRateLimiter = new LogRateLimiter(log, 0) - - when: "messages are logged" - for (int i = 0; i < REPEAT_COUNT; i++) { - logRateLimiter."${method}"(MESSAGE, object) - } - - then: "all messages are output with appropriate log level" - REPEAT_COUNT * log."${method}"({it.contains(MESSAGE)}, object) - - where: - method | _ - "warn" | _ - "error" | _ - } - - def "test debugging disabled, large delay: #method"() { - setup: "debug is disabled and delay between log is large" - log.isDebugEnabled() >> false - def logRateLimiter = new LogRateLimiter(log, 10000000) - - when: "messages are logged" - for (int i = 0; i < REPEAT_COUNT; i++) { - logRateLimiter."${method}"(MESSAGE, object) - } - - then: "message is output once" - 1 * log."${method}"({it.contains(MESSAGE)}, object) - - where: - method | _ - "warn" | _ - "error" | _ - } - -} diff --git a/dd-trace/src/test/groovy/datadog/trace/tracer/SpanContextImplTest.groovy b/dd-trace/src/test/groovy/datadog/trace/tracer/SpanContextImplTest.groovy deleted file mode 100644 index 01599734b1..0000000000 --- a/dd-trace/src/test/groovy/datadog/trace/tracer/SpanContextImplTest.groovy +++ /dev/null @@ -1,54 +0,0 @@ -package datadog.trace.tracer - -import datadog.trace.api.sampling.PrioritySampling -import nl.jqno.equalsverifier.EqualsVerifier -import nl.jqno.equalsverifier.Warning -import spock.lang.Specification - - -class SpanContextImplTest extends Specification { - - def "test getters"() { - when: - def context = new SpanContextImpl("trace id", "parent id", "span id") - - then: - context.getTraceId() == "trace id" - context.getParentId() == "parent id" - context.getSpanId() == "span id" - - // TODO: this still to be implemented - context.getSamplingFlags() == PrioritySampling.SAMPLER_KEEP - } - - def "create from parent"() { - setup: - def parent = new SpanContextImpl("trace id", "parent's parent id", "parent span id") - - when: - def context = SpanContextImpl.fromParent(parent) - - then: - context.getTraceId() == "trace id" - context.getParentId() == "parent span id" - context.getSpanId() ==~ /\d+/ - } - - def "create from no parent"() { - when: - def context = SpanContextImpl.fromParent(null) - - then: - context.getTraceId() ==~ /\d+/ - context.getParentId() == SpanContextImpl.ZERO - context.getSpanId() ==~ /\d+/ - } - - def "test equals"() { - when: - EqualsVerifier.forClass(SpanContextImpl).suppress(Warning.STRICT_INHERITANCE).verify() - - then: - noExceptionThrown() - } -} diff --git a/dd-trace/src/test/groovy/datadog/trace/tracer/SpanImplTest.groovy b/dd-trace/src/test/groovy/datadog/trace/tracer/SpanImplTest.groovy deleted file mode 100644 index 7268c43f8a..0000000000 --- a/dd-trace/src/test/groovy/datadog/trace/tracer/SpanImplTest.groovy +++ /dev/null @@ -1,370 +0,0 @@ -package datadog.trace.tracer - - -import com.fasterxml.jackson.databind.ObjectMapper -import datadog.trace.api.DDTags -import spock.lang.Specification - -class SpanImplTest extends Specification { - - private static final String SERVICE_NAME = "service.name" - private static final String PARENT_TRACE_ID = "trace id" - private static final String PARENT_SPAN_ID = "span id" - private static final long START_TIME = 100 - private static final long DURATION = 321 - - def interceptors = [Mock(name: "interceptor-1", Interceptor), Mock(name: "interceptor-2", Interceptor)] - def tracer = Mock(Tracer) { - getDefaultServiceName() >> SERVICE_NAME - getInterceptors() >> interceptors - } - def parentContext = Mock(SpanContextImpl) { - getTraceId() >> PARENT_TRACE_ID - getSpanId() >> PARENT_SPAN_ID - } - def startTimestamp = Mock(Timestamp) { - getTime() >> START_TIME - getDuration() >> DURATION - getDuration(_) >> { args -> args[0] + DURATION } - } - def trace = Mock(TraceImpl) { - getTracer() >> tracer - } - - ObjectMapper objectMapper = new ObjectMapper() - - def "test setters and default values"() { - when: "create span" - def span = new SpanImpl(trace, parentContext, startTimestamp) - - then: "span got created" - span.getTrace() == trace - span.getStartTimestamp() == startTimestamp - span.getDuration() == null - span.getContext().getTraceId() == PARENT_TRACE_ID - span.getContext().getParentId() == PARENT_SPAN_ID - span.getContext().getSpanId() ==~ /\d+/ - span.getTraceId() == PARENT_TRACE_ID - span.getParentId() == PARENT_SPAN_ID - span.getSpanId() == span.getContext().getSpanId() - span.getService() == SERVICE_NAME - span.getResource() == null - span.getType() == null - span.getName() == null - !span.isErrored() - - when: "span settings changes" - span.setService("new.service.name") - span.setResource("resource") - span.setType("type") - span.setName("name") - span.setErrored(true) - - then: "span fields get updated" - span.getService() == "new.service.name" - span.getResource() == "resource" - span.getType() == "type" - span.getName() == "name" - span.isErrored() - } - - def "test setter #setter on finished span"() { - setup: "create span" - def span = new SpanImpl(trace, parentContext, startTimestamp) - span.finish() - - when: "call setter on finished span" - span."$setter"(newValue) - - then: "error reported" - 1 * tracer.reportError(_, { - it[0] == fieldName - it[1] == span - }) - and: "value unchanged" - span."$getter"() == oldValue - - where: - fieldName | setter | getter | newValue | oldValue - "service" | "setService" | "getService" | "new service" | SERVICE_NAME - "resource" | "setResource" | "getResource" | "new resource" | null - "type" | "setType" | "getType" | "new type" | null - "name" | "setName" | "getName" | "new name" | null - "errored" | "setErrored" | "isErrored" | true | false - } - - def "test meta set and remove for #key"() { - when: - def span = new SpanImpl(trace, parentContext, startTimestamp) - - then: - span.getMeta(key) == null - - when: - span.setMeta(key, value) - - then: - span.getMeta(key) == value - - when: - span.setMeta(key, value.class.cast(null)) - - then: - span.getMeta(key) == null - - where: - key | value - "string.key" | "string" - "boolean.key" | true - "number.key" | 123 - } - - def "test getMeta"() { - setup: - def span = new SpanImpl(trace, parentContext, startTimestamp) - - when: - span.setMeta("number.key", 123) - span.setMeta("string.key", "meta string") - span.setMeta("boolean.key", true) - - then: - span.getMeta() == ["number.key": 123, "string.key": "meta string", "boolean.key": true] - span.getMetaString() == ["number.key": "123", "string.key": "meta string", "boolean.key": "true"] - } - - def "test meta setter on finished span for #key"() { - setup: "create span" - def span = new SpanImpl(trace, parentContext, startTimestamp) - span.finish() - - when: "call setter on finished span" - span.setMeta(key, value) - - then: "error reported" - 1 * tracer.reportError(_, { - it[0] == "meta value " + key - it[1] == span - }) - and: "value unchanged" - span.getMeta(key) == null - - where: - key | value - "string.key" | "string" - "boolean.key" | true - "number.key" | 123 - } - - def "test attachThrowable"() { - setup: - def exception = new RuntimeException("test message") - when: - def span = new SpanImpl(trace, parentContext, startTimestamp) - - then: - !span.isErrored() - span.getMeta(DDTags.ERROR_MSG) == null - span.getMeta(DDTags.ERROR_TYPE) == null - span.getMeta(DDTags.ERROR_STACK) == null - - when: - span.attachThrowable(exception) - - then: - span.isErrored() - span.getMeta(DDTags.ERROR_MSG) == "test message" - span.getMeta(DDTags.ERROR_TYPE) == RuntimeException.getName() - span.getMeta(DDTags.ERROR_STACK) != null - } - - def "test attachThrowable on finished span"() { - setup: "create span" - def exception = new RuntimeException("test message") - def span = new SpanImpl(trace, parentContext, startTimestamp) - span.finish() - - when: "attach throwable" - span.attachThrowable(exception) - - then: "error reported" - 1 * tracer.reportError(_, { - it[0] == "throwable" - it[1] == span - }) - and: "span unchanged" - !span.isErrored() - span.getMeta(DDTags.ERROR_MSG) == null - span.getMeta(DDTags.ERROR_TYPE) == null - span.getMeta(DDTags.ERROR_STACK) == null - } - - def "test no parent"() { - when: - def span = new SpanImpl(trace, null, startTimestamp) - - then: - span.getContext().getTraceId() ==~ /\d+/ - span.getContext().getParentId() == SpanContextImpl.ZERO - span.getContext().getSpanId() ==~ /\d+/ - } - - def "test lifecycle '#name'"() { - when: "create span" - def span = new SpanImpl(trace, parentContext, startTimestamp) - - then: "interceptors called" - interceptors.each({ interceptor -> - then: - 1 * interceptor.afterSpanStarted({ - // Apparently invocation verification has to know expected value before 'when' section - // To work around this we just check parent span id - it.getContext().getParentId() == parentContext.getSpanId() - }) - 0 * interceptor._ - }) - then: - !span.isFinished() - - when: "finish/finalize span" - span."$method"(*methodArgs) - - then: "interceptors called" - interceptors.reverseEach({ interceptor -> - then: - 1 * interceptor.beforeSpanFinished({ - it == span - it.getDuration() == null // Make sure duration is not set yet - }) - 0 * interceptor._ - }) - then: "trace is informed that span is closed" - 1 * trace.finishSpan({ - it == span - it.getDuration() == expectedDuration - }, finalizeErrorReported) - 0 * trace._ - span.isFinished() - - when: "try to finish span again" - span.finish(*secondFinishCallArgs) - - then: "interceptors are not called" - interceptors.each({ interceptor -> - 0 * interceptor._ - }) - and: "trace is not informed" - 0 * trace.finishSpan(_, _) - and: "error is reported" - 1 * tracer.reportError(_, span) - - where: - name | method | methodArgs | expectedDuration | finalizeErrorReported | secondFinishCallArgs - "happy" | "finish" | [] | DURATION | false | [] - "happy" | "finish" | [] | DURATION | false | [222] - "happy with timestamp" | "finish" | [111] | DURATION + 111 | false | [222] - "happy with timestamp" | "finish" | [111] | DURATION + 111 | false | [] - // Note: doing GC tests with mocks is nearly impossible because mocks hold all sort of references - // We will do 'real' GC test as part of integration testing, this is more of a unit test - "finalized" | "finalize" | [] | DURATION | true | [] - "finalized" | "finalize" | [] | DURATION | true | [222] - } - - def "finished span GCed without errors"() { - setup: "create span" - def span = new SpanImpl(trace, parentContext, startTimestamp) - - when: "finish span" - span.finish() - - then: - span.isFinished() - 0 * tracer.reportError(_, *_) - - when: "finalize span" - span.finalize() - - then: - 0 * tracer.reportError(_, *_) - } - - def "finalize catches all exceptions"() { - setup: - def span = new SpanImpl(trace, parentContext, startTimestamp) - - when: - span.finalize() - - then: - 1 * startTimestamp.getDuration() >> { throw new Throwable() } - noExceptionThrown() - } - - def "span without timestamp"() { - when: - new SpanImpl(trace, parentContext, null) - - then: - thrown TraceException - } - - def "test JSON rendering"() { - setup: "create span" - def parentContext = new SpanContextImpl("123", "456", "789") - def span = new SpanImpl(trace, parentContext, startTimestamp) - span.setResource("test resource") - span.setType("test type") - span.setName("test name") - span.setMeta("number.key", 123) - span.setMeta("string.key", "meta string") - span.setMeta("boolean.key", true) - span.finish() - - when: "convert to JSON" - def string = objectMapper.writeValueAsString(span) - def parsedSpan = objectMapper.readerFor(JsonSpan).readValue(string) - - then: - parsedSpan == new JsonSpan(span) - } - - def "test JSON rendering with throwable"() { - setup: "create span" - def parentContext = new SpanContextImpl("123", "456", "789") - def span = new SpanImpl(trace, parentContext, startTimestamp) - span.attachThrowable(new RuntimeException("test")) - span.finish() - - when: "convert to JSON" - def string = objectMapper.writeValueAsString(span) - def parsedSpan = objectMapper.readerFor(JsonSpan).readValue(string) - - then: - parsedSpan == new JsonSpan(span) - } - - def "test JSON rendering with big ID values"() { - setup: "create span" - def parentContext = new SpanContextImpl( - BigInteger.valueOf(2).pow(64).subtract(1).toString(), - "123", - BigInteger.valueOf(2).pow(64).subtract(2).toString()) - def span = new SpanImpl(trace, parentContext, startTimestamp) - span.finish() - - when: "convert to JSON" - def string = objectMapper.writeValueAsString(span) - def parsedSpan = objectMapper.readValue(string, JsonSpan) - - then: - parsedSpan == new JsonSpan(span) - - when: - def json = objectMapper.readTree(string) - - then: "make sure ids rendered as number" - json.get("trace_id").isNumber() - json.get("parent_id").isNumber() - json.get("span_id").isNumber() - } -} diff --git a/dd-trace/src/test/groovy/datadog/trace/tracer/TimestampTest.groovy b/dd-trace/src/test/groovy/datadog/trace/tracer/TimestampTest.groovy deleted file mode 100644 index 4f3838e7d2..0000000000 --- a/dd-trace/src/test/groovy/datadog/trace/tracer/TimestampTest.groovy +++ /dev/null @@ -1,163 +0,0 @@ -package datadog.trace.tracer - -import com.fasterxml.jackson.databind.ObjectMapper -import nl.jqno.equalsverifier.EqualsVerifier -import nl.jqno.equalsverifier.Warning -import spock.lang.Specification - -import static java.util.concurrent.TimeUnit.MICROSECONDS -import static java.util.concurrent.TimeUnit.NANOSECONDS - -class TimestampTest extends Specification { - - private static final long CLOCK_START_TIME = 100 - private static final long CLOCK_START_NANO_TICKS = 300 - private static final long CLOCK_NANO_TICKS = 500 - - private static final long FINISH_NANO_TICKS = 600 - private static final long FINISH_TIME = 700 - - def tracer = Mock(Tracer) - def clock = Mock(Clock) { - getTracer() >> tracer - } - - ObjectMapper objectMapper = new ObjectMapper() - - def "test getter"() { - when: - def timestamp = new Timestamp(clock) - - then: - timestamp.getClock() == clock - } - - def "test getTime"() { - setup: - clock.nanoTicks() >> CLOCK_NANO_TICKS - clock.getStartTimeNano() >> CLOCK_START_TIME - clock.getStartNanoTicks() >> CLOCK_START_NANO_TICKS - def timestamp = new Timestamp(clock) - - when: - def duration = timestamp.getDuration(FINISH_TIME) - - then: - duration == 400 - 0 * tracer._ - } - - def "test timestamp with custom time"() { - setup: - clock.nanoTicks() >> CLOCK_NANO_TICKS - clock.getStartTimeNano() >> CLOCK_START_TIME - clock.getStartNanoTicks() >> CLOCK_START_NANO_TICKS - def timestamp = new Timestamp(clock, CLOCK_START_TIME + offset, unit) - - when: - def time = timestamp.getTime() - - then: - time == expected - - where: - offset | unit | expected - 10 | NANOSECONDS | 410 - -20 | NANOSECONDS | 380 - 3 | MICROSECONDS | 103300 - -4 | MICROSECONDS | 96300 - } - - def "test getDuration with literal finish time"() { - setup: - clock.nanoTicks() >> CLOCK_NANO_TICKS - clock.getStartTimeNano() >> CLOCK_START_TIME - clock.getStartNanoTicks() >> CLOCK_START_NANO_TICKS - def timestamp = new Timestamp(clock) - - when: - def duration = timestamp.getDuration(FINISH_TIME) - - then: - duration == 400 - 0 * tracer._ - } - - def "test getDuration with timestamp '#name'"() { - setup: - clock.nanoTicks() >> startNanoTicks >> finishNanoTicks - def startTimestamp = new Timestamp(clock) - def finishTimestamp = new Timestamp(clock) - - when: - def duration = startTimestamp.getDuration(finishTimestamp) - - then: - duration == expectedDuration - 0 * tracer._ - - where: - name | startNanoTicks | finishNanoTicks | expectedDuration - 'normal' | CLOCK_START_NANO_TICKS | FINISH_NANO_TICKS | FINISH_NANO_TICKS - CLOCK_START_NANO_TICKS - 'overflow' | Long.MAX_VALUE - CLOCK_START_NANO_TICKS | Long.MIN_VALUE + FINISH_NANO_TICKS | FINISH_NANO_TICKS + CLOCK_START_NANO_TICKS + 1 - } - - def "test getDuration with current time"() { - setup: - clock.createCurrentTimestamp() >> { new Timestamp(clock) } - clock.nanoTicks() >> CLOCK_START_NANO_TICKS >> FINISH_NANO_TICKS - def timestamp = new Timestamp(clock) - - when: - def duration = timestamp.getDuration() - - then: - duration == FINISH_NANO_TICKS - CLOCK_START_NANO_TICKS - 0 * tracer._ - } - - def "test getDuration with wrong clock"() { - setup: - clock.nanoTicks() >> CLOCK_NANO_TICKS - clock.getStartTimeNano() >> CLOCK_START_TIME - clock.getStartNanoTicks() >> CLOCK_START_NANO_TICKS - def timestamp = new Timestamp(clock) - def otherClock = Mock(Clock) { - getTracer() >> tracer - nanoTicks() >> CLOCK_NANO_TICKS + 400 - getStartTimeNano() >> CLOCK_START_TIME + 200 - getStartNanoTicks() >> CLOCK_START_NANO_TICKS + 100 - } - def finishTimestamp = new Timestamp(otherClock) - - when: - def duration = timestamp.getDuration(finishTimestamp) - - then: - duration == 500 - 1 * tracer.reportError(_, clock, otherClock) - } - - def "test equals"() { - when: - EqualsVerifier.forClass(Timestamp).suppress(Warning.STRICT_INHERITANCE).verify() - - then: - noExceptionThrown() - } - - def "test JSON rendering"() { - setup: - clock.nanoTicks() >> CLOCK_NANO_TICKS - clock.getStartTimeNano() >> CLOCK_START_TIME - clock.getStartNanoTicks() >> CLOCK_START_NANO_TICKS - def timestamp = new Timestamp(clock) - - when: - def string = objectMapper.writeValueAsString(timestamp) - - - then: - string == "300" - } -} diff --git a/dd-trace/src/test/groovy/datadog/trace/tracer/TraceExceptionTest.groovy b/dd-trace/src/test/groovy/datadog/trace/tracer/TraceExceptionTest.groovy deleted file mode 100644 index 3bd3d0c088..0000000000 --- a/dd-trace/src/test/groovy/datadog/trace/tracer/TraceExceptionTest.groovy +++ /dev/null @@ -1,49 +0,0 @@ -package datadog.trace.tracer - -import spock.lang.Specification - - -class TraceExceptionTest extends Specification { - - static final MESSAGE = "message" - - def "test default constructor"() { - when: - def exception = new TraceException() - - then: - exception != null - } - - def "test message constructor"() { - when: - def exception = new TraceException(MESSAGE) - - then: - exception.getMessage() == MESSAGE - } - - def "test cause constructor"() { - setup: - def cause = new RuntimeException() - - when: - def exception = new TraceException(cause) - - then: - exception.getCause() == cause - } - - def "test cause and message constructor"() { - setup: - def cause = new RuntimeException() - - when: - def exception = new TraceException(MESSAGE, cause) - - then: - exception.getMessage() == MESSAGE - exception.getCause() == cause - } - -} diff --git a/dd-trace/src/test/groovy/datadog/trace/tracer/TraceImplTest.groovy b/dd-trace/src/test/groovy/datadog/trace/tracer/TraceImplTest.groovy deleted file mode 100644 index e69f521aab..0000000000 --- a/dd-trace/src/test/groovy/datadog/trace/tracer/TraceImplTest.groovy +++ /dev/null @@ -1,493 +0,0 @@ -package datadog.trace.tracer - -import com.fasterxml.jackson.core.type.TypeReference -import com.fasterxml.jackson.databind.ObjectMapper -import datadog.trace.tracer.sampling.Sampler -import datadog.trace.tracer.writer.Writer -import spock.lang.Specification - -class TraceImplTest extends Specification { - - private static final String SERVICE_NAME = "service.name" - private static final String PARENT_TRACE_ID = "trace id" - private static final String PARENT_SPAN_ID = "span id" - - def interceptors = [ - Mock(name: "interceptor-1", Interceptor) { - beforeTraceWritten(_) >> { args -> args[0] } - }, - Mock(name: "interceptor-2", Interceptor) { - beforeTraceWritten(_) >> { args -> args[0] } - } - ] - def writer = Mock(Writer) - def sampler = Mock(Sampler) { - sample(_) >> true - } - def tracer = Mock(Tracer) { - getDefaultServiceName() >> SERVICE_NAME - getInterceptors() >> interceptors - getWriter() >> writer - getSampler() >> sampler - } - def parentContext = Mock(SpanContextImpl) { - getTraceId() >> PARENT_TRACE_ID - getSpanId() >> PARENT_SPAN_ID - } - def startTimestamp = Mock(Timestamp) - - ObjectMapper objectMapper = new ObjectMapper() - - def "test getters"() { - when: - def trace = new TraceImpl(tracer, parentContext, startTimestamp) - - then: - trace.getTracer() == tracer - trace.getRootSpan().getTrace() == trace - trace.getRootSpan().getStartTimestamp() == startTimestamp - trace.getRootSpan().getContext().getTraceId() == PARENT_TRACE_ID - trace.getRootSpan().getContext().getParentId() == PARENT_SPAN_ID - trace.isValid() - } - - def "test getSpans on unfinished spans"() { - setup: - def trace = new TraceImpl(tracer, parentContext, startTimestamp) - - when: - trace.getSpans() - - then: - 1 * tracer.reportError(_, trace) - } - - def "test timestamp creation"() { - setup: - def newTimestamp = Mock(Timestamp) - def clock = Mock(Clock) { - createCurrentTimestamp() >> newTimestamp - } - startTimestamp.getClock() >> clock - def trace = new TraceImpl(tracer, parentContext, startTimestamp) - - when: - def createdTimestamp = trace.createCurrentTimestamp() - - then: - createdTimestamp == newTimestamp - } - - def "finish root span"() { - setup: - def trace = new TraceImpl(tracer, parentContext, startTimestamp) - - when: "root span is finished" - trace.getRootSpan().finish() - - then: "trace gets counted" - 1 * writer.incrementTraceCount() - then: "interceptors get called" - interceptors.reverseEach({ interceptor -> - then: - 1 * interceptor.beforeTraceWritten(trace) >> trace - }) - then: "trace gets sampled" - 1 * sampler.sample(trace) >> { true } - then: "trace gets written" - 1 * writer.write(trace) - trace.isValid() - trace.getSpans() == [trace.getRootSpan()] - - when: "root span is finalized" - trace.getRootSpan().finalize() - - then: "nothing happens" - 0 * writer.incrementTraceCount() - interceptors.reverseEach({ interceptor -> - 0 * interceptor.beforeTraceWritten(_) - }) - 0 * sampler.sample(_) - 0 * writer.write(_) - 0 * tracer.reportError(_, *_) - } - - def "GC root span"() { - setup: - def trace = new TraceImpl(tracer, parentContext, startTimestamp) - - when: "root span is finalized" - trace.getRootSpan().finalize() - - then: "trace gets counted" - 1 * writer.incrementTraceCount() - then: "interceptors get called" - interceptors.reverseEach({ interceptor -> - then: - 1 * interceptor.beforeTraceWritten(trace) >> trace - }) - then: "trace gets sampled" - 1 * sampler.sample(trace) >> { true } - then: "trace gets written" - 1 * writer.write(trace) - !trace.isValid() - } - - def "finish root span dropped by interceptor"() { - setup: - def trace = new TraceImpl(tracer, parentContext, startTimestamp) - - when: - trace.getRootSpan().finish() - - then: "trace gets counted" - 1 * writer.incrementTraceCount() - then: - 1 * interceptors[1].beforeTraceWritten(trace) >> null - 0 * interceptors[0].beforeTraceWritten(_) - 0 * sampler.sample(_) - 0 * writer.write(_) - } - - def "finish root span replaced by interceptor"() { - setup: - def trace = new TraceImpl(tracer, parentContext, startTimestamp) - def replacementTrace = new TraceImpl(tracer, parentContext, startTimestamp) - - when: - trace.getRootSpan().finish() - - then: "trace gets counted" - 1 * writer.incrementTraceCount() - then: - 1 * interceptors[1].beforeTraceWritten(trace) >> replacementTrace - then: - 1 * interceptors[0].beforeTraceWritten(replacementTrace) >> replacementTrace - then: - 1 * sampler.sample(replacementTrace) >> true - then: - 1 * writer.write(replacementTrace) - } - - def "finish root span dropped by sampler"() { - setup: - def trace = new TraceImpl(tracer, parentContext, startTimestamp) - - when: - trace.getRootSpan().finish() - - then: "trace gets counted" - 1 * writer.incrementTraceCount() - then: - 1 * sampler.sample(trace) >> false - 0 * writer.write(_) - } - - def "finish root span and then finish it again by error"() { - setup: - def trace = new TraceImpl(tracer, parentContext, startTimestamp) - - when: "root span is finished" - trace.getRootSpan().finish() - - then: "trace gets counted" - 1 * writer.incrementTraceCount() - then: "interceptors get called" - interceptors.reverseEach({ interceptor -> - then: - 1 * interceptor.beforeTraceWritten(trace) >> trace - }) - then: "trace gets sampled" - 1 * sampler.sample(trace) >> { true } - then: "trace gets written" - 1 * writer.write(trace) - - when: "root span is finalized" - trace.finishSpan(trace.getRootSpan(), false) - - then: "error is reported" - interceptors.reverseEach({ interceptor -> - 0 * interceptor.beforeTraceWritten(_) - }) - 0 * sampler.sample(_) - 0 * writer.incrementTraceCount() - 0 * writer.write(_) - 1 * tracer.reportError(_, "finish span", trace) - } - - def "create and finish new span"() { - setup: - def newSpanTimestamp = Mock(Timestamp) - def trace = new TraceImpl(tracer, parentContext, startTimestamp) - - when: "new span is created" - def span = trace.createSpan(trace.getRootSpan().getContext(), newSpanTimestamp) - - then: - span.getTrace() == trace - span.getStartTimestamp() == newSpanTimestamp - span.getContext().getTraceId() == PARENT_TRACE_ID - span.getContext().getParentId() == trace.getRootSpan().getContext().getSpanId() - - when: "root span is finished" - trace.getRootSpan().finish() - - then: "nothing gets written" - 0 * writer.incrementTraceCount() - 0 * writer.write(_) - - when: "new span is finished" - span.finish() - - then: "trace gets written" - 1 * writer.incrementTraceCount() - 1 * writer.write(trace) - trace.isValid() - trace.getSpans() == [trace.getRootSpan(), span] - } - - def "create and finish new span with default timestamp"() { - setup: - def newSpanTimestamp = Mock(Timestamp) - def clock = Mock(Clock) { - createCurrentTimestamp() >> newSpanTimestamp - } - startTimestamp.getClock() >> clock - def trace = new TraceImpl(tracer, parentContext, startTimestamp) - - when: "new span is created" - def span = trace.createSpan(trace.getRootSpan().getContext()) - - then: - span.getTrace() == trace - span.getStartTimestamp() == newSpanTimestamp - span.getContext().getTraceId() == PARENT_TRACE_ID - span.getContext().getParentId() == trace.getRootSpan().getContext().getSpanId() - - when: "root span is finished" - trace.getRootSpan().finish() - - then: "nothing gets written" - 0 * writer.incrementTraceCount() - 0 * writer.write(_) - - when: "new span is finished" - span.finish() - - then: "trace gets written" - 1 * writer.incrementTraceCount() - 1 * writer.write(trace) - trace.isValid() - trace.getSpans() == [trace.getRootSpan(), span] - } - - def "create span on finished trace"() { - setup: - def trace = new TraceImpl(tracer, parentContext, startTimestamp) - - when: "root span is finished" - trace.getRootSpan().finish() - - then: "trace is finished" - trace.getSpans() != [] - - when: "new span is created" - trace.createSpan(trace.getRootSpan().getContext(), startTimestamp) - - then: "error is reported" - 1 * tracer.reportError(_, "create span", trace) - } - - def "create span and finish it twice"() { - setup: - def trace = new TraceImpl(tracer, parentContext, startTimestamp) - def span = trace.createSpan(trace.getRootSpan().getContext(), startTimestamp) - - when: "new span is created" - span.finish() - trace.finishSpan(span, true) - - then: "error is reported" - 1 * tracer.reportError(_, span, trace) - } - - def "create span with null parent context"() { - setup: - def trace = new TraceImpl(tracer, parentContext, startTimestamp) - - when: "new span with null parent context is created" - trace.createSpan(null, startTimestamp) - - then: "error is reported" - thrown TraceException - } - - def "create span with parent context from different trace"() { - setup: - def trace = new TraceImpl(tracer, parentContext, startTimestamp) - def anotherParentContext = Mock(SpanContextImpl) { - getTraceId() >> "different trace" - getSpanId() >> PARENT_SPAN_ID - } - - when: "new span with null parent context is created" - trace.createSpan(anotherParentContext, startTimestamp) - - then: "error is reported" - thrown TraceException - } - - def "create and close new continuation"() { - setup: - def trace = new TraceImpl(tracer, parentContext, startTimestamp) - - when: "new continuation is created" - def continuation = trace.createContinuation(trace.getRootSpan()) - - then: - continuation.getSpan() == trace.getRootSpan() - - when: "root span is finished" - trace.getRootSpan().finish() - - then: "nothing gets written" - 0 * writer.incrementTraceCount() - 0 * writer.write(_) - - when: "new continuation is closed" - continuation.close() - - then: "trace gets written" - 1 * writer.incrementTraceCount() - 1 * writer.write(trace) - trace.isValid() - trace.getSpans() == [trace.getRootSpan()] - } - - def "GC continuation"() { - setup: - def trace = new TraceImpl(tracer, parentContext, startTimestamp) - def continuation = trace.createContinuation(trace.getRootSpan()) - trace.getRootSpan().finish() - - when: "continuation finalized" - continuation.finalize() - - then: "trace gets counted" - 1 * writer.incrementTraceCount() - then: "interceptors get called" - interceptors.reverseEach({ interceptor -> - then: - 1 * interceptor.beforeTraceWritten(trace) >> trace - }) - then: "trace gets sampled" - 1 * sampler.sample(trace) >> { true } - then: "trace gets written" - 1 * writer.write(trace) - !trace.isValid() - } - - def "create and close new continuation, then close it again"() { - setup: - def trace = new TraceImpl(tracer, parentContext, startTimestamp) - def continuation = trace.createContinuation(trace.getRootSpan()) - continuation.close() - - when: "continuation is closed again" - trace.closeContinuation(continuation, true) - - then: "error is reported" - 1 * tracer.reportError(_, continuation, trace) - } - - def "create and close new continuation, then close it in finished trace"() { - setup: - def trace = new TraceImpl(tracer, parentContext, startTimestamp) - def continuation = trace.createContinuation(trace.getRootSpan()) - continuation.close() - trace.getRootSpan().finish() - - when: "continuation is closed again" - trace.closeContinuation(continuation, true) - - then: "error is reported" - 1 * tracer.reportError(_, "close continuation", trace) - } - - def "create continuation on finished trace"() { - setup: - def trace = new TraceImpl(tracer, parentContext, startTimestamp) - - when: "root span is finished" - trace.getRootSpan().finish() - - then: "trace is finished" - trace.getSpans() != [] - - when: "new continuation is created" - trace.createContinuation(trace.getRootSpan()) - - then: "error is reported" - 1 * tracer.reportError(_, "create continuation", trace) - } - - def "create continuation with null parent span"() { - setup: - def trace = new TraceImpl(tracer, parentContext, startTimestamp) - - when: "new continuation with null parent span is created" - trace.createContinuation(null) - - then: "error is reported" - thrown TraceException - } - - def "create continuation with parent span from another trace"() { - setup: - def trace = new TraceImpl(tracer, parentContext, startTimestamp) - def anotherParentContext = Mock(SpanContextImpl) { - getTraceId() >> "different trace" - getSpanId() >> PARENT_SPAN_ID - } - def anotherParentSpan = Mock(SpanImpl) { - getContext() >> anotherParentContext - } - - when: "new continuation from span from another trace is created" - trace.createContinuation(anotherParentSpan) - - then: "error is reported" - thrown TraceException - } - - def "test JSON rendering"() { - setup: "create trace" - def clock = new Clock(tracer) - def parentContext = new SpanContextImpl("123", "456", "789") - def trace = new TraceImpl(tracer, parentContext, clock.createCurrentTimestamp()) - trace.getRootSpan().setResource("test resource") - trace.getRootSpan().setType("test type") - trace.getRootSpan().setName("test name") - trace.getRootSpan().setMeta("number.key", 123) - trace.getRootSpan().setMeta("string.key", "meta string") - trace.getRootSpan().setMeta("boolean.key", true) - - def childSpan = trace.createSpan(trace.getRootSpan().getContext()) - childSpan.setResource("child span test resource") - childSpan.setType("child span test type") - childSpan.setName("child span test name") - childSpan.setMeta("child.span.number.key", 123) - childSpan.setMeta("child.span.string.key", "meta string") - childSpan.setMeta("child.span.boolean.key", true) - childSpan.finish() - - trace.getRootSpan().finish() - - when: "convert to JSON" - def string = objectMapper.writeValueAsString(trace) - def parsedTrace = objectMapper.readValue(string, new TypeReference>() {}) - - then: - parsedTrace == [new JsonSpan(childSpan), new JsonSpan(trace.getRootSpan())] - } -} diff --git a/dd-trace/src/test/groovy/datadog/trace/tracer/TracerTest.groovy b/dd-trace/src/test/groovy/datadog/trace/tracer/TracerTest.groovy deleted file mode 100644 index 4df7b864af..0000000000 --- a/dd-trace/src/test/groovy/datadog/trace/tracer/TracerTest.groovy +++ /dev/null @@ -1,285 +0,0 @@ -package datadog.trace.tracer - -import datadog.trace.api.Config -import datadog.trace.tracer.sampling.AllSampler -import datadog.trace.tracer.sampling.Sampler -import datadog.trace.tracer.writer.AgentWriter -import datadog.trace.tracer.writer.SampleRateByService -import datadog.trace.tracer.writer.Writer -import datadog.trace.util.gc.GCUtils -import spock.lang.Requires -import spock.lang.Shared -import spock.lang.Specification - -import java.lang.ref.WeakReference -import java.util.concurrent.TimeUnit -import java.util.concurrent.atomic.AtomicInteger - -@Requires({ !System.getProperty("java.vm.name").contains("IBM J9 VM") }) -class TracerTest extends Specification { - - @Shared - def config = Config.get() - - def testWriter = new TestWriter() - - // TODO: add more tests for different config options and interceptors - def "test getters"() { - when: - def tracer = Tracer.builder().config(config).build() - - then: - tracer.getWriter() instanceof AgentWriter - tracer.getSampler() instanceof AllSampler - tracer.getInterceptors() == [] - } - - def "close closes writer"() { - setup: - def writer = Mock(Writer) - def tracer = Tracer.builder().writer(writer).build() - - when: - tracer.close() - - then: "closed writer" - 1 * writer.close() - 0 * _ // don't allow any other interaction - } - - def "finalize closes writer"() { - setup: - def writer = Mock(Writer) - def tracer = Tracer.builder().writer(writer).build() - - when: - tracer.finalize() - - then: "closed writer" - 1 * writer.close() - 0 * _ // don't allow any other interaction - - when: - tracer.finalize() - - then: "thrown error swallowed" - 1 * writer.close() >> { throw new Exception("test error") } - 0 * _ // don't allow any other interaction - } - - def "test create current timestamp"() { - setup: - def tracer = Tracer.builder().config(config).build() - - when: - def timestamp = tracer.createCurrentTimestamp() - - then: - timestamp.getDuration() <= TimeUnit.MINUTES.toNanos(1) // Assume test takes less than a minute to run - } - - def "test trace happy path"() { - setup: - def tracer = Tracer.builder().config(config).writer(testWriter).build() - - when: - def rootSpan = tracer.buildTrace(null) - def continuation = rootSpan.getTrace().createContinuation(rootSpan) - def span = rootSpan.getTrace().createSpan(rootSpan.getContext(), tracer.createCurrentTimestamp()) - - then: - rootSpan.getService() == config.getServiceName() - - when: - rootSpan.finish() - continuation.close() - - then: - testWriter.traces == [] - - when: - span.finish() - - then: - testWriter.traces == [rootSpan.getContext().getTraceId()] - testWriter.validity == [true] - rootSpan.getTrace().getSpans() == [rootSpan, span] - testWriter.traceCount.get() == 1 - } - - def "sampler called on span completion"() { - setup: - def sampler = Mock(Sampler) - def tracer = Tracer.builder().sampler(sampler).build() - - when: - tracer.buildTrace(null).finish() - - then: "closed writer" - 1 * sampler.sample(_ as Trace) - 0 * _ // don't allow any other interaction - } - - def "interceptor called on span events"() { - setup: - def interceptor = Mock(Interceptor) - def tracer = Tracer.builder().interceptors([interceptor]).build() - - when: - tracer.buildTrace(null).finish() - - then: "closed writer" - 1 * interceptor.afterSpanStarted(_ as Span) - 1 * interceptor.beforeSpanFinished(_ as Span) - 1 * interceptor.beforeTraceWritten(_ as Trace) - 0 * _ // don't allow any other interaction - } - - def "test inject"() { - //TODO implement this test properly - setup: - def context = Mock(SpanContext) - def tracer = Tracer.builder().config(config).writer(testWriter).build() - - when: - tracer.inject(context, null, null) - - then: - noExceptionThrown() - } - - def "test extract"() { - //TODO implement this test properly - setup: - def tracer = Tracer.builder().config(config).writer(testWriter).build() - - when: - def context = tracer.extract(null, null) - - then: - context == null - } - - def testReportError() { - //TODO implement this test properly - setup: - def tracer = Tracer.builder().config(config).writer(testWriter).build() - - when: - tracer.reportError("message %s", 123) - - then: - thrown TraceException - } - - def "test trace that had a GCed span"() { - setup: - def tracer = Tracer.builder().config(config).writer(testWriter).build() - - when: "trace and spans get created" - def rootSpan = tracer.buildTrace(null) - def traceId = rootSpan.getContext().getTraceId() - def span = rootSpan.getTrace().createSpan(rootSpan.getContext(), tracer.createCurrentTimestamp()) - rootSpan.finish() - - then: "nothing is written yet" - testWriter.traces == [] - - when: "remove all references to traces and spans and wait for GC" - span = null - def traceRef = new WeakReference<>(rootSpan.getTrace()) - rootSpan = null - GCUtils.awaitGC(traceRef) - - then: "invalid trace is written" - testWriter.waitForTraces(1) - testWriter.traces == [traceId] - testWriter.validity == [false] - testWriter.traceCount.get() == 1 - } - - def "test trace that had a GCed continuation"() { - setup: - def tracer = Tracer.builder().config(config).writer(testWriter).build() - - when: "trace and spans get created" - def rootSpan = tracer.buildTrace(null) - def traceId = rootSpan.getContext().getTraceId() - def continuation = rootSpan.getTrace().createContinuation(rootSpan) - rootSpan.finish() - - then: "nothing is written yet" - testWriter.traces == [] - - when: "remove all references to traces and spans and wait for GC" - continuation = null - def traceRef = new WeakReference<>(rootSpan.getTrace()) - rootSpan = null - GCUtils.awaitGC(traceRef) - - then: "invalid trace is written" - testWriter.waitForTraces(1) - testWriter.traces == [traceId] - testWriter.validity == [false] - testWriter.traceCount.get() == 1 - } - - /** - * We cannot use mocks for testing of things related to GC because mocks capture arguments with hardlinks. - * For the same reason this test writer cannot capture complete references to traces in this writer. Instead - * we capture 'strategic' values. Other values have been tested in 'lower level' tests. - */ - static class TestWriter implements Writer { - - def traces = new ArrayList() - def validity = new ArrayList() - def traceCount = new AtomicInteger() - - @Override - synchronized void write(Trace trace) { - traces.add(trace.getRootSpan().getContext().getTraceId()) - validity.add(trace.isValid()) - } - - @Override - void incrementTraceCount() { - traceCount.incrementAndGet() - } - - @Override - SampleRateByService getSampleRateByService() { - return null // Doesn't matter for now - } - - @Override - void start() { - //nothing to do for now - } - - @Override - void close() { - //nothing to do for now - } - - /** - * JVM gives very little guarantees for when finalizers are run. {@link WeakReference} documentation - * says that weak reference is set to null first, and then objects are marked as finalizable. Then - * finalization happens asynchronously in separate thread. - * This means that currently we do not have a good way of knowing that all traces have been closed/GCed right - * now and only thing we can do is to bluntly wait. - * In the future we have plans to implement limiting of number of inflight traces - this might provide us with - * better way. - * @param numberOfTraces number of traces to wait for. - */ - void waitForTraces(int numberOfTraces) { - while (true) { - synchronized (this) { - if (traces.size() >= numberOfTraces && validity.size() >= numberOfTraces) { - return - } - } - Thread.sleep(500) - } - } - } -} diff --git a/dd-trace/src/test/groovy/datadog/trace/tracer/writer/AgentClientTest.groovy b/dd-trace/src/test/groovy/datadog/trace/tracer/writer/AgentClientTest.groovy deleted file mode 100644 index 6558fc081a..0000000000 --- a/dd-trace/src/test/groovy/datadog/trace/tracer/writer/AgentClientTest.groovy +++ /dev/null @@ -1,177 +0,0 @@ -package datadog.trace.tracer.writer - -import com.fasterxml.jackson.core.type.TypeReference -import com.fasterxml.jackson.databind.ObjectMapper -import com.github.tomakehurst.wiremock.http.Request -import com.github.tomakehurst.wiremock.junit.WireMockRule -import com.github.tomakehurst.wiremock.matching.MatchResult -import datadog.trace.tracer.Clock -import datadog.trace.tracer.JsonSpan -import datadog.trace.tracer.SpanContextImpl -import datadog.trace.tracer.Trace -import datadog.trace.tracer.TraceImpl -import datadog.trace.tracer.Tracer -import datadog.trace.tracer.sampling.Sampler -import org.junit.Rule -import org.msgpack.jackson.dataformat.MessagePackFactory -import spock.lang.Specification - -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse -import static com.github.tomakehurst.wiremock.client.WireMock.equalTo -import static com.github.tomakehurst.wiremock.client.WireMock.put -import static com.github.tomakehurst.wiremock.client.WireMock.putRequestedFor -import static com.github.tomakehurst.wiremock.client.WireMock.stubFor -import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo -import static com.github.tomakehurst.wiremock.client.WireMock.verify -import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig - -class AgentClientTest extends Specification { - - private static final int TRACE_COUNT = 10 - private static final String SERVICE_NAME = "service.name" - - private final ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory()) - - def writer = Mock(Writer) - def sampler = Mock(Sampler) { - sample(_) >> true - } - def tracer = Mock(Tracer) { - getDefaultServiceName() >> SERVICE_NAME - getInterceptors() >> [] - getWriter() >> writer - getSampler() >> sampler - } - - @Rule - public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().dynamicPort().dynamicHttpsPort()) - - private AgentClient client - - def setup() { - client = new AgentClient("localhost", wireMockRule.port()) - - def response = ["rate_by_service": ["test": 0.1, "another test": 0.2]] - - stubFor(put(urlEqualTo(AgentClient.TRACES_ENDPOINT)) - .willReturn(aResponse() - .withStatus(200) - .withHeader("Content-Type", "application/msgpack") - .withBody(objectMapper.writeValueAsBytes(response)))) - } - - def "test send traces"() { - setup: - def traces = [createTrace("123"), createTrace("312")] - - when: - def response = client.sendTraces(traces, TRACE_COUNT) - - then: "got expected response" - response.getRate("test") == 0.1d - response.getRate("another test") == 0.2d - response.getRate("doesn't exist") == null - and: "request got expected parameters" - byte[] requestBody = null - verify(putRequestedFor(urlEqualTo(AgentClient.TRACES_ENDPOINT)) - .withHeader(AgentClient.CONTENT_TYPE, equalTo(AgentClient.MSGPACK)) - .withHeader(AgentClient.DATADOG_META_LANG, equalTo("java")) - // TODO: fill in these headers - // .withHeader(AgentClient.DATADOG_META_LANG_VERSION, equalTo("java")) - // .withHeader(AgentClient.DATADOG_META_LANG_INTERPRETER, equalTo("java")) - // .withHeader(AgentClient.DATADOG_META_TRACER_VERSION, equalTo("java")) - .withHeader(AgentClient.X_DATADOG_TRACE_COUNT, equalTo(Integer.toString(TRACE_COUNT))) - .andMatching({ Request request -> - requestBody = request.getBody() - MatchResult.of(true) - })) - objectMapper.readValue(requestBody, new TypeReference>>() {}) == traces.collect { - it.getSpans().collect { new JsonSpan(it) } - } - } - - def "test send empty list"() { - when: - def response = client.sendTraces([], TRACE_COUNT) - - then: "got expected response" - response.getRate("test") == 0.1d - response.getRate("another test") == 0.2d - response.getRate("doesn't exist") == null - and: "request got expected parameters" - verify(putRequestedFor(urlEqualTo(AgentClient.TRACES_ENDPOINT)) - .withHeader(AgentClient.CONTENT_TYPE, equalTo(AgentClient.MSGPACK)) - .withHeader(AgentClient.DATADOG_META_LANG, equalTo("java")) - // TODO: fill in these headers - // .withHeader(AgentClient.DATADOG_META_LANG_VERSION, equalTo("java")) - // .withHeader(AgentClient.DATADOG_META_LANG_INTERPRETER, equalTo("java")) - // .withHeader(AgentClient.DATADOG_META_TRACER_VERSION, equalTo("java")) - .withHeader(AgentClient.X_DATADOG_TRACE_COUNT, equalTo(Integer.toString(TRACE_COUNT))) - .andMatching({ Request request -> - MatchResult.of(objectMapper.readValue(request.getBody(), new TypeReference>>() {}) == []) - })) - } - - def "test failure"() { - setup: - stubFor(put(urlEqualTo(AgentClient.TRACES_ENDPOINT)) - .willReturn(aResponse() - .withStatus(500))) - def trace = createTrace("123") - - when: - def response = client.sendTraces([trace], TRACE_COUNT) - - then: - response == null - } - - def "test timeout"() { - setup: - stubFor(put(urlEqualTo(AgentClient.TRACES_ENDPOINT)) - .willReturn(aResponse() - .withStatus(200) - .withChunkedDribbleDelay(5, AgentClient.READ_TIMEOUT * 2))) - def trace = createTrace("123") - - when: - def response = client.sendTraces([trace], TRACE_COUNT) - - then: - response == null - } - - - def "test invalid url"() { - when: - client = new AgentClient("localhost", -100) - - then: - thrown RuntimeException - } - - Trace createTrace(String traceId) { - def clock = new Clock(tracer) - def parentContext = new SpanContextImpl("123", "456", "789") - def trace = new TraceImpl(tracer, parentContext, clock.createCurrentTimestamp()) - trace.getRootSpan().setResource("test resource") - trace.getRootSpan().setType("test type") - trace.getRootSpan().setName("test name") - trace.getRootSpan().setMeta("number.key", 123) - trace.getRootSpan().setMeta("string.key", "meta string") - trace.getRootSpan().setMeta("boolean.key", true) - - def childSpan = trace.createSpan(trace.getRootSpan().getContext()) - childSpan.setResource("child span test resource") - childSpan.setType("child span test type") - childSpan.setName("child span test name") - childSpan.setMeta("child.span.number.key", 234) - childSpan.setMeta("child.span.string.key", "new meta string") - childSpan.setMeta("child.span.boolean.key", true) - childSpan.finish() - - trace.getRootSpan().finish() - return trace - } - -} diff --git a/dd-trace/src/test/groovy/datadog/trace/tracer/writer/AgentWriterTest.groovy b/dd-trace/src/test/groovy/datadog/trace/tracer/writer/AgentWriterTest.groovy deleted file mode 100644 index 653ff7fc62..0000000000 --- a/dd-trace/src/test/groovy/datadog/trace/tracer/writer/AgentWriterTest.groovy +++ /dev/null @@ -1,186 +0,0 @@ -package datadog.trace.tracer.writer - -import datadog.trace.tracer.Trace -import spock.lang.Retry -import spock.lang.Specification - -import java.util.concurrent.ExecutorService -import java.util.concurrent.TimeUnit - - -class AgentWriterTest extends Specification { - - // Amount of time within with we expect flush to happen. - // We make this slightly longer than flush time. - private static final int FLUSH_DELAY = TimeUnit.SECONDS.toMillis(AgentWriter.FLUSH_TIME_SECONDS * 2) - - private static final AGENT_URL = new URL("http://example.com") - - def sampleRateByService = Mock(SampleRateByService) - def client = Mock(AgentClient) { - getAgentUrl() >> AGENT_URL - } - - def "test happy path"() { - setup: - def incrementTraceCountBy = 5 - def traces = [ - Mock(Trace) { - isValid() >> true - }, - Mock(Trace) { - isValid() >> false - }, - Mock(Trace) { - isValid() >> true - }] - def writer = new AgentWriter(client) - - when: - for (def trace : traces) { - writer.write(trace) - } - incrementTraceCountBy.times { - writer.incrementTraceCount() - } - - // Starting writer after submissions to make sure all updates go out in 1 request - writer.start() - - Thread.sleep(FLUSH_DELAY) - - then: - 1 * client.sendTraces([traces[0], traces[2]], incrementTraceCountBy) >> sampleRateByService - and: - writer.getSampleRateByService() == sampleRateByService - then: - 0 * client.sendTraces(_, _) - - cleanup: - writer.close() - } - - def "test small queue"() { - setup: - def traces = [ - Mock(Trace) { - isValid() >> true - }, - Mock(Trace) { - isValid() >> true - }] - def writer = new AgentWriter(client, 1) - - when: - for (def trace : traces) { - writer.write(trace) - } - writer.start() - Thread.sleep(FLUSH_DELAY) - - then: - 1 * client.sendTraces([traces[0]], 0) - - cleanup: - writer.close() - } - - def "test client exception handling"() { - setup: - def traces = [ - Mock(Trace) { - isValid() >> true - }, - Mock(Trace) { - isValid() >> true - }] - def writer = new AgentWriter(client) - writer.start() - - when: - writer.write(traces[0]) - Thread.sleep(FLUSH_DELAY) - - then: - 1 * client.sendTraces([traces[0]], 0) >> { throw new IOException("test exception") } - writer.getSampleRateByService() == SampleRateByService.EMPTY_INSTANCE - - when: - writer.write(traces[1]) - Thread.sleep(FLUSH_DELAY) - - then: - 1 * client.sendTraces([traces[1]], 0) >> sampleRateByService - writer.getSampleRateByService() == sampleRateByService - - cleanup: - writer.close() - } - - def "test agent url getter"() { - setup: - def writer = new AgentWriter(client) - - when: - def agentUrl = writer.getAgentUrl() - - then: - agentUrl == AGENT_URL - } - - def "test default sample rate by service"() { - setup: - def writer = new AgentWriter(client) - - when: - def sampleRateByService = writer.getSampleRateByService() - - then: - sampleRateByService == SampleRateByService.EMPTY_INSTANCE - } - - @Retry - def "test start/#closeMethod"() { - setup: - def writer = new AgentWriter(client) - - expect: - !isWriterThreadRunning() - - when: - writer.start() - - then: - isWriterThreadRunning() - - when: - writer."${closeMethod}"() - - then: - !isWriterThreadRunning() - - where: - closeMethod | _ - "close" | _ - "finalize" | _ - } - - def "test shutdown callback"() { - setup: - def executor = Mock(ExecutorService) { - awaitTermination(_, _) >> { throw new InterruptedException() } - } - def callback = new AgentWriter.ShutdownCallback(executor) - - when: - callback.run() - - then: - noExceptionThrown() - } - - boolean isWriterThreadRunning() { - // This is known to fail sometimes. - return Thread.getAllStackTraces().keySet().any { t -> t.getName() == "dd-agent-writer" } - } -} diff --git a/dd-trace/src/test/groovy/datadog/trace/tracer/writer/LoggingWriterTest.groovy b/dd-trace/src/test/groovy/datadog/trace/tracer/writer/LoggingWriterTest.groovy deleted file mode 100644 index 628dde4e12..0000000000 --- a/dd-trace/src/test/groovy/datadog/trace/tracer/writer/LoggingWriterTest.groovy +++ /dev/null @@ -1,48 +0,0 @@ -package datadog.trace.tracer.writer - -import datadog.trace.tracer.Trace -import spock.lang.Shared -import spock.lang.Specification - -// TODO: this test set is incomplete, fill it in -class LoggingWriterTest extends Specification { - - @Shared - def writer = new LoggingWriter() - - def "test start"() { - when: - writer.start() - - then: - noExceptionThrown() - } - - def "test close"() { - when: - writer.close() - - then: - noExceptionThrown() - } - - def "test write"() { - setup: - def trace = Mock(Trace) - - when: - writer.write(trace) - - then: - 1 * trace.toString() - } - - def "test getter"() { - when: - def sampleRateByInstance = writer.getSampleRateByService() - - then: - sampleRateByInstance == SampleRateByService.EMPTY_INSTANCE - } - -} diff --git a/dd-trace/src/test/groovy/datadog/trace/tracer/writer/SampleRateByServiceTest.groovy b/dd-trace/src/test/groovy/datadog/trace/tracer/writer/SampleRateByServiceTest.groovy deleted file mode 100644 index 5476bef1cf..0000000000 --- a/dd-trace/src/test/groovy/datadog/trace/tracer/writer/SampleRateByServiceTest.groovy +++ /dev/null @@ -1,40 +0,0 @@ -package datadog.trace.tracer.writer - -import com.fasterxml.jackson.databind.ObjectMapper -import nl.jqno.equalsverifier.EqualsVerifier -import nl.jqno.equalsverifier.Warning -import spock.lang.Specification - -class SampleRateByServiceTest extends Specification { - - private static final Map TEST_MAP = ["test": 0.1d, "another test": 0.2d] - - ObjectMapper objectMapper = new ObjectMapper() - - def "test constructor and getter"() { - when: - def sampleRate = new SampleRateByService(TEST_MAP) - - then: - sampleRate.getRate("test") == 0.1d - sampleRate.getRate("another test") == 0.2d - sampleRate.getRate("doesn't exist") == null - } - - def "test JSON parsing"() { - when: - def sampleRate = objectMapper.readValue("{\"test\": 0.8, \"another test\": 0.9}", SampleRateByService) - - then: - sampleRate.getRate("test") == 0.8d - sampleRate.getRate("another test") == 0.9d - } - - def "test equals"() { - when: - EqualsVerifier.forClass(SampleRateByService).suppress(Warning.STRICT_INHERITANCE).verify() - - then: - noExceptionThrown() - } -} diff --git a/dd-trace/src/test/groovy/datadog/trace/tracer/writer/WriterTest.groovy b/dd-trace/src/test/groovy/datadog/trace/tracer/writer/WriterTest.groovy deleted file mode 100644 index 1ff4562e10..0000000000 --- a/dd-trace/src/test/groovy/datadog/trace/tracer/writer/WriterTest.groovy +++ /dev/null @@ -1,63 +0,0 @@ -package datadog.trace.tracer.writer - -import datadog.trace.api.Config -import spock.lang.Specification - - -class WriterTest extends Specification { - - def "test builder logging writer"() { - setup: - def config = Mock(Config) { - getWriterType() >> Config.LOGGING_WRITER_TYPE - } - - when: - def writer = Writer.Builder.forConfig(config) - - then: - writer instanceof LoggingWriter - } - - def "test builder logging writer properties"() { - setup: - def properties = new Properties() - properties.setProperty(Config.WRITER_TYPE, Config.LOGGING_WRITER_TYPE) - - when: - def writer = Writer.Builder.forConfig(properties) - - then: - writer instanceof LoggingWriter - } - - def "test builder agent writer: '#writerType'"() { - setup: - def config = Mock(Config) { - getWriterType() >> writerType - getAgentHost() >> "test.host" - getAgentPort() >> 1234 - } - - when: - def writer = Writer.Builder.forConfig(config) - - then: - writer instanceof AgentWriter - ((AgentWriter) writer).getAgentUrl() == new URL("http://test.host:1234/v0.4/traces"); - - where: - writerType | _ - Config.DD_AGENT_WRITER_TYPE | _ - "some odd string" | _ - } - - def "test builder no config"() { - when: - Writer.Builder.forConfig(null) - - then: - thrown NullPointerException - } - -} diff --git a/settings.gradle b/settings.gradle index 5e3510a75c..fd2d31072c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,16 +1,12 @@ rootProject.name = 'dd-trace-java' -// core tracing projects +// external apis include ':dd-trace-api' -include ':dd-java-agent:agent-bootstrap' -include ':dd-trace' -include ':dd-trace-ext' - -// implements for third-party tracing libraries include ':dd-trace-ot' // agent projects include ':dd-java-agent' +include ':dd-java-agent:agent-bootstrap' include ':dd-java-agent:agent-tooling' include ':dd-java-agent:agent-jmxfetch'