diff --git a/opentracing-api/src/main/java/io/opentracing/Span.java b/opentracing-api/src/main/java/io/opentracing/Span.java index 7a670c46..870a0241 100644 --- a/opentracing-api/src/main/java/io/opentracing/Span.java +++ b/opentracing-api/src/main/java/io/opentracing/Span.java @@ -1,5 +1,5 @@ /** - * Copyright 2016 The OpenTracing Authors + * Copyright 2016-2017 The OpenTracing Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -22,16 +22,21 @@ *

Spans are created by the {@link Tracer#buildSpan} interface. */ public interface Span extends Closeable { + + SpanManager.Visibility visibility(); + /** * Retrieve the associated SpanContext. * - * This may be called at any time, including after calls to finish(). + * This may be called at any time, including after calls to markAsFinished(). * * @return the SpanContext that encapsulates Span state that should propagate across process boundaries. */ SpanContext context(); /** + * Deactivates spans from spanManager. + * * Sets the end timestamp to now and records the span. * *

With the exception of calls to Span.context(), this should be the last call made to the span instance, and to @@ -42,12 +47,14 @@ public interface Span extends Closeable { void finish(); /** + * Deactivates spans from spanManager. + * * Sets an explicit end timestamp and records the span. * *

With the exception of calls to Span.context(), this should be the last call made to the span instance, and to * do otherwise leads to undefined behavior. * - * @param finishMicros an explicit finish time, in microseconds since the epoch + * @param finishMicros an explicit markAsFinished time, in microseconds since the epoch * * @see Span#context() */ diff --git a/opentracing-api/src/main/java/io/opentracing/SpanManager.java b/opentracing-api/src/main/java/io/opentracing/SpanManager.java new file mode 100644 index 00000000..bd4c7f60 --- /dev/null +++ b/opentracing-api/src/main/java/io/opentracing/SpanManager.java @@ -0,0 +1,41 @@ +/** + * Copyright 2016-2017 The OpenTracing Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.opentracing; + +/** + * @author Pavol Loffay + */ +public interface SpanManager { + + /** + * @param span span to bundle into visibility + * @return visibility + */ + Visibility bundle(Span span); + + /** + * @return not finished active span or null + */ + Visibility active(); + + interface Visibility { + void activate(); + void deactivate(); + + Span span(); + SpanContext context(); + // should be called by span#finish(); + void markAsFinished(); + } +} diff --git a/opentracing-api/src/main/java/io/opentracing/ThreadLocalSpanManager.java b/opentracing-api/src/main/java/io/opentracing/ThreadLocalSpanManager.java new file mode 100644 index 00000000..a2d25e0d --- /dev/null +++ b/opentracing-api/src/main/java/io/opentracing/ThreadLocalSpanManager.java @@ -0,0 +1,74 @@ +/** + * Copyright 2016-2017 The OpenTracing Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.opentracing; + +/** + * @author Pavol Loffay + */ +public class ThreadLocalSpanManager implements SpanManager { + + private final ThreadLocal activeContext = new ThreadLocal(); + + public ThreadLocalSpanManager() {} + + @Override + public Visibility bundle(Span span) { + return span.visibility() == null ? new ThreadLocalVisibility(span) : span.visibility(); + } + + @Override + public Visibility active() { + return activeContext.get() == null ? null : activeContext.get(); + } + + class ThreadLocalVisibility implements Visibility { + private final Span span; + private ThreadLocalVisibility previous; + private boolean finished; + + public ThreadLocalVisibility(Span span) { + this.span = span; + } + + @Override + public Span span() { + return finished ? null : span; + } + + @Override + public SpanContext context() { + return span.context(); + } + + @Override + public void activate() { + previous = activeContext.get(); + activeContext.set(this); + } + + @Override + public void deactivate() { + if (this != activeContext.get()) { + // should not happen + return; + } + activeContext.set(previous); + } + + @Override + public void markAsFinished() { + this.finished = true; + } + } +} diff --git a/opentracing-api/src/main/java/io/opentracing/Tracer.java b/opentracing-api/src/main/java/io/opentracing/Tracer.java index 10f23b5e..c9983d4d 100644 --- a/opentracing-api/src/main/java/io/opentracing/Tracer.java +++ b/opentracing-api/src/main/java/io/opentracing/Tracer.java @@ -20,6 +20,9 @@ */ public interface Tracer { + // span could be directly returned + SpanManager spanManager(); + /** * Return a new SpanBuilder for a Span with the given `operationName`. * @@ -113,6 +116,8 @@ interface SpanBuilder extends SpanContext { */ SpanBuilder addReference(String referenceType, SpanContext referencedContext); + SpanBuilder asRoot(); + /** Same as {@link Span#setTag(String, String)}, but for the span being built. */ SpanBuilder withTag(String key, String value); @@ -128,5 +133,6 @@ interface SpanBuilder extends SpanContext { /** Returns the started Span. */ Span start(); + Span startAndActivate(); } } diff --git a/opentracing-api/src/main/java/io/opentracing/tag/Tags.java b/opentracing-api/src/main/java/io/opentracing/tag/Tags.java index 2bfac906..7bbfccfb 100644 --- a/opentracing-api/src/main/java/io/opentracing/tag/Tags.java +++ b/opentracing-api/src/main/java/io/opentracing/tag/Tags.java @@ -14,7 +14,7 @@ package io.opentracing.tag; /** - * The following span tags are recommended for instrumentors who are trying to capture more + * The following span tags are recommended for instrumentors who are trying to bundle more * semantic information about the spans. Tracers may expose additional features based on these * standardized data points. Tag names follow a general structure of namespacing. * diff --git a/opentracing-mock/src/main/java/io/opentracing/mock/MockSpan.java b/opentracing-mock/src/main/java/io/opentracing/mock/MockSpan.java index 8685764d..ed1d615a 100644 --- a/opentracing-mock/src/main/java/io/opentracing/mock/MockSpan.java +++ b/opentracing-mock/src/main/java/io/opentracing/mock/MockSpan.java @@ -18,6 +18,7 @@ import io.opentracing.Span; import io.opentracing.SpanContext; +import io.opentracing.SpanManager; /** * MockSpans are created via MockTracer.buildSpan(...), but they are also returned via calls to @@ -29,6 +30,8 @@ public final class MockSpan implements Span { // A simple-as-possible (consecutive for repeatability) id generator. private static AtomicLong nextId = new AtomicLong(0); + private final SpanManager.Visibility visibility; + private final MockTracer mockTracer; private MockContext context; private final long parentId; // 0 if there's no parent. @@ -66,10 +69,10 @@ public long startMicros() { return startMicros; } /** - * @return the finish time of the Span; only valid after a call to finish(). + * @return the markAsFinished time of the Span; only valid after a call to markAsFinished(). */ public long finishMicros() { - assert finishMicros > 0 : "must call finish() before finishMicros()"; + assert finishMicros > 0 : "must call markAsFinished() before finishMicros()"; return finishMicros; } @@ -93,6 +96,11 @@ public List generatedErrors() { return new ArrayList<>(errors); } + @Override + public SpanManager.Visibility visibility() { + return visibility; + } + @Override public synchronized MockContext context() { return this.context; @@ -107,6 +115,7 @@ public void finish() { public synchronized void finish(long finishMicros) { finishedCheck("Finishing already finished span"); this.finishMicros = finishMicros; + visibility.markAsFinished(); this.mockTracer.appendFinishedSpan(this); this.finished = true; } @@ -248,10 +257,17 @@ public long timestampMicros() { } } - MockSpan(MockTracer tracer, String operationName, long startMicros, Map initialTags, MockContext parent) { + MockSpan(MockTracer tracer, String operationName, long startMicros, Map initialTags, MockContext + parent, boolean activate) { this.mockTracer = tracer; this.operationName = operationName; this.startMicros = startMicros; + + this.visibility = tracer.spanManager().bundle(this); + if (activate) { + this.visibility.activate(); + } + if (initialTags == null) { this.tags = new HashMap<>(); } else { diff --git a/opentracing-mock/src/main/java/io/opentracing/mock/MockTracer.java b/opentracing-mock/src/main/java/io/opentracing/mock/MockTracer.java index f41bebbd..5896a4e1 100644 --- a/opentracing-mock/src/main/java/io/opentracing/mock/MockTracer.java +++ b/opentracing-mock/src/main/java/io/opentracing/mock/MockTracer.java @@ -23,6 +23,8 @@ import io.opentracing.References; import io.opentracing.Span; import io.opentracing.SpanContext; +import io.opentracing.SpanManager; +import io.opentracing.ThreadLocalSpanManager; import io.opentracing.Tracer; import io.opentracing.propagation.Format; import io.opentracing.propagation.TextMap; @@ -38,30 +40,36 @@ public class MockTracer implements Tracer { private List finishedSpans = new ArrayList<>(); private final Propagator propagator; + private final SpanManager spanManager; public MockTracer() { - this(Propagator.PRINTER); + this(Propagator.PRINTER, new ThreadLocalSpanManager()); } /** * Create a new MockTracer that passes through any calls to inject() and/or extract(). */ public MockTracer(Propagator propagator) { + this(propagator, new ThreadLocalSpanManager()); + } + + public MockTracer(Propagator propagator, SpanManager spanManager) { this.propagator = propagator; + this.spanManager = spanManager; } /** * Clear the finishedSpans() queue. * - * Note that this does *not* have any effect on Spans created by MockTracer that have not finish()ed yet; those - * will still be enqueued in finishedSpans() when they finish(). + * Note that this does *not* have any effect on Spans created by MockTracer that have not markAsFinished()ed yet; those + * will still be enqueued in finishedSpans() when they markAsFinished(). */ public synchronized void reset() { this.finishedSpans.clear(); } /** - * @return a copy of all finish()ed MockSpans started by this MockTracer (since construction or the last call to + * @return a copy of all markAsFinished()ed MockSpans started by this MockTracer (since construction or the last call to * MockTracer.reset()). * * @see MockTracer#reset() @@ -144,9 +152,14 @@ public MockSpan.MockContext extract(Format format, C carrier) { }; } + @Override + public SpanManager spanManager() { + return spanManager; + } + @Override public SpanBuilder buildSpan(String operationName) { - return new SpanBuilder(operationName); + return new SpanBuilder(operationName, spanManager); } @Override @@ -170,8 +183,14 @@ public final class SpanBuilder implements Tracer.SpanBuilder { private MockSpan.MockContext firstParent; private Map initialTags = new HashMap<>(); - SpanBuilder(String operationName) { + SpanBuilder(String operationName, SpanManager spanManager) { this.operationName = operationName; + + SpanManager.Visibility inferredParent = spanManager.active(); + if (inferredParent != null) { + addReference(inferredParent.span() == null ? References.FOLLOWS_FROM : References.CHILD_OF, + inferredParent.context()); + } } @Override public SpanBuilder asChildOf(SpanContext parent) { @@ -192,6 +211,12 @@ public SpanBuilder addReference(String referenceType, SpanContext referencedCont return this; } + @Override + public Tracer.SpanBuilder asRoot() { + firstParent = null; + return this; + } + @Override public SpanBuilder withTag(String key, String value) { this.initialTags.put(key, value); @@ -221,7 +246,17 @@ public MockSpan start() { if (this.startMicros == 0) { this.startMicros = MockSpan.nowMicros(); } - return new MockSpan(MockTracer.this, this.operationName, this.startMicros, initialTags, this.firstParent); + return new MockSpan(MockTracer.this, this.operationName, this.startMicros, initialTags, + this.firstParent, false); + } + + @Override + public MockSpan startAndActivate() { + if (this.startMicros == 0) { + this.startMicros = MockSpan.nowMicros(); + } + return new MockSpan(MockTracer.this, this.operationName, this.startMicros, initialTags, + this.firstParent, true); } @Override diff --git a/opentracing-mock/src/test/java/io/opentracing/mock/MockTracerTest.java b/opentracing-mock/src/test/java/io/opentracing/mock/MockTracerTest.java index c24959c6..dadf57fa 100644 --- a/opentracing-mock/src/test/java/io/opentracing/mock/MockTracerTest.java +++ b/opentracing-mock/src/test/java/io/opentracing/mock/MockTracerTest.java @@ -33,7 +33,7 @@ public class MockTracerTest { @Test public void testRootSpan() { - // Create and finish a root Span. + // Create and markAsFinished a root Span. MockTracer tracer = new MockTracer(); { Span span = tracer.buildSpan("tester").withStartTimestamp(1000).start(); @@ -87,7 +87,7 @@ public void testRootSpan() { @Test public void testChildSpan() { - // Create and finish a root Span. + // Create and markAsFinished a root Span. MockTracer tracer = new MockTracer(); { Span parent = tracer.buildSpan("parent").withStartTimestamp(1000).start(); diff --git a/opentracing-mock/src/test/java/io/opentracing/mock/Sandbox.java b/opentracing-mock/src/test/java/io/opentracing/mock/Sandbox.java new file mode 100644 index 00000000..1fc4dcca --- /dev/null +++ b/opentracing-mock/src/test/java/io/opentracing/mock/Sandbox.java @@ -0,0 +1,40 @@ +/** + * Copyright 2016-2017 The OpenTracing Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.opentracing.mock; + +import org.junit.Assert; +import org.junit.Test; + +/** + * @author Pavol Loffay + */ +public class Sandbox { + + @Test + public void test() { + MockTracer mockTracer = new MockTracer(); + MockSpan root = mockTracer.buildSpan("root").start(); + Assert.assertTrue(mockTracer.spanManager().active() == null); + + root.visibility().activate(); + Assert.assertEquals(root, mockTracer.spanManager().active()); + + MockSpan child = mockTracer.buildSpan("child").start(); + + child.finish(); + root.finish(); + + Assert.assertEquals(root.context().spanId(), child.parentId()); + } +} diff --git a/pom.xml b/pom.xml index fe932431..92d68b87 100644 --- a/pom.xml +++ b/pom.xml @@ -24,8 +24,8 @@ opentracing-api - opentracing-noop - opentracing-impl + + opentracing-mock