diff --git a/apm-agent-core/src/main/java/co/elastic/apm/impl/ElasticApmTracer.java b/apm-agent-core/src/main/java/co/elastic/apm/impl/ElasticApmTracer.java index 367e2fee74..92fe867e99 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/impl/ElasticApmTracer.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/impl/ElasticApmTracer.java @@ -229,6 +229,8 @@ public void captureException(long epochTimestampMillis, @Nullable Throwable e, @ error.getTransaction().getTransactionId().copyFrom(transaction.getId()); } error.asChildOf(active); + } else { + error.getTraceContext().getId().setToRandomValue(); } reporter.report(error); } diff --git a/apm-agent-core/src/main/java/co/elastic/apm/impl/error/ErrorCapture.java b/apm-agent-core/src/main/java/co/elastic/apm/impl/error/ErrorCapture.java index 77123df41c..527fa89daf 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/impl/error/ErrorCapture.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/impl/error/ErrorCapture.java @@ -37,7 +37,7 @@ */ public class ErrorCapture implements Recyclable { - private final TraceContext traceContext = new TraceContext(); + private final TraceContext traceContext = TraceContext.with128BitId(); /** * Context diff --git a/apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/ConstantSampler.java b/apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/ConstantSampler.java index a9c78b65c5..63d20cf15e 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/ConstantSampler.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/ConstantSampler.java @@ -19,7 +19,7 @@ */ package co.elastic.apm.impl.sampling; -import co.elastic.apm.impl.transaction.TraceId; +import co.elastic.apm.impl.transaction.Id; /** * This is a implementation of {@link Sampler} which always returns the same sampling decision. @@ -44,7 +44,7 @@ public static Sampler of(boolean decision) { } @Override - public boolean isSampled(TraceId traceId) { + public boolean isSampled(Id traceId) { return decision; } } diff --git a/apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/ProbabilitySampler.java b/apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/ProbabilitySampler.java index 160d5c4d0f..3437e17869 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/ProbabilitySampler.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/ProbabilitySampler.java @@ -19,8 +19,7 @@ */ package co.elastic.apm.impl.sampling; -import co.elastic.apm.impl.transaction.TraceId; -import co.elastic.apm.impl.transaction.TransactionId; +import co.elastic.apm.impl.transaction.Id; /** * This implementation of {@link Sampler} samples based on a sampling probability (or sampling rate) between 0.0 and 1.0. @@ -30,9 +29,9 @@ *
* Implementation notes: *
- * We are taking advantage of the fact, that the {@link TransactionId} is randomly generated. + * We are taking advantage of the fact, that the {@link Id} is randomly generated. * So instead of generating another random number, - * we just see if the long value returned by {@link TransactionId#getMostSignificantBits()} + * we just see if the long value returned by {@link Id#getLeastSignificantBits()} * falls into the range between the {@code lowerBound} and thehigherBound
.
* This is a visual representation of the mechanism with a sampling rate of 0.5 (=50%):
* @@ -49,6 +48,11 @@ public class ProbabilitySampler implements Sampler { private final long lowerBound; private final long higherBound; + private ProbabilitySampler(double samplingRate) { + higherBound = (long) (Long.MAX_VALUE * samplingRate); + lowerBound = -higherBound; + } + public static Sampler of(double samplingRate) { if (samplingRate == 1) { return ConstantSampler.of(true); @@ -59,14 +63,9 @@ public static Sampler of(double samplingRate) { return new ProbabilitySampler(samplingRate); } - private ProbabilitySampler(double samplingRate) { - higherBound = (long) (Long.MAX_VALUE * samplingRate); - lowerBound = -higherBound; - } - @Override - public boolean isSampled(TraceId traceId) { - final long mostSignificantBits = traceId.getMostSignificantBits(); + public boolean isSampled(Id traceId) { + final long mostSignificantBits = traceId.getLeastSignificantBits(); return mostSignificantBits > lowerBound && mostSignificantBits < higherBound; } } diff --git a/apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/Sampler.java b/apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/Sampler.java index 105e9e4ba9..974fa4c088 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/Sampler.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/Sampler.java @@ -20,7 +20,7 @@ package co.elastic.apm.impl.sampling; import co.elastic.apm.impl.transaction.Span; -import co.elastic.apm.impl.transaction.TraceId; +import co.elastic.apm.impl.transaction.Id; import co.elastic.apm.impl.transaction.Transaction; /** @@ -45,5 +45,5 @@ public interface Sampler { * @param traceId The id of the transaction. * @return The sampling decision. */ - boolean isSampled(TraceId traceId); + boolean isSampled(Id traceId); } diff --git a/apm-agent-core/src/main/java/co/elastic/apm/impl/transaction/AbstractSpan.java b/apm-agent-core/src/main/java/co/elastic/apm/impl/transaction/AbstractSpan.java index 3791a15073..e1c5df5ecc 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/impl/transaction/AbstractSpan.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/impl/transaction/AbstractSpan.java @@ -31,7 +31,7 @@ public abstract class AbstractSpanimplements Recyclable { private static final Logger logger = LoggerFactory.getLogger(AbstractSpan.class); - protected final TraceContext traceContext = new TraceContext(); + protected final TraceContext traceContext = TraceContext.with64BitId(); /** * Generic designation of a transaction in the scope of a single service (eg: 'GET /users/:id') */ diff --git a/apm-agent-core/src/main/java/co/elastic/apm/impl/transaction/Id.java b/apm-agent-core/src/main/java/co/elastic/apm/impl/transaction/Id.java new file mode 100644 index 0000000000..2e6bd8dcd1 --- /dev/null +++ b/apm-agent-core/src/main/java/co/elastic/apm/impl/transaction/Id.java @@ -0,0 +1,170 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 Elastic and contributors + * %% + * 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. + * #L% + */ +package co.elastic.apm.impl.transaction; + +import co.elastic.apm.objectpool.Recyclable; +import co.elastic.apm.util.HexUtils; +import com.dslplatform.json.JsonWriter; + +import javax.annotation.Nullable; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + +/** + * A 128 bit globally unique ID of the whole trace forest + */ +public class Id implements Recyclable { + + private final byte[] data; + private boolean empty = true; + @Nullable + private String cachedStringRepresentation; + + public static Id new128BitId() { + return new Id(16); + } + + public static Id new64BitId() { + return new Id(8); + } + + private Id(int idLengthBytes) { + data = new byte[idLengthBytes]; + } + + public void setToRandomValue() { + setToRandomValue(ThreadLocalRandom.current()); + } + + public void setToRandomValue(Random random) { + random.nextBytes(data); + onMutation(false); + } + + public void fromHexString(String hexEncodedString, int offset) { + HexUtils.nextBytes(hexEncodedString, offset, data); + onMutation(); + } + + public void fromLongs(long... values) { + if (values.length * Long.BYTES != data.length) { + throw new IllegalArgumentException("Invalid number of long values"); + } + final ByteBuffer buffer = ByteBuffer.wrap(data); + for (long value : values) { + buffer.putLong(value); + } + onMutation(); + } + + @Override + public void resetState() { + for (int i = 0; i < data.length; i++) { + data[i] = 0; + } + onMutation(true); + } + + public void copyFrom(Id other) { + System.arraycopy(other.data, 0, data, 0, data.length); + onMutation(other.empty); + } + + private void onMutation() { + onMutation(isAllZeros(data)); + } + + private void onMutation(boolean empty) { + cachedStringRepresentation = null; + this.empty = empty; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Id that = (Id) o; + return Arrays.equals(data, that.data); + } + + @Override + public int hashCode() { + return Arrays.hashCode(data); + } + + @Override + public String toString() { + String s = cachedStringRepresentation; + if (s == null) { + s = cachedStringRepresentation = HexUtils.bytesToHex(data); + } + return s; + } + + public boolean isEmpty() { + return empty; + } + + private static boolean isAllZeros(byte[] bytes) { + for (byte b : bytes) { + if (b != 0) { + return false; + } + } + return true; + } + + public void writeAsHex(JsonWriter jw) { + HexUtils.writeBytesAsHex(data, jw); + } + + public void writeAsHex(StringBuilder sb) { + HexUtils.writeBytesAsHex(data, sb); + } + + /** + * Returns the last 8 bytes of this id as a {@code long}. + * + * The least significant bits (the right part) of an id is preferred to be used for making random sampling decisions. + *
+ *+ * "There are systems that make random sampling decisions based on the value of trace-id. + * So to increase interoperability it is recommended to keep the random part on the right side of trace-id value." + *
+ * @see W3C trace context spec + * @return the last 8 bytes of this id as a {@code long} + */ + public long getLeastSignificantBits() { + return readLong(data.length - 8); + } + + /** + * Converts the next 8 bytes, starting from the offset, to a {@code long} + */ + public long readLong(int offset) { + long lsb = 0; + for (int i = offset; i < offset + 8; i++) { + lsb = (lsb << 8) | (data[i] & 0xff); + } + return lsb; + } +} diff --git a/apm-agent-core/src/main/java/co/elastic/apm/impl/transaction/SpanId.java b/apm-agent-core/src/main/java/co/elastic/apm/impl/transaction/SpanId.java index 26e03d3534..94d67b3993 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/impl/transaction/SpanId.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/impl/transaction/SpanId.java @@ -30,6 +30,7 @@ /** * A 64 bit random id which is used as a unique id for {@link Span}s within a {@link Transaction} */ +@Deprecated public class SpanId implements Recyclable { private static final int LENGTH = 8; diff --git a/apm-agent-core/src/main/java/co/elastic/apm/impl/transaction/TraceContext.java b/apm-agent-core/src/main/java/co/elastic/apm/impl/transaction/TraceContext.java index 1f73f431dc..cc6e6d99b0 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/impl/transaction/TraceContext.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/impl/transaction/TraceContext.java @@ -51,12 +51,36 @@ public class TraceContext implements Recyclable { // ??????1? -> maybe recorded // ??????0? -> not recorded private static final byte FLAG_RECORDED = 0b0000_0010; - private final TraceId traceId = new TraceId(); - private final SpanId id = new SpanId(); - private final SpanId parentId = new SpanId(); + private final Id traceId = Id.new128BitId(); + private final Id id; + private final Id parentId = Id.new64BitId(); + private final Id transactionId = Id.new64BitId(); private final StringBuilder outgoingHeader = new StringBuilder(TRACE_PARENT_LENGTH); private byte flags; + /** + * Creates a new {@code traceparent}-compliant {@link TraceContext} with a 64 bit {@link #id}. + *+ * Note: the {@link #traceId} will still be 128 bit + *
+ */ + public static TraceContext with64BitId() { + return new TraceContext(Id.new64BitId()); + } + + /** + * Creates a new {@link TraceContext} with a 128 bit {@link #id}, + * suitable for errors, + * as those might not have a trace reference and therefore require a larger id in order to be globally unique. + */ + public static TraceContext with128BitId() { + return new TraceContext(Id.new128BitId()); + } + + private TraceContext(Id id) { + this.id = id; + } + public boolean asChildOf(String traceParentHeader) { try { if (traceParentHeader.length() != 55) { @@ -72,10 +96,11 @@ public boolean asChildOf(String traceParentHeader) { return false; } parseParentId(traceParentHeader); - if (parentId.asLong() == 0) { + if (parentId.isEmpty()) { return false; } id.setToRandomValue(); + transactionId.copyFrom(id); flags = getTraceOptions(traceParentHeader); // TODO don't blindly trust the flags from the caller // consider implement rate limiting and/or having a list of trusted sources @@ -93,24 +118,26 @@ public boolean asChildOf(String traceParentHeader) { public void asRootSpan(Sampler sampler) { traceId.setToRandomValue(); id.setToRandomValue(); + transactionId.copyFrom(id); if (sampler.isSampled(traceId)) { this.flags = FLAG_RECORDED | FLAG_REQUESTED; } } - public void asChildOf(TraceContext traceContext) { - traceId.copyFrom(traceContext.traceId); - parentId.copyFrom(traceContext.id); - flags = traceContext.flags; + public void asChildOf(TraceContext parent) { + traceId.copyFrom(parent.traceId); + parentId.copyFrom(parent.id); + transactionId.copyFrom(parent.transactionId); + flags = parent.flags; id.setToRandomValue(); } private void parseTraceId(String traceParent) { - HexUtils.nextBytes(traceParent, 3, traceId.getBytes()); + traceId.fromHexString(traceParent, 3); } private void parseParentId(String traceParent) { - HexUtils.nextBytes(traceParent, 36, parentId.getBytes()); + parentId.fromHexString(traceParent, 36); } private byte getTraceOptions(String traceParent) { @@ -131,11 +158,11 @@ public void resetState() { * * @return the trace id */ - public TraceId getTraceId() { + public Id getTraceId() { return traceId; } - public SpanId getId() { + public Id getId() { return id; } @@ -144,10 +171,14 @@ public SpanId getId() { * * @return the parent id */ - public SpanId getParentId() { + public Id getParentId() { return parentId; } + public Id getTransactionId() { + return transactionId; + } + public boolean isSampled() { return isRecorded(); } @@ -204,11 +235,11 @@ public StringBuilder getOutgoingTraceParentHeader() { return outgoingHeader; } - private void fillTraceParentHeader(StringBuilder sb, SpanId spanId) { + private void fillTraceParentHeader(StringBuilder sb, Id spanId) { sb.append("00-"); - HexUtils.writeBytesAsHex(traceId.getBytes(), sb); + traceId.writeAsHex(sb); sb.append('-'); - HexUtils.writeBytesAsHex(spanId.getBytes(), sb); + spanId.writeAsHex(sb); sb.append('-'); HexUtils.writeByteAsHex(flags, sb); } @@ -218,6 +249,6 @@ public boolean isChildOf(TraceContext parent) { } public boolean hasContent() { - return !traceId.isEmpty() && parentId.asLong() != 0 && id.asLong() != 0; + return !traceId.isEmpty() && !parentId.isEmpty() && !id.isEmpty(); } } diff --git a/apm-agent-core/src/main/java/co/elastic/apm/impl/transaction/TraceId.java b/apm-agent-core/src/main/java/co/elastic/apm/impl/transaction/TraceId.java deleted file mode 100644 index 9a8cb7a068..0000000000 --- a/apm-agent-core/src/main/java/co/elastic/apm/impl/transaction/TraceId.java +++ /dev/null @@ -1,124 +0,0 @@ -/*- - * #%L - * Elastic APM Java agent - * %% - * Copyright (C) 2018 Elastic and contributors - * %% - * 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. - * #L% - */ -package co.elastic.apm.impl.transaction; - -import co.elastic.apm.objectpool.Recyclable; -import co.elastic.apm.util.HexUtils; - -import javax.annotation.Nullable; -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.Random; -import java.util.concurrent.ThreadLocalRandom; - -/** - * A 128 bit globally unique ID of the whole trace forest - */ -public class TraceId implements Recyclable { - - private final static TraceId EMPTY = new TraceId(); - - private static final int SIZE = 16; - private final byte[] data = new byte[SIZE]; - @Nullable - private String cachedStringRepresentation; - - public void setToRandomValue() { - setToRandomValue(ThreadLocalRandom.current()); - } - - public void setToRandomValue(Random random) { - random.nextBytes(data); - cachedStringRepresentation = null; - } - - public void setValue(long mostSignificantBits, long leastSignificantBits) { - ByteBuffer.wrap(data).putLong(mostSignificantBits).putLong(leastSignificantBits); - cachedStringRepresentation = null; - } - - @Override - public void resetState() { - for (int i = 0; i < data.length; i++) { - data[i] = 0; - } - cachedStringRepresentation = null; - } - - public void copyFrom(TraceId other) { - System.arraycopy(other.data, 0, data, 0, SIZE); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - TraceId that = (TraceId) o; - return Arrays.equals(data, that.data); - } - - @Override - public int hashCode() { - return Arrays.hashCode(data); - } - - @Override - public String toString() { - String s = cachedStringRepresentation; - if (s == null) { - s = cachedStringRepresentation = HexUtils.bytesToHex(data); - } - return s; - } - - public boolean isEmpty() { - return EMPTY.equals(this); - } - - /** - * Returns the mutable underlying byte array - * - * @return the mutable underlying byte array - */ - public byte[] getBytes() { - return data; - } - - /** - * Returns the first 8 bytes of this transaction id as a {@code long} - * - * @return the first 8 bytes of this transaction id as a {@code long} - */ - public long getMostSignificantBits() { - long msb = 0; - for (int i = 0; i < 8; i++) { - msb = (msb << 8) | (data[i] & 0xff); - } - return msb; - } - - public long getLeastSignificantBits() { - long lsb = 0; - for (int i = 8; i < 16; i++) { - lsb = (lsb << 8) | (data[i] & 0xff); - } - return lsb; - } -} diff --git a/apm-agent-core/src/main/java/co/elastic/apm/report/serialize/DslJsonSerializer.java b/apm-agent-core/src/main/java/co/elastic/apm/report/serialize/DslJsonSerializer.java index ebb618479c..732c6253a0 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/report/serialize/DslJsonSerializer.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/report/serialize/DslJsonSerializer.java @@ -40,12 +40,12 @@ import co.elastic.apm.impl.payload.TransactionPayload; import co.elastic.apm.impl.stacktrace.StacktraceConfiguration; import co.elastic.apm.impl.transaction.Db; +import co.elastic.apm.impl.transaction.Id; import co.elastic.apm.impl.transaction.Span; import co.elastic.apm.impl.transaction.SpanCount; import co.elastic.apm.impl.transaction.TraceContext; import co.elastic.apm.impl.transaction.Transaction; import co.elastic.apm.impl.transaction.TransactionId; -import co.elastic.apm.util.HexUtils; import co.elastic.apm.util.PotentiallyMultiValuedMap; import com.dslplatform.json.BoolConverter; import com.dslplatform.json.DslJson; @@ -82,20 +82,20 @@ public class DslJsonSerializer implements PayloadSerializer { * so that {@link #getBufferSize()} is the total amount of buffered bytes. */ public static final int BUFFER_SIZE = 16384; - private static final byte NEW_LINE = (byte) '\n'; - private final CollectionexcludedStackFrames = Arrays.asList("java.lang.reflect", "com.sun", "sun.", "jdk.internal."); static final int MAX_VALUE_LENGTH = 1024; static final int MAX_LONG_STRING_VALUE_LENGTH = 10000; + private static final byte NEW_LINE = (byte) '\n'; private static final Logger logger = LoggerFactory.getLogger(DslJsonSerializer.class); private static final String[] DISALLOWED_IN_TAG_KEY = new String[]{".", "*", "\""}; // visible for testing final JsonWriter jw; + private final Collection excludedStackFrames = Arrays.asList("java.lang.reflect", "com.sun", "sun.", "jdk.internal."); private final StringBuilder replaceBuilder = new StringBuilder(MAX_LONG_STRING_VALUE_LENGTH + 1); private final DateSerializer dateSerializer; private final boolean distributedTracing; + private final StacktraceConfiguration stacktraceConfiguration; @Nullable private OutputStream os; - private final StacktraceConfiguration stacktraceConfiguration; public DslJsonSerializer(boolean distributedTracingEnabled, StacktraceConfiguration stacktraceConfiguration) { this.stacktraceConfiguration = stacktraceConfiguration; @@ -228,7 +228,7 @@ private void serializeError(ErrorCapture errorCapture) { if (distributedTracing) { if (errorCapture.getTraceContext().hasContent()) { - serializeTraceContext(errorCapture.getTraceContext()); + serializeTraceContext(errorCapture.getTraceContext(), true); } } else { serializeTransactionReference(errorCapture); @@ -424,7 +424,7 @@ private void serializeTransaction(final Transaction transaction) { writeDateField("timestamp", transaction.getTimestamp()); writeField("name", transaction.getName()); if (distributedTracing) { - serializeTraceContext(transaction.getTraceContext()); + serializeTraceContext(transaction.getTraceContext(), false); } else { writeField("id", transaction.getId()); } @@ -443,11 +443,17 @@ private void serializeTransaction(final Transaction transaction) { jw.writeByte(OBJECT_END); } - private void serializeTraceContext(TraceContext traceContext) { - writeHexField("trace_id", traceContext.getTraceId().getBytes()); - writeHexField("id", traceContext.getId().getBytes()); - if (traceContext.getParentId().asLong() != 0) { - writeHexField("parent_id", traceContext.getParentId().getBytes()); + private void serializeTraceContext(TraceContext traceContext, boolean serializeTransactionId) { + // errors might only have an id + writeHexField("id", traceContext.getId()); + if (!traceContext.getTraceId().isEmpty()) { + writeHexField("trace_id", traceContext.getTraceId()); + } + if (serializeTransactionId && !traceContext.getTransactionId().isEmpty()) { + writeHexField("transaction_id", traceContext.getTransactionId()); + } + if (!traceContext.getParentId().isEmpty()) { + writeHexField("parent_id", traceContext.getParentId()); } } @@ -470,11 +476,7 @@ private void serializeSpan(final Span span) { writeField("name", span.getName()); writeDateField("timestamp", span.getTimestamp()); if (distributedTracing) { - serializeTraceContext(span.getTraceContext()); - final Transaction transaction = span.getTransaction(); - if (transaction != null) { - writeHexField("transaction_id", transaction.getTraceContext().getId().getBytes()); - } + serializeTraceContext(span.getTraceContext(), true); } else { writeField("id", span.getId().asLong()); final long parent = span.getParent().asLong(); @@ -570,8 +572,7 @@ private void serializeSpanContext(SpanContext context) { Map tags = context.getTags(); if (!tags.isEmpty()) { - if(dbContextWritten) - { + if (dbContextWritten) { jw.writeByte(COMMA); } writeFieldName("tags"); @@ -915,10 +916,10 @@ private void writeField(String fieldName, TransactionId id) { jw.writeByte(COMMA); } - private void writeHexField(String fieldName, byte[] value) { + private void writeHexField(String fieldName, Id traceId) { writeFieldName(fieldName); jw.writeByte(JsonWriter.QUOTE); - HexUtils.writeBytesAsHex(value, jw); + traceId.writeAsHex(jw); jw.writeByte(JsonWriter.QUOTE); jw.writeByte(COMMA); } diff --git a/apm-agent-core/src/test/java/co/elastic/apm/impl/sampling/ProbabilitySamplerTest.java b/apm-agent-core/src/test/java/co/elastic/apm/impl/sampling/ProbabilitySamplerTest.java index caed8024a7..5c66b24275 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/impl/sampling/ProbabilitySamplerTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/impl/sampling/ProbabilitySamplerTest.java @@ -19,7 +19,7 @@ */ package co.elastic.apm.impl.sampling; -import co.elastic.apm.impl.transaction.TraceId; +import co.elastic.apm.impl.transaction.Id; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -40,7 +40,7 @@ void setUp() { @Test void isSampledEmpiricalTest() { int sampledTransactions = 0; - TraceId id = new TraceId(); + Id id = Id.new128BitId(); for (int i = 0; i < ITERATIONS; i++) { id.setToRandomValue(); if (sampler.isSampled(id)) { @@ -53,30 +53,30 @@ void isSampledEmpiricalTest() { @Test void testSamplingUpperBoundary() { long upperBound = Long.MAX_VALUE / 2; - final TraceId transactionId = new TraceId(); + final Id transactionId = Id.new128BitId(); - transactionId.setValue(upperBound - 1, 0); + transactionId.fromLongs((long) 0, upperBound - 1); assertThat(ProbabilitySampler.of(0.5).isSampled(transactionId)).isTrue(); - transactionId.setValue(upperBound, 0); + transactionId.fromLongs((long) 0, upperBound); assertThat(ProbabilitySampler.of(0.5).isSampled(transactionId)).isTrue(); - transactionId.setValue(upperBound + 1, 0); + transactionId.fromLongs((long) 0, upperBound + 1); assertThat(ProbabilitySampler.of(0.5).isSampled(transactionId)).isFalse(); } @Test void testSamplingLowerBoundary() { long lowerBound = -Long.MAX_VALUE / 2; - final TraceId transactionId = new TraceId(); + final Id transactionId = Id.new128BitId(); - transactionId.setValue(lowerBound + 1, 0); + transactionId.fromLongs((long) 0, lowerBound + 1); assertThat(ProbabilitySampler.of(0.5).isSampled(transactionId)).isTrue(); - transactionId.setValue(lowerBound, 0); + transactionId.fromLongs((long) 0, lowerBound); assertThat(ProbabilitySampler.of(0.5).isSampled(transactionId)).isTrue(); - transactionId.setValue(lowerBound - 1, 0); + transactionId.fromLongs((long) 0, lowerBound - 1); assertThat(ProbabilitySampler.of(0.5).isSampled(transactionId)).isFalse(); } diff --git a/apm-agent-core/src/test/java/co/elastic/apm/impl/transaction/IdTest.java b/apm-agent-core/src/test/java/co/elastic/apm/impl/transaction/IdTest.java new file mode 100644 index 0000000000..716aa93618 --- /dev/null +++ b/apm-agent-core/src/test/java/co/elastic/apm/impl/transaction/IdTest.java @@ -0,0 +1,78 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 Elastic and contributors + * %% + * 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. + * #L% + */ +package co.elastic.apm.impl.transaction; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class IdTest { + + @Test + void testReInit() { + final Id id = Id.new64BitId(); + + id.fromHexString("0000000000000001", 0); + assertThat(id.toString()).isEqualTo("0000000000000001"); + assertThat(id.isEmpty()).isFalse(); + + id.fromHexString("0000000000000002", 0); + assertThat(id.toString()).isEqualTo("0000000000000002"); + assertThat(id.isEmpty()).isFalse(); + } + + @Test + void testReset() { + final Id id = Id.new64BitId(); + + id.fromHexString("0000000000000001", 0); + assertThat(id.toString()).isEqualTo("0000000000000001"); + assertThat(id.isEmpty()).isFalse(); + + id.resetState(); + assertThat(id.toString()).isEqualTo("0000000000000000"); + assertThat(id.isEmpty()).isTrue(); + } + + @Test + void testInitEmpty() { + final Id id = Id.new64BitId(); + assertThat(id.toString()).isEqualTo("0000000000000000"); + assertThat(id.isEmpty()).isTrue(); + + id.fromHexString("0000000000000000", 0); + assertThat(id.toString()).isEqualTo("0000000000000000"); + assertThat(id.isEmpty()).isTrue(); + + id.fromLongs(0); + assertThat(id.toString()).isEqualTo("0000000000000000"); + assertThat(id.isEmpty()).isTrue(); + } + + @Test + void testFromAndToLong() { + final Id id = Id.new128BitId(); + + id.fromLongs(21, 42); + assertThat(id.isEmpty()).isFalse(); + assertThat(id.readLong(0)).isEqualTo(21); + assertThat(id.readLong(8)).isEqualTo(42); + } +} diff --git a/apm-agent-core/src/test/java/co/elastic/apm/impl/transaction/TraceContextTest.java b/apm-agent-core/src/test/java/co/elastic/apm/impl/transaction/TraceContextTest.java index 1144b9689d..9e836422ca 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/impl/transaction/TraceContextTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/impl/transaction/TraceContextTest.java @@ -38,7 +38,7 @@ void setUp() { @Test void parseFromTraceParentHeader_notRecorded_notRequested() { - final TraceContext traceContext = new TraceContext(); + final TraceContext traceContext = TraceContext.with64BitId(); final String header = "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-00"; assertThat(traceContext.asChildOf(header)).isTrue(); assertThat(traceContext.isSampled()).isFalse(); @@ -51,7 +51,7 @@ void parseFromTraceParentHeader_notRecorded_notRequested() { @Test void parseFromTraceParentHeader_recorded_notRequested() { - final TraceContext traceContext = new TraceContext(); + final TraceContext traceContext = TraceContext.with64BitId(); final String header = "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-02"; assertThat(traceContext.asChildOf(header)).isTrue(); assertThat(traceContext.isSampled()).isTrue(); @@ -64,7 +64,7 @@ void parseFromTraceParentHeader_recorded_notRequested() { @Test void parseFromTraceParentHeader_notRecorded_requested() { - final TraceContext traceContext = new TraceContext(); + final TraceContext traceContext = TraceContext.with64BitId(); final String header = "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"; assertThat(traceContext.asChildOf(header)).isTrue(); assertThat(traceContext.isSampled()).isTrue(); @@ -78,7 +78,7 @@ void parseFromTraceParentHeader_notRecorded_requested() { @Test void parseFromTraceParentHeader_recorded_requested() { - final TraceContext traceContext = new TraceContext(); + final TraceContext traceContext = TraceContext.with64BitId(); final String header = "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-03"; assertThat(traceContext.asChildOf(header)).isTrue(); assertThat(traceContext.isSampled()).isTrue(); @@ -91,7 +91,7 @@ void parseFromTraceParentHeader_recorded_requested() { @Test void outgoingHeader() { - final TraceContext traceContext = new TraceContext(); + final TraceContext traceContext = TraceContext.with64BitId(); final String header = "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-03"; assertThat(traceContext.asChildOf(header)).isTrue(); assertThat(traceContext.getOutgoingTraceParentHeader().toString()) @@ -100,7 +100,7 @@ void outgoingHeader() { @Test void outgoingHeaderRootSpan() { - final TraceContext traceContext = new TraceContext(); + final TraceContext traceContext = TraceContext.with64BitId(); traceContext.asRootSpan(ConstantSampler.of(true)); assertThat(traceContext.getOutgoingTraceParentHeader().toString()).hasSize(55); assertThat(traceContext.getOutgoingTraceParentHeader().toString()).startsWith("00-"); @@ -109,7 +109,7 @@ void outgoingHeaderRootSpan() { @Test void parseFromTraceParentHeader_notSampled() { - final TraceContext traceContext = new TraceContext(); + final TraceContext traceContext = TraceContext.with64BitId(); final String header = "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-00"; assertThat(traceContext.asChildOf(header)).isTrue(); assertThat(traceContext.isSampled()).isFalse(); @@ -118,7 +118,7 @@ void parseFromTraceParentHeader_notSampled() { @Test void testResetState() { - final TraceContext traceContext = new TraceContext(); + final TraceContext traceContext = TraceContext.with64BitId(); traceContext.asChildOf("00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-00"); traceContext.resetState(); assertThat(traceContext.getIncomingTraceParentHeader()).isEqualTo("00-00000000000000000000000000000000-0000000000000000-00"); @@ -126,14 +126,16 @@ void testResetState() { @Test void testRandomValue() { - final TraceContext traceContext = new TraceContext(); + final TraceContext traceContext = TraceContext.with64BitId(); traceContext.asRootSpan(ConstantSampler.of(true)); - assertIsRoot(traceContext); + assertThat(traceContext.getTraceId().isEmpty()).isFalse(); + assertThat(traceContext.getParentId().isEmpty()).isTrue(); + assertThat(traceContext.getId().isEmpty()).isFalse(); } @Test void testSetSampled() { - final TraceContext traceContext = new TraceContext(); + final TraceContext traceContext = TraceContext.with64BitId(); traceContext.asRootSpan(ConstantSampler.of(false)); assertThat(traceContext.isSampled()).isFalse(); traceContext.setRecorded(true); @@ -180,13 +182,7 @@ void testInvalidHeader_invalidTotalLength() { } private void assertInvalid(String s) { - final TraceContext traceContext = new TraceContext(); + final TraceContext traceContext = TraceContext.with64BitId(); assertThat(traceContext.asChildOf(s)).isFalse(); } - - private void assertIsRoot(TraceContext traceContext) { - assertThat(traceContext.getTraceId().isEmpty()).isFalse(); - assertThat(traceContext.getParentId().asLong()).isZero(); - assertThat(traceContext.getId().asLong()).isNotZero(); - } } diff --git a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/plugin/api/SpanInstrumentation.java b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/plugin/api/SpanInstrumentation.java index e0114519f5..dfb3cdd737 100644 --- a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/plugin/api/SpanInstrumentation.java +++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/plugin/api/SpanInstrumentation.java @@ -25,7 +25,6 @@ import co.elastic.apm.impl.transaction.AbstractSpan; import co.elastic.apm.impl.transaction.Span; import co.elastic.apm.impl.transaction.Transaction; -import co.elastic.apm.impl.transaction.TransactionId; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; @@ -34,7 +33,6 @@ import java.util.Collection; import java.util.Collections; -import java.util.UUID; import static co.elastic.apm.plugin.api.ElasticApmApiInstrumentation.PUBLIC_API_INSTRUMENTATION_GROUP; import static net.bytebuddy.matcher.ElementMatchers.named; diff --git a/apm-opentracing/src/test/java/co/elastic/apm/opentracing/OpenTracingBridgeTest.java b/apm-opentracing/src/test/java/co/elastic/apm/opentracing/OpenTracingBridgeTest.java index 59cb4c708c..e0f21fb99e 100644 --- a/apm-opentracing/src/test/java/co/elastic/apm/opentracing/OpenTracingBridgeTest.java +++ b/apm-opentracing/src/test/java/co/elastic/apm/opentracing/OpenTracingBridgeTest.java @@ -246,7 +246,7 @@ void testInjectExtract() { final HashMap map = new HashMap<>(); apmTracer.inject(scope.span().context(), Format.Builtin.TEXT_MAP, new TextMapInjectAdapter(map)); - final TraceContext injectedContext = new TraceContext(); + final TraceContext injectedContext = TraceContext.with64BitId(); assertThat(injectedContext.asChildOf(map.get(TraceContext.TRACE_PARENT_HEADER))).isTrue(); assertThat(injectedContext.getTraceId().toString()).isEqualTo(traceId); assertThat(injectedContext.getParentId()).isEqualTo(tracer.currentTransaction().getTraceContext().getId());