diff --git a/htrace-core/src/main/java/org/apache/htrace/Span.java b/htrace-core/src/main/java/org/apache/htrace/Span.java index 71164d4..dc4d74f 100644 --- a/htrace-core/src/main/java/org/apache/htrace/Span.java +++ b/htrace-core/src/main/java/org/apache/htrace/Span.java @@ -75,13 +75,7 @@ public interface Span { * The spanId is immutable and cannot be changed. It is safe to access this * from multiple threads. */ - long getSpanId(); - - /** - * A pseudo-unique (random) number assigned to the trace associated with this - * span - */ - long getTraceId(); + SpanId getSpanId(); /** * Create a child span of this span with the given description @@ -96,14 +90,14 @@ public interface Span { * * The array will be empty if there are no parents. */ - long[] getParents(); + SpanId[] getParents(); /** * Set the parents of this span.

* * Any existing parents will be cleared by this call. */ - void setParents(long[] parents); + void setParents(SpanId[] parents); /** * Add a data annotation associated with this span @@ -157,11 +151,8 @@ public interface Span { public void serialize(Span span, JsonGenerator jgen, SerializerProvider provider) throws IOException { jgen.writeStartObject(); - if (span.getTraceId() != 0) { - jgen.writeStringField("i", String.format("%016x", span.getTraceId())); - } - if (span.getSpanId() != 0) { - jgen.writeStringField("s", String.format("%016x", span.getSpanId())); + if (span.getSpanId().isValid()) { + jgen.writeStringField("a", span.getSpanId().toString()); } if (span.getStartTimeMillis() != 0) { jgen.writeNumberField("b", span.getStartTimeMillis()); @@ -177,8 +168,8 @@ public interface Span { jgen.writeStringField("r", processId); } jgen.writeArrayFieldStart("p"); - for (long parent : span.getParents()) { - jgen.writeString(String.format("%016x", parent)); + for (SpanId parent : span.getParents()) { + jgen.writeString(parent.toString()); } jgen.writeEndArray(); Map traceInfoMap = span.getKVAnnotations(); diff --git a/htrace-core/src/main/java/org/apache/htrace/SpanId.java b/htrace-core/src/main/java/org/apache/htrace/SpanId.java new file mode 100644 index 0000000..245eb6b --- /dev/null +++ b/htrace-core/src/main/java/org/apache/htrace/SpanId.java @@ -0,0 +1,149 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.htrace; + +import java.math.BigInteger; +import java.lang.Void; +import java.util.concurrent.ThreadLocalRandom; +import java.util.Random; + +public final class SpanId implements Comparable { + private static final int SPAN_ID_STRING_LENGTH = 36; + private final long high; + private final long low; + + /** + * The invalid span ID, which is all zeroes. + * + * It is also the "least" span ID in the sense that it is considered + * smaller than any other span ID. + */ + public static SpanId INVALID = new SpanId(0, 0); + + private static long nonZeroRand64() { + while (true) { + long r = ThreadLocalRandom.current().nextLong(); + if (r != 0) { + return r; + } + } + } + + public static SpanId fromRandom() { + return new SpanId(nonZeroRand64(), nonZeroRand64()); + } + + public static SpanId fromString(String str) { + if (str.length() != SPAN_ID_STRING_LENGTH) { + throw new RuntimeException("Invalid SpanID string: length was not " + + SPAN_ID_STRING_LENGTH); + } + if ((str.charAt(8) != '-') || (str.charAt(13) != '-') || + (str.charAt(18) != '-') || (str.charAt(23) != '-')) { + throw new RuntimeException("Invalid SpanID string: failed to find " + + "dashes at the expected positions."); + } + long high = + ((Long.parseLong(str.substring(0, 8), 16)) << 32) | + ((Long.parseLong(str.substring(9, 13), 16)) << 16) | + ((Long.parseLong(str.substring(14, 18), 16))); + long low = + ((Long.parseLong(str.substring(19, 23), 16)) << 48) | + ((Long.parseLong(str.substring(24, 36), 16))); + return new SpanId(high, low); + } + + public SpanId(long high, long low) { + this.high = high; + this.low = low; + } + + public long getHigh() { + return high; + } + + public long getLow() { + return low; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof SpanId)) { + return false; + } + SpanId other = (SpanId)o; + return ((other.high == high) && (other.low == low)); + } + + @Override + public int compareTo(SpanId other) { + int cmp = compareAsUnsigned(high, other.high); + if (cmp != 0) { + return cmp; + } + return compareAsUnsigned(low, other.low); + } + + private static int compareAsUnsigned(long a, long b) { + boolean aSign = a < 0; + boolean bSign = b < 0; + if (aSign != bSign) { + if (aSign) { + return 1; + } else { + return -1; + } + } + if (aSign) { + a = -a; + b = -b; + } + if (a < b) { + return -1; + } else if (a > b) { + return 1; + } else { + return 0; + } + } + + @Override + public int hashCode() { + return (int)((0xffffffff & (high >> 32))) ^ + (int)((0xffffffff & (high >> 0))) ^ + (int)((0xffffffff & (low >> 32))) ^ + (int)((0xffffffff & (low >> 0))); + } + + @Override + public String toString() { + return String.format("%08x-%04x-%04x-%04x-%012x", + (0x00000000ffffffffL & (high >> 32)), + (0x0000ffff & (high >> 16)), + (0x0000ffff & (high >> 0)), + (0x0000ffff & (low >> 48)), + (0xffffffffffffL & low)); + } + + public boolean isValid() { + return (high != 0) || (low != 0); + } + + public SpanId newChildId() { + return new SpanId(high, nonZeroRand64()); + } +} diff --git a/htrace-core/src/main/java/org/apache/htrace/Trace.java b/htrace-core/src/main/java/org/apache/htrace/Trace.java index 6e69118..4cc3a7c 100644 --- a/htrace-core/src/main/java/org/apache/htrace/Trace.java +++ b/htrace-core/src/main/java/org/apache/htrace/Trace.java @@ -75,15 +75,16 @@ public class Trace { return startSpan(description, TrueIfTracingSampler.INSTANCE); } - public static TraceScope startSpan(String description, TraceInfo tinfo) { - if (tinfo == null) return continueSpan(null); + public static TraceScope startSpan(String description, SpanId parentId) { + if (parentId == null) { + return continueSpan(null); + } Span newSpan = new MilliSpan.Builder(). begin(System.currentTimeMillis()). end(0). description(description). - traceId(tinfo.traceId). - spanId(Tracer.nonZeroRandom64()). - parents(new long[] { tinfo.spanId }). + spanId(parentId.newChildId()). + parents(new SpanId[] { parentId }). build(); return continueSpan(newSpan); } diff --git a/htrace-core/src/main/java/org/apache/htrace/TraceInfo.java b/htrace-core/src/main/java/org/apache/htrace/TraceInfo.java deleted file mode 100644 index 9e7d74a..0000000 --- a/htrace-core/src/main/java/org/apache/htrace/TraceInfo.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.htrace; - - -public class TraceInfo { - public final long traceId; - public final long spanId; - - public TraceInfo(long traceId, long spanId) { - this.traceId = traceId; - this.spanId = spanId; - } - - @Override - public String toString() { - return "TraceInfo(traceId=" + traceId + ", spanId=" + spanId + ")"; - } - - public static TraceInfo fromSpan(Span s) { - if (s == null) return null; - return new TraceInfo(s.getTraceId(), s.getSpanId()); - } -} diff --git a/htrace-core/src/main/java/org/apache/htrace/TraceTree.java b/htrace-core/src/main/java/org/apache/htrace/TraceTree.java index 5edfde0..980f262 100644 --- a/htrace-core/src/main/java/org/apache/htrace/TraceTree.java +++ b/htrace-core/src/main/java/org/apache/htrace/TraceTree.java @@ -47,38 +47,32 @@ public class TraceTree { new Comparator() { @Override public int compare(Span a, Span b) { - if (a.getSpanId() < b.getSpanId()) { - return -1; - } else if (a.getSpanId() > b.getSpanId()) { - return 1; - } else { - return 0; - } + return a.getSpanId().compareTo(b.getSpanId()); } }; private final TreeSet treeSet; - private final HashMap> parentToSpans; + private final HashMap> parentToSpans; SpansByParent(Collection spans) { TreeSet treeSet = new TreeSet(COMPARATOR); - parentToSpans = new HashMap>(); + parentToSpans = new HashMap>(); for (Span span : spans) { treeSet.add(span); - for (long parent : span.getParents()) { - LinkedList list = parentToSpans.get(Long.valueOf(parent)); + for (SpanId parent : span.getParents()) { + LinkedList list = parentToSpans.get(parent); if (list == null) { list = new LinkedList(); - parentToSpans.put(Long.valueOf(parent), list); + parentToSpans.put(parent, list); } list.add(span); } if (span.getParents().length == 0) { - LinkedList list = parentToSpans.get(Long.valueOf(0L)); + LinkedList list = parentToSpans.get(SpanId.INVALID); if (list == null) { list = new LinkedList(); - parentToSpans.put(Long.valueOf(0L), list); + parentToSpans.put(SpanId.INVALID, list); } list.add(span); } @@ -86,7 +80,7 @@ public class TraceTree { this.treeSet = treeSet; } - public List find(long parentId) { + public List find(SpanId parentId) { LinkedList spans = parentToSpans.get(parentId); if (spans == null) { return new LinkedList(); @@ -110,13 +104,8 @@ public class TraceTree { int cmp = a.getProcessId().compareTo(b.getProcessId()); if (cmp != 0) { return cmp; - } else if (a.getSpanId() < b.getSpanId()) { - return -1; - } else if (a.getSpanId() > b.getSpanId()) { - return 1; - } else { - return 0; } + return a.getSpanId().compareTo(b.getSpanId()); } }; @@ -133,8 +122,7 @@ public class TraceTree { public List find(String processId) { List spans = new ArrayList(); Span span = new MilliSpan.Builder(). - traceId(Long.MIN_VALUE). - spanId(Long.MIN_VALUE). + spanId(SpanId.INVALID). processId(processId). build(); while (true) { diff --git a/htrace-core/src/main/java/org/apache/htrace/Tracer.java b/htrace-core/src/main/java/org/apache/htrace/Tracer.java index b0ed451..d07e1a8 100644 --- a/htrace-core/src/main/java/org/apache/htrace/Tracer.java +++ b/htrace-core/src/main/java/org/apache/htrace/Tracer.java @@ -48,8 +48,7 @@ public class Tracer { return null; } }; - public static final TraceInfo DONT_TRACE = new TraceInfo(-1, -1); - private static final long EMPTY_PARENT_ARRAY[] = new long[0]; + private static final SpanId EMPTY_PARENT_ARRAY[] = new SpanId[0]; /** * Log a client error, and throw an exception. @@ -81,9 +80,8 @@ public class Tracer { begin(System.currentTimeMillis()). end(0). description(description). - traceId(nonZeroRandom64()). parents(EMPTY_PARENT_ARRAY). - spanId(nonZeroRandom64()). + spanId(SpanId.fromRandom()). build(); } else { return parent.child(description); diff --git a/htrace-core/src/main/java/org/apache/htrace/impl/MilliSpan.java b/htrace-core/src/main/java/org/apache/htrace/impl/MilliSpan.java index 8544867..48ceacb 100644 --- a/htrace-core/src/main/java/org/apache/htrace/impl/MilliSpan.java +++ b/htrace-core/src/main/java/org/apache/htrace/impl/MilliSpan.java @@ -26,6 +26,7 @@ import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.ObjectWriter; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import org.apache.htrace.Span; +import org.apache.htrace.SpanId; import org.apache.htrace.TimelineAnnotation; import org.apache.htrace.Tracer; @@ -52,37 +53,26 @@ public class MilliSpan implements Span { private static ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private static ObjectReader JSON_READER = OBJECT_MAPPER.reader(MilliSpan.class); private static ObjectWriter JSON_WRITER = OBJECT_MAPPER.writer(); - private static final long EMPTY_PARENT_ARRAY[] = new long[0]; + private static final SpanId EMPTY_PARENT_ARRAY[] = new SpanId[0]; private static final String EMPTY_STRING = ""; private long begin; private long end; private final String description; - private final long traceId; - private long parents[]; - private final long spanId; + private SpanId parents[]; + private final SpanId spanId; private Map traceInfo = null; private String processId; private List timeline = null; - private static long nonZeroRandom64() { - long id; - Random random = ThreadLocalRandom.current(); - do { - id = random.nextLong(); - } while (id == 0); - return id; - } - @Override public Span child(String childDescription) { return new MilliSpan.Builder(). begin(System.currentTimeMillis()). end(0). description(childDescription). - traceId(traceId). - parents(new long[] {spanId}). - spanId(nonZeroRandom64()). + parents(new SpanId[] {spanId}). + spanId(spanId.newChildId()). processId(processId). build(); } @@ -94,9 +84,8 @@ public class MilliSpan implements Span { private long begin; private long end; private String description = EMPTY_STRING; - private long traceId; - private long parents[] = EMPTY_PARENT_ARRAY; - private long spanId; + private SpanId parents[] = EMPTY_PARENT_ARRAY; + private SpanId spanId = SpanId.INVALID; private Map traceInfo = null; private String processId = EMPTY_STRING; private List timeline = null; @@ -119,26 +108,21 @@ public class MilliSpan implements Span { return this; } - public Builder traceId(long traceId) { - this.traceId = traceId; - return this; - } - - public Builder parents(long parents[]) { + public Builder parents(SpanId parents[]) { this.parents = parents; return this; } - public Builder parents(List parentList) { - long[] parents = new long[parentList.size()]; + public Builder parents(List parentList) { + SpanId[] parents = new SpanId[parentList.size()]; for (int i = 0; i < parentList.size(); i++) { - parents[i] = parentList.get(i).longValue(); + parents[i] = parentList.get(i); } this.parents = parents; return this; } - public Builder spanId(long spanId) { + public Builder spanId(SpanId spanId) { this.spanId = spanId; return this; } @@ -167,9 +151,8 @@ public class MilliSpan implements Span { this.begin = 0; this.end = 0; this.description = EMPTY_STRING; - this.traceId = 0; this.parents = EMPTY_PARENT_ARRAY; - this.spanId = 0; + this.spanId = SpanId.INVALID; this.traceInfo = null; this.processId = EMPTY_STRING; this.timeline = null; @@ -179,7 +162,6 @@ public class MilliSpan implements Span { this.begin = builder.begin; this.end = builder.end; this.description = builder.description; - this.traceId = builder.traceId; this.parents = builder.parents; this.spanId = builder.spanId; this.traceInfo = builder.traceInfo; @@ -227,26 +209,21 @@ public class MilliSpan implements Span { } @Override - public long getSpanId() { + public SpanId getSpanId() { return spanId; } @Override - public long[] getParents() { + public SpanId[] getParents() { return parents; } @Override - public void setParents(long[] parents) { + public void setParents(SpanId[] parents) { this.parents = parents; } @Override - public long getTraceId() { - return traceId; - } - - @Override public long getStartTimeMillis() { return begin; } @@ -318,10 +295,6 @@ public class MilliSpan implements Span { return writer.toString(); } - private static long parseUnsignedHexLong(String s) { - return new BigInteger(s, 16).longValue(); - } - public static class MilliSpanDeserializer extends JsonDeserializer { @Override @@ -341,25 +314,21 @@ public class MilliSpan implements Span { if (dNode != null) { builder.description(dNode.asText()); } - JsonNode iNode = root.get("i"); - if (iNode != null) { - builder.traceId(parseUnsignedHexLong(iNode.asText())); - } - JsonNode sNode = root.get("s"); + JsonNode sNode = root.get("a"); if (sNode != null) { - builder.spanId(parseUnsignedHexLong(sNode.asText())); + builder.spanId(SpanId.fromString(sNode.asText())); } JsonNode rNode = root.get("r"); if (rNode != null) { builder.processId(rNode.asText()); } JsonNode parentsNode = root.get("p"); - LinkedList parents = new LinkedList(); + LinkedList parents = new LinkedList(); if (parentsNode != null) { for (Iterator iter = parentsNode.elements(); iter.hasNext(); ) { JsonNode parentIdNode = iter.next(); - parents.add(parseUnsignedHexLong(parentIdNode.asText())); + parents.add(SpanId.fromString(parentIdNode.asText())); } } builder.parents(parents); diff --git a/htrace-core/src/test/java/org/apache/htrace/TestBadClient.java b/htrace-core/src/test/java/org/apache/htrace/TestBadClient.java index e13a0f8..868c0d0 100644 --- a/htrace-core/src/test/java/org/apache/htrace/TestBadClient.java +++ b/htrace-core/src/test/java/org/apache/htrace/TestBadClient.java @@ -24,8 +24,6 @@ import org.apache.htrace.HTraceConfiguration; import org.apache.htrace.Span; import org.apache.htrace.SpanReceiver; import org.apache.htrace.Tracer; -import org.apache.htrace.TraceTree.SpansByParent; -import org.apache.htrace.TraceTree; import org.apache.htrace.impl.AlwaysSampler; import org.apache.htrace.impl.LocalFileSpanReceiver; import org.apache.htrace.impl.POJOSpanReceiver; diff --git a/htrace-core/src/test/java/org/apache/htrace/TestHTrace.java b/htrace-core/src/test/java/org/apache/htrace/TestHTrace.java index 0ab8f35..dc2e849 100644 --- a/htrace-core/src/test/java/org/apache/htrace/TestHTrace.java +++ b/htrace-core/src/test/java/org/apache/htrace/TestHTrace.java @@ -19,7 +19,6 @@ package org.apache.htrace; import org.apache.htrace.HTraceConfiguration; import org.apache.htrace.Span; import org.apache.htrace.SpanReceiver; -import org.apache.htrace.TraceTree; import org.apache.htrace.TraceTree.SpansByParent; import org.apache.htrace.impl.LocalFileSpanReceiver; import org.apache.htrace.impl.POJOSpanReceiver; @@ -77,7 +76,7 @@ public class TestHTrace { Collection spans = psr.getSpans(); TraceTree traceTree = new TraceTree(spans); - Collection roots = traceTree.getSpansByParent().find(0); + Collection roots = traceTree.getSpansByParent().find(SpanId.INVALID); Assert.assertTrue("Trace tree must have roots", !roots.isEmpty()); Assert.assertEquals(numTraces, roots.size()); @@ -118,11 +117,10 @@ public class TestHTrace { @Test(timeout=60000) public void testRootSpansHaveNonZeroSpanId() throws Exception { - TraceInfo traceInfo = new TraceInfo(100L, 200L); - TraceScope scope = Trace.startSpan("myRootSpan", traceInfo); + TraceScope scope = Trace.startSpan("myRootSpan", new SpanId(100L, 200L)); Assert.assertNotNull(scope); Assert.assertEquals("myRootSpan", scope.getSpan().getDescription()); - Assert.assertEquals(100L, scope.getSpan().getTraceId()); - Assert.assertTrue(0 != scope.getSpan().getSpanId()); + Assert.assertEquals(100L, scope.getSpan().getSpanId().getHigh()); + Assert.assertTrue(scope.getSpan().getSpanId().isValid()); } } diff --git a/htrace-core/src/test/java/org/apache/htrace/TestSpanId.java b/htrace-core/src/test/java/org/apache/htrace/TestSpanId.java new file mode 100644 index 0000000..10e6cca --- /dev/null +++ b/htrace-core/src/test/java/org/apache/htrace/TestSpanId.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.htrace; + +import java.util.Random; +import org.apache.htrace.SpanId; +import org.junit.Assert; +import org.junit.Test; + +public class TestSpanId { + private void testRoundTrip(SpanId id) throws Exception { + String str = id.toString(); + SpanId id2 = SpanId.fromString(str); + Assert.assertEquals(id, id2); + } + + @Test + public void testToStringAndFromString() throws Exception { + testRoundTrip(SpanId.INVALID); + testRoundTrip(new SpanId(0x1234567812345678L, 0x1234567812345678L)); + testRoundTrip(new SpanId(0xf234567812345678L, 0xf234567812345678L)); + testRoundTrip(new SpanId(0xffffffffffffffffL, 0xffffffffffffffffL)); + Random rand = new Random(12345); + for (int i = 0; i < 100; i++) { + testRoundTrip(new SpanId(rand.nextLong(), rand.nextLong())); + } + } + + @Test + public void testValidAndInvalidIds() throws Exception { + Assert.assertFalse(SpanId.INVALID.isValid()); + Assert.assertTrue( + new SpanId(0x1234567812345678L, 0x1234567812345678L).isValid()); + Assert.assertTrue( + new SpanId(0xf234567812345678L, 0xf234567812345678L).isValid()); + } + + private void expectLessThan(SpanId a, SpanId b) throws Exception { + int cmp = a.compareTo(b); + Assert.assertTrue("Expected " + a + " to be less than " + b, + (cmp < 0)); + int cmp2 = b.compareTo(a); + Assert.assertTrue("Expected " + b + " to be greater than " + a, + (cmp2 > 0)); + } + + @Test + public void testIdComparisons() throws Exception { + expectLessThan(new SpanId(0x0000000000000001L, 0x0000000000000001L), + new SpanId(0x0000000000000001L, 0x0000000000000002L)); + expectLessThan(new SpanId(0x0000000000000001L, 0x0000000000000001L), + new SpanId(0x0000000000000002L, 0x0000000000000000L)); + expectLessThan(SpanId.INVALID, + new SpanId(0xffffffffffffffffL, 0xffffffffffffffffL)); + expectLessThan(new SpanId(0x1234567812345678L, 0x1234567812345678L), + new SpanId(0x1234567812345678L, 0xf234567812345678L)); + } +} diff --git a/htrace-core/src/test/java/org/apache/htrace/TraceCreator.java b/htrace-core/src/test/java/org/apache/htrace/TraceCreator.java index 7ec6309..c3a91eb 100644 --- a/htrace-core/src/test/java/org/apache/htrace/TraceCreator.java +++ b/htrace-core/src/test/java/org/apache/htrace/TraceCreator.java @@ -23,7 +23,6 @@ import java.util.concurrent.ThreadLocalRandom; import org.apache.htrace.Sampler; import org.apache.htrace.SpanReceiver; import org.apache.htrace.Trace; -import org.apache.htrace.TraceInfo; import org.apache.htrace.TraceScope; /** diff --git a/htrace-core/src/test/java/org/apache/htrace/impl/TestMilliSpan.java b/htrace-core/src/test/java/org/apache/htrace/impl/TestMilliSpan.java index 2e0edde..7023973 100644 --- a/htrace-core/src/test/java/org/apache/htrace/impl/TestMilliSpan.java +++ b/htrace-core/src/test/java/org/apache/htrace/impl/TestMilliSpan.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import org.apache.htrace.Span; +import org.apache.htrace.SpanId; import org.apache.htrace.TimelineAnnotation; import org.junit.Test; @@ -37,7 +38,6 @@ public class TestMilliSpan { assertEquals(expected.getStartTimeMillis(), got.getStartTimeMillis()); assertEquals(expected.getStopTimeMillis(), got.getStopTimeMillis()); assertEquals(expected.getDescription(), got.getDescription()); - assertEquals(expected.getTraceId(), got.getTraceId()); assertEquals(expected.getSpanId(), got.getSpanId()); assertEquals(expected.getProcessId(), got.getProcessId()); assertTrue(Arrays.equals(expected.getParents(), got.getParents())); @@ -74,10 +74,10 @@ public class TestMilliSpan { description("foospan"). begin(123L). end(456L). - parents(new long[] { 7L }). + parents(new SpanId[] { new SpanId(7L, 7L) }). processId("b2404.halxg.com:8080"). - spanId(989L). - traceId(444).build(); + spanId(new SpanId(7L, 8L)). + build(); String json = span.toJson(); MilliSpan dspan = MilliSpan.fromJson(json); compareSpans(span, dspan); @@ -85,14 +85,15 @@ public class TestMilliSpan { @Test public void testJsonSerializationWithNegativeLongValue() throws Exception { + SpanId parentId = new SpanId(-1L, -1L); MilliSpan span = new MilliSpan.Builder(). description("foospan"). begin(-1L). end(-1L). - parents(new long[] { -1L }). + parents(new SpanId[] { parentId }). processId("b2404.halxg.com:8080"). - spanId(-1L). - traceId(-1L).build(); + spanId(parentId.newChildId()). + build(); String json = span.toJson(); MilliSpan dspan = MilliSpan.fromJson(json); compareSpans(span, dspan); @@ -101,14 +102,15 @@ public class TestMilliSpan { @Test public void testJsonSerializationWithRandomLongValue() throws Exception { Random random = new SecureRandom(); + SpanId parentId = SpanId.fromRandom(); MilliSpan span = new MilliSpan.Builder(). description("foospan"). begin(random.nextLong()). end(random.nextLong()). - parents(new long[] { random.nextLong() }). + parents(new SpanId[] { parentId }). processId("b2404.halxg.com:8080"). - spanId(random.nextLong()). - traceId(random.nextLong()).build(); + spanId(parentId.newChildId()). + build(); String json = span.toJson(); MilliSpan dspan = MilliSpan.fromJson(json); compareSpans(span, dspan); @@ -120,10 +122,9 @@ public class TestMilliSpan { description("foospan"). begin(300). end(400). - parents(new long[] { }). + parents(new SpanId[] { }). processId("b2408.halxg.com:8080"). - spanId(111111111L). - traceId(4443); + spanId(new SpanId(111111111L, 111111111L)); Map traceInfo = new HashMap(); traceInfo.put("abc", "123"); traceInfo.put("def", "456"); diff --git a/htrace-flume/src/main/java/org/apache/htrace/impl/FlumeSpanReceiver.java b/htrace-flume/src/main/java/org/apache/htrace/impl/FlumeSpanReceiver.java index baa4fa1..b96daa0 100644 --- a/htrace-flume/src/main/java/org/apache/htrace/impl/FlumeSpanReceiver.java +++ b/htrace-flume/src/main/java/org/apache/htrace/impl/FlumeSpanReceiver.java @@ -173,8 +173,7 @@ public class FlumeSpanReceiver implements SpanReceiver { for (Span span : dequeuedSpans) { // Headers allow Flume to filter Map headers = new HashMap(); - headers.put("TraceId", Long.toString(span.getTraceId())); - headers.put("SpanId", Long.toString(span.getSpanId())); + headers.put("SpanId", span.getSpanId().toString()); headers.put("ProcessId", span.getProcessId()); headers.put("Description", span.getDescription()); diff --git a/htrace-flume/src/test/java/org/apache/htrace/impl/TestFlumeSpanReceiver.java b/htrace-flume/src/test/java/org/apache/htrace/impl/TestFlumeSpanReceiver.java index f7d3840..65eef90 100644 --- a/htrace-flume/src/test/java/org/apache/htrace/impl/TestFlumeSpanReceiver.java +++ b/htrace-flume/src/test/java/org/apache/htrace/impl/TestFlumeSpanReceiver.java @@ -38,6 +38,7 @@ import org.apache.flume.source.avro.AvroSourceProtocol; import org.apache.flume.source.avro.Status; import org.apache.htrace.HTraceConfiguration; import org.apache.htrace.Span; +import org.apache.htrace.SpanId; import org.apache.htrace.SpanReceiver; import org.apache.htrace.Trace; import org.apache.htrace.TraceCreator; @@ -74,8 +75,7 @@ public class TestFlumeSpanReceiver { spans = new ArrayList(); Span rootSpan = new MilliSpan.Builder(). description(ROOT_SPAN_DESC). - traceId(1). - spanId(100). + spanId(new SpanId(100, 100)). processId("test"). begin(System.currentTimeMillis()). build(); diff --git a/htrace-hbase/src/main/java/org/apache/htrace/impl/HBaseSpanReceiver.java b/htrace-hbase/src/main/java/org/apache/htrace/impl/HBaseSpanReceiver.java index 2faf4bb..22d438b 100644 --- a/htrace-hbase/src/main/java/org/apache/htrace/impl/HBaseSpanReceiver.java +++ b/htrace-hbase/src/main/java/org/apache/htrace/impl/HBaseSpanReceiver.java @@ -208,17 +208,17 @@ public class HBaseSpanReceiver implements SpanReceiver { try { for (Span span : dequeuedSpans) { sbuilder.clear() - .setTraceId(span.getTraceId()) + .setTraceId(span.getSpanId().getHigh()) .setStart(span.getStartTimeMillis()) .setStop(span.getStopTimeMillis()) - .setSpanId(span.getSpanId()) + .setSpanId(span.getSpanId().getLow()) .setProcessId(span.getProcessId()) .setDescription(span.getDescription()); if (span.getParents().length == 0) { sbuilder.setParentId(0); } else if (span.getParents().length > 0) { - sbuilder.setParentId(span.getParents()[0]); + sbuilder.setParentId(span.getParents()[0].getLow()); if (span.getParents().length > 1) { LOG.error("error: HBaseSpanReceiver does not support spans " + "with multiple parents. Ignoring multiple parents for " + @@ -231,7 +231,7 @@ public class HBaseSpanReceiver implements SpanReceiver { .setMessage(ta.getMessage()) .build()); } - Put put = new Put(Bytes.toBytes(span.getTraceId())); + Put put = new Put(Bytes.toBytes(span.getSpanId().getHigh())); put.add(HBaseSpanReceiver.this.cf, sbuilder.build().toByteArray(), null); @@ -360,7 +360,7 @@ public class HBaseSpanReceiver implements SpanReceiver { Trace.addReceiver(receiver); TraceScope parent = Trace.startSpan("HBaseSpanReceiver.main.parent", Sampler.ALWAYS); Thread.sleep(10); - long traceid = parent.getSpan().getTraceId(); + long traceid = parent.getSpan().getSpanId().getHigh(); TraceScope child1 = Trace.startSpan("HBaseSpanReceiver.main.child.1"); Thread.sleep(10); TraceScope child2 = Trace.startSpan("HBaseSpanReceiver.main.child.2", parent.getSpan()); diff --git a/htrace-hbase/src/test/java/org/apache/htrace/impl/TestHBaseSpanReceiver.java b/htrace-hbase/src/test/java/org/apache/htrace/impl/TestHBaseSpanReceiver.java index bf93220..703bc7d 100644 --- a/htrace-hbase/src/test/java/org/apache/htrace/impl/TestHBaseSpanReceiver.java +++ b/htrace-hbase/src/test/java/org/apache/htrace/impl/TestHBaseSpanReceiver.java @@ -38,6 +38,7 @@ import org.apache.hadoop.hbase.client.ResultScanner; import org.apache.hadoop.hbase.client.Scan; import org.apache.hadoop.hbase.util.Bytes; import org.apache.htrace.Span; +import org.apache.htrace.SpanId; import org.apache.htrace.SpanReceiver; import org.apache.htrace.TimelineAnnotation; import org.apache.htrace.TraceCreator; @@ -95,7 +96,7 @@ public class TestHBaseSpanReceiver { TraceTree traceTree = new TraceTree(spans); Collection roots = - traceTree.getSpansByParent().find(0); + traceTree.getSpansByParent().find(SpanId.INVALID); Assert.assertTrue("Trace tree must have roots", !roots.isEmpty()); Assert.assertEquals(3, roots.size()); @@ -144,19 +145,14 @@ public class TestHBaseSpanReceiver { } @Override - public long getTraceId() { - return span.getTraceId(); - } - - @Override - public long[] getParents() { + public SpanId[] getParents() { return (span.getParentId() == 0L) ? - (new long[] {}) : - (new long[] { span.getParentId() }); + (new SpanId[] {}) : + (new SpanId[] { new SpanId(span.getTraceId(), span.getParentId()) }); } @Override - public void setParents(long[] parents) { + public void setParents(SpanId[] parents) { throw new UnsupportedOperationException(); } @@ -171,8 +167,8 @@ public class TestHBaseSpanReceiver { } @Override - public long getSpanId() { - return span.getSpanId(); + public SpanId getSpanId() { + return new SpanId(span.getTraceId(), span.getSpanId()); } @Override diff --git a/htrace-htraced/go/src/org/apache/htrace/client/client.go b/htrace-htraced/go/src/org/apache/htrace/client/client.go index 6a62e81..e96d457 100644 --- a/htrace-htraced/go/src/org/apache/htrace/client/client.go +++ b/htrace-htraced/go/src/org/apache/htrace/client/client.go @@ -69,7 +69,7 @@ func (hcl *Client) GetServerInfo() (*common.ServerInfo, error) { // Get information about a trace span. Returns nil, nil if the span was not found. func (hcl *Client) FindSpan(sid common.SpanId) (*common.Span, error) { - buf, rc, err := hcl.makeGetRequest(fmt.Sprintf("span/%016x", uint64(sid))) + buf, rc, err := hcl.makeGetRequest(fmt.Sprintf("span/%s", sid.String())) if err != nil { if rc == http.StatusNoContent { return nil, nil @@ -133,8 +133,8 @@ func (hcl *Client) writeSpansHttp(req *common.WriteSpansReq) error { // Find the child IDs of a given span ID. func (hcl *Client) FindChildren(sid common.SpanId, lim int) ([]common.SpanId, error) { - buf, _, err := hcl.makeGetRequest(fmt.Sprintf("span/%016x/children?lim=%d", - uint64(sid), lim)) + buf, _, err := hcl.makeGetRequest(fmt.Sprintf("span/%s/children?lim=%d", + sid.String(), lim)) if err != nil { return nil, err } @@ -209,7 +209,7 @@ func (hcl *Client) DumpAll(lim int, out chan *common.Span) error { defer func() { close(out) }() - searchId := common.SpanId(0) + searchId := common.INVALID_SPAN_ID for { q := common.Query{ Lim: lim, @@ -232,7 +232,7 @@ func (hcl *Client) DumpAll(lim int, out chan *common.Span) error { for i := range spans { out <- &spans[i] } - searchId = spans[len(spans)-1].Id + 1 + searchId = spans[len(spans)-1].Id.Next() } } diff --git a/htrace-htraced/go/src/org/apache/htrace/common/span.go b/htrace-htraced/go/src/org/apache/htrace/common/span.go index b276844..d3a1940 100644 --- a/htrace-htraced/go/src/org/apache/htrace/common/span.go +++ b/htrace-htraced/go/src/org/apache/htrace/common/span.go @@ -20,10 +20,11 @@ package common import ( + "bytes" "encoding/json" "errors" "fmt" - "strconv" + "hash/fnv" ) // @@ -43,18 +44,88 @@ type TimelineAnnotation struct { Msg string `json:"m"` } -type SpanId uint64 +type SpanId []byte + +var INVALID_SPAN_ID SpanId = make([]byte, 16) // all zeroes func (id SpanId) String() string { - return fmt.Sprintf("%016x", uint64(id)) + return fmt.Sprintf("%02x%02x%02x%02x-"+ + "%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7], id[8], + id[9], id[10], id[11], id[12], id[13], id[14], id[15]) +} + +func (id SpanId) Val() []byte { + return []byte(id) } -func (id SpanId) Val() uint64 { - return uint64(id) +func (id SpanId) FindProblem() string { + if id == nil { + return "The span ID is nil" + } + if len(id) != 16 { + return "The span ID is not exactly 16 bytes." + } + if bytes.Equal(id.Val(), INVALID_SPAN_ID.Val()) { + return "The span ID is all zeros." + } + return "" +} + +func (id SpanId) ToArray() [16]byte { + var ret [16]byte + copy(ret[:], id.Val()[:]) + return ret +} + +// Return the next ID in lexicographical order. For the maximum ID, +// returns the minimum. +func (id SpanId) Next() SpanId { + next := make([]byte, 16) + copy(next, id) + for i := len(next) - 1; i >= 0; i-- { + if next[i] == 0xff { + next[i] = 0 + } else { + next[i] = next[i] + 1 + break + } + } + return next +} + +// Return the previous ID in lexicographical order. For the minimum ID, +// returns the maximum ID. +func (id SpanId) Prev() SpanId { + prev := make([]byte, 16) + copy(prev, id) + for i := len(prev) - 1; i >= 0; i-- { + if prev[i] == 0x00 { + prev[i] = 0xff + } else { + prev[i] = prev[i] - 1 + break + } + } + return prev } func (id SpanId) MarshalJSON() ([]byte, error) { - return []byte(`"` + fmt.Sprintf("%016x", uint64(id)) + `"`), nil + return []byte(`"` + id.String() + `"`), nil +} + +func (id SpanId) Compare(other SpanId) int { + return bytes.Compare(id.Val(), other.Val()) +} + +func (id SpanId) Equal(other SpanId) bool { + return bytes.Equal(id.Val(), other.Val()) +} + +func (id SpanId) Hash32() uint32 { + h := fnv.New32a() + h.Write(id.Val()) + return h.Sum32() } type SpanSlice []*Span @@ -64,7 +135,7 @@ func (s SpanSlice) Len() int { } func (s SpanSlice) Less(i, j int) bool { - return s[i].Id < s[j].Id + return s[i].Id.Compare(s[j].Id) < 0 } func (s SpanSlice) Swap(i, j int) { @@ -78,7 +149,7 @@ func (s SpanIdSlice) Len() int { } func (s SpanIdSlice) Less(i, j int) bool { - return s[i] < s[j] + return s[i].Compare(s[j]) < 0 } func (s SpanIdSlice) Swap(i, j int) { @@ -98,11 +169,18 @@ func (id *SpanId) UnmarshalJSON(b []byte) error { } func (id *SpanId) FromString(str string) error { - v, err := strconv.ParseUint(str, 16, 64) + i := SpanId(make([]byte, 16)) + n, err := fmt.Sscanf(str, "%02x%02x%02x%02x-"+ + "%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + &i[0], &i[1], &i[2], &i[3], &i[4], &i[5], &i[6], &i[7], &i[8], + &i[9], &i[10], &i[11], &i[12], &i[13], &i[14], &i[15]) if err != nil { return err } - *id = SpanId(v) + if n != 16 { + return errors.New("Failed to find 16 hex digits in the SpanId") + } + *id = i return nil } @@ -110,7 +188,6 @@ type SpanData struct { Begin int64 `json:"b"` End int64 `json:"e"` Description string `json:"d"` - TraceId SpanId `json:"i"` Parents []SpanId `json:"p"` Info TraceInfoMap `json:"n,omitempty"` ProcessId string `json:"r"` @@ -118,7 +195,7 @@ type SpanData struct { } type Span struct { - Id SpanId `json:"s"` + Id SpanId `json:"a"` SpanData } diff --git a/htrace-htraced/go/src/org/apache/htrace/common/span_test.go b/htrace-htraced/go/src/org/apache/htrace/common/span_test.go index f218b3a..a30ce52 100644 --- a/htrace-htraced/go/src/org/apache/htrace/common/span_test.go +++ b/htrace-htraced/go/src/org/apache/htrace/common/span_test.go @@ -20,33 +20,35 @@ package common import ( + "bytes" + "encoding/hex" + "fmt" + "github.com/ugorji/go/codec" "testing" ) func TestSpanToJson(t *testing.T) { t.Parallel() - span := Span{Id: 2305843009213693952, + span := Span{Id: TestId("33f25a1a-750a-471d-b5ba-fa59309d7d6f"), SpanData: SpanData{ Begin: 123, End: 456, Description: "getFileDescriptors", - TraceId: 999, Parents: []SpanId{}, ProcessId: "testProcessId", }} ExpectStrEqual(t, - `{"s":"2000000000000000","b":123,"e":456,"d":"getFileDescriptors","i":"00000000000003e7","p":[],"r":"testProcessId"}`, + `{"a":"33f25a1a-750a-471d-b5ba-fa59309d7d6f","b":123,"e":456,"d":"getFileDescriptors","p":[],"r":"testProcessId"}`, string(span.ToJson())) } func TestAnnotatedSpanToJson(t *testing.T) { t.Parallel() - span := Span{Id: 1305813009213693952, + span := Span{Id: TestId("11eace42-e640-4b40-a764-4214cb779a08"), SpanData: SpanData{ Begin: 1234, End: 4567, Description: "getFileDescriptors2", - TraceId: 999, Parents: []SpanId{}, ProcessId: "testAnnotatedProcessId", TimelineAnnotations: []TimelineAnnotation{ @@ -61,6 +63,54 @@ func TestAnnotatedSpanToJson(t *testing.T) { }, }} ExpectStrEqual(t, - `{"s":"121f2e036d442000","b":1234,"e":4567,"d":"getFileDescriptors2","i":"00000000000003e7","p":[],"r":"testAnnotatedProcessId","t":[{"t":7777,"m":"contactedServer"},{"t":8888,"m":"passedFd"}]}`, + `{"a":"11eace42-e640-4b40-a764-4214cb779a08","b":1234,"e":4567,"d":"getFileDescriptors2","p":[],"r":"testAnnotatedProcessId","t":[{"t":7777,"m":"contactedServer"},{"t":8888,"m":"passedFd"}]}`, string(span.ToJson())) } + +func TestSpanNext(t *testing.T) { + ExpectStrEqual(t, TestId("00000000-0000-0000-0000-000000000001").String(), + TestId("00000000-0000-0000-0000-000000000000").Next().String()) + ExpectStrEqual(t, TestId("00000000-0000-0000-0000-000000f00000").String(), + TestId("00000000-0000-0000-0000-000000efffff").Next().String()) + ExpectStrEqual(t, TestId("00000000-0000-0000-0000-000000000000").String(), + TestId("ffffffff-ffff-ffff-ffff-ffffffffffff").Next().String()) +} + +func TestSpanPrev(t *testing.T) { + ExpectStrEqual(t, TestId("00000000-0000-0000-0000-000000000000").String(), + TestId("00000000-0000-0000-0000-000000000001").Prev().String()) + ExpectStrEqual(t, TestId("00000000-0000-0000-0000-000000efffff").String(), + TestId("00000000-0000-0000-0000-000000f00000").Prev().String()) + ExpectStrEqual(t, TestId("ffffffff-ffff-ffff-ffff-ffffffffffff").String(), + TestId("00000000-0000-0000-0000-000000000000").Prev().String()) +} + +func TestSpanMsgPack(t *testing.T) { + span := Span{Id: TestId("33f25a1a-750a-471d-b5ba-fa59309d7d6f"), + SpanData: SpanData{ + Begin: 1234, + End: 5678, + Description: "getFileDescriptors", + Parents: []SpanId{}, + ProcessId: "testProcessId", + }} + mh := new(codec.MsgpackHandle) + mh.WriteExt = true + w := bytes.NewBuffer(make([]byte, 0, 2048)) + enc := codec.NewEncoder(w, mh) + err := enc.Encode(span) + if err != nil { + t.Fatal("Error encoding span as msgpack: " + err.Error()) + } + buf := w.Bytes() + fmt.Printf("span: %s\n", hex.EncodeToString(buf)) + mh = new(codec.MsgpackHandle) + mh.WriteExt = true + dec := codec.NewDecoder(bytes.NewReader(buf), mh) + var span2 Span + err = dec.Decode(&span2) + if err != nil { + t.Fatal("Failed to reverse msgpack encoding for " + span.String()) + } + ExpectSpansEqual(t, &span, &span2) +} diff --git a/htrace-htraced/go/src/org/apache/htrace/common/test_util.go b/htrace-htraced/go/src/org/apache/htrace/common/test_util.go index 871c847..ec9151b 100644 --- a/htrace-htraced/go/src/org/apache/htrace/common/test_util.go +++ b/htrace-htraced/go/src/org/apache/htrace/common/test_util.go @@ -72,3 +72,12 @@ func ExpectStrEqual(t *testing.T, expect string, actual string) { func ExpectSpansEqual(t *testing.T, spanA *Span, spanB *Span) { ExpectStrEqual(t, string(spanA.ToJson()), string(spanB.ToJson())) } + +func TestId(str string) SpanId { + var spanId SpanId + err := spanId.FromString(str) + if err != nil { + panic(err.Error()) + } + return spanId +} diff --git a/htrace-htraced/go/src/org/apache/htrace/htrace/cmd.go b/htrace-htraced/go/src/org/apache/htrace/htrace/cmd.go index 38cdb58..8fd7067 100644 --- a/htrace-htraced/go/src/org/apache/htrace/htrace/cmd.go +++ b/htrace-htraced/go/src/org/apache/htrace/htrace/cmd.go @@ -63,10 +63,10 @@ func main() { version := app.Command("version", "Print the version of this program.") serverInfo := app.Command("serverInfo", "Print information retrieved from an htraced server.") findSpan := app.Command("findSpan", "Print information about a trace span with a given ID.") - findSpanId := findSpan.Arg("id", "Span ID to find. Example: 0x123456789abcdef").Required().Uint64() + findSpanId := findSpan.Arg("id", "Span ID to find. Example: be305e54-4534-2110-a0b2-e06b9effe112").Required().String() findChildren := app.Command("findChildren", "Print out the span IDs that are children of a given span ID.") - parentSpanId := findChildren.Arg("id", "Span ID to print children for. Example: 0x123456789abcdef"). - Required().Uint64() + parentSpanId := findChildren.Arg("id", "Span ID to print children for. Example: be305e54-4534-2110-a0b2-e06b9effe112"). + Required().String() childLim := findChildren.Flag("lim", "Maximum number of child IDs to print.").Default("20").Int() loadFile := app.Command("loadFile", "Write whitespace-separated JSON spans from a file to the server.") loadFilePath := loadFile.Arg("path", @@ -123,9 +123,13 @@ func main() { case serverInfo.FullCommand(): os.Exit(printServerInfo(hcl)) case findSpan.FullCommand(): - os.Exit(doFindSpan(hcl, common.SpanId(*findSpanId))) + var id *common.SpanId + id.FromString(*findSpanId) + os.Exit(doFindSpan(hcl, *id)) case findChildren.FullCommand(): - os.Exit(doFindChildren(hcl, common.SpanId(*parentSpanId), *childLim)) + var id *common.SpanId + id.FromString(*parentSpanId) + os.Exit(doFindChildren(hcl, *id, *childLim)) case loadJson.FullCommand(): os.Exit(doLoadSpanJson(hcl, *loadJsonArg)) case loadFile.FullCommand(): diff --git a/htrace-htraced/go/src/org/apache/htrace/htrace/file_test.go b/htrace-htraced/go/src/org/apache/htrace/htrace/file_test.go index b6f9cac..3a86d0d 100644 --- a/htrace-htraced/go/src/org/apache/htrace/htrace/file_test.go +++ b/htrace-htraced/go/src/org/apache/htrace/htrace/file_test.go @@ -25,7 +25,6 @@ import ( "io/ioutil" "org/apache/htrace/common" "org/apache/htrace/conf" - "org/apache/htrace/test" "os" "strings" "testing" @@ -116,10 +115,10 @@ func TestFailureDeferringWriter(t *testing.T) { } func TestReadSpans(t *testing.T) { - SPAN_TEST_STR := `{"i":"bdd6d4ee48de59bf","s":"c0681027d3ea4928",` + + SPAN_TEST_STR := `{"a":"b9f2a1e0-7b6e-4f16-b0c2-b27303b20e79",` + `"b":1424736225037,"e":1424736225901,"d":"ClientNamenodeProtocol#getFileInfo",` + - `"r":"FsShell","p":["60538dfb4df91418"]} -{"i":"bdd6d4ee48de59bf","s":"60538dfb4df91418","b":1424736224969,` + + `"r":"FsShell","p":["3afebdc0-a13f-4feb-811c-c5c0e42d30b1"]} +{"a":"3afebdc0-a13f-4feb-811c-c5c0e42d30b1","b":1424736224969,` + `"e":1424736225960,"d":"getFileInfo","r":"FsShell","p":[],"n":{"path":"/"}} ` r := strings.NewReader(SPAN_TEST_STR) @@ -129,20 +128,18 @@ func TestReadSpans(t *testing.T) { } SPAN_TEST_EXPECTED := common.SpanSlice{ &common.Span{ - Id: test.SpanId("c0681027d3ea4928"), + Id: common.TestId("b9f2a1e0-7b6e-4f16-b0c2-b27303b20e79"), SpanData: common.SpanData{ - TraceId: test.SpanId("bdd6d4ee48de59bf"), Begin: 1424736225037, End: 1424736225901, Description: "ClientNamenodeProtocol#getFileInfo", ProcessId: "FsShell", - Parents: []common.SpanId{test.SpanId("60538dfb4df91418")}, + Parents: []common.SpanId{common.TestId("3afebdc0-a13f-4feb-811c-c5c0e42d30b1")}, }, }, &common.Span{ - Id: test.SpanId("60538dfb4df91418"), + Id: common.TestId("3afebdc0-a13f-4feb-811c-c5c0e42d30b1"), SpanData: common.SpanData{ - TraceId: test.SpanId("bdd6d4ee48de59bf"), Begin: 1424736224969, End: 1424736225960, Description: "getFileInfo", diff --git a/htrace-htraced/go/src/org/apache/htrace/htrace/graph.go b/htrace-htraced/go/src/org/apache/htrace/htrace/graph.go index dabf2df..024d973 100644 --- a/htrace-htraced/go/src/org/apache/htrace/htrace/graph.go +++ b/htrace-htraced/go/src/org/apache/htrace/htrace/graph.go @@ -64,32 +64,32 @@ func jsonSpanFileToDotFile(jsonFile string, dotFile string) error { // Create output in dotfile format from a set of spans. func spansToDot(spans common.SpanSlice, writer io.Writer) error { sort.Sort(spans) - idMap := make(map[common.SpanId]*common.Span) + idMap := make(map[[16]byte]*common.Span) for i := range spans { span := spans[i] - if idMap[span.Id] != nil { + if idMap[span.Id.ToArray()] != nil { fmt.Fprintf(os.Stderr, "There were multiple spans listed which "+ "had ID %s.\nFirst:%s\nOther:%s\n", span.Id.String(), - idMap[span.Id].ToJson(), span.ToJson()) + idMap[span.Id.ToArray()].ToJson(), span.ToJson()) } else { - idMap[span.Id] = span + idMap[span.Id.ToArray()] = span } } - childMap := make(map[common.SpanId]common.SpanSlice) + childMap := make(map[[16]byte]common.SpanSlice) for i := range spans { child := spans[i] for j := range child.Parents { - parent := idMap[child.Parents[j]] + parent := idMap[child.Parents[j].ToArray()] if parent == nil { fmt.Fprintf(os.Stderr, "Can't find parent id %s for %s\n", child.Parents[j].String(), child.ToJson()) } else { - children := childMap[parent.Id] + children := childMap[parent.Id.ToArray()] if children == nil { children = make(common.SpanSlice, 0) } children = append(children, child) - childMap[parent.Id] = children + childMap[parent.Id.ToArray()] = children } } } @@ -102,7 +102,7 @@ func spansToDot(spans common.SpanSlice, writer io.Writer) error { } // Write out the edges between nodes... the parent/children relationships for i := range spans { - children := childMap[spans[i].Id] + children := childMap[spans[i].Id.ToArray()] sort.Sort(children) if children != nil { for c := range children { diff --git a/htrace-htraced/go/src/org/apache/htrace/htrace/graph_test.go b/htrace-htraced/go/src/org/apache/htrace/htrace/graph_test.go index 8698a98..d6319f3 100644 --- a/htrace-htraced/go/src/org/apache/htrace/htrace/graph_test.go +++ b/htrace-htraced/go/src/org/apache/htrace/htrace/graph_test.go @@ -22,16 +22,14 @@ package main import ( "bytes" "org/apache/htrace/common" - "org/apache/htrace/test" "testing" ) func TestSpansToDot(t *testing.T) { TEST_SPANS := common.SpanSlice{ &common.Span{ - Id: test.SpanId("6af3cc058e5d829d"), + Id: common.TestId("814c8ee0-e798-4be3-a8af-00ac64adccb6"), SpanData: common.SpanData{ - TraceId: test.SpanId("0e4716fe911244de"), Begin: 1424813349020, End: 1424813349134, Description: "newDFSInputStream", @@ -43,25 +41,23 @@ func TestSpansToDot(t *testing.T) { }, }, &common.Span{ - Id: test.SpanId("75d16cc5b2c07d8a"), + Id: common.TestId("cf2d5de6-9645-4548-bc05-5d1e6024054c"), SpanData: common.SpanData{ - TraceId: test.SpanId("0e4716fe911244de"), Begin: 1424813349025, End: 1424813349133, Description: "getBlockLocations", ProcessId: "FsShell", - Parents: []common.SpanId{test.SpanId("6af3cc058e5d829d")}, + Parents: []common.SpanId{common.TestId("814c8ee0-e798-4be3-a8af-00ac64adccb6")}, }, }, &common.Span{ - Id: test.SpanId("e2c7273efb280a8c"), + Id: common.TestId("37623806-f9c6-4483-b834-b8ea5d6b4827"), SpanData: common.SpanData{ - TraceId: test.SpanId("0e4716fe911244de"), Begin: 1424813349027, End: 1424813349073, Description: "ClientNamenodeProtocol#getBlockLocations", ProcessId: "FsShell", - Parents: []common.SpanId{test.SpanId("75d16cc5b2c07d8a")}, + Parents: []common.SpanId{common.TestId("cf2d5de6-9645-4548-bc05-5d1e6024054c")}, }, }, } @@ -71,11 +67,11 @@ func TestSpansToDot(t *testing.T) { t.Fatalf("spansToDot failed: error %s\n", err.Error()) } EXPECTED_STR := `digraph spans { - "6af3cc058e5d829d" [label="newDFSInputStream"]; - "75d16cc5b2c07d8a" [label="getBlockLocations"]; - "e2c7273efb280a8c" [label="ClientNamenodeProtocol#getBlockLocations"]; - "6af3cc058e5d829d" -> "75d16cc5b2c07d8a"; - "75d16cc5b2c07d8a" -> "e2c7273efb280a8c"; + "37623806-f9c6-4483-b834-b8ea5d6b4827" [label="ClientNamenodeProtocol#getBlockLocations"]; + "814c8ee0-e798-4be3-a8af-00ac64adccb6" [label="newDFSInputStream"]; + "cf2d5de6-9645-4548-bc05-5d1e6024054c" [label="getBlockLocations"]; + "814c8ee0-e798-4be3-a8af-00ac64adccb6" -> "cf2d5de6-9645-4548-bc05-5d1e6024054c"; + "cf2d5de6-9645-4548-bc05-5d1e6024054c" -> "37623806-f9c6-4483-b834-b8ea5d6b4827"; } ` if w.String() != EXPECTED_STR { diff --git a/htrace-htraced/go/src/org/apache/htrace/htraced/client_test.go b/htrace-htraced/go/src/org/apache/htrace/htraced/client_test.go index 218c1c8..02a00f3 100644 --- a/htrace-htraced/go/src/org/apache/htrace/htraced/client_test.go +++ b/htrace-htraced/go/src/org/apache/htrace/htraced/client_test.go @@ -121,7 +121,7 @@ func TestClientOperations(t *testing.T) { t.Fatalf("FindChildren(%s) returned an invalid number of "+ "children: expected %d, got %d\n", parentId, 1, len(children)) } - if children[0] != childSpan.Id { + if !children[0].Equal(childSpan.Id) { t.Fatalf("FindChildren(%s) returned an invalid child id: expected %s, "+ " got %s\n", parentId, childSpan.Id, children[0]) } diff --git a/htrace-htraced/go/src/org/apache/htrace/htraced/datastore.go b/htrace-htraced/go/src/org/apache/htrace/htraced/datastore.go index 35c7dad..6669047 100644 --- a/htrace-htraced/go/src/org/apache/htrace/htraced/datastore.go +++ b/htrace-htraced/go/src/org/apache/htrace/htraced/datastore.go @@ -22,6 +22,7 @@ package main import ( "bytes" "encoding/gob" + "encoding/hex" "errors" "fmt" "github.com/jmhodges/levigo" @@ -68,6 +69,7 @@ const CURRENT_LAYOUT_VERSION = 2 var EMPTY_BYTE_BUF []byte = []byte{} const VERSION_KEY = 'v' + const SPAN_ID_INDEX_PREFIX = 's' const BEGIN_TIME_INDEX_PREFIX = 'b' const END_TIME_INDEX_PREFIX = 'e' @@ -90,56 +92,6 @@ func (stats *Statistics) Copy() *Statistics { } } -// Translate an 8-byte value into a leveldb key. -func makeKey(tag byte, val uint64) []byte { - return []byte{ - tag, - byte(0xff & (val >> 56)), - byte(0xff & (val >> 48)), - byte(0xff & (val >> 40)), - byte(0xff & (val >> 32)), - byte(0xff & (val >> 24)), - byte(0xff & (val >> 16)), - byte(0xff & (val >> 8)), - byte(0xff & (val >> 0)), - } -} - -func keyToInt(key []byte) uint64 { - var id uint64 - id = (uint64(key[0]) << 56) | - (uint64(key[1]) << 48) | - (uint64(key[2]) << 40) | - (uint64(key[3]) << 32) | - (uint64(key[4]) << 24) | - (uint64(key[5]) << 16) | - (uint64(key[6]) << 8) | - (uint64(key[7]) << 0) - return id -} - -func makeSecondaryKey(tag byte, fir uint64, sec uint64) []byte { - return []byte{ - tag, - byte(0xff & (fir >> 56)), - byte(0xff & (fir >> 48)), - byte(0xff & (fir >> 40)), - byte(0xff & (fir >> 32)), - byte(0xff & (fir >> 24)), - byte(0xff & (fir >> 16)), - byte(0xff & (fir >> 8)), - byte(0xff & (fir >> 0)), - byte(0xff & (sec >> 56)), - byte(0xff & (sec >> 48)), - byte(0xff & (sec >> 40)), - byte(0xff & (sec >> 32)), - byte(0xff & (sec >> 24)), - byte(0xff & (sec >> 16)), - byte(0xff & (sec >> 8)), - byte(0xff & (sec >> 0)), - } -} - // A single directory containing a levelDB instance. type shard struct { // The data store that this shard is part of @@ -186,6 +138,18 @@ func s2u64(val int64) uint64 { return ret } +func u64toSlice(val uint64) []byte { + return []byte{ + byte(0xff & (val >> 56)), + byte(0xff & (val >> 48)), + byte(0xff & (val >> 40)), + byte(0xff & (val >> 32)), + byte(0xff & (val >> 24)), + byte(0xff & (val >> 16)), + byte(0xff & (val >> 8)), + byte(0xff & (val >> 0))} +} + func (shd *shard) writeSpan(span *common.Span) error { batch := levigo.NewWriteBatch() defer batch.Close() @@ -197,21 +161,27 @@ func (shd *shard) writeSpan(span *common.Span) error { if err != nil { return err } - batch.Put(makeKey(SPAN_ID_INDEX_PREFIX, span.Id.Val()), spanDataBuf.Bytes()) + primaryKey := + append([]byte{SPAN_ID_INDEX_PREFIX}, span.Id.Val()...) + batch.Put(primaryKey, spanDataBuf.Bytes()) // Add this to the parent index. for parentIdx := range span.Parents { - batch.Put(makeSecondaryKey(PARENT_ID_INDEX_PREFIX, - span.Parents[parentIdx].Val(), span.Id.Val()), EMPTY_BYTE_BUF) + key := append(append([]byte{PARENT_ID_INDEX_PREFIX}, + span.Parents[parentIdx].Val()...), span.Id.Val()...) + batch.Put(key, EMPTY_BYTE_BUF) } // Add to the other secondary indices. - batch.Put(makeSecondaryKey(BEGIN_TIME_INDEX_PREFIX, s2u64(span.Begin), - span.Id.Val()), EMPTY_BYTE_BUF) - batch.Put(makeSecondaryKey(END_TIME_INDEX_PREFIX, s2u64(span.End), - span.Id.Val()), EMPTY_BYTE_BUF) - batch.Put(makeSecondaryKey(DURATION_INDEX_PREFIX, s2u64(span.Duration()), - span.Id.Val()), EMPTY_BYTE_BUF) + beginTimeKey := append(append([]byte{BEGIN_TIME_INDEX_PREFIX}, + u64toSlice(s2u64(span.Begin))...), span.Id.Val()...) + batch.Put(beginTimeKey, EMPTY_BYTE_BUF) + endTimeKey := append(append([]byte{END_TIME_INDEX_PREFIX}, + u64toSlice(s2u64(span.End))...), span.Id.Val()...) + batch.Put(endTimeKey, EMPTY_BYTE_BUF) + durationKey := append(append([]byte{DURATION_INDEX_PREFIX}, + u64toSlice(s2u64(span.Duration()))...), span.Id.Val()...) + batch.Put(durationKey, EMPTY_BYTE_BUF) err = shd.ldb.Write(shd.store.writeOpts, batch) if err != nil { @@ -226,7 +196,7 @@ func (shd *shard) writeSpan(span *common.Span) error { func (shd *shard) FindChildren(sid common.SpanId, childIds []common.SpanId, lim int32) ([]common.SpanId, int32, error) { - searchKey := makeKey('p', sid.Val()) + searchKey := append([]byte{PARENT_ID_INDEX_PREFIX}, sid.Val()...) iter := shd.ldb.NewIterator(shd.store.readOpts) defer iter.Close() iter.Seek(searchKey) @@ -241,7 +211,7 @@ func (shd *shard) FindChildren(sid common.SpanId, childIds []common.SpanId, if !bytes.HasPrefix(key, searchKey) { break } - id := common.SpanId(keyToInt(key[9:])) + id := common.SpanId(key[17:]) childIds = append(childIds, id) lim-- iter.Next() @@ -462,7 +432,7 @@ func (store *dataStore) Close() { // Get the index of the shard which stores the given spanId. func (store *dataStore) getShardIndex(sid common.SpanId) int { - return int(sid.Val() % uint64(len(store.shards))) + return int(sid.Hash32() % uint32(len(store.shards))) } func (store *dataStore) WriteSpan(span *common.Span) { @@ -475,7 +445,8 @@ func (store *dataStore) FindSpan(sid common.SpanId) *common.Span { func (shd *shard) FindSpan(sid common.SpanId) *common.Span { lg := shd.store.lg - buf, err := shd.ldb.Get(shd.store.readOpts, makeKey('s', sid.Val())) + primaryKey := append([]byte{SPAN_ID_INDEX_PREFIX}, sid.Val()...) + buf, err := shd.ldb.Get(shd.store.readOpts, primaryKey) if err != nil { if strings.Index(err.Error(), "NotFound:") != -1 { return nil @@ -541,8 +512,7 @@ func (store *dataStore) FindChildren(sid common.SpanId, lim int32) []common.Span type predicateData struct { *common.Predicate - uintKey uint64 - strKey string + key []byte } func loadPredicateData(pred *common.Predicate) (*predicateData, error) { @@ -558,11 +528,11 @@ func loadPredicateData(pred *common.Predicate) (*predicateData, error) { return nil, errors.New(fmt.Sprintf("Unable to parse span id '%s': %s", pred.Val, err.Error())) } - p.uintKey = id.Val() + p.key = id.Val() break case common.DESCRIPTION: // Any string is valid for a description. - p.strKey = pred.Val + p.key = []byte(pred.Val) break case common.BEGIN_TIME, common.END_TIME, common.DURATION: // Parse a base-10 signed numeric field. @@ -571,11 +541,11 @@ func loadPredicateData(pred *common.Predicate) (*predicateData, error) { return nil, errors.New(fmt.Sprintf("Unable to parse %s '%s': %s", pred.Field, pred.Val, err.Error())) } - p.uintKey = s2u64(v) + p.key = u64toSlice(s2u64(v)) break case common.PROCESS_ID: // Any string is valid for a process ID. - p.strKey = pred.Val + p.key = []byte(pred.Val) break default: return nil, errors.New(fmt.Sprintf("Unknown field %s", pred.Field)) @@ -626,22 +596,22 @@ func (pred *predicateData) fieldIsNumeric() bool { } // Get the values that this predicate cares about for a given span. -func (pred *predicateData) extractRelevantSpanData(span *common.Span) (uint64, string) { +func (pred *predicateData) extractRelevantSpanData(span *common.Span) []byte { switch pred.Field { case common.SPAN_ID: - return span.Id.Val(), "" + return span.Id.Val() case common.DESCRIPTION: - return 0, span.Description + return []byte(span.Description) case common.BEGIN_TIME: - return s2u64(span.Begin), "" + return u64toSlice(s2u64(span.Begin)) case common.END_TIME: - return s2u64(span.End), "" + return u64toSlice(s2u64(span.End)) case common.DURATION: - return s2u64(span.Duration()), "" + return u64toSlice(s2u64(span.Duration())) case common.PROCESS_ID: - return 0, span.ProcessId + return []byte(span.ProcessId) default: - panic(fmt.Sprintf("Field type %s isn't a 64-bit integer.", pred.Field)) + panic(fmt.Sprintf("Unknown field type %s.", pred.Field)) } } @@ -656,56 +626,33 @@ func (pred *predicateData) spanPtrIsBefore(a *common.Span, b *common.Span) bool return true } // Compare the spans according to this predicate. - aInt, aStr := pred.extractRelevantSpanData(a) - bInt, bStr := pred.extractRelevantSpanData(b) - if pred.fieldIsNumeric() { - if pred.Op.IsDescending() { - return aInt > bInt - } else { - return aInt < bInt - } + aVal := pred.extractRelevantSpanData(a) + bVal := pred.extractRelevantSpanData(b) + cmp := bytes.Compare(aVal, bVal) + if pred.Op.IsDescending() { + return cmp > 0 } else { - if pred.Op.IsDescending() { - return aStr > bStr - } else { - return aStr < bStr - } + return cmp < 0 } } // Returns true if the predicate is satisfied by the given span. func (pred *predicateData) satisfiedBy(span *common.Span) bool { - intVal, strVal := pred.extractRelevantSpanData(span) - if pred.fieldIsNumeric() { - switch pred.Op { - case common.EQUALS: - return intVal == pred.uintKey - case common.LESS_THAN_OR_EQUALS: - return intVal <= pred.uintKey - case common.GREATER_THAN_OR_EQUALS: - return intVal >= pred.uintKey - case common.GREATER_THAN: - return intVal > pred.uintKey - default: - panic(fmt.Sprintf("unknown Op type %s should have been caught "+ - "during normalization", pred.Op)) - } - } else { - switch pred.Op { - case common.CONTAINS: - return strings.Contains(strVal, pred.strKey) - case common.EQUALS: - return strVal == pred.strKey - case common.LESS_THAN_OR_EQUALS: - return strVal <= pred.strKey - case common.GREATER_THAN_OR_EQUALS: - return strVal >= pred.strKey - case common.GREATER_THAN: - return strVal > pred.strKey - default: - panic(fmt.Sprintf("unknown Op type %s should have been caught "+ - "during normalization", pred.Op)) - } + val := pred.extractRelevantSpanData(span) + switch pred.Op { + case common.CONTAINS: + return bytes.Contains(val, pred.key) + case common.EQUALS: + return bytes.Equal(val, pred.key) + case common.LESS_THAN_OR_EQUALS: + return bytes.Compare(val, pred.key) <= 0 + case common.GREATER_THAN_OR_EQUALS: + return bytes.Compare(val, pred.key) >= 0 + case common.GREATER_THAN: + return bytes.Compare(val, pred.key) > 0 + default: + panic(fmt.Sprintf("unknown Op type %s should have been caught "+ + "during normalization", pred.Op)) } } @@ -746,7 +693,7 @@ func (pred *predicateData) createSource(store *dataStore, prev *common.Span) (*s // organized as [type-code][8b-secondary-key][8b-span-id], elements // with the same secondary index field are ordered by span ID. So we // create a 17-byte key incorporating the span ID from 'prev.' - var startId common.SpanId + startId := common.INVALID_SPAN_ID switch pred.Op { case common.EQUALS: if pred.Field == common.SPAN_ID { @@ -759,17 +706,17 @@ func (pred *predicateData) createSource(store *dataStore, prev *common.Span) (*s lg.Debugf("Attempted to use a continuation token with an EQUALS "+ "SPAN_ID query. %s. Setting search id = 0", pred.Predicate.String()) - startId = 0 + startId = common.INVALID_SPAN_ID } else { // When doing an EQUALS search on a secondary index, the // results are sorted by span id. - startId = prev.Id + 1 + startId = prev.Id.Next() } case common.LESS_THAN_OR_EQUALS: // Subtract one from the previous span id. Since the previous // start ID will never be 0 (0 is an illegal span id), we'll never // wrap around when doing this. - startId = prev.Id - 1 + startId = prev.Id.Prev() case common.GREATER_THAN_OR_EQUALS: // We can't add one to the span id, since the previous span ID // might be the maximum value. So just switch over to using @@ -785,21 +732,22 @@ func (pred *predicateData) createSource(store *dataStore, prev *common.Span) (*s panic(str) } if pred.Field == common.SPAN_ID { - pred.uintKey = uint64(startId) - searchKey = makeKey(src.keyPrefix, uint64(startId)) + pred.key = startId.Val() + searchKey = append([]byte{src.keyPrefix}, startId.Val()...) } else { // Start where the previous query left off. This means adjusting // our uintKey. - pred.uintKey, _ = pred.extractRelevantSpanData(prev) - searchKey = makeSecondaryKey(src.keyPrefix, pred.uintKey, uint64(startId)) + pred.key = pred.extractRelevantSpanData(prev) + searchKey = append(append([]byte{src.keyPrefix}, pred.key...), + startId.Val()...) } if lg.TraceEnabled() { lg.Tracef("Handling continuation token %s for %s. startId=%d, "+ - "pred.uintKey=%d\n", prev, pred.Predicate.String(), startId, - pred.uintKey) + "pred.uintKey=%s\n", prev, pred.Predicate.String(), startId, + hex.EncodeToString(pred.key)) } } else { - searchKey = makeKey(src.keyPrefix, pred.uintKey) + searchKey = append([]byte{src.keyPrefix}, pred.key...) } for i := range src.iters { src.iters[i].Seek(searchKey) @@ -866,7 +814,7 @@ func (src *source) populateNextFromShard(shardIdx int) { var sid common.SpanId if src.keyPrefix == SPAN_ID_INDEX_PREFIX { // The span id maps to the span itself. - sid = common.SpanId(keyToInt(key[1:])) + sid = common.SpanId(key[1:17]) span, err = src.store.shards[shardIdx].decodeSpan(sid, iter.Value()) if err != nil { lg.Debugf("Internal error decoding span %s in shard %d: %s\n", @@ -875,7 +823,7 @@ func (src *source) populateNextFromShard(shardIdx int) { } } else { // With a secondary index, we have to look up the span by id. - sid = common.SpanId(keyToInt(key[9:])) + sid = common.SpanId(key[9:25]) span = src.store.shards[shardIdx].FindSpan(sid) if span == nil { lg.Debugf("Internal error rehydrating span %s in shard %d\n", @@ -948,7 +896,7 @@ func (store *dataStore) obtainSource(preds *[]*predicateData, span *common.Span) // If there are no predicates that are indexed, read rows in order of span id. spanIdPred := common.Predicate{Op: common.GREATER_THAN_OR_EQUALS, Field: common.SPAN_ID, - Val: "0000000000000000", + Val: common.INVALID_SPAN_ID.String(), } spanIdPredData, err := loadPredicateData(&spanIdPred) if err != nil { diff --git a/htrace-htraced/go/src/org/apache/htrace/htraced/datastore_test.go b/htrace-htraced/go/src/org/apache/htrace/htraced/datastore_test.go index 4696547..34ed8ec 100644 --- a/htrace-htraced/go/src/org/apache/htrace/htraced/datastore_test.go +++ b/htrace-htraced/go/src/org/apache/htrace/htraced/datastore_test.go @@ -45,31 +45,28 @@ func TestCreateDatastore(t *testing.T) { } var SIMPLE_TEST_SPANS []common.Span = []common.Span{ - common.Span{Id: 1, + common.Span{Id: common.TestId("00000000-0000-0000-0000-000000000001"), SpanData: common.SpanData{ Begin: 123, End: 456, Description: "getFileDescriptors", - TraceId: 999, Parents: []common.SpanId{}, ProcessId: "firstd", }}, - common.Span{Id: 2, + common.Span{Id: common.TestId("00000000-0000-0000-0000-000000000002"), SpanData: common.SpanData{ Begin: 125, End: 200, Description: "openFd", - TraceId: 999, - Parents: []common.SpanId{1}, + Parents: []common.SpanId{common.TestId("00000000-0000-0000-0000-000000000001")}, ProcessId: "secondd", }}, - common.Span{Id: 3, + common.Span{Id: common.TestId("00000000-0000-0000-0000-000000000003"), SpanData: common.SpanData{ Begin: 200, End: 456, Description: "passFd", - TraceId: 999, - Parents: []common.SpanId{1}, + Parents: []common.SpanId{common.TestId("00000000-0000-0000-0000-000000000001")}, ProcessId: "thirdd", }}, } @@ -98,27 +95,27 @@ func TestDatastoreWriteAndRead(t *testing.T) { if ht.Store.GetStatistics().NumSpansWritten < uint64(len(SIMPLE_TEST_SPANS)) { t.Fatal() } - span := ht.Store.FindSpan(1) + span := ht.Store.FindSpan(common.TestId("00000000-0000-0000-0000-000000000001")) if span == nil { t.Fatal() } - if span.Id != 1 { + if !span.Id.Equal(common.TestId("00000000-0000-0000-0000-000000000001")) { t.Fatal() } common.ExpectSpansEqual(t, &SIMPLE_TEST_SPANS[0], span) - children := ht.Store.FindChildren(1, 1) + children := ht.Store.FindChildren(common.TestId("00000000-0000-0000-0000-000000000001"), 1) if len(children) != 1 { t.Fatalf("expected 1 child, but got %d\n", len(children)) } - children = ht.Store.FindChildren(1, 2) + children = ht.Store.FindChildren(common.TestId("00000000-0000-0000-0000-000000000001"), 2) if len(children) != 2 { t.Fatalf("expected 2 children, but got %d\n", len(children)) } sort.Sort(common.SpanIdSlice(children)) - if children[0] != 2 { + if !children[0].Equal(common.TestId("00000000-0000-0000-0000-000000000002")) { t.Fatal() } - if children[1] != 3 { + if !children[1].Equal(common.TestId("00000000-0000-0000-0000-000000000003")) { t.Fatal() } } @@ -258,7 +255,7 @@ func TestQueries3(t *testing.T) { common.Predicate{ Op: common.LESS_THAN_OR_EQUALS, Field: common.SPAN_ID, - Val: "0", + Val: common.TestId("00000000-0000-0000-0000-000000000000").String(), }, }, Lim: 200, @@ -269,7 +266,7 @@ func TestQueries3(t *testing.T) { common.Predicate{ Op: common.LESS_THAN_OR_EQUALS, Field: common.SPAN_ID, - Val: "2", + Val: common.TestId("00000000-0000-0000-0000-000000000002").String(), }, }, Lim: 200, @@ -477,7 +474,7 @@ func TestQueriesWithContinuationTokens1(t *testing.T) { common.Predicate{ Op: common.EQUALS, Field: common.SPAN_ID, - Val: "1", + Val: common.TestId("00000000-0000-0000-0000-000000000001").String(), }, }, Lim: 100, @@ -491,7 +488,7 @@ func TestQueriesWithContinuationTokens1(t *testing.T) { common.Predicate{ Op: common.LESS_THAN_OR_EQUALS, Field: common.SPAN_ID, - Val: "2", + Val: common.TestId("00000000-0000-0000-0000-000000000002").String(), }, }, Lim: 100, diff --git a/htrace-htraced/go/src/org/apache/htrace/htraced/hrpc.go b/htrace-htraced/go/src/org/apache/htrace/htraced/hrpc.go index a53380e..48d54d8 100644 --- a/htrace-htraced/go/src/org/apache/htrace/htraced/hrpc.go +++ b/htrace-htraced/go/src/org/apache/htrace/htraced/hrpc.go @@ -193,6 +193,10 @@ func (hand *HrpcHandler) WriteSpans(req *common.WriteSpansReq, "defaultPid = %s\n", len(req.Spans), req.DefaultPid) for i := range req.Spans { span := req.Spans[i] + spanIdProblem := span.Id.FindProblem() + if spanIdProblem != "" { + return errors.New(fmt.Sprintf("Invalid span ID: %s", spanIdProblem)) + } if span.ProcessId == "" { span.ProcessId = req.DefaultPid } diff --git a/htrace-htraced/go/src/org/apache/htrace/htraced/rest.go b/htrace-htraced/go/src/org/apache/htrace/htraced/rest.go index ee62642..3afe042 100644 --- a/htrace-htraced/go/src/org/apache/htrace/htraced/rest.go +++ b/htrace-htraced/go/src/org/apache/htrace/htraced/rest.go @@ -76,14 +76,15 @@ type dataStoreHandler struct { func (hand *dataStoreHandler) parseSid(w http.ResponseWriter, str string) (common.SpanId, bool) { - val, err := strconv.ParseUint(str, 16, 64) + var id common.SpanId + err := id.FromString(str) if err != nil { writeError(hand.lg, w, http.StatusBadRequest, fmt.Sprintf("Failed to parse span ID %s: %s", str, err.Error())) w.Write([]byte("Error parsing : " + err.Error())) - return 0, false + return common.INVALID_SPAN_ID, false } - return common.SpanId(val), true + return id, true } func (hand *dataStoreHandler) getReqField32(fieldName string, w http.ResponseWriter, diff --git a/htrace-htraced/go/src/org/apache/htrace/test/random.go b/htrace-htraced/go/src/org/apache/htrace/test/random.go index d10e2f9..3f0c428 100644 --- a/htrace-htraced/go/src/org/apache/htrace/test/random.go +++ b/htrace-htraced/go/src/org/apache/htrace/test/random.go @@ -38,6 +38,15 @@ func NonZeroRand64(rnd *rand.Rand) int64 { } } +func NonZeroRandSpanId(rnd *rand.Rand) common.SpanId { + var id common.SpanId + id = make([]byte, 16) + for i := 0; i < len(id); i++ { + id[i] = byte(rnd.Intn(0x100)) + } + return id +} + func NonZeroRand32(rnd *rand.Rand) int32 { for { r := rnd.Int31() @@ -60,12 +69,11 @@ func NewRandomSpan(rnd *rand.Rand, potentialParents []*common.Span) *common.Span parents = []common.SpanId{potentialParents[parentIdx].Id} } } - return &common.Span{Id: common.SpanId(NonZeroRand64(rnd)), + return &common.Span{Id: NonZeroRandSpanId(rnd), SpanData: common.SpanData{ Begin: NonZeroRand64(rnd), End: NonZeroRand64(rnd), Description: "getFileDescriptors", - TraceId: common.SpanId(NonZeroRand64(rnd)), Parents: parents, ProcessId: fmt.Sprintf("process%d", NonZeroRand32(rnd)), }} diff --git a/htrace-htraced/go/src/org/apache/htrace/test/util.go b/htrace-htraced/go/src/org/apache/htrace/test/util.go deleted file mode 100644 index cc058e0..0000000 --- a/htrace-htraced/go/src/org/apache/htrace/test/util.go +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package test - -import ( - "org/apache/htrace/common" -) - -func SpanId(str string) common.SpanId { - var spanId common.SpanId - err := spanId.FromString(str) - if err != nil { - panic(err.Error()) - } - return spanId -} diff --git a/htrace-htraced/src/test/java/org/apache/htrace/impl/TestHTracedRESTReceiver.java b/htrace-htraced/src/test/java/org/apache/htrace/impl/TestHTracedRESTReceiver.java index a6faa02..515a8c4 100644 --- a/htrace-htraced/src/test/java/org/apache/htrace/impl/TestHTracedRESTReceiver.java +++ b/htrace-htraced/src/test/java/org/apache/htrace/impl/TestHTracedRESTReceiver.java @@ -28,6 +28,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.htrace.HTraceConfiguration; import org.apache.htrace.Span; +import org.apache.htrace.SpanId; import org.apache.htrace.util.DataDir; import org.apache.htrace.util.HTracedProcess; import org.apache.htrace.util.TestUtil; @@ -126,8 +127,8 @@ public class TestHTracedRESTReceiver { Span spans[] = new Span[NUM_SPANS]; for (int i = 0; i < NUM_SPANS; i++) { MilliSpan.Builder builder = new MilliSpan.Builder(). - parents(new long[]{1L}). - spanId(i); + parents(new SpanId[] { new SpanId(1L, 1L) }). + spanId(new SpanId(1L, i)); if (i == NUM_SPANS - 1) { builder.processId("specialPid"); } @@ -150,7 +151,8 @@ public class TestHTracedRESTReceiver { for (int i = 0; i < NUM_SPANS; i++) { // This is what the REST server expects when querying for a // span id. - String findSpan = String.format("span/%016x", i); + String findSpan = String.format("span/%s", + new SpanId(1L, i).toString()); ContentResponse response = http.GET(restServerUrl + findSpan); String content = processGET(response); @@ -160,7 +162,8 @@ public class TestHTracedRESTReceiver { } LOG.info("Got " + content + " for span " + i); MilliSpan dspan = MilliSpan.fromJson(content); - assertEquals((long)i, dspan.getSpanId()); + assertEquals(new SpanId(1, i).toString(), + dspan.getSpanId().toString()); // Every span should have the process ID we set in the // configuration... except for the last span, which had // a custom value set. diff --git a/htrace-webapp/src/main/web/app/span.js b/htrace-webapp/src/main/web/app/span.js index d29f020..3313459 100644 --- a/htrace-webapp/src/main/web/app/span.js +++ b/htrace-webapp/src/main/web/app/span.js @@ -20,7 +20,7 @@ var htrace = htrace || {}; // The invalid span ID, which is all zeroes. -htrace.INVALID_SPAN_ID = "0000000000000000"; +htrace.INVALID_SPAN_ID = "00000000-0000-0000-0000-000000000000"; // Convert an array of htrace.Span models into a comma-separated string. htrace.spanModelsToString = function(spans) { @@ -81,8 +81,7 @@ htrace.Span = Backbone.Model.extend({ // forced to be numbers. parse: function(response, options) { var span = {}; - this.set("spanId", response.s ? response.s : htrace.INVALID_SPAN_ID); - this.set("traceId", response.i ? response.i : htrace.INVALID_SPAN_ID); + this.set("spanId", response.a ? response.s : htrace.INVALID_SPAN_ID); this.set("processId", response.r ? response.r : ""); this.set("parents", response.p ? response.p : []); this.set("description", response.d ? response.d : ""); @@ -120,10 +119,7 @@ htrace.Span = Backbone.Model.extend({ unparse: function() { var obj = { }; if (!(this.get("spanId") === htrace.INVALID_SPAN_ID)) { - obj.s = this.get("spanId"); - } - if (!(this.get("traceId") === htrace.INVALID_SPAN_ID)) { - obj.i = this.get("traceId"); + obj.a = this.get("spanId"); } if (!(this.get("processId") === "")) { obj.r = this.get("processId"); diff --git a/htrace-webapp/src/main/web/app/string.js b/htrace-webapp/src/main/web/app/string.js index b0dfb74..4d75cf6 100644 --- a/htrace-webapp/src/main/web/app/string.js +++ b/htrace-webapp/src/main/web/app/string.js @@ -47,20 +47,34 @@ htrace.dateToString = function(val) { return moment.utc(val).format("YYYY-MM-DDTHH:mm:ss,SSS"); }; -// Normalize a span ID into the format the server expects to see -// (no leading 0x). -htrace.normalizeSpanId = function(str) { - // Strip off the 0x prefix, if there is one. - if (str.indexOf("0x") == 0) { - str = str.substring(2); - } - if (str.length != 16) { - throw "The length of '" + str + "' was " + str.length + - ", but span IDs must be 16 characters long."; - } - if (str.search(/[^0-9a-fA-F]/) != -1) { +htrace.checkHexPortion = function(str, i, j) { + if (str.substring(i, j).search(/[^0-9a-fA-F]/) != -1) { throw "Span IDs must contain only hexadecimal digits, but '" + str + "' contained invalid characters."; } +} + +htrace.checkDash = function(str, i) { + if (str[i] != '-') { + throw "Span ID lacked a dash at position " + i; + } +} + +// Normalize a span ID into the format the server expects to see-- +// i.e. something like 00000000-0000-0000-0000-000000000000. +htrace.normalizeSpanId = function(str) { + if (str.length != 36) { + throw "The length of '" + str + "' was " + str.length + + ", but span IDs must be 36 characters long."; + } + htrace.checkHexPortion(str, 0, 8) + htrace.checkDash(str, 8) + htrace.checkHexPortion(str, 9, 13) + htrace.checkDash(str, 13) + htrace.checkHexPortion(str, 14, 18) + htrace.checkDash(str, 18) + htrace.checkHexPortion(str, 19, 23) + htrace.checkDash(str, 23) + htrace.checkHexPortion(str, 24, 35) return str; }; diff --git a/htrace-zipkin/src/main/java/org/apache/htrace/zipkin/HTraceToZipkinConverter.java b/htrace-zipkin/src/main/java/org/apache/htrace/zipkin/HTraceToZipkinConverter.java index 4154d92..871cf47 100644 --- a/htrace-zipkin/src/main/java/org/apache/htrace/zipkin/HTraceToZipkinConverter.java +++ b/htrace-zipkin/src/main/java/org/apache/htrace/zipkin/HTraceToZipkinConverter.java @@ -110,15 +110,15 @@ public class HTraceToZipkinConverter { Endpoint ep = new Endpoint(ipv4Address, (short) getPort(serviceName), serviceName); List annotationList = createZipkinAnnotations(hTraceSpan, ep); List binaryAnnotationList = createZipkinBinaryAnnotations(hTraceSpan, ep); - zipkinSpan.setTrace_id(hTraceSpan.getTraceId()); + zipkinSpan.setTrace_id(hTraceSpan.getSpanId().getHigh()); if (hTraceSpan.getParents().length > 0) { if (hTraceSpan.getParents().length > 1) { LOG.error("zipkin doesn't support spans with multiple parents. Omitting " + "other parents for " + hTraceSpan); } - zipkinSpan.setParent_id(hTraceSpan.getParents()[0]); + zipkinSpan.setParent_id(hTraceSpan.getParents()[0].getLow()); } - zipkinSpan.setId(hTraceSpan.getSpanId()); + zipkinSpan.setId(hTraceSpan.getSpanId().getLow()); zipkinSpan.setName(hTraceSpan.getDescription()); zipkinSpan.setAnnotations(annotationList); zipkinSpan.setBinary_annotations(binaryAnnotationList); diff --git a/htrace-zipkin/src/test/java/org/apache/htrace/TestHTraceSpanToZipkinSpan.java b/htrace-zipkin/src/test/java/org/apache/htrace/TestHTraceSpanToZipkinSpan.java index de4a8cd..1241463 100644 --- a/htrace-zipkin/src/test/java/org/apache/htrace/TestHTraceSpanToZipkinSpan.java +++ b/htrace-zipkin/src/test/java/org/apache/htrace/TestHTraceSpanToZipkinSpan.java @@ -47,9 +47,8 @@ public class TestHTraceSpanToZipkinSpan { Span rootSpan = new MilliSpan.Builder(). description(ROOT_SPAN_DESC). - traceId(1). - parents(new long[] { } ). - spanId(100). + parents(new SpanId[] { } ). + spanId(new SpanId(100, 100)). processId("test"). begin(System.currentTimeMillis()). build(); @@ -74,8 +73,10 @@ public class TestHTraceSpanToZipkinSpan { String traceName = "testHTraceAnnotationTimestamp"; long startTime = System.currentTimeMillis() * 1000; Span ms = new MilliSpan.Builder(). - description(traceName).traceId(1).parents(new long[] { }). - spanId(2).processId(traceName).begin(System.currentTimeMillis()). + description(traceName).parents(new SpanId[] { }). + spanId(new SpanId(2L, 2L)). + processId(traceName). + begin(System.currentTimeMillis()). build(); Thread.sleep(500); @@ -116,17 +117,20 @@ public class TestHTraceSpanToZipkinSpan { @Test public void testHTraceDefaultPort() throws IOException { MilliSpan ms = new MilliSpan.Builder().description("test"). - traceId(1).parents(new long[] { 2 }). - spanId(3).processId("hmaster"). - begin(System.currentTimeMillis()).build(); + parents(new SpanId[] { new SpanId(2L, 2L) }). + spanId(new SpanId(2L, 3L)). + processId("hmaster"). + begin(System.currentTimeMillis()). + build(); com.twitter.zipkin.gen.Span zs = new HTraceToZipkinConverter(12345, (short) -1).convert(ms); for (com.twitter.zipkin.gen.Annotation annotation:zs.getAnnotations()) { assertEquals((short)60000, annotation.getHost().getPort()); } // make sure it's all lower cased - ms = new MilliSpan.Builder().description("test").traceId(1). - parents(new long[] {2}).spanId(3). + ms = new MilliSpan.Builder().description("test"). + parents(new SpanId[] {new SpanId(2, 2)}). + spanId(new SpanId(2, 3)). processId("HregIonServer"). begin(System.currentTimeMillis()).build(); zs = new HTraceToZipkinConverter(12345, (short) -1).convert(ms); @@ -136,13 +140,12 @@ public class TestHTraceSpanToZipkinSpan { } private void assertSpansAreEquivalent(Span s, com.twitter.zipkin.gen.Span zs) { - assertEquals(s.getTraceId(), zs.getTrace_id()); assertTrue("zipkin doesn't support multiple parents to a single span.", s.getParents().length <= 1); if (s.getParents().length == 1) { - assertEquals(s.getParents()[0], zs.getParent_id()); + assertEquals(s.getParents()[0].getLow(), zs.getParent_id()); } - assertEquals(s.getSpanId(), zs.getId()); + assertEquals(s.getSpanId().getLow(), zs.getId()); Assert.assertNotNull(zs.getAnnotations()); if (ROOT_SPAN_DESC.equals(zs.getName())) { assertEquals(5, zs.getAnnotations().size());// two start, two stop + one timeline annotation diff --git a/src/main/site/markdown/index.md b/src/main/site/markdown/index.md index 9b3034f..1ec9c45 100644 --- a/src/main/site/markdown/index.md +++ b/src/main/site/markdown/index.md @@ -184,13 +184,11 @@ returned will be a child of the current span, otherwise it will start a new trace in the current thread (it will be a `ProcessRootMilliSpan`). All of the other `startSpan()` methods take some parameter describing the parent span of the span to be created. The -versions that take a `TraceInfo` or a `long traceId` and `long -parentId` will mostly be used when continuing a trace over RPC. The -receiver of the RPC will check the message for the additional two -`longs` and will call `startSpan()` if they are attached. The last -`startSpan()` takes a `Span parent`. The result of `parent.child()` -will be used for the new span. `Span.child()` simply returns a span -that is a child of `this`. +version that takes a parent id will mostly be used when continuing a trace over +RPC. The receiver of the RPC will check the message for the 128-bit parent trace +ID and will call `startSpan()` if it is attached. The last `startSpan()` takes +a `Span parent`. The result of `parent.child()` will be used for the new span. +`Span.child()` simply returns a span that is a child of `this`. ###Span Receivers In order to use the tracing information consisting of spans,