diff --git a/apm-network/src/main/proto/TraceSegmentService.proto b/apm-network/src/main/proto/TraceSegmentService.proto index 62a1b16cb..a1e0aa134 100644 --- a/apm-network/src/main/proto/TraceSegmentService.proto +++ b/apm-network/src/main/proto/TraceSegmentService.proto @@ -12,12 +12,16 @@ service TraceSegmentService { } message UpstreamSegment { - repeated string globalTraceIds = 1; + repeated UniqueId globalTraceIds = 1; bytes segment = 2; // the byte array of TraceSegmentObject } +message UniqueId { + repeated int64 idParts = 1; +} + message TraceSegmentObject { - string traceSegmentId = 1; + UniqueId traceSegmentId = 1; repeated TraceSegmentReference refs = 2; repeated SpanObject spans = 3; int32 applicationId = 4; @@ -26,7 +30,7 @@ message TraceSegmentObject { message TraceSegmentReference { RefType refType = 1; - string parentTraceSegmentId = 2; + UniqueId parentTraceSegmentId = 2; int32 parentSpanId = 3; int32 parentApplicationInstanceId = 4; string networkAddress = 5; diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/AbstractTracerContext.java b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/AbstractTracerContext.java index 3b9d569d1..35099a516 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/AbstractTracerContext.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/AbstractTracerContext.java @@ -55,7 +55,7 @@ public interface AbstractTracerContext { * @return the string represents the id. * @see {@link TracingContext} and {@link IgnoredTracerContext} */ - String getGlobalTraceId(); + String getReadableGlobalTraceId(); /** * Create an entry span diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ContextCarrier.java b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ContextCarrier.java index 9ea1cc761..b52f11d0f 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ContextCarrier.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ContextCarrier.java @@ -3,6 +3,7 @@ package org.skywalking.apm.agent.core.context; import java.io.Serializable; import java.util.List; import org.skywalking.apm.agent.core.context.ids.DistributedTraceId; +import org.skywalking.apm.agent.core.context.ids.ID; import org.skywalking.apm.agent.core.context.ids.PropagatedTraceId; import org.skywalking.apm.agent.core.context.trace.TraceSegment; import org.skywalking.apm.agent.core.dictionary.DictionaryUtil; @@ -18,7 +19,7 @@ public class ContextCarrier implements Serializable { /** * {@link TraceSegment#traceSegmentId} */ - private String traceSegmentId; + private ID traceSegmentId; private int spanId = -1; @@ -44,7 +45,7 @@ public class ContextCarrier implements Serializable { public String serialize() { if (this.isValid()) { return StringUtil.join('|', - this.getTraceSegmentId(), + this.getTraceSegmentId().toBase64(), this.getSpanId() + "", this.getApplicationInstanceId() + "", this.getPeerHost(), @@ -66,7 +67,7 @@ public class ContextCarrier implements Serializable { String[] parts = text.split("\\|", 7); if (parts.length == 7) { try { - this.traceSegmentId = parts[0]; + this.traceSegmentId = new ID(parts[0]); this.spanId = Integer.parseInt(parts[1]); this.applicationInstanceId = Integer.parseInt(parts[2]); this.peerHost = parts[3]; @@ -87,7 +88,7 @@ public class ContextCarrier implements Serializable { * @return true for unbroken {@link ContextCarrier} or no-initialized. Otherwise, false; */ public boolean isValid() { - return !StringUtil.isEmpty(traceSegmentId) + return traceSegmentId != null && getSpanId() > -1 && applicationInstanceId != DictionaryUtil.nullValue() && !StringUtil.isEmpty(peerHost) @@ -116,7 +117,7 @@ public class ContextCarrier implements Serializable { this.parentOperationName = parentOperationId + ""; } - public String getTraceSegmentId() { + public ID getTraceSegmentId() { return traceSegmentId; } @@ -124,7 +125,7 @@ public class ContextCarrier implements Serializable { return spanId; } - void setTraceSegmentId(String traceSegmentId) { + void setTraceSegmentId(ID traceSegmentId) { this.traceSegmentId = traceSegmentId; } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ContextManager.java b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ContextManager.java index f6347dca0..01765f638 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ContextManager.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ContextManager.java @@ -76,7 +76,7 @@ public class ContextManager implements TracingContextListener, BootService, Igno if (segment == null) { return "N/A"; } else { - return segment.getGlobalTraceId(); + return segment.getReadableGlobalTraceId(); } } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ContextSnapshot.java b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ContextSnapshot.java index 7542b5320..7543fab3a 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ContextSnapshot.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ContextSnapshot.java @@ -2,6 +2,7 @@ package org.skywalking.apm.agent.core.context; import java.util.List; import org.skywalking.apm.agent.core.context.ids.DistributedTraceId; +import org.skywalking.apm.agent.core.context.ids.ID; import org.skywalking.apm.util.StringUtil; /** @@ -14,7 +15,7 @@ public class ContextSnapshot { /** * trace segment id of the parent trace segment. */ - private String traceSegmentId; + private ID traceSegmentId; /** * span id of the parent span, in parent trace segment. @@ -30,7 +31,7 @@ public class ContextSnapshot { */ private DistributedTraceId primaryDistributedTraceId; - ContextSnapshot(String traceSegmentId, int spanId, + ContextSnapshot(ID traceSegmentId, int spanId, List distributedTraceIds) { this.traceSegmentId = traceSegmentId; this.spanId = spanId; @@ -59,7 +60,7 @@ public class ContextSnapshot { return primaryDistributedTraceId; } - public String getTraceSegmentId() { + public ID getTraceSegmentId() { return traceSegmentId; } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/IgnoredTracerContext.java b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/IgnoredTracerContext.java index 4ad37e361..891cdf6f4 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/IgnoredTracerContext.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/IgnoredTracerContext.java @@ -41,7 +41,7 @@ public class IgnoredTracerContext implements AbstractTracerContext { } @Override - public String getGlobalTraceId() { + public String getReadableGlobalTraceId() { return "[Ignored Trace]"; } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/TracingContext.java b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/TracingContext.java index b054025f8..8f4c26688 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/TracingContext.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/TracingContext.java @@ -184,8 +184,8 @@ public class TracingContext implements AbstractTracerContext { * @return the first global trace id. */ @Override - public String getGlobalTraceId() { - return segment.getRelatedGlobalTraces().get(0).get(); + public String getReadableGlobalTraceId() { + return segment.getRelatedGlobalTraces().get(0).toString(); } /** diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ids/DistributedTraceId.java b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ids/DistributedTraceId.java index 58b1f9d17..f6e5623ef 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ids/DistributedTraceId.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ids/DistributedTraceId.java @@ -1,5 +1,7 @@ package org.skywalking.apm.agent.core.context.ids; +import org.skywalking.apm.network.proto.UniqueId; + /** * The DistributedTraceId presents a distributed call chain. *

@@ -14,14 +16,27 @@ package org.skywalking.apm.agent.core.context.ids; * @author wusheng */ public abstract class DistributedTraceId { - private String id; + private ID id; - public DistributedTraceId(String id) { + public DistributedTraceId(ID id) { this.id = id; } - public String get() { - return id; + public DistributedTraceId(String id) { + this.id = new ID(id); + } + + public String toBase64() { + return id.toBase64(); + } + + @Override + public String toString() { + return id.toString(); + } + + public UniqueId toUniqueId() { + return id.transform(); } /** @@ -38,7 +53,7 @@ public abstract class DistributedTraceId { if (o == null || getClass() != o.getClass()) return false; - DistributedTraceId id1 = (DistributedTraceId) o; + DistributedTraceId id1 = (DistributedTraceId)o; return id != null ? id.equals(id1.id) : id1.id == null; } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ids/DistributedTraceIds.java b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ids/DistributedTraceIds.java index fab3ad63d..94ab46fa0 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ids/DistributedTraceIds.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ids/DistributedTraceIds.java @@ -1,10 +1,5 @@ package org.skywalking.apm.agent.core.context.ids; -import com.google.gson.TypeAdapter; -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonWriter; -import java.io.IOException; import java.util.Collections; import java.util.LinkedList; import java.util.List; @@ -12,7 +7,6 @@ import java.util.List; /** * @author wusheng */ -@JsonAdapter(DistributedTraceIds.Serializer.class) public class DistributedTraceIds { private LinkedList relatedGlobalTraces; @@ -32,35 +26,4 @@ public class DistributedTraceIds { relatedGlobalTraces.add(distributedTraceId); } } - - public static class Serializer extends TypeAdapter { - - @Override - public void write(JsonWriter out, DistributedTraceIds value) throws IOException { - List globalTraces = value.getRelatedGlobalTraces(); - - if (globalTraces.size() > 0) { - out.beginArray(); - for (DistributedTraceId trace : globalTraces) { - out.value(trace.get()); - } - out.endArray(); - } - } - - @Override - public DistributedTraceIds read(JsonReader in) throws IOException { - DistributedTraceIds distributedTraceIds = new DistributedTraceIds(); - in.beginArray(); - try { - while (in.hasNext()) { - PropagatedTraceId traceId = new PropagatedTraceId(in.nextString()); - distributedTraceIds.append(traceId); - } - } finally { - in.endArray(); - } - return distributedTraceIds; - } - } } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ids/GlobalIdGenerator.java b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ids/GlobalIdGenerator.java index d9720fbcf..17572a2d6 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ids/GlobalIdGenerator.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ids/GlobalIdGenerator.java @@ -1,8 +1,7 @@ package org.skywalking.apm.agent.core.context.ids; -import java.util.UUID; -import org.skywalking.apm.util.MachineInfo; -import org.skywalking.apm.util.StringUtil; +import org.skywalking.apm.agent.core.conf.RemoteDownstreamConfig; +import org.skywalking.apm.agent.core.dictionary.DictionaryUtil; public final class GlobalIdGenerator { private static final ThreadLocal THREAD_ID_SEQUENCE = new ThreadLocal() { @@ -12,23 +11,42 @@ public final class GlobalIdGenerator { } }; - private static final int PROCESS_UUID; - - static { - String uuid = UUID.randomUUID().toString().replaceAll("-", ""); - PROCESS_UUID = uuid.substring(uuid.length() - 7).hashCode(); - } - private GlobalIdGenerator() { } - public static String generate(String type) { + /** + * Generate a new id, combined by three long numbers. + * + * The first one represents application instance id. (most likely just an integer value, would be helpful in + * protobuf) + * + * The second one represents thread id. (most likely just an integer value, would be helpful in protobuf) + * + * The third one also has two parts,
+ * 1) a timestamp, measured in milliseconds
+ * 2) a seq, in current thread, between 0(included) and 9999(included) + * + * Notice, a long costs 8 bytes, three longs cost 24 bytes. And at the same time, a char costs 2 bytes. So + * sky-walking's old global and segment id like this: "S.1490097253214.-866187727.57515.1.1" which costs at least 72 + * bytes. + * + * @return an array contains three long numbers, which represents a unique id. + */ + public static ID generate() { + if (RemoteDownstreamConfig.Agent.APPLICATION_INSTANCE_ID == DictionaryUtil.nullValue()) { + throw new IllegalStateException(); + } Integer seq = THREAD_ID_SEQUENCE.get(); seq++; + if (seq > 9999) { + seq = 0; + } THREAD_ID_SEQUENCE.set(seq); - return StringUtil.join('.', - type + "", System.currentTimeMillis() + "", PROCESS_UUID + "", - MachineInfo.getProcessNo() + "", Thread.currentThread().getId() + "", seq + ""); + return new ID( + RemoteDownstreamConfig.Agent.APPLICATION_INSTANCE_ID, + Thread.currentThread().getId(), + System.currentTimeMillis() * 10000 + seq + ); } } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ids/ID.java b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ids/ID.java new file mode 100644 index 000000000..a127eb245 --- /dev/null +++ b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ids/ID.java @@ -0,0 +1,114 @@ +package org.skywalking.apm.agent.core.context.ids; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import org.skywalking.apm.agent.core.context.ids.base64.Base64; +import org.skywalking.apm.network.proto.UniqueId; + +/** + * @author wusheng + */ +public class ID { + private static final Base64.Encoder ENCODER = Base64.getEncoder(); + private static final Base64.Decoder DECODER = Base64.getDecoder(); + + private long part1; + private long part2; + private long part3; + + public ID(long part1, long part2, long part3) { + this.part1 = part1; + this.part2 = part2; + this.part3 = part3; + } + + public ID(String base64String) { + int index = 0; + for (int part = 0; part < 3; part++) { + String encodedString; + char potentialTypeChar = base64String.charAt(index); + long value; + if (potentialTypeChar == '#') { + encodedString = base64String.substring(index + 1, index + 5); + index += 5; + value = ByteBuffer.wrap(DECODER.decode(encodedString)).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(0); + } else if (potentialTypeChar == '$') { + encodedString = base64String.substring(index + 1, index + 9); + index += 8; + value = ByteBuffer.wrap(DECODER.decode(encodedString)).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get(0); + } else { + encodedString = base64String.substring(index + 1, index + 13); + index += 12; + value = ByteBuffer.wrap(DECODER.decode(encodedString)).order(ByteOrder.LITTLE_ENDIAN).asLongBuffer().get(0); + } + + if (part == 0) { + part1 = part; + } else if (part == 1) { + part2 = part; + } else { + part3 = part; + } + + } + } + + public String toBase64() { + return long2Base64(part1) + long2Base64(part2) + long2Base64(part3); + } + + private String long2Base64(long partN) { + if (partN < 0) { + throw new IllegalArgumentException("negative value."); + } + if (partN < 32768) { + // 0 - 32767 + // "#" as a prefix of a short value with base64 encoding. + byte[] data = new byte[2]; + ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().put((short)partN); + return '#' + ENCODER.encodeToString(data); + } else if (partN <= 2147483647) { + // 32768 - 2147483647 + // "$" as a prefix of an integer value (greater than a short) with base64 encoding. + byte[] data = new byte[4]; + ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().put((int)partN); + return '$' + ENCODER.encodeToString(data); + } else { + // > 2147483647 + // a long value (greater than an integer) + byte[] data = new byte[8]; + ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN).asLongBuffer().put(partN); + return ENCODER.encodeToString(data); + } + } + + @Override public String toString() { + return part1 + "." + part2 + '.' + part3; + } + + @Override public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + ID id = (ID)o; + + if (part1 != id.part1) + return false; + if (part2 != id.part2) + return false; + return part3 == id.part3; + } + + @Override public int hashCode() { + int result = (int)(part1 ^ (part1 >>> 32)); + result = 31 * result + (int)(part2 ^ (part2 >>> 32)); + result = 31 * result + (int)(part3 ^ (part3 >>> 32)); + return result; + } + + public UniqueId transform() { + return UniqueId.newBuilder().addIdParts(part1).addIdParts(part2).addIdParts(part3).build(); + } +} diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ids/NewDistributedTraceId.java b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ids/NewDistributedTraceId.java index fc058e218..53c4fc726 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ids/NewDistributedTraceId.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ids/NewDistributedTraceId.java @@ -6,9 +6,7 @@ package org.skywalking.apm.agent.core.context.ids; * @author wusheng */ public class NewDistributedTraceId extends DistributedTraceId { - private static final String ID_TYPE = "T"; - public NewDistributedTraceId() { - super(GlobalIdGenerator.generate(ID_TYPE)); + super(GlobalIdGenerator.generate()); } } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ids/base64/Base64.java b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ids/base64/Base64.java new file mode 100644 index 000000000..a36a165e0 --- /dev/null +++ b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ids/base64/Base64.java @@ -0,0 +1,973 @@ +package org.skywalking.apm.agent.core.context.ids.base64; + +import java.io.FilterOutputStream; +import java.io.InputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.Arrays; + +/** + * This class consists exclusively of static methods for obtaining + * encoders and decoders for the Base64 encoding scheme. The + * implementation of this class supports the following types of Base64 + * as specified in + * RFC 4648 and + * RFC 2045. + * + *

+ * + *

Unless otherwise noted, passing a {@code null} argument to a + * method of this class will cause a {@link java.lang.NullPointerException + * NullPointerException} to be thrown. + * + * @author Xueming Shen + * @since 1.8 + */ + +public class Base64 { + + private Base64() {} + + /** + * Returns a {@link Encoder} that encodes using the + * Basic type base64 encoding scheme. + * + * @return A Base64 encoder. + */ + public static Encoder getEncoder() { + return Encoder.RFC4648; + } + + /** + * Returns a {@link Encoder} that encodes using the + * URL and Filename safe type base64 + * encoding scheme. + * + * @return A Base64 encoder. + */ + public static Encoder getUrlEncoder() { + return Encoder.RFC4648_URLSAFE; + } + + /** + * Returns a {@link Encoder} that encodes using the + * MIME type base64 encoding scheme. + * + * @return A Base64 encoder. + */ + public static Encoder getMimeEncoder() { + return Encoder.RFC2045; + } + + /** + * Returns a {@link Encoder} that encodes using the + * MIME type base64 encoding scheme + * with specified line length and line separators. + * + * @param lineLength + * the length of each output line (rounded down to nearest multiple + * of 4). If {@code lineLength <= 0} the output will not be separated + * in lines + * @param lineSeparator + * the line separator for each output line + * + * @return A Base64 encoder. + * + * @throws IllegalArgumentException if {@code lineSeparator} includes any + * character of "The Base64 Alphabet" as specified in Table 1 of + * RFC 2045. + */ + public static Encoder getMimeEncoder(int lineLength, byte[] lineSeparator) { + Objects.requireNonNull(lineSeparator); + int[] base64 = Decoder.fromBase64; + for (byte b : lineSeparator) { + if (base64[b & 0xff] != -1) + throw new IllegalArgumentException( + "Illegal base64 line separator character 0x" + Integer.toString(b, 16)); + } + if (lineLength <= 0) { + return Encoder.RFC4648; + } + return new Encoder(false, lineSeparator, lineLength >> 2 << 2, true); + } + + /** + * Returns a {@link Decoder} that decodes using the + * Basic type base64 encoding scheme. + * + * @return A Base64 decoder. + */ + public static Decoder getDecoder() { + return Decoder.RFC4648; + } + + /** + * Returns a {@link Decoder} that decodes using the + * URL and Filename safe type base64 + * encoding scheme. + * + * @return A Base64 decoder. + */ + public static Decoder getUrlDecoder() { + return Decoder.RFC4648_URLSAFE; + } + + /** + * Returns a {@link Decoder} that decodes using the + * MIME type base64 decoding scheme. + * + * @return A Base64 decoder. + */ + public static Decoder getMimeDecoder() { + return Decoder.RFC2045; + } + + /** + * This class implements an encoder for encoding byte data using + * the Base64 encoding scheme as specified in RFC 4648 and RFC 2045. + * + *

Instances of {@link Encoder} class are safe for use by + * multiple concurrent threads. + * + *

Unless otherwise noted, passing a {@code null} argument to + * a method of this class will cause a + * {@link java.lang.NullPointerException NullPointerException} to + * be thrown. + * + * @see Decoder + * @since 1.8 + */ + public static class Encoder { + + private final byte[] newline; + private final int linemax; + private final boolean isURL; + private final boolean doPadding; + + private Encoder(boolean isURL, byte[] newline, int linemax, boolean doPadding) { + this.isURL = isURL; + this.newline = newline; + this.linemax = linemax; + this.doPadding = doPadding; + } + + /** + * This array is a lookup table that translates 6-bit positive integer + * index values into their "Base64 Alphabet" equivalents as specified + * in "Table 1: The Base64 Alphabet" of RFC 2045 (and RFC 4648). + */ + private static final char[] toBase64 = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' + }; + + /** + * It's the lookup table for "URL and Filename safe Base64" as specified + * in Table 2 of the RFC 4648, with the '+' and '/' changed to '-' and + * '_'. This table is used when BASE64_URL is specified. + */ + private static final char[] toBase64URL = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' + }; + + private static final int MIMELINEMAX = 76; + private static final byte[] CRLF = new byte[] {'\r', '\n'}; + + static final Encoder RFC4648 = new Encoder(false, null, -1, true); + static final Encoder RFC4648_URLSAFE = new Encoder(true, null, -1, true); + static final Encoder RFC2045 = new Encoder(false, CRLF, MIMELINEMAX, true); + + private final int outLength(int srclen) { + int len = 0; + if (doPadding) { + len = 4 * ((srclen + 2) / 3); + } else { + int n = srclen % 3; + len = 4 * (srclen / 3) + (n == 0 ? 0 : n + 1); + } + if (linemax > 0) // line separators + len += (len - 1) / linemax * newline.length; + return len; + } + + /** + * Encodes all bytes from the specified byte array into a newly-allocated + * byte array using the {@link Base64} encoding scheme. The returned byte + * array is of the length of the resulting bytes. + * + * @param src + * the byte array to encode + * @return A newly-allocated byte array containing the resulting + * encoded bytes. + */ + public byte[] encode(byte[] src) { + int len = outLength(src.length); // dst array size + byte[] dst = new byte[len]; + int ret = encode0(src, 0, src.length, dst); + if (ret != dst.length) + return Arrays.copyOf(dst, ret); + return dst; + } + + /** + * Encodes all bytes from the specified byte array using the + * {@link Base64} encoding scheme, writing the resulting bytes to the + * given output byte array, starting at offset 0. + * + *

It is the responsibility of the invoker of this method to make + * sure the output byte array {@code dst} has enough space for encoding + * all bytes from the input byte array. No bytes will be written to the + * output byte array if the output byte array is not big enough. + * + * @param src + * the byte array to encode + * @param dst + * the output byte array + * @return The number of bytes written to the output byte array + * + * @throws IllegalArgumentException if {@code dst} does not have enough + * space for encoding all input bytes. + */ + public int encode(byte[] src, byte[] dst) { + int len = outLength(src.length); // dst array size + if (dst.length < len) + throw new IllegalArgumentException( + "Output byte array is too small for encoding all input bytes"); + return encode0(src, 0, src.length, dst); + } + + /** + * Encodes the specified byte array into a String using the {@link Base64} + * encoding scheme. + * + *

This method first encodes all input bytes into a base64 encoded + * byte array and then constructs a new String by using the encoded byte + * array and the {@link StandardCharsets#ISO_8859_1 + * ISO-8859-1} charset. + * + *

In other words, an invocation of this method has exactly the same + * effect as invoking + * {@code new String(encode(src), StandardCharsets.ISO_8859_1)}. + * + * @param src + * the byte array to encode + * @return A String containing the resulting Base64 encoded characters + */ + @SuppressWarnings("deprecation") + public String encodeToString(byte[] src) { + byte[] encoded = encode(src); + return new String(encoded, 0, 0, encoded.length); + } + + /** + * Encodes all remaining bytes from the specified byte buffer into + * a newly-allocated ByteBuffer using the {@link Base64} encoding + * scheme. + * + * Upon return, the source buffer's position will be updated to + * its limit; its limit will not have been changed. The returned + * output buffer's position will be zero and its limit will be the + * number of resulting encoded bytes. + * + * @param buffer + * the source ByteBuffer to encode + * @return A newly-allocated byte buffer containing the encoded bytes. + */ + public ByteBuffer encode(ByteBuffer buffer) { + int len = outLength(buffer.remaining()); + byte[] dst = new byte[len]; + int ret = 0; + if (buffer.hasArray()) { + ret = encode0(buffer.array(), + buffer.arrayOffset() + buffer.position(), + buffer.arrayOffset() + buffer.limit(), + dst); + buffer.position(buffer.limit()); + } else { + byte[] src = new byte[buffer.remaining()]; + buffer.get(src); + ret = encode0(src, 0, src.length, dst); + } + if (ret != dst.length) + dst = Arrays.copyOf(dst, ret); + return ByteBuffer.wrap(dst); + } + + /** + * Wraps an output stream for encoding byte data using the {@link Base64} + * encoding scheme. + * + *

It is recommended to promptly close the returned output stream after + * use, during which it will flush all possible leftover bytes to the underlying + * output stream. Closing the returned output stream will close the underlying + * output stream. + * + * @param os + * the output stream. + * @return the output stream for encoding the byte data into the + * specified Base64 encoded format + */ + public OutputStream wrap(OutputStream os) { + Objects.requireNonNull(os); + return new EncOutputStream(os, isURL ? toBase64URL : toBase64, + newline, linemax, doPadding); + } + + /** + * Returns an encoder instance that encodes equivalently to this one, + * but without adding any padding character at the end of the encoded + * byte data. + * + *

The encoding scheme of this encoder instance is unaffected by + * this invocation. The returned encoder instance should be used for + * non-padding encoding operation. + * + * @return an equivalent encoder that encodes without adding any + * padding character at the end + */ + public Encoder withoutPadding() { + if (!doPadding) + return this; + return new Encoder(isURL, newline, linemax, false); + } + + private int encode0(byte[] src, int off, int end, byte[] dst) { + char[] base64 = isURL ? toBase64URL : toBase64; + int sp = off; + int slen = (end - off) / 3 * 3; + int sl = off + slen; + if (linemax > 0 && slen > linemax / 4 * 3) + slen = linemax / 4 * 3; + int dp = 0; + while (sp < sl) { + int sl0 = Math.min(sp + slen, sl); + for (int sp0 = sp, dp0 = dp ; sp0 < sl0; ) { + int bits = (src[sp0++] & 0xff) << 16 | + (src[sp0++] & 0xff) << 8 | + (src[sp0++] & 0xff); + dst[dp0++] = (byte)base64[(bits >>> 18) & 0x3f]; + dst[dp0++] = (byte)base64[(bits >>> 12) & 0x3f]; + dst[dp0++] = (byte)base64[(bits >>> 6) & 0x3f]; + dst[dp0++] = (byte)base64[bits & 0x3f]; + } + int dlen = (sl0 - sp) / 3 * 4; + dp += dlen; + sp = sl0; + if (dlen == linemax && sp < end) { + for (byte b : newline){ + dst[dp++] = b; + } + } + } + if (sp < end) { // 1 or 2 leftover bytes + int b0 = src[sp++] & 0xff; + dst[dp++] = (byte)base64[b0 >> 2]; + if (sp == end) { + dst[dp++] = (byte)base64[(b0 << 4) & 0x3f]; + if (doPadding) { + dst[dp++] = '='; + dst[dp++] = '='; + } + } else { + int b1 = src[sp++] & 0xff; + dst[dp++] = (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)]; + dst[dp++] = (byte)base64[(b1 << 2) & 0x3f]; + if (doPadding) { + dst[dp++] = '='; + } + } + } + return dp; + } + } + + /** + * This class implements a decoder for decoding byte data using the + * Base64 encoding scheme as specified in RFC 4648 and RFC 2045. + * + *

The Base64 padding character {@code '='} is accepted and + * interpreted as the end of the encoded byte data, but is not + * required. So if the final unit of the encoded byte data only has + * two or three Base64 characters (without the corresponding padding + * character(s) padded), they are decoded as if followed by padding + * character(s). If there is a padding character present in the + * final unit, the correct number of padding character(s) must be + * present, otherwise {@code IllegalArgumentException} ( + * {@code IOException} when reading from a Base64 stream) is thrown + * during decoding. + * + *

Instances of {@link Decoder} class are safe for use by + * multiple concurrent threads. + * + *

Unless otherwise noted, passing a {@code null} argument to + * a method of this class will cause a + * {@link java.lang.NullPointerException NullPointerException} to + * be thrown. + * + * @see Encoder + * @since 1.8 + */ + public static class Decoder { + + private final boolean isURL; + private final boolean isMIME; + + private Decoder(boolean isURL, boolean isMIME) { + this.isURL = isURL; + this.isMIME = isMIME; + } + + /** + * Lookup table for decoding unicode characters drawn from the + * "Base64 Alphabet" (as specified in Table 1 of RFC 2045) into + * their 6-bit positive integer equivalents. Characters that + * are not in the Base64 alphabet but fall within the bounds of + * the array are encoded to -1. + * + */ + private static final int[] fromBase64 = new int[256]; + static { + Arrays.fill(fromBase64, -1); + for (int i = 0; i < Encoder.toBase64.length; i++) + fromBase64[Encoder.toBase64[i]] = i; + fromBase64['='] = -2; + } + + /** + * Lookup table for decoding "URL and Filename safe Base64 Alphabet" + * as specified in Table2 of the RFC 4648. + */ + private static final int[] fromBase64URL = new int[256]; + + static { + Arrays.fill(fromBase64URL, -1); + for (int i = 0; i < Encoder.toBase64URL.length; i++) + fromBase64URL[Encoder.toBase64URL[i]] = i; + fromBase64URL['='] = -2; + } + + static final Decoder RFC4648 = new Decoder(false, false); + static final Decoder RFC4648_URLSAFE = new Decoder(true, false); + static final Decoder RFC2045 = new Decoder(false, true); + + /** + * Decodes all bytes from the input byte array using the {@link Base64} + * encoding scheme, writing the results into a newly-allocated output + * byte array. The returned byte array is of the length of the resulting + * bytes. + * + * @param src + * the byte array to decode + * + * @return A newly-allocated byte array containing the decoded bytes. + * + * @throws IllegalArgumentException + * if {@code src} is not in valid Base64 scheme + */ + public byte[] decode(byte[] src) { + byte[] dst = new byte[outLength(src, 0, src.length)]; + int ret = decode0(src, 0, src.length, dst); + if (ret != dst.length) { + dst = Arrays.copyOf(dst, ret); + } + return dst; + } + + /** + * Decodes a Base64 encoded String into a newly-allocated byte array + * using the {@link Base64} encoding scheme. + * + *

An invocation of this method has exactly the same effect as invoking + * {@code decode(src.getBytes(StandardCharsets.ISO_8859_1))} + * + * @param src + * the string to decode + * + * @return A newly-allocated byte array containing the decoded bytes. + * + * @throws IllegalArgumentException + * if {@code src} is not in valid Base64 scheme + */ + public byte[] decode(String src) { + return decode(src.getBytes(StandardCharsets.ISO_8859_1)); + } + + /** + * Decodes all bytes from the input byte array using the {@link Base64} + * encoding scheme, writing the results into the given output byte array, + * starting at offset 0. + * + *

It is the responsibility of the invoker of this method to make + * sure the output byte array {@code dst} has enough space for decoding + * all bytes from the input byte array. No bytes will be be written to + * the output byte array if the output byte array is not big enough. + * + *

If the input byte array is not in valid Base64 encoding scheme + * then some bytes may have been written to the output byte array before + * IllegalargumentException is thrown. + * + * @param src + * the byte array to decode + * @param dst + * the output byte array + * + * @return The number of bytes written to the output byte array + * + * @throws IllegalArgumentException + * if {@code src} is not in valid Base64 scheme, or {@code dst} + * does not have enough space for decoding all input bytes. + */ + public int decode(byte[] src, byte[] dst) { + int len = outLength(src, 0, src.length); + if (dst.length < len) + throw new IllegalArgumentException( + "Output byte array is too small for decoding all input bytes"); + return decode0(src, 0, src.length, dst); + } + + /** + * Decodes all bytes from the input byte buffer using the {@link Base64} + * encoding scheme, writing the results into a newly-allocated ByteBuffer. + * + *

Upon return, the source buffer's position will be updated to + * its limit; its limit will not have been changed. The returned + * output buffer's position will be zero and its limit will be the + * number of resulting decoded bytes + * + *

{@code IllegalArgumentException} is thrown if the input buffer + * is not in valid Base64 encoding scheme. The position of the input + * buffer will not be advanced in this case. + * + * @param buffer + * the ByteBuffer to decode + * + * @return A newly-allocated byte buffer containing the decoded bytes + * + * @throws IllegalArgumentException + * if {@code src} is not in valid Base64 scheme. + */ + public ByteBuffer decode(ByteBuffer buffer) { + int pos0 = buffer.position(); + try { + byte[] src; + int sp, sl; + if (buffer.hasArray()) { + src = buffer.array(); + sp = buffer.arrayOffset() + buffer.position(); + sl = buffer.arrayOffset() + buffer.limit(); + buffer.position(buffer.limit()); + } else { + src = new byte[buffer.remaining()]; + buffer.get(src); + sp = 0; + sl = src.length; + } + byte[] dst = new byte[outLength(src, sp, sl)]; + return ByteBuffer.wrap(dst, 0, decode0(src, sp, sl, dst)); + } catch (IllegalArgumentException iae) { + buffer.position(pos0); + throw iae; + } + } + + /** + * Returns an input stream for decoding {@link Base64} encoded byte stream. + * + *

The {@code read} methods of the returned {@code InputStream} will + * throw {@code IOException} when reading bytes that cannot be decoded. + * + *

Closing the returned input stream will close the underlying + * input stream. + * + * @param is + * the input stream + * + * @return the input stream for decoding the specified Base64 encoded + * byte stream + */ + public InputStream wrap(InputStream is) { + Objects.requireNonNull(is); + return new DecInputStream(is, isURL ? fromBase64URL : fromBase64, isMIME); + } + + private int outLength(byte[] src, int sp, int sl) { + int[] base64 = isURL ? fromBase64URL : fromBase64; + int paddings = 0; + int len = sl - sp; + if (len == 0) + return 0; + if (len < 2) { + if (isMIME && base64[0] == -1) + return 0; + throw new IllegalArgumentException( + "Input byte[] should at least have 2 bytes for base64 bytes"); + } + if (isMIME) { + // scan all bytes to fill out all non-alphabet. a performance + // trade-off of pre-scan or Arrays.copyOf + int n = 0; + while (sp < sl) { + int b = src[sp++] & 0xff; + if (b == '=') { + len -= (sl - sp + 1); + break; + } + if ((b = base64[b]) == -1) + n++; + } + len -= n; + } else { + if (src[sl - 1] == '=') { + paddings++; + if (src[sl - 2] == '=') + paddings++; + } + } + if (paddings == 0 && (len & 0x3) != 0) + paddings = 4 - (len & 0x3); + return 3 * ((len + 3) / 4) - paddings; + } + + private int decode0(byte[] src, int sp, int sl, byte[] dst) { + int[] base64 = isURL ? fromBase64URL : fromBase64; + int dp = 0; + int bits = 0; + int shiftto = 18; // pos of first byte of 4-byte atom + while (sp < sl) { + int b = src[sp++] & 0xff; + if ((b = base64[b]) < 0) { + if (b == -2) { // padding byte '=' + // = shiftto==18 unnecessary padding + // x= shiftto==12 a dangling single x + // x to be handled together with non-padding case + // xx= shiftto==6&&sp==sl missing last = + // xx=y shiftto==6 last is not = + if (shiftto == 6 && (sp == sl || src[sp++] != '=') || + shiftto == 18) { + throw new IllegalArgumentException( + "Input byte array has wrong 4-byte ending unit"); + } + break; + } + if (isMIME) // skip if for rfc2045 + continue; + else + throw new IllegalArgumentException( + "Illegal base64 character " + + Integer.toString(src[sp - 1], 16)); + } + bits |= (b << shiftto); + shiftto -= 6; + if (shiftto < 0) { + dst[dp++] = (byte)(bits >> 16); + dst[dp++] = (byte)(bits >> 8); + dst[dp++] = (byte)(bits); + shiftto = 18; + bits = 0; + } + } + // reached end of byte array or hit padding '=' characters. + if (shiftto == 6) { + dst[dp++] = (byte)(bits >> 16); + } else if (shiftto == 0) { + dst[dp++] = (byte)(bits >> 16); + dst[dp++] = (byte)(bits >> 8); + } else if (shiftto == 12) { + // dangling single "x", incorrectly encoded. + throw new IllegalArgumentException( + "Last unit does not have enough valid bits"); + } + // anything left is invalid, if is not MIME. + // if MIME, ignore all non-base64 character + while (sp < sl) { + if (isMIME && base64[src[sp++]] < 0) + continue; + throw new IllegalArgumentException( + "Input byte array has incorrect ending byte at " + sp); + } + return dp; + } + } + + /* + * An output stream for encoding bytes into the Base64. + */ + private static class EncOutputStream extends FilterOutputStream { + + private int leftover = 0; + private int b0, b1, b2; + private boolean closed = false; + + private final char[] base64; // byte->base64 mapping + private final byte[] newline; // line separator, if needed + private final int linemax; + private final boolean doPadding;// whether or not to pad + private int linepos = 0; + + EncOutputStream(OutputStream os, char[] base64, + byte[] newline, int linemax, boolean doPadding) { + super(os); + this.base64 = base64; + this.newline = newline; + this.linemax = linemax; + this.doPadding = doPadding; + } + + @Override + public void write(int b) throws IOException { + byte[] buf = new byte[1]; + buf[0] = (byte)(b & 0xff); + write(buf, 0, 1); + } + + private void checkNewline() throws IOException { + if (linepos == linemax) { + out.write(newline); + linepos = 0; + } + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (closed) + throw new IOException("Stream is closed"); + if (off < 0 || len < 0 || len > b.length - off) + throw new ArrayIndexOutOfBoundsException(); + if (len == 0) + return; + if (leftover != 0) { + if (leftover == 1) { + b1 = b[off++] & 0xff; + len--; + if (len == 0) { + leftover++; + return; + } + } + b2 = b[off++] & 0xff; + len--; + checkNewline(); + out.write(base64[b0 >> 2]); + out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]); + out.write(base64[(b1 << 2) & 0x3f | (b2 >> 6)]); + out.write(base64[b2 & 0x3f]); + linepos += 4; + } + int nBits24 = len / 3; + leftover = len - (nBits24 * 3); + while (nBits24-- > 0) { + checkNewline(); + int bits = (b[off++] & 0xff) << 16 | + (b[off++] & 0xff) << 8 | + (b[off++] & 0xff); + out.write(base64[(bits >>> 18) & 0x3f]); + out.write(base64[(bits >>> 12) & 0x3f]); + out.write(base64[(bits >>> 6) & 0x3f]); + out.write(base64[bits & 0x3f]); + linepos += 4; + } + if (leftover == 1) { + b0 = b[off++] & 0xff; + } else if (leftover == 2) { + b0 = b[off++] & 0xff; + b1 = b[off++] & 0xff; + } + } + + @Override + public void close() throws IOException { + if (!closed) { + closed = true; + if (leftover == 1) { + checkNewline(); + out.write(base64[b0 >> 2]); + out.write(base64[(b0 << 4) & 0x3f]); + if (doPadding) { + out.write('='); + out.write('='); + } + } else if (leftover == 2) { + checkNewline(); + out.write(base64[b0 >> 2]); + out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]); + out.write(base64[(b1 << 2) & 0x3f]); + if (doPadding) { + out.write('='); + } + } + leftover = 0; + out.close(); + } + } + } + + /* + * An input stream for decoding Base64 bytes + */ + private static class DecInputStream extends InputStream { + + private final InputStream is; + private final boolean isMIME; + private final int[] base64; // base64 -> byte mapping + private int bits = 0; // 24-bit buffer for decoding + private int nextin = 18; // next available "off" in "bits" for input; + // -> 18, 12, 6, 0 + private int nextout = -8; // next available "off" in "bits" for output; + // -> 8, 0, -8 (no byte for output) + private boolean eof = false; + private boolean closed = false; + + DecInputStream(InputStream is, int[] base64, boolean isMIME) { + this.is = is; + this.base64 = base64; + this.isMIME = isMIME; + } + + private byte[] sbBuf = new byte[1]; + + @Override + public int read() throws IOException { + return read(sbBuf, 0, 1) == -1 ? -1 : sbBuf[0] & 0xff; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (closed) + throw new IOException("Stream is closed"); + if (eof && nextout < 0) // eof and no leftover + return -1; + if (off < 0 || len < 0 || len > b.length - off) + throw new IndexOutOfBoundsException(); + int oldOff = off; + if (nextout >= 0) { // leftover output byte(s) in bits buf + do { + if (len == 0) + return off - oldOff; + b[off++] = (byte)(bits >> nextout); + len--; + nextout -= 8; + } while (nextout >= 0); + bits = 0; + } + while (len > 0) { + int v = is.read(); + if (v == -1) { + eof = true; + if (nextin != 18) { + if (nextin == 12) + throw new IOException("Base64 stream has one un-decoded dangling byte."); + // treat ending xx/xxx without padding character legal. + // same logic as v == '=' below + b[off++] = (byte)(bits >> (16)); + len--; + if (nextin == 0) { // only one padding byte + if (len == 0) { // no enough output space + bits >>= 8; // shift to lowest byte + nextout = 0; + } else { + b[off++] = (byte) (bits >> 8); + } + } + } + if (off == oldOff) + return -1; + else + return off - oldOff; + } + if (v == '=') { // padding byte(s) + // = shiftto==18 unnecessary padding + // x= shiftto==12 dangling x, invalid unit + // xx= shiftto==6 && missing last '=' + // xx=y or last is not '=' + if (nextin == 18 || nextin == 12 || + nextin == 6 && is.read() != '=') { + throw new IOException("Illegal base64 ending sequence:" + nextin); + } + b[off++] = (byte)(bits >> (16)); + len--; + if (nextin == 0) { // only one padding byte + if (len == 0) { // no enough output space + bits >>= 8; // shift to lowest byte + nextout = 0; + } else { + b[off++] = (byte) (bits >> 8); + } + } + eof = true; + break; + } + if ((v = base64[v]) == -1) { + if (isMIME) // skip if for rfc2045 + continue; + else + throw new IOException("Illegal base64 character " + + Integer.toString(v, 16)); + } + bits |= (v << nextin); + if (nextin == 0) { + nextin = 18; // clear for next + nextout = 16; + while (nextout >= 0) { + b[off++] = (byte)(bits >> nextout); + len--; + nextout -= 8; + if (len == 0 && nextout >= 0) { // don't clean "bits" + return off - oldOff; + } + } + bits = 0; + } else { + nextin -= 6; + } + } + return off - oldOff; + } + + @Override + public int available() throws IOException { + if (closed) + throw new IOException("Stream is closed"); + return is.available(); // TBD: + } + + @Override + public void close() throws IOException { + if (!closed) { + closed = true; + is.close(); + } + } + } +} diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ids/base64/Objects.java b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ids/base64/Objects.java new file mode 100644 index 000000000..d65ea3032 --- /dev/null +++ b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ids/base64/Objects.java @@ -0,0 +1,157 @@ +package org.skywalking.apm.agent.core.context.ids.base64; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +/** + * This class consists of {@code static} utility methods for operating + * on objects. These utilities include {@code null}-safe or {@code + * null}-tolerant methods for computing the hash code of an object, + * returning a string for an object, and comparing two objects. + * + * @since 1.7 + */ +public final class Objects { + private Objects() { + throw new AssertionError("No java.util.Objects instances for you!"); + } + + /** + * Returns {@code true} if the arguments are equal to each other + * and {@code false} otherwise. + * Consequently, if both arguments are {@code null}, {@code true} + * is returned and if exactly one argument is {@code null}, {@code + * false} is returned. Otherwise, equality is determined by using + * the {@link Object#equals equals} method of the first + * argument. + * + * @param a an object + * @param b an object to be compared with {@code a} for equality + * @return {@code true} if the arguments are equal to each other + * and {@code false} otherwise + * @see Object#equals(Object) + */ + public static boolean equals(Object a, Object b) { + return (a == b) || (a != null && a.equals(b)); + } + + /** + * Returns the hash code of a non-{@code null} argument and 0 for + * a {@code null} argument. + * + * @param o an object + * @return the hash code of a non-{@code null} argument and 0 for + * a {@code null} argument + * @see Object#hashCode + */ + public static int hashCode(Object o) { + return o != null ? o.hashCode() : 0; + } + + /** + * Generates a hash code for a sequence of input values. The hash + * code is generated as if all the input values were placed into an + * array, and that array were hashed by calling {@link + * Arrays#hashCode(Object[])}. + * + *

This method is useful for implementing {@link + * Object#hashCode()} on objects containing multiple fields. For + * example, if an object that has three fields, {@code x}, {@code + * y}, and {@code z}, one could write: + * + *

+     * @Override public int hashCode() {
+     *     return Objects.hash(x, y, z);
+     * }
+     * 
+ * + * Warning: When a single object reference is supplied, the returned + * value does not equal the hash code of that object reference. This + * value can be computed by calling {@link #hashCode(Object)}. + * + * @param values the values to be hashed + * @return a hash value of the sequence of input values + * @see Arrays#hashCode(Object[]) + * @see List#hashCode + */ + public static int hash(Object... values) { + return Arrays.hashCode(values); + } + + /** + * Returns the result of calling {@code toString} for a non-{@code + * null} argument and {@code "null"} for a {@code null} argument. + * + * @param o an object + * @return the result of calling {@code toString} for a non-{@code + * null} argument and {@code "null"} for a {@code null} argument + * @see Object#toString + * @see String#valueOf(Object) + */ + public static String toString(Object o) { + return String.valueOf(o); + } + + /** + * Returns the result of calling {@code toString} on the first + * argument if the first argument is not {@code null} and returns + * the second argument otherwise. + * + * @param o an object + * @param nullDefault string to return if the first argument is + * {@code null} + * @return the result of calling {@code toString} on the first + * argument if it is not {@code null} and the second argument + * otherwise. + */ + public static String toString(Object o, String nullDefault) { + return (o != null) ? o.toString() : nullDefault; + } + + /** + * Returns 0 if the arguments are identical and {@code + * c.compare(a, b)} otherwise. + * Consequently, if both arguments are {@code null} 0 + * is returned. + * + *

Note that if one of the arguments is {@code null}, a {@code + * NullPointerException} may or may not be thrown depending on + * what ordering policy, if any, the {@link Comparator Comparator} + * chooses to have for {@code null} values. + * + * @param the type of the objects being compared + * @param a an object + * @param b an object to be compared with {@code a} + * @param c the {@code Comparator} to compare the first two arguments + * @return 0 if the arguments are identical and {@code + * c.compare(a, b)} otherwise. + * @see Comparable + * @see Comparator + */ + public static int compare(T a, T b, Comparator c) { + return (a == b) ? 0 : c.compare(a, b); + } + + /** + * Checks that the specified object reference is not {@code null}. This + * method is designed primarily for doing parameter validation in methods + * and constructors, as demonstrated below: + *

+     * public Foo(Bar bar) {
+     *     this.bar = Objects.requireNonNull(bar);
+     * }
+     * 
+ * + * @param obj the object reference to check for nullity + * @param the type of the reference + * @return {@code obj} if not {@code null} + * @throws NullPointerException if {@code obj} is {@code null} + */ + public static T requireNonNull(T obj) { + if (obj == null) + throw new NullPointerException(); + return obj; + } + +} diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ids/base64/StandardCharsets.java b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ids/base64/StandardCharsets.java new file mode 100644 index 000000000..cdc70f7df --- /dev/null +++ b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ids/base64/StandardCharsets.java @@ -0,0 +1,44 @@ +package org.skywalking.apm.agent.core.context.ids.base64; + +import java.nio.charset.Charset; + +/** + * Constant definitions for the standard {@link Charset Charsets}. These + * charsets are guaranteed to be available on every implementation of the Java + * platform. + * + * @see Standard Charsets + * @since 1.7 + */ +public final class StandardCharsets { + + private StandardCharsets() { + throw new AssertionError("No java.nio.charset.StandardCharsets instances for you!"); + } + /** + * Seven-bit ASCII, a.k.a. ISO646-US, a.k.a. the Basic Latin block of the + * Unicode character set + */ + public static final Charset US_ASCII = Charset.forName("US-ASCII"); + /** + * ISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1 + */ + public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); + /** + * Eight-bit UCS Transformation Format + */ + public static final Charset UTF_8 = Charset.forName("UTF-8"); + /** + * Sixteen-bit UCS Transformation Format, big-endian byte order + */ + public static final Charset UTF_16BE = Charset.forName("UTF-16BE"); + /** + * Sixteen-bit UCS Transformation Format, little-endian byte order + */ + public static final Charset UTF_16LE = Charset.forName("UTF-16LE"); + /** + * Sixteen-bit UCS Transformation Format, byte order identified by an + * optional byte-order mark + */ + public static final Charset UTF_16 = Charset.forName("UTF-16"); +} diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/trace/TraceSegment.java b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/trace/TraceSegment.java index 6fcc27622..6857c8b8f 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/trace/TraceSegment.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/trace/TraceSegment.java @@ -6,6 +6,7 @@ import org.skywalking.apm.agent.core.conf.RemoteDownstreamConfig; import org.skywalking.apm.agent.core.context.ids.DistributedTraceId; import org.skywalking.apm.agent.core.context.ids.DistributedTraceIds; import org.skywalking.apm.agent.core.context.ids.GlobalIdGenerator; +import org.skywalking.apm.agent.core.context.ids.ID; import org.skywalking.apm.agent.core.context.ids.NewDistributedTraceId; import org.skywalking.apm.logging.ILog; import org.skywalking.apm.logging.LogManager; @@ -31,7 +32,7 @@ public class TraceSegment { * The id of this trace segment. * Every segment has its unique-global-id. */ - private String traceSegmentId; + private ID traceSegmentId; /** * The refs of parent trace segments, except the primary one. @@ -71,7 +72,7 @@ public class TraceSegment { * and generate a new segment id. */ public TraceSegment() { - this.traceSegmentId = GlobalIdGenerator.generate(ID_TYPE); + this.traceSegmentId = GlobalIdGenerator.generate(); this.spans = new LinkedList(); this.relatedGlobalTraces = new DistributedTraceIds(); this.relatedGlobalTraces.append(new NewDistributedTraceId()); @@ -117,7 +118,7 @@ public class TraceSegment { return this; } - public String getTraceSegmentId() { + public ID getTraceSegmentId() { return traceSegmentId; } @@ -157,13 +158,13 @@ public class TraceSegment { public UpstreamSegment transform() { UpstreamSegment.Builder upstreamBuilder = UpstreamSegment.newBuilder(); for (DistributedTraceId distributedTraceId : getRelatedGlobalTraces()) { - upstreamBuilder = upstreamBuilder.addGlobalTraceIds(distributedTraceId.get()); + upstreamBuilder = upstreamBuilder.addGlobalTraceIds(distributedTraceId.toUniqueId()); } TraceSegmentObject.Builder traceSegmentBuilder = TraceSegmentObject.newBuilder(); /** * Trace Segment */ - traceSegmentBuilder.setTraceSegmentId(this.traceSegmentId); + traceSegmentBuilder.setTraceSegmentId(this.traceSegmentId.transform()); // TraceSegmentReference if (this.refs != null) { for (TraceSegmentRef ref : this.refs) { diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/trace/TraceSegmentRef.java b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/trace/TraceSegmentRef.java index 0b71f9db3..74dc4bff9 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/trace/TraceSegmentRef.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/trace/TraceSegmentRef.java @@ -2,6 +2,7 @@ package org.skywalking.apm.agent.core.context.trace; import org.skywalking.apm.agent.core.context.ContextCarrier; import org.skywalking.apm.agent.core.context.ContextSnapshot; +import org.skywalking.apm.agent.core.context.ids.ID; import org.skywalking.apm.agent.core.dictionary.DictionaryUtil; import org.skywalking.apm.network.proto.RefType; import org.skywalking.apm.network.proto.TraceSegmentReference; @@ -15,7 +16,7 @@ import org.skywalking.apm.network.proto.TraceSegmentReference; public class TraceSegmentRef { private SegmentRefType type; - private String traceSegmentId; + private ID traceSegmentId; private int spanId = -1; @@ -103,7 +104,7 @@ public class TraceSegmentRef { refBuilder.setRefType(RefType.CrossThread); } - refBuilder.setParentTraceSegmentId(traceSegmentId); + refBuilder.setParentTraceSegmentId(traceSegmentId.transform()); refBuilder.setParentSpanId(spanId); if (entryOperationId == DictionaryUtil.nullValue()) { refBuilder.setEntryServiceName(entryOperationName); diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/remote/AppAndServiceRegisterClient.java b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/remote/AppAndServiceRegisterClient.java index c32ed0ddb..2493616b4 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/remote/AppAndServiceRegisterClient.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/remote/AppAndServiceRegisterClient.java @@ -1,6 +1,7 @@ package org.skywalking.apm.agent.core.remote; import io.grpc.ManagedChannel; +import java.util.UUID; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -33,6 +34,7 @@ import static org.skywalking.apm.agent.core.remote.GRPCChannelStatus.CONNECTED; */ public class AppAndServiceRegisterClient implements BootService, GRPCChannelListener, Runnable, TracingContextListener { private static final ILog logger = LogManager.getLogger(AppAndServiceRegisterClient.class); + private static final String PROCESS_UUID = UUID.randomUUID().toString().replaceAll("-", ""); private volatile GRPCChannelStatus status = GRPCChannelStatus.DISCONNECT; private volatile ApplicationRegisterServiceGrpc.ApplicationRegisterServiceBlockingStub applicationRegisterServiceBlockingStub; @@ -100,6 +102,7 @@ public class AppAndServiceRegisterClient implements BootService, GRPCChannelList ApplicationInstanceMapping instanceMapping = instanceDiscoveryServiceBlockingStub.register(ApplicationInstance.newBuilder() .setApplicationId(RemoteDownstreamConfig.Agent.APPLICATION_ID) + .setAgentUUID(PROCESS_UUID) .setRegisterTime(System.currentTimeMillis()) .build()); if (instanceMapping.getApplicationInstanceId() != DictionaryUtil.nullValue()) { diff --git a/pom.xml b/pom.xml index 368df9535..45d1a7924 100644 --- a/pom.xml +++ b/pom.xml @@ -174,6 +174,7 @@ org/skywalking/apm/network/**/*.class org/skywalking/apm/collector/remote/grpc/**/*.class + org/skywalking/apm/agent/core/context/ids/base64/*.class @@ -199,7 +200,9 @@ true true org/skywalking/apm/network/**/*.java, - org/skywalking/apm/collector/remote/grpc/**/*.java + org/skywalking/apm/collector/remote/grpc/**/*.java, + org/skywalking/apm/agent/core/context/ids/base64/*.java + check