diff --git a/htrace-core/pom.xml b/htrace-core/pom.xml
index 5c37dc8..d3b7f1b 100644
--- a/htrace-core/pom.xml
+++ b/htrace-core/pom.xml
@@ -145,20 +145,18 @@ language governing permissions and limitations under the License. -->
This client depends on the REST defined in rest.go in the htraced REST server.
+ * For example, a GET on /serverInfo returns the htraced server info.
+ *
+ *
Create an instance by doing:
+ * SpanReceiver receiver =
+ * HTracedRESTReceiver.builder().hostname(HTRACED_HOSTNAME).port(HTRACED_PORT).build();
+ * Then call receiver.receiveSpan(Span); to send spans to htraced. This method
+ * returns immediately. It sends the spans in background asynchronously.
+ *
+ *
TODO: How to be more dependent on rest.go so we break if it changes?
+ * TODO: Figure logging. Jetty does its own.
+ * TODO: Add tests. Add start/stop htraced.
+ */
+public class HTracedRESTReceiver implements SpanReceiver {
+ // Uses Builder and Fluent patterns as described here:
+ // http://jlordiales.me/2012/12/13/the-builder-pattern-in-practice/
+ private static final Log LOG = LogFactory.getLog(HTracedRESTReceiver.class);
+
+ private final HttpClient httpClient;
+ // TODO: Take process name and add this to user agent? Help debugging?
+ // TODO: A logger to use. Have it passed into the receiver builder. TODO.
+
+ /**
+ * REST URL to use writing Spans.
+ */
+ private final String writeSpansRESTURL;
+
+ /**
+ * Runs background task to do the REST PUT.
+ * TODO: Make period configurable.
+ */
+ private final ScheduledExecutorService scheduler;
+
+ /**
+ * Keep around reference so can cancel any running scheduled task.
+ */
+ private final ScheduledFuture> scheduledFuture;
+
+ /**
+ * Simple queue to hold spans between periodic runs of the httpclient.
+ * This queue is unbounded but it gets serviced and elements drained
+ * on a period. The draining thread, even if it dies, ScheduledExecutorService
+ * launches a new one (as I read it -- TODO: test).
+ */
+ private final ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue();
+
+ /**
+ * Builder class. Get instance by calling {@link HTracedRESTReceiver#builder()}.
+ */
+ public static class Builder {
+ private Duration timeout = new Duration(10000);
+ private String hostname = "localhost";
+ /**
+ * Default port for htraced.
+ */
+ private int port = 9095;
+
+ private Builder() {}
+
+ public Builder hostname(final String hostname) {
+ this.hostname = hostname;
+ return this;
+ }
+
+ public Builder port(final int port) {
+ this.port = port;
+ return this;
+ }
+
+ /**
+ * @param timeout Connection setup and request timeouts.
+ * @return This
+ */
+ public Builder timeout(final long timeout) {
+ this.timeout = new Duration(timeout);
+ return this;
+ }
+
+ public HTracedRESTReceiver build() throws Exception {
+ return new HTracedRESTReceiver(this);
+ }
+ }
+
+ private HTracedRESTReceiver(Builder builder) throws Exception {
+ this.httpClient = new HttpClient();
+ this.httpClient.setUserAgentField(new HttpField(HttpHeader.USER_AGENT,
+ this.getClass().getSimpleName()));
+ this.httpClient.setConnectTimeout(builder.timeout.getMillis());
+ // Use same timeout for connection and idle for now.
+ this.httpClient.setIdleTimeout(builder.timeout.getMillis());
+ this.httpClient.start();
+ // Build up the writeSpans URL.
+ this.writeSpansRESTURL = "http://" + builder.hostname + ":" + builder.port + "/writeSpans";
+ // Make a scheduler with one thread to run our POST of spans on a period.
+ this.scheduler = Executors.newScheduledThreadPool(1);
+ this.scheduledFuture =
+ this.scheduler.scheduleAtFixedRate(new PostSpans(this.queue), 1, 1, TimeUnit.SECONDS);
+ }
+
+ /**
+ * Post spans runnable.
+ */
+ private class PostSpans implements Runnable {
+ private final Queue q;
+
+ PostSpans(final Queue q) {
+ this.q = q;
+ }
+
+ @Override
+ public void run() {
+ Span span = this.q.poll();
+ if (span == null) return;
+ // We got a span. Send at least this one span.
+ Request request = httpClient.newRequest(writeSpansRESTURL).method(HttpMethod.POST);
+ request.header(HttpHeader.CONTENT_TYPE, "application/json");
+ int count = 1;
+ request.content(new StringContentProvider(span.toJson()));
+ // Drain queue of spans if more than just one.
+ while ((span = this.q.poll()) != null) {
+ request.content(new StringContentProvider(span.toJson()));
+ count++;
+ }
+ try {
+ ContentResponse response = request.send();
+ if (response.getStatus() == HttpStatus.OK_200) {
+ LOG.info("POSTED " + count + " spans");
+ } else {
+ LOG.error(response.getStatus());
+ LOG.error(response.getHeaders());
+ LOG.error(response.getContentAsString());
+ }
+ } catch (InterruptedException e) {
+ LOG.error(e);
+ } catch (TimeoutException e) {
+ LOG.error(e);
+ } catch (ExecutionException e) {
+ LOG.error(e);
+ }
+ }
+ }
+
+ /**
+ * @return New builder instance.
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (this.scheduledFuture != null) this.scheduledFuture.cancel(true);
+ if (this.scheduler == null) this.scheduler.shutdown();
+ if (this.httpClient != null) {
+ try {
+ this.httpClient.stop();
+ } catch (Exception e) {
+ throw new IOException(e);
+ }
+ }
+ }
+
+ @Override
+ public void receiveSpan(Span span) {
+ this.queue.add(span);
+ }
+
+ /**
+ * Exercise our little span.
+ * @param args
+ * @throws Exception
+ */
+ public static void main(String[] args) throws Exception {
+ HTracedRESTReceiver receiver =
+ HTracedRESTReceiver.builder().build();
+ try {
+ // Do basic a GET /server/info against localhost:9095 htraced
+ ContentResponse response = receiver.httpClient.GET("http://localhost:9095/server/info");
+ System.out.println(response.getMediaType());
+ System.out.println(response.getContentAsString());
+ // TODO: Fix MilliSpan. Requires a parentid. Shouldn't have to have one.
+ for (int i = 0; i < 100; i++) {
+ Span span = new MilliSpan.Builder().parents(new long [] {1L}).build();
+ receiver.receiveSpan(span);
+ Thread.sleep(100);
+ }
+ } finally {
+ receiver.close();
+ }
+ }
+}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index bf85272..38ae4b3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -32,6 +32,7 @@ language governing permissions and limitations under the License. -->