diff --git a/build.gradle b/build.gradle index f3f4223ab..6259a66e1 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { } -ext.opentracingVersion = '0.13.0' +ext.opentracingVersion = '0.15.0' ext.guavaVersion = '18.0' ext.apacheThriftVersion = '0.9.3' ext.jerseyVersion = '2.22.2' @@ -16,7 +16,6 @@ ext.jacksonVersion = '2.7.4' ext.junitVersion = '4.12' ext.mockitoVersion = '2.0.2-beta' -ext.powermockVersion = '1.6.4' allprojects { apply plugin: 'idea' // intellij support diff --git a/jaeger-context/src/main/test/java/com/uber/jaeger/context/CallableTest.java b/jaeger-context/src/test/java/com/uber/jaeger/context/CallableTest.java similarity index 87% rename from jaeger-context/src/main/test/java/com/uber/jaeger/context/CallableTest.java rename to jaeger-context/src/test/java/com/uber/jaeger/context/CallableTest.java index dc97e7d43..5c9b346b9 100644 --- a/jaeger-context/src/main/test/java/com/uber/jaeger/context/CallableTest.java +++ b/jaeger-context/src/test/java/com/uber/jaeger/context/CallableTest.java @@ -19,11 +19,8 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.uber.jaeger.filters.jaxrs2; +package com.uber.jaeger.context; -import com.uber.jaeger.Tracer; -import com.uber.jaeger.reporters.InMemoryReporter; -import com.uber.jaeger.samplers.ConstSampler; import io.opentracing.Span; import org.junit.Before; import org.junit.Test; @@ -35,14 +32,12 @@ import static org.mockito.Mockito.when; public class CallableTest { - Tracer tracer; TraceContext traceContext; Span span; @Before public void setUp() { - tracer = new Tracer.Builder("raza", new InMemoryReporter(), new ConstSampler(true)).build(); - span = tracer.buildSpan("firefly").start(); + span = mock(Span.class); traceContext = mock(TraceContext.class); when(traceContext.getCurrentSpan()).thenReturn(span); when(traceContext.pop()).thenReturn(span); diff --git a/jaeger-context/src/main/test/java/com/uber/jaeger/context/RunnableTest.java b/jaeger-context/src/test/java/com/uber/jaeger/context/RunnableTest.java similarity index 86% rename from jaeger-context/src/main/test/java/com/uber/jaeger/context/RunnableTest.java rename to jaeger-context/src/test/java/com/uber/jaeger/context/RunnableTest.java index b0fbb6d82..2fa3aadb3 100644 --- a/jaeger-context/src/main/test/java/com/uber/jaeger/context/RunnableTest.java +++ b/jaeger-context/src/test/java/com/uber/jaeger/context/RunnableTest.java @@ -19,11 +19,8 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.uber.jaeger.filters.jaxrs2; +package com.uber.jaeger.context; -import com.uber.jaeger.Tracer; -import com.uber.jaeger.reporters.InMemoryReporter; -import com.uber.jaeger.samplers.ConstSampler; import io.opentracing.Span; import org.junit.Before; import org.junit.Test; @@ -35,14 +32,12 @@ import static org.mockito.Mockito.when; public class RunnableTest { - Tracer tracer; TraceContext traceContext; Span span; @Before public void setUp() { - tracer = new Tracer.Builder("death-star", new InMemoryReporter(), new ConstSampler(true)).build(); - span = tracer.buildSpan("destroyer").start(); + span = mock(Span.class); traceContext = mock(TraceContext.class); when(traceContext.getCurrentSpan()).thenReturn(span); when(traceContext.pop()).thenReturn(span); diff --git a/jaeger-context/src/main/test/java/com/uber/jaeger/context/TracedExecutorServiceTest.java b/jaeger-context/src/test/java/com/uber/jaeger/context/TracedExecutorServiceTest.java similarity index 93% rename from jaeger-context/src/main/test/java/com/uber/jaeger/context/TracedExecutorServiceTest.java rename to jaeger-context/src/test/java/com/uber/jaeger/context/TracedExecutorServiceTest.java index a5d944eeb..3ca874ee9 100644 --- a/jaeger-context/src/main/test/java/com/uber/jaeger/context/TracedExecutorServiceTest.java +++ b/jaeger-context/src/test/java/com/uber/jaeger/context/TracedExecutorServiceTest.java @@ -19,20 +19,17 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.uber.jaeger.filters.jaxrs2; +package com.uber.jaeger.context; + +import io.opentracing.Span; +import org.junit.Before; +import org.junit.Test; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; -import com.uber.jaeger.Tracer; -import com.uber.jaeger.reporters.InMemoryReporter; -import com.uber.jaeger.samplers.ConstSampler; -import io.opentracing.Span; -import org.junit.Before; -import org.junit.Test; - import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; @@ -41,14 +38,13 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.powermock.api.mockito.PowerMockito.when; +import static org.mockito.Mockito.when; public class TracedExecutorServiceTest { TimeUnit TIME_UNIT = TimeUnit.MILLISECONDS; TracedExecutorService tracedExecutorService; ExecutorService wrappedExecutorService; - Tracer tracer = new Tracer.Builder("test-executor-service", new InMemoryReporter(), new ConstSampler(true)).build(); Span span; TraceContext traceContext; List> callableList; @@ -56,7 +52,7 @@ public class TracedExecutorServiceTest { @Before public void setUp() { wrappedExecutorService = mock(ExecutorService.class); - span = tracer.buildSpan("span-op").start(); + span = mock(Span.class); traceContext = mock(TraceContext.class); when(traceContext.pop()).thenReturn(span); when(traceContext.getCurrentSpan()).thenReturn(span); diff --git a/jaeger-core/build.gradle b/jaeger-core/build.gradle index a25926cbd..758ee936f 100644 --- a/jaeger-core/build.gradle +++ b/jaeger-core/build.gradle @@ -20,10 +20,6 @@ dependencies { testCompile group: 'junit', name: 'junit', version: junitVersion testCompile group: 'org.mockito', name: 'mockito-all', version: mockitoVersion - // TODO use only one mocking framework - testCompile group: 'org.powermock', name: 'powermock-api-mockito', version: powermockVersion - testCompile group: 'org.powermock', name: 'powermock-core', version: powermockVersion - testCompile group: 'org.powermock', name: 'powermock-module-junit4', version: powermockVersion } compileThrift { diff --git a/jaeger-core/src/main/java/com/uber/jaeger/Span.java b/jaeger-core/src/main/java/com/uber/jaeger/Span.java index 9543dcf0c..b5089f1c2 100644 --- a/jaeger-core/src/main/java/com/uber/jaeger/Span.java +++ b/jaeger-core/src/main/java/com/uber/jaeger/Span.java @@ -22,32 +22,43 @@ package com.uber.jaeger; import com.twitter.zipkin.thriftjava.Endpoint; -import com.uber.jaeger.utils.Utils; import io.opentracing.tag.Tags; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; public class Span implements io.opentracing.Span { private final Tracer tracer; - private final long start; // time in microseconds - private final String operationName; + private final long startTimeMicroseconds; + private final long startTimeNanoTicks; + private final boolean computeDurationViaNanoTicks; + private long durationMicroseconds; // span durationMicroseconds + private String operationName; private SpanContext context; private Endpoint peer; - private long duration; // time in microseconds - private Map tags; + private Map tags; private List logs; private String localComponent; private boolean isClient; private boolean isRPC; - Span(Tracer tracer, String operationName, SpanContext context, long start, Map tags) { + Span(Tracer tracer, + String operationName, + SpanContext context, + long startTimeMicroseconds, + long startTimeNanoTicks, + boolean computeDurationViaNanoTicks, + Map tags + ) { this.tracer = tracer; this.operationName = operationName; this.context = context; - this.start = start; + this.startTimeMicroseconds = startTimeMicroseconds; + this.startTimeNanoTicks = startTimeNanoTicks; + this.computeDurationViaNanoTicks = computeDurationViaNanoTicks; this.tags = tags; } @@ -56,12 +67,12 @@ public String getLocalComponent() { } public long getStart() { - return start; + return startTimeMicroseconds; } public long getDuration() { synchronized (this) { - return duration; + return durationMicroseconds; } } @@ -87,13 +98,22 @@ private Endpoint getOrMakePeer() { public Map getTags() { synchronized (this) { - // TODO wasteful allocation and copying - return new HashMap<>(tags); + return Collections.unmodifiableMap(tags); } } + @Override + public Span setOperationName(String operationName) { + synchronized (this) { + this.operationName = operationName; + } + return this; + } + public String getOperationName() { - return operationName; + synchronized (this) { + return operationName; + } } public List getLogs() { @@ -101,9 +121,7 @@ public List getLogs() { if (logs == null) { return null; } - - // TODO wasteful allocation and copying - return new ArrayList<>(logs); + return Collections.unmodifiableList(logs); } } @@ -143,13 +161,22 @@ public io.opentracing.SpanContext context() { @Override public void finish() { - finish(Utils.getMicroseconds()); + if (computeDurationViaNanoTicks) { + long nanoDuration = tracer.clock().currentNanoTicks() - startTimeNanoTicks; + finishWithDuration(nanoDuration / 1000); + } else { + finish(tracer.clock().currentTimeMicros()); + } } @Override public void finish(long finishMicros) { + finishWithDuration(finishMicros - startTimeMicroseconds); + } + + private void finishWithDuration(long durationMicros) { synchronized(this) { - this.duration = finishMicros - start; + this.durationMicroseconds = durationMicros; } if (context.isSampled()) { @@ -238,7 +265,7 @@ private Span setTagAsObject(String key, Object value) { @Override public Span log(String message, /* @Nullable */ Object payload) { - return log(Utils.getMicroseconds(), message, payload); + return log(tracer.clock().currentTimeMicros(), message, payload); } @Override diff --git a/jaeger-core/src/main/java/com/uber/jaeger/Tracer.java b/jaeger-core/src/main/java/com/uber/jaeger/Tracer.java index d6997d346..8af6f9b3e 100644 --- a/jaeger-core/src/main/java/com/uber/jaeger/Tracer.java +++ b/jaeger-core/src/main/java/com/uber/jaeger/Tracer.java @@ -31,6 +31,8 @@ import com.uber.jaeger.propagation.TextMapCodec; import com.uber.jaeger.reporters.Reporter; import com.uber.jaeger.samplers.Sampler; +import com.uber.jaeger.utils.Clock; +import com.uber.jaeger.utils.SystemClock; import com.uber.jaeger.utils.Utils; import io.opentracing.References; import io.opentracing.propagation.Format; @@ -51,16 +53,30 @@ public class Tracer implements io.opentracing.Tracer { public static final String VERSION = loadVersion(); private final static Logger logger = LoggerFactory.getLogger(Tracer.class); + private final String serviceName; private final Reporter reporter; private final Sampler sampler; private final PropagationRegistry registry; - private final String serviceName; + private final Clock clock; private final Metrics metrics; private final int ip; private final Map tags; - private Tracer(String serviceName, Reporter reporter, Sampler sampler, PropagationRegistry registry, Metrics metrics) { + private Tracer( + String serviceName, + Reporter reporter, + Sampler sampler, + PropagationRegistry registry, + Clock clock, + Metrics metrics + ) { this.serviceName = serviceName; + this.reporter = reporter; + this.sampler = sampler; + this.registry = registry; + this.clock = clock; + this.metrics = metrics; + int ip; try { ip = Utils.ipToInt(Inet4Address.getLocalHost().getHostAddress()); @@ -77,11 +93,6 @@ private Tracer(String serviceName, Reporter reporter, Sampler sampler, Propagati tags.put("jaeger.hostname", hostname); } this.tags = Collections.unmodifiableMap(tags); - - this.reporter = reporter; - this.sampler = sampler; - this.registry = registry; - this.metrics = metrics; } public Metrics getMetrics() { @@ -96,6 +107,8 @@ public int getIP() { return ip; } + Clock clock() { return clock; } + Reporter getReporter() { return reporter; } @@ -131,7 +144,7 @@ public io.opentracing.SpanContext extract(Format format, T carrier) { private class SpanBuilder implements io.opentracing.Tracer.SpanBuilder { private String operationName = null; - private long start; + private long startTimeMicroseconds; private SpanContext parent; private final Map tags = new HashMap<>(); @@ -139,6 +152,14 @@ private class SpanBuilder implements io.opentracing.Tracer.SpanBuilder { this.operationName = operationName; } + @Override + public Iterable> baggageItems() { + if (parent == null) { + return Collections.emptyList(); + } + return parent.baggageItems(); + } + @Override public io.opentracing.Tracer.SpanBuilder asChildOf(io.opentracing.SpanContext parent) { return addReference(References.CHILD_OF, parent); @@ -178,7 +199,7 @@ public io.opentracing.Tracer.SpanBuilder withTag(String key, Number value) { @Override public io.opentracing.Tracer.SpanBuilder withStartTimestamp(long microseconds) { - this.start = microseconds; + this.startTimeMicroseconds = microseconds; return this; } @@ -222,8 +243,15 @@ private boolean isRPCServer() { public io.opentracing.Span start() { SpanContext context = parent == null ? createNewContext() : createChildContext(); - if (start == 0) { - start = Utils.getMicroseconds(); + long startTimeNanoTicks = 0; + boolean computeDurationViaNanoTicks = false; + + if (startTimeMicroseconds == 0) { + startTimeMicroseconds = clock.currentTimeMicros(); + if (!clock.isMicrosAccurate()) { + startTimeNanoTicks = clock.currentNanoTicks(); + computeDurationViaNanoTicks = true; + } } if (parent == null || isRPCServer()) { @@ -231,7 +259,9 @@ public io.opentracing.Span start() { tags.putAll(Tracer.this.tags); } - Span span = new Span(Tracer.this, operationName, context, start, tags); + Span span = new Span(Tracer.this, operationName, context, + startTimeMicroseconds, startTimeNanoTicks, + computeDurationViaNanoTicks, tags); if (context.isSampled()) { metrics.spansSampled.inc(1); } else { @@ -251,6 +281,7 @@ public static final class Builder { private final PropagationRegistry registry = new PropagationRegistry(); private Metrics metrics; private String serviceName; + private Clock clock = new SystemClock(); public Builder(String serviceName, Reporter reporter, Sampler sampler) { if (serviceName == null || serviceName.trim().length() == 0) { @@ -264,7 +295,6 @@ public Builder(String serviceName, Reporter reporter, Sampler sampler) { TextMapCodec textMapCodec = new TextMapCodec(false); this.registerInjector(Format.Builtin.TEXT_MAP, textMapCodec); this.registerExtractor(Format.Builtin.TEXT_MAP, textMapCodec); - // TODO for now we register the same codec for HTTP_HEADERS TextMapCodec httpCodec = new TextMapCodec(true); this.registerInjector(Format.Builtin.HTTP_HEADERS, httpCodec); this.registerExtractor(Format.Builtin.HTTP_HEADERS, httpCodec); @@ -286,13 +316,18 @@ public Builder withStatsReporter(StatsReporter statsReporter) { return this; } + public Builder withClock(Clock clock) { + this.clock = clock; + return this; + } + Builder withMetrics(Metrics metrics) { this.metrics = metrics; return this; } public Tracer build() { - return new Tracer(this.serviceName, reporter, sampler, registry, metrics); + return new Tracer(this.serviceName, reporter, sampler, registry, clock, metrics); } } diff --git a/jaeger-core/src/main/java/com/uber/jaeger/reporters/InMemoryReporter.java b/jaeger-core/src/main/java/com/uber/jaeger/reporters/InMemoryReporter.java index 7e1b29712..8c912c1f5 100644 --- a/jaeger-core/src/main/java/com/uber/jaeger/reporters/InMemoryReporter.java +++ b/jaeger-core/src/main/java/com/uber/jaeger/reporters/InMemoryReporter.java @@ -50,4 +50,10 @@ public List getSpans() { return spans; } } + + public void clear() { + synchronized (this) { + spans.clear(); + } + } } \ No newline at end of file diff --git a/jaeger-core/src/main/java/com/uber/jaeger/utils/Clock.java b/jaeger-core/src/main/java/com/uber/jaeger/utils/Clock.java new file mode 100644 index 000000000..fed92e615 --- /dev/null +++ b/jaeger-core/src/main/java/com/uber/jaeger/utils/Clock.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2016, Uber Technologies, Inc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.uber.jaeger.utils; + +/** + * A small abstraction around system clock that aims to provide microsecond precision + * with the best accuracy possible. + */ +public interface Clock { + /** + * Returns the current time in microseconds. + * + * @return the difference, measured in microseconds, between the current time + * and and the Epoch (that is, midnight, January 1, 1970 UTC). + */ + long currentTimeMicros(); + + /** + * Returns the current value of the running Java Virtual Machine's + * high-resolution time source, in nanoseconds. + * + *

This method can only be used to measure elapsed time and is + * not related to any other notion of system or wall-clock time. + * + * @return the current value of the running Java Virtual Machine's + * high-resolution time source, in nanoseconds + */ + long currentNanoTicks(); + + /** + * @return true if the time returned by {@link #currentTimeMicros()} + * is accurate enough to calculate span duration as (end-start). + * If this method returns false, the {@code Tracer} will use + * {@link #currentNanoTicks()} for calculating duration instead. + */ + boolean isMicrosAccurate(); +} diff --git a/jaeger-core/src/main/java/com/uber/jaeger/utils/RateLimiter.java b/jaeger-core/src/main/java/com/uber/jaeger/utils/RateLimiter.java index 9483e120d..3dbcff80d 100644 --- a/jaeger-core/src/main/java/com/uber/jaeger/utils/RateLimiter.java +++ b/jaeger-core/src/main/java/com/uber/jaeger/utils/RateLimiter.java @@ -23,19 +23,27 @@ public class RateLimiter { private final double creditsPerSecond; + private final double creditsPerNanosecond; + private final Clock clock; private double balance; private long lastTick; public RateLimiter(double creditsPerSecond) { + this(creditsPerSecond, new SystemClock()); + } + + public RateLimiter(double creditsPerSecond, Clock clock) { + this.clock = clock; this.creditsPerSecond = creditsPerSecond; this.balance = creditsPerSecond; + this.creditsPerNanosecond = creditsPerSecond / 1.0e9; } public boolean checkCredit(double itemCost) { - long currentTime = Utils.getNanoseconds(); + long currentTime = clock.currentNanoTicks(); double elapsedTime = currentTime - lastTick; lastTick = currentTime; - balance += (elapsedTime / 1.0e9) * creditsPerSecond; + balance += elapsedTime * creditsPerNanosecond; if (balance > creditsPerSecond) { balance = creditsPerSecond; } diff --git a/jaeger-core/src/main/java/com/uber/jaeger/utils/SystemClock.java b/jaeger-core/src/main/java/com/uber/jaeger/utils/SystemClock.java new file mode 100644 index 000000000..659ff44e8 --- /dev/null +++ b/jaeger-core/src/main/java/com/uber/jaeger/utils/SystemClock.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2016, Uber Technologies, Inc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.uber.jaeger.utils; + +/** + * Default implementation of a clock that delegates its calls to the system clock. + * The microsecond-precision time is simulated by (millis * 1000), therefore + * the {@link #isMicrosAccurate()} is false. + * + * @see System#currentTimeMillis() + * @see System#nanoTime() + */ +public class SystemClock implements Clock { + + @Override + public long currentTimeMicros() { + return System.currentTimeMillis() * 1000; + } + + @Override + public long currentNanoTicks() { + return System.nanoTime(); + } + + @Override + public boolean isMicrosAccurate() { + return false; + } +} diff --git a/jaeger-core/src/main/java/com/uber/jaeger/utils/Utils.java b/jaeger-core/src/main/java/com/uber/jaeger/utils/Utils.java index 0dee5e457..af4dd775b 100644 --- a/jaeger-core/src/main/java/com/uber/jaeger/utils/Utils.java +++ b/jaeger-core/src/main/java/com/uber/jaeger/utils/Utils.java @@ -27,7 +27,6 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.TimeUnit; public class Utils { public static String normalizeBaggageKey(String key) { @@ -64,12 +63,4 @@ public static long uniqueID() { } return val; } - - public static long getMicroseconds() { - return TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis()) + (TimeUnit.NANOSECONDS.toMicros(System.nanoTime()) % 1000); - } - - public static long getNanoseconds() { - return TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()) + (System.nanoTime() % 1000000); - } } \ No newline at end of file diff --git a/jaeger-core/src/test/java/com/uber/jaeger/SpanTest.java b/jaeger-core/src/test/java/com/uber/jaeger/SpanTest.java index 84cf6c718..5bdd5931d 100644 --- a/jaeger-core/src/test/java/com/uber/jaeger/SpanTest.java +++ b/jaeger-core/src/test/java/com/uber/jaeger/SpanTest.java @@ -21,28 +21,24 @@ */ package com.uber.jaeger; -import java.util.HashMap; -import java.util.Random; - import com.uber.jaeger.metrics.InMemoryStatsReporter; import com.uber.jaeger.reporters.InMemoryReporter; import com.uber.jaeger.samplers.ConstSampler; -import com.uber.jaeger.utils.Utils; +import com.uber.jaeger.utils.Clock; import io.opentracing.tag.Tags; import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.BDDMockito; -import org.powermock.api.mockito.PowerMockito; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; + +import java.util.Random; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; -@RunWith(PowerMockRunner.class) -@PrepareForTest(Utils.class) public class SpanTest { + private Clock clock; + private InMemoryReporter reporter; private Tracer tracer; private Span span; private InMemoryStatsReporter metricsReporter; @@ -51,9 +47,11 @@ public class SpanTest { @Before public void setUp() throws Exception { metricsReporter = new InMemoryStatsReporter(); - - tracer = new Tracer.Builder("SamplerTest", new InMemoryReporter(), new ConstSampler(true)) + reporter = new InMemoryReporter(); + clock = mock(Clock.class); + tracer = new Tracer.Builder("SamplerTest", reporter, new ConstSampler(true)) .withStatsReporter(metricsReporter) + .withClock(clock) .build(); span = (Span) tracer.buildSpan("some-operation").start(); @@ -85,6 +83,15 @@ public void testSetBooleanTag() { assertEquals(expected, span.getTags().get(key)); } + @Test + public void testSetOperationName() { + String expected = "modified.operation"; + + assertEquals("some-operation", span.getOperationName()); + span.setOperationName(expected); + assertEquals(expected, span.getOperationName()); + } + @Test public void testSetStringTag() { String expected = "expected.value"; @@ -104,16 +111,67 @@ public void testSetNumberTag() { } @Test - public void testSpanFinish() { - Span span = (Span) tracer.buildSpan("test-service-name").withStartTimestamp(333).start(); + public void testWithTimestampAccurateClock() { + testWithTimestamp(true); + } - PowerMockito.mockStatic(Utils.class); - BDDMockito.given(Utils.getMicroseconds()).willReturn(999L); + @Test + public void testWithTimestampInaccurateClock() { + testWithTimestamp(false); + } + private void testWithTimestamp(boolean accurate) { + when(clock.isMicrosAccurate()).thenReturn(accurate); + when(clock.currentTimeMicros()) + .thenThrow(new IllegalStateException("currentTimeMicros() called")); + when(clock.currentNanoTicks()) + .thenThrow(new IllegalStateException("currentNanoTicks() called")); + + Span span = (Span) tracer.buildSpan("test-service-name") + .withStartTimestamp(567) + .start(); + span.finish(999); + + assertEquals(1, reporter.getSpans().size()); + assertEquals(567, span.getStart()); + assertEquals(999-567, span.getDuration()); + } + + @Test + public void testWithoutTimestampsAccurateClock() { + when(clock.isMicrosAccurate()).thenReturn(true); + when(clock.currentTimeMicros()) + .thenReturn(1L) + .thenReturn(5L); + when(clock.currentNanoTicks()) + .thenThrow(new IllegalStateException("currentNanoTicks() called")); + + Span span = (Span) tracer.buildSpan("test-service-name") + .start(); span.finish(); - InMemoryReporter reporter = (InMemoryReporter) tracer.getReporter(); - assertEquals(reporter.getSpans().size(), 1); - assertEquals(span.getDuration(), 666); + + assertEquals(1, reporter.getSpans().size()); + assertEquals(1, span.getStart()); + assertEquals(4, span.getDuration()); + } + + @Test + public void testWithoutTimestampsInaccurateClock() { + when(clock.isMicrosAccurate()).thenReturn(false); + when(clock.currentTimeMicros()) + .thenReturn(100L) + .thenThrow(new IllegalStateException("currentTimeMicros() called 2nd time")); + when(clock.currentNanoTicks()) + .thenReturn(20000L) + .thenReturn(30000L); + + Span span = (Span) tracer.buildSpan("test-service-name") + .start(); + span.finish(); + + assertEquals(1, reporter.getSpans().size()); + assertEquals(100, span.getStart()); + assertEquals(10, span.getDuration()); } @Test @@ -139,7 +197,7 @@ public void testOperationName() { public void testLog() { long expectedTimestamp = 2222; String expectedLog = "some-log"; - Object expectedPayload = this.tracer; + Object expectedPayload = new Object(); span.log(expectedTimestamp, expectedLog, expectedPayload); @@ -152,12 +210,11 @@ public void testLog() { @Test public void testLogWithTimestamp() { - long expectedTimestamp = 2222; - String expectedLog = "some-log"; - Object expectedPayload = this.tracer; + final long expectedTimestamp = 2222; + final String expectedLog = "some-log"; + final Object expectedPayload = new Object(); - PowerMockito.mockStatic(Utils.class); - BDDMockito.given(Utils.getMicroseconds()).willReturn(expectedTimestamp); + when(clock.currentTimeMicros()).thenReturn(expectedTimestamp); Span span = (Span) tracer.buildSpan("test-service-operation").start(); span.log(expectedLog, expectedPayload); diff --git a/jaeger-core/src/test/java/com/uber/jaeger/utils/RateLimiterTest.java b/jaeger-core/src/test/java/com/uber/jaeger/utils/RateLimiterTest.java index 566834a3c..c5a7fb45f 100644 --- a/jaeger-core/src/test/java/com/uber/jaeger/utils/RateLimiterTest.java +++ b/jaeger-core/src/test/java/com/uber/jaeger/utils/RateLimiterTest.java @@ -22,49 +22,60 @@ package com.uber.jaeger.utils; import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.BDDMockito; -import org.powermock.api.mockito.PowerMockito; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; import java.util.concurrent.TimeUnit; import static junit.framework.TestCase.assertFalse; import static org.junit.Assert.assertTrue; -@RunWith(PowerMockRunner.class) -@PrepareForTest(Utils.class) public class RateLimiterTest { RateLimiter limiter; + private static class MockClock implements Clock { + + long timeNanos; + + @Override + public long currentTimeMicros() { + return 0; + } + + @Override + public long currentNanoTicks() { + return timeNanos; + } + + @Override + public boolean isMicrosAccurate() { + return false; + } + } + @Test public void testRateLimiterWholeNumber() { - RateLimiter limiter = new RateLimiter(2.0); + MockClock clock = new MockClock(); + RateLimiter limiter = new RateLimiter(2.0, clock); long currentTime = TimeUnit.MICROSECONDS.toNanos(100); - PowerMockito.mockStatic(Utils.class); - BDDMockito.given(Utils.getNanoseconds()).willReturn(currentTime); + clock.timeNanos = currentTime; assertTrue(limiter.checkCredit(1.0)); assertTrue(limiter.checkCredit(1.0)); assertFalse(limiter.checkCredit(1.0)); // move time 250ms forward, not enough credits to pay for 1.0 item currentTime += TimeUnit.MILLISECONDS.toNanos(250); - BDDMockito.given(Utils.getNanoseconds()).willReturn(currentTime); + clock.timeNanos = currentTime; assertFalse(limiter.checkCredit(1.0)); // move time 500ms forward, now enough credits to pay for 1.0 item currentTime += TimeUnit.MILLISECONDS.toNanos(500); - BDDMockito.given(Utils.getNanoseconds()).willReturn(currentTime); - + clock.timeNanos = currentTime; assertTrue(limiter.checkCredit(1.0)); assertFalse(limiter.checkCredit(1.0)); // move time 5s forward, enough to accumulate credits for 10 messages, but it should still be capped at 2 currentTime += TimeUnit.MILLISECONDS.toNanos(5000); - BDDMockito.given(Utils.getNanoseconds()).willReturn(currentTime); - + clock.timeNanos = currentTime; assertTrue(limiter.checkCredit(1.0)); assertTrue(limiter.checkCredit(1.0)); @@ -75,31 +86,29 @@ public void testRateLimiterWholeNumber() { @Test public void testRateLimiterLessThanOne() { - RateLimiter limiter = new RateLimiter(0.5); + MockClock clock = new MockClock(); + RateLimiter limiter = new RateLimiter(0.5, clock); long currentTime = TimeUnit.MICROSECONDS.toNanos(100); - PowerMockito.mockStatic(Utils.class); - BDDMockito.given(Utils.getNanoseconds()).willReturn(currentTime); + clock.timeNanos = currentTime; assertTrue(limiter.checkCredit(0.25)); assertTrue(limiter.checkCredit(0.25)); assertFalse(limiter.checkCredit(0.25)); // move time 250ms forward, not enough credits to pay for 1.0 item currentTime += TimeUnit.MILLISECONDS.toNanos(250); - BDDMockito.given(Utils.getNanoseconds()).willReturn(currentTime); + clock.timeNanos = currentTime; assertFalse(limiter.checkCredit(0.25)); // move time 500ms forward, now enough credits to pay for 1.0 item currentTime += TimeUnit.MILLISECONDS.toNanos(500); - BDDMockito.given(Utils.getNanoseconds()).willReturn(currentTime); - + clock.timeNanos = currentTime; assertTrue(limiter.checkCredit(0.25)); assertFalse(limiter.checkCredit(0.25)); // move time 5s forward, enough to accumulate credits for 10 messages, but it should still be capped at 2 currentTime += TimeUnit.MILLISECONDS.toNanos(5000); - BDDMockito.given(Utils.getNanoseconds()).willReturn(currentTime); - + clock.timeNanos = currentTime; assertTrue(limiter.checkCredit(0.25)); assertTrue(limiter.checkCredit(0.25)); diff --git a/jaeger-jaxrs2/build.gradle b/jaeger-jaxrs2/build.gradle index a30201ff6..3620bc73d 100644 --- a/jaeger-jaxrs2/build.gradle +++ b/jaeger-jaxrs2/build.gradle @@ -16,10 +16,6 @@ dependencies { testCompile group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: jacksonVersion testCompile group: 'junit', name: 'junit', version: junitVersion testCompile group: 'org.mockito', name: 'mockito-all', version: mockitoVersion - // TODO use only one mocking framework - testCompile group: 'org.powermock', name: 'powermock-api-mockito', version: powermockVersion - testCompile group: 'org.powermock', name: 'powermock-core', version: powermockVersion - testCompile group: 'org.powermock', name: 'powermock-module-junit4', version: powermockVersion } jacocoTestReport { diff --git a/jaeger-jaxrs2/src/test/java/com/uber/jaeger/propagation/FilterIntegrationTest.java b/jaeger-jaxrs2/src/test/java/com/uber/jaeger/propagation/FilterIntegrationTest.java index f44f895ee..7d8c6a9c5 100644 --- a/jaeger-jaxrs2/src/test/java/com/uber/jaeger/propagation/FilterIntegrationTest.java +++ b/jaeger-jaxrs2/src/test/java/com/uber/jaeger/propagation/FilterIntegrationTest.java @@ -21,14 +21,6 @@ */ package com.uber.jaeger.propagation; -import javax.ws.rs.client.Client; -import javax.ws.rs.client.ClientBuilder; -import javax.ws.rs.client.WebTarget; -import javax.ws.rs.container.ContainerRequestContext; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedHashMap; -import javax.ws.rs.core.Response; - import com.fasterxml.jackson.databind.ObjectMapper; import com.uber.jaeger.Span; import com.uber.jaeger.context.ThreadLocalTraceContext; @@ -46,11 +38,19 @@ import org.junit.Before; import org.junit.Test; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedHashMap; +import javax.ws.rs.core.Response; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.BDDMockito.given; -import static org.powermock.api.mockito.PowerMockito.mock; +import static org.mockito.Mockito.mock; public class FilterIntegrationTest { private JerseyServer server;