diff --git a/dd-trace-ext/README.md b/dd-trace-ext/README.md new file mode 100644 index 0000000000..54c177619f --- /dev/null +++ b/dd-trace-ext/README.md @@ -0,0 +1,4 @@ +# 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 new file mode 100644 index 0000000000..bd6e0d7315 --- /dev/null +++ b/dd-trace-ext/dd-trace-ext.gradle @@ -0,0 +1,6 @@ +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 new file mode 100644 index 0000000000..2e1603d710 --- /dev/null +++ b/dd-trace-ext/src/main/java/datadog/trace/tracer/ext/Examples.java @@ -0,0 +1,76 @@ +package datadog.trace.tracer.ext; + +import datadog.trace.tracer.Span; +import datadog.trace.tracer.Trace; +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(new Tracer()), false); + + // scope + final TracerContext ctx = TracerContext.getGlobalContext(); + // without try-with-resources + { + Span rootSpan = ctx.getTracer().buildTrace(null); + final Scope scope = ctx.pushScope(rootSpan); + rootSpan.setError(true); + 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 + { + Span rootSpan = ctx.getTracer().buildTrace(null); + try (Scope scope = ctx.pushScope(rootSpan)) { + // the body + } catch (Throwable t) { + rootSpan.setError(true); + rootSpan.attachThrowable(t); + throw t; + } finally { + rootSpan.finish(); + } + } + + // continuations + { + Span rootSpan = ctx.getTracer().buildTrace(null); + final Trace.Continuation cont = rootSpan.getTrace().createContinuation(rootSpan); + { // on another thread + final Span parent = cont.span(); + try { + // body + } finally { + cont.close(); + } + } + } + + // create a span as a child of the currently active span + Span childSpan = ctx.peekScope().span().getTrace().createSpan(ctx.peekScope().span()); + } +} 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 new file mode 100644 index 0000000000..2694822929 --- /dev/null +++ b/dd-trace-ext/src/main/java/datadog/trace/tracer/ext/Scope.java @@ -0,0 +1,26 @@ +package datadog.trace.tracer.ext; + +import datadog.trace.tracer.Span; +import datadog.trace.tracer.Trace; + +/** + * 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(Trace.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()}. + */ + 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 new file mode 100644 index 0000000000..a14cfdfe64 --- /dev/null +++ b/dd-trace-ext/src/main/java/datadog/trace/tracer/ext/TracerContext.java @@ -0,0 +1,86 @@ +package datadog.trace.tracer.ext; + +import datadog.trace.tracer.Span; +import datadog.trace.tracer.Trace; +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( + TracerContext context, 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(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(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(Trace.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(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 new file mode 100644 index 0000000000..7043db7a19 --- /dev/null +++ b/dd-trace/README.md @@ -0,0 +1,4 @@ +# 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 new file mode 100644 index 0000000000..64f4a8f769 --- /dev/null +++ b/dd-trace/dd-trace.gradle @@ -0,0 +1,15 @@ +description = 'dd-trace' +apply from: "${rootDir}/gradle/java.gradle" + +dependencies { + annotationProcessor deps.autoservice + implementation deps.autoservice + + compile project(':dd-trace-api') + + compile deps.jackson + compile deps.slf4j + // any higher versions seems to break ES tests with this exception: + // java.lang.NoSuchMethodError: com.fasterxml.jackson.dataformat.smile.SmileGenerator.getOutputContext() + compile group: 'org.msgpack', name: 'jackson-dataformat-msgpack', version: '0.8.14' +} diff --git a/dd-trace/src/main/java/datadog/trace/tracer/Clock.java b/dd-trace/src/main/java/datadog/trace/tracer/Clock.java new file mode 100644 index 0000000000..22bfd41704 --- /dev/null +++ b/dd-trace/src/main/java/datadog/trace/tracer/Clock.java @@ -0,0 +1,56 @@ +package datadog.trace.tracer; + +import java.util.concurrent.TimeUnit; + +/** + * A simple wrapper for system clock that aims to provide the current time + * + *
+ * + *
+ * + *
+ * + *
The JDK provides two clocks: + *
+ * + *
At this time, we are using a millis precision (converted to micros) in order to guarantee + * consistency between the span start times and the durations + */ +public class Clock { + + /** + * Get the current nanos ticks (i.e. System.nanonTime()), this method can't be use for date + * accuracy (only duration calculations) + * + * @return The current nanos ticks + */ + public static long nanoTime() { + return System.nanoTime(); + } + + /** + * Get the current epoch time in micros. + * + *
Note: The actual precision is the millis. + * + * @return the current epoch time in micros + */ + public static 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 + */ + public static long epochTimeNano() { + return TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()); + } +} diff --git a/dd-trace/src/main/java/datadog/trace/tracer/Span.java b/dd-trace/src/main/java/datadog/trace/tracer/Span.java new file mode 100644 index 0000000000..ff41aee958 --- /dev/null +++ b/dd-trace/src/main/java/datadog/trace/tracer/Span.java @@ -0,0 +1,117 @@ +package datadog.trace.tracer; + +/** + * A single measurement of time with arbitrary key-value attributes. + * + *
All spans are thread safe. + * + *
To create a Span, see {@link Tracer#buildTrace()} + */ +public interface Span { + + /** @return The trace this span is associated with. */ + Trace getTrace(); + + /** 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. + */ + void finish(long finishTimestampNanoseconds); + + /** Returns true if a finish method has been invoked on this span. */ + boolean isFinished(); + + /** + * Get the span context for this span. + * + * @return the span context. + */ + SpanContext getContext(); + + /** + * 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. TODO: can + * return null + */ + Object getMeta(String key); + + /** + * Set key-value metadata on the span. + * + *
TODO: Forbid setting null? + */ + 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); + + /** Get the span's name */ + String getName(); + /** + * Set the span's name. + * + * @param newName the new name for the span. + */ + void setName(String newName); + + String getResource(); + + void setResource(String newResource); + + String getService(); + + void setService(String newService); + + String getType(); + + void setType(String newType); + + boolean isErrored(); + + /** Attach a throwable to this span. */ + void attachThrowable(Throwable t); + + /** + * Mark the span as having an error. + * + * @param isErrored true if the span has an error. + */ + void setError(boolean isErrored); + + // TODO: OpenTracing Span#log methods. Do we need something here to support them? Current DDSpan + // does not implement. + + /** + * A Interceptor allows adding hooks to particular events between a span starting and finishing. + */ + interface Interceptor { + /** + * Called after a span is started. + * + * @param span the started span + */ + void spanStarted(Span span); + + /** Called after a span's metadata is updated. */ + void afterMetadataSet(Span span, Object key, Object value); + + /** + * Called after a span is finished. + * + * @param span the started span + */ + void spanFinished(Span span); + } +} diff --git a/dd-trace/src/main/java/datadog/trace/tracer/SpanContext.java b/dd-trace/src/main/java/datadog/trace/tracer/SpanContext.java new file mode 100644 index 0000000000..d3ad805ef1 --- /dev/null +++ b/dd-trace/src/main/java/datadog/trace/tracer/SpanContext.java @@ -0,0 +1,39 @@ +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 span id.
+ *
+ * @return 64 bit unsigned integer in String format.
+ */
+ String getSpanId();
+
+ /**
+ * Get this context's parent span id.
+ *
+ * @return 64 bit unsigned integer in String format.
+ */
+ String getParentId();
+
+ /**
+ * Get this context's trace id.
+ *
+ * @return 64 bit unsigned integer in String format.
+ */
+ String getTraceId();
+
+ /**
+ * 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/SpanImpl.java b/dd-trace/src/main/java/datadog/trace/tracer/SpanImpl.java
new file mode 100644
index 0000000000..339ad0c8a8
--- /dev/null
+++ b/dd-trace/src/main/java/datadog/trace/tracer/SpanImpl.java
@@ -0,0 +1,138 @@
+package datadog.trace.tracer;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class SpanImpl implements Span {
+ private final Clock clock = null;
+ private final Trace trace = null;
+ // See See https://docs.datadoghq.com/api/?lang=python#tracing
+ // Required attributes to report to datadog.
+ private final SpanContext context = null;
+ /* Span name. May not exceed 100 characters. */
+ private final String name = "";
+ /* Span resource (e.g. http endpoint). May not exceed 5000 characters. */
+ private final String resource = "";
+ /* Span service. May not exceed 100 characters. */
+ private final String service = "";
+ /* The start time of the request in nanoseconds from the unix epoch. */
+ private final long startEpochNano = -1;
+ /* Duration of the span in nanoseconds */
+ private final AtomicLong durationNano = new AtomicLong(-1);
+ // optional attributes to report to datadog
+ /* The type of the span (web, db, etc). See DDSpanTypes. */
+ private final String type = null;
+ /* Marks the span as having an error. */
+ private final boolean isErrored = false;
+ /* Additional key-value pairs for a span. */
+ private final Map 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()}
+ */
+public interface Trace {
+ /** Get the tracer which created this trace. */
+ Tracer getTracer();
+
+ /** Get the root span for this trace. This will never be null. */
+ Span getRootSpan();
+
+ /**
+ * Create a new span in this trace as a child of the given parentSpan.
+ *
+ * @param parentSpan 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(Span parentSpan);
+
+ /**
+ * 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);
+
+ interface Interceptor {
+ /**
+ * Invoked when a trace is eligible for writing but hasn't been handed off to its writer yet.
+ *
+ * @param trace The intercepted trace.
+ */
+ void beforeTraceWritten(Trace trace);
+ }
+
+ /** A way to prevent a trace from reporting without creating a span. */
+ interface Continuation {
+ /**
+ * Close the continuation. Continuation's trace will not block reporting on account of this
+ * continuation.
+ *
+ * Has no effect after the first invocation.
+ */
+ void close();
+
+ // TODO: doc
+ Span span();
+ }
+}
diff --git a/dd-trace/src/main/java/datadog/trace/tracer/TraceImpl.java b/dd-trace/src/main/java/datadog/trace/tracer/TraceImpl.java
new file mode 100644
index 0000000000..766a51fa95
--- /dev/null
+++ b/dd-trace/src/main/java/datadog/trace/tracer/TraceImpl.java
@@ -0,0 +1,54 @@
+package datadog.trace.tracer;
+
+import datadog.trace.tracer.writer.Writer;
+import java.lang.ref.WeakReference;
+import java.util.List;
+import java.util.Set;
+
+public class TraceImpl implements Trace {
+ private final Writer writer = null;
+
+ // TODO: Document approach to weak-referencdes and cleanup. If a span has to be closed by our GC
+ // logic the trace should increment the writer's count but not report (invalid api usage produces
+ // suspect data).
+ private final Set 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/Writer.java b/dd-trace/src/main/java/datadog/trace/tracer/writer/Writer.java
new file mode 100644
index 0000000000..b10ded6791
--- /dev/null
+++ b/dd-trace/src/main/java/datadog/trace/tracer/writer/Writer.java
@@ -0,0 +1,27 @@
+package datadog.trace.tracer.writer;
+
+import datadog.trace.tracer.Trace;
+
+/** A writer sends traces to some place. */
+public interface Writer {
+ /**
+ * Write a trace represented by the entire list of all the finished spans
+ *
+ * @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();
+
+ /** 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();
+}
diff --git a/settings.gradle b/settings.gradle
index f1bbb807a8..b96b646b67 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,13 +1,22 @@
rootProject.name = 'dd-trace-java'
-include ':dd-trace-ot'
-include ':dd-java-agent'
+// core tracing projects
+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-tooling'
include ':dd-java-agent:agent-jmxfetch'
+
+// misc
include ':dd-java-agent:testing'
include ':dd-java-agent-ittests'
-include ':dd-trace-api'
// instrumentation:
include ':dd-java-agent:instrumentation:akka-http-10.0'