Skip to content

Commit

Permalink
Uses latest zipkin library in support of simplified json format (#43)
Browse files Browse the repository at this point in the history
This uses internal classes to perform a lot of the translation formerly
done here. By updating to latest zipkin, this also allows use of the new
POST /api/v2/spans endpoint from openzipkin/zipkin#1499

I'm happy to support this including work to switch over to the public
zipkin2 types once they become available later this year. In the mean
time, this uses shade to ensure internal types aren't leaked.
  • Loading branch information
adriancole authored and Bogdan Drutu committed Aug 14, 2017
1 parent a99b7c0 commit f19adc2
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 164 deletions.
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
</scm>

<properties>
<zipkin.version>1.22.0</zipkin.version>
<zipkin.version>1.30.2</zipkin.version>
<cloud.trace.sdk.version>0.3.2</cloud.trace.sdk.version>
<grpc.version>1.0.2</grpc.version>
</properties>
Expand All @@ -53,7 +53,7 @@
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.4.2.RELEASE</version>
<version>1.5.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
Expand Down
46 changes: 45 additions & 1 deletion translation/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,49 @@
</dependency>

</dependencies>

<build>
<plugins>
<!-- Repackage internal zipkin classes until Span2 is a public type -->
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<minimizeJar>true</minimizeJar>
<createDependencyReducedPom>false</createDependencyReducedPom>
<relocations>
<relocation>
<pattern>zipkin.internal</pattern>
<shadedPattern>com.google.cloud.trace.zipkin.translation.internal.zipkin</shadedPattern>
</relocation>
</relocations>
<artifactSet>
<includes>
<include>io.zipkin.java:zipkin</include>
</includes>
</artifactSet>
<filters>
<filter>
<!-- Shade references to span2 until zipkin2 is out -->
<artifact>io.zipkin.java:zipkin</artifact>
<includes>
<include>zipkin/internal/*Span2*.class</include>
<include>zipkin/internal/Util.class</include>
</includes>
<excludes>
<exclude>*</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,14 @@
package com.google.cloud.trace.zipkin.translation;

import com.google.common.collect.ImmutableMap;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import zipkin.Annotation;
import zipkin.BinaryAnnotation;
import zipkin.Span;
import zipkin.internal.Span2;
import zipkin.internal.Span2Converter;

/**
* LabelExtractor extracts the set of Stackdriver Span labels equivalent to the annotations in a given Zipkin Span.
Expand Down Expand Up @@ -60,27 +58,33 @@ public LabelExtractor(Map<String, String> renamedLabels) {
* @return A map of the Stackdriver span labels equivalent to the Zipkin annotations.
*/
public Map<String, String> extract(Span zipkinSpan) {
Map<String, String> labels = new HashMap<>();
for (BinaryAnnotation annotation : zipkinSpan.binaryAnnotations) {
labels.put(getLabelName(annotation.key), readBinaryAnnotation(annotation));
Map<String, String> result = new LinkedHashMap<>();
for (Span2 span2 : Span2Converter.fromSpan(zipkinSpan)) {
result.putAll(extract(span2));
}
return result;
}

Map<String, String> extract(Span2 zipkinSpan) { // not exposed until Span2 is a formal type
Map<String, String> result = new LinkedHashMap<>();
for (Map.Entry<String, String> tag : zipkinSpan.tags().entrySet()) {
result.put(getLabelName(tag.getKey()), tag.getValue());
}

for (Annotation annotation : zipkinSpan.annotations()) {
result.put(getLabelName(annotation.value), formatTimestamp(annotation.timestamp));
}

for (Annotation annotation : zipkinSpan.annotations) {
labels.put(getLabelName(annotation.value), formatTimestamp(annotation.timestamp));
if ("cs".equals(annotation.value) || "sr".equals(annotation.value)) {
// Consistently grab the serviceName from a specific annotation.
if (annotation.endpoint != null && annotation.endpoint.serviceName != null) {
labels.put(kComponentLabelKey, annotation.endpoint.serviceName);
}
}
if (zipkinSpan.localEndpoint() != null && !zipkinSpan.localEndpoint().serviceName.isEmpty()) {
result.put(kComponentLabelKey, zipkinSpan.localEndpoint().serviceName);
}

if (zipkinSpan.parentId == null) {
String agentName = System.getProperty("stackdriver.trace.zipkin.agent", "zipkin-java");
labels.put(kAgentLabelKey, agentName);
if (zipkinSpan.parentId() == null) {
String agentName = System.getProperty("stackdriver.trace.zipkin.agent", "zipkin-java");
result.put(kAgentLabelKey, agentName);
}

return labels;
return result;
}

private String getLabelName(String zipkinName) {
Expand All @@ -91,26 +95,6 @@ private String getLabelName(String zipkinName) {
}
}

private String readBinaryAnnotation(BinaryAnnotation annotation) {
// The value of a BinaryAnnotation is encoded in big endian order.
ByteBuffer buffer = ByteBuffer.wrap(annotation.value).order(ByteOrder.BIG_ENDIAN);
switch (annotation.type) {
case BOOL:
return annotation.value[0] == 1 ? "true" : "false";
case I16:
return Short.toString(buffer.getShort());
case I32:
return Integer.toString(buffer.getInt());
case I64:
return Long.toString(buffer.getLong());
case DOUBLE:
return Double.toString(buffer.getDouble());
case STRING:
default:
return new String(annotation.value, Charset.forName("UTF-8"));
}
}

private String formatTimestamp(long microseconds) {
long milliseconds = microseconds / 1000;
Date date = new Date(milliseconds);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,15 @@

package com.google.cloud.trace.zipkin.translation;

import static zipkin.Constants.CLIENT_RECV;
import static zipkin.Constants.CLIENT_SEND;
import static zipkin.Constants.SERVER_RECV;
import static zipkin.Constants.SERVER_SEND;

import com.google.devtools.cloudtrace.v1.TraceSpan;
import com.google.devtools.cloudtrace.v1.TraceSpan.SpanKind;
import com.google.protobuf.Timestamp;
import java.util.HashMap;
import java.util.Map;
import zipkin.Annotation;
import javax.annotation.Nullable;
import zipkin.Span;
import zipkin.internal.Span2;
import zipkin.internal.Span2Converter;

/**
* SpanTranslator converts a Zipkin Span to a Stackdriver Trace Span.
Expand Down Expand Up @@ -61,69 +58,55 @@ public SpanTranslator() {
* @return A Stackdriver Trace Span.
*/
public TraceSpan translate(Span zipkinSpan) {
Map<String, Annotation> annotations = getAnnotations(zipkinSpan);
TraceSpan.Builder spanBuilder = TraceSpan.newBuilder();
TraceSpan.Builder builder = TraceSpan.newBuilder();
for (Span2 span : Span2Converter.fromSpan(zipkinSpan)) {
translate(builder, span);
}
return builder.build();
}

spanBuilder.setName(zipkinSpan.name);
SpanKind kind = getSpanKind(annotations);
TraceSpan.Builder translate(TraceSpan.Builder spanBuilder, Span2 zipkinSpan) {
spanBuilder.setName(zipkinSpan.name());
SpanKind kind = getSpanKind(zipkinSpan.kind());
spanBuilder.setKind(kind);
rewriteIds(zipkinSpan, spanBuilder, kind);
writeBestTimestamp(zipkinSpan, spanBuilder, annotations);
spanBuilder.putAllLabels(labelExtractor.extract(zipkinSpan));
return spanBuilder.build();
}

private void writeBestTimestamp(Span zipkinSpan, TraceSpan.Builder spanBuilder, Map<String, Annotation> annotations) {
if (zipkinSpan.timestamp != null) {
// Span.timestamp is the authoritative value if it's present.
spanBuilder.setStartTime(createTimestamp(zipkinSpan.timestamp));
spanBuilder.setEndTime(createTimestamp(zipkinSpan.timestamp + zipkinSpan.duration));
} else if (annotations.containsKey(CLIENT_SEND) && annotations.containsKey(CLIENT_RECV)) {
// Client timestamps are more authoritative than server timestamps.
spanBuilder.setStartTime(
createTimestamp(annotations.get(CLIENT_SEND).timestamp)
);
spanBuilder.setEndTime(
createTimestamp(annotations.get(CLIENT_RECV).timestamp)
);
} else if (annotations.containsKey(SERVER_RECV) && annotations.containsKey(SERVER_SEND)) {
spanBuilder.setStartTime(
createTimestamp(annotations.get(SERVER_RECV).timestamp)
);
spanBuilder.setEndTime(
createTimestamp(annotations.get(SERVER_SEND).timestamp)
);
if (zipkinSpan.timestamp() != null) {
spanBuilder.setStartTime(createTimestamp(zipkinSpan.timestamp()));
if (zipkinSpan.duration() != null) {
Timestamp endTime = createTimestamp(zipkinSpan.timestamp() + zipkinSpan.duration());
spanBuilder.setEndTime(endTime);
}
}
spanBuilder.putAllLabels(labelExtractor.extract(zipkinSpan));
return spanBuilder;
}

/**
* Rewrite Span IDs to split multi-host Zipkin spans into multiple single-host Stackdriver spans.
*/
private void rewriteIds(Span zipkinSpan, TraceSpan.Builder builder, SpanKind kind) {
private void rewriteIds(Span2 zipkinSpan, TraceSpan.Builder builder, SpanKind kind) {
// Change the spanId of RPC_CLIENT spans.
if (kind == SpanKind.RPC_CLIENT) {
builder.setSpanId(rewriteId(zipkinSpan.id));
builder.setSpanId(rewriteId(zipkinSpan.id()));
} else {
builder.setSpanId(zipkinSpan.id);
builder.setSpanId(zipkinSpan.id());
}

// Change the parentSpanId of RPC_SERVER spans to use the rewritten spanId of the RPC_CLIENT spans.
if (kind == SpanKind.RPC_SERVER) {
if (zipkinSpan.timestamp != null ) {
// The timestamp field should only be written by instrumentation whenever it "owns" a span.
// Because this field is here, we know that the server "owns" this span which implies this
// is a single-host span.
// This means the parent RPC_CLIENT span was a separate span with id=zipkinSpan.parentId. When
// that span fragment was converted, it would have had id=rewriteId(zipkinSpan.parentId)
builder.setParentSpanId(rewriteId(zipkinSpan.parentId));
} else {
if (Boolean.TRUE.equals(zipkinSpan.shared())) {
// This is a multi-host span.
// This means the parent client-side span has the same id as this span. When that fragment of
// the span was converted, it would have had id rewriteId(zipkinSpan.id)
builder.setParentSpanId(rewriteId(zipkinSpan.id));
builder.setParentSpanId(rewriteId(zipkinSpan.id()));
} else {
// This span isn't shared: the server "owns" this span and it is a single-host span.
// This means the parent RPC_CLIENT span was a separate span with id=zipkinSpan.parentId. When
// that span fragment was converted, it would have had id=rewriteId(zipkinSpan.parentId)
builder.setParentSpanId(rewriteId(zipkinSpan.parentId()));
}
} else {
long parentId = zipkinSpan.parentId == null ? 0 : zipkinSpan.parentId;
long parentId = zipkinSpan.parentId() == null ? 0 : zipkinSpan.parentId();
builder.setParentSpanId(parentId);
}
}
Expand All @@ -137,22 +120,15 @@ private long rewriteId(Long id) {
return id ^ pad;
}

private SpanKind getSpanKind(Map<String, Annotation> annotations) {
if (annotations.containsKey(CLIENT_SEND) || annotations.containsKey(CLIENT_RECV)) {
private SpanKind getSpanKind(@Nullable Span2.Kind zipkinKind) {
if (zipkinKind == null) return SpanKind.SPAN_KIND_UNSPECIFIED;
if (zipkinKind == Span2.Kind.CLIENT) {
return SpanKind.RPC_CLIENT;
}
if (annotations.containsKey(SERVER_SEND) || annotations.containsKey(SERVER_RECV)) {
if (zipkinKind == Span2.Kind.SERVER) {
return SpanKind.RPC_SERVER;
}
return SpanKind.SPAN_KIND_UNSPECIFIED;
}

private Map<String, Annotation> getAnnotations(Span zipkinSpan) {
Map<String, Annotation> annotations = new HashMap<>();
for (Annotation annotation : zipkinSpan.annotations) {
annotations.put(annotation.value, annotation);
}
return annotations;
return SpanKind.UNRECOGNIZED;
}

private Timestamp createTimestamp(long microseconds) {
Expand Down
Loading

0 comments on commit f19adc2

Please sign in to comment.