Remove dd-trace and dd-trace-ext
This commit is contained in:
parent
e895d7c3a7
commit
717a27080b
|
@ -1,4 +0,0 @@
|
|||
# Convenience Utils for Datadog Tracer Internal API
|
||||
- Trace Scopes
|
||||
- Global Tracer
|
||||
- Async controls
|
|
@ -1,6 +0,0 @@
|
|||
description = 'dd-trace-ext'
|
||||
apply from: "${rootDir}/gradle/java.gradle"
|
||||
|
||||
dependencies {
|
||||
compile project(':dd-trace')
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
*
|
||||
* <p>To create a scope, see {@link TracerContext#pushScope(Span)} and {@link
|
||||
* TracerContext#pushScope(Continuation)}.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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();
|
||||
}
|
|
@ -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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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;
|
||||
}
|
||||
}
|
|
@ -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.
|
|
@ -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'
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
package datadog.trace.decorator;
|
||||
|
||||
import datadog.trace.tracer.Span;
|
||||
|
||||
public abstract class HttpClientDecorator<REQUEST, RESPONSE> 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;
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
package datadog.trace.decorator;
|
||||
|
||||
import datadog.trace.tracer.Span;
|
||||
|
||||
public abstract class HttpServerDecorator<REQUEST, RESPONSE> 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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
*
|
||||
* <p>The JDK provides two clocks:
|
||||
* <li>one in nanoseconds, for precision, but it can only use to measure durations
|
||||
* <li>one in milliseconds, for accuracy, useful to provide epoch time
|
||||
*
|
||||
* <p>Once created this class captures current time with millisecond presition and current
|
||||
* nanosecond counter.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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;
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
package datadog.trace.tracer;
|
||||
|
||||
/**
|
||||
* Continuations are used to prevent a trace from reporting without creating a span.
|
||||
*
|
||||
* <p>All spans are thread safe.
|
||||
*
|
||||
* <p>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();
|
||||
}
|
|
@ -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.
|
||||
*
|
||||
* <p>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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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.
|
||||
*
|
||||
* <p>TODO: can we make this class not public?
|
||||
*
|
||||
* <p>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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,141 +0,0 @@
|
|||
package datadog.trace.tracer;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A single measurement of time with arbitrary key-value attributes.
|
||||
*
|
||||
* <p>All spans are thread safe.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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).
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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<String, Object> 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.
|
||||
*
|
||||
* <p>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);
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
package datadog.trace.tracer;
|
||||
|
||||
/**
|
||||
* All attributes of a {@link Span} which propagate for distributed tracing.
|
||||
*
|
||||
* <p>All Spans must have a SpanContext, but not all SpanContexts require a span.
|
||||
*
|
||||
* <p>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();
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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<String, Object> meta = new HashMap<>();
|
||||
|
||||
private final List<Interceptor> 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<String, Object> 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<String, String> getMetaString() {
|
||||
final Map<String, String> result = new HashMap<>(meta.size());
|
||||
for (final Map.Entry<String, Object> 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.
|
||||
*
|
||||
* <p>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<String> {
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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();
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
*
|
||||
* <p>A trace will be written when all of its spans are finished and all trace continuations are
|
||||
* closed.
|
||||
*
|
||||
* <p>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<Span> 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);
|
||||
}
|
|
@ -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.
|
||||
*
|
||||
* <p>Note that the detail message associated with {@code cause} is <i>not</i> 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 <tt>null</tt> 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
|
||||
* <tt>(cause==null ? null : cause.toString())</tt> (which typically contains the class and detail
|
||||
* message of <tt>cause</tt>). 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 <tt>null</tt> value is permitted, and indicates that the cause is nonexistent or
|
||||
* unknown.)
|
||||
*/
|
||||
public TraceException(final Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
|
@ -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<Span> inFlightSpans =
|
||||
Collections.newSetFromMap(new WeakHashMap<Span, Boolean>());
|
||||
private final Set<Continuation> inFlightContinuations =
|
||||
Collections.newSetFromMap(new WeakHashMap<Continuation, Boolean>());
|
||||
|
||||
/** Strong refs to spans which are closed */
|
||||
private final List<Span> 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<Span> 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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>Note: This has to be called under object lock.
|
||||
*/
|
||||
private Trace runInterceptorsBeforeTraceWritten(Trace trace) {
|
||||
final List<Interceptor> 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.
|
||||
*
|
||||
* <p>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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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<Interceptor> 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<Interceptor> 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.<Interceptor>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<Interceptor> 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 <T> void inject(final SpanContext spanContext, final Object format, final T carrier) {}
|
||||
|
||||
public <T> 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<Tracer> reference;
|
||||
|
||||
private ShutdownHook(final Tracer tracer) {
|
||||
reference = new WeakReference<>(tracer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
final Tracer tracer = reference.get();
|
||||
if (tracer != null) {
|
||||
tracer.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
package datadog.trace.tracer.sampling;
|
||||
|
||||
import datadog.trace.tracer.Trace;
|
||||
|
||||
/**
|
||||
* Keeps or discards traces.
|
||||
*
|
||||
* <p>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);
|
||||
}
|
|
@ -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<Trace> 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<Trace> traces;
|
||||
|
||||
TracesRequest(final List<Trace> traces) {
|
||||
this.traces = Collections.unmodifiableList(traces);
|
||||
}
|
||||
|
||||
@JsonValue
|
||||
public List<Trace> 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<Trace> 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> 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<Trace> 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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<String, Double> rateByService;
|
||||
|
||||
@JsonCreator
|
||||
SampleRateByService(final Map<String, Double> 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);
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
*
|
||||
* <p>It is up to the tracer to decide if the trace should be written (e.g. for invalid traces).
|
||||
*
|
||||
* <p>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() {}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
|
@ -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<String, String> 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()
|
||||
}
|
||||
}
|
|
@ -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" | _
|
||||
}
|
||||
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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<List<JsonSpan>>() {})
|
||||
|
||||
then:
|
||||
parsedTrace == [new JsonSpan(childSpan), new JsonSpan(trace.getRootSpan())]
|
||||
}
|
||||
}
|
|
@ -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<String>()
|
||||
def validity = new ArrayList<Boolean>()
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<List<List<JsonSpan>>>() {}) == 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<List<List<JsonSpan>>>() {}) == [])
|
||||
}))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
}
|
|
@ -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" }
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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<String, Double> 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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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'
|
||||
|
||||
|
|
Loading…
Reference in New Issue