HTRACE-31. more JSON serialization fixes (cmccabe)
diff --git a/htrace-core/pom.xml b/htrace-core/pom.xml
index 8ce720d..54c6c6c 100644
--- a/htrace-core/pom.xml
+++ b/htrace-core/pom.xml
@@ -61,8 +61,8 @@
<shadedPattern>org.apache.htrace.commons.logging</shadedPattern>
</relocation>
<relocation>
- <pattern>org.mortbay</pattern>
- <shadedPattern>org.apache.htrace.mortbay</shadedPattern>
+ <pattern>com.fasterxml.jackson.core</pattern>
+ <shadedPattern>org.apache.htrace.fasterxml.jackson.core</shadedPattern>
</relocation>
</relocations>
</configuration>
@@ -137,9 +137,14 @@
<artifactId>commons-logging</artifactId>
</dependency>
<dependency>
- <groupId>org.mortbay.jetty</groupId>
- <artifactId>jetty-util</artifactId>
- <version>6.1.26</version>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ <version>2.4.0</version>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ <version>2.4.0</version>
</dependency>
</dependencies>
</project>
diff --git a/htrace-core/src/go/src/org/apache/htrace/common/span.go b/htrace-core/src/go/src/org/apache/htrace/common/span.go
index 81fe0e8..540ba12 100644
--- a/htrace-core/src/go/src/org/apache/htrace/common/span.go
+++ b/htrace-core/src/go/src/org/apache/htrace/common/span.go
@@ -38,14 +38,14 @@
type TraceInfoMap map[string][]byte
type TimelineAnnotation struct {
- Time int64 `json:"time,string"`
- Msg string `json:"msg"`
+ Time int64 `json:"t"`
+ Msg string `json:"m"`
}
type SpanId int64
func (id SpanId) String() string {
- return fmt.Sprintf("%08x", id)
+ return fmt.Sprintf("%016x", id)
}
func (id SpanId) Val() int64 {
@@ -56,13 +56,18 @@
return []byte(`"` + fmt.Sprintf("%016x", uint64(id)) + `"`), nil
}
-func (id SpanId) UnMarshalJSON() ([]byte, error) {
- return []byte(`"` + strconv.FormatUint(uint64(id), 16) + `"`), nil
+func (id *SpanId) UnMarshalJSON(b []byte) error {
+ v, err := strconv.ParseUint(string(b), 16, 64)
+ if err != nil {
+ return err
+ }
+ *id = SpanId(v)
+ return nil
}
type SpanData struct {
- Begin int64 `json:"b,string"`
- End int64 `json:"e,string"`
+ Begin int64 `json:"b"`
+ End int64 `json:"e"`
Description string `json:"d"`
TraceId SpanId `json:"i"`
Parents []SpanId `json:"p"`
diff --git a/htrace-core/src/go/src/org/apache/htrace/common/span_test.go b/htrace-core/src/go/src/org/apache/htrace/common/span_test.go
index 1d098fc..f218b3a 100644
--- a/htrace-core/src/go/src/org/apache/htrace/common/span_test.go
+++ b/htrace-core/src/go/src/org/apache/htrace/common/span_test.go
@@ -35,7 +35,7 @@
ProcessId: "testProcessId",
}}
ExpectStrEqual(t,
- `{"s":"2000000000000000","b":"123","e":"456","d":"getFileDescriptors","i":"00000000000003e7","p":[],"r":"testProcessId"}`,
+ `{"s":"2000000000000000","b":123,"e":456,"d":"getFileDescriptors","i":"00000000000003e7","p":[],"r":"testProcessId"}`,
string(span.ToJson()))
}
@@ -61,6 +61,6 @@
},
}}
ExpectStrEqual(t,
- `{"s":"121f2e036d442000","b":"1234","e":"4567","d":"getFileDescriptors2","i":"00000000000003e7","p":[],"r":"testAnnotatedProcessId","t":[{"time":"7777","msg":"contactedServer"},{"time":"8888","msg":"passedFd"}]}`,
+ `{"s":"121f2e036d442000","b":1234,"e":4567,"d":"getFileDescriptors2","i":"00000000000003e7","p":[],"r":"testAnnotatedProcessId","t":[{"t":7777,"m":"contactedServer"},{"t":8888,"m":"passedFd"}]}`,
string(span.ToJson()))
}
diff --git a/htrace-core/src/go/src/org/apache/htrace/htrace/cmd.go b/htrace-core/src/go/src/org/apache/htrace/htrace/cmd.go
index d4bb253..9633ba3 100644
--- a/htrace-core/src/go/src/org/apache/htrace/htrace/cmd.go
+++ b/htrace-core/src/go/src/org/apache/htrace/htrace/cmd.go
@@ -90,7 +90,7 @@
// Print information about a trace span.
func doFindSpan(restAddr string, sid int64) int {
- buf, err := makeRestRequest(restAddr, fmt.Sprintf("findSid?sid=%d", sid))
+ buf, err := makeRestRequest(restAddr, fmt.Sprintf("findSid?sid=%016x", sid))
if err != nil {
fmt.Printf("%s\n", err.Error())
return 1
@@ -113,7 +113,7 @@
// Find information about the children of a span.
func doFindChildren(restAddr string, sid int64, lim int) int {
- buf, err := makeRestRequest(restAddr, fmt.Sprintf("findChildren?sid=%d&lim=%d", sid, lim))
+ buf, err := makeRestRequest(restAddr, fmt.Sprintf("findChildren?sid=%016x&lim=%d", sid, lim))
if err != nil {
fmt.Printf("%s\n", err.Error())
return 1
diff --git a/htrace-core/src/go/src/org/apache/htrace/htraced/rest.go b/htrace-core/src/go/src/org/apache/htrace/htraced/rest.go
index f0bb2c1..5b97313 100644
--- a/htrace-core/src/go/src/org/apache/htrace/htraced/rest.go
+++ b/htrace-core/src/go/src/org/apache/htrace/htraced/rest.go
@@ -55,13 +55,13 @@
w.Write([]byte("No " + fieldName + " specified."))
return -1, false
}
- val, err := strconv.ParseInt(str, 10, 64)
+ val, err := strconv.ParseUint(str, 16, 64)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Error parsing " + fieldName + ": " + err.Error()))
return -1, false
}
- return val, true
+ return int64(val), true
}
func (hand *dataStoreHandler) getReqField32(fieldName string, w http.ResponseWriter,
@@ -72,7 +72,7 @@
w.Write([]byte("No " + fieldName + " specified."))
return -1, false
}
- val, err := strconv.ParseInt(str, 10, 32)
+ val, err := strconv.ParseUint(str, 16, 32)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Error parsing " + fieldName + ": " + err.Error()))
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 50cec7c..b8af10d 100644
--- a/htrace-core/src/main/java/org/apache/htrace/Span.java
+++ b/htrace-core/src/main/java/org/apache/htrace/Span.java
@@ -16,6 +16,12 @@
*/
package org.apache.htrace;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+
+import java.io.IOException;
import java.util.List;
import java.util.Map;
@@ -27,6 +33,7 @@
* Spans form a tree structure with the parent relationship. The first span in a
* trace has no parent span.
*/
+@JsonSerialize(using = Span.SpanSerializer.class)
public interface Span {
public static final long ROOT_SPAN_ID = 0x74ace;
@@ -118,4 +125,45 @@
* Serialize to Json
*/
String toJson();
+
+ public static class SpanSerializer extends JsonSerializer<Span> {
+ @Override
+ public void serialize(Span span, JsonGenerator jgen, SerializerProvider provider)
+ throws IOException {
+ jgen.writeStartObject();
+ jgen.writeStringField("i", String.format("%016x", span.getTraceId()));
+ jgen.writeStringField("s", String.format("%016x", span.getSpanId()));
+ jgen.writeNumberField("b", span.getStartTimeMillis());
+ jgen.writeNumberField("e", span.getStopTimeMillis());
+ jgen.writeStringField("d", span.getDescription());
+ jgen.writeStringField("r", span.getProcessId());
+ jgen.writeArrayFieldStart("p");
+ if (span.getParentId() != ROOT_SPAN_ID) {
+ jgen.writeString(String.format("%016x", span.getParentId()));
+ }
+ jgen.writeEndArray();
+ Map<byte[], byte[]> traceInfoMap = span.getKVAnnotations();
+ if (!traceInfoMap.isEmpty()) {
+ jgen.writeObjectFieldStart("n");
+ for (Map.Entry<byte[], byte[]> e : traceInfoMap.entrySet()) {
+ jgen.writeStringField(new String(e.getKey(), "UTF-8"),
+ new String(e.getValue(), "UTF-8"));
+ }
+ jgen.writeEndObject();
+ }
+ List<TimelineAnnotation> timelineAnnotations =
+ span.getTimelineAnnotations();
+ if (!timelineAnnotations.isEmpty()) {
+ jgen.writeArrayFieldStart("t");
+ for (TimelineAnnotation tl : timelineAnnotations) {
+ jgen.writeStartObject();
+ jgen.writeNumberField("t", tl.getTime());
+ jgen.writeStringField("m", tl.getMessage());
+ jgen.writeEndObject();
+ }
+ jgen.writeEndArray();
+ }
+ jgen.writeEndObject();
+ }
+ }
}
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 b58839b..3932b79 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
@@ -16,15 +16,24 @@
*/
package org.apache.htrace.impl;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import org.apache.htrace.Span;
import org.apache.htrace.TimelineAnnotation;
import org.apache.htrace.Tracer;
-import org.mortbay.util.ajax.JSON;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.IOException;
+import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
-import java.util.LinkedHashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
@@ -33,12 +42,13 @@
* A Span implementation that stores its information in milliseconds since the
* epoch.
*/
+@JsonDeserialize(using = MilliSpan.MilliSpanDeserializer.class)
public class MilliSpan implements Span {
private static Random rand = new Random();
- private long start;
- private long stop;
+ private long begin;
+ private long end;
private final String description;
private final long traceId;
private final long parents[];
@@ -52,6 +62,94 @@
return new MilliSpan(description, traceId, spanId, rand.nextLong(), processId);
}
+ /**
+ * The public interface for constructing a MilliSpan.
+ */
+ public static class Builder {
+ private long begin;
+ private long end;
+ private String description;
+ private long traceId;
+ private long parents[];
+ private long spanId;
+ private Map<byte[], byte[]> traceInfo = null;
+ private String processId;
+ private List<TimelineAnnotation> timeline = null;
+
+ public Builder() {
+ }
+
+ public Builder begin(long begin) {
+ this.begin = begin;
+ return this;
+ }
+
+ public Builder end(long end) {
+ this.end = end;
+ return this;
+ }
+
+ public Builder description(String description) {
+ this.description = description;
+ return this;
+ }
+
+ public Builder traceId(long traceId) {
+ this.traceId = traceId;
+ return this;
+ }
+
+ public Builder parents(long parents[]) {
+ this.parents = parents;
+ return this;
+ }
+
+ public Builder parents(List<Long> parentList) {
+ long[] parents = new long[parentList.size()];
+ for (int i = 0; i < parentList.size(); i++) {
+ parents[i] = parentList.get(i).longValue();
+ }
+ this.parents = parents;
+ return this;
+ }
+
+ public Builder spanId(long spanId) {
+ this.spanId = spanId;
+ return this;
+ }
+
+ public Builder traceInfo(Map<byte[], byte[]> traceInfo) {
+ this.traceInfo = traceInfo.isEmpty() ? null : traceInfo;
+ return this;
+ }
+
+ public Builder processId(String processId) {
+ this.processId = processId;
+ return this;
+ }
+
+ public Builder timeline(List<TimelineAnnotation> timeline) {
+ this.timeline = timeline.isEmpty() ? null : timeline;
+ return this;
+ }
+
+ public MilliSpan build() {
+ return new MilliSpan(this);
+ }
+ }
+
+ private MilliSpan(Builder builder) {
+ 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;
+ this.processId = builder.processId;
+ this.timeline = builder.timeline;
+ }
+
public MilliSpan(String description, long traceId, long parentSpanId, long spanId, String processId) {
this.description = description;
this.traceId = traceId;
@@ -61,18 +159,18 @@
this.parents = new long[] { parentSpanId };
}
this.spanId = spanId;
- this.start = System.currentTimeMillis();
- this.stop = 0;
+ this.begin = System.currentTimeMillis();
+ this.end = 0;
this.processId = processId;
}
@Override
public synchronized void stop() {
- if (stop == 0) {
- if (start == 0)
+ if (end == 0) {
+ if (begin == 0)
throw new IllegalStateException("Span for " + description
+ " has not been started");
- stop = System.currentTimeMillis();
+ end = System.currentTimeMillis();
Tracer.getInstance().deliver(this);
}
}
@@ -83,16 +181,16 @@
@Override
public synchronized boolean isRunning() {
- return start != 0 && stop == 0;
+ return begin != 0 && end == 0;
}
@Override
public synchronized long getAccumulatedMillis() {
- if (start == 0)
+ if (begin == 0)
return 0;
- if (stop > 0)
- return stop - start;
- return currentTimeMillis() - start;
+ if (end > 0)
+ return end - begin;
+ return currentTimeMillis() - begin;
}
@Override
@@ -127,12 +225,12 @@
@Override
public long getStartTimeMillis() {
- return start;
+ return begin;
}
@Override
public long getStopTimeMillis() {
- return stop;
+ return end;
}
@Override
@@ -172,24 +270,62 @@
@Override
public String toJson() {
- Map<String, Object> values = new LinkedHashMap<String, Object>();
- values.put("i", String.format("%016x", traceId));
- values.put("s", String.format("%016x", spanId));
- String parentStrs[] = new String[parents.length];
- for (int parentIdx = 0; parentIdx < parents.length; parentIdx++) {
- parentStrs[parentIdx] = String.format("%016x", parents[parentIdx]);
+ StringWriter writer = new StringWriter();
+ ObjectMapper mapper = new ObjectMapper();
+ try {
+ mapper.writeValue(writer, this);
+ } catch (IOException e) {
+ // An IOException should not be possible when writing to a string.
+ throw new RuntimeException(e);
}
- values.put("p", parentStrs);
- values.put("r", processId);
- values.put("b", Long.toString(start));
- values.put("e", Long.toString(stop));
- values.put("d", description);
- if (timeline != null) {
- values.put("t", timeline);
+ return writer.toString();
+ }
+
+ public static class MilliSpanDeserializer
+ extends JsonDeserializer<MilliSpan> {
+ @Override
+ public MilliSpan deserialize(JsonParser jp, DeserializationContext ctxt)
+ throws IOException, JsonProcessingException {
+ JsonNode root = jp.getCodec().readTree(jp);
+ Builder builder = new Builder();
+ builder.begin(root.get("b").asLong()).
+ end(root.get("e").asLong()).
+ description(root.get("d").asText()).
+ traceId(Long.parseLong(root.get("i").asText(), 16)).
+ spanId(Long.parseLong(root.get("s").asText(), 16)).
+ processId(root.get("r").asText());
+ JsonNode parentsNode = root.get("p");
+ LinkedList<Long> parents = new LinkedList<Long>();
+ for (Iterator<JsonNode> iter = parentsNode.elements();
+ iter.hasNext(); ) {
+ JsonNode parentIdNode = iter.next();
+ parents.add(Long.parseLong(parentIdNode.asText(), 16));
+ }
+ builder.parents(parents);
+ JsonNode traceInfoNode = root.get("n");
+ if (traceInfoNode != null) {
+ HashMap<byte[], byte[]> traceInfo = new HashMap<byte[], byte[]>();
+ for (Iterator<String> iter = traceInfoNode.fieldNames();
+ iter.hasNext(); ) {
+ String field = iter.next();
+ traceInfo.put(field.getBytes("UTF-8"),
+ traceInfoNode.get(field).asText().getBytes("UTF-8"));
+ }
+ builder.traceInfo(traceInfo);
+ }
+ JsonNode timelineNode = root.get("t");
+ if (timelineNode != null) {
+ LinkedList<TimelineAnnotation> timeline =
+ new LinkedList<TimelineAnnotation>();
+ for (Iterator<JsonNode> iter = timelineNode.elements();
+ iter.hasNext(); ) {
+ JsonNode ann = iter.next();
+ timeline.add(new TimelineAnnotation(ann.get("t").asLong(),
+ ann.get("m").asText()));
+ }
+ builder.timeline(timeline);
+ }
+ return builder.build();
}
- if (traceInfo != null){
- values.put("n", traceInfo);
- }
- return JSON.toString(values);
}
}
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
new file mode 100644
index 0000000..908e74e
--- /dev/null
+++ b/htrace-core/src/test/java/org/apache/htrace/impl/TestMilliSpan.java
@@ -0,0 +1,120 @@
+/*
+ * 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.impl;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import static org.junit.Assert.assertEquals;
+
+import org.apache.htrace.Span;
+import org.apache.htrace.TimelineAnnotation;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+public class TestMilliSpan {
+ private void compareSpans(Span expected, Span got) throws Exception {
+ 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());
+ assertEquals(expected.getParentId(), got.getParentId());
+ Map<byte[], byte[]> expectedT = expected.getKVAnnotations();
+ Map<byte[], byte[]> gotT = got.getKVAnnotations();
+ if (expectedT == null) {
+ assertEquals(null, gotT);
+ } else {
+ assertEquals(expectedT.size(), gotT.size());
+ Map<String, String> expectedTMap = new HashMap<String, String>();
+ for (byte[] key : expectedT.keySet()) {
+ expectedTMap.put(new String(key, "UTF-8"),
+ new String(expectedT.get(key), "UTF-8"));
+ }
+ Map<String, String> gotTMap = new HashMap<String, String>();
+ for (byte[] key : gotT.keySet()) {
+ gotTMap.put(new String(key, "UTF-8"),
+ new String(gotT.get(key), "UTF-8"));
+ }
+ for (String key : expectedTMap.keySet()) {
+ assertEquals(expectedTMap.get(key), gotTMap.get(key));
+ }
+ }
+ List<TimelineAnnotation> expectedTimeline =
+ expected.getTimelineAnnotations();
+ List<TimelineAnnotation> gotTimeline =
+ got.getTimelineAnnotations();
+ if (expectedTimeline == null) {
+ assertEquals(null, gotTimeline);
+ } else {
+ assertEquals(expectedTimeline.size(), gotTimeline.size());
+ Iterator<TimelineAnnotation> iter = gotTimeline.iterator();
+ for (TimelineAnnotation expectedAnn : expectedTimeline) {
+ TimelineAnnotation gotAnn = iter.next();
+ assertEquals(expectedAnn.getMessage(), gotAnn.getMessage());
+ assertEquals(expectedAnn.getTime(), gotAnn.getTime());
+ }
+ }
+ }
+
+ @Test
+ public void testJsonSerialization() throws Exception {
+ MilliSpan span = new MilliSpan.Builder().
+ description("foospan").
+ begin(123L).
+ end(456L).
+ parents(new long[] { 7L }).
+ processId("b2404.halxg.com:8080").
+ spanId(989L).
+ traceId(444).build();
+ String json = span.toJson();
+ ObjectMapper mapper = new ObjectMapper();
+ MilliSpan dspan = mapper.readValue(json, MilliSpan.class);
+ compareSpans(span, dspan);
+ }
+
+ @Test
+ public void testJsonSerializationWithOptionalFields() throws Exception {
+ MilliSpan.Builder builder = new MilliSpan.Builder().
+ description("foospan").
+ begin(300).
+ end(400).
+ parents(new long[] { }).
+ processId("b2408.halxg.com:8080").
+ spanId(111111111L).
+ traceId(4443);
+ Map<byte[], byte[]> traceInfo = new HashMap<byte[], byte[]>();
+ traceInfo.put("abc".getBytes("UTF-8"), "123".getBytes("UTF-8"));
+ traceInfo.put("def".getBytes("UTF-8"), "456".getBytes("UTF-8"));
+ builder.traceInfo(traceInfo);
+ List<TimelineAnnotation> timeline = new LinkedList<TimelineAnnotation>();
+ timeline.add(new TimelineAnnotation(310L, "something happened"));
+ timeline.add(new TimelineAnnotation(380L, "something else happened"));
+ timeline.add(new TimelineAnnotation(390L, "more things"));
+ builder.timeline(timeline);
+ MilliSpan span = builder.build();
+ String json = span.toJson();
+ ObjectMapper mapper = new ObjectMapper();
+ MilliSpan dspan = mapper.readValue(json, MilliSpan.class);
+ compareSpans(span, dspan);
+ }
+}