Skip to content

Commit

Permalink
WIP TraceContext propagation handler
Browse files Browse the repository at this point in the history
  • Loading branch information
Adrian Cole committed Jun 14, 2018
1 parent e13017a commit 706e068
Show file tree
Hide file tree
Showing 11 changed files with 539 additions and 0 deletions.
1 change: 1 addition & 0 deletions brave/src/main/java/brave/propagation/Propagation.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ interface KeyFactory<K> {

/** Replaces a propagated key with the given value */
interface Setter<C, K> {
// BRAVE4: make this a charsequence as there's no need to allocate a string
void put(C carrier, K key, String value);
}

Expand Down
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
<module>brave-bom</module>
<module>brave-tests</module>
<module>context</module>
<module>propagation/trace-context</module>
<module>instrumentation</module>
<module>spring-beans</module>
</modules>
Expand Down
5 changes: 5 additions & 0 deletions propagation/trace-context/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# brave-propagation-trace-context

TODO: this is very early impl and doesn't do things like compare if it
should really resume a trace or not. It is a several hour spike and will
continue
33 changes: 33 additions & 0 deletions propagation/trace-context/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-propagation-parent</artifactId>
<version>5.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>brave-propagation-trace-context</artifactId>
<name>Brave Propagation: Trace-Context (traceparent, tracestate)</name>

<properties>
<main.basedir>${project.basedir}/../..</main.basedir>
<main.java.version>1.6</main.java.version>
<main.signature.artifact>java16</main.signature.artifact>
</properties>

<build>
<plugins>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<Automatic-Module-Name>brave.propagation.trace-context</Automatic-Module-Name>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package brave.propagation.tracecontext;

import brave.propagation.Propagation.Getter;
import brave.propagation.SamplingFlags;
import brave.propagation.TraceContext;
import brave.propagation.TraceContext.Extractor;
import brave.propagation.TraceContextOrSamplingFlags;
import brave.propagation.tracecontext.TraceContextPropagation.Extra;
import java.util.Collections;
import java.util.List;

import static brave.propagation.tracecontext.TraceparentFormat.FORMAT_LENGTH;
import static brave.propagation.tracecontext.TraceparentFormat.maybeExtractParent;
import static brave.propagation.tracecontext.TraceparentFormat.validateFormat;

final class TraceContextExtractor<C, K> implements Extractor<C> {
final Getter<C, K> getter;
final K tracestateKey;
final TracestateFormat tracestateFormat;

TraceContextExtractor(TraceContextPropagation<K> propagation, Getter<C, K> getter) {
this.getter = getter;
this.tracestateKey = propagation.tracestateKey;
this.tracestateFormat = new TracestateFormat(propagation.stateName);
}

@Override
public TraceContextOrSamplingFlags extract(C carrier) {
if (carrier == null) throw new NullPointerException("carrier == null");
String tracestateString = getter.get(carrier, tracestateKey);
if (tracestateString == null) return EMPTY;

TraceparentFormatHandler handler = new TraceparentFormatHandler();
CharSequence otherState = tracestateFormat.parseAndReturnOtherState(tracestateString, handler);

List<Object> extra;
if (otherState == null) {
extra = DEFAULT_EXTRA;
} else {
Extra e = new Extra();
e.otherState = otherState;
extra = Collections.singletonList(e);
}

if (handler.context == null) {
if (extra == DEFAULT_EXTRA) return EMPTY;
return TraceContextOrSamplingFlags.newBuilder()
.extra(extra)
.samplingFlags(SamplingFlags.EMPTY)
.build();
}
return TraceContextOrSamplingFlags.newBuilder().context(handler.context).extra(extra).build();
}

static final class TraceparentFormatHandler implements TracestateFormat.Handler {
TraceContext context;

@Override
public boolean onThisState(CharSequence tracestateString, int pos) {
if (validateFormat(tracestateString, pos) < FORMAT_LENGTH) {
return false;
}
context = maybeExtractParent(tracestateString, pos);
return true;
}
}

/** When present, this context was created with TracestatePropagation */
static final Extra MARKER = new Extra();

static final List<Object> DEFAULT_EXTRA = Collections.singletonList(MARKER);
static final TraceContextOrSamplingFlags EMPTY =
TraceContextOrSamplingFlags.EMPTY.toBuilder().extra(DEFAULT_EXTRA).build();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package brave.propagation.tracecontext;

import brave.propagation.Propagation.Setter;
import brave.propagation.TraceContext;
import brave.propagation.TraceContext.Injector;
import brave.propagation.tracecontext.TraceContextPropagation.Extra;

import static brave.propagation.tracecontext.TraceparentFormat.writeTraceparentFormat;

final class TraceContextInjector<C, K> implements Injector<C> {
final TracestateFormat tracestateFormat;
final Setter<C, K> setter;
final K traceparentKey, tracestateKey;

TraceContextInjector(TraceContextPropagation<K> propagation, Setter<C, K> setter) {
this.tracestateFormat = new TracestateFormat(propagation.stateName);
this.traceparentKey = propagation.traceparentKey;
this.tracestateKey = propagation.tracestateKey;
this.setter = setter;
}

@Override
public void inject(TraceContext traceContext, C carrier) {
String thisState = writeTraceparentFormat(traceContext);
setter.put(carrier, traceparentKey, thisState);

CharSequence otherState = null;
for (int i = 0, length = traceContext.extra().size(); i < length; i++) {
Object next = traceContext.extra().get(i);
if (next instanceof Extra) {
otherState = ((Extra) next).otherState;
break;
}
}

String tracestate = tracestateFormat.write(thisState, otherState);
setter.put(carrier, tracestateKey, tracestate);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package brave.propagation.tracecontext;

import brave.propagation.Propagation;
import brave.propagation.TraceContext.Extractor;
import brave.propagation.TraceContext.Injector;
import java.util.Arrays;
import java.util.List;

public final class TraceContextPropagation<K> implements Propagation<K> {

public static final Propagation.Factory FACTORY =
new Propagation.Factory() {
@Override
public <K> Propagation<K> create(KeyFactory<K> keyFactory) {
return new TraceContextPropagation<>(keyFactory);
}

@Override
public boolean requires128BitTraceId() {
return true;
}

@Override
public String toString() {
return "TracestatePropagationFactory";
}
};

final String stateName;
final K traceparentKey, tracestateKey;
final List<K> fields;

TraceContextPropagation(KeyFactory<K> keyFactory) {
this.stateName = "tc";
this.traceparentKey = keyFactory.create("traceparent");
this.tracestateKey = keyFactory.create("tracestate");
this.fields = Arrays.asList(traceparentKey, tracestateKey);
}

@Override
public List<K> keys() {
return fields;
}

@Override
public <C> Injector<C> injector(Setter<C, K> setter) {
if (setter == null) throw new NullPointerException("setter == null");
return new TraceContextInjector<>(this, setter);
}

@Override
public <C> Extractor<C> extractor(Getter<C, K> getter) {
if (getter == null) throw new NullPointerException("getter == null");
return new TraceContextExtractor<>(this, getter);
}

static final class Extra { // hidden intentionally
CharSequence otherState;

@Override
public String toString() {
return "TracestatePropagation{"
+ (otherState != null ? ("fields=" + otherState.toString()) : "")
+ "}";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package brave.propagation.tracecontext;

import brave.internal.Nullable;
import brave.propagation.TraceContext;
import java.util.logging.Logger;

import static brave.internal.HexCodec.lenientLowerHexToUnsignedLong;
import static brave.internal.HexCodec.writeHexLong;

final class TraceparentFormat {
static final Logger logger = Logger.getLogger(TraceparentFormat.class.getName());
static final int FORMAT_LENGTH = 55;

static String writeTraceparentFormat(TraceContext context) {
// 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
char[] result = new char[FORMAT_LENGTH];
result[0] = '0'; // version
result[1] = '0'; // version
result[2] = '-'; // delimiter
writeHexLong(result, 3, context.traceIdHigh());
writeHexLong(result, 19, context.traceId());
result[35] = '-'; // delimiter
writeHexLong(result, 36, context.spanId());
result[52] = '-'; // delimiter
result[53] = '0'; // options
result[54] = context.sampled() != null && context.sampled() ? '1' : '0'; // options
return new String(result);
}

/** returns the count of valid characters read from the input position */
static int validateFormat(CharSequence parent, int pos) {
int length = Math.max(parent.length() - pos, FORMAT_LENGTH);
if (length < FORMAT_LENGTH) {
logger.fine("Bad length.");
return 0;
}

for (int i = 0; i < length; i++) {
char c = parent.charAt(i + pos);
if (c == '-') {
// There are delimiters separating the version, trace ID, span ID and options fields.
if (i != 2 && i != 35 && i != 52) {
logger.fine("Expected hyphen at " + (i + pos));
return i;
}
// Everything else is hex
} else if ((c < '0' || c > '9') && (c < 'a' || c > 'f')) {
logger.fine("Expected lower hex at " + (i + pos));
return i;
}
}
return length;
}

static @Nullable TraceContext maybeExtractParent(CharSequence parent, int pos) {
int version = parseUnsigned16BitLowerHex(parent, pos);
if (version == -1) {
logger.fine("Malformed version.");
return null;
}
if (version != 0) {
logger.fine("Unsupported version.");
return null;
}

long traceIdHigh = lenientLowerHexToUnsignedLong(parent, pos + 3, pos + 19);
long traceId = lenientLowerHexToUnsignedLong(parent, pos + 19, pos + 35);
if (traceIdHigh == 0L && traceId == 0L) {
logger.fine("Invalid input: expected non-zero trace ID");
return null;
}

long spanId = lenientLowerHexToUnsignedLong(parent, pos + 36, pos + 52);
if (spanId == 0L) {
logger.fine("Invalid input: expected non-zero span ID");
return null;
}

int traceOptions = parseUnsigned16BitLowerHex(parent, pos + 53);
if (traceOptions == -1) {
logger.fine("Malformed trace options.");
return null;
}

// TODO: treat it as a bitset?
// https://github.com/w3c/distributed-tracing/issues/8#issuecomment-382958021
// TODO handle deferred decision https://github.com/w3c/distributed-tracing/issues/8
boolean sampled = (traceOptions & 1) == 1;

return TraceContext.newBuilder()
.traceIdHigh(traceIdHigh)
.traceId(traceId)
.spanId(spanId)
.sampled(sampled)
.build();
}

/** Returns -1 if it wasn't hex */
static int parseUnsigned16BitLowerHex(CharSequence lowerHex, int pos) {
int result = 0;
for (int i = 0; i < 2; i++) {
char c = lowerHex.charAt(pos + i);
result <<= 4;
if (c >= '0' && c <= '9') {
result |= c - '0';
} else if (c >= 'a' && c <= 'f') {
result |= c - 'a' + 10;
} else {
return -1;
}
}
return result;
}
}
Loading

0 comments on commit 706e068

Please sign in to comment.