diff --git a/api/src/main/java/io/opentelemetry/api/trace/DefaultTracer.java b/api/src/main/java/io/opentelemetry/api/trace/DefaultTracer.java index 6ee6ae54c2..a18c697602 100644 --- a/api/src/main/java/io/opentelemetry/api/trace/DefaultTracer.java +++ b/api/src/main/java/io/opentelemetry/api/trace/DefaultTracer.java @@ -10,6 +10,7 @@ import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.internal.Utils; import io.opentelemetry.context.Context; import java.util.Objects; +import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; @@ -107,7 +108,7 @@ final class DefaultTracer implements Tracer { } @Override - public NoopSpanBuilder setStartTimestamp(long startTimestamp) { + public NoopSpanBuilder setStartTimestamp(long startTimestamp, TimeUnit unit) { Utils.checkArgument(startTimestamp >= 0, "Negative startTimestamp"); return this; } diff --git a/api/src/main/java/io/opentelemetry/api/trace/PropagatedSpan.java b/api/src/main/java/io/opentelemetry/api/trace/PropagatedSpan.java index a69540dd44..18dda502f7 100644 --- a/api/src/main/java/io/opentelemetry/api/trace/PropagatedSpan.java +++ b/api/src/main/java/io/opentelemetry/api/trace/PropagatedSpan.java @@ -7,6 +7,7 @@ package io.opentelemetry.api.trace; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; +import java.util.concurrent.TimeUnit; import javax.annotation.concurrent.Immutable; /** @@ -71,7 +72,7 @@ final class PropagatedSpan implements Span { } @Override - public Span addEvent(String name, long timestamp) { + public Span addEvent(String name, long timestamp, TimeUnit unit) { return this; } @@ -81,7 +82,7 @@ final class PropagatedSpan implements Span { } @Override - public Span addEvent(String name, Attributes attributes, long timestamp) { + public Span addEvent(String name, Attributes attributes, long timestamp, TimeUnit unit) { return this; } @@ -114,7 +115,7 @@ final class PropagatedSpan implements Span { public void end() {} @Override - public void end(long timestamp) {} + public void end(long timestamp, TimeUnit unit) {} @Override public SpanContext getSpanContext() { diff --git a/api/src/main/java/io/opentelemetry/api/trace/Span.java b/api/src/main/java/io/opentelemetry/api/trace/Span.java index 1bc5aa1e9c..0fa7bc41a2 100644 --- a/api/src/main/java/io/opentelemetry/api/trace/Span.java +++ b/api/src/main/java/io/opentelemetry/api/trace/Span.java @@ -5,10 +5,15 @@ package io.opentelemetry.api.trace; +import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; + import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.context.Context; import io.opentelemetry.context.ImplicitContextKeyed; +import java.time.Instant; +import java.util.concurrent.TimeUnit; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; @@ -199,10 +204,32 @@ public interface Span extends ImplicitContextKeyed { * occurred. * * @param name the name of the event. - * @param timestamp the explicit event timestamp in nanos since epoch. + * @param timestamp the explicit event timestamp since epoch. + * @param unit the unit of the timestamp * @return this. */ - Span addEvent(String name, long timestamp); + Span addEvent(String name, long timestamp, TimeUnit unit); + + /** + * Adds an event to the {@link Span} with the given {@code timestamp}, as nanos since epoch. Note, + * this {@code timestamp} is not the same as {@link System#nanoTime()} but may be computed using + * it, for example, by taking a difference of readings from {@link System#nanoTime()} and adding + * to the span start time. + * + *

When possible, it is preferred to use {@link #addEvent(String)} at the time the event + * occurred. + * + * @param name the name of the event. + * @param timestamp the explicit event timestamp since epoch. + * @return this. + */ + default Span addEvent(String name, Instant timestamp) { + if (timestamp == null) { + return addEvent(name); + } + return addEvent( + name, SECONDS.toNanos(timestamp.getEpochSecond()) + timestamp.getNano(), NANOSECONDS); + } /** * Adds an event to the {@link Span} with the given {@link Attributes}. The timestamp of the event @@ -227,10 +254,37 @@ public interface Span extends ImplicitContextKeyed { * @param name the name of the event. * @param attributes the attributes that will be added; these are associated with this event, not * the {@code Span} as for {@code setAttribute()}. - * @param timestamp the explicit event timestamp in nanos since epoch. + * @param timestamp the explicit event timestamp since epoch. + * @param unit the unit of the timestamp * @return this. */ - Span addEvent(String name, Attributes attributes, long timestamp); + Span addEvent(String name, Attributes attributes, long timestamp, TimeUnit unit); + + /** + * Adds an event to the {@link Span} with the given {@link Attributes} and {@code timestamp}. + * Note, this {@code timestamp} is not the same as {@link System#nanoTime()} but may be computed + * using it, for example, by taking a difference of readings from {@link System#nanoTime()} and + * adding to the span start time. + * + *

When possible, it is preferred to use {@link #addEvent(String)} at the time the event + * occurred. + * + * @param name the name of the event. + * @param attributes the attributes that will be added; these are associated with this event, not + * the {@code Span} as for {@code setAttribute()}. + * @param timestamp the explicit event timestamp since epoch. + * @return this. + */ + default Span addEvent(String name, Attributes attributes, Instant timestamp) { + if (timestamp == null) { + return addEvent(name, attributes); + } + return addEvent( + name, + attributes, + SECONDS.toNanos(timestamp.getEpochSecond()) + timestamp.getNano(), + NANOSECONDS); + } /** * Sets the status to the {@code Span}. @@ -312,10 +366,31 @@ public interface Span extends ImplicitContextKeyed { *

Use this method for specifying explicit end options, such as end {@code Timestamp}. When no * explicit values are required, use {@link #end()}. * - * @param timestamp the explicit timestamp, as nanos from the epoch, for this {@code Span}. {@code - * 0} indicates current time should be used. + * @param timestamp the explicit timestamp from the epoch, for this {@code Span}. {@code 0} + * indicates current time should be used. + * @param unit the unit of the timestamp */ - void end(long timestamp); + void end(long timestamp, TimeUnit unit); + + /** + * Marks the end of {@code Span} execution with the specified timestamp. + * + *

Only the timing of the first end call for a given {@code Span} will be recorded, and + * implementations are free to ignore all further calls. + * + *

Use this method for specifying explicit end options, such as end {@code Timestamp}. When no + * explicit values are required, use {@link #end()}. + * + * @param timestamp the explicit timestamp from the epoch, for this {@code Span}. {@code 0} + * indicates current time should be used. + */ + default void end(Instant timestamp) { + if (timestamp == null) { + end(); + return; + } + end(SECONDS.toNanos(timestamp.getEpochSecond()) + timestamp.getNano(), NANOSECONDS); + } /** * Returns the {@code SpanContext} associated with this {@code Span}. @@ -570,11 +645,32 @@ public interface Span extends ImplicitContextKeyed { * *

Important this is NOT equivalent with System.nanoTime(). * - * @param startTimestamp the explicit start timestamp of the newly created {@code Span} in nanos - * since epoch. + * @param startTimestamp the explicit start timestamp from the epoch of the newly created {@code + * Span}. + * @param unit the unit of the timestamp. * @return this. */ - Builder setStartTimestamp(long startTimestamp); + Builder setStartTimestamp(long startTimestamp, TimeUnit unit); + + /** + * Sets an explicit start timestamp for the newly created {@code Span}. + * + *

Use this method to specify an explicit start timestamp. If not called, the implementation + * will use the timestamp value at {@link #startSpan()} time, which should be the default case. + * + *

Important this is NOT equivalent with System.nanoTime(). + * + * @param startTimestamp the explicit start timestamp from the epoch of the newly created {@code + * Span}. + * @return this. + */ + default Builder setStartTimestamp(Instant startTimestamp) { + if (startTimestamp == null) { + return this; + } + return setStartTimestamp( + SECONDS.toNanos(startTimestamp.getEpochSecond()) + startTimestamp.getNano(), NANOSECONDS); + } /** * Starts a new {@link Span}. diff --git a/api/src/test/java/io/opentelemetry/api/trace/PropagatedSpanTest.java b/api/src/test/java/io/opentelemetry/api/trace/PropagatedSpanTest.java index c6f26c3e5b..6f28085ccd 100644 --- a/api/src/test/java/io/opentelemetry/api/trace/PropagatedSpanTest.java +++ b/api/src/test/java/io/opentelemetry/api/trace/PropagatedSpanTest.java @@ -15,6 +15,8 @@ import static io.opentelemetry.api.common.AttributeKey.stringKey; import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.common.Attributes; +import java.time.Instant; +import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; class PropagatedSpanTest { @@ -41,15 +43,18 @@ class PropagatedSpanTest { span.setAttribute(doubleArrayKey("NullArrayDouble"), null); span.setAttribute((String) null, null); span.addEvent("event"); - span.addEvent("event", 0); + span.addEvent("event", 0, TimeUnit.NANOSECONDS); + span.addEvent("event", Instant.EPOCH); span.addEvent("event", Attributes.of(booleanKey("MyBooleanAttributeKey"), true)); - span.addEvent("event", Attributes.of(booleanKey("MyBooleanAttributeKey"), true), 0); + span.addEvent( + "event", Attributes.of(booleanKey("MyBooleanAttributeKey"), true), 0, TimeUnit.NANOSECONDS); span.setStatus(StatusCode.OK); span.setStatus(StatusCode.OK, "null"); span.recordException(new IllegalStateException()); span.recordException(new IllegalStateException(), Attributes.empty()); span.end(); - span.end(0); + span.end(0, TimeUnit.NANOSECONDS); + span.end(Instant.EPOCH); } @Test diff --git a/api/src/test/java/io/opentelemetry/api/trace/SpanBuilderTest.java b/api/src/test/java/io/opentelemetry/api/trace/SpanBuilderTest.java index b822d2cb12..64c59bdcc6 100644 --- a/api/src/test/java/io/opentelemetry/api/trace/SpanBuilderTest.java +++ b/api/src/test/java/io/opentelemetry/api/trace/SpanBuilderTest.java @@ -12,6 +12,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.Span.Kind; import io.opentelemetry.context.Context; +import java.time.Instant; +import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; /** Unit tests for {@link Span.Builder}. */ @@ -32,7 +34,8 @@ class SpanBuilderTest { spanBuilder.setAttribute("key", .12345); spanBuilder.setAttribute("key", true); spanBuilder.setAttribute(stringKey("key"), "value"); - spanBuilder.setStartTimestamp(12345L); + spanBuilder.setStartTimestamp(12345L, TimeUnit.NANOSECONDS); + spanBuilder.setStartTimestamp(Instant.EPOCH); assertThat(spanBuilder.startSpan().getSpanContext().isValid()).isFalse(); } @@ -47,7 +50,7 @@ class SpanBuilderTest { Span.Builder spanBuilder = tracer.spanBuilder("MySpanName"); assertThrows( IllegalArgumentException.class, - () -> spanBuilder.setStartTimestamp(-1), + () -> spanBuilder.setStartTimestamp(-1, TimeUnit.NANOSECONDS), "Negative startTimestamp"); } } diff --git a/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/SpanConverter.java b/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/SpanConverter.java index d413dae339..363982a35f 100644 --- a/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/SpanConverter.java +++ b/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/SpanConverter.java @@ -82,7 +82,8 @@ class SpanConverter { .spanBuilder(ocSpanData.getName()) .setStartTimestamp( TimeUnit.SECONDS.toNanos(ocSpanData.getStartTimestamp().getSeconds()) - + ocSpanData.getStartTimestamp().getNanos()); + + ocSpanData.getStartTimestamp().getNanos(), + TimeUnit.NANOSECONDS); if (ocSpanData.getKind() != null) { builder.setSpanKind(mapKind(ocSpanData.getKind())); } @@ -167,7 +168,8 @@ class SpanConverter { AttributeKey.longKey(MESSAGE_EVENT_ATTRIBUTE_KEY_SIZE_COMPRESSED), event.getEvent().getCompressedMessageSize()), TimeUnit.SECONDS.toNanos(event.getTimestamp().getSeconds()) - + event.getTimestamp().getNanos()); + + event.getTimestamp().getNanos(), + TimeUnit.NANOSECONDS); } } @@ -190,7 +192,8 @@ class SpanConverter { annotation.getEvent().getDescription(), attributesBuilder.build(), TimeUnit.SECONDS.toNanos(annotation.getTimestamp().getSeconds()) - + annotation.getTimestamp().getNanos()); + + annotation.getTimestamp().getNanos(), + TimeUnit.NANOSECONDS); } } diff --git a/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/SpanConverterTest.java b/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/SpanConverterTest.java index b2602c94d9..4a735d6a91 100644 --- a/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/SpanConverterTest.java +++ b/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/SpanConverterTest.java @@ -105,7 +105,8 @@ class SpanConverterTest { "First annotation!", Attributes.builder().put("Attribute1", false).put("Attribute2", 123).build(), TimeUnit.SECONDS.toNanos(annotations.get(0).getTimestamp().getSeconds()) - + annotations.get(0).getTimestamp().getNanos()); + + annotations.get(0).getTimestamp().getNanos(), + TimeUnit.NANOSECONDS); verify(spanSpy, times(1)) .addEvent( "Second annotation!", @@ -114,7 +115,8 @@ class SpanConverterTest { .put("Attribute2", "attributeValue") .build(), TimeUnit.SECONDS.toNanos(annotations.get(1).getTimestamp().getSeconds()) - + annotations.get(1).getTimestamp().getNanos()); + + annotations.get(1).getTimestamp().getNanos(), + TimeUnit.NANOSECONDS); } @Test @@ -145,7 +147,8 @@ class SpanConverterTest { .put("message.event.size.compressed", 34) .build(), TimeUnit.SECONDS.toNanos(messageEvents.get(0).getTimestamp().getSeconds()) - + messageEvents.get(0).getTimestamp().getNanos()); + + messageEvents.get(0).getTimestamp().getNanos(), + TimeUnit.NANOSECONDS); verify(spanSpy, times(1)) .addEvent( "8", @@ -155,7 +158,8 @@ class SpanConverterTest { .put("message.event.size.compressed", 180) .build(), TimeUnit.SECONDS.toNanos(messageEvents.get(1).getTimestamp().getSeconds()) - + messageEvents.get(1).getTimestamp().getNanos()); + + messageEvents.get(1).getTimestamp().getNanos(), + TimeUnit.NANOSECONDS); } private static RecordEventsSpanImpl createOpenCensusSpan() { diff --git a/sdk-extensions/zpages/src/test/java/io/opentelemetry/sdk/extension/zpages/TracezZPageHandlerTest.java b/sdk-extensions/zpages/src/test/java/io/opentelemetry/sdk/extension/zpages/TracezZPageHandlerTest.java index c611b878dd..e91dab936e 100644 --- a/sdk-extensions/zpages/src/test/java/io/opentelemetry/sdk/extension/zpages/TracezZPageHandlerTest.java +++ b/sdk-extensions/zpages/src/test/java/io/opentelemetry/sdk/extension/zpages/TracezZPageHandlerTest.java @@ -20,6 +20,7 @@ import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.util.Map; +import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -59,8 +60,9 @@ class TracezZPageHandlerTest { Span runningSpan = tracer.spanBuilder(RUNNING_SPAN).startSpan(); - Span latencySpan = tracer.spanBuilder(LATENCY_SPAN).setStartTimestamp(1L).startSpan(); - latencySpan.end(10002); + Span latencySpan = + tracer.spanBuilder(LATENCY_SPAN).setStartTimestamp(1L, TimeUnit.NANOSECONDS).startSpan(); + latencySpan.end(10002, TimeUnit.NANOSECONDS); Span errorSpan = tracer.spanBuilder(ERROR_SPAN).startSpan(); errorSpan.setStatus(StatusCode.ERROR); @@ -142,32 +144,41 @@ class TracezZPageHandlerTest { void summaryTable_linkForLatencyBasedSpans_OnePerBoundary() { OutputStream output = new ByteArrayOutputStream(); // Boundary 0, >1us - Span latencySpanSubtype0 = tracer.spanBuilder(LATENCY_SPAN).setStartTimestamp(1L).startSpan(); - latencySpanSubtype0.end(1002); + Span latencySpanSubtype0 = + tracer.spanBuilder(LATENCY_SPAN).setStartTimestamp(1L, TimeUnit.NANOSECONDS).startSpan(); + latencySpanSubtype0.end(1002, TimeUnit.NANOSECONDS); // Boundary 1, >10us - Span latencySpanSubtype1 = tracer.spanBuilder(LATENCY_SPAN).setStartTimestamp(1L).startSpan(); - latencySpanSubtype1.end(10002); + Span latencySpanSubtype1 = + tracer.spanBuilder(LATENCY_SPAN).setStartTimestamp(1L, TimeUnit.NANOSECONDS).startSpan(); + latencySpanSubtype1.end(10002, TimeUnit.NANOSECONDS); // Boundary 2, >100us - Span latencySpanSubtype2 = tracer.spanBuilder(LATENCY_SPAN).setStartTimestamp(1L).startSpan(); - latencySpanSubtype2.end(100002); + Span latencySpanSubtype2 = + tracer.spanBuilder(LATENCY_SPAN).setStartTimestamp(1L, TimeUnit.NANOSECONDS).startSpan(); + latencySpanSubtype2.end(100002, TimeUnit.NANOSECONDS); // Boundary 3, >1ms - Span latencySpanSubtype3 = tracer.spanBuilder(LATENCY_SPAN).setStartTimestamp(1L).startSpan(); - latencySpanSubtype3.end(1000002); + Span latencySpanSubtype3 = + tracer.spanBuilder(LATENCY_SPAN).setStartTimestamp(1L, TimeUnit.NANOSECONDS).startSpan(); + latencySpanSubtype3.end(1000002, TimeUnit.NANOSECONDS); // Boundary 4, >10ms - Span latencySpanSubtype4 = tracer.spanBuilder(LATENCY_SPAN).setStartTimestamp(1L).startSpan(); - latencySpanSubtype4.end(10000002); + Span latencySpanSubtype4 = + tracer.spanBuilder(LATENCY_SPAN).setStartTimestamp(1L, TimeUnit.NANOSECONDS).startSpan(); + latencySpanSubtype4.end(10000002, TimeUnit.NANOSECONDS); // Boundary 5, >100ms - Span latencySpanSubtype5 = tracer.spanBuilder(LATENCY_SPAN).setStartTimestamp(1L).startSpan(); - latencySpanSubtype5.end(100000002); + Span latencySpanSubtype5 = + tracer.spanBuilder(LATENCY_SPAN).setStartTimestamp(1L, TimeUnit.NANOSECONDS).startSpan(); + latencySpanSubtype5.end(100000002, TimeUnit.NANOSECONDS); // Boundary 6, >1s - Span latencySpanSubtype6 = tracer.spanBuilder(LATENCY_SPAN).setStartTimestamp(1L).startSpan(); - latencySpanSubtype6.end(1000000002); + Span latencySpanSubtype6 = + tracer.spanBuilder(LATENCY_SPAN).setStartTimestamp(1L, TimeUnit.NANOSECONDS).startSpan(); + latencySpanSubtype6.end(1000000002, TimeUnit.NANOSECONDS); // Boundary 7, >10s - Span latencySpanSubtype7 = tracer.spanBuilder(LATENCY_SPAN).setStartTimestamp(1L).startSpan(); - latencySpanSubtype7.end(10000000002L); + Span latencySpanSubtype7 = + tracer.spanBuilder(LATENCY_SPAN).setStartTimestamp(1L, TimeUnit.NANOSECONDS).startSpan(); + latencySpanSubtype7.end(10000000002L, TimeUnit.NANOSECONDS); // Boundary 8, >100s - Span latencySpanSubtype8 = tracer.spanBuilder(LATENCY_SPAN).setStartTimestamp(1L).startSpan(); - latencySpanSubtype8.end(100000000002L); + Span latencySpanSubtype8 = + tracer.spanBuilder(LATENCY_SPAN).setStartTimestamp(1L, TimeUnit.NANOSECONDS).startSpan(); + latencySpanSubtype8.end(100000000002L, TimeUnit.NANOSECONDS); TracezZPageHandler tracezZPageHandler = new TracezZPageHandler(dataAggregator); tracezZPageHandler.emitHtml(emptyQueryMap, output); @@ -205,14 +216,18 @@ class TracezZPageHandlerTest { void summaryTable_linkForLatencyBasedSpans_MultipleForOneBoundary() { OutputStream output = new ByteArrayOutputStream(); // 4 samples in boundary 5, >100ms - Span latencySpan100ms1 = tracer.spanBuilder(LATENCY_SPAN).setStartTimestamp(1L).startSpan(); - latencySpan100ms1.end(112931232L); - Span latencySpan100ms2 = tracer.spanBuilder(LATENCY_SPAN).setStartTimestamp(1L).startSpan(); - latencySpan100ms2.end(138694322L); - Span latencySpan100ms3 = tracer.spanBuilder(LATENCY_SPAN).setStartTimestamp(1L).startSpan(); - latencySpan100ms3.end(154486482L); - Span latencySpan100ms4 = tracer.spanBuilder(LATENCY_SPAN).setStartTimestamp(1L).startSpan(); - latencySpan100ms4.end(194892582L); + Span latencySpan100ms1 = + tracer.spanBuilder(LATENCY_SPAN).setStartTimestamp(1L, TimeUnit.NANOSECONDS).startSpan(); + latencySpan100ms1.end(112931232L, TimeUnit.NANOSECONDS); + Span latencySpan100ms2 = + tracer.spanBuilder(LATENCY_SPAN).setStartTimestamp(1L, TimeUnit.NANOSECONDS).startSpan(); + latencySpan100ms2.end(138694322L, TimeUnit.NANOSECONDS); + Span latencySpan100ms3 = + tracer.spanBuilder(LATENCY_SPAN).setStartTimestamp(1L, TimeUnit.NANOSECONDS).startSpan(); + latencySpan100ms3.end(154486482L, TimeUnit.NANOSECONDS); + Span latencySpan100ms4 = + tracer.spanBuilder(LATENCY_SPAN).setStartTimestamp(1L, TimeUnit.NANOSECONDS).startSpan(); + latencySpan100ms4.end(194892582L, TimeUnit.NANOSECONDS); TracezZPageHandler tracezZPageHandler = new TracezZPageHandler(dataAggregator); tracezZPageHandler.emitHtml(emptyQueryMap, output); @@ -270,10 +285,12 @@ class TracezZPageHandlerTest { @Test void spanDetails_emitLatencySpanDetailsCorrectly() { OutputStream output = new ByteArrayOutputStream(); - Span latencySpan1 = tracer.spanBuilder(LATENCY_SPAN).setStartTimestamp(1L).startSpan(); - latencySpan1.end(10002); - Span latencySpan2 = tracer.spanBuilder(LATENCY_SPAN).setStartTimestamp(1L).startSpan(); - latencySpan2.end(10002); + Span latencySpan1 = + tracer.spanBuilder(LATENCY_SPAN).setStartTimestamp(1L, TimeUnit.NANOSECONDS).startSpan(); + latencySpan1.end(10002, TimeUnit.NANOSECONDS); + Span latencySpan2 = + tracer.spanBuilder(LATENCY_SPAN).setStartTimestamp(1L, TimeUnit.NANOSECONDS).startSpan(); + latencySpan2.end(10002, TimeUnit.NANOSECONDS); Map queryMap = ImmutableMap.of("zspanname", LATENCY_SPAN, "ztype", "1", "zsubtype", "1"); diff --git a/sdk/tracing/src/main/java/io/opentelemetry/sdk/trace/RecordEventsReadableSpan.java b/sdk/tracing/src/main/java/io/opentelemetry/sdk/trace/RecordEventsReadableSpan.java index 03169a7448..6780cee4ad 100644 --- a/sdk/tracing/src/main/java/io/opentelemetry/sdk/trace/RecordEventsReadableSpan.java +++ b/sdk/tracing/src/main/java/io/opentelemetry/sdk/trace/RecordEventsReadableSpan.java @@ -31,6 +31,7 @@ import java.io.StringWriter; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nonnull; @@ -317,11 +318,11 @@ final class RecordEventsReadableSpan implements ReadWriteSpan { } @Override - public ReadWriteSpan addEvent(String name, long timestamp) { + public ReadWriteSpan addEvent(String name, long timestamp, TimeUnit unit) { if (name == null) { return this; } - addTimedEvent(Event.create(timestamp, name, Attributes.empty(), 0)); + addTimedEvent(Event.create(unit.toNanos(timestamp), name, Attributes.empty(), 0)); return this; } @@ -341,14 +342,14 @@ final class RecordEventsReadableSpan implements ReadWriteSpan { } @Override - public ReadWriteSpan addEvent(String name, Attributes attributes, long timestamp) { + public ReadWriteSpan addEvent(String name, Attributes attributes, long timestamp, TimeUnit unit) { if (name == null) { return this; } int totalAttributeCount = attributes.size(); addTimedEvent( Event.create( - timestamp, + unit.toNanos(timestamp), name, copyAndLimitAttributes(attributes, traceConfig.getMaxNumberOfAttributesPerEvent()), totalAttributeCount)); @@ -408,7 +409,7 @@ final class RecordEventsReadableSpan implements ReadWriteSpan { if (exception == null) { return this; } - long timestamp = clock.now(); + long timestampNanos = clock.now(); Attributes.Builder attributes = Attributes.builder(); attributes.put(SemanticAttributes.EXCEPTION_TYPE, exception.getClass().getCanonicalName()); @@ -423,7 +424,11 @@ final class RecordEventsReadableSpan implements ReadWriteSpan { attributes.putAll(additionalAttributes); } - addEvent(SemanticAttributes.EXCEPTION_EVENT_NAME, attributes.build(), timestamp); + addEvent( + SemanticAttributes.EXCEPTION_EVENT_NAME, + attributes.build(), + timestampNanos, + TimeUnit.NANOSECONDS); return this; } @@ -448,8 +453,11 @@ final class RecordEventsReadableSpan implements ReadWriteSpan { } @Override - public void end(long timestamp) { - endInternal(timestamp == 0 ? clock.now() : timestamp); + public void end(long timestamp, TimeUnit unit) { + if (unit == null) { + unit = TimeUnit.NANOSECONDS; + } + endInternal(timestamp == 0 ? clock.now() : unit.toNanos(timestamp)); } private void endInternal(long endEpochNanos) { diff --git a/sdk/tracing/src/main/java/io/opentelemetry/sdk/trace/SpanBuilderSdk.java b/sdk/tracing/src/main/java/io/opentelemetry/sdk/trace/SpanBuilderSdk.java index 33c003de30..c69ab9bc47 100644 --- a/sdk/tracing/src/main/java/io/opentelemetry/sdk/trace/SpanBuilderSdk.java +++ b/sdk/tracing/src/main/java/io/opentelemetry/sdk/trace/SpanBuilderSdk.java @@ -34,6 +34,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; /** {@link SpanBuilderSdk} is SDK implementation of {@link Span.Builder}. */ @@ -165,9 +166,9 @@ final class SpanBuilderSdk implements Span.Builder { } @Override - public Span.Builder setStartTimestamp(long startTimestamp) { + public Span.Builder setStartTimestamp(long startTimestamp, TimeUnit unit) { Utils.checkArgument(startTimestamp >= 0, "Negative startTimestamp"); - startEpochNanos = startTimestamp; + startEpochNanos = unit.toNanos(startTimestamp); return this; } diff --git a/sdk/tracing/src/test/java/io/opentelemetry/sdk/trace/RecordEventsReadableSpanTest.java b/sdk/tracing/src/test/java/io/opentelemetry/sdk/trace/RecordEventsReadableSpanTest.java index 68d5d8fa35..d45fbc604c 100644 --- a/sdk/tracing/src/test/java/io/opentelemetry/sdk/trace/RecordEventsReadableSpanTest.java +++ b/sdk/tracing/src/test/java/io/opentelemetry/sdk/trace/RecordEventsReadableSpanTest.java @@ -38,6 +38,7 @@ import io.opentelemetry.sdk.trace.data.SpanData.Link; import io.opentelemetry.sdk.trace.data.SpanData.Status; import java.io.PrintWriter; import java.io.StringWriter; +import java.time.Instant; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -481,11 +482,62 @@ class RecordEventsReadableSpanTest { try { span.addEvent("event1"); span.addEvent("event2", Attributes.of(stringKey("e1key"), "e1Value")); + span.addEvent("event3", 10, TimeUnit.SECONDS); + span.addEvent("event4", Instant.ofEpochSecond(20)); + span.addEvent( + "event5", Attributes.builder().put("foo", "bar").build(), 30, TimeUnit.MILLISECONDS); + span.addEvent( + "event6", Attributes.builder().put("foo", "bar").build(), Instant.ofEpochMilli(1000)); } finally { span.end(); } List events = span.toSpanData().getEvents(); - assertThat(events.size()).isEqualTo(2); + assertThat(events).hasSize(6); + assertThat(events.get(0)) + .satisfies( + event -> { + assertThat(event.getName()).isEqualTo("event1"); + assertThat(event.getAttributes()).isEqualTo(Attributes.empty()); + assertThat(event.getEpochNanos()).isEqualTo(START_EPOCH_NANOS); + }); + assertThat(events.get(1)) + .satisfies( + event -> { + assertThat(event.getName()).isEqualTo("event2"); + assertThat(event.getAttributes()) + .isEqualTo(Attributes.of(stringKey("e1key"), "e1Value")); + assertThat(event.getEpochNanos()).isEqualTo(START_EPOCH_NANOS); + }); + assertThat(events.get(2)) + .satisfies( + event -> { + assertThat(event.getName()).isEqualTo("event3"); + assertThat(event.getAttributes()).isEqualTo(Attributes.empty()); + assertThat(event.getEpochNanos()).isEqualTo(TimeUnit.SECONDS.toNanos(10)); + }); + assertThat(events.get(3)) + .satisfies( + event -> { + assertThat(event.getName()).isEqualTo("event4"); + assertThat(event.getAttributes()).isEqualTo(Attributes.empty()); + assertThat(event.getEpochNanos()).isEqualTo(TimeUnit.SECONDS.toNanos(20)); + }); + assertThat(events.get(4)) + .satisfies( + event -> { + assertThat(event.getName()).isEqualTo("event5"); + assertThat(event.getAttributes()) + .isEqualTo(Attributes.builder().put("foo", "bar").build()); + assertThat(event.getEpochNanos()).isEqualTo(TimeUnit.MILLISECONDS.toNanos(30)); + }); + assertThat(events.get(5)) + .satisfies( + event -> { + assertThat(event.getName()).isEqualTo("event6"); + assertThat(event.getAttributes()) + .isEqualTo(Attributes.builder().put("foo", "bar").build()); + assertThat(event.getEpochNanos()).isEqualTo(TimeUnit.MILLISECONDS.toNanos(1000)); + }); } @Test @@ -511,6 +563,20 @@ class RecordEventsReadableSpanTest { assertThat(spanData.getTotalAttributeCount()).isEqualTo(2 * maxNumberOfAttributes); } + @Test + void endWithTimestamp_numeric() { + RecordEventsReadableSpan span1 = createTestRootSpan(); + span1.end(10, TimeUnit.NANOSECONDS); + assertThat(span1.toSpanData().getEndEpochNanos()).isEqualTo(10); + } + + @Test + void endWithTimestamp_instant() { + RecordEventsReadableSpan span1 = createTestRootSpan(); + span1.end(Instant.ofEpochMilli(10)); + assertThat(span1.toSpanData().getEndEpochNanos()).isEqualTo(TimeUnit.MILLISECONDS.toNanos(10)); + } + @Test void droppingAndAddingAttributes() { final int maxNumberOfAttributes = 8; @@ -690,11 +756,15 @@ class RecordEventsReadableSpanTest { span.setStatus(null, null); span.updateName(null); span.addEvent(null); - span.addEvent(null, 0); - span.addEvent(null, null); - span.addEvent(null, null, 0); + span.addEvent(null, 0, null); + span.addEvent(null, (Attributes) null); + span.addEvent(null, (Instant) null); + span.addEvent(null, null, 0, null); + span.addEvent(null, null, null); span.recordException(null); - span.end(0); + span.end(0, TimeUnit.NANOSECONDS); + span.end(1, null); + span.end(null); // Ignored the bad calls SpanData data = span.toSpanData(); diff --git a/sdk/tracing/src/test/java/io/opentelemetry/sdk/trace/SpanBuilderSdkTest.java b/sdk/tracing/src/test/java/io/opentelemetry/sdk/trace/SpanBuilderSdkTest.java index 6ab613359d..d9d6bb5dfe 100644 --- a/sdk/tracing/src/test/java/io/opentelemetry/sdk/trace/SpanBuilderSdkTest.java +++ b/sdk/tracing/src/test/java/io/opentelemetry/sdk/trace/SpanBuilderSdkTest.java @@ -34,9 +34,11 @@ import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.data.SpanData.Link; import io.opentelemetry.sdk.trace.samplers.Sampler; import io.opentelemetry.sdk.trace.samplers.SamplingResult; +import java.time.Instant; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -793,11 +795,36 @@ class SpanBuilderSdkTest { } } + @Test + void startTimestamp_numeric() { + RecordEventsReadableSpan span = + (RecordEventsReadableSpan) + tracerSdk + .spanBuilder(SPAN_NAME) + .setStartTimestamp(10, TimeUnit.NANOSECONDS) + .startSpan(); + span.end(); + assertThat(span.toSpanData().getStartEpochNanos()).isEqualTo(10); + } + + @Test + void startTimestamp_instant() { + RecordEventsReadableSpan span = + (RecordEventsReadableSpan) + tracerSdk + .spanBuilder(SPAN_NAME) + .setStartTimestamp(Instant.ofEpochMilli(100)) + .startSpan(); + span.end(); + assertThat(span.toSpanData().getStartEpochNanos()) + .isEqualTo(TimeUnit.MILLISECONDS.toNanos(100)); + } + @Test void startTimestamp_null() { assertThrows( IllegalArgumentException.class, - () -> tracerSdk.spanBuilder(SPAN_NAME).setStartTimestamp(-1), + () -> tracerSdk.spanBuilder(SPAN_NAME).setStartTimestamp(-1, TimeUnit.NANOSECONDS), "Negative startTimestamp"); }