You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@htrace.apache.org by cm...@apache.org on 2015/04/20 02:50:19 UTC

[1/4] incubator-htrace git commit: HTRACE-106: htrace: add C / C++ native client (cmccabe)

Repository: incubator-htrace
Updated Branches:
  refs/heads/master 20f00f9c2 -> a9d85254b


http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/test.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/test/test.c b/htrace-c/src/test/test.c
new file mode 100644
index 0000000..7a04006
--- /dev/null
+++ b/htrace-c/src/test/test.c
@@ -0,0 +1,158 @@
+/**
+ * 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.
+ */
+
+#include "test/test.h"
+#include "util/string.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+void fail(const char *fmt, ...)
+{
+    va_list ap;
+
+    va_start(ap, fmt);
+    vfprintf(stderr, fmt, ap);
+    va_end(ap);
+    exit(1);
+}
+
+static int vexpect_decode(const char *expected, const char *text, int ty,
+        const char *str)
+{
+    switch (ty) {
+    case TEST_ERROR_EQ:
+        if (strcmp(expected, str) != 0) {
+            fprintf(stderr, "error: expected '%s', but got '%s', which "
+                   "is not equal. %s\n", expected, str, text);
+            return -1;
+        }
+        break;
+    case TEST_ERROR_GE:
+        if (strcmp(expected, str) > 0) {
+            fprintf(stderr, "error: expected '%s', but got '%s'. "
+                   "Expected something greater or equal.  %s\n",
+                   expected, str, text);
+            return -1;
+        }
+        break;
+    case TEST_ERROR_GT:
+        if (strcmp(expected, str) >= 0) {
+            fprintf(stderr, "error: expected '%s', but got '%s'. "
+                   "Expected something greater.  %s\n",
+                   expected, str, text);
+            return -1;
+        }
+        break;
+    case TEST_ERROR_LE:
+        if (strcmp(expected, str) < 0) {
+            fprintf(stderr, "error: expected '%s', but got '%s'. "
+                   "Expected something less or equal.  %s\n",
+                   expected, str, text);
+            return -1;
+        }
+        break;
+    case TEST_ERROR_LT:
+        if (strcmp(expected, str) <= 0) {
+            fprintf(stderr, "error: expected '%s', but got '%s'. "
+                   "Expected something less.  %s\n",
+                   expected, str, text);
+            return -1;
+        }
+        break;
+    case TEST_ERROR_NE:
+        if (strcmp(expected, str) == 0) {
+            fprintf(stderr, "error: did not expect '%s': '%s'\n",
+                   expected, text);
+            return -1;
+        }
+        break;
+    default:
+        fprintf(stderr, "runtime error: invalid type %d passed in: '%s'\n",
+                ty, text);
+        return -1;
+    }
+    return 0;
+}
+
+int vexpect(const char *expected, const char *text, int ty,
+        const char *fmt, va_list ap)
+{
+    char *str = NULL;
+    int ret;
+
+    if (vasprintf(&str, fmt, ap) < 0) { // TODO: portability
+        fprintf(stderr, "error: vasprintf failed: %s\n", text);
+        return -1;
+    }
+    ret = vexpect_decode(expected, text, ty, str);
+    free(str);
+    return ret;
+}
+
+int expect(const char *expected, const char *text, int ty,
+        const char *fmt, ...)
+{
+    int ret;
+    va_list ap;
+
+    va_start(ap, fmt);
+    ret = vexpect(expected, text, ty, fmt, ap);
+    va_end(ap);
+    return ret;
+}
+
+void *xcalloc(size_t len)
+{
+    void *v = calloc(1, len);
+    if (!v) {
+        abort();
+    }
+    return v;
+}
+
+char *xstrdup(const char *in)
+{
+    char *out = strdup(in);
+    if (!out) {
+        abort();
+    }
+    return out;
+}
+
+void hexdump(void *in, int in_len, char *out, int out_len)
+{
+    int i;
+    uint8_t *b = in;
+    const char *prefix = "";
+
+    for (i = 0; i < in_len; i++) {
+        fwdprintf(&out, &out_len, "%s%02x", prefix, b[i]);
+        prefix = " ";
+    }
+}
+
+// vim: ts=4:sw=4:tw=79:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/test.h
----------------------------------------------------------------------
diff --git a/htrace-c/src/test/test.h b/htrace-c/src/test/test.h
new file mode 100644
index 0000000..3edd2bc
--- /dev/null
+++ b/htrace-c/src/test/test.h
@@ -0,0 +1,211 @@
+/**
+ * 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.
+ */
+
+#ifndef APACHE_HTRACE_TEST_TEST_H
+#define APACHE_HTRACE_TEST_TEST_H
+
+#include <inttypes.h> /* for PRIdPTR */
+#include <stdarg.h> /* for va_list */
+#include <unistd.h> /* for size_t */
+
+#define TEST_ERROR_EQ 0
+#define TEST_ERROR_GE 1
+#define TEST_ERROR_GT 2
+#define TEST_ERROR_LE 3
+#define TEST_ERROR_LT 4
+#define TEST_ERROR_NE 5
+
+/**
+ * Fail with an error message.
+ *
+ * @param fmt       printf-style format string.
+ * @param ...       printf-style arguments.
+ */
+void fail(const char *fmt, ...);
+
+/**
+ * Check for a test condition.
+ *
+ * @param expected  The string which is expected.
+ * @param text      Some text that will be printed out if the test
+ *                      condition fails.
+ * @param ty        Comparison type.  See TEST_ERROR_* constants.
+ * @param fmt       printf-style format string.
+ * @param ap        printf-style arguments.
+ *
+ * @return      0 on success; 1 on failure.
+ */
+int vexpect(const char *expected, const char *text, int ty,
+        const char *fmt, va_list ap);
+
+/**
+ * Check for a test condition.
+ *
+ * @param expected  The string which is expected.
+ * @param text      Some text that will be printed out if the test
+ *                      condition fails.
+ * @param ty        Comparison type.  See TEST_ERROR_* constants.
+ * @param fmt       printf-style format string.
+ * @param ...       printf-style arguments.
+ *
+ * @return          0 on success; 1 on failure.
+ */
+int expect(const char *expected, const char *text, int ty,
+        const char *fmt, ...) __attribute__((format(printf, 4, 5)));
+
+/**
+ * Allocate a zero-initialized region of memory, or die.
+ *
+ * @param len       The length
+ *
+ * @return          A pointer to a zero-initialized malloc'ed region.
+ */
+void *xcalloc(size_t len);
+
+/**
+ * Copy a string, or die.
+ *
+ * @param in        The string to copy
+ *
+ * @return          A dynamically allocated copy of the input string.
+ */
+char *xstrdup(const char *in);
+
+/**
+ * Dump a set of bytes to a buffer in hexadecimal form.
+ *
+ * @param in        The input.
+ * @param buf_len   Length of the input buffer.
+ * @param out       The buffer to dump to.
+ * @param out_len   Length of the buffer to dump to.  If the output buffer is
+ *                      not long enough, not all the output will be written.
+ */
+void hexdump(void *in, int in_len, char *buf, int buf_len);
+
+#define TEST_ERROR_GET_LINE_HELPER2(x) #x
+#define TEST_ERROR_GET_LINE_HELPER(x) TEST_ERROR_GET_LINE_HELPER2(x)
+#define TEST_ERROR_LOCATION_TEXT __FILE__ " at line " \
+    TEST_ERROR_GET_LINE_HELPER(__LINE__)
+
+#define EXPECT(...) do { if (expect(__VA_ARGS__)) return 1; } while (0);
+
+#define EXPECT_EQ(expected, fmt, ...) \
+    EXPECT(expected, TEST_ERROR_LOCATION_TEXT, TEST_ERROR_EQ, \
+           fmt, __VA_ARGS__);
+
+#define EXPECT_STR_EQ(expected, str) \
+    EXPECT_EQ(expected, "%s", str)
+
+#define EXPECT_GE(expected, fmt, ...) \
+    EXPECT(expected, TEST_ERROR_LOCATION_TEXT, TEST_ERROR_GE, \
+           fmt, __VA_ARGS__);
+
+#define EXPECT_GT(expected, fmt, ...) \
+    EXPECT(expected, TEST_ERROR_LOCATION_TEXT, TEST_ERROR_GT, \
+           fmt, __VA_ARGS__);
+
+#define EXPECT_LE(expected, fmt, ...) \
+    EXPECT(expected, TEST_ERROR_LOCATION_TEXT, TEST_ERROR_LE, \
+           fmt, __VA_ARGS__);
+
+#define EXPECT_LT(expected, fmt, ...) \
+    EXPECT(expected, TEST_ERROR_LOCATION_TEXT, TEST_ERROR_LT, \
+           fmt, __VA_ARGS__);
+
+#define EXPECT_NE(expected, fmt, ...) \
+    EXPECT(expected, TEST_ERROR_LOCATION_TEXT, TEST_ERROR_NE, \
+           fmt, __VA_ARGS__);
+
+#define COMMON_TEST__TO_STR(x) #x
+#define COMMON_TEST__TO_STR2(x) COMMON_TEST__TO_STR(x)
+
+#define EXPECT_INT_EQ(expected, x) do { \
+  char expected_buf[16] = { 0 }; \
+  snprintf(expected_buf, sizeof(expected_buf), "%d", expected); \
+  EXPECT(expected_buf, TEST_ERROR_LOCATION_TEXT, TEST_ERROR_EQ, "%d", x); \
+} while(0);
+
+#define EXPECT_INT_GE(expected, x) do { \
+  char expected_buf[16] = { 0 }; \
+  snprintf(expected_buf, sizeof(expected_buf), "%d", expected); \
+  EXPECT(expected_buf, TEST_ERROR_LOCATION_TEXT, TEST_ERROR_GE, "%d", x); \
+} while(0);
+
+#define EXPECT_INT_GT(expected, x) do { \
+  char expected_buf[16] = { 0 }; \
+  snprintf(expected_buf, sizeof(expected_buf), "%d", expected); \
+  EXPECT(expected_buf, TEST_ERROR_LOCATION_TEXT, TEST_ERROR_GT, "%d", x); \
+} while(0);
+
+#define EXPECT_UINT64_EQ(expected, x) do { \
+  char expected_buf[32] = { 0 }; \
+  snprintf(expected_buf, sizeof(expected_buf), "%" PRIu64, expected); \
+  EXPECT(expected_buf, TEST_ERROR_LOCATION_TEXT, TEST_ERROR_EQ, \
+         "%" PRIu64, x); \
+} while(0);
+
+#define EXPECT_UINT64_GE(expected, x) do { \
+  char expected_buf[32] = { 0 }; \
+  snprintf(expected_buf, sizeof(expected_buf), "%" PRIu64, expected); \
+  EXPECT(expected_buf, TEST_ERROR_LOCATION_TEXT, TEST_ERROR_GE, \
+         "%" PRIu64, x); \
+} while(0);
+
+#define EXPECT_UINT64_GT(expected, x) do { \
+  char expected_buf[32] = { 0 }; \
+  snprintf(expected_buf, sizeof(expected_buf), "%" PRIu64, expected); \
+  EXPECT(expected_buf, TEST_ERROR_LOCATION_TEXT, TEST_ERROR_GT, \
+         "%" PRIu64, x); \
+} while(0);
+
+#define EXPECT_INT64_EQ(expected, x) do { \
+  char expected_buf[32] = { 0 }; \
+  snprintf(expected_buf, sizeof(expected_buf), "%" PRId64, expected); \
+  EXPECT(expected_buf, TEST_ERROR_LOCATION_TEXT, TEST_ERROR_EQ, \
+         "%" PRId64, x); \
+} while(0);
+
+#define EXPECT_UINTPTR_EQ(expected, x) do { \
+  char expected_buf[32] = { 0 }; \
+  snprintf(expected_buf, sizeof(expected_buf), "%" PRIuPTR, expected); \
+  EXPECT(expected_buf, TEST_ERROR_LOCATION_TEXT, TEST_ERROR_EQ, \
+         "%" PRIuPTR, x); \
+} while(0);
+
+#define EXPECT_INT_ZERO(x) \
+    EXPECT("0", TEST_ERROR_LOCATION_TEXT, TEST_ERROR_EQ, \
+           "%d", x);
+
+#define EXPECT_TRUE(x) \
+    EXPECT("1", TEST_ERROR_LOCATION_TEXT, TEST_ERROR_EQ, \
+           "%d", (!!x));
+
+#define EXPECT_FALSE(x) \
+    EXPECT("0", TEST_ERROR_LOCATION_TEXT, TEST_ERROR_EQ, \
+           "%d", (!!x));
+
+#define EXPECT_NONNULL(x) \
+    EXPECT("0", TEST_ERROR_LOCATION_TEXT, TEST_ERROR_NE, \
+           "%"PRIuPTR, (uintptr_t)x);
+
+#define EXPECT_NULL(x) \
+    EXPECT("0", TEST_ERROR_LOCATION_TEXT, TEST_ERROR_EQ, \
+           "%"PRIuPTR, (uintptr_t)x);
+#endif
+
+// vim: ts=4:sw=4:tw=79:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/test_config.h.cmake
----------------------------------------------------------------------
diff --git a/htrace-c/src/test/test_config.h.cmake b/htrace-c/src/test/test_config.h.cmake
new file mode 100644
index 0000000..62b1744
--- /dev/null
+++ b/htrace-c/src/test/test_config.h.cmake
@@ -0,0 +1,28 @@
+/**
+* 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.
+*/
+
+#ifndef APACHE_HTRACE_TEST_TEST_CONFIG_H
+#define APACHE_HTRACE_TEST_TEST_CONFIG_H
+
+// The absolute path to the htrace binary, for use in unit tests.
+#cmakedefine HTRACE_ABSPATH "@HTRACE_ABSPATH@"
+
+// The absolute path to the htraced binary, for use in unit tests.
+#cmakedefine HTRACED_ABSPATH "@HTRACED_ABSPATH@"
+
+#endif

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/time-unit.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/test/time-unit.c b/htrace-c/src/test/time-unit.c
new file mode 100644
index 0000000..acdd14f
--- /dev/null
+++ b/htrace-c/src/test/time-unit.c
@@ -0,0 +1,84 @@
+/**
+ * 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.
+ */
+
+#include "core/conf.h"
+#include "test/test.h"
+#include "util/log.h"
+#include "util/time.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define TEST_STR1 "This is a test.  "
+#define TEST_STR1_LEN (sizeof(TEST_STR1) - 1)
+
+static struct htrace_conf *g_test_conf;
+
+static struct htrace_log *g_test_lg;
+
+static int test_timespec_conversions(void)
+{
+    uint64_t ms;
+    struct timespec ts, ts2;
+    ts.tv_sec = 1;
+    ts.tv_nsec = 999999999;
+    ms = timespec_to_ms(&ts);
+    EXPECT_UINT64_EQ((uint64_t)1999, ms);
+    ts2.tv_sec = 0;
+    ts2.tv_nsec = 0;
+    ms_to_timespec(ms, &ts2);
+    EXPECT_UINT64_EQ(1LU, ts2.tv_sec);
+    EXPECT_UINT64_EQ((uint64_t)999000000ULL, ts2.tv_nsec);
+    return EXIT_SUCCESS;
+}
+
+static int test_now_increases(int monotonic)
+{
+    int i;
+    uint64_t first = now_ms(g_test_lg);
+
+    for (i = 0; i < 10; i++) {
+        uint64_t next;
+        sleep_ms(2); // At least 2 ms.
+        if (monotonic) {
+            next = monotonic_now_ms(g_test_lg);
+        } else {
+            next = now_ms(g_test_lg);
+        }
+        EXPECT_UINT64_GT(first, next);
+    }
+    return EXIT_SUCCESS;
+}
+
+int main(void)
+{
+    g_test_conf = htrace_conf_from_strs("", "");
+    g_test_lg = htrace_log_alloc(g_test_conf);
+
+    test_timespec_conversions();
+
+    test_now_increases(0);
+    test_now_increases(1);
+
+    htrace_log_free(g_test_lg);
+    htrace_conf_free(g_test_conf);
+    return EXIT_SUCCESS;
+}
+
+// vim: ts=4:sw=4:tw=79:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/util/build.h.cmake
----------------------------------------------------------------------
diff --git a/htrace-c/src/util/build.h.cmake b/htrace-c/src/util/build.h.cmake
new file mode 100644
index 0000000..ad04882
--- /dev/null
+++ b/htrace-c/src/util/build.h.cmake
@@ -0,0 +1,23 @@
+/**
+* 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.
+*/
+#ifndef APACHE_HTRACE_UTIL_BUILD_H
+#define APACHE_HTRACE_UTIL_BUILD_H
+
+#cmakedefine HAVE_IMPROVED_TLS
+
+#endif

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/util/htable.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/util/htable.c b/htrace-c/src/util/htable.c
new file mode 100644
index 0000000..d38462e
--- /dev/null
+++ b/htrace-c/src/util/htable.c
@@ -0,0 +1,290 @@
+/**
+ * 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.
+ */
+
+#include "util/htable.h"
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/**
+ * @file htable.c
+ *
+ * Implements a hash table that uses probing.
+ */
+
+struct htable_pair {
+    void *key;
+    void *val;
+};
+
+/**
+ * A hash table which uses linear probing.
+ */
+struct htable {
+    uint32_t capacity;
+    uint32_t used;
+    htable_hash_fn_t hash_fun;
+    htable_eq_fn_t eq_fun;
+    struct htable_pair *elem;
+};
+
+/**
+ * An internal function for inserting a value into the hash table.
+ *
+ * Note: this function assumes that you have made enough space in the table.
+ *
+ * @param nelem         The new element to insert.
+ * @param capacity      The capacity of the hash table.
+ * @param hash_fun      The hash function to use.
+ * @param key           The key to insert.
+ * @param val           The value to insert.
+ */
+static void htable_insert_internal(struct htable_pair *nelem,
+        uint32_t capacity, htable_hash_fn_t hash_fun, void *key,
+        void *val)
+{
+    uint32_t i;
+
+    i = hash_fun(key, capacity);
+    while (1) {
+        if (!nelem[i].key) {
+            nelem[i].key = key;
+            nelem[i].val = val;
+            return;
+        }
+        i++;
+        if (i == capacity) {
+            i = 0;
+        }
+    }
+}
+
+static int htable_realloc(struct htable *htable, uint32_t new_capacity)
+{
+    struct htable_pair *nelem;
+    uint32_t i, old_capacity = htable->capacity;
+    htable_hash_fn_t hash_fun = htable->hash_fun;
+
+    nelem = calloc(new_capacity, sizeof(struct htable_pair));
+    if (!nelem) {
+        return ENOMEM;
+    }
+    for (i = 0; i < old_capacity; i++) {
+        struct htable_pair *pair = htable->elem + i;
+        if (pair->key) {
+            htable_insert_internal(nelem, new_capacity, hash_fun,
+                                   pair->key, pair->val);
+        }
+    }
+    free(htable->elem);
+    htable->elem = nelem;
+    htable->capacity = new_capacity;
+    return 0;
+}
+
+static uint32_t round_up_to_power_of_2(uint32_t i)
+{
+    i--;
+    i |= i >> 1;
+    i |= i >> 2;
+    i |= i >> 4;
+    i |= i >> 8;
+    i |= i >> 16;
+    i++;
+    return i;
+}
+
+struct htable *htable_alloc(uint32_t size,
+                htable_hash_fn_t hash_fun, htable_eq_fn_t eq_fun)
+{
+    struct htable *htable;
+
+    htable = calloc(1, sizeof(*htable));
+    if (!htable) {
+        return NULL;
+    }
+    size = round_up_to_power_of_2(size);
+    if (size < HTABLE_MIN_SIZE) {
+        size = HTABLE_MIN_SIZE;
+    }
+    htable->hash_fun = hash_fun;
+    htable->eq_fun = eq_fun;
+    htable->used = 0;
+    if (htable_realloc(htable, size)) {
+        free(htable);
+        return NULL;
+    }
+    return htable;
+}
+
+void htable_visit(struct htable *htable, visitor_fn_t fun, void *ctx)
+{
+    uint32_t i;
+
+    for (i = 0; i != htable->capacity; ++i) {
+        struct htable_pair *elem = htable->elem + i;
+        if (elem->key) {
+            fun(ctx, elem->key, elem->val);
+        }
+    }
+}
+
+void htable_free(struct htable *htable)
+{
+    if (htable) {
+        free(htable->elem);
+        free(htable);
+    }
+}
+
+int htable_put(struct htable *htable, void *key, void *val)
+{
+    int ret;
+    uint32_t nused;
+
+    // NULL is not a valid key value.
+    // This helps us implement htable_get_internal efficiently, since we know
+    // that we can stop when we encounter the first NULL key.
+    if (!key) {
+        return EINVAL;
+    }
+    // NULL is not a valid value.  Otherwise the results of htable_get would
+    // be confusing (does a NULL return mean entry not found, or that the
+    // entry was found and was NULL?)
+    if (!val) {
+        return EINVAL;
+    }
+    // Re-hash if we have used more than half of the hash table
+    nused = htable->used + 1;
+    if (nused >= (htable->capacity / 2)) {
+        ret = htable_realloc(htable, htable->capacity * 2);
+        if (ret)
+            return ret;
+    }
+    htable_insert_internal(htable->elem, htable->capacity,
+                                htable->hash_fun, key, val);
+    htable->used++;
+    return 0;
+}
+
+static int htable_get_internal(const struct htable *htable,
+                               const void *key, uint32_t *out)
+{
+    uint32_t start_idx, idx;
+
+    start_idx = htable->hash_fun(key, htable->capacity);
+    idx = start_idx;
+    while (1) {
+        struct htable_pair *pair = htable->elem + idx;
+        if (!pair->key) {
+            // We always maintain the invariant that the entries corresponding
+            // to a given key are stored in a contiguous block, not separated
+            // by any NULLs.  So if we encounter a NULL, our search is over.
+            return ENOENT;
+        } else if (htable->eq_fun(pair->key, key)) {
+            *out = idx;
+            return 0;
+        }
+        idx++;
+        if (idx == htable->capacity) {
+            idx = 0;
+        }
+        if (idx == start_idx) {
+            return ENOENT;
+        }
+    }
+}
+
+void *htable_get(const struct htable *htable, const void *key)
+{
+    uint32_t idx;
+
+    if (htable_get_internal(htable, key, &idx)) {
+        return NULL;
+    }
+    return htable->elem[idx].val;
+}
+
+void htable_pop(struct htable *htable, const void *key,
+                void **found_key, void **found_val)
+{
+    uint32_t hole, i;
+    const void *nkey;
+
+    if (htable_get_internal(htable, key, &hole)) {
+        *found_key = NULL;
+        *found_val = NULL;
+        return;
+    }
+    i = hole;
+    htable->used--;
+    // We need to maintain the compactness invariant used in
+    // htable_get_internal.  This invariant specifies that the entries for any
+    // given key are never separated by NULLs (although they may be separated
+    // by entries for other keys.)
+    while (1) {
+        i++;
+        if (i == htable->capacity) {
+            i = 0;
+        }
+        nkey = htable->elem[i].key;
+        if (!nkey) {
+            *found_key = htable->elem[hole].key;
+            *found_val = htable->elem[hole].val;
+            htable->elem[hole].key = NULL;
+            htable->elem[hole].val = NULL;
+            return;
+        } else if (htable->eq_fun(key, nkey)) {
+            htable->elem[hole].key = htable->elem[i].key;
+            htable->elem[hole].val = htable->elem[i].val;
+            hole = i;
+        }
+    }
+}
+
+uint32_t htable_used(const struct htable *htable)
+{
+    return htable->used;
+}
+
+uint32_t htable_capacity(const struct htable *htable)
+{
+    return htable->capacity;
+}
+
+uint32_t ht_hash_string(const void *str, uint32_t max)
+{
+    const char *s = str;
+    uint32_t hash = 0;
+
+    while (*s) {
+        hash = (hash * 31) + *s;
+        s++;
+    }
+    return hash % max;
+}
+
+int ht_compare_string(const void *a, const void *b)
+{
+    return strcmp(a, b) == 0;
+}
+
+// vim: ts=4:sw=4:tw=79:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/util/htable.h
----------------------------------------------------------------------
diff --git a/htrace-c/src/util/htable.h b/htrace-c/src/util/htable.h
new file mode 100644
index 0000000..9715efa
--- /dev/null
+++ b/htrace-c/src/util/htable.h
@@ -0,0 +1,169 @@
+/**
+ * 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.
+ */
+
+#ifndef APACHE_HTRACE_HTABLE_H
+#define APACHE_HTRACE_HTABLE_H
+
+/**
+ * @file htable.h
+ *
+ * Interfaces for a hash table that uses probing.
+ *
+ * This is an internal header, not intended for external use.
+ */
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdint.h>
+
+#define HTABLE_MIN_SIZE 4
+
+struct htable;
+
+/**
+ * An HTable hash function.
+ *
+ * @param key       The key.
+ * @param capacity  The total capacity.
+ *
+ * @return          The hash slot.  Must be less than the capacity.
+ */
+typedef uint32_t (*htable_hash_fn_t)(const void *key, uint32_t capacity);
+
+/**
+ * An HTable equality function.  Compares two keys.
+ *
+ * @param a         First key.
+ * @param b         Second key.
+ *
+ * @return          nonzero if the keys are equal.
+ */
+typedef int (*htable_eq_fn_t)(const void *a, const void *b);
+
+/**
+ * Allocate a new hash table.
+ *
+ * @param capacity  The minimum suggested starting capacity.
+ * @param hash_fun  The hash function to use in this hash table.
+ * @param eq_fun    The equals function to use in this hash table.
+ *
+ * @return          The new hash table on success; NULL on OOM.
+ */
+struct htable *htable_alloc(uint32_t capacity, htable_hash_fn_t hash_fun,
+                            htable_eq_fn_t eq_fun);
+
+typedef void (*visitor_fn_t)(void *ctx, void *key, void *val);
+
+/**
+ * Visit all of the entries in the hash table.
+ *
+ * @param htable    The hash table.
+ * @param fun       The callback function to invoke on each key and value.
+ * @param ctx       Context pointer to pass to the callback.
+ */
+void htable_visit(struct htable *htable, visitor_fn_t fun, void *ctx);
+
+/**
+ * Free the hash table.
+ *
+ * It is up the calling code to ensure that the keys and values inside the
+ * table are de-allocated, if that is necessary.
+ *
+ * @param htable    The hash table.
+ */
+void htable_free(struct htable *htable);
+
+/**
+ * Add an entry to the hash table.
+ *
+ * @param htable    The hash table.
+ * @param key       The key to add.  This cannot be NULL.
+ * @param fun       The value to add.  This cannot be NULL.
+ *
+ * @return          0 on success;
+ *                  EINVAL if we're trying to insert a NULL key or value.
+ *                  ENOMEM if there is not enough memory to add the element.
+ *                  EFBIG if the hash table has too many entries to fit in 32
+ *                      bits.
+ */
+int htable_put(struct htable *htable, void *key, void *val);
+
+/**
+ * Get an entry from the hash table.
+ *
+ * @param htable    The hash table.
+ * @param key       The key to find.
+ *
+ * @return          NULL if there is no such entry; the entry otherwise.
+ */
+void *htable_get(const struct htable *htable, const void *key);
+
+/**
+ * Get an entry from the hash table and remove it.
+ *
+ * @param htable    The hash table.
+ * @param key       The key for the entry find and remove.
+ * @param found_key (out param) NULL if the entry was not found; the found key
+ *                      otherwise.
+ * @param found_val (out param) NULL if the entry was not found; the found
+ *                      value otherwise.
+ */
+void htable_pop(struct htable *htable, const void *key,
+                void **found_key, void **found_val);
+
+/**
+ * Get the number of entries used in the hash table.
+ *
+ * @param htable    The hash table.
+ *
+ * @return          The number of entries used in the hash table.
+ */
+uint32_t htable_used(const struct htable *htable);
+
+/**
+ * Get the capacity of the hash table.
+ *
+ * @param htable    The hash table.
+ *
+ * @return          The capacity of the hash table.
+ */
+uint32_t htable_capacity(const struct htable *htable);
+
+/**
+ * Hash a string.
+ *
+ * @param str       The string.
+ * @param max       Maximum hash value
+ *
+ * @return          A number less than max.
+ */
+uint32_t ht_hash_string(const void *str, uint32_t max);
+
+/**
+ * Compare two strings.
+ *
+ * @param a         The first string.
+ * @param b         The second string.
+ *
+ * @return          1 if the strings are identical; 0 otherwise.
+ */
+int ht_compare_string(const void *a, const void *b);
+
+#endif
+
+// vim: ts=4:sw=4:tw=79:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/util/log.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/util/log.c b/htrace-c/src/util/log.c
new file mode 100644
index 0000000..64e99de
--- /dev/null
+++ b/htrace-c/src/util/log.c
@@ -0,0 +1,104 @@
+/**
+ * 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.
+ */
+
+#include "core/conf.h"
+#include "core/htrace.h"
+#include "util/log.h"
+
+#include <errno.h>
+#include <pthread.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+struct htrace_log {
+    /**
+     * The lock which protects this log from concurrent writes.
+     */
+    pthread_mutex_t lock;
+
+    /**
+     * The log file.
+     */
+    FILE *fp;
+
+    /**
+     * Nonzero if we should close this file when closing the log.
+     */
+    int should_close;
+};
+
+struct htrace_log *htrace_log_alloc(const struct htrace_conf *conf)
+{
+    struct htrace_log *lg;
+    const char *path;
+
+    lg = calloc(1, sizeof(*lg));
+    if (!lg) {
+        fprintf(stderr, "htrace_log_alloc: out of memory.\n");
+        return NULL;
+    }
+    path = htrace_conf_get(conf, HTRACE_LOG_PATH_KEY);
+    if (!path) {
+        lg->fp = stderr;
+        return lg;
+    }
+    lg->fp = fopen(path, "a");
+    if (!lg->fp) {
+        int err = errno;
+        fprintf(stderr, "htrace_log_alloc: failed to open %s for "
+                "append: %d (%s).\n",
+                path, err, terror(err));
+        lg->fp = stderr;
+        return lg;
+    }
+    // If we're logging to a file, we need to close the file when we close the
+    // log.
+    lg->should_close = 1;
+    return lg;
+}
+
+void htrace_log_free(struct htrace_log *lg)
+{
+    if (!lg) {
+        return;
+    }
+    pthread_mutex_destroy(&lg->lock);
+    if (lg->should_close) {
+        fclose(lg->fp);
+    }
+    free(lg);
+}
+
+static void htrace_logv(struct htrace_log *lg, const char *fmt, va_list ap)
+{
+    pthread_mutex_lock(&lg->lock);
+    vfprintf(lg->fp, fmt, ap);
+    pthread_mutex_unlock(&lg->lock);
+}
+
+void htrace_log(struct htrace_log *lg, const char *fmt, ...)
+{
+    va_list ap;
+    va_start(ap, fmt);
+    htrace_logv(lg, fmt, ap);
+    va_end(ap);
+}
+
+// vim: ts=4:sw=4:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/util/log.h
----------------------------------------------------------------------
diff --git a/htrace-c/src/util/log.h b/htrace-c/src/util/log.h
new file mode 100644
index 0000000..52f8ed2
--- /dev/null
+++ b/htrace-c/src/util/log.h
@@ -0,0 +1,69 @@
+/**
+ * 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.
+ */
+
+#ifndef APACHE_HTRACE_UTIL_LOG_H
+#define APACHE_HTRACE_UTIL_LOG_H
+
+/**
+ * @file log.h
+ *
+ * Functions for HTrace logging.
+ *
+ * This is an internal header, not intended for external use.
+ */
+
+struct htrace_conf;
+
+/**
+ * Allocate a new htrace_log.
+ *
+ * @param conf          The configuration.  Will be deep copied.
+ *
+ * @return              The htrace_log, or NULL on OOM.
+ */
+struct htrace_log *htrace_log_alloc(const struct htrace_conf *conf);
+
+/**
+ * A thread-safe version of the strerror call.
+ *
+ * @param err           The POSIX error code.
+ *
+ * @return              A static or thread-local error string.
+ */
+const char *terror(int err);
+
+/**
+ * Free an htrace_log.
+ *
+ * @param lg            The log to be freed.
+ */
+void htrace_log_free(struct htrace_log *lg);
+
+/**
+ * Create an htrace log message.
+ *
+ * @param lg            The log to use.
+ * @param fmt           The format string to use.
+ * @param ...           Printf-style variable length arguments.
+ */
+void htrace_log(struct htrace_log *lg, const char *fmt, ...)
+      __attribute__((format(printf, 2, 3)));
+
+#endif
+
+// vim: ts=4:sw=4:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/util/process_id.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/util/process_id.c b/htrace-c/src/util/process_id.c
new file mode 100644
index 0000000..c11b693
--- /dev/null
+++ b/htrace-c/src/util/process_id.c
@@ -0,0 +1,304 @@
+/**
+ * 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.
+ */
+
+#include "util/log.h"
+#include "util/process_id.h"
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <ifaddrs.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+/**
+ * @file process_id.c
+ *
+ * Implements process IDs for the HTrace C client.
+ */
+
+/**
+ * The maximum number of bytes that can be in a process ID substitution
+ * variable.
+ */
+#define MAX_VAR_NAME 32
+
+enum ip_addr_type {
+    ADDR_TYPE_IPV6_LOOPBACK = 0,
+    ADDR_TYPE_IPV4_LOOPBACK,
+    ADDR_TYPE_IPV6_OTHER,
+    ADDR_TYPE_IPV4_OTHER,
+    ADDR_TYPE_IPV6_SITE_LOCAL,
+    ADDR_TYPE_IPV4_SITE_LOCAL
+};
+
+static int handle_process_subst_var(struct htrace_log *lg, char **out,
+                                    const char *var, const char *tname);
+
+enum ip_addr_type get_ipv4_addr_type(const struct sockaddr_in *ip);
+
+enum ip_addr_type get_ipv6_addr_type(const struct sockaddr_in6 *ip);
+
+static int append_char(char **out, int *j, char c)
+{
+    char *nout = realloc(*out, *j + 2); // leave space for NULL
+    if (!nout) {
+        return 0;
+    }
+    *out = nout;
+    (*out)[*j] = c;
+    *j = *j + 1;
+    (*out)[*j] = '\0';
+    return 1;
+}
+
+char *calculate_process_id(struct htrace_log *lg, const char *fmt,
+                           const char *tname)
+{
+    int i = 0, j = 0, escaping = 0, v = 0;
+    char *out = NULL, *var = NULL;
+
+    out = strdup("");
+    if (!out) {
+        goto oom;
+    }
+    while (1) {
+        char c = fmt[i++];
+        if (c == '\0') {
+            break;
+        } else if (c == '\\') {
+            if (!escaping) {
+                escaping = 1;
+                continue;
+            }
+        }
+        switch (v) {
+        case 0:
+            if (c == '%') {
+                if (!escaping) {
+                    if (!append_char(&var, &v, '%')) {
+                        goto oom;
+                    }
+                    continue;
+                }
+            }
+            break;
+        case 1:
+            if (c == '{') {
+                if (!escaping) {
+                    if (!append_char(&var, &v, '{')) {
+                        goto oom;
+                    }
+                    continue;
+                }
+            }
+            if (!append_char(&out, &j, '%')) {
+                goto oom;
+            }
+            break;
+        default:
+            if (c == '}') {
+                if (!escaping) {
+                    if (!append_char(&var, &v, '}')) {
+                        goto oom;
+                    }
+                    var[v++] = '\0';
+                    if (!handle_process_subst_var(lg, &out, var, tname)) {
+                        goto oom;
+                    }
+                    free(var);
+                    var = NULL;
+                    j = strlen(out);
+                    v = 0;
+                    continue;
+                }
+            }
+            escaping = 0;
+            if (!append_char(&var, &v, c)) {
+                goto oom;
+            }
+            continue;
+        }
+        escaping = 0;
+        v = 0;
+        if (!append_char(&out, &j, c)) {
+            goto oom;
+        }
+    }
+    out[j] = '\0';
+    if (v > 0) {
+      htrace_log(lg, "calculate_process_id(%s): unterminated process ID "
+                 "substitution variable at the end of the format string.",
+                 fmt);
+    }
+    free(var);
+    return out;
+
+oom:
+    htrace_log(lg, "calculate_process_id(tname=%s): OOM\n", tname);
+    free(out);
+    free(var);
+    return NULL;
+}
+
+static int handle_process_subst_var(struct htrace_log *lg, char **out,
+                                    const char *var, const char *tname)
+{
+    char *nout = NULL;
+
+    if (strcmp(var, "%{tname}") == 0) {
+        if (asprintf(&nout, "%s%s", *out, tname) < 0) {
+            htrace_log(lg, "handle_process_subst_var(var=%s): OOM", var);
+            return 0;
+        }
+        free(*out);
+        *out = nout;
+    } else if (strcmp(var, "%{ip}") == 0) {
+        char ip_str[256];
+        get_best_ip(lg, ip_str, sizeof(ip_str));
+        if (asprintf(&nout, "%s%s", *out, ip_str) < 0) {
+            htrace_log(lg, "handle_process_subst_var(var=%s): OOM", var);
+            return 0;
+        }
+        free(*out);
+        *out = nout;
+    } else if (strcmp(var, "%{pid}") == 0) {
+        char pid_str[64];
+        pid_t pid = getpid();
+
+        snprintf(pid_str, sizeof(pid_str), "%lld", (long long)pid);
+        if (asprintf(&nout, "%s%s", *out, pid_str) < 0) {
+            htrace_log(lg, "handle_process_subst_var(var=%s): OOM", var);
+            return 0;
+        }
+        free(*out);
+        *out = nout;
+    } else {
+        htrace_log(lg, "handle_process_subst_var(var=%s): unknown process "
+                   "ID substitution variable.\n", var);
+    }
+    return 1;
+}
+
+/**
+ * Get the "best" IP address for this node.
+ *
+ * This is complicated since nodes can have multiple network interfaces,
+ * and each network interface can have multiple IP addresses.  What we're
+ * looking for here is an IP address that will serve to identify this node
+ * to HTrace.  So we prefer site-local addresess (i.e. private ones on the
+ * LAN) to publicly routable interfaces.  If there are multiple addresses
+ * to choose from, we select the one which comes first in textual sort
+ * order.  This should ensure that we at least consistently call each node
+ * by a single name.
+ */
+void get_best_ip(struct htrace_log *lg, char *ip_str, size_t ip_str_len)
+{
+    struct ifaddrs *head, *ifa;
+    enum ip_addr_type ty = ADDR_TYPE_IPV4_LOOPBACK, nty;
+    char temp_ip_str[128];
+
+    snprintf(ip_str, ip_str_len, "%s", "127.0.0.1");
+    if (getifaddrs(&head) < 0) {
+        int res = errno;
+        htrace_log(lg, "get_best_ip: getifaddrs failed: %s\n", terror(res));
+        return;
+    }
+    for (ifa = head; ifa; ifa = ifa->ifa_next){
+        if (!ifa->ifa_addr) {
+            continue;
+        }
+        if (ifa->ifa_addr->sa_family == AF_INET) {
+            struct sockaddr_in *addr =
+                (struct sockaddr_in *)ifa->ifa_addr;
+            nty = get_ipv4_addr_type(addr);
+            if (nty < ty) {
+                continue;
+            }
+            if (!inet_ntop(AF_INET, &addr->sin_addr, temp_ip_str,
+                           sizeof(temp_ip_str))) {
+                htrace_log(lg, "get_best_ip_impl: inet_ntop(%s, AF_INET) "
+                           "failed\n", ifa->ifa_name);
+                continue;
+            }
+            if ((nty == ty) && (strcmp(temp_ip_str, ip_str) > 0)) {
+                continue;
+            }
+            snprintf(ip_str, ip_str_len, "%s", temp_ip_str);
+            ty = nty;
+        } else if (ifa->ifa_addr->sa_family == AF_INET6) {
+            struct sockaddr_in6 *addr =
+                (struct sockaddr_in6 *)ifa->ifa_addr;
+            nty = get_ipv6_addr_type(addr);
+            if (nty < ty) {
+                continue;
+            }
+            if (!inet_ntop(AF_INET6, &addr->sin6_addr, temp_ip_str,
+                           sizeof(temp_ip_str))) {
+                htrace_log(lg, "get_best_ip_impl: inet_ntop(%s, AF_INET6) "
+                           "failed\n", ifa->ifa_name);
+                continue;
+            }
+            if ((nty == ty) && (strcmp(temp_ip_str, ip_str) > 0)) {
+                continue;
+            }
+            snprintf(ip_str, ip_str_len, "%s", temp_ip_str);
+            ty = nty;
+        }
+    }
+    freeifaddrs(head);
+}
+
+enum ip_addr_type get_ipv4_addr_type(const struct sockaddr_in *ip)
+{
+    union {
+        uint8_t b[4];
+        uint32_t addr;
+    } addr;
+    addr.addr = ip->sin_addr.s_addr; // always big-endian
+    if (addr.b[0] == 127) { // 127.0.0.0/24
+        return ADDR_TYPE_IPV4_LOOPBACK;
+    }
+    if ((addr.b[0] == 10) && (addr.b[1] == 0) && (addr.b[2] == 0)) {
+        return ADDR_TYPE_IPV4_SITE_LOCAL; // 10.0.0.0/8
+    }
+    if ((addr.b[0] == 192) && (addr.b[1] == 168)) {
+        return ADDR_TYPE_IPV4_SITE_LOCAL; // 192.168.0.0/16
+    }
+    if ((addr.b[0] == 172) && (addr.b[1] == 16) && ((addr.b[2] & 0xf0) == 0)) {
+        return ADDR_TYPE_IPV4_SITE_LOCAL; // 172.16.0.0/12
+    }
+    return ADDR_TYPE_IPV4_OTHER;
+}
+
+enum ip_addr_type get_ipv6_addr_type(const struct sockaddr_in6 *ip)
+{
+    if (IN6_IS_ADDR_LOOPBACK(ip)) {
+        return ADDR_TYPE_IPV6_LOOPBACK;
+    } else if (IN6_IS_ADDR_SITELOCAL(ip)) {
+        return ADDR_TYPE_IPV6_SITE_LOCAL;
+    } else {
+        return ADDR_TYPE_IPV6_OTHER;
+    }
+}
+
+// vim:ts=4:sw=4:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/util/process_id.h
----------------------------------------------------------------------
diff --git a/htrace-c/src/util/process_id.h b/htrace-c/src/util/process_id.h
new file mode 100644
index 0000000..ff702ce
--- /dev/null
+++ b/htrace-c/src/util/process_id.h
@@ -0,0 +1,58 @@
+/**
+ * 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.
+ */
+
+#ifndef APACHE_HTRACE_UTIL_PROCESS_ID_H
+#define APACHE_HTRACE_UTIL_PROCESS_ID_H
+
+#include <unistd.h> /* for size_t */
+
+/**
+ * @file process_id.h
+ *
+ * Implements process IDs for the HTrace C client.
+ *
+ * This is an internal header, not intended for external use.
+ */
+
+struct htrace_log;
+
+/**
+ * Calculate the process ID.
+ *
+ * @param lg                A log object which will be used to report warnings.
+ * @param fmt               The user-provided string to use when calculating the
+ *                              process ID.
+ * @param tname             The name supplied when creating the htracer.
+ *
+ * @return                  NULL on OOM; the process ID otherwise.
+ */
+char *calculate_process_id(struct htrace_log *lg, const char *fmt,
+                           const char *tname);
+
+/**
+ * Get the best IP address representing this host.
+ *
+ * @param lg                A log object which will be used to report warnings.
+ * @param ip_str            (out param) output string
+ * @param ip_str_len        Length of output string
+ */
+void get_best_ip(struct htrace_log *lg, char *ip_str, size_t ip_str_len);
+
+#endif
+
+// vim: ts=4: sw=4: et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/util/rand.h
----------------------------------------------------------------------
diff --git a/htrace-c/src/util/rand.h b/htrace-c/src/util/rand.h
new file mode 100644
index 0000000..3b55006
--- /dev/null
+++ b/htrace-c/src/util/rand.h
@@ -0,0 +1,63 @@
+/**
+ * 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.
+ */
+
+#ifndef APACHE_HTRACE_UTIL_RAND_H
+#define APACHE_HTRACE_UTIL_RAND_H
+
+/**
+ * @file rand.h
+ *
+ * Functions for providing thread-safe random numbers.
+ *
+ * This is an internal header, not intended for external use.
+ */
+
+#include <stdint.h>
+
+struct htrace_log;
+struct random_src;
+
+/**
+ * Creates a random source.
+ *
+ * @param log     The htrace_log object to use.  We may hold on to a reference
+ *                    to this log.
+ * @return        NULL on OOM; the random source otherwise.
+ */
+struct random_src *random_src_alloc(struct htrace_log *lg);
+
+/**
+ * Frees a random source.
+ *
+ * @param rnd     The random source.
+ */
+void random_src_free(struct random_src *rnd);
+
+/**
+ * Gets a random 32-bit number from the random source.
+ */
+uint32_t random_u32(struct random_src *rnd);
+
+/**
+ * Gets a random 64-bit number from the random source.
+ */
+uint64_t random_u64(struct random_src *rnd);
+
+#endif
+
+// vim: ts=4:sw=4:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/util/rand_linux.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/util/rand_linux.c b/htrace-c/src/util/rand_linux.c
new file mode 100644
index 0000000..960a1f6
--- /dev/null
+++ b/htrace-c/src/util/rand_linux.c
@@ -0,0 +1,143 @@
+/**
+ * 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.
+ */
+
+#include "util/log.h"
+#include "util/rand.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#define URANDOM_PATH "/dev/urandom"
+
+#define PSAMP_THREAD_LOCAL_BUF_LEN 256
+
+/**
+ * @file rand_linux.c
+ *
+ * A Linux implementation of a random number source.  This implementation reads
+ * random numbers from /dev/urandom.  To avoid reading from /dev/urandom too
+ * often, we have a thread-local cache of random data.  This is done using ELF
+ * TLS.
+ */
+
+struct random_src {
+    /**
+     * The HTrace log.
+     */
+    struct htrace_log *lg;
+
+    /**
+     * File descriptor for /dev/urandom.
+     */
+    int urandom_fd;
+};
+
+/**
+ * A thread-local cache of random bits.
+ */
+static __thread uint32_t g_rnd_cache[PSAMP_THREAD_LOCAL_BUF_LEN];
+
+/**
+ * An index into our thread-local cache of random bits.
+ */
+static __thread int g_rnd_cache_idx = PSAMP_THREAD_LOCAL_BUF_LEN;
+
+static void refill_rand_cache(struct random_src *rnd)
+{
+    size_t total = 0;
+
+    while (1) {
+        ssize_t res;
+        ssize_t rem = (PSAMP_THREAD_LOCAL_BUF_LEN * sizeof(uint32_t)) - total;
+        if (rem == 0) {
+            break;
+        }
+        res = read(rnd->urandom_fd, ((uint8_t*)&g_rnd_cache) + total, rem);
+        if (res < 0) {
+            int err = errno;
+            if (err == EINTR) {
+                continue;
+            }
+            htrace_log(rnd->lg, "refill_rand_cache: error refilling "
+                       "random cache: %d (%s)\n", err,
+                       terror(err));
+            return;
+        }
+        total += res;
+    }
+    g_rnd_cache_idx = 0;
+}
+
+struct random_src *random_src_alloc(struct htrace_log *lg)
+{
+    struct random_src *rnd;
+    int err;
+
+    rnd = calloc(1, sizeof(*rnd));
+    if (!rnd) {
+        htrace_log(lg, "random_src_alloc: OOM\n");
+        return NULL;
+    }
+    rnd->urandom_fd = open(URANDOM_PATH, O_RDONLY);
+    if (rnd->urandom_fd < 0) {
+        err = errno;
+        htrace_log(lg, "random_src_alloc: failed to open "
+                   URANDOM_PATH ": error %d (%s)\n", err,
+                   terror(err));
+        free(rnd);
+        return NULL;
+    }
+    rnd->lg = lg;
+    return rnd;
+}
+
+void random_src_free(struct random_src *rnd)
+{
+    if (!rnd) {
+        return;
+    }
+    if (close(rnd->urandom_fd)) {
+        int err = errno;
+        htrace_log(rnd->lg, "linux_prob_sampler_free: close error: "
+                   "%d (%s)\n", err, terror(err));
+    }
+    free(rnd);
+}
+
+uint32_t random_u32(struct random_src *rnd)
+{
+    if (g_rnd_cache_idx >= PSAMP_THREAD_LOCAL_BUF_LEN) {
+        refill_rand_cache(rnd);
+    }
+    return g_rnd_cache[g_rnd_cache_idx++];
+}
+
+uint64_t random_u64(struct random_src *rnd)
+{
+    uint64_t val = random_u32(rnd);
+    val <<= 32;
+    return val | random_u32(rnd);
+}
+
+// vim: ts=4:sw=4:tw=79:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/util/rand_posix.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/util/rand_posix.c b/htrace-c/src/util/rand_posix.c
new file mode 100644
index 0000000..5b2c0e7
--- /dev/null
+++ b/htrace-c/src/util/rand_posix.c
@@ -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.
+ */
+
+#include "util/log.h"
+#include "util/rand.h"
+
+#include <errno.h>
+#include <inttypes.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/**
+ * @file rand_posix.c
+ *
+ * A POSIX implementation of random number source.  We have to use a mutex here
+ * to protect the rand_r() state.  If we used rand() instead, we would also
+ * need to use a mutex.  The standard POSIX functions for getting random
+ * numbers are actually really unfortunate in many ways.  Hopefully we can
+ * provide platform-specific implementatinos of the probability sampler for all
+ * the major platforms.
+ */
+
+/**
+ * A sampler that fires with a certain probability.
+ */
+struct random_src {
+    /**
+     * Mutex protecting rand_r.
+     */
+    pthread_mutex_t lock;
+
+    /**
+     * State used with rand_r.
+     */
+    unsigned int rand_state;
+};
+
+struct random_src *random_src_alloc(struct htrace_log *lg)
+{
+    struct random_src *rnd;
+    int ret;
+
+    rnd = calloc(1, sizeof(*rnd));
+    if (!rnd) {
+        htrace_log(lg, "random_src_alloc: OOM\n");
+        return NULL;
+    }
+    ret = pthread_mutex_init(&rnd->lock, NULL);
+    if (ret) {
+        htrace_log(lg, "random_src_alloc: pthread_mutex_create "
+                   "failed: error %d (%s)\n", ret, terror(ret));
+        free(rnd);
+        return NULL;
+    }
+    return rnd;
+}
+
+void random_src_free(struct random_src *rnd)
+{
+    if (!rnd) {
+        return;
+    }
+    pthread_mutex_destroy(&rnd->lock);
+    free(rnd);
+}
+
+uint32_t random_u32(struct random_src *rnd)
+{
+    uint32_t val = 0;
+    pthread_mutex_lock(&rnd->lock);
+    // rand_r gives at least 15 bits of randomness.
+    // So we need to xor it 3 times to get 32 bits' worth.
+    val ^= rand_r(&rnd->rand_state);
+    val <<= 15;
+    val ^= rand_r(&rnd->rand_state);
+    val <<= 15;
+    val ^= rand_r(&rnd->rand_state);
+    val <<= 15;
+    pthread_mutex_unlock(&rnd->lock);
+    return val;
+}
+
+uint64_t random_u64(struct random_src *rnd)
+{
+    uint64_t val = 0;
+    pthread_mutex_lock(&rnd->lock);
+    // rand_r gives at least 15 bits of randomness.
+    // So we need to xor it 5 times to get 64 bits' worth.
+    val ^= rand_r(&rnd->rand_state);
+    val <<= 15;
+    val ^= rand_r(&rnd->rand_state);
+    val <<= 15;
+    val ^= rand_r(&rnd->rand_state);
+    val <<= 15;
+    val ^= rand_r(&rnd->rand_state);
+    val <<= 15;
+    val ^= rand_r(&rnd->rand_state);
+    val <<= 15;
+    pthread_mutex_unlock(&rnd->lock);
+    return val;
+}
+
+// vim: ts=4:sw=4:tw=79:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/util/string.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/util/string.c b/htrace-c/src/util/string.c
new file mode 100644
index 0000000..e9a4e91
--- /dev/null
+++ b/htrace-c/src/util/string.c
@@ -0,0 +1,107 @@
+/**
+ * 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.
+ */
+
+#include "util/log.h"
+#include "util/string.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+int fwdprintf(char **buf, int* rem, const char *fmt, ...)
+{
+    int amt, res;
+    char *b;
+    va_list ap;
+
+    if (!buf) {
+        char tmp[1] = { 0 };
+        va_start(ap, fmt);
+        res = vsnprintf(tmp, sizeof(tmp), fmt, ap);
+        va_end(ap);
+        if (res < 0) {
+            res = 0;
+        }
+        return res;
+    }
+    b = *buf;
+    va_start(ap, fmt);
+    amt = *rem;
+    res = vsnprintf(b, amt, fmt, ap);
+    va_end(ap);
+    if (res < 0) {
+        res = 0;
+    } else {
+        int sub = (amt < res) ? amt : res;
+        *rem = amt - sub;
+        *buf = b + sub;
+    }
+    return res;
+}
+
+int validate_json_string(struct htrace_log *lg, const char *str)
+{
+    const unsigned char *b = (const unsigned char *)str;
+    int off = 0;
+
+    while(*b) {
+        // Note: we don't allow newline (0x0a), tab (0x09), or carriage return
+        // (0x0d) because they cause problems down the line.
+        if (((0x20 <= b[0] && b[0] <= 0x7E)) &&
+                ((b[0] != '"') && (b[0] != '\\'))) {
+            b++;
+            off++;
+            continue;
+        }
+        if((0xC2 <= b[0] && b[0] <= 0xDF) && (0x80 <= b[1] && b[1] <= 0xBF)) {
+            b += 2; // 2-byte UTF-8, U+0080 to U+07FF
+            off += 2;
+            continue;
+        }
+        if ((b[0] == 0xe0 &&
+                    (0xa0 <= b[1] && b[1] <= 0xbf) &&
+                    (0x80 <= b[2] && b[2] <= 0xbf)
+                ) || (
+                    ((0xe1 <= b[0] && b[0] <= 0xec) ||
+                        b[0] == 0xee ||
+                        b[0] == 0xef) &&
+                    (0x80 <= b[1] && b[1] <= 0xbf) &&
+                    (0x80 <= b[2] && b[2] <= 0xbf)
+                ) || (
+                    b[0] == 0xed &&
+                    (0x80 <= b[1] && b[1] <= 0x9f) &&
+                    (0x80 <= b[2] && b[2] <= 0xbf)
+                )) {
+            b += 3; // 3-byte UTF-8, U+0800 U+FFFF
+            off += 3;
+            continue;
+        }
+        // Note: we don't allow code points outside the basic multilingual plane
+        // (BMP) at the moment.  The problem with them is that Javascript
+        // doesn't support them directly (they have to be encoded with UCS-2
+        // surrogate pairs).  TODO: teach htraced to do that encoding.
+        if (lg) {
+            htrace_log(lg, "validate_json_string(%s): byte %d (0x%02x) "
+                       "was problematic.\n", str, off, b[0]);
+        }
+        return 0;
+    }
+    return 1;
+}
+
+// vim: ts=4:sw=4:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/util/string.h
----------------------------------------------------------------------
diff --git a/htrace-c/src/util/string.h b/htrace-c/src/util/string.h
new file mode 100644
index 0000000..77e764b
--- /dev/null
+++ b/htrace-c/src/util/string.h
@@ -0,0 +1,64 @@
+/**
+ * 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.
+ */
+
+#ifndef APACHE_HTRACE_UTIL_STRING_H
+#define APACHE_HTRACE_UTIL_STRING_H
+
+/**
+ * @file string.h
+ *
+ * Functions for manipulating strings.
+ *
+ * This is an internal header, not intended for external use.
+ */
+
+struct htrace_log;
+
+/**
+ * Print to a buffer and move the pointer forward by the number of bytes
+ * written.
+ *
+ * @param buf           (inout) The buffer to write to.  We will advance this
+ *                          buffer by the number of bytes written.
+ *                          If this buffer is NULL, nothing will be written.
+ * @param rem           (inout) The maximum number of bytes to write to the
+ *                          buffer.  If bytes are written to the buffer, this
+ *                          number will be decremented by that amount.
+ * @param fmt           Printf-style format string.
+ *
+ * @return              The number of bytes that would have been used if bytes
+ *                          had been written
+ */
+int fwdprintf(char **buf, int* rem, const char *fmt, ...)
+      __attribute__((format(printf, 3, 4)));
+
+/**
+ * Validate that a string could appear in a JSON expression without causing
+ * problems.  We don't accept control characters, double quotes, backslashes,
+ * tabs, newlines, or carriage returns.
+ *
+ * @param lg            The log to print messages about invalid strings to.
+ * @param str           The string.
+ *
+ * @return              0 if the string is problematic; 1 if it's safe.
+ */
+int validate_json_string(struct htrace_log *lg, const char *str);
+
+#endif
+
+// vim: ts=4:sw=4:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/util/terror.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/util/terror.c b/htrace-c/src/util/terror.c
new file mode 100644
index 0000000..6db1fd8
--- /dev/null
+++ b/htrace-c/src/util/terror.c
@@ -0,0 +1,65 @@
+/**
+ * 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.
+ */
+
+// Get the POSIX definition of strerror_r.
+#define _POSIX_C_SOURCE 200112L
+#undef _GNU_SOURCE
+
+#include "util/build.h"
+#include "util/log.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+/**
+ * @file terror.c
+ *
+ * Implements the thread-safe terror() function.
+ *
+ * glibc makes it difficult to get access to the POSIX definition of strerror_r.
+ * Keeping this in a separate file allows us to put the proper macro magic at
+ * the top of just this file.
+ */
+
+#ifdef HAVE_IMPROVED_TLS
+const char *terror(int err)
+{
+    static __thread char buf[4096];
+    int ret;
+
+    ret = strerror_r(err, buf, sizeof(buf));
+    if (ret) {
+        return "unknown error";
+    }
+    return buf;
+}
+#else
+extern const char *sys_errlist[];
+extern int sys_nerr;
+
+const char *terror(int err)
+{
+    if (err >= sys_nerr) {
+        return "unknown error";
+    }
+    return sys_errlist[err];
+}
+#endif
+
+// vim: ts=4:sw=4:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/util/time.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/util/time.c b/htrace-c/src/util/time.c
new file mode 100644
index 0000000..d4ad8ed
--- /dev/null
+++ b/htrace-c/src/util/time.c
@@ -0,0 +1,96 @@
+/**
+ * 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.
+ */
+
+#include "util/log.h"
+#include "util/time.h"
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/time.h>
+#include <time.h>
+
+uint64_t timespec_to_ms(const struct timespec *ts)
+{
+    uint64_t seconds_ms, microseconds_ms;
+    seconds_ms = ts->tv_sec;
+    seconds_ms *= 1000LLU;
+    microseconds_ms = ts->tv_nsec;
+    microseconds_ms /= 1000000LLU;
+    return seconds_ms + microseconds_ms;
+}
+
+void ms_to_timespec(uint64_t ms, struct timespec *ts)
+{
+    uint64_t sec = ms / 1000LLU;
+    ts->tv_sec = sec;
+    ms -= (sec * 1000LLU);
+    ts->tv_nsec = ms * 1000000LLU;
+}
+
+uint64_t now_ms(struct htrace_log *lg)
+{
+    struct timespec ts;
+    int err;
+
+    if (clock_gettime(CLOCK_REALTIME, &ts)) {
+        err = errno;
+        if (lg) {
+            htrace_log(lg, "clock_gettime(CLOCK_REALTIME) error: %d (%s)\n",
+                       err, terror(err));
+        }
+        return 0;
+    }
+    return timespec_to_ms(&ts);
+}
+
+uint64_t monotonic_now_ms(struct htrace_log *lg)
+{
+    struct timespec ts;
+    int err;
+
+    if (clock_gettime(CLOCK_MONOTONIC, &ts)) {
+        err = errno;
+        if (lg) {
+            htrace_log(lg, "clock_gettime(CLOCK_MONOTONIC) error: %d (%s)\n",
+                       err, terror(err));
+        }
+        return 0;
+    }
+    return timespec_to_ms(&ts);
+}
+
+void sleep_ms(uint64_t ms)
+{
+    struct timespec req, rem;
+
+    ms_to_timespec(ms, &req);
+    memset(&rem, 0, sizeof(rem));
+    do {
+        if (nanosleep(&req, &rem) < 0) {
+            if (errno == EINTR) {
+                rem.tv_sec = req.tv_sec;
+                rem.tv_nsec = req.tv_nsec;
+                continue;
+            }
+        }
+    } while (0);
+}
+
+// vim: ts=4:sw=4:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/util/time.h
----------------------------------------------------------------------
diff --git a/htrace-c/src/util/time.h b/htrace-c/src/util/time.h
new file mode 100644
index 0000000..9b4f5d4
--- /dev/null
+++ b/htrace-c/src/util/time.h
@@ -0,0 +1,79 @@
+/**
+ * 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.
+ */
+
+#ifndef APACHE_HTRACE_UTIL_TIME_H
+#define APACHE_HTRACE_UTIL_TIME_H
+
+/**
+ * @file time.h
+ *
+ * Functions for dealing with time.
+ *
+ * This is an internal header, not intended for external use.
+ */
+
+#include <stdint.h>
+
+struct htrace_log;
+struct timespec;
+
+/**
+ * Convert a timespec into a time in milliseconds.
+ *
+ * @param ts            The timespec to convert.
+ *
+ * @return              The current time in milliseconds.
+ */
+uint64_t timespec_to_ms(const struct timespec *ts);
+
+/**
+ * Convert a time in milliseconds into a timespec.
+ *
+ * @param ms            The time in milliseconds to convert.
+ * @param ts            (out param) The timespec to fill.
+ */
+void ms_to_timespec(uint64_t ms, struct timespec *ts);
+
+/**
+ * Get the current wall-clock time in milliseconds.
+ *
+ * @param log           The log to use for error messsages.
+ *
+ * @return              The current wall-clock time in milliseconds.
+ */
+uint64_t now_ms(struct htrace_log *log);
+
+/**
+ * Get the current monotonic time in milliseconds.
+ *
+ * @param log           The log to use for error messsages.
+ *
+ * @return              The current monotonic clock time in milliseconds.
+ */
+uint64_t monotonic_now_ms(struct htrace_log *log);
+
+/**
+ * Sleep for at least a given number of milliseconds.
+ *
+ * @param ms            The number of milliseconds to sleep.
+ */
+void sleep_ms(uint64_t ms);
+
+#endif
+
+// vim: ts=4:sw=4:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/style.txt
----------------------------------------------------------------------
diff --git a/htrace-c/style.txt b/htrace-c/style.txt
new file mode 100644
index 0000000..d3c042e
--- /dev/null
+++ b/htrace-c/style.txt
@@ -0,0 +1,219 @@
+HTrace C client coding style
+===============================================================================
+WHITESPACE
+    The HTrace C client uses 4-space indentation.  We use spaces rather than
+hard tabs.  4 space tabs discourage excessive nesting, which makes C code
+harder to understand.  We do not use hard tabs because it requires additional
+editor configuration for many people to see them as intended.
+
+    We have a 79-column limit on line lengths.  Excessively long lines make it
+difficult to perform side-by side diffs during a code review.  They are also
+difficult to read.
+
+    The opening brace for functions is placed on the line after the function
+definition.  Example:
+
+ > void my_function(int foo)
+ > {
+ >     ...
+ > }
+
+    However, the brace for an if statement or structure definition does not get
+its own line:
+ > if (foo) {
+ >     ...
+ > }
+ > struct bar {
+ >     ...
+ > }
+
+    This style visually emphasizes the start of the function, but avoids using
+excessive vertical space for if statements and structures.
+
+    When declaring pointers, we use "int *foo" rather than "int* foo".
+Using the second form implies that "int*" is the type of everything that
+follows.  However, this is incorrect; "int* foo, bar;" declares a foo as a
+pointer-to-int, but bar as an int.  To declare two pointers-to-int on the same
+line in C, you must use int *foo, *bar"-- a reality which the second form makes
+obvious.
+
+    We put braces around all "if" statements, to avoid situations where
+a statement visually appears to be part of an if statement, but in reality is
+not.
+
+
+NAMING
+    We use_underscores for naming rather than CamelCase.  This is a somewhat
+arbitrary choice, but it fits in better with the C standard library and
+most other C libraries.  We do not use "Hungarian Notation," the practice of
+encoding type names into variable names.  Variables have types that are
+enforced by the compiler in C, so this should be sufficient.
+
+    In general, we do not use typedefs for structure names.  Using typedefs for
+structure names prevents the use of forward declarations (see FORWARD
+DECLARATIONS).  It also obscures the true nature of the type... is a foo_t a
+primitive type such as an int, or a structure?  It is usually a bad idea to
+copy a large structure by passing it directly rather than passing a pointer to
+it.  This anti-pattern is more likely to occur if the true nature of the type
+is hidden from the programmer.
+
+    Typedefs are sometimes useful for shortening the type of function pointers.
+They also may be used to represent types that can vary based on architecture of
+platform (although it would be better still to avoid this, most of the time.)
+However, they should not be overused.
+
+    Macros are named in ALL_UPPER_CASE.
+
+
+PUBLIC API
+    Most functions and structures inside the HTrace C client are not "publicly
+visible."  What this means is that they are not accessible to external users of
+libhtrace.so.  These internal functions and structures are part of the
+implementation of libhtrace, not the API.  We have the freedom to change or
+remove them as appropriate without worrying about downstream users being
+affected.
+
+    A few functions and symbols are publicly visible.  These functions are part
+of the "public API" of libhtrace.so.  Every function and structure defined in
+htrace.h is part of the public API.  This public/private separation is enforced
+by the linker, which strips out non-public symbols from the library symbol
+table.
+
+    Publicly visible functions and structures should avoid making architectural
+or platform assumptions.  For example, assuming that time_t is 64 bit is a
+mistake in the public API.
+
+    In general, we want to avoid making backwards-incompatible changes to the
+public API within minor releases of HTrace.  What changes are backwards
+incompatible?  A few examples of backwards incompatible changes are:
+
+* Modifying the types of parameters taken by a publicly visible function.
+* Changing the number of parameters passed to a publicly visible function.
+* Modifying or removing parameters from a publicly visible structure.
+* Removing a publicly visible macro
+
+    In contrast, we can add new functions or structure definitions to the
+public API without breaking backwards compatibility.
+
+    The C++ API is implemented as a header file which wraps the C API.  This
+means that we don't have to worry about C++ binary compatibility issues, which
+can be quite complex.
+
+    The htrace C client exports only the files libhtrace.so, htrace.h, and
+htrace.hpp.  We do not package up our internal header files in the final build!
+They are not accessible or usable outside the library itself.
+
+
+FORWARD DECLARATIONS
+    It is often a good idea to avoid defining a structure in a header file.
+Instead, one can often use a "forward declaration" to make the compiler aware
+that the structure type exists, without specifying its details.  Here is an
+example of a forward declaration:
+
+> struct htrace_conf;
+
+    This declaration notifies the compiler that the type exists.  Most types
+discussed in htrace.h are forward declarations rather than definitions.  This
+gives us the freedom to change the type later, without breaking the public API
+(see PUBLIC API).  Forward declarations can also speed up compilation, by
+minimizing the number of header files that need to be included.
+
+
+ERROR HANDLING
+    C does not have "finally" blocks like Java or a "defer" statement like
+Golang.  As a consequence, programmers must clean up resources which they
+allocate manually.
+
+    One useful pattern for handling errors is the "single exit function"
+pattern.  In this pattern, a function has a single exit point and we perform
+cleanup right before the exit.  An example:
+
+ > int my_function()
+ > {
+ >     int success = 0;
+ >     struct my_resource *resource1 = NULL, *resource2 = NULL;
+ > 
+ >     resource1 = allocate_resource1();
+ >     if (!resource1) {
+ >         goto done;
+ >     }
+ >     resource2 = allocate_resource1();
+ >     if (!resource2) {
+ >         goto done;
+ >     }
+ >     do_stuff(resource1, resource2);
+ >     success = 1;
+ > done:
+ >     if (resource1) {
+ >         free_resource1();
+ >     }
+ >     if (resource2) {
+ >         free_resource2();
+ >     }
+ >     return success;
+ > }
+
+    Similar to a "finally" block in Java, the code after "done" is always
+executed, and will do whatever cleanup is required.  This is much easier and
+more maintainable than trying to manually deallocate whatever is necessary each
+time an error must be handled.  Although this may seem unfamiliar to new C
+programmers, it is a traditional error handling paradigm in kernel code.
+
+    Another error handling paradigm that is sometimes used in HTrace is the
+"error string return."  This paradigm works as follows:
+
+ > void my_function(char *err, size_t err_len)
+ > {
+ >     err[0] = '\0';
+ >     ...
+ >     if (failed) {
+ >         snprintf(err, err_len, "Failed because the foo was %d", foo);
+ >         return;
+ >     }
+ >     ...
+ > }
+
+    The idea behind the error string return is that an error string is more
+flexible than an error code return.  This is generally more useful for
+internal, non-public APIs where there aren't a set of well-defined error codes
+for every possible failure case.  Note that functions which accept an error
+string always initialize the error string to the empty string (no error) as the
+first thing they do.
+
+
+PORTABILITY
+    This code should be portable to both UNIX-based platforms and Microsoft
+Windows.  Although we don't have Windows support yet, it would be nice to
+implement it eventually.
+
+    Using #ifdefs for large blocks of platform-specific code makes source code
+files difficult to read.  When we need to have different implementations of
+something based on the platform or architecture, it is often more appropriate
+to simply exclude or include an entire file from compilation.  This also
+encourages programmers to think about creating platform-neutral interfaces to
+well-encapsulated platform-specific code segments.
+
+
+LIBRARY CONTEXT ISSUES
+    Writing code for a library is more challenging in some ways than writing
+code for an application.
+
+    We cannot call fork() or exec() from our library, because the host
+application may have serious problems with these functions.  For example, if
+the host application has set up atexit() handlers, a fork plus an exit will
+cause those handlers to run unexpectedly.
+
+    We should minimize library dependencies to avoid creating headaches for our
+users.  The more dependencies we use, the more dependencies they must pull in,
+whether they want them or not.  This is why we use the libjson library for unit
+tests, but we do not include it as a dependency of libhtrace.so itself.
+
+    We cannot assume that our implementation of malloc() is the same one used
+by the calling code.  If the library dynamically allocates something, the
+library must also provide a complimentary function to free that thing.  The
+calling code should never call free() or delete on a memory area allocated by
+libhtrace.
+
+    libhtrace may be pulled in "transitively" as a dependency of another
+library.  Or it may be pulled in transitively as well as being used directly by
+the application.  We should support all of these use-cases.

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-core/src/go/src/org/apache/htrace/htraced/rest.go
----------------------------------------------------------------------
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 978862a..1449802 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
@@ -44,7 +44,7 @@ func setResponseHeaders(hdr http.Header) {
 func writeError(lg *common.Logger, w http.ResponseWriter, errCode int,
 	errStr string) {
 	str := strings.Replace(errStr, `"`, `'`, -1)
-	lg.Info(str)
+	lg.Info(str + "\n")
 	w.WriteHeader(errCode)
 	w.Write([]byte(`{ "error" : "` + str + `"}`))
 }

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index c96dc1a..3fb131e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,6 +28,7 @@ language governing permissions and limitations under the License. -->
   <url>http://htrace.incubator.apache.org</url>
 
   <modules>
+    <module>htrace-c</module>
     <module>htrace-core</module>
     <module>htrace-zipkin</module>
     <module>htrace-hbase</module>


[4/4] incubator-htrace git commit: HTRACE-106: htrace: add C / C++ native client (cmccabe)

Posted by cm...@apache.org.
HTRACE-106: htrace: add C / C++ native client (cmccabe)


Project: http://git-wip-us.apache.org/repos/asf/incubator-htrace/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-htrace/commit/a9d85254
Tree: http://git-wip-us.apache.org/repos/asf/incubator-htrace/tree/a9d85254
Diff: http://git-wip-us.apache.org/repos/asf/incubator-htrace/diff/a9d85254

Branch: refs/heads/master
Commit: a9d85254b42380ba09332be6c25d08939b6d794f
Parents: 20f00f9
Author: Colin P. Mccabe <cm...@apache.org>
Authored: Sun Apr 19 17:15:19 2015 -0700
Committer: Colin P. Mccabe <cm...@apache.org>
Committed: Sun Apr 19 17:48:35 2015 -0700

----------------------------------------------------------------------
 htrace-c/BUILDING.txt                           |  32 +
 htrace-c/README.md                              |  31 +
 htrace-c/pom.xml                                | 108 +++
 htrace-c/src/CMakeLists.txt                     | 215 ++++++
 htrace-c/src/core/conf.c                        | 260 ++++++++
 htrace-c/src/core/conf.h                        | 110 ++++
 htrace-c/src/core/htrace.h                      | 346 ++++++++++
 htrace-c/src/core/htrace.hpp                    | 239 +++++++
 htrace-c/src/core/htracer.c                     | 167 +++++
 htrace-c/src/core/htracer.h                     | 101 +++
 htrace-c/src/core/scope.c                       | 166 +++++
 htrace-c/src/core/scope.h                       |  58 ++
 htrace-c/src/core/span.c                        | 187 ++++++
 htrace-c/src/core/span.h                        | 138 ++++
 htrace-c/src/receiver/curl.c                    | 124 ++++
 htrace-c/src/receiver/curl.h                    |  60 ++
 htrace-c/src/receiver/htraced.c                 | 649 +++++++++++++++++++
 htrace-c/src/receiver/local_file.c              | 184 ++++++
 htrace-c/src/receiver/noop.c                    |  65 ++
 htrace-c/src/receiver/receiver.c                |  75 +++
 htrace-c/src/receiver/receiver.h                | 114 ++++
 htrace-c/src/sampler/always.c                   |  71 ++
 htrace-c/src/sampler/never.c                    |  71 ++
 htrace-c/src/sampler/prob.c                     | 137 ++++
 htrace-c/src/sampler/sampler.c                  |  88 +++
 htrace-c/src/sampler/sampler.h                  | 121 ++++
 htrace-c/src/test/conf-unit.c                   |  95 +++
 htrace-c/src/test/htable-unit.c                 |  92 +++
 htrace-c/src/test/htraced_rcv-unit.c            | 110 ++++
 htrace-c/src/test/linkage-unit.c                | 105 +++
 htrace-c/src/test/local_file_rcv-unit.c         |  74 +++
 htrace-c/src/test/log-unit.c                    |  97 +++
 htrace-c/src/test/mini_htraced-unit.c           |  48 ++
 htrace-c/src/test/mini_htraced.c                | 599 +++++++++++++++++
 htrace-c/src/test/mini_htraced.h                | 154 +++++
 htrace-c/src/test/process_id-unit.c             |  80 +++
 htrace-c/src/test/rand-unit.c                   |  93 +++
 htrace-c/src/test/rtest.c                       | 155 +++++
 htrace-c/src/test/rtest.h                       |  76 +++
 htrace-c/src/test/rtestpp.cc                    | 154 +++++
 htrace-c/src/test/sampler-unit.c                | 138 ++++
 htrace-c/src/test/span-unit.c                   | 173 +++++
 htrace-c/src/test/span_table.c                  | 139 ++++
 htrace-c/src/test/span_table.h                  |  95 +++
 htrace-c/src/test/span_util-unit.c              |  74 +++
 htrace-c/src/test/span_util.c                   | 294 +++++++++
 htrace-c/src/test/span_util.h                   |  66 ++
 htrace-c/src/test/string-unit.c                 |  73 +++
 htrace-c/src/test/temp_dir-unit.c               |  89 +++
 htrace-c/src/test/temp_dir.c                    | 204 ++++++
 htrace-c/src/test/temp_dir.h                    |  67 ++
 htrace-c/src/test/test.c                        | 158 +++++
 htrace-c/src/test/test.h                        | 211 ++++++
 htrace-c/src/test/test_config.h.cmake           |  28 +
 htrace-c/src/test/time-unit.c                   |  84 +++
 htrace-c/src/util/build.h.cmake                 |  23 +
 htrace-c/src/util/htable.c                      | 290 +++++++++
 htrace-c/src/util/htable.h                      | 169 +++++
 htrace-c/src/util/log.c                         | 104 +++
 htrace-c/src/util/log.h                         |  69 ++
 htrace-c/src/util/process_id.c                  | 304 +++++++++
 htrace-c/src/util/process_id.h                  |  58 ++
 htrace-c/src/util/rand.h                        |  63 ++
 htrace-c/src/util/rand_linux.c                  | 143 ++++
 htrace-c/src/util/rand_posix.c                  | 120 ++++
 htrace-c/src/util/string.c                      | 107 +++
 htrace-c/src/util/string.h                      |  64 ++
 htrace-c/src/util/terror.c                      |  65 ++
 htrace-c/src/util/time.c                        |  96 +++
 htrace-c/src/util/time.h                        |  79 +++
 htrace-c/style.txt                              | 219 +++++++
 .../go/src/org/apache/htrace/htraced/rest.go    |   2 +-
 pom.xml                                         |   1 +
 73 files changed, 9717 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/BUILDING.txt
----------------------------------------------------------------------
diff --git a/htrace-c/BUILDING.txt b/htrace-c/BUILDING.txt
new file mode 100644
index 0000000..f6728a0
--- /dev/null
+++ b/htrace-c/BUILDING.txt
@@ -0,0 +1,32 @@
+Building the htrace-c client
+===============================================================================
+To build the htrace-c client, activate the native profile.  Example:
+
+    mvn package -DskipTests -Dmaven.javadoc.skip=true -Pnative
+
+BUILD DEPENDENCIES
+    C compiler
+        To compile the sources.
+
+    CMake
+        The CMake build system.  Needed to run the native build.  See
+        www.cmake.org.  This should be available via "yum install cmake" or
+        similar.
+
+    libcurl-devel
+        A library to transfer data over HTTP.  See http://curl.haxx.se/dev/.
+        Should be available via "yum install libcurl-devel" or similar.
+
+TEST DEPENDENCIES
+    C++ compiler
+        To compile some of the unit tests.
+
+    libjson-c
+        A library to parse JSON.  This is only used for unit tests.  See
+        https://github.com/json-c/json-c/wiki.  Should be available via "yum
+        install json-c-devel" or similar.
+
+    htraced binary
+        You must compile the htraced binaries before running the unit tests.
+        You can do this by running "mvn compile" on the top-level project, or
+        in the htrace-htraced directory. 

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/README.md
----------------------------------------------------------------------
diff --git a/htrace-c/README.md b/htrace-c/README.md
new file mode 100644
index 0000000..0347e83
--- /dev/null
+++ b/htrace-c/README.md
@@ -0,0 +1,31 @@
+The HTrace C client
+===============================================================================
+The HTrace C client is useful for distributed systems in C and C++ that need
+tracing.
+
+To use the HTrace C client, you must link against the libhtrace.so library.
+On a UNIX-based platform, you would link against the version of the library
+containing only the major version number.  For example, you might link against
+libhtrace.so.3, which would then link against the appropriate minor version of
+the library, such as libhtrace.so.3.2.0.  The libhtrace API will not change in
+backwards-incompatible ways within a major version.
+
+Some APIs in the library take htrace_conf objects.  You can create these
+objects via htrace_conf_from_str.  A string of the form "KEY1=VAL1;KEY2=VAL2"
+will create a configuration object with KEY1 set to VAL1, KEY2 set to VAL2,
+etc.  htrace.h defines the configuration keys you can set, such as
+HTRACE_LOG_PATH_KEY.
+
+In general, you will want to create a single global htrace_ctx object,
+representing an htrace context object, for your program.  The all threads can
+use this htrace_ctx object.  The htrace_ctx contains all the per-process
+htrace state, such as the process name, the thread-local data, and the htrace
+receiver object that the process is using.
+
+If your process supports orderly shutdown, you can call htrace_ctx_free to
+accomplish this.  However, you should be sure that there are no references to
+the htrace context before freeing it.  Most daemons do not support orderly
+shutdown, so this is not usually a problem.  It is likely to come up in the
+context of a library which uses the native htrace client.
+
+For a quick reference to the basics of trace spans and scopes, see htrace.h.

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/pom.xml
----------------------------------------------------------------------
diff --git a/htrace-c/pom.xml b/htrace-c/pom.xml
new file mode 100644
index 0000000..51e3478
--- /dev/null
+++ b/htrace-c/pom.xml
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed 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. See accompanying LICENSE file.
+-->
+<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">
+  <modelVersion>4.0.0</modelVersion>
+
+  <artifactId>htrace-c</artifactId>
+  <packaging>jar</packaging>
+
+  <parent>
+    <artifactId>htrace</artifactId>
+    <groupId>org.apache.htrace</groupId>
+    <version>3.2.0-incubating-SNAPSHOT</version>
+    <relativePath>..</relativePath>
+  </parent>
+
+  <name>htrace-c</name>
+  <url>http://incubator.apache.org/projects/htrace.html</url>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+
+  <profiles>
+    <profile>
+      <id>native</id>
+      <activation>
+        <activeByDefault>false</activeByDefault>
+      </activation>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-enforcer-plugin</artifactId>
+            <executions>
+              <execution>
+                <id>enforce-os</id>
+                <goals>
+                  <goal>enforce</goal>
+                </goals>
+                <configuration>
+                  <rules>
+                    <requireOS>
+                      <family>mac</family>
+                      <family>unix</family>
+                      <message>The native build is only supported on Mac and other UNIX systems.</message>
+                    </requireOS>
+                  </rules>
+                  <fail>true</fail>
+                </configuration>
+              </execution>
+            </executions>
+          </plugin>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-antrun-plugin</artifactId>
+            <executions>
+              <execution>
+                <id>make</id>
+                <phase>compile</phase>
+                <goals><goal>run</goal></goals>
+                <configuration>
+                  <target>
+                    <mkdir dir="${project.build.directory}/build"/>
+                    <exec executable="cmake" dir="${project.build.directory}/build" failonerror="true">
+                      <arg line="${basedir}/src -DCMAKE_INSTALL_PREFIX=${project.build.directory}/install"/>
+                    </exec>
+                    <exec executable="make" dir="${project.build.directory}/build" failonerror="true">
+                      <arg line="install VERBOSE=1"/>
+                    </exec>
+                  </target>
+                </configuration>
+              </execution>
+              <execution>
+                <id>native_tests</id>
+                <phase>test</phase>
+                <goals><goal>run</goal></goals>
+                <configuration>
+                  <target>
+                    <exec executable="sh" dir="${project.build.directory}/build" failonerror="true">
+                      <arg value="-c"/>
+                      <arg value="[ x$SKIPTESTS = xtrue ] || make test"/>
+                      <env key="SKIPTESTS" value="${skipTests}"/>
+                    </exec>
+                  </target>
+                </configuration>
+              </execution>
+            </executions>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
+</project>

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/htrace-c/src/CMakeLists.txt b/htrace-c/src/CMakeLists.txt
new file mode 100644
index 0000000..c9b124d
--- /dev/null
+++ b/htrace-c/src/CMakeLists.txt
@@ -0,0 +1,215 @@
+#
+# 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.
+#
+
+#
+# Build the C client for the HTrace distributed tracing system.
+#
+
+cmake_minimum_required(VERSION 2.8 FATAL_ERROR)
+set(CMAKE_BUILD_TYPE, Release)  # Default to release builds
+
+# Define "make check" as an alias for "make test."
+add_custom_target(check COMMAND ctest)
+enable_testing()
+
+if (WIN32)
+    MESSAGE(FATAL_ERROR "Windows support is not yet available.")
+else() # UNIX
+    set(CMAKE_C_FLAGS "-g ${CMAKE_C_FLAGS} -Wall -O2 -fno-strict-aliasing")
+    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_REENTRANT") # Enable pthreads.
+    if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
+        # _GNU_SOURCE is needed to see all the glibc definitions.
+        set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_GNU_SOURCE")
+        # Use the 64-bit forms of off_t, etc.
+        set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64")
+    endif()
+endif()
+
+INCLUDE(CheckCSourceCompiles)
+CHECK_C_SOURCE_COMPILES("int main(void) { static __thread int i = 0; return 0; }" HAVE_IMPROVED_TLS)
+CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/util/build.h.cmake ${CMAKE_BINARY_DIR}/util/build.h)
+
+get_filename_component(HTRACE_ABSPATH "../../htrace-core/src/go/build/htrace" ABSOLUTE)
+get_filename_component(HTRACED_ABSPATH "../../htrace-core/src/go/build/htraced" ABSOLUTE)
+CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/test/test_config.h.cmake ${CMAKE_BINARY_DIR}/test/test_config.h)
+
+find_package(CURL REQUIRED)
+
+find_package(PkgConfig)
+pkg_check_modules(PC_JSON-C QUIET json-c)
+find_path(JSON_C_INCLUDE_DIR "json.h"
+    HINTS ${PC_JSON-C_INCLUDEDIR} ${PC_JSON-C_INCLUDE_DIRS} PATH_SUFFIXES json-c json)
+find_library(JSON_C_LIBRARY NAMES json-c json libjson
+    HINTS ${PC_JSON-C_LIBDIR} ${PC_JSON-C_LIBRARY_DIRS})
+IF(JSON_C_INCLUDE_DIR AND JSON_C_LIBRARY)
+ELSE(JSON_C_INCLUDE_DIR AND JSON_C_LIBRARY)
+    MESSAGE(FATAL_ERROR "Failed to find libjson-c. Try installing libjson-c with apt-get or yum, or install it manually from http://oss.metaparadigm.com/json-c/")
+ENDIF(JSON_C_INCLUDE_DIR AND JSON_C_LIBRARY)
+
+include_directories(${CURL_INCLUDE_DIR}
+                    ${CMAKE_BINARY_DIR}
+                    ${CMAKE_SOURCE_DIR})
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
+    set(RAND_SRC "util/rand_linux.c")
+else()
+    set(RAND_SRC "util/rand_posix.c")
+endif()
+
+set(SRC_ALL
+    ${RAND_SRC}
+    core/conf.c
+    core/htracer.c
+    core/scope.c
+    core/span.c
+    receiver/curl.c
+    receiver/htraced.c
+    receiver/local_file.c
+    receiver/noop.c
+    receiver/receiver.c
+    sampler/always.c
+    sampler/never.c
+    sampler/prob.c
+    sampler/sampler.c
+    util/htable.c
+    util/log.c
+    util/process_id.c
+    util/string.c
+    util/terror.c
+    util/time.c
+)
+
+set(DEPS_ALL
+    ${CURL_LIBRARY}
+    pthread)
+
+# The unit test version of the library, which exposes all symbols.
+add_library(htrace_test STATIC
+    ${SRC_ALL}
+    test/mini_htraced.c
+    test/span_table.c
+    test/span_util.c
+    test/temp_dir.c
+    test/test.c
+)
+target_link_libraries(htrace_test ${DEPS_ALL} ${JSON_C_LIBRARY})
+
+# Hide all symbols by default.  Only the symbols we specifically mark as
+# visible should be accessable by the library user.  This should avoid
+# conflicts between our function and global variable names and those of
+# the host program.
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden")
+
+# The production version of the library, which exposes only the public API.
+add_library(htrace SHARED ${SRC_ALL})
+target_link_libraries(htrace ${DEPS_ALL})
+set(HTRACE_VERSION_MAJOR "3")
+set(HTRACE_VERSION_MINOR "2")
+set(HTRACE_VERSION_PATCH "0")
+set(HTRACE_VERSION_STRING
+    "${HTRACE_VERSION_MAJOR}.${HTRACE_VERSION_MINOR}.${HTRACE_VERSION_PATCH}")
+set_target_properties(htrace PROPERTIES
+    VERSION ${HTRACE_VERSION_STRING}
+    SOVERSION ${HTRACE_VERSION_MAJOR})
+
+macro(add_utest utest)
+    add_executable(${utest}
+        ${ARGN}
+    )
+    target_link_libraries(${utest} htrace_test)
+    add_test(${utest} ${CMAKE_CURRENT_BINARY_DIR}/${utest} ${utest})
+endmacro(add_utest)
+
+add_utest(conf-unit
+    test/conf-unit.c
+)
+
+add_utest(htable-unit
+    test/htable-unit.c
+)
+
+add_utest(htraced_rcv-unit
+    test/htraced_rcv-unit.c
+    test/rtest.c
+)
+
+add_executable(linkage-unit test/linkage-unit.c)
+target_link_libraries(linkage-unit htrace dl)
+add_test(linkage-unit ${CMAKE_CURRENT_BINARY_DIR}/linkage-unit linkage-unit)
+
+add_utest(local_file_rcv-unit
+    test/local_file_rcv-unit.c
+    test/rtest.c
+)
+
+add_utest(local_file_rcvpp-unit
+    test/local_file_rcv-unit.c
+    test/rtestpp.cc
+)
+
+add_utest(log-unit
+    test/log-unit.c
+)
+
+add_utest(mini_htraced-unit
+    test/mini_htraced-unit.c
+)
+
+add_utest(process_id-unit
+    test/process_id-unit.c
+)
+
+if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
+    add_utest(rand_linux-unit
+        test/rand-unit.c
+        util/rand_linux.c
+    )
+endif()
+add_utest(rand_posix-unit
+    test/rand-unit.c
+    util/rand_posix.c
+)
+
+add_utest(sampler-unit
+    test/sampler-unit.c
+)
+
+add_utest(span-unit
+    test/span-unit.c
+)
+
+add_utest(span_util-unit
+    test/span_util-unit.c
+)
+
+add_utest(string-unit
+    test/string-unit.c
+)
+
+add_utest(temp_dir-unit
+    test/temp_dir-unit.c
+)
+
+add_utest(time-unit
+    test/time-unit.c
+)
+
+# Install libhtrace.so and htrace.h.
+# These are the only build products that external users can consume.
+install(TARGETS htrace DESTINATION lib)
+install(FILES ${CMAKE_SOURCE_DIR}/core/htrace.h DESTINATION include)

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/core/conf.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/core/conf.c b/htrace-c/src/core/conf.c
new file mode 100644
index 0000000..936e224
--- /dev/null
+++ b/htrace-c/src/core/conf.c
@@ -0,0 +1,260 @@
+/**
+ * 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.
+ */
+
+#include "core/conf.h"
+#include "core/htrace.h"
+#include "util/htable.h"
+#include "util/log.h"
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define HTRACE_DEFAULT_CONF_KEYS (\
+     HTRACE_PROB_SAMPLER_FRACTION_KEY "=0.01"\
+     ";" HTRACED_BUFFER_SIZE_KEY "=67108864"\
+     ";" HTRACED_SEND_TIMEOUT_MS_KEY "=120000"\
+     ";" HTRACE_PROCESS_ID "=%{tname}/%{ip}"\
+     ";" HTRACED_ADDRESS_KEY "=localhost:9095"\
+    )
+
+static int parse_key_value(char *str, char **key, char **val)
+{
+    char *eq = strchr(str, '=');
+    if (eq) {
+        *eq = '\0';
+        *val = strdup(eq + 1);
+    } else {
+        *val = strdup("true");
+    }
+    if (!*val) {
+        return ENOMEM;
+    }
+    *key = strdup(str);
+    if (!*key) {
+        free(*val);
+        return ENOMEM;
+    }
+    return 0;
+}
+
+static struct htable *htable_from_str(const char *str)
+{
+    struct htable *ht;
+    char *cstr = NULL, *saveptr = NULL, *tok;
+    int ret = ENOMEM;
+
+    ht = htable_alloc(8, ht_hash_string, ht_compare_string);
+    if (!ht) {
+        goto done;
+    }
+    if (!str) {
+        ret = 0;
+        goto done;
+    }
+    cstr = strdup(str);
+    if (!cstr) {
+        goto done;
+    }
+
+    for (tok = strtok_r(cstr, ";", &saveptr); tok;
+             tok = strtok_r(NULL, ";", &saveptr)) {
+        char *key = NULL, *val = NULL;
+        ret = parse_key_value(tok, &key, &val);
+        if (ret) {
+            goto done;
+        }
+        ret = htable_put(ht, key, val);
+        if (ret) {
+            goto done;
+        }
+    }
+    ret = 0;
+done:
+    if (ret) {
+        htable_free(ht);
+        ht = NULL;
+    }
+    free(cstr);
+    return ht;
+}
+
+struct htrace_conf *htrace_conf_from_strs(const char *values,
+                                          const char *defaults)
+{
+    struct htrace_conf *cnf;
+
+    cnf = calloc(1, sizeof(*cnf));
+    if (!cnf) {
+        return NULL;
+    }
+    cnf->values = htable_from_str(values);
+    if (!cnf->values) {
+        htrace_conf_free(cnf);
+        return NULL;
+    }
+    cnf->defaults = htable_from_str(defaults);
+    if (!cnf->defaults) {
+        htrace_conf_free(cnf);
+        return NULL;
+    }
+    return cnf;
+}
+
+struct htrace_conf *htrace_conf_from_str(const char *values)
+{
+    return htrace_conf_from_strs(values, HTRACE_DEFAULT_CONF_KEYS);
+}
+
+static void htrace_tuple_free(void *ctx, void *key, void *val)
+{
+    free(key);
+    free(val);
+}
+
+void htrace_conf_free(struct htrace_conf *cnf)
+{
+    if (!cnf) {
+        return;
+    }
+    if (cnf->values) {
+        htable_visit(cnf->values, htrace_tuple_free, NULL);
+        htable_free(cnf->values);
+    }
+    if (cnf->defaults) {
+        htable_visit(cnf->defaults, htrace_tuple_free, NULL);
+        htable_free(cnf->defaults);
+    }
+    free(cnf);
+}
+
+const char *htrace_conf_get(const struct htrace_conf *cnf, const char *key)
+{
+    const char *val;
+
+    val = htable_get(cnf->values, key);
+    if (val)
+        return val;
+    val = htable_get(cnf->defaults, key);
+    return val;
+}
+
+static int convert_double(struct htrace_log *log, const char *key,
+                   const char *in, double *out)
+{
+    char *endptr = NULL;
+    int err;
+    double ret;
+
+    errno = 0;
+    ret = strtod(in, &endptr);
+    if (errno) {
+        err = errno;
+        htrace_log(log, "error parsing %s for %s: %d (%s)\n",
+                   in, key, err, terror(err));
+        return 0;
+    }
+    while (1) {
+        char c = *endptr;
+        if (c == '\0') {
+            break;
+        }
+        if ((c != ' ') || (c != '\t')) {
+            htrace_log(log, "error parsing %s for %s: garbage at end "
+                       "of string.\n", in, key);
+            return 0;
+        }
+    }
+    *out = ret;
+    return 1;
+}
+
+double htrace_conf_get_double(struct htrace_log *log,
+                             const struct htrace_conf *cnf, const char *key)
+{
+    const char *val;
+    double out = 0;
+
+    val = htable_get(cnf->values, key);
+    if (val) {
+        if (convert_double(log, key, val, &out)) {
+            return out;
+        }
+    }
+    val = htable_get(cnf->defaults, key);
+    if (val) {
+        if (convert_double(log, key, val, &out)) {
+            return out;
+        }
+    }
+    return 0;
+}
+
+static int convert_u64(struct htrace_log *log, const char *key,
+                   const char *in, uint64_t *out)
+{
+    char *endptr = NULL;
+    int err;
+    uint64_t ret;
+
+    errno = 0;
+    ret = strtoull(in, &endptr, 10);
+    if (errno) {
+        err = errno;
+        htrace_log(log, "error parsing %s for %s: %d (%s)\n",
+                   in, key, err, terror(err));
+        return 0;
+    }
+    while (1) {
+        char c = *endptr;
+        if (c == '\0') {
+            break;
+        }
+        if ((c != ' ') || (c != '\t')) {
+            htrace_log(log, "error parsing %s for %s: garbage at end "
+                       "of string.\n", in, key);
+            return 0;
+        }
+    }
+    *out = ret;
+    return 1;
+}
+
+uint64_t htrace_conf_get_u64(struct htrace_log *log,
+                             const struct htrace_conf *cnf, const char *key)
+{
+    const char *val;
+    uint64_t out = 0;
+
+    val = htable_get(cnf->values, key);
+    if (val) {
+        if (convert_u64(log, key, val, &out)) {
+            return out;
+        }
+    }
+    val = htable_get(cnf->defaults, key);
+    if (val) {
+        if (convert_u64(log, key, val, &out)) {
+            return out;
+        }
+    }
+    return 0;
+}
+
+// vim:ts=4:sw=4:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/core/conf.h
----------------------------------------------------------------------
diff --git a/htrace-c/src/core/conf.h b/htrace-c/src/core/conf.h
new file mode 100644
index 0000000..aa0bb0b
--- /dev/null
+++ b/htrace-c/src/core/conf.h
@@ -0,0 +1,110 @@
+/**
+ * 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.
+ */
+
+#ifndef APACHE_HTRACE_CORE_CONF_H
+#define APACHE_HTRACE_CORE_CONF_H
+
+#include <stdint.h>
+
+/**
+ * @file conf.h
+ *
+ * Functions to manipulate HTrace configuration objects.
+ *
+ * This is an internal header, not intended for external use.
+ */
+
+struct htable;
+struct htrace_log;
+
+struct htrace_conf {
+    /**
+     * A hash table mapping keys to the values that were set.
+     */
+    struct htable *values;
+
+    /**
+     * A hash table mapping keys to the default values that were set for those
+     * keys.
+     */
+    struct htable *defaults;
+};
+
+/**
+ * Create an HTrace conf object from a values string and a defaults string.
+ *
+ * See {@ref htrace_conf_from_str} for the format.
+ *
+ * The configuration object must be later freed with htrace_conf_free.
+ *
+ * @param str       The configuration string.
+ *
+ * @return          NULL on OOM; the htrace configuration otherwise.
+ */
+struct htrace_conf *htrace_conf_from_strs(const char *values,
+                                          const char *defaults);
+
+/**
+ * Free an HTrace configuration object.
+ *
+ * @param cnf       The HTrace configuration object.
+ */
+void htrace_conf_free(struct htrace_conf *cnf);
+
+/**
+ * Get the value of a key in a configuration.
+ *
+ * @param cnf       The configuration.
+ * @param key       The key.
+ *
+ * @return          NULL if the key was not found in the values or the
+ *                      defaults; the value otherwise.
+ */
+const char *htrace_conf_get(const struct htrace_conf *cnf, const char *key);
+
+/**
+ * Get the value of a key in a configuration as a floating point double.
+ *
+ * @param log       Log to send parse error messages to.
+ * @param cnf       The configuration.
+ * @param key       The key.
+ *
+ * @return          The value if it was found.
+ *                  The default value if it was not found.
+ *                  0.0 if there was no default value.
+ */
+double htrace_conf_get_double(struct htrace_log *log,
+                const struct htrace_conf *cnf, const char *key);
+
+/**
+ * Get the value of a key in a configuration as a uint64_t.
+ *
+ * @param log       Log to send parse error messages to.
+ * @param cnf       The configuration.
+ * @param key       The key.
+ *
+ * @return          The value if it was found.
+ *                  The default value if it was not found.
+ *                  0 if there was no default value.
+ */
+uint64_t htrace_conf_get_u64(struct htrace_log *log,
+                const struct htrace_conf *cnf, const char *key);
+
+#endif
+
+// vim: ts=4: sw=4: et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/core/htrace.h
----------------------------------------------------------------------
diff --git a/htrace-c/src/core/htrace.h b/htrace-c/src/core/htrace.h
new file mode 100644
index 0000000..cdebd31
--- /dev/null
+++ b/htrace-c/src/core/htrace.h
@@ -0,0 +1,346 @@
+/**
+ * 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.
+ */
+
+#ifndef APACHE_HTRACE_HTRACE_H
+#define APACHE_HTRACE_HTRACE_H
+
+#include <stdint.h> /* for uint64_t, etc. */
+
+/**
+ * The public API for the HTrace C client.
+ *
+ * SPANS AND SCOPES
+ * HTrace is a tracing framework for distributed systems.  The smallest unit of
+ * tracing in HTrace is the trace span.  Trace spans represent intervals during
+ * which a thread is performing some work.  Trace spans are identified by a
+ * 64-bit ID called the trace span ID.  Trace spans can have one or more
+ * parents.  The parent of a trace span is the operation or operations that
+ * caused it to happen.
+ *
+ * Trace spans are managed by htrace_scope objects.  Creating an htrace_scope
+ * (potentially) starts a trace span.  The trace span will be closed once the
+ * htrace_scope is closed and freed.
+ *
+ * SPAN RECEIVERS
+ * When a span is closed, it is sent to the current "span receiver."  Span
+ * receivers decide what to do with the span data.  For example, the "local
+ * file" span receiver saves the span data to a local file.  The "htraced" span
+ * receiver sends the span data to the htraced daemon.
+ *
+ * Most interesting span receivers will start a background thread to handle
+ * their workload.  This background thread will last until the associated
+ * htracer is shut down.
+ *
+ * SAMPLING
+ * HTrace is based around the concept of sampling.  That means that only some
+ * trace scopes are managing spans-- the rest do nothing.  Sampling is managed
+ * by htrace_sampler objects.  The two most important samplers are the
+ * probability based sampler, and the "always" and "never" samplers.
+ *
+ * TRACERS
+ * The HTrace C client eschews globals.  Instead, you are invited to create your
+ * own htracer (HTrace context) object and use it throughout your program or
+ * library.  The htracer object contains the logging settings and the currently
+ * configured span receiver.  Tracers are thread-safe, so you can use the same
+ * tracer for all of your threads if you like.
+ *
+ * As already mentioned, the Tracer may contain threads, so please do not call
+ * htracer_create until you are ready to start threads in your program.  For
+ * example, do not call it prior to daemonizing.
+ *
+ * COMPATIBILITY
+ * When modifying this code, please try to avoid breaking binary compatibility.
+ * Applications compiled against older versions of libhtrace.so should continue
+ * to work when new versions of the library are dropped in.
+ *
+ * Adding new functions is always OK.  Modifying the type signature of existing
+ * functions is not OK.  When adding structures, try to avoid including the
+ * structure definition in this header, so that we can change it later on with
+ * no harmful effects.  Perhaps we may need to break compatibility at some
+ * point, but let's try to avoid that if we can.
+ *
+ * PORTABILITY
+ * We aim for POSIX compatibility, although we have not done a lot of testing on
+ * non-Linux systems.  Eventually, we will want to support Windows.
+ */
+
+#ifdef __cplusplus
+extern  "C" {
+#endif
+
+#pragma GCC visibility push(default) // Begin publicly visible symbols
+
+// Configuration keys.
+
+/**
+ * The path to use for the htrace client log.
+ * If this is unset, we will log to stderr.
+ */
+#define HTRACE_LOG_PATH_KEY "log.path"
+
+/**
+ * The span receiver implementation to use.
+ *
+ * Possible values:
+ *   noop            The "no op" span receiver, which discards all spans.
+ *   local.file      A receiver which writes spans to local files.
+ *   htraced         The htraced span receiver, which sends spans to htraced.
+ */
+#define HTRACE_SPAN_RECEIVER_KEY "span.receiver"
+
+/**
+ * The path which the local file span receiver should write spans to.
+ */
+#define HTRACE_LOCAL_FILE_RCV_PATH_KEY "local.file.path"
+
+/**
+ * The hostname and port which the htraced span receiver should send its spans
+ * to.  This is in the format "hostname:port".
+ */
+#define HTRACED_ADDRESS_KEY "htraced.address"
+
+/**
+ * The timeout to use when sending spans to the htraced server.
+ */
+#define HTRACED_SEND_TIMEOUT_MS_KEY "htraced.send.timeout.ms"
+
+/**
+ * The size of the circular buffer to use in the htraced receiver.
+ */
+#define HTRACED_BUFFER_SIZE_KEY "htraced.buffer.size"
+
+/**
+ * The process ID string to use.
+ *
+ * %{ip} will be replaced by an IP address;
+ * %{pid} will be replaced by the operating system process ID;
+ * %{tname} will be replaced by the Tracer name.
+ *
+ * Defaults to %{tname}/%{ip}
+ */
+#define HTRACE_PROCESS_ID "process.id"
+
+/**
+ * The sampler to use.
+ *
+ * Possible values:
+ *   never          A sampler which never fires.
+ *   always         A sampler which always fires.
+ *   prob           A sampler which fires with some probability.
+ */
+#define HTRACE_SAMPLER_KEY "sampler"
+
+/**
+ * For the probability sampler, the fraction of the time that we should create a
+ * new span.  This is a floating point number which is between 0.0 and 1.1,
+ * inclusive.  It is _not_ a percentage.
+ */
+#define HTRACE_PROB_SAMPLER_FRACTION_KEY "prob.sampler.fraction"
+
+    // Forward declarations
+    struct htrace_conf;
+    struct htracer;
+    struct htrace_scope;
+
+    /**
+     * Create an HTrace conf object from a string.
+     *
+     * The string should be in the form:
+     * key1=val1;key2=val2;...
+     * Entries without an equals sign will set the key to 'true'.
+     *
+     * The configuration object must be later freed with htrace_conf_free.
+     *
+     * @param values        The configuration string to parse.
+     *                          You may free this string after this function
+     *                          returns.
+     *
+     * @return              NULL on out-of-memory error; the configuration
+     *                          object otherwise.
+     */
+    struct htrace_conf *htrace_conf_from_str(const char *values);
+
+    /**
+     * Free an htrace configuration.
+     *
+     * @param conf      The configuration object to free.
+     */
+    void htrace_conf_free(struct htrace_conf *cnf);
+
+    /**
+     * Create a Tracer.
+     *
+     * This function does a few things:
+     *      - Initialize logging (if there are configuration tuples related to
+     *          logging)
+     *      - Initialize trace span receivers, if any are configured.
+     *
+     * This function may start background threads.
+     *
+     * @param tname         The name of the tracer to create.  Will be
+     *                          deep-copied.  Must not be null.
+     * @param conf          The configuration to use.  You may free this
+     *                          configuration object after calling this
+     *                          function.
+     *
+     * @return              NULL on OOM; the tracer otherwise.
+     */
+    struct htracer *htracer_create(const char *tname,
+                                   const struct htrace_conf *cnf);
+
+    /**
+     * Get the Tracer name.
+     *
+     * @param tracer        The tracer.
+     *
+     * @return              The tracer name.  This string is managed by the
+     *                          tracer itself and will remain valid until the
+     *                          tracer is freed.
+     */
+    const char *htracer_tname(const struct htracer *tracer);
+
+    /**
+     * Free an HTracer.
+     *
+     * Frees the memory and other resources associated with a Tracer.
+     * Closes the log file if there is one open.  Shuts down the span receiver
+     * if there is one active.  Attempt to flush all buffered spans.
+     *
+     * Do not call this function until all the samplers which hold a reference
+     * to this htracer have been freed.  Do not call this function if there is
+     * currently an active htrace_scope object which holds a reference to this
+     * tracer.
+     *
+     * @param tracer        The tracer to free.
+     */
+    void htracer_free(struct htracer *tracer);
+
+    /**
+     * Create an htrace configuration sample from a configuration.
+     *
+     * Samplers are thread-safe; you may use the same sampler simultaneously
+     * from multiple threads.
+     *
+     * @param tracer        The HTracer to use.  The sampler will hold a
+     *                          reference to this tracer.  Do not free the
+     *                          tracer until after the sampler has been freed.
+     * @param conf          The configuration to use.  You may free this
+     *                          configuration object after calling this
+     *                          function.
+     *
+     * @return              NULL if we are out of memory.
+     *                      NULL if the configuration was invalid.
+     *                      NULL if no sampler is configured.
+     *                      The sampler otherwise.
+     *                      Error conditions will be logged to the htracer log.
+     */
+    struct htrace_sampler *htrace_sampler_create(struct htracer *tracer,
+                                                 struct htrace_conf *cnf);
+
+    /**
+     * Get the name of an HTrace sampler.
+     *
+     * @param smp           The sampler.
+     *
+     * @return              The sampler name.  This string is managed by the
+     *                          sampler itself and will remain valid until the
+     *                          sampler is freed.
+     */
+    const char *htrace_sampler_to_str(struct htrace_sampler *smp);
+
+    /**
+     * Free an htrace sampler.
+     *
+     * @param sampler       The sampler to free.
+     */
+    void htrace_sampler_free(struct htrace_sampler *smp);
+
+    /**
+     * Start a new trace span if necessary.
+     *
+     * You must call htrace_close_span on the scope object returned by this
+     * function.
+     *
+     * @param tracer    The htracer to use.  Must remain valid for the
+     *                      duration of the scope.
+     * @param sampler   The sampler to use, or NULL for no sampler.
+     *                      If no sampler is used, we will create a new span
+     *                      only if there is a current active span.
+     * @param desc      The description of the trace span.  Will be deep-copied.
+     *
+     * @return          The trace scope.  NULL if we ran out of memory, or if we
+     *                      are not tracing.
+     */
+    struct htrace_scope* htrace_start_span(struct htracer *tracer,
+                        struct htrace_sampler *sampler, const char *desc);
+
+    /**
+     * Detach the trace span from the given trace scope.
+     *
+     * @param scope     The trace scope, or NULL.
+     *
+     * @return          NULL if there was no attached trace scope;
+     *                  the trace scope otherwise.
+     */
+    struct htrace_span *htrace_scope_detach(struct htrace_scope *scope);
+
+    /**
+     * Create a new scope object with the given span.
+     *
+     * @param tracer    The htracer to use.
+     * @param span      The trace span, or NULL.
+     *
+     * @return          NULL if there was no trace span;
+     *                  the trace scope otherwise.
+     */
+    struct htrace_scope* htrace_restart_span(struct htracer *tracer,
+                                             struct htrace_span *span);
+
+    /**
+     * Get the span id of an HTrace scope.
+     *
+     * @param scope     The trace scope, or NULL.
+     *
+     * @return          The span ID of the trace span, or 0 if there is no trace
+     *                      span inside the scope, or if NULL was passed.
+     */
+    uint64_t htrace_scope_get_span_id(const struct htrace_scope *scope);
+
+    /**
+     * Close a trace scope.
+     *
+     * This must be called from the same thread that the trace scope was created
+     * in.
+     *
+     * @param scope     The trace scope to close.  You may pass NULL here
+     *                      with no harmful effects-- it will be ignored.
+     *                      If there is a span associated with the trace scope,
+     *                      it will be sent to the relevant span receiver.
+     *                      Then the scope and the span will be freed.
+     */
+    void htrace_scope_close(struct htrace_scope *scope);
+
+#pragma GCC visibility pop // End publicly visible symbols
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
+// vim: ts=4:sw=4:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/core/htrace.hpp
----------------------------------------------------------------------
diff --git a/htrace-c/src/core/htrace.hpp b/htrace-c/src/core/htrace.hpp
new file mode 100644
index 0000000..e4a87cb
--- /dev/null
+++ b/htrace-c/src/core/htrace.hpp
@@ -0,0 +1,239 @@
+/**
+ * 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.
+ */
+
+#ifndef APACHE_HTRACE_HTRACE_HPP
+#define APACHE_HTRACE_HTRACE_HPP
+
+#include "htrace.h"
+
+#include <string>
+
+/**
+ * The public C++ API for the HTrace native client.
+ *
+ * The C++ API is a wrapper around the C API.  The advantage of this is that we
+ * can change the C++ API in this file without breaking binary compatibility.
+ *
+ * EXCEPTIONS
+ * We do not use exceptions in this API.  This code should be usable by
+ * libraries and applications that are using the Google C++ coding style.
+ * The one case where we do use exceptions is to translate NULL pointer returns
+ * on OOM into std::bad_alloc exceptions.  In general, it is extremely unlikely
+ * that the size of memory allocations we are doing will produce OOM.  Most
+ * C++ programs do not attempt to handle OOM anyway because of the extra code
+ * complexity that would be required.  So translating this into an exception is
+ * fine.
+ *
+ * C++11
+ * This code should not require C++11.  We might add #ifdefs later to take
+ * advantage of certain C++11 or later features if they are available.
+ */
+
+namespace htrace {
+  class Sampler;
+  class Scope;
+  class Tracer;
+
+  /**
+   * An HTrace Configuration object.
+   *
+   * Configurations are thread-safe.  They can be used by multiple threads
+   * simultaneously.
+   */
+  class Conf {
+  public:
+    /**
+     * Create a new HTrace Conf.
+     *
+     * @param values    A configuration string containing a series of
+     *                  semicolon-separated key=value entries.
+     *                  We do not hold on to a reference to this string.
+     * @param defaults  Another semicolon-separated set of key=value entries.
+     *                  The defaults to be used when there is no corresponding
+     *                  value in 'values.' We do not hold on to a reference to
+     *                  this string.
+     */
+    Conf(const char *values)
+      : conf_(htrace_conf_from_str(values))
+    {
+      if (!conf_) {
+        throw std::bad_alloc();
+      }
+    }
+
+    Conf(const std::string &values)
+      : conf_(htrace_conf_from_str(values.c_str()))
+    {
+      if (!conf_) {
+        throw std::bad_alloc();
+      }
+    }
+
+    ~Conf() {
+      htrace_conf_free(conf_);
+      conf_ = NULL;
+    }
+
+  private:
+    friend class Tracer;
+    friend class Sampler;
+    Conf &operator=(Conf &other); // Can't copy
+    Conf(Conf &other);
+    struct htrace_conf *conf_;
+  };
+
+  /**
+   * An HTrace context object.
+   *
+   * Contexts are thread-safe.  They can be used by multiple threads simultaneoy
+   * Most applications will not need more than one HTrace context, which is
+   * often global (or at least widely used.)
+   */
+  class Tracer {
+  public:
+    /**
+     * Create a new Tracer.
+     *
+     * @param name    The name of the tracer to create.  We do not hold on to a
+     *                  reference to this string.
+     * @param conf    The configuration to use for the new tracer.  We do not
+     *                  hold on to a reference to this configuration.
+     */
+    Tracer(const std::string &name, const Conf &conf)
+      : tracer_(htracer_create(name.c_str(), conf.conf_))
+    {
+      if (!tracer_) {
+        throw std::bad_alloc();
+      }
+    }
+
+    std::string Name() {
+      return std::string(htracer_tname(tracer_));
+    }
+
+    /**
+     * Free the Tracer.
+     *
+     * This destructor must not be called until all the other objects which hold
+     * a reference (such as samplers and trace scopes) are freed.  It is often
+     * not necessary to destroy this object at all unless you are writing a
+     * library and want to support unloading your library, or you are writing an
+     * application and want to support some kind of graceful shutdown.
+     *
+     * We could make this friendlier with some kind of reference counting via
+     * atomic variables, but only at the cost of reduced performance.
+     */
+    ~Tracer() {
+      htracer_free(tracer_);
+      tracer_ = NULL;
+    }
+
+  private:
+    friend class Sampler;
+    friend class Scope;
+    Tracer(const Tracer &other); // Can't copy
+    const Tracer &operator=(const Tracer &other);
+    struct htracer *tracer_;
+  };
+
+  /**
+   * An HTrace sampler.
+   *
+   * Samplers determine when new spans are created.
+   * See htrace.h for more information.
+   *
+   * Samplers are thread-safe.  They can be used by multiple threads
+   * simultaneously.
+   */
+  class Sampler {
+  public:
+    /**
+     * Create a new Sampler.
+     *
+     * @param tracer  The tracer to use.  You must not free this tracer until
+     *                  after this sampler is freed.
+     * @param conf    The configuration to use for the new sampler.  We do not
+     *                  hold on to a reference to this configuration.
+     */
+    Sampler(Tracer *tracer, const Conf &conf)
+        : smp_(htrace_sampler_create(tracer->tracer_, conf.conf_)) {
+      if (!smp_) {
+        throw std::bad_alloc();
+      }
+    }
+
+    /**
+     * Get a description of this Sampler.
+     */
+    std::string ToString() {
+      return std::string(htrace_sampler_to_str(smp_));
+    }
+
+    ~Sampler() {
+      htrace_sampler_free(smp_);
+      smp_ = NULL;
+    }
+
+  private:
+    friend class Tracer;
+    friend class Scope;
+    Sampler(const Sampler &other); // Can't copy
+    const Sampler &operator=(const Sampler &other);
+
+    struct htrace_sampler *smp_;
+  };
+
+  class Scope {
+  public:
+    Scope(Tracer &tracer, const char *name)
+      : scope_(htrace_start_span(tracer.tracer_, NULL, name)) {
+    }
+
+    Scope(Tracer &tracer, const std::string &name)
+      : scope_(htrace_start_span(tracer.tracer_, NULL, name.c_str())) {
+    }
+
+    Scope(Tracer &tracer, Sampler &smp, const char *name)
+      : scope_(htrace_start_span(tracer.tracer_, smp.smp_, name)) {
+    }
+
+    Scope(Tracer &tracer, Sampler &smp, const std::string &name)
+      : scope_(htrace_start_span(tracer.tracer_, smp.smp_, name.c_str())) {
+    }
+
+    ~Scope() {
+      htrace_scope_close(scope_);
+      scope_ = NULL;
+    }
+
+    uint64_t GetSpanId() {
+      return htrace_scope_get_span_id(scope_);
+    }
+
+  private:
+    friend class Tracer;
+    Scope(htrace::Scope &other); // Can't copy
+    Scope& operator=(Scope &scope); // Can't assign
+
+    struct htrace_scope *scope_;
+  };
+}
+
+#endif
+
+// vim: ts=2:sw=2:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/core/htracer.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/core/htracer.c b/htrace-c/src/core/htracer.c
new file mode 100644
index 0000000..3305beb
--- /dev/null
+++ b/htrace-c/src/core/htracer.c
@@ -0,0 +1,167 @@
+/**
+ * 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.
+ */
+
+#include "core/conf.h"
+#include "core/htrace.h"
+#include "core/htracer.h"
+#include "core/scope.h"
+#include "core/span.h"
+#include "receiver/receiver.h"
+#include "util/log.h"
+#include "util/process_id.h"
+#include "util/rand.h"
+#include "util/string.h"
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+/**
+ * @file htracer.c
+ *
+ * Implementation of the Tracer object.
+ */
+
+struct htracer *htracer_create(const char *tname,
+                               const struct htrace_conf *cnf)
+{
+    struct htracer *tracer;
+    int ret;
+
+    tracer = calloc(1, sizeof(*tracer));
+    if (!tracer) {
+        return NULL;
+    }
+    tracer->lg = htrace_log_alloc(cnf);
+    if (!tracer->lg) {
+        free(tracer);
+        return NULL;
+    }
+    ret = pthread_key_create(&tracer->tls, NULL);
+    if (ret) {
+        htrace_log(tracer->lg, "htracer_create: pthread_key_create "
+                   "failed: %s.\n", terror(ret));
+        htrace_log_free(tracer->lg);
+        return NULL;
+    }
+    tracer->tname = strdup(tname);
+    if (!tracer->tname) {
+        htrace_log(tracer->lg, "htracer_create: failed to "
+                   "duplicate name string.\n");
+        htracer_free(tracer);
+        return NULL;
+    }
+    tracer->prid = calculate_process_id(tracer->lg,
+            htrace_conf_get(cnf, HTRACE_PROCESS_ID), tname);
+    if (!tracer->prid) {
+        htrace_log(tracer->lg, "htracer_create: failed to "
+                   "create process id string.\n");
+        htracer_free(tracer);
+        return NULL;
+    }
+    if (!validate_json_string(tracer->lg, tracer->prid)) {
+        htrace_log(tracer->lg, "htracer_create: process ID string '%s' is "
+                   "problematic.\n", tracer->prid);
+        htracer_free(tracer);
+        return NULL;
+    }
+    tracer->rnd = random_src_alloc(tracer->lg);
+    if (!tracer->rnd) {
+        htrace_log(tracer->lg, "htracer_create: failed to "
+                   "allocate a random source.\n");
+        htracer_free(tracer);
+        return NULL;
+    }
+    tracer->rcv = htrace_rcv_create(tracer, cnf);
+    if (!tracer->rcv) {
+        htrace_log(tracer->lg, "htracer_create: failed to "
+                   "create a receiver.\n");
+        htracer_free(tracer);
+        return NULL;
+    }
+    return tracer;
+}
+
+const char *htracer_tname(const struct htracer *tracer)
+{
+    return tracer->tname;
+}
+
+void htracer_free(struct htracer *tracer)
+{
+    struct htrace_rcv *rcv;
+
+    if (!tracer) {
+        return;
+    }
+    pthread_key_delete(tracer->tls);
+    rcv = tracer->rcv;
+    if (rcv) {
+        rcv->ty->free(rcv);
+    }
+    random_src_free(tracer->rnd);
+    free(tracer->tname);
+    free(tracer->prid);
+    htrace_log_free(tracer->lg);
+    free(tracer);
+}
+
+struct htrace_scope *htracer_cur_scope(struct htracer *tracer)
+{
+    return pthread_getspecific(tracer->tls);
+}
+
+int htracer_push_scope(struct htracer *tracer, struct htrace_scope *cur,
+                           struct htrace_scope *next)
+{
+    int ret;
+    next->parent = cur;
+    ret = pthread_setspecific(tracer->tls, next);
+    if (ret) {
+        htrace_log(tracer->lg, "htracer_push_scope: pthread_setspecific "
+                   "failed: %s\n", terror(ret));
+        return EIO;
+    }
+    return 0;
+}
+
+int htracer_pop_scope(struct htracer *tracer, struct htrace_scope *scope)
+{
+    struct htrace_scope *cur_scope;
+    int ret;
+
+    cur_scope = pthread_getspecific(tracer->tls);
+    if (cur_scope != scope) {
+        htrace_log(tracer->lg, "htracer_pop_scope: attempted to pop a scope "
+                   "that wasn't the top of the stack.  Current top of stack: "
+                   "%s.  Attempted to pop: %s.\n",
+                   (cur_scope->span ? cur_scope->span->desc : "(detached)"),
+                   (scope->span ? scope->span->desc : "(detached)"));
+        return EIO;
+    }
+    ret = pthread_setspecific(tracer->tls, scope->parent);
+    if (ret) {
+        htrace_log(tracer->lg, "htracer_pop_scope: pthread_setspecific "
+                   "failed: %s\n", terror(ret));
+        return EIO;
+    }
+    return 0;
+}
+
+// vim:ts=4:sw=4:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/core/htracer.h
----------------------------------------------------------------------
diff --git a/htrace-c/src/core/htracer.h b/htrace-c/src/core/htracer.h
new file mode 100644
index 0000000..2acdf70
--- /dev/null
+++ b/htrace-c/src/core/htracer.h
@@ -0,0 +1,101 @@
+/**
+ * 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.
+ */
+
+#ifndef APACHE_HTRACE_CORE_TRACER_H
+#define APACHE_HTRACE_CORE_TRACER_H
+
+#include <pthread.h> /* for pthread_key_t */
+
+/**
+ * @file tracer.h
+ *
+ * The HTracer object.
+ *
+ * This is an internal header, not intended for external use.
+ */
+
+struct htrace_log;
+struct htrace_rcv;
+struct random_src;
+
+struct htracer {
+    /**
+     * Key for thread-local data.
+     */
+    pthread_key_t tls;
+
+    /**
+     * The htrace log to use.
+     */
+    struct htrace_log *lg;
+
+    /**
+     * The name of this tracer.
+     */
+    char *tname;
+
+    /**
+     * The process id of this context.
+     */
+    char *prid;
+
+    /**
+     * The random source to use in this context.
+     */
+    struct random_src *rnd;
+
+    /**
+     * The span receiver to use.
+     */
+    struct htrace_rcv *rcv;
+};
+
+/**
+ * Get the current scope in a given context.
+ *
+ * @param tracer            The context.
+ *
+ * @return                  The current scope, or NULL if there is none.
+ */
+struct htrace_scope *htracer_cur_scope(struct htracer *tracer);
+
+/**
+ * Push another scope on to the current context.
+ *
+ * @param tracer            The context.
+ * @param cur               The current scope on the context.
+ * @param next              The scope to push.
+ *
+ * @return                  0 on success; nonzero otherwise.
+ */
+int htracer_push_scope(struct htracer *tracer, struct htrace_scope *cur,
+                       struct htrace_scope *next);
+
+/**
+ * Pop a scope from the current context.
+ *
+ * @param tracer            The context.
+ * @param scope             The scope which should be the top of the stack.
+ *
+ * @return                  0 on success; nonzero otherwise.
+ */
+int htracer_pop_scope(struct htracer *tracer, struct htrace_scope *scope);
+
+#endif
+
+// vim: ts=4: sw=4: et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/core/scope.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/core/scope.c b/htrace-c/src/core/scope.c
new file mode 100644
index 0000000..93008cb
--- /dev/null
+++ b/htrace-c/src/core/scope.c
@@ -0,0 +1,166 @@
+/**
+ * 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.
+ */
+
+#include "core/htrace.h"
+#include "core/htracer.h"
+#include "core/scope.h"
+#include "core/span.h"
+#include "receiver/receiver.h"
+#include "sampler/sampler.h"
+#include "util/log.h"
+#include "util/rand.h"
+#include "util/string.h"
+#include "util/time.h"
+
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+/**
+ * @file scope.c
+ *
+ * Implementation of HTrace scopes.
+ */
+
+struct htrace_scope* htrace_start_span(struct htracer *tracer,
+        struct htrace_sampler *sampler, const char *desc)
+{
+    struct htrace_scope *cur_scope, *scope = NULL, *pscope;
+    struct htrace_span *span = NULL;
+    uint64_t span_id;
+
+    // Validate the description string.  This ensures that it doesn't have
+    // anything silly in it like embedded double quotes, backslashes, or control
+    // characters.
+    if (!validate_json_string(tracer->lg, desc)) {
+        htrace_log(tracer->lg, "htrace_span_alloc(desc=%s): invalid "
+                   "description string.\n", desc);
+        return NULL;
+    }
+    cur_scope = htracer_cur_scope(tracer);
+    if (!cur_scope) {
+        if (!sampler->ty->next(sampler)) {
+            return NULL;
+        }
+    }
+    do {
+        span_id = random_u64(tracer->rnd);
+    } while (span_id == 0);
+    span = htrace_span_alloc(desc, now_ms(tracer->lg), span_id);
+    if (!span) {
+        htrace_log(tracer->lg, "htrace_span_alloc(desc=%s): OOM\n", desc);
+        return NULL;
+    }
+    scope = malloc(sizeof(*scope));
+    if (!scope) {
+        htrace_span_free(span);
+        htrace_log(tracer->lg, "htrace_start_span(desc=%s): OOM\n", desc);
+        return NULL;
+    }
+    scope->tracer = tracer;
+    scope->span = span;
+
+    // Search enclosing trace scopes for the first one that hasn't disowned
+    // its trace span.
+    for (pscope = cur_scope; pscope; pscope = pscope->parent) {
+        struct htrace_span *pspan = pscope->span;
+        if (pspan) {
+            span->parent.single = pspan->span_id;
+            span->num_parents = 1;
+            break;
+        }
+        pscope = pscope->parent;
+    }
+    if (htracer_push_scope(tracer, cur_scope, scope) != 0) {
+        htrace_span_free(span);
+        free(scope);
+        return NULL;
+    }
+    return scope;
+}
+
+struct htrace_span *htrace_scope_detach(struct htrace_scope *scope)
+{
+    struct htrace_span *span = scope->span;
+
+    if (span == NULL) {
+        htrace_log(scope->tracer->lg, "htrace_scope_detach: attempted to "
+                   "detach a scope which was already detached.\n");
+        return NULL;
+    }
+    scope->span = NULL;
+    return span;
+}
+
+struct htrace_scope* htrace_restart_span(struct htracer *tracer,
+                                         struct htrace_span *span)
+{
+    struct htrace_scope *cur_scope, *scope = NULL;
+
+    scope = malloc(sizeof(*scope));
+    if (!scope) {
+        htrace_span_free(span);
+        htrace_log(tracer->lg, "htrace_start_span(desc=%s, parent_id=%016"PRIx64
+                   "): OOM\n", span->desc, span->span_id);
+        return NULL;
+    }
+    scope->tracer = tracer;
+    scope->parent = NULL;
+    scope->span = span;
+    cur_scope = htracer_cur_scope(tracer);
+    if (htracer_push_scope(tracer, cur_scope, scope) != 0) {
+        htrace_span_free(span);
+        free(scope);
+        return NULL;
+    }
+    return scope;
+}
+
+uint64_t htrace_scope_get_span_id(const struct htrace_scope *scope)
+{
+    struct htrace_span *span;
+
+    if (!scope) {
+        return 0;
+    }
+    span = scope->span;
+    return span ? span->span_id : 0;
+}
+
+void htrace_scope_close(struct htrace_scope *scope)
+{
+    struct htracer *tracer;
+
+    if (!scope) {
+        return;
+    }
+    tracer = scope->tracer;
+    if (htracer_pop_scope(tracer, scope) == 0) {
+        struct htrace_span *span = scope->span;
+        if (span) {
+            struct htrace_rcv *rcv = tracer->rcv;
+            span->end_ms = now_ms(tracer->lg);
+            rcv->ty->add_span(rcv, span);
+            htrace_span_free(span);
+        }
+        free(scope);
+    }
+}
+
+// vim:ts=4:sw=4:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/core/scope.h
----------------------------------------------------------------------
diff --git a/htrace-c/src/core/scope.h b/htrace-c/src/core/scope.h
new file mode 100644
index 0000000..f76cd42
--- /dev/null
+++ b/htrace-c/src/core/scope.h
@@ -0,0 +1,58 @@
+/**
+ * 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.
+ */
+
+#ifndef APACHE_HTRACE_SCOPE_H
+#define APACHE_HTRACE_SCOPE_H
+
+/**
+ * @file scope.h
+ *
+ * Functions related to trace scopes.
+ *
+ * This is an internal header, not intended for external use.
+ */
+
+#include <stdint.h>
+
+/**
+ * A trace scope.
+ *
+ * Currently, trace scopes contain span data (there is no separate object for
+ * the span data.)
+ */
+struct htrace_scope {
+    /**
+     * The HTracer object associated with this scope.  Cannot be NULL.
+     * This memory is managed externally from the htrace_scope object.
+     */
+    struct htracer *tracer;
+
+    /**
+     * The parent scope, or NULL if this is a top-level scope.
+     */
+    struct htrace_scope *parent;
+
+    /**
+     * The span object associated with this scope, or NULL if there is none.
+     */
+    struct htrace_span *span;
+};
+
+#endif
+
+// vim: ts=4:sw=4:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/core/span.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/core/span.c b/htrace-c/src/core/span.c
new file mode 100644
index 0000000..13ba3cf
--- /dev/null
+++ b/htrace-c/src/core/span.c
@@ -0,0 +1,187 @@
+/**
+ * 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.
+ */
+
+#include "core/span.h"
+#include "receiver/receiver.h"
+#include "sampler/sampler.h"
+#include "util/log.h"
+#include "util/rand.h"
+#include "util/string.h"
+#include "util/time.h"
+
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+/**
+ * @file span.c
+ *
+ * Implementation of HTrace spans.
+ */
+
+struct htrace_span *htrace_span_alloc(const char *desc,
+                uint64_t begin_ms, uint64_t span_id)
+{
+    struct htrace_span *span;
+
+    span = malloc(sizeof(*span));
+    if (!span) {
+        return NULL;
+    }
+    span->desc = strdup(desc);
+    if (!span->desc) {
+        free(span);
+        return NULL;
+    }
+    span->begin_ms = begin_ms;
+    span->end_ms = 0;
+    span->span_id = span_id;
+    span->prid = NULL;
+    span->num_parents = 0;
+    span->parent.single = 0;
+    span->parent.list = NULL;
+    return span;
+}
+
+void htrace_span_free(struct htrace_span *span)
+{
+    if (!span) {
+        return;
+    }
+    free(span->desc);
+    free(span->prid);
+    if (span->num_parents > 1) {
+        free(span->parent.list);
+    }
+    free(span);
+}
+
+static int compare_spanids(const void *va, const void *vb)
+{
+    uint64_t a = *((uint64_t*)va);
+    uint64_t b = *((uint64_t*)vb);
+    if (a < b) {
+        return -1;
+    } else if (a > b) {
+        return 1;
+    } else {
+        return 0;
+    }
+}
+
+void htrace_span_sort_and_dedupe_parents(struct htrace_span *span)
+{
+    int i, j, num_parents = span->num_parents;
+    uint64_t prev;
+
+    if (num_parents <= 1) {
+        return;
+    }
+    qsort(span->parent.list, num_parents, sizeof(uint64_t), compare_spanids);
+    prev = span->parent.list[0];
+    j = 1;
+    for (i = 1; i < num_parents; i++) {
+        uint64_t id = span->parent.list[i];
+        if (id != prev) {
+            span->parent.list[j++] = span->parent.list[i];
+            prev = id;
+        }
+    }
+    span->num_parents = j;
+    if (j == 1) {
+        // After deduplication, there is now only one entry.  Switch to the
+        // optimized no-malloc representation for 1 entry.
+        free(span->parent.list);
+        span->parent.single = prev;
+    } else if (j != num_parents) {
+        // After deduplication, there are now fewer entries.  Use realloc to
+        // shrink the size of our dynamic allocation if possible.
+        uint64_t *nlist = realloc(span->parent.list, sizeof(uint64_t) * j);
+        if (nlist) {
+            span->parent.list = nlist;
+        }
+    }
+}
+
+/**
+ * Translate the span to a JSON string.
+ *
+ * This function can be called in two ways.  With buf == NULL, we will determine
+ * the size of the buffer that would be required to hold a JSON string
+ * containing the span contents.  With buf non-NULL, we will write the span
+ * contents to the provided buffer.
+ *
+ * @param scope             The scope
+ * @param max               The maximum number of bytes to write to buf.
+ * @param buf               If non-NULL, where the string will be written.
+ *
+ * @return                  The number of bytes that the span json would take
+ *                          up if it were written out.
+ */
+static int span_json_sprintf_impl(const struct htrace_span *span,
+                                  int max, char *buf)
+{
+    int num_parents, i, ret = 0;
+    const char *prefix = "";
+
+    // Note that we have validated the description and process ID strings to
+    // make sure they don't contain anything evil.  So we don't need to escape
+    // them here.
+
+    ret += fwdprintf(&buf, &max, "{\"s\":\"%016" PRIx64 "\",\"b\":%" PRId64
+                 ",\"e\":%" PRId64",", span->span_id, span->begin_ms,
+                 span->end_ms);
+    if (span->desc) {
+        ret += fwdprintf(&buf, &max, "\"d\":\"%s\",", span->desc);
+    }
+    if (span->prid) {
+        ret += fwdprintf(&buf, &max, "\"r\":\"%s\",", span->prid);
+    }
+    num_parents = span->num_parents;
+    if (num_parents == 0) {
+        ret += fwdprintf(&buf, &max, "\"p\":[]");
+    } else if (num_parents == 1) {
+        ret += fwdprintf(&buf, &max, "\"p\":[\"%016"PRIx64"\"]",
+                         span->parent.single);
+    } else if (num_parents > 1) {
+        ret += fwdprintf(&buf, &max, "\"p\":[");
+        for (i = 0; i < num_parents; i++) {
+            ret += fwdprintf(&buf, &max, "%s\"%016" PRIx64 "\"", prefix,
+                             span->parent.list[i]);
+            prefix = ",";
+        }
+        ret += fwdprintf(&buf, &max, "]");
+    }
+    ret += fwdprintf(&buf, &max, "}");
+    // Add one to 'ret' to take into account the terminating null that we
+    // need to write.
+    return ret + 1;
+}
+
+int span_json_size(const struct htrace_span *scope)
+{
+    return span_json_sprintf_impl(scope, 0, NULL);
+}
+
+void span_json_sprintf(const struct htrace_span *scope, int max, void *buf)
+{
+    span_json_sprintf_impl(scope, max, buf);
+}
+
+// vim:ts=4:sw=4:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/core/span.h
----------------------------------------------------------------------
diff --git a/htrace-c/src/core/span.h b/htrace-c/src/core/span.h
new file mode 100644
index 0000000..b19bd94
--- /dev/null
+++ b/htrace-c/src/core/span.h
@@ -0,0 +1,138 @@
+/**
+ * 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.
+ */
+
+#ifndef APACHE_HTRACE_SPAN_H
+#define APACHE_HTRACE_SPAN_H
+
+/**
+ * @file span.h
+ *
+ * Functions related to HTrace spans and trace scopes.
+ *
+ * This is an internal header, not intended for external use.
+ */
+
+#include <stdint.h>
+
+struct htracer;
+
+struct htrace_span {
+    /**
+     * The name of this trace scope.
+     * Dynamically allocated.  Will never be NULL.
+     */
+    char *desc;
+
+    /**
+     * The beginning time in wall-clock milliseconds.
+     */
+    uint64_t begin_ms;
+
+    /**
+     * The end time in wall-clock milliseconds.
+     */
+    uint64_t end_ms;
+
+    /**
+     * The span id.
+     */
+    uint64_t span_id;
+
+    /**
+     * The process ID of this trace scope.
+     * Dynamically allocated.  May be null.
+     */
+    char *prid;
+
+    /**
+     * The number of parents.
+     */
+    int num_parents;
+
+    union {
+        /**
+         * If there is 1 parent, this is the parent ID.
+         */
+        uint64_t single;
+
+        /**
+         * If there are multiple parents, this is a pointer to a dynamically
+         * allocated array of parent IDs.
+         */
+        uint64_t *list;
+    } parent;
+};
+
+/**
+ * Allocate an htrace span.
+ *
+ * @param desc          The span name to use.  Will be deep-copied.
+ * @param begin_ms      The value to use for begin_ms.
+ * @param span_id       The span ID to use.
+ *
+ * @return              NULL on OOM; the span otherwise.
+ */
+struct htrace_span *htrace_span_alloc(const char *desc,
+                uint64_t begin_ms, uint64_t span_id);
+
+/**
+ * Free the memory associated with an htrace span.
+ *
+ * @param span          The span to free.
+ */
+void htrace_span_free(struct htrace_span *span);
+
+/**
+ * Sort and deduplicate the parents array within the span.
+ *
+ * @param span          The span to process.
+ */
+void htrace_span_sort_and_dedupe_parents(struct htrace_span *span);
+
+/**
+ * Escape a JSON string.  Specifically, put backslashes before double quotes and
+ * other backslashes.
+ *
+ * @param in            The string to escape.
+ *
+ * @param out           The escaped string.  Malloced. NULL on OOM.
+ */
+char *json_escape(const char *in);
+
+/**
+ * Get the buffer size that would be needed to serialize this span to a buffer.
+ *
+ * @param span          The span.
+ *
+ * @return              The buffer size in bytes.  This will never be less
+ *                          than 1.
+ */
+int span_json_size(const struct htrace_span *span);
+
+/**
+ * Get the buffer size that would be needed to serialize this span to a buffer.
+ *
+ * @param span          The span.
+ *
+ * @return              The buffer size in bytes.
+ */
+void span_json_sprintf(const struct htrace_span *span, int max, void *buf);
+
+#endif
+
+// vim: ts=4:sw=4:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/receiver/curl.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/receiver/curl.c b/htrace-c/src/receiver/curl.c
new file mode 100644
index 0000000..0905dd4
--- /dev/null
+++ b/htrace-c/src/receiver/curl.c
@@ -0,0 +1,124 @@
+/**
+ * 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.
+ */
+
+#include "core/conf.h"
+#include "util/log.h"
+
+#include <curl/curl.h>
+#include <pthread.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/**
+ * Unfortunately, libcurl requires a non-threadsafe initialization function to
+ * be called before it is usable.  This is unfortunate for a library like
+ * libhtrace, which is designed to be used in a multi-threaded context.
+ *
+ * This mutex protects us against an application creating two htraced receivers
+ * at around the same time, and calling that non-threadsafe initialization
+ * function.
+ *
+ * Of course, this doesn't protect us against the application also initializing
+ * libcurl.  We can protect against that by statically linking a private copy of
+ * libcurl into libhtrace, so that we will be initializing and using our own
+ * private copy of libcurl rather than the application's.
+ */
+static pthread_mutex_t g_curl_refcnt_lock = PTHREAD_MUTEX_INITIALIZER;
+
+/**
+ * The current number of CURL handles that are open.
+ */
+static int64_t g_curl_refcnt;
+
+static int curl_addref(struct htrace_log *lg)
+{
+    int success = 0;
+    CURLcode curl_err = 0;
+
+    pthread_mutex_lock(&g_curl_refcnt_lock);
+    if (g_curl_refcnt >= 1) {
+        g_curl_refcnt++;
+        success = 1;
+        goto done;
+    }
+    curl_err = curl_global_init(CURL_GLOBAL_ALL);
+    if (curl_err) {
+        htrace_log(lg, "curl_global_init failed: error %d (%s)\n",
+                   curl_err, curl_easy_strerror(curl_err));
+        goto done;
+    }
+    htrace_log(lg, "successfully initialized libcurl...\n");
+    g_curl_refcnt = 1;
+    success = 1;
+
+done:
+    pthread_mutex_unlock(&g_curl_refcnt_lock);
+    return success;
+}
+
+static void curl_unref(struct htrace_log *lg)
+{
+    pthread_mutex_lock(&g_curl_refcnt_lock);
+    g_curl_refcnt--;
+    if (g_curl_refcnt > 0) {
+        goto done;
+    }
+    curl_global_cleanup();
+    htrace_log(lg, "shut down libcurl...\n");
+done:
+    pthread_mutex_unlock(&g_curl_refcnt_lock);
+}
+
+CURL* htrace_curl_init(struct htrace_log *lg, const struct htrace_conf *conf)
+{
+    CURL *curl = NULL;
+    int success = 0;
+
+    if (!curl_addref(lg)) {
+        return NULL;
+    }
+    curl = curl_easy_init();
+    if (!curl) {
+        htrace_log(lg, "curl_easy_init failed.\n");
+        goto done;
+    }
+    success = 1;
+
+done:
+    if (!success) {
+        if (curl) {
+            curl_easy_cleanup(curl);
+        }
+        curl_unref(lg);
+        return NULL;
+    }
+    return curl;
+}
+
+void htrace_curl_free(struct htrace_log *lg, CURL *curl)
+{
+    if (!curl) {
+        return;
+    }
+    curl_easy_cleanup(curl);
+    curl_unref(lg);
+}
+
+// vim:ts=4:sw=4:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/receiver/curl.h
----------------------------------------------------------------------
diff --git a/htrace-c/src/receiver/curl.h b/htrace-c/src/receiver/curl.h
new file mode 100644
index 0000000..9249cd3
--- /dev/null
+++ b/htrace-c/src/receiver/curl.h
@@ -0,0 +1,60 @@
+/**
+ * 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.
+ */
+
+#ifndef APACHE_HTRACE_RECEIVER_CURL_H
+#define APACHE_HTRACE_RECEIVER_CURL_H
+
+/**
+ * @file curl.h
+ *
+ * Utility functions wrapping libcurl.
+ *
+ * This is an internal header, not intended for external use.
+ */
+
+#include <curl/curl.h> // for the CURL type
+
+struct htrace_conf;
+struct htrace_log;
+
+/**
+ * Initialize a libcurl handle.
+ *
+ * This function also takes care of calling curl_global_init if necessary.
+ *
+ * @param lg            The HTrace log to use for error messages.
+ * @param conf          The HTrace configuration to use.
+ *
+ * @return              A libcurl handle, or NULL on failure.
+ */
+CURL* htrace_curl_init(struct htrace_log *lg, const struct htrace_conf *conf);
+
+/**
+ * Free a libcurl handle.
+ *
+ * This function also takes care of calling curl_global_cleanup if necessary.
+ *
+ * @param lg            The HTrace log to use for error messages.
+ *
+ * @param curl          The libcurl handle.
+ */
+void htrace_curl_free(struct htrace_log *lg, CURL *curl);
+
+#endif
+
+// vim: ts=4: sw=4: et


[3/4] incubator-htrace git commit: HTRACE-106: htrace: add C / C++ native client (cmccabe)

Posted by cm...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/receiver/htraced.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/receiver/htraced.c b/htrace-c/src/receiver/htraced.c
new file mode 100644
index 0000000..ea61541
--- /dev/null
+++ b/htrace-c/src/receiver/htraced.c
@@ -0,0 +1,649 @@
+/**
+ * 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.
+ */
+
+#include "core/conf.h"
+#include "core/htrace.h"
+#include "core/htracer.h"
+#include "core/span.h"
+#include "receiver/curl.h"
+#include "receiver/receiver.h"
+#include "test/test.h"
+#include "util/log.h"
+#include "util/time.h"
+
+#include <errno.h>
+#include <curl/curl.h>
+#include <inttypes.h>
+#include <pthread.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/**
+ * @file htraced.c
+ *
+ * The htraced span receiver which implements sending spans on to htraced.
+ */
+
+/**
+ * The minimum buffer size to allow for the htraced circular buffer.
+ *
+ * This should hopefully allow at least a few spans to be buffered.
+ */
+#define HTRACED_MIN_BUFFER_SIZE (4ULL * 1024ULL * 1024ULL)
+
+/**
+ * The maximum buffer size to allow for the htraced circular buffer.
+ * This is mainly to avoid overflow.  Of course, you couldn't allocate a buffer
+ * anywhere near this size anyway.
+ */
+#define HTRACED_MAX_BUFFER_SIZE 0x7ffffffffffffffLL
+
+/**
+ * The minimum number of milliseconds to allow for send_timeo_ms.
+ */
+#define HTRACED_SEND_TIMEO_MS_MIN 30000LL
+
+/**
+ * The maximum number of milliseconds to allow for send_timeo_ms.
+ * This is mainly to avoid overflow.
+ */
+#define HTRACED_SEND_TIMEO_MS_MAX 86400000LL
+
+/**
+ * The maximum size of the message we will send over the wire.
+ * This also sets the size of the transmission buffer.
+ * This constant must not be more than 2^^32 on 32-bit systems.
+ */
+#define HTRACED_MAX_MSG_LEN (8ULL * 1024ULL * 1024ULL)
+
+/**
+ * The maximum number of times we will try to add a span to the circular buffer
+ * before giving up.
+ */
+#define HTRACED_MAX_ADD_TRIES 3
+
+/**
+ * The maximum number of times to try to send some spans to the htraced daemon
+ * before giving up.
+ */
+#define HTRACED_MAX_SEND_TRIES 3
+
+/**
+ * The number of milliseconds to sleep after failing to send some spans to the
+ * htraced daemon.
+ */
+#define HTRACED_SEND_RETRY_SLEEP_MS 5000
+
+/*
+ * A span receiver that writes spans to htraced.
+ */
+struct htraced_rcv {
+    struct htrace_rcv base;
+
+    /**
+     * Nonzero if the receiver should shut down.
+     */
+    int shutdown;
+
+    /**
+     * The htracer object associated with this receciver.
+     */
+    struct htracer *tracer;
+
+    /**
+     * The HTraced server URL.
+     * Dynamically allocated.
+     */
+    char *url;
+
+    /**
+     * Buffered span data becomes eligible to be sent even if there isn't much
+     * in the buffer after this timeout elapses.
+     */
+    uint64_t send_timeo_ms;
+
+    /**
+     * The maximum number of bytes we will buffer before waking the sending
+     * thread.  We may sometimes send slightly more than this amount if the
+     * thread takes a while to wake up.
+     */
+    uint64_t send_threshold;
+
+    /**
+     * The CURL handle.
+     */
+    CURL *curl;
+
+    /**
+     * Length of the circular buffer.
+     */
+    uint64_t clen;
+
+    /**
+     * A circular buffer containing span data.
+     */
+    uint8_t *cbuf;
+
+    /**
+     * 'start' pointer of the circular buffer.
+     */
+    uint64_t cstart;
+
+    /**
+     * 'end' pointer of the circular buffer.
+     */
+    uint64_t cend;
+
+    /**
+     * The monotonic-clock time at which we last did a send operation.
+     */
+    uint64_t last_send_ms;
+
+    /**
+     * RPC messages are copied into this buffer before being sent.
+     * Its length is HTRACED_MAX_MSG_LEN.
+     */
+    uint8_t *sbuf;
+
+    /**
+     * Lock protecting the circular buffer from concurrent writes.
+     */
+    pthread_mutex_t lock;
+
+    /**
+     * Condition variable used to wake up the background thread.
+     */
+    pthread_cond_t cond;
+
+    /**
+     * Background transmitter thread.
+     */
+    pthread_t xmit_thread;
+};
+
+void* run_htraced_xmit_manager(void *data);
+static int should_xmit(struct htraced_rcv *rcv, uint64_t now);
+static void htraced_xmit(struct htraced_rcv *rcv, uint64_t now);
+static uint64_t cbuf_used(const struct htraced_rcv *rcv);
+static int32_t cbuf_to_sbuf(struct htraced_rcv *rcv);
+
+static struct htrace_rcv *htraced_rcv_create(struct htracer *tracer,
+                                             const struct htrace_conf *conf)
+{
+    struct htraced_rcv *rcv;
+    const char *url;
+    int ret;
+
+    url = htrace_conf_get(conf, HTRACED_ADDRESS_KEY);
+    if (!url) {
+        htrace_log(tracer->lg, "htraced_rcv_create: no value found for %s. "
+                   "You must set this configuration key to the "
+                   "hostname:port identifying the htraced server.\n",
+                   HTRACED_ADDRESS_KEY);
+        goto error;
+    }
+    rcv = malloc(sizeof(*rcv));
+    if (!rcv) {
+        htrace_log(tracer->lg, "htraced_rcv_create: OOM while "
+                   "allocating htraced_rcv.\n");
+        goto error;
+    }
+    rcv->base.ty = &g_htraced_rcv_ty;
+    rcv->shutdown = 0;
+    rcv->tracer = tracer;
+    if (asprintf(&rcv->url, "%s/writeSpans", url) < 0) {
+        rcv->url = NULL;
+        goto error_free_rcv;
+    }
+    rcv->send_timeo_ms = htrace_conf_get_u64(tracer->lg, conf,
+                    HTRACED_SEND_TIMEOUT_MS_KEY);
+    if (rcv->send_timeo_ms < HTRACED_SEND_TIMEO_MS_MIN) {
+        htrace_log(tracer->lg, "htraced_rcv_create: invalid send timeout of %"
+                   PRId64 " ms.  Setting the minimum timeout of %lld"
+                   " ms instead.\n", rcv->send_timeo_ms, HTRACED_SEND_TIMEO_MS_MIN);
+        rcv->send_timeo_ms = HTRACED_SEND_TIMEO_MS_MIN;
+    } else if (rcv->send_timeo_ms > HTRACED_SEND_TIMEO_MS_MAX) {
+        htrace_log(tracer->lg, "htraced_rcv_create: invalid send timeout of %"
+                   PRId64 " ms.  Setting the maximum timeout of %lld"
+                   " ms instead.\n", rcv->send_timeo_ms, HTRACED_SEND_TIMEO_MS_MAX);
+        rcv->send_timeo_ms = HTRACED_SEND_TIMEO_MS_MAX;
+    }
+    rcv->curl = htrace_curl_init(tracer->lg, conf);
+    if (!rcv->curl) {
+        goto error_free_url;
+    }
+    rcv->clen = htrace_conf_get_u64(tracer->lg, conf, HTRACED_BUFFER_SIZE_KEY);
+    if (rcv->clen < HTRACED_MIN_BUFFER_SIZE) {
+        htrace_log(tracer->lg, "htraced_rcv_create: invalid buffer size %" PRId64
+                   ".  Setting the minimum buffer size of %llu"
+                   " instead.\n", rcv->clen, HTRACED_MIN_BUFFER_SIZE);
+        rcv->clen = HTRACED_MIN_BUFFER_SIZE;
+    } else if (rcv->clen > HTRACED_MAX_BUFFER_SIZE) {
+        htrace_log(tracer->lg, "htraced_rcv_create: invalid buffer size %" PRId64
+                   ".  Setting the maximum buffer size of %lld"
+                   " instead.\n", rcv->clen, HTRACED_MAX_BUFFER_SIZE);
+        rcv->clen = HTRACED_MAX_BUFFER_SIZE;
+    }
+    rcv->cbuf = malloc(rcv->clen);
+    if (!rcv->cbuf) {
+        htrace_log(tracer->lg, "htraced_rcv_create: failed to malloc %"PRId64
+                   " bytes for the htraced circular buffer.\n", rcv->clen);
+        goto error_free_curl;
+    }
+    // Send when the buffer gets 1/4 full.
+    rcv->send_threshold = rcv->clen * 0.25;
+    rcv->cstart = 0;
+    rcv->cend = 0;
+    rcv->last_send_ms = monotonic_now_ms(tracer->lg);
+    rcv->sbuf = malloc(HTRACED_MAX_MSG_LEN);
+    if (!rcv->sbuf) {
+        goto error_free_cbuf;
+    }
+    ret = pthread_mutex_init(&rcv->lock, NULL);
+    if (ret) {
+        htrace_log(tracer->lg, "htraced_rcv_create: pthread_mutex_init "
+                   "error %d: %s\n", ret, terror(ret));
+        goto error_free_sbuf;
+    }
+    ret = pthread_cond_init(&rcv->cond, NULL);
+    if (ret) {
+        htrace_log(tracer->lg, "htraced_rcv_create: pthread_cond_init "
+                   "error %d: %s\n", ret, terror(ret));
+        goto error_free_lock;
+    }
+    ret = pthread_create(&rcv->xmit_thread, NULL, run_htraced_xmit_manager, rcv);
+    if (ret) {
+        htrace_log(tracer->lg, "htraced_rcv_create: failed to create xmit thread: "
+                   "error %d: %s\n", ret, terror(ret));
+        goto error_free_cvar;
+    }
+    htrace_log(tracer->lg, "Initialized htraced receiver with url=%s, "
+               "send_timeo_ms=%" PRId64 ", send_threshold=%" PRId64 ", clen=%"
+               PRId64 ".\n", rcv->url, rcv->send_timeo_ms, rcv->send_threshold,
+               rcv->clen);
+    return (struct htrace_rcv*)rcv;
+
+error_free_cvar:
+    pthread_cond_destroy(&rcv->cond);
+error_free_lock:
+    pthread_mutex_destroy(&rcv->lock);
+error_free_sbuf:
+    free(rcv->sbuf);
+error_free_cbuf:
+    free(rcv->cbuf);
+error_free_curl:
+    htrace_curl_free(tracer->lg, rcv->curl);
+error_free_url:
+    free(rcv->url);
+error_free_rcv:
+    free(rcv);
+error:
+    return NULL;
+}
+
+void* run_htraced_xmit_manager(void *data)
+{
+    struct htraced_rcv *rcv = data;
+    struct htrace_log *lg = rcv->tracer->lg;
+    uint64_t now, wakeup;
+    struct timespec wakeup_ts;
+    int ret;
+
+    pthread_mutex_lock(&rcv->lock);
+    while (1) {
+        now = monotonic_now_ms(lg);
+        while (should_xmit(rcv, now)) {
+            htraced_xmit(rcv, now);
+        }
+        if (rcv->shutdown) {
+            while (cbuf_used(rcv) > 0) {
+                htraced_xmit(rcv, now);
+            }
+            break;
+        }
+        // Wait for one of a few things to happen:
+        // * Shutdown
+        // * The wakeup timer to elapse, leading us to check if we should send
+        //      because of send_timeo_ms.
+        // * A writer to signal that we should wake up because enough bytes are
+        //      buffered.
+        wakeup = now + (rcv->send_timeo_ms / 2);
+        ms_to_timespec(wakeup, &wakeup_ts);
+        ret = pthread_cond_timedwait(&rcv->cond, &rcv->lock, &wakeup_ts);
+        if ((ret != 0) && (ret != ETIMEDOUT)) {
+            htrace_log(lg, "run_htraced_xmit_manager: pthread_cond_timedwait "
+                       "error: %d (%s)\n", ret, terror(ret));
+        }
+    }
+    pthread_mutex_unlock(&rcv->lock);
+    htrace_log(lg, "run_htraced_xmit_manager: shutting down the transmission "
+               "manager thread.\n");
+    return NULL;
+}
+
+/**
+ * Determine if the xmit manager should send.
+ * This function must be called with the lock held.
+ *
+ * @param rcv           The htraced receiver.
+ * @param now           The current time in milliseconds.
+ *
+ * @return              nonzero if we should send now.
+ */
+static int should_xmit(struct htraced_rcv *rcv, uint64_t now)
+{
+    uint64_t used;
+
+    used = cbuf_used(rcv);
+    if (used > rcv->send_threshold) {
+        // We have buffered a lot of bytes, so let's send.
+        return 1;
+    }
+    if (now - rcv->last_send_ms > rcv->send_timeo_ms) {
+        // It's been too long since the last transmission, so let's send.
+        if (used > 0) {
+            return 1;
+        }
+    }
+    return 0; // Let's wait.
+}
+
+/**
+ * Send all the spans which we have buffered.
+ *
+ * @param rcv           The htraced receiver.
+ * @param slen          The length of the buffer to send.
+ *
+ * @return              1 on success; 0 otherwise.
+ */
+static int htraced_xmit_impl(struct htraced_rcv *rcv, int32_t slen)
+{
+    struct htrace_log *lg = rcv->tracer->lg;
+    CURLcode res;
+    char *pid_header = NULL;
+    struct curl_slist *headers = NULL;
+    int ret = 0;
+
+    // Disable the use of SIGALARM to interrupt DNS lookups.
+    curl_easy_setopt(rcv->curl, CURLOPT_NOSIGNAL, 1);
+    // Do not use a global DNS cache.
+    curl_easy_setopt(rcv->curl, CURLOPT_DNS_USE_GLOBAL_CACHE, 0);
+    // Disable verbosity.
+    curl_easy_setopt(rcv->curl, CURLOPT_VERBOSE, 0);
+    // The user agent is libhtraced.
+    curl_easy_setopt(rcv->curl, CURLOPT_USERAGENT, "libhtraced");
+    // Set URL
+    curl_easy_setopt(rcv->curl, CURLOPT_URL, rcv->url);
+    // Set POST
+    curl_easy_setopt(rcv->curl, CURLOPT_POST, 1L);
+    // Set the size that we're copying from rcv->sbuf
+    curl_easy_setopt(rcv->curl, CURLOPT_POSTFIELDSIZE, (long)slen);
+    if (asprintf(&pid_header, "htrace-pid: %s", rcv->tracer->prid) < 0) {
+        htrace_log(lg, "htraced_xmit(%s) failed: OOM allocating htrace-pid\n",
+                   rcv->url);
+        goto done;
+    }
+    curl_easy_setopt(rcv->curl, CURLOPT_POSTFIELDS, rcv->sbuf);
+    headers = curl_slist_append(headers, pid_header);
+    if (!headers) {
+        htrace_log(lg, "htraced_xmit(%s) failed: OOM allocating headers\n",
+                   rcv->url);
+        return 0;
+    }
+    headers = curl_slist_append(headers, "Content-Type: application/json");
+    if (!headers) {
+        htrace_log(lg, "htraced_xmit(%s) failed: OOM allocating headers\n",
+                   rcv->url);
+        return 0;
+    }
+    curl_easy_setopt(rcv->curl, CURLOPT_HTTPHEADER, headers);
+    res = curl_easy_perform(rcv->curl);
+    if (res != CURLE_OK) {
+        htrace_log(lg, "htraced_xmit(%s) failed: error %lld (%s)\n",
+                   rcv->url, (long long)res, curl_easy_strerror(res));
+    }
+    ret = res == CURLE_OK;
+done:
+    curl_easy_reset(rcv->curl);
+    free(pid_header);
+    curl_slist_free_all(headers);
+    return ret;
+}
+
+static void htraced_xmit(struct htraced_rcv *rcv, uint64_t now)
+{
+    int32_t slen;
+    int tries = 0;
+
+    // Move span data from the circular buffer into the transmission buffer.
+    slen = cbuf_to_sbuf(rcv);
+
+    // Release the lock while doing network I/O, so that we don't block threads
+    // adding spans.
+    pthread_mutex_unlock(&rcv->lock);
+    while (1) {
+        int retry, success = htraced_xmit_impl(rcv, slen);
+        if (success) {
+            break;
+        }
+        tries++;
+        retry = (tries < HTRACED_MAX_SEND_TRIES);
+        htrace_log(rcv->tracer->lg, "htraced_xmit(%s) failed on try %d.  %s\n",
+                   rcv->url, tries,
+                   (retry ? "Retrying after a delay." : "Giving up."));
+        if (!retry) {
+            break;
+        }
+    }
+    pthread_mutex_lock(&rcv->lock);
+    rcv->last_send_ms = now;
+}
+
+/**
+ * Move data from the circular buffer into the transmission buffer, advancing
+ * the circular buffer's start offset.
+ *
+ * This function must be called with the lock held.
+ *
+ * Note that we rely on HTRACED_MAX_MSG_LEN being < 4GB in this function for
+ * correctness on 32-bit systems.
+ *
+ * @param rcv           The htraced receiver.
+ *
+ * @return              The amount of data copied.
+ */
+static int32_t cbuf_to_sbuf(struct htraced_rcv *rcv)
+{
+    int32_t rem = HTRACED_MAX_MSG_LEN;
+    size_t amt;
+
+    if (rcv->cstart < rcv->cend) {
+        amt = rcv->cend - rcv->cstart;
+        if (amt > rem) {
+            amt = rem;
+        }
+        memcpy(rcv->sbuf, rcv->cbuf + rcv->cstart, amt);
+        rem -= amt;
+        rcv->cstart += amt;
+    } else {
+        amt = rcv->clen - rcv->cstart;
+        if (amt > rem) {
+            amt = rem;
+        }
+        memcpy(rcv->sbuf, rcv->cbuf + rcv->cstart, amt);
+        rem -= amt;
+        rcv->cstart += amt;
+        if (rem > 0) {
+            amt = rcv->cend;
+            if (amt > rem) {
+                amt = rem;
+            }
+            memcpy(rcv->sbuf, rcv->cbuf, amt);
+            rem -= amt;
+            rcv->cstart = amt;
+        }
+    }
+    return HTRACED_MAX_MSG_LEN - rem;
+}
+
+/**
+ * Returns the current number of bytes used in the htraced circular buffer.
+ * Must be called under the lock.
+ *
+ * @param rcv           The htraced receiver.
+ *
+ * @return              The number of bytes used.
+ */
+static uint64_t cbuf_used(const struct htraced_rcv *rcv)
+{
+    if (rcv->cstart <= rcv->cend) {
+        return rcv->cend - rcv->cstart;
+    }
+    return rcv->clen - (rcv->cstart - rcv->cend);
+}
+
+static void htraced_rcv_add_span(struct htrace_rcv *r,
+                                 struct htrace_span *span)
+{
+    int json_len, tries, retry;
+    uint64_t used, rem;
+    struct htraced_rcv *rcv = (struct htraced_rcv *)r;
+    struct htrace_log *lg = rcv->tracer->lg;
+
+    {
+        char buf[4096];
+        span_json_sprintf(span, sizeof(buf), buf);
+    }
+
+    json_len = span_json_size(span);
+    tries = 0;
+    do {
+        pthread_mutex_lock(&rcv->lock);
+        used = cbuf_used(rcv);
+        if (used + json_len >= rcv->clen) {
+            pthread_cond_signal(&rcv->cond);
+            pthread_mutex_unlock(&rcv->lock);
+            tries++;
+            retry = tries < HTRACED_MAX_ADD_TRIES;
+            htrace_log(lg, "htraced_rcv_add_span: not enough space in the "
+                           "circular buffer.  Have %" PRId64 ", need %d"
+                           ".  %s...\n", (rcv->clen - used), json_len,
+                           (retry ? "Retrying" : "Giving up"));
+            if (retry) {
+                pthread_yield();
+                continue;
+            }
+            return;
+        }
+    } while (0);
+    // OK, now we have the lock, and we know that there is enough space in the
+    // circular buffer.
+    rem = rcv->clen - rcv->cend;
+    if (rem < json_len) {
+        // Handle a 'torn write' where the circular buffer loops around to the
+        // beginning in the middle of the write.
+        char *temp = alloca(json_len);
+        span_json_sprintf(span, json_len, temp);
+        temp[json_len - 1] = '\n';
+        memcpy(rcv->cbuf + rcv->cend, temp, rem);
+        memcpy(rcv->cbuf, temp + rem, json_len - rem);
+        rcv->cend = json_len - rem;
+    } else {
+        span_json_sprintf(span, json_len, rcv->cbuf + rcv->cend);
+        rcv->cbuf[rcv->cend + json_len - 1] = '\n';
+        rcv->cend += json_len;
+    }
+    used += json_len;
+    if (used > rcv->send_threshold) {
+        pthread_cond_signal(&rcv->cond);
+    }
+    pthread_mutex_unlock(&rcv->lock);
+}
+
+static void htraced_rcv_flush(struct htrace_rcv *r)
+{
+    struct htraced_rcv *rcv = (struct htraced_rcv *)r;
+
+    while (1) {
+        pthread_mutex_lock(&rcv->lock);
+        if (cbuf_used(rcv) == 0) {
+            // If the buffer is empty, we're done.
+            // Note that there is no guarantee that we'll ever be done if spans
+            // are being added continuously throughout the flush.  This is OK,
+            // since flush() is actually only used by unit tests.
+            // We could do something more clever here, but it would be a lot more
+            // complex.
+            pthread_mutex_unlock(&rcv->lock);
+            break;
+        }
+        // Get the xmit thread to send what it can, by resetting the "last send
+        // time" to the oldest possible monotonic time.
+        rcv->last_send_ms = 0;
+        pthread_cond_signal(&rcv->cond);
+        pthread_mutex_unlock(&rcv->lock);
+    }
+}
+
+static void htraced_rcv_free(struct htrace_rcv *r)
+{
+    struct htraced_rcv *rcv = (struct htraced_rcv *)r;
+    struct htrace_log *lg;
+    int ret;
+
+    if (!rcv) {
+        return;
+    }
+    lg = rcv->tracer->lg;
+    htrace_log(lg, "Shutting down htraced receiver with url=%s\n", rcv->url);
+    pthread_mutex_lock(&rcv->lock);
+    rcv->shutdown = 1;
+    pthread_cond_signal(&rcv->cond);
+    pthread_mutex_unlock(&rcv->lock);
+    ret = pthread_join(rcv->xmit_thread, NULL);
+    if (ret) {
+        htrace_log(lg, "htraced_rcv_free: pthread_join "
+                   "error %d: %s\n", ret, terror(ret));
+    }
+    free(rcv->url);
+    free(rcv->cbuf);
+    free(rcv->sbuf);
+    htrace_curl_free(lg, rcv->curl);
+    ret = pthread_mutex_destroy(&rcv->lock);
+    if (ret) {
+        htrace_log(lg, "htraced_rcv_free: pthread_mutex_destroy "
+                   "error %d: %s\n", ret, terror(ret));
+    }
+    ret = pthread_cond_destroy(&rcv->cond);
+    if (ret) {
+        htrace_log(lg, "htraced_rcv_free: pthread_cond_destroy "
+                   "error %d: %s\n", ret, terror(ret));
+    }
+    free(rcv);
+}
+
+const struct htrace_rcv_ty g_htraced_rcv_ty = {
+    "htraced",
+    htraced_rcv_create,
+    htraced_rcv_add_span,
+    htraced_rcv_flush,
+    htraced_rcv_free,
+};
+
+// vim:ts=4:sw=4:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/receiver/local_file.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/receiver/local_file.c b/htrace-c/src/receiver/local_file.c
new file mode 100644
index 0000000..667da29
--- /dev/null
+++ b/htrace-c/src/receiver/local_file.c
@@ -0,0 +1,184 @@
+/**
+ * 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.
+ */
+
+#include "core/conf.h"
+#include "core/htrace.h"
+#include "core/htracer.h"
+#include "core/span.h"
+#include "receiver/receiver.h"
+#include "util/log.h"
+
+#include <errno.h>
+#include <pthread.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/*
+ * A span receiver that writes spans to a local file.
+ */
+struct local_file_rcv {
+    struct htrace_rcv base;
+
+    /**
+     * The htracer object associated with this receciver.
+     */
+    struct htracer *tracer;
+
+    /**
+     * The local file.
+     */
+    FILE *fp;
+
+    /**
+     * Path to the local file.  Dynamically allocated.
+     */
+    char *path;
+
+    /**
+     * Lock protecting the local file from concurrent writes.
+     */
+    pthread_mutex_t lock;
+};
+
+static void local_file_rcv_free(struct htrace_rcv *r);
+
+static struct htrace_rcv *local_file_rcv_create(struct htracer *tracer,
+                                             const struct htrace_conf *conf)
+{
+    struct local_file_rcv *rcv;
+    const char *path;
+    int ret;
+
+    path = htrace_conf_get(conf, HTRACE_LOCAL_FILE_RCV_PATH_KEY);
+    if (!path) {
+        htrace_log(tracer->lg, "local_file_rcv_create: no value found for %s. "
+                   "You must set this configuration key to the path you wish "
+                   "to write spans to.\n", HTRACE_LOCAL_FILE_RCV_PATH_KEY);
+        return NULL;
+    }
+    rcv = calloc(1, sizeof(*rcv));
+    if (!rcv) {
+        htrace_log(tracer->lg, "local_file_rcv_create: OOM while "
+                   "allocating local_file_rcv.\n");
+        return NULL;
+    }
+    ret = pthread_mutex_init(&rcv->lock, NULL);
+    if (ret) {
+        htrace_log(tracer->lg, "local_file_rcv_create: failed to "
+                   "create mutex while setting up local_file_rcv: "
+                   "error %d (%s)\n", ret, terror(ret));
+        free(rcv);
+        return NULL;
+    }
+    rcv->base.ty = &g_local_file_rcv_ty;
+    rcv->path = strdup(path);
+    if (!rcv->path) {
+        local_file_rcv_free((struct htrace_rcv*)rcv);
+        return NULL;
+    }
+    rcv->tracer = tracer;
+    rcv->fp = fopen(path, "a");
+    if (!rcv->fp) {
+        ret = errno;
+        htrace_log(tracer->lg, "local_file_rcv_create: failed to "
+                   "open '%s' for write: error %d (%s)\n",
+                   path, ret, terror(ret));
+        local_file_rcv_free((struct htrace_rcv*)rcv);
+    }
+    htrace_log(tracer->lg, "Initialized local_file receiver with path=%s.\n",
+               rcv->path);
+    return (struct htrace_rcv*)rcv;
+}
+
+static void local_file_rcv_add_span(struct htrace_rcv *r,
+                                    struct htrace_span *span)
+{
+    int len, res, err;
+    char *buf;
+    struct local_file_rcv *rcv = (struct local_file_rcv *)r;
+
+    span->prid = rcv->tracer->prid;
+    len = span_json_size(span);
+    buf = malloc(len + 1);
+    if (!buf) {
+        span->prid = NULL;
+        htrace_log(rcv->tracer->lg, "local_file_rcv_add_span: OOM\n");
+        return;
+    }
+    span_json_sprintf(span, len, buf);
+    span->prid = NULL;
+    buf[len - 1] = '\n';
+    buf[len] = '\0';
+    pthread_mutex_lock(&rcv->lock);
+    res = fwrite(buf, 1, len, rcv->fp);
+    err = errno;
+    pthread_mutex_unlock(&rcv->lock);
+    if (res < len) {
+        htrace_log(rcv->tracer->lg, "local_file_rcv_add_span(%s): fwrite error: "
+                   "%d (%s)\n", rcv->path, err, terror(err));
+    }
+    free(buf);
+}
+
+static void local_file_rcv_flush(struct htrace_rcv *r)
+{
+    struct local_file_rcv *rcv = (struct local_file_rcv *)r;
+    if (fflush(rcv->fp) < 0) {
+        int e = errno;
+        htrace_log(rcv->tracer->lg, "local_file_rcv_flush(path=%s): fflush "
+                   "error: %s\n", rcv->path, terror(e));
+    }
+}
+
+static void local_file_rcv_free(struct htrace_rcv *r)
+{
+    struct local_file_rcv *rcv = (struct local_file_rcv *)r;
+    int ret;
+    struct htrace_log *lg;
+
+    if (!rcv) {
+        return;
+    }
+    lg = rcv->tracer->lg;
+    htrace_log(lg, "Shutting down local_file receiver with path=%s\n",
+               rcv->path);
+    ret = pthread_mutex_destroy(&rcv->lock);
+    if (ret) {
+        htrace_log(lg, "local_file_rcv_free: pthread_mutex_destroy "
+                   "error %d: %s\n", ret, terror(ret));
+    }
+    ret = fclose(rcv->fp);
+    if (ret) {
+        htrace_log(lg, "local_file_rcv_free: fclose error "
+                   "%d: %s\n", ret, terror(ret));
+    }
+    free(rcv->path);
+    free(rcv);
+}
+
+const struct htrace_rcv_ty g_local_file_rcv_ty = {
+    "local.file",
+    local_file_rcv_create,
+    local_file_rcv_add_span,
+    local_file_rcv_flush,
+    local_file_rcv_free,
+};
+
+// vim:ts=4:sw=4:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/receiver/noop.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/receiver/noop.c b/htrace-c/src/receiver/noop.c
new file mode 100644
index 0000000..980854b
--- /dev/null
+++ b/htrace-c/src/receiver/noop.c
@@ -0,0 +1,65 @@
+/**
+ * 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.
+ */
+
+#include "core/htracer.h"
+#include "receiver/receiver.h"
+#include "util/log.h"
+
+/**
+ * A span receiver that does nothing but discard all spans.
+ */
+struct noop_rcv {
+    struct htrace_rcv base;
+};
+
+struct noop_rcv g_noop_rcv = {
+    { &g_noop_rcv_ty },
+};
+
+static struct htrace_rcv *noop_rcv_create(struct htracer *tracer,
+                                          const struct htrace_conf *conf)
+{
+    htrace_log(tracer->lg, "Using no-op htrace span receiver.\n");
+    return (struct htrace_rcv *)&g_noop_rcv;
+}
+
+static void noop_rcv_add_span(struct htrace_rcv *rcv,
+                              struct htrace_span *span)
+{
+    // do nothing
+}
+
+static void noop_rcv_flush(struct htrace_rcv *rcv)
+{
+    // do nothing
+}
+
+static void noop_rcv_free(struct htrace_rcv *rcv)
+{
+    // do nothing
+}
+
+const struct htrace_rcv_ty g_noop_rcv_ty = {
+    "noop",
+    noop_rcv_create,
+    noop_rcv_add_span,
+    noop_rcv_flush,
+    noop_rcv_free,
+};
+
+// vim:ts=4:sw=4:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/receiver/receiver.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/receiver/receiver.c b/htrace-c/src/receiver/receiver.c
new file mode 100644
index 0000000..7fd3669
--- /dev/null
+++ b/htrace-c/src/receiver/receiver.c
@@ -0,0 +1,75 @@
+/**
+ * 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.
+ */
+
+#include "core/conf.h"
+#include "core/htrace.h"
+#include "core/htracer.h"
+#include "receiver/receiver.h"
+#include "util/log.h"
+
+#include <stdint.h>
+#include <string.h>
+
+const struct htrace_rcv_ty * const g_rcv_tys[] = {
+    &g_noop_rcv_ty,
+    &g_local_file_rcv_ty,
+    &g_htraced_rcv_ty,
+    NULL,
+};
+
+static const struct htrace_rcv_ty *select_rcv_ty(struct htracer *tracer,
+                                             const struct htrace_conf *conf)
+{
+    const char *tstr;
+    const char *prefix = "";
+    size_t i;
+    char buf[256] = { 0 };
+
+    tstr = htrace_conf_get(conf, HTRACE_SPAN_RECEIVER_KEY);
+    if (!tstr) {
+        htrace_log(tracer->lg, "No %s configured.\n", HTRACE_SPAN_RECEIVER_KEY);
+        return &g_noop_rcv_ty;
+    }
+    for (i = 0; g_rcv_tys[i]; i++) {
+        if (strcmp(g_rcv_tys[i]->name, tstr) == 0) {
+            return g_rcv_tys[i];
+        }
+    }
+    for (i = 0; g_rcv_tys[i]; i++) {
+        if ((strlen(buf) + strlen(prefix) +
+                 strlen(g_rcv_tys[i]->name)) < sizeof(buf)) {
+            strcat(buf, prefix);
+            strcat(buf, g_rcv_tys[i]->name);
+            prefix = ", ";
+        }
+    }
+    htrace_log(tracer->lg, "Unknown span receiver type as '%s'.  Valid "
+               "span receiver types are: %s\n", tstr, buf);
+    return &g_noop_rcv_ty;
+}
+
+struct htrace_rcv *htrace_rcv_create(struct htracer *tracer,
+                                     const struct htrace_conf *conf)
+{
+    const struct htrace_rcv_ty *ty;
+
+    ty = select_rcv_ty(tracer, conf);
+    return ty->create(tracer, conf);
+}
+
+// vim:ts=4:sw=4:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/receiver/receiver.h
----------------------------------------------------------------------
diff --git a/htrace-c/src/receiver/receiver.h b/htrace-c/src/receiver/receiver.h
new file mode 100644
index 0000000..1e01486
--- /dev/null
+++ b/htrace-c/src/receiver/receiver.h
@@ -0,0 +1,114 @@
+/**
+ * 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.
+ */
+
+#ifndef APACHE_HTRACE_RECEIVER_RECEIVER_H
+#define APACHE_HTRACE_RECEIVER_RECEIVER_H
+
+/**
+ * @file rcv.h
+ *
+ * Functions related to HTrace receivers.
+ *
+ * This is an internal header, not intended for external use.
+ */
+
+struct htrace_conf;
+struct htrace_span;
+struct htracer;
+
+/**
+ * Base class for an HTrace span receiver.
+ *
+ * Implementations should begin with this class as the first member.
+ */
+struct htrace_rcv {
+    /**
+     * The type of the receiver.
+     */
+    const struct htrace_rcv_ty *ty;
+};
+
+/**
+ * A table of callbacks that implements an HTrace span receiver.
+ */
+struct htrace_rcv_ty {
+    /**
+     * The name of this HTrace span receiver type.
+     */
+    const char * const name;
+
+    /**
+     * Create an HTrace span receiver of this type.
+     *
+     * @param tracer        The HTrace context to use.  The span receiver may
+     *                          hold on to this pointer.
+     * @param conf          The HTrace configuration to use.  The span
+     *                          receiver must not hold on to this pointer.
+     *
+     * @return              The HTrace span receciver.
+     */
+    struct htrace_rcv *(*create)(struct htracer *tracer,
+                                 const struct htrace_conf *conf);
+
+    /**
+     * Callback to add a new span.
+     *
+     * @param rcv           The HTrace span receiver.
+     * @param span          The trace span to add.
+     */
+    void (*add_span)(struct htrace_rcv *rcv, struct htrace_span *span);
+
+    /**
+     * Flush all buffered spans to the backing store used by this receiver.
+     *
+     * @param rcv           The HTrace span receiver.
+     */
+    void (*flush)(struct htrace_rcv *rcv);
+
+    /**
+     * Frees this HTrace span receiver.
+     *
+     * @param rcv           The HTrace span receiver.
+     */
+    void (*free)(struct htrace_rcv *rcv);
+};
+
+/**
+ * Create an HTrace span receiver.
+ *
+ * @param tracer        The HTrace context to use.  The newly created
+ *                          span receiver may hold on to this pointer.
+ * @param conf          The HTrace configuration to use.  The newly
+ *                          created span receiver will not hold on to this
+ *                          pointer.
+ *
+ * @return              The HTrace span receciver.
+ */
+struct htrace_rcv *htrace_rcv_create(struct htracer *tracer,
+                                     const struct htrace_conf *conf);
+
+/*
+ * HTrace span receiver types.
+ */
+const struct htrace_rcv_ty g_noop_rcv_ty;
+const struct htrace_rcv_ty g_local_file_rcv_ty;
+const struct htrace_rcv_ty g_htraced_rcv_ty;
+
+#endif
+
+// vim: ts=4: sw=4: et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/sampler/always.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/sampler/always.c b/htrace-c/src/sampler/always.c
new file mode 100644
index 0000000..a570e79
--- /dev/null
+++ b/htrace-c/src/sampler/always.c
@@ -0,0 +1,71 @@
+/**
+ * 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.
+ */
+
+#include "sampler/sampler.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/**
+ * A sampler that always fires.
+ */
+struct always_sampler {
+    struct htrace_sampler base;
+};
+
+static struct htrace_sampler *always_sampler_create(struct htracer *tracer,
+                                            const struct htrace_conf *conf);
+static const char *always_sampler_to_str(struct htrace_sampler *sampler);
+static int always_sampler_next(struct htrace_sampler *sampler);
+static void always_sampler_free(struct htrace_sampler *sampler);
+
+const struct htrace_sampler_ty g_always_sampler_ty = {
+    "always",
+    always_sampler_create,
+    always_sampler_to_str,
+    always_sampler_next,
+    always_sampler_free,
+};
+
+const struct always_sampler g_always_sampler = {
+    { (struct htrace_sampler_ty*) &g_always_sampler_ty },
+};
+
+static struct htrace_sampler *always_sampler_create(struct htracer *tracer,
+                                            const struct htrace_conf *conf)
+{
+    return (struct htrace_sampler*)&g_always_sampler;
+}
+
+static const char *always_sampler_to_str(struct htrace_sampler *sampler)
+{
+    return "AlwaysSampler";
+}
+
+static int always_sampler_next(struct htrace_sampler *sampler)
+{
+    return 1;
+}
+
+static void always_sampler_free(struct htrace_sampler *sampler)
+{
+    // do nothing.
+}
+
+// vim: ts=4:sw=4:tw=79:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/sampler/never.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/sampler/never.c b/htrace-c/src/sampler/never.c
new file mode 100644
index 0000000..cf25bfa
--- /dev/null
+++ b/htrace-c/src/sampler/never.c
@@ -0,0 +1,71 @@
+/**
+ * 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.
+ */
+
+#include "sampler/sampler.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/**
+ * A sampler that never fires.
+ */
+struct never_sampler {
+    struct htrace_sampler base;
+};
+
+static struct htrace_sampler *never_sampler_create(struct htracer *tracer,
+                                            const struct htrace_conf *conf);
+static const char *never_sampler_to_str(struct htrace_sampler *sampler);
+static int never_sampler_next(struct htrace_sampler *sampler);
+static void never_sampler_free(struct htrace_sampler *sampler);
+
+const struct htrace_sampler_ty g_never_sampler_ty = {
+    "never",
+    never_sampler_create,
+    never_sampler_to_str,
+    never_sampler_next,
+    never_sampler_free,
+};
+
+struct never_sampler g_never_sampler = {
+    { (struct htrace_sampler_ty*) &g_never_sampler_ty },
+};
+
+static struct htrace_sampler *never_sampler_create(struct htracer *tracer,
+                                            const struct htrace_conf *conf)
+{
+    return (struct htrace_sampler*)&g_never_sampler;
+}
+
+static const char *never_sampler_to_str(struct htrace_sampler *sampler)
+{
+    return "NeverSampler";
+}
+
+static int never_sampler_next(struct htrace_sampler *sampler)
+{
+    return 0;
+}
+
+static void never_sampler_free(struct htrace_sampler *sampler)
+{
+    // do nothing.
+}
+
+// vim: ts=4:sw=4:tw=79:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/sampler/prob.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/sampler/prob.c b/htrace-c/src/sampler/prob.c
new file mode 100644
index 0000000..789a2eb
--- /dev/null
+++ b/htrace-c/src/sampler/prob.c
@@ -0,0 +1,137 @@
+/**
+ * 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.
+ */
+
+#include "core/conf.h"
+#include "core/htrace.h"
+#include "core/htracer.h"
+#include "sampler/sampler.h"
+#include "util/log.h"
+#include "util/rand.h"
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/**
+ * A sampler that fires with a certain chance.
+ */
+struct prob_sampler {
+    struct htrace_sampler base;
+
+    /**
+     * A random source.
+     */
+    struct random_src *rnd;
+
+    /**
+     * The name of this probability sampler.
+     */
+    char *name;
+
+    /**
+     * The threshold at which we should sample.
+     */
+    uint32_t threshold;
+};
+
+static double get_prob_sampler_threshold(struct htrace_log *lg,
+                                         const struct htrace_conf *conf);
+static struct htrace_sampler *prob_sampler_create(struct htracer *tracer,
+                                          const struct htrace_conf *conf);
+static const char *prob_sampler_to_str(struct htrace_sampler *s);
+static int prob_sampler_next(struct htrace_sampler *s);
+static void prob_sampler_free(struct htrace_sampler *s);
+
+const struct htrace_sampler_ty g_prob_sampler_ty = {
+    "prob",
+    prob_sampler_create,
+    prob_sampler_to_str,
+    prob_sampler_next,
+    prob_sampler_free,
+};
+
+static double get_prob_sampler_threshold(struct htrace_log *lg,
+                                       const struct htrace_conf *conf)
+{
+    double fraction =
+        htrace_conf_get_double(lg, conf, HTRACE_PROB_SAMPLER_FRACTION_KEY);
+    if (fraction < 0) {
+        htrace_log(lg, "sampler_create: can't have a sampling fraction "
+                   "less than 0.  Setting fraction to 0.\n");
+        fraction = 0.0;
+    } else if (fraction > 1.0) {
+        htrace_log(lg, "sampler_create: can't have a sampling fraction "
+                   "greater than 1.  Setting fraction to 1.\n");
+        fraction = 1.0;
+    }
+    return fraction;
+}
+
+static struct htrace_sampler *prob_sampler_create(struct htracer *tracer,
+                                          const struct htrace_conf *conf)
+{
+    struct prob_sampler *smp;
+    double fraction;
+
+    smp = calloc(1, sizeof(*smp));
+    if (!smp) {
+        htrace_log(tracer->lg, "prob_sampler_create: OOM\n");
+        return NULL;
+    }
+    smp->base.ty = &g_prob_sampler_ty;
+    smp->rnd = random_src_alloc(tracer->lg);
+    if (!smp->rnd) {
+        htrace_log(tracer->lg, "random_src_alloc failed.\n");
+        free(smp);
+        return NULL;
+    }
+    fraction = get_prob_sampler_threshold(tracer->lg, conf);
+    smp->threshold = 0xffffffffLU * fraction;
+    if (asprintf(&smp->name, "ProbabilitySampler(fraction=%.03g)",
+                 fraction) < 0) {
+        smp->name = NULL;
+        random_src_free(smp->rnd);
+        free(smp);
+    }
+    return (struct htrace_sampler *)smp;
+}
+
+static const char *prob_sampler_to_str(struct htrace_sampler *s)
+{
+    struct prob_sampler *smp = (struct prob_sampler *)s;
+    return smp->name;
+}
+
+static int prob_sampler_next(struct htrace_sampler *s)
+{
+    struct prob_sampler *smp = (struct prob_sampler *)s;
+    return random_u32(smp->rnd) < smp->threshold;
+}
+
+static void prob_sampler_free(struct htrace_sampler *s)
+{
+    struct prob_sampler *smp = (struct prob_sampler *)s;
+    random_src_free(smp->rnd);
+    free(smp->name);
+    free(smp);
+}
+
+// vim: ts=4:sw=4:tw=79:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/sampler/sampler.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/sampler/sampler.c b/htrace-c/src/sampler/sampler.c
new file mode 100644
index 0000000..81aef94
--- /dev/null
+++ b/htrace-c/src/sampler/sampler.c
@@ -0,0 +1,88 @@
+/**
+ * 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.
+ */
+
+#include "core/conf.h"
+#include "core/htrace.h"
+#include "core/htracer.h"
+#include "sampler/sampler.h"
+#include "util/log.h"
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+const struct htrace_sampler_ty * const g_sampler_tys[] = {
+    &g_never_sampler_ty,
+    &g_always_sampler_ty,
+    &g_prob_sampler_ty,
+    NULL,
+};
+
+static const struct htrace_sampler_ty *select_sampler_ty(
+                        struct htracer *tracer, const struct htrace_conf *cnf)
+{
+    const char *tstr;
+    const char *prefix = "";
+    size_t i;
+    char buf[256] = { 0 };
+
+    tstr = htrace_conf_get(cnf, HTRACE_SAMPLER_KEY);
+    if (!tstr) {
+        htrace_log(tracer->lg, "No %s configured.\n", HTRACE_SAMPLER_KEY);
+        return &g_never_sampler_ty;
+    }
+    for (i = 0; g_sampler_tys[i]; i++) {
+        if (strcmp(g_sampler_tys[i]->name, tstr) == 0) {
+            return g_sampler_tys[i];
+        }
+    }
+    for (i = 0; g_sampler_tys[i]; i++) {
+        if ((strlen(buf) + strlen(prefix) +
+                 strlen(g_sampler_tys[i]->name)) < sizeof(buf)) {
+            strcat(buf, prefix);
+            strcat(buf, g_sampler_tys[i]->name);
+            prefix = ", ";
+        }
+    }
+    htrace_log(tracer->lg, "Unknown sampler type '%s'.  Valid "
+               "sampler types are: %s\n", tstr, buf);
+    return &g_never_sampler_ty;
+}
+
+struct htrace_sampler *htrace_sampler_create(struct htracer *tracer,
+                                             struct htrace_conf *cnf)
+{
+    const struct htrace_sampler_ty *ty;
+
+    ty = select_sampler_ty(tracer, cnf);
+    return ty->create(tracer, cnf);
+}
+
+const char *htrace_sampler_to_str(struct htrace_sampler *smp)
+{
+    return smp->ty->to_str(smp);
+}
+
+void htrace_sampler_free(struct htrace_sampler *smp)
+{
+    return smp->ty->free(smp);
+}
+
+// vim: ts=4:sw=4:tw=79:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/sampler/sampler.h
----------------------------------------------------------------------
diff --git a/htrace-c/src/sampler/sampler.h b/htrace-c/src/sampler/sampler.h
new file mode 100644
index 0000000..1c423e6
--- /dev/null
+++ b/htrace-c/src/sampler/sampler.h
@@ -0,0 +1,121 @@
+/**
+ * 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.
+ */
+
+#ifndef APACHE_HTRACE_SAMPLER_SAMPLER_H
+#define APACHE_HTRACE_SAMPLER_SAMPLER_H
+
+/**
+ * @file sampler.h
+ *
+ * Functions related to HTrace samplers.
+ *
+ * This is an internal header, not intended for external use.
+ */
+
+#include <stdint.h>
+
+struct htrace_conf;
+struct htrace_log;
+struct htracer;
+
+/**
+ * Base class for an HTrace sampler.
+ *
+ * Implementations should begin with this class as the first member.
+ */
+struct htrace_sampler {
+    /**
+     * The type of the sampler.
+     */
+    const struct htrace_sampler_ty *ty;
+};
+
+/**
+ * A table of callbacks that implements an HTrace sampler.
+ */
+struct htrace_sampler_ty {
+    /**
+     * The name of this probability sampler type.
+     *
+     * This is used to select the sampler via the configuration.
+     */
+    const char *name;
+
+    /**
+     * Create an HTrace sampler of this type.
+     *
+     * @param tracer        The HTrace context to use.  The sampler may
+     *                          hold on to this pointer.
+     * @param conf          The HTrace configuration to use.  The sampler
+     *                          must not hold on to this pointer.
+     *
+     * @return              The HTrace span receciver.
+     */
+    struct htrace_sampler *(*create)(struct htracer *tracer,
+                                 const struct htrace_conf *conf);
+
+    /**
+     * Get the name of this HTrace sampler.
+     *
+     * @param smp           The sampler.
+     *
+     * @return              A description of this sampler object.  This string
+     *                          must remain valid at least until the sampler
+     *                          is freed.
+     */
+    const char*(*to_str)(struct htrace_sampler *smp);
+
+    /**
+     * Sampler callback.
+     *
+     * This callback must be able to be safely called by multiple threads
+     * simultaneously.
+     *
+     * @param smp           The HTrace sampler.
+     *
+     * @return              1 to begin a new span; 0 otherwise.
+     */
+    int (*next)(struct htrace_sampler *smp);
+
+    /**
+     * Frees this HTrace sampler.
+     *
+     * @param rcv           The HTrace sampler.
+     */
+    void (*free)(struct htrace_sampler *smp);
+};
+
+/**
+ * Get the configured fraction for the probability sampler.
+ *
+ * @param log           A log to send parse error messages to.
+ * @param conf          The configuration to use.
+ *
+ * @return              A double between 0.0 and 1.0, inclusive.
+ */
+double get_prob_sampler_fraction(struct htrace_log *lg,
+                                 struct htrace_conf *conf);
+
+extern const struct htrace_sampler_ty g_never_sampler_ty;
+extern const struct htrace_sampler_ty g_always_sampler_ty;
+extern const struct htrace_sampler_ty g_prob_sampler_ty;
+extern const struct always_sampler g_always_sampler;
+
+#endif
+
+// vim: ts=4:sw=4:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/conf-unit.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/test/conf-unit.c b/htrace-c/src/test/conf-unit.c
new file mode 100644
index 0000000..debc866
--- /dev/null
+++ b/htrace-c/src/test/conf-unit.c
@@ -0,0 +1,95 @@
+/**
+ * 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.
+ */
+
+#include "core/conf.h"
+#include "test/test.h"
+#include "util/log.h"
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static int test_simple_conf(void)
+{
+    struct htrace_conf *conf;
+    struct htrace_log *lg;
+    conf = htrace_conf_from_strs("foo=bar;foo2=baz;foo3=quux;foo5=123",
+                                 "foo3=default3;foo4=default4");
+    lg = htrace_log_alloc(conf);
+    EXPECT_NONNULL(conf);
+    EXPECT_STR_EQ("bar", htrace_conf_get(conf, "foo"));
+    EXPECT_STR_EQ("quux", htrace_conf_get(conf, "foo3"));
+    EXPECT_STR_EQ("default4", htrace_conf_get(conf, "foo4"));
+    EXPECT_UINT64_EQ((uint64_t)123, htrace_conf_get_u64(lg, conf, "foo5"));
+    EXPECT_UINT64_EQ((uint64_t)123, htrace_conf_get_u64(lg, conf, "foo5"));
+    EXPECT_NULL(htrace_conf_get(conf, "unknown"));
+
+    htrace_log_free(lg);
+    htrace_conf_free(conf);
+    return EXIT_SUCCESS;
+}
+
+static int test_double_conf(void)
+{
+    struct htrace_conf *conf;
+    struct htrace_log *lg;
+    double d;
+
+    conf = htrace_conf_from_strs("my.double=5.4;bozo=wakkawakkaa",
+                                 "my.double=1.1;bozo=2.0");
+    EXPECT_NONNULL(conf);
+    lg = htrace_log_alloc(conf);
+    d = htrace_conf_get_double(lg, conf, "my.double");
+    // Do a sloppy comparison to avoid thinking about IEEE float precision
+    // issues
+    if ((d > 5.401) || (d < 5.399)) {
+        htrace_log(lg, "failed to parse my.double... expected 5.4, "
+                   "got %g\n", d);
+        return EXIT_FAILURE;
+    }
+    // 'bozo' should fall back on the default, since the configured value
+    // cannot be parsed.
+    d = htrace_conf_get_double(lg, conf, "bozo");
+    if ((d > 2.001) || (d < 1.999)) {
+        htrace_log(lg, "failed to parse bozo... expected 2.0, "
+                   "got %g\n", d);
+        return EXIT_FAILURE;
+    }
+    // 'unknown' should get 0.0, since there is no value or default.
+    d = htrace_conf_get_double(lg, conf, "unknown");
+    if ((d > 0.001) || (d < -0.001)) {
+        htrace_log(lg, "failed to parse unknown... expected 0.0, "
+                   "got %g\n", d);
+        return EXIT_FAILURE;
+    }
+
+    htrace_log_free(lg);
+    htrace_conf_free(conf);
+    return EXIT_SUCCESS;
+}
+
+int main(void)
+{
+    test_simple_conf();
+    test_double_conf();
+
+    return EXIT_SUCCESS;
+}
+
+// vim: ts=4:sw=4:tw=79:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/htable-unit.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/test/htable-unit.c b/htrace-c/src/test/htable-unit.c
new file mode 100644
index 0000000..a656fcc
--- /dev/null
+++ b/htrace-c/src/test/htable-unit.c
@@ -0,0 +1,92 @@
+/**
+ * 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.
+ */
+
+#include "test/test.h"
+#include "util/htable.h"
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static uint32_t simple_hash(const void *key, uint32_t size)
+{
+    uintptr_t k = (uintptr_t)key;
+    return ((13 + k) * 6367) % size;
+}
+
+static int simple_compare(const void *a, const void *b)
+{
+    return a == b;
+}
+
+static void expect_102(void *f, void *k, void *v)
+{
+    int *found_102 = f;
+    uintptr_t key = (uintptr_t)k;
+    uintptr_t val = (uintptr_t)v;
+
+    if ((key == 2) && (val == 102)) {
+        *found_102 = 1;
+    } else {
+        abort();
+    }
+}
+
+static void *htable_pop_val(struct htable *ht, void *key)
+{
+    void *old_key, *old_val;
+
+    htable_pop(ht, key, &old_key, &old_val);
+    return old_val;
+}
+
+int main(void)
+{
+    struct htable *ht;
+    int found_102 = 0;
+
+    ht = htable_alloc(4, simple_hash, simple_compare);
+    EXPECT_INT_EQ(0, htable_used(ht));
+    EXPECT_INT_EQ(4, htable_capacity(ht));
+    EXPECT_NULL(htable_get(ht, (void*)123));
+    EXPECT_NULL(htable_pop_val(ht, (void*)123));
+    EXPECT_INT_ZERO(htable_put(ht, (void*)123, (void*)456));
+    EXPECT_UINTPTR_EQ(456L, (uintptr_t)htable_get(ht, (void*)123));
+    EXPECT_UINTPTR_EQ(456L, (uintptr_t)htable_pop_val(ht, (void*)123));
+    EXPECT_NULL(htable_pop_val(ht, (void*)123));
+
+    // Enlarge the hash table
+    EXPECT_INT_ZERO(htable_put(ht, (void*)1, (void*)101));
+    EXPECT_INT_ZERO(htable_put(ht, (void*)2, (void*)102));
+    EXPECT_INT_ZERO(htable_put(ht, (void*)3, (void*)103));
+    EXPECT_INT_EQ(3, htable_used(ht));
+    EXPECT_INT_EQ(8, htable_capacity(ht));
+    EXPECT_UINTPTR_EQ(102L, (uintptr_t)htable_get(ht, (void*)2));
+    EXPECT_UINTPTR_EQ(101L, (uintptr_t)htable_pop_val(ht, (void*)1));
+    EXPECT_UINTPTR_EQ(103L, (uintptr_t)htable_pop_val(ht, (void*)3));
+    EXPECT_INT_EQ(1, htable_used(ht));
+    htable_visit(ht, expect_102, &found_102);
+    EXPECT_INT_EQ(1, found_102);
+    htable_free(ht);
+
+    return EXIT_SUCCESS;
+}
+
+// vim: ts=4:sw=4:tw=79:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/htraced_rcv-unit.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/test/htraced_rcv-unit.c b/htrace-c/src/test/htraced_rcv-unit.c
new file mode 100644
index 0000000..29f62d1
--- /dev/null
+++ b/htrace-c/src/test/htraced_rcv-unit.c
@@ -0,0 +1,110 @@
+/**
+ * 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.
+ */
+
+#include "core/conf.h"
+#include "core/htrace.h"
+#include "test/mini_htraced.h"
+#include "test/rtest.h"
+#include "test/span_table.h"
+#include "test/span_util.h"
+#include "test/temp_dir.h"
+#include "test/test.h"
+#include "util/log.h"
+#include "util/time.h"
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static int htraced_rcv_test(struct rtest *rt)
+{
+    char err[512], *conf_str, *json_path;
+    size_t err_len = sizeof(err);
+    struct mini_htraced_params params;
+    struct mini_htraced *ht = NULL;
+    struct span_table *st;
+    uint64_t start_ms;
+
+    params.name = rt->name;
+    params.confstr = "";
+    mini_htraced_build(&params, &ht, err, err_len);
+    EXPECT_STR_EQ("", err);
+
+    EXPECT_INT_GE(0, asprintf(&json_path, "%s/%s",
+                ht->root_dir, "spans.json"));
+    EXPECT_INT_GE(0, asprintf(&conf_str, "%s=%s;%s=%s",
+                HTRACE_SPAN_RECEIVER_KEY, "htraced",
+                HTRACED_ADDRESS_KEY, ht->htraced_http_addr));
+    EXPECT_INT_ZERO(rt->run(rt, conf_str));
+    start_ms = monotonic_now_ms(NULL);
+    //
+    // It may take a little while for htraced to commit the incoming spans sent
+    // via RPC to its data store.  htraced does not have read-after-write
+    // consistency, in other words.  This isn't normally an issue since trace
+    // collection is done in the background.
+    //
+    // For this unit test, it means that we want to retry if we find too few
+    // spans the first time we dump the htraced data store contents.
+    //
+    while (1) {
+        int nspans;
+
+        // This uses the bin/htrace program to dump the spans to a json file.
+        mini_htraced_dump_spans(ht, err, err_len, json_path);
+        EXPECT_STR_EQ("", err);
+        st = span_table_alloc();
+        EXPECT_NONNULL(st);
+        nspans = load_trace_span_file(json_path, st);
+        EXPECT_INT_GE(0, nspans);
+        if (nspans >= rt->spans_created) {
+            break;
+        }
+        span_table_free(st);
+        st = NULL;
+        EXPECT_UINT64_GE(start_ms, monotonic_now_ms(NULL) + 30000);
+        sleep_ms(100);
+        fprintf(stderr, "htraced_test_app1: retrying htrace dumpAll...\n");
+    }
+    EXPECT_INT_ZERO(rt->verify(rt, st));
+    free(conf_str);
+    free(json_path);
+    span_table_free(st);
+    mini_htraced_stop(ht);
+    mini_htraced_free(ht);
+
+    return EXIT_SUCCESS;
+}
+
+int main(void)
+{
+    int i;
+
+    for (i = 0; g_rtests[i]; i++) {
+        struct rtest *rtest = g_rtests[i];
+        if (htraced_rcv_test(rtest) != EXIT_SUCCESS) {
+            fprintf(stderr, "rtest %s failed\n", rtest->name);
+            return EXIT_FAILURE;
+        }
+    }
+
+    return EXIT_SUCCESS;
+}
+
+// vim: ts=4:sw=4:tw=79:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/linkage-unit.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/test/linkage-unit.c b/htrace-c/src/test/linkage-unit.c
new file mode 100644
index 0000000..f3c5eba
--- /dev/null
+++ b/htrace-c/src/test/linkage-unit.c
@@ -0,0 +1,105 @@
+/**
+ * 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.
+ */
+
+#include "core/htrace.h"
+#include "test/test_config.h"
+
+#include <dlfcn.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/**
+ * @file linkage-unit.c
+ *
+ * Tests the linkage of libhtrace.so.
+ * Verifies that the functions in htrace.h are publicly visible, and that
+ * functions not included in that header are not.
+ *
+ * This unit test links against the production libhtrace.so, not the test
+ * library.  This also verifies that we can run a program compiled against the
+ * production library without encountering unresolved symbols or similar.
+ */
+
+static const char * const PUBLIC_SYMS[] = {
+    "htrace_conf_free",
+    "htrace_conf_from_str",
+    "htrace_restart_span",
+    "htrace_sampler_create",
+    "htrace_sampler_free",
+    "htrace_sampler_to_str",
+    "htrace_scope_close",
+    "htrace_scope_detach",
+    "htrace_scope_get_span_id",
+    "htrace_start_span",
+    "htracer_create",
+    "htracer_free",
+    "htracer_tname"
+};
+
+#define PUBLIC_SYMS_SIZE (sizeof(PUBLIC_SYMS) / sizeof(PUBLIC_SYMS[0]))
+
+/**
+ * Test that we can call the htrace_conf_from_strs function without aborting at
+ * runtime.  This may seem like a trivial test, but keep in mind this is the
+ * only unit test that uses the real libhtrace.so, not the testing library.
+ */
+static int test_call_htrace_conf_from_strs(void)
+{
+    struct htrace_conf *cnf;
+
+    cnf = htrace_conf_from_str("foo=bar");
+    htrace_conf_free(cnf);
+    return EXIT_SUCCESS;
+}
+
+/**
+ * Test that we can find all the public symbols in the library.
+ */
+static int find_public_symbols(void)
+{
+    int i;
+    void *sym;
+
+    for (i = 0; i < PUBLIC_SYMS_SIZE; i++) {
+        sym = dlsym(RTLD_DEFAULT, PUBLIC_SYMS[i]);
+        if (!sym) {
+            fprintf(stderr, "Failed to find %s, or its value was NULL.\n",
+                    PUBLIC_SYMS[i]);
+            return EXIT_FAILURE;
+        }
+    }
+    if (dlsym(RTLD_DEFAULT, "htrace_span_alloc")) {
+        fprintf(stderr, "Found non-public symbol htrace_span_alloc.\n");
+        return EXIT_FAILURE;
+    }
+    return EXIT_SUCCESS;
+}
+
+int main(void)
+{
+    if (test_call_htrace_conf_from_strs()) {
+        abort();
+    }
+    if (find_public_symbols()) {
+        abort();
+    }
+    return EXIT_SUCCESS;
+}
+
+// vim: ts=4:sw=4:tw=79:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/local_file_rcv-unit.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/test/local_file_rcv-unit.c b/htrace-c/src/test/local_file_rcv-unit.c
new file mode 100644
index 0000000..8d518a9
--- /dev/null
+++ b/htrace-c/src/test/local_file_rcv-unit.c
@@ -0,0 +1,74 @@
+/**
+ * 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.
+ */
+
+#include "core/conf.h"
+#include "core/htrace.h"
+#include "test/rtest.h"
+#include "test/span_table.h"
+#include "test/span_util.h"
+#include "test/temp_dir.h"
+#include "test/test.h"
+#include "util/log.h"
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static int local_file_rcv_test(struct rtest *rt)
+{
+    char err[512];
+    size_t err_len = sizeof(err);
+    char *local_path, *tdir, *conf_str = NULL;
+    struct span_table *st;
+
+    st = span_table_alloc();
+    tdir = create_tempdir("local_file_rcv-unit", 0777, err, err_len);
+    EXPECT_STR_EQ("", err);
+    register_tempdir_for_cleanup(tdir);
+    EXPECT_INT_GE(0, asprintf(&local_path, "%s/%s", tdir, "spans.json"));
+    EXPECT_INT_GE(0, asprintf(&conf_str, "%s=%s;%s=%s",
+                HTRACE_SPAN_RECEIVER_KEY, "local.file",
+                HTRACE_LOCAL_FILE_RCV_PATH_KEY, local_path));
+    EXPECT_INT_ZERO(rt->run(rt, conf_str));
+    EXPECT_INT_GE(0, load_trace_span_file(local_path, st));
+    EXPECT_INT_ZERO(rt->verify(rt, st));
+    free(conf_str);
+    free(local_path);
+    free(tdir);
+    span_table_free(st);
+
+    return EXIT_SUCCESS;
+}
+
+int main(void)
+{
+    int i;
+
+    for (i = 0; g_rtests[i]; i++) {
+        struct rtest *rtest = g_rtests[i];
+        if (local_file_rcv_test(rtest) != EXIT_SUCCESS) {
+            fprintf(stderr, "rtest %s failed\n", rtest->name);
+            return EXIT_FAILURE;
+        }
+    }
+
+    return EXIT_SUCCESS;
+}
+
+// vim: ts=4:sw=4:tw=79:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/log-unit.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/test/log-unit.c b/htrace-c/src/test/log-unit.c
new file mode 100644
index 0000000..af7de48
--- /dev/null
+++ b/htrace-c/src/test/log-unit.c
@@ -0,0 +1,97 @@
+/**
+ * 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.
+ */
+
+#include "core/conf.h"
+#include "test/temp_dir.h"
+#include "test/test.h"
+#include "util/log.h"
+
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static int verify_log_file(const char *path)
+{
+    FILE *fp;
+    char contents[4096];
+    size_t res;
+    const char * const expected_contents =
+        "foo 2, bar, and baz.\nquux as well.\n";
+
+    fp = fopen(path, "r");
+    if (!fp) {
+        int e = errno;
+        fprintf(stderr, "failed to open %s: error %d (%s)\n",
+                path, e, terror(e));
+        return EXIT_FAILURE;
+    }
+    memset(contents, 0, sizeof(contents));
+    res = fread(contents, 1, sizeof(contents), fp);
+    if (res < strlen(expected_contents)) {
+        int e = errno;
+        if (feof(fp)) {
+            fprintf(stderr, "fread(%s): unexpected eof.\n", path);
+            return EXIT_FAILURE;
+        }
+        fprintf(stderr, "fread(%s): error %d (%s)\n",
+                path, e, terror(e));
+        return EXIT_FAILURE;
+    }
+    fclose(fp);
+    EXPECT_STR_EQ(expected_contents, contents);
+
+    return EXIT_SUCCESS;
+}
+
+static int verify_log_to_file(void)
+{
+    struct htrace_conf *conf;
+    struct htrace_log *lg;
+    char *tdir, log_path[PATH_MAX], conf_str[PATH_MAX];
+    char err[128];
+    size_t err_len = sizeof(err);
+
+    tdir = create_tempdir("verify_log_to_file", 0775, err, err_len);
+    EXPECT_NONNULL(tdir);
+    EXPECT_INT_ZERO(register_tempdir_for_cleanup(tdir));
+    snprintf(log_path, sizeof(log_path), "%s/log.txt", tdir);
+    snprintf(conf_str, sizeof(conf_str), "log.path=%s", log_path);
+    conf = htrace_conf_from_strs(conf_str, "");
+    EXPECT_NONNULL(conf);
+    lg = htrace_log_alloc(conf);
+    EXPECT_NONNULL(lg);
+    htrace_log(lg, "foo %d, bar, and baz.\n", 2);
+    htrace_log(lg, "quux as well.\n");
+    htrace_log_free(lg);
+    EXPECT_INT_ZERO(verify_log_file(log_path));
+    htrace_conf_free(conf);
+    free(tdir);
+
+    return EXIT_SUCCESS;
+}
+
+int main(void)
+{
+    EXPECT_INT_ZERO(verify_log_to_file());
+
+    return EXIT_SUCCESS;
+}
+
+// vim: ts=4:sw=4:tw=79:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/mini_htraced-unit.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/test/mini_htraced-unit.c b/htrace-c/src/test/mini_htraced-unit.c
new file mode 100644
index 0000000..bfcd2ef
--- /dev/null
+++ b/htrace-c/src/test/mini_htraced-unit.c
@@ -0,0 +1,48 @@
+/**
+ * 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.
+ */
+
+#include "test/mini_htraced.h"
+#include "test/test.h"
+
+#include <stdlib.h>
+
+static int test_mini_htraced_start_stop(void)
+{
+    char err[128];
+    size_t err_len = sizeof(err);
+    struct mini_htraced_params params;
+    struct mini_htraced *ht = NULL;
+
+    err[0] = '\0';
+    params.name = "test_mini_htraced_start_stop";
+    params.confstr = "";
+    mini_htraced_build(&params, &ht, err, err_len);
+    EXPECT_STR_EQ("", err);
+    mini_htraced_stop(ht);
+    mini_htraced_free(ht);
+
+    return 0;
+}
+
+int main(void)
+{
+    EXPECT_INT_ZERO(test_mini_htraced_start_stop());
+    return EXIT_SUCCESS;
+}
+
+// vim: ts=4:sw=4:tw=79:et


[2/4] incubator-htrace git commit: HTRACE-106: htrace: add C / C++ native client (cmccabe)

Posted by cm...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/mini_htraced.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/test/mini_htraced.c b/htrace-c/src/test/mini_htraced.c
new file mode 100644
index 0000000..31b5484
--- /dev/null
+++ b/htrace-c/src/test/mini_htraced.c
@@ -0,0 +1,599 @@
+/**
+ * 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.
+ */
+
+#include "core/conf.h"
+#include "core/htrace.h"
+#include "test/mini_htraced.h"
+#include "test/temp_dir.h"
+#include "test/test_config.h"
+#include "test/test.h"
+#include "util/log.h"
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <json/json_object.h>
+#include <json/json_tokener.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/prctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+/**
+ * The maximum size of the notification data sent from the htraced daemon on
+ * startup.
+ */
+#define MAX_NDATA 65536
+
+/**
+ * The separator to use in between paths.  TODO: portability
+ */
+#define PATH_LIST_SEP ':'
+
+/**
+ * The maximum number of arguments when launching an external process.
+ */
+#define MAX_LAUNCH_ARGS 32
+
+/**
+ * Retry an operation that may get EINTR.
+ * The operation must return a non-negative value on success.
+ */
+#define RETRY_ON_EINTR(ret, expr) do { \
+    ret = expr; \
+    if (ret >= 0) \
+        break; \
+} while (errno == EINTR);
+
+#define MINI_HTRACED_LAUNCH_REDIRECT_FDS 0x1
+
+static void mini_htraced_open_snsock(struct mini_htraced *ht, char *err,
+                                     size_t err_len);
+
+static void mini_htraced_write_conf_file(struct mini_htraced *ht,
+                                  char *err, size_t err_len);
+
+static int mini_htraced_write_conf_key(FILE *fp, const char *key,
+                                       const char *fmt, ...)
+    __attribute__((format(printf, 3, 4)));
+
+static void mini_htraced_launch_daemon(struct mini_htraced *ht,
+                                char *err, size_t err_len);
+
+/**
+ * Launch an external process.
+ *
+ * @param ht                The mini htraced object.
+ * @param path              The binary to launch
+ * @param err               (out param) the error, or empty string on success.
+ * @param err_len           The length of the error buffer.
+ * @param flags             The flags.
+ *                              MINI_HTRACED_LAUNCH_REDIRECT_FDS: redirect
+ *                                  stderr, stdout, stdin to null.
+ *                                  finished successfully.
+ * @param ...               Additional arguments to pass to the process.
+ *                              NULL_terminated.  The first argument will
+ *                              always be the path to the binary.
+ *
+ * @return                  The new process ID, on success.  -1 on failure.
+ *                              The error string will always be set on failure.
+ */
+pid_t mini_htraced_launch(const struct mini_htraced *ht, const char *path,
+                         char *err, size_t err_len, int flags, ...)
+    __attribute__((sentinel));
+
+
+static void mini_htraced_read_startup_notification(struct mini_htraced *ht,
+                                       char *err, size_t err_len);
+
+static void parse_startup_notification(struct mini_htraced *ht,
+                                       char *ndata, size_t ndata_len,
+                                       char *err, size_t err_len);
+
+void mini_htraced_build(const struct mini_htraced_params *params,
+                        struct mini_htraced **hret,
+                        char *err, size_t err_len)
+{
+    struct mini_htraced *ht = NULL;
+    int i, ret;
+
+    err[0] = '\0';
+    ht = calloc(1, sizeof(*ht));
+    if (!ht) {
+        snprintf(err, err_len, "out of memory allocating mini_htraced object");
+        goto done;
+    }
+    ht->snsock = -1;
+    ht->root_dir = create_tempdir(params->name, 0777, err, err_len);
+    if (err[0]) {
+        goto done;
+    }
+    ret = register_tempdir_for_cleanup(ht->root_dir);
+    if (ret) {
+        snprintf(err, err_len, "register_tempdir_for_cleanup(%s) "
+                 "failed: %s", ht->root_dir, terror(ret));
+        goto done;
+    }
+    for (i = 0; i < NUM_DATA_DIRS; i++) {
+        if (asprintf(ht->data_dir + i, "%s/dir%d", ht->root_dir, i) < 0) {
+            ht->data_dir[i] = NULL;
+            snprintf(err, err_len, "failed to create path to data dir %d", i);
+            goto done;
+        }
+    }
+    if (asprintf(&ht->htraced_log_path, "%s/htraced.log", ht->root_dir) < 0) {
+        ht->htraced_log_path = NULL;
+        snprintf(err, err_len, "failed to create path to htraced.log");
+        goto done;
+    }
+    if (asprintf(&ht->htraced_conf_path, "%s/htraced-conf.xml",
+                 ht->root_dir) < 0) {
+        ht->htraced_conf_path = NULL;
+        snprintf(err, err_len, "failed to create path to htraced-conf.xml");
+        goto done;
+    }
+    mini_htraced_open_snsock(ht, err, err_len);
+    if (err[0]) {
+        goto done;
+    }
+    mini_htraced_write_conf_file(ht, err, err_len);
+    if (err[0]) {
+        goto done;
+    }
+    mini_htraced_launch_daemon(ht, err, err_len);
+    if (err[0]) {
+        goto done;
+    }
+    mini_htraced_read_startup_notification(ht, err, err_len);
+    if (err[0]) {
+        goto done;
+    }
+    if (asprintf(&ht->client_conf_defaults, "%s=%s",
+             HTRACED_ADDRESS_KEY, ht->htraced_http_addr) < 0) {
+        ht->client_conf_defaults = NULL;
+        snprintf(err, err_len, "failed to allocate client conf defaults.");
+        goto done;
+    }
+    *hret = ht;
+    err[0] = '\0';
+
+done:
+    if (err[0]) {
+        mini_htraced_free(ht);
+    }
+}
+
+static int do_waitpid(pid_t pid, char *err, size_t err_len)
+{
+    err[0] = '\0';
+    while (1) {
+        int status, res = waitpid(pid, &status, 0);
+        if (res < 0) {
+            if (errno == EINTR) {
+                continue;
+            }
+            snprintf(err, err_len, "waitpid(%lld) error: %s",
+                    (long long)pid, terror(res));
+            return -1;
+        }
+        if (WIFEXITED(status)) {
+            return WEXITSTATUS(status);
+        }
+        return -1; // signal or other exit
+    }
+}
+
+void mini_htraced_stop(struct mini_htraced *ht)
+{
+    char err[512];
+    size_t err_len = sizeof(err);
+
+    if (!ht->htraced_pid_valid) {
+        return;
+    }
+    kill(ht->htraced_pid, SIGTERM);
+    ht->htraced_pid_valid = 0;
+    do_waitpid(ht->htraced_pid, err, err_len);
+    if (err[0]) {
+        fprintf(stderr, "%s\n", err);
+    }
+}
+
+void mini_htraced_free(struct mini_htraced *ht)
+{
+    int i;
+
+    if (!ht) {
+        return;
+    }
+    mini_htraced_stop(ht);
+    if (ht->root_dir) {
+        unregister_tempdir_for_cleanup(ht->root_dir);
+        if (!getenv("SKIP_CLEANUP")) {
+            recursive_unlink(ht->root_dir);
+        }
+    }
+    free(ht->root_dir);
+    for (i = 0; i < NUM_DATA_DIRS; i++) {
+        free(ht->data_dir[i]);
+    }
+    free(ht->htraced_log_path);
+    free(ht->htraced_conf_path);
+    free(ht->client_conf_defaults);
+    if (ht->snsock >= 0) {
+        close(ht->snsock);
+        ht->snsock = -1;
+    }
+    free(ht->htraced_http_addr);
+    free(ht);
+}
+
+static void mini_htraced_open_snsock(struct mini_htraced *ht, char *err,
+                                     size_t err_len)
+{
+    struct sockaddr_in snaddr;
+    socklen_t len = sizeof(snaddr);
+    struct timeval tv;
+
+    err[0] = '\0';
+    memset(&snaddr, 0, sizeof(snaddr));
+    snaddr.sin_family = AF_INET;
+    snaddr.sin_addr.s_addr = htonl(INADDR_ANY);
+    ht->snsock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
+    if (ht->snsock < 0) {
+        int res = errno;
+        snprintf(err, err_len, "Failed to create new socket: %s\n",
+                 terror(res));
+        return;
+    }
+    if (bind(ht->snsock, (struct sockaddr *) &snaddr, sizeof(snaddr)) < 0) {
+        int res = errno;
+        snprintf(err, err_len, "bind failed: %s\n", terror(res));
+        return;
+    }
+    if (getsockname(ht->snsock, (struct sockaddr *)&snaddr, &len) < 0) {
+        int res = errno;
+        snprintf(err, err_len, "getsockname failed: %s\n", terror(res));
+        return;
+    }
+    ht->snport = ntohs(snaddr.sin_port);
+    if (listen(ht->snsock, 32) < 0) {
+        int res = errno;
+        snprintf(err, err_len, "listen failed: %s\n", terror(res));
+        return;
+    }
+    // On Linux, at least, this makes accept() time out after 30 seconds.  I'm
+    // too lazy to use select() here.
+    tv.tv_sec = 30;
+    tv.tv_usec = 0;
+    setsockopt(ht->snsock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
+}
+
+static void mini_htraced_write_conf_file(struct mini_htraced *ht,
+                                  char *err, size_t err_len)
+{
+    FILE *fp;
+    int res;
+
+    err[0] = '\0';
+    fp = fopen(ht->htraced_conf_path, "w");
+    if (!fp) {
+        res = errno;
+        snprintf(err, err_len, "fopen(%s) failed: %s",
+                 ht->htraced_conf_path, terror(res));
+        goto error;
+    }
+    if (fprintf(fp, "\
+<?xml version=\"1.0\"?>\n\
+<?xml-stylesheet type=\"text/xsl\" href=\"configuration.xsl\"?>\n\
+<configuration>\n") < 0) {
+        goto ioerror;
+    }
+    if (mini_htraced_write_conf_key(fp, "log.path", "%s",
+                                    ht->htraced_log_path)) {
+        goto ioerror;
+    }
+    if (mini_htraced_write_conf_key(fp, "web.address", "127.0.0.1:0")) {
+        goto ioerror;
+    }
+    if (mini_htraced_write_conf_key(fp, "data.store.directories",
+            "%s%c%s", ht->data_dir[0], PATH_LIST_SEP, ht->data_dir[1])) {
+        goto ioerror;
+    }
+    if (mini_htraced_write_conf_key(fp, "startup.notification.address",
+            "localhost:%d", ht->snport)) {
+        goto ioerror;
+    }
+    if (mini_htraced_write_conf_key(fp, "log.level", "%s", "TRACE")) {
+        goto ioerror;
+    }
+    if (fprintf(fp, "</configuration>\n") < 0) {
+        goto ioerror;
+    }
+    res = fclose(fp);
+    if (res) {
+        snprintf(err, err_len, "fclose(%s) failed: %s",
+                 ht->htraced_conf_path, terror(res));
+    }
+    return;
+
+ioerror:
+    snprintf(err, err_len, "fprintf(%s) error",
+             ht->htraced_conf_path);
+error:
+    if (fp) {
+        fclose(fp);
+    }
+}
+
+static int mini_htraced_write_conf_key(FILE *fp, const char *key,
+                                       const char *fmt, ...)
+{
+    va_list ap;
+
+    if (fprintf(fp, "  <property>\n    <name>%s</name>\n    <value>",
+                key) < 0) {
+        return 1;
+    }
+    va_start(ap, fmt);
+    if (vfprintf(fp, fmt, ap) < 0) {
+        va_end(ap);
+        return 1;
+    }
+    va_end(ap);
+    if (fprintf(fp, "</value>\n  </property>\n") < 0) {
+        return 1;
+    }
+    return 0;
+}
+
+pid_t mini_htraced_launch(const struct mini_htraced *ht, const char *path,
+                         char *err, size_t err_len, int flags, ...)
+{
+    pid_t pid;
+    int res, num_args = 0;
+    va_list ap;
+    const char *args[MAX_LAUNCH_ARGS + 1];
+    const char * const env[] = {
+        "HTRACED_CONF_DIR=.", "HTRACED_WEB_DIR=", NULL
+    };
+
+    err[0] = '\0';
+    if (access(path, X_OK) < 0) {
+        snprintf(err, err_len, "The %s binary is not accessible and "
+                 "executable.", path);
+        return -1;
+    }
+    va_start(ap, flags);
+    args[num_args++] = path;
+    while (1) {
+        const char *arg = va_arg(ap, char*);
+        if (!arg) {
+            break;
+        }
+        if (num_args >= MAX_LAUNCH_ARGS) {
+            va_end(ap);
+            snprintf(err, err_len, "Too many arguments to launch!  The "
+                 "maximum number of arguments is %d.\n", MAX_LAUNCH_ARGS);
+            return -1;
+        }
+        args[num_args++] = arg;
+    }
+    va_end(ap);
+    args[num_args++] = NULL;
+
+    pid = fork();
+    if (pid == -1) {
+        // Fork failed.
+        res = errno;
+        snprintf(err, err_len, "fork() failed for %s: %s\n",
+                 path, terror(res));
+        return -1;
+    } else if (pid == 0) {
+        // Child process.
+        // We don't want to delete the temporary directory when this child
+        // process exists.  The parent process is responsible for that, if it
+        // is to be done at all.
+        unregister_tempdir_for_cleanup(ht->root_dir);
+
+        // Make things nicer by exiting when the parent process exits.
+        prctl(PR_SET_PDEATHSIG, SIGHUP);
+
+        if (flags & MINI_HTRACED_LAUNCH_REDIRECT_FDS) {
+            int null_fd;
+            RETRY_ON_EINTR(null_fd, open("/dev/null", O_WRONLY));
+            if (null_fd < 0) {
+                _exit(127);
+            }
+            RETRY_ON_EINTR(res, dup2(null_fd, STDOUT_FILENO));
+            if (res < 0) {
+                _exit(127);
+            }
+            RETRY_ON_EINTR(res, dup2(null_fd, STDERR_FILENO));
+            if (res < 0) {
+                _exit(127);
+            }
+        }
+        if (chdir(ht->root_dir) < 0) {
+            _exit(127);
+        }
+        execve(path, (char *const*)args, (char * const*)env);
+        _exit(127);
+    }
+    // Parent process.
+    return pid;
+}
+
+static void mini_htraced_launch_daemon(struct mini_htraced *ht,
+                                char *err, size_t err_len)
+{
+    int flags = 0;
+    pid_t pid;
+
+    if (!getenv("SKIP_CLEANUP")) {
+        flags |= MINI_HTRACED_LAUNCH_REDIRECT_FDS;
+    }
+    pid = mini_htraced_launch(ht, HTRACED_ABSPATH, err, err_len, flags, NULL);
+    if (err[0]) {
+        return;
+    }
+    ht->htraced_pid_valid = 1;
+    ht->htraced_pid = pid;
+}
+
+void mini_htraced_dump_spans(struct mini_htraced *ht,
+                             char *err, size_t err_len,
+                             const char *path)
+{
+    pid_t pid;
+    int ret;
+    char *addr = NULL;
+
+    err[0] = '\0';
+    if (asprintf(&addr, "--addr=%s", ht->htraced_http_addr) < 0) {
+        addr = NULL;
+        snprintf(err, err_len, "OOM while allocating the addr string");
+        return;
+    }
+    pid = mini_htraced_launch(ht, HTRACE_ABSPATH, err, err_len, 0,
+                addr, "dumpAll", path, NULL);
+    free(addr);
+    if (err[0]) {
+        return;
+    }
+    ret = do_waitpid(pid, err, err_len);
+    if (err[0]) {
+        return;
+    }
+    if (ret != EXIT_SUCCESS) {
+        snprintf(err, err_len, "%s returned non-zero exit status %d\n",
+                 HTRACE_ABSPATH, ret);
+        return;
+    }
+}
+
+static void mini_htraced_read_startup_notification(struct mini_htraced *ht,
+                                       char *err, size_t err_len)
+{
+    char *ndata = NULL;
+    int res, sock = -1;
+    size_t ndata_len = 0;
+
+    err[0] = '\0';
+    ndata = malloc(MAX_NDATA);
+    if (!ndata) {
+        snprintf(err, err_len, "failed to allocate %d byte buffer for "
+                 "notification data.", MAX_NDATA);
+        goto done;
+    }
+    RETRY_ON_EINTR(sock, accept(ht->snsock, NULL, NULL));
+    if (sock < 0) {
+        int e = errno;
+        snprintf(err, err_len, "accept failed: %s", terror(e));
+        goto done;
+    }
+    while (ndata_len < MAX_NDATA) {
+        res = recv(sock, ndata + ndata_len, MAX_NDATA - ndata_len, 0);
+        if (res == 0) {
+            break;
+        }
+        if (res < 0) {
+            int e = errno;
+            if (e == EINTR) {
+                continue;
+            }
+            snprintf(err, err_len, "recv error: %s", terror(e));
+            goto done;
+        }
+        ndata_len += res;
+    }
+    parse_startup_notification(ht, ndata, ndata_len, err, err_len);
+    if (err[0]) {
+        goto done;
+    }
+
+done:
+    if (sock >= 0) {
+        close(sock);
+    }
+    free(ndata);
+}
+
+static void parse_startup_notification(struct mini_htraced *ht,
+                                       char *ndata, size_t ndata_len,
+                                       char *err, size_t err_len)
+{
+    struct json_tokener *tok = NULL;
+    struct json_object *root = NULL, *http_addr, *process_id;
+    int32_t pid;
+
+    err[0] = '\0';
+    tok = json_tokener_new();
+    if (!tok) {
+        snprintf(err, err_len, "json_tokener_new failed.");
+        goto done;
+    }
+    root = json_tokener_parse_ex(tok, ndata, ndata_len);
+    if (!root) {
+        enum json_tokener_error jerr = json_tokener_get_error(tok);
+        snprintf(err, err_len, "Failed to parse startup notification: %s.",
+                 json_tokener_error_desc(jerr));
+        goto done;
+    }
+    // Find the http address, in the form of hostname:port, which the htraced
+    // is listening on.
+    if (!json_object_object_get_ex(root, "HttpAddr", &http_addr)) {
+        snprintf(err, err_len, "Failed to find HttpAddr in the startup "
+                 "notification.");
+        goto done;
+    }
+    ht->htraced_http_addr = strdup(json_object_get_string(http_addr));
+    if (!ht->htraced_http_addr) {
+        snprintf(err, err_len, "OOM");
+        goto done;
+    }
+    // Check that the process ID from the startup notification matches the
+    // process ID from the fork.
+    if (!json_object_object_get_ex(root, "ProcessId", &process_id)) {
+        snprintf(err, err_len, "Failed to find ProcessId in the startup "
+                 "notification.");
+        goto done;
+    }
+    pid = json_object_get_int(process_id);
+    if (pid != ht->htraced_pid) {
+        snprintf(err, err_len, "Startup notification pid was %lld, but the "
+                 "htraced process id was %lld.",
+                 (long long)pid, (long long)ht->htraced_pid);
+        goto done;
+    }
+
+done:
+    json_tokener_free(tok);
+    if (root) {
+        json_object_put(root);
+    }
+}
+
+// vim: ts=4:sw=4:tw=79:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/mini_htraced.h
----------------------------------------------------------------------
diff --git a/htrace-c/src/test/mini_htraced.h b/htrace-c/src/test/mini_htraced.h
new file mode 100644
index 0000000..a803f55
--- /dev/null
+++ b/htrace-c/src/test/mini_htraced.h
@@ -0,0 +1,154 @@
+/**
+ * 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.
+ */
+
+#ifndef APACHE_HTRACE_TEST_MINI_HTRACED_H
+#define APACHE_HTRACE_TEST_MINI_HTRACED_H
+
+/**
+ * @file mini_htraced.h
+ *
+ * Implements a mini htraced cluster which can be used in unit tests.
+ *
+ * This is useful for testing the htraced trace sink of the native client.
+ *
+ * This is an internal header, not intended for external use.
+ */
+
+#include <unistd.h> /* for pid_t and size_t */
+
+struct htrace_conf;
+
+#define NUM_DATA_DIRS 2
+
+struct mini_htraced_params {
+    /**
+     * The name of the mini htraced process to start.
+     * This shows up in the test directory name and some other places.
+     * The memory should be managed by the caller.
+     */
+    const char *name;
+
+    /**
+     * The configuration to use, in string form.
+     * The memory should be managed by the caller.
+     */
+    const char *confstr;
+};
+
+struct mini_htraced {
+    /**
+     * The process ID of the mini htraced.
+     */
+    int pid;
+
+    /**
+     * The path to the root directory that all the state associated with this
+     * mini_htraced will be placed under.  Malloced.
+     */
+    char *root_dir;
+
+    /**
+     * Paths to the data directories to use for htraced.  Malloced.
+     */
+    char *data_dir[NUM_DATA_DIRS];
+
+    /**
+     * Path to the server's log file.  Malloced.
+     */
+    char *htraced_log_path;
+
+    /**
+     * Path to the server's conf file.  Malloced.
+     */
+    char *htraced_conf_path;
+
+    /**
+     * The client configuration defaults.  Malloced.
+     */
+    char *client_conf_defaults;
+
+    /**
+     * The startup notification port.
+     */
+    int snport;
+
+    /**
+     * The startup notification socket, or -1 if the socket has been closed.
+     */
+    int snsock;
+
+    /**
+     * Nonzero if the htraced pid is valid.
+     */
+    int htraced_pid_valid;
+
+    /**
+     * The process ID of the htraced pid.
+     */
+    pid_t htraced_pid;
+
+    /**
+     * The HTTP address of the htraced, in hostname:port format.
+     */
+    char *htraced_http_addr;
+};
+
+/**
+ * Build the mini HTraced cluster.
+ *
+ * @param params            The parameters to use.
+ * @param ht                (out param) The mini htraced object on success.
+ * @param err               (out param) The error message if there was an
+ *                              error.
+ * @param err_len           The length of the error buffer provided by the
+ *                              caller.
+ */
+void mini_htraced_build(const struct mini_htraced_params *params, struct mini_htraced **ht,
+                        char *err, size_t err_len);
+
+/**
+ * Stop the htraced process.
+ *
+ * @param ht                The mini htraced object.
+ */
+void mini_htraced_stop(struct mini_htraced *ht);
+
+/**
+ * Free the memory associated with the mini htraced object.
+ *
+ * @param ht                The mini htraced object.
+ */
+void mini_htraced_free(struct mini_htraced *ht);
+
+/**
+ * Dump the spans contained in this htraced instance to a file.
+ *
+ * @param ht                The mini htraced object.
+ * @param err               (out param) The error message if there was an
+ *                              error.
+ * @param err_len           The length of the error buffer provided by the
+ *                              caller.
+ * @param path              The path to dump the spans to, in json form.
+ */
+void mini_htraced_dump_spans(struct mini_htraced *ht,
+                             char *err, size_t err_len,
+                             const char *path);
+
+#endif
+
+// vim: ts=4:sw=4:tw=79:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/process_id-unit.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/test/process_id-unit.c b/htrace-c/src/test/process_id-unit.c
new file mode 100644
index 0000000..5454eb0
--- /dev/null
+++ b/htrace-c/src/test/process_id-unit.c
@@ -0,0 +1,80 @@
+/**
+ * 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.
+ */
+
+#include "core/conf.h"
+#include "test/test.h"
+#include "util/log.h"
+#include "util/process_id.h"
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+static struct htrace_conf *g_cnf;
+
+static struct htrace_log *g_lg;
+
+static int test_calculate_process_id(const char *expected, const char *fmt)
+{
+    char *process_id;
+
+    process_id = calculate_process_id(g_lg, fmt, "fooproc");
+    EXPECT_NONNULL(process_id);
+    EXPECT_STR_EQ(expected, process_id);
+    free(process_id);
+
+    return EXIT_SUCCESS;
+}
+
+static int test_calculate_process_ids(void)
+{
+    char buf[128];
+
+    EXPECT_INT_ZERO(test_calculate_process_id("my.name", "my.name"));
+    EXPECT_INT_ZERO(test_calculate_process_id("my.fooproc", "my.%{tname}"));
+    EXPECT_INT_ZERO(test_calculate_process_id("\%foo", "\%foo"));
+    EXPECT_INT_ZERO(test_calculate_process_id("fooproc", "%{tname}"));
+    EXPECT_INT_ZERO(test_calculate_process_id("\\fooproc", "\\\\%{tname}"));
+    EXPECT_INT_ZERO(test_calculate_process_id("\%{tname}", "\\%{tname}"));
+
+    snprintf(buf, sizeof(buf), "me.%lld", (long long)getpid());
+    EXPECT_INT_ZERO(test_calculate_process_id(buf, "me.%{pid}"));
+
+    get_best_ip(g_lg, buf, sizeof(buf));
+    EXPECT_INT_ZERO(test_calculate_process_id(buf, "%{ip}"));
+
+    return EXIT_SUCCESS;
+}
+
+int main(void)
+{
+    g_cnf = htrace_conf_from_strs("", "");
+    EXPECT_NONNULL(g_cnf);
+    g_lg = htrace_log_alloc(g_cnf);
+    EXPECT_NONNULL(g_lg);
+    EXPECT_INT_ZERO(test_calculate_process_ids());
+    htrace_log_free(g_lg);
+    htrace_conf_free(g_cnf);
+
+    return EXIT_SUCCESS;
+}
+
+// vim: ts=4:sw=4:tw=79:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/rand-unit.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/test/rand-unit.c b/htrace-c/src/test/rand-unit.c
new file mode 100644
index 0000000..c02dbf1
--- /dev/null
+++ b/htrace-c/src/test/rand-unit.c
@@ -0,0 +1,93 @@
+/**
+ * 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.
+ */
+
+#include "core/conf.h"
+#include "test/test.h"
+#include "util/log.h"
+#include "util/rand.h"
+
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct htrace_log *g_rand_unit_lg;
+
+#define ARRAY_SIZE 128
+
+static int compare_u32(const void *v1, const void *v2)
+{
+    uint32_t a = *(uint32_t *)v1;
+    uint32_t b = *(uint32_t *)v2;
+
+    if (a < b) {
+        return -1;
+    } else if (b < a) {
+        return 1;
+    } else {
+        return 0;
+    }
+}
+
+/**
+ * Test that we can generate an array of unique uint32_t objects.
+ */
+static int test_u32_uniqueness(void)
+{
+    struct random_src *rnd = random_src_alloc(g_rand_unit_lg);
+    uint32_t prev, *arr;
+    int i, duplicate;
+
+    do {
+        arr = calloc(ARRAY_SIZE, sizeof(uint32_t));
+        EXPECT_NONNULL(arr);
+        for (i = 0; i < ARRAY_SIZE; i++) {
+            arr[i] = random_u32(rnd);
+        }
+        qsort(arr, ARRAY_SIZE, sizeof(arr[0]), compare_u32);
+        prev = 0;
+        duplicate = 0;
+        for (i = 0; i < ARRAY_SIZE; i++) {
+            if (arr[i] == prev) {
+                duplicate = 1;
+            }
+            prev = arr[i];
+        }
+    } while (duplicate);
+    random_src_free(rnd);
+    free(arr);
+    return EXIT_SUCCESS;
+}
+
+int main(void)
+{
+    struct htrace_conf *conf;
+
+    conf = htrace_conf_from_strs("", "");
+    EXPECT_NONNULL(conf);
+    g_rand_unit_lg = htrace_log_alloc(conf);
+    EXPECT_NONNULL(g_rand_unit_lg);
+    EXPECT_INT_ZERO(test_u32_uniqueness());
+    htrace_log_free(g_rand_unit_lg);
+    htrace_conf_free(conf);
+
+    return EXIT_SUCCESS;
+}
+
+// vim: ts=4:sw=4:tw=79:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/rtest.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/test/rtest.c b/htrace-c/src/test/rtest.c
new file mode 100644
index 0000000..0ec5272
--- /dev/null
+++ b/htrace-c/src/test/rtest.c
@@ -0,0 +1,155 @@
+/**
+ * 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.
+ */
+
+#include "core/conf.h"
+#include "core/htrace.h"
+#include "core/span.h"
+#include "test/rtest.h"
+#include "test/span_table.h"
+#include "test/span_util.h"
+#include "test/test.h"
+#include "util/log.h"
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define RECEIVER_TEST_TNAME "receiver-unit"
+
+/**
+ * @file rtestpp.cc
+ *
+ * The C receiver tests.
+ */
+
+struct rtest_data {
+    struct htrace_conf *cnf;
+    struct htrace_sampler *always;
+    struct htracer *tracer;
+};
+
+static void get_receiver_test_prid(char *prid, size_t prid_len)
+{
+    snprintf(prid, prid_len, RECEIVER_TEST_TNAME "/%lld", (long long)getpid());
+}
+
+static int rtest_data_init(const char *conf_str, struct rtest_data **out)
+{
+    struct rtest_data *rdata = calloc(1, sizeof(*(rdata)));
+    EXPECT_NONNULL(rdata);
+    rdata->cnf = htrace_conf_from_strs(conf_str,
+            HTRACE_PROCESS_ID"=%{tname}/%{pid};sampler=always");
+    EXPECT_NONNULL(rdata->cnf);
+    rdata->tracer = htracer_create(RECEIVER_TEST_TNAME, rdata->cnf);
+    EXPECT_NONNULL(rdata->tracer);
+    rdata->always = htrace_sampler_create(rdata->tracer, rdata->cnf);
+    EXPECT_NONNULL(rdata->always);
+    *out = rdata;
+    return EXIT_SUCCESS;
+}
+
+static void rtest_data_free(struct rtest_data *rdata)
+{
+    htrace_sampler_free(rdata->always);
+    rdata->always = NULL;
+    htracer_free(rdata->tracer);
+    rdata->tracer = NULL;
+    htrace_conf_free(rdata->cnf);
+    rdata->cnf = NULL;
+    free(rdata);
+}
+
+static int rtest_verify_table_size(struct rtest *rt, struct span_table *st)
+{
+    EXPECT_INT_EQ(rt->spans_created, span_table_size(st));
+
+    return EXIT_SUCCESS;
+}
+
+static int doit(struct rtest_data *rdata)
+{
+    struct htrace_scope *scope1, *scope2, *scope2_5;
+
+    scope1 = htrace_start_span(rdata->tracer, NULL, "part1");
+    EXPECT_UINT64_GT(0L, htrace_scope_get_span_id(scope1));
+    htrace_scope_close(scope1);
+    scope2 = htrace_start_span(rdata->tracer, NULL, "part2");
+    EXPECT_UINT64_GT(0L, htrace_scope_get_span_id(scope2));
+    scope2_5 = htrace_start_span(rdata->tracer, NULL, "part2.5");
+    htrace_scope_close(scope2_5);
+    htrace_scope_close(scope2);
+    return EXIT_SUCCESS;
+}
+
+int rtest_simple_run(struct rtest *rt, const char *conf_str)
+{
+    struct htrace_scope *scope0;
+    struct rtest_data *rdata = NULL;
+
+    EXPECT_INT_ZERO(rtest_data_init(conf_str, &rdata));
+    EXPECT_NONNULL(rdata);
+    scope0 = htrace_start_span(rdata->tracer, rdata->always, "doit");
+    doit(rdata);
+    htrace_scope_close(scope0);
+    rt->spans_created = 4;
+    rtest_data_free(rdata);
+    return EXIT_SUCCESS;
+}
+
+int rtest_simple_verify(struct rtest *rt, struct span_table *st)
+{
+    struct htrace_span *span;
+    uint64_t doit_id, part2_id;
+    char prid[128];
+
+    EXPECT_INT_ZERO(rtest_verify_table_size(rt, st));
+    get_receiver_test_prid(prid, sizeof(prid));
+    EXPECT_INT_ZERO(span_table_get(st, &span, "doit", prid));
+    doit_id = span->span_id;
+    EXPECT_INT_ZERO(span->num_parents);
+
+    EXPECT_INT_ZERO(span_table_get(st, &span, "part1", prid));
+    EXPECT_INT_EQ(1, span->num_parents);
+    EXPECT_UINT64_EQ(doit_id, span->parent.single);
+
+    EXPECT_INT_ZERO(span_table_get(st, &span, "part2", prid));
+    EXPECT_INT_EQ(1, span->num_parents);
+    part2_id = span->span_id;
+    EXPECT_UINT64_EQ(doit_id, span->parent.single);
+
+    EXPECT_INT_ZERO(span_table_get(st, &span, "part2.5", prid));
+    EXPECT_INT_EQ(1, span->num_parents);
+    EXPECT_UINT64_EQ(part2_id, span->parent.single);
+
+    return EXIT_SUCCESS;
+}
+
+static struct rtest g_rtest_simple = {
+    "rtest_simple",
+    rtest_simple_run,
+    rtest_simple_verify,
+};
+
+struct rtest * const g_rtests[] = {
+    &g_rtest_simple,
+    NULL
+};
+
+// vim: ts=4:sw=4:tw=79:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/rtest.h
----------------------------------------------------------------------
diff --git a/htrace-c/src/test/rtest.h b/htrace-c/src/test/rtest.h
new file mode 100644
index 0000000..52d0309
--- /dev/null
+++ b/htrace-c/src/test/rtest.h
@@ -0,0 +1,76 @@
+/**
+ * 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.
+ */
+
+#ifndef APACHE_HTRACE_TEST_RTEST_H
+#define APACHE_HTRACE_TEST_RTEST_H
+
+/**
+ * @file rtest.h
+ *
+ * Declares receiver tests.
+ *
+ * This is an internal header, not intended for external use.
+ */
+
+struct span_table;
+
+/**
+ * A receiver test.
+ */
+struct rtest {
+    /**
+     * The name of the receiver test.
+     */
+    const char *name;
+
+    /**
+     * Run the receiver test.
+     *
+     * @param rt        The receiver test.
+     * @param conf_str  The configuration string to use.
+     *
+     * @return zero on success
+     */
+    int (*run)(struct rtest *rt, const char *conf_str);
+
+    /**
+     * Verify that the receiver test succeeded.
+     *
+     * @param rt        The receiver test.
+     * @param st        The span table.
+     *
+     * @return          zero on success
+     */
+    int (*verify)(struct rtest *rt, struct span_table *st);
+
+    /**
+     * The number of spans that have been created by this test.
+     */
+    int spans_created;
+};
+
+/**
+ * A NULL-terminated list of pointers to rtests.
+ */
+extern struct rtest * const g_rtests[];
+
+#define RECEIVER_TEST_TNAME "receiver-unit"
+
+#endif
+
+// vim: ts=4:sw=4:tw=79:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/rtestpp.cc
----------------------------------------------------------------------
diff --git a/htrace-c/src/test/rtestpp.cc b/htrace-c/src/test/rtestpp.cc
new file mode 100644
index 0000000..649826c
--- /dev/null
+++ b/htrace-c/src/test/rtestpp.cc
@@ -0,0 +1,154 @@
+/**
+ * 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.
+ */
+
+#define __STDC_FORMAT_MACROS
+
+#include "core/htrace.hpp"
+
+extern "C" {
+#include "core/conf.h"
+#include "core/span.h"
+#include "test/rtest.h"
+#include "test/span_table.h"
+#include "test/span_util.h"
+#include "test/test.h"
+#include "util/log.h"
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+}
+
+/**
+ * @file rtestpp.cc
+ *
+ * The C++ receiver tests.
+ */
+
+using std::string;
+
+class RTestData {
+public:
+    RTestData(struct rtest *rt, const char *conf_str)
+        : rt_(rt),
+          cnf_(string(HTRACE_PROCESS_ID"=%{tname}/%{pid};sampler=always;") +
+               string(conf_str).c_str()),
+          tracer_(RECEIVER_TEST_TNAME, cnf_),
+          always_(&tracer_, cnf_)
+    {
+    }
+
+    // Test that initialization succeeeded
+    int TestInit() {
+        EXPECT_STR_EQ(RECEIVER_TEST_TNAME, tracer_.Name().c_str());
+        EXPECT_STR_EQ("AlwaysSampler", always_.ToString().c_str());
+        return EXIT_SUCCESS;
+    }
+
+    struct rtest *rt_;
+    htrace::Conf cnf_;
+    htrace::Tracer tracer_;
+    htrace::Sampler always_;
+
+private:
+    RTestData(const RTestData &other); // disallow copying
+    RTestData& operator=(const RTestData &other); // disallow assignment
+};
+
+static void get_receiver_test_prid(char *prid, size_t prid_len)
+{
+    snprintf(prid, prid_len, RECEIVER_TEST_TNAME "/%lld", (long long)getpid());
+}
+
+static int rtest_generic_verify(struct rtest *rt, struct span_table *st)
+{
+    EXPECT_INT_EQ(rt->spans_created, span_table_size(st));
+
+    return EXIT_SUCCESS;
+}
+
+static int doit(RTestData &tdata, struct rtest *rt)
+{
+    {
+        htrace::Scope scope1(tdata.tracer_, "part1");
+        EXPECT_UINT64_GT(0L, scope1.GetSpanId());
+    }
+    {
+        htrace::Scope scope2(tdata.tracer_, "part2");
+        EXPECT_UINT64_GT(0L, scope2.GetSpanId());
+        {
+            htrace::Scope scope2_5(tdata.tracer_, "part2.5");
+            EXPECT_UINT64_GT(0L, scope2_5.GetSpanId());
+        }
+    }
+    return EXIT_SUCCESS;
+}
+
+int rtestpp_simple_run(struct rtest *rt, const char *conf_str)
+{
+    RTestData tdata(rt, conf_str);
+    EXPECT_INT_ZERO(tdata.TestInit());
+
+    htrace::Scope scope0(tdata.tracer_, tdata.always_, "doit");
+    doit(tdata, rt);
+    rt->spans_created = 4;
+    return EXIT_SUCCESS;
+}
+
+int rtestpp_simple_verify(struct rtest *rt, struct span_table *st)
+{
+    struct htrace_span *span;
+    uint64_t doit_id, part2_id;
+    char prid[128];
+
+    EXPECT_INT_ZERO(rtest_generic_verify(rt, st));
+    get_receiver_test_prid(prid, sizeof(prid));
+    EXPECT_INT_ZERO(span_table_get(st, &span, "doit", prid));
+    doit_id = span->span_id;
+    EXPECT_INT_ZERO(span->num_parents);
+
+    EXPECT_INT_ZERO(span_table_get(st, &span, "part1", prid));
+    EXPECT_INT_EQ(1, span->num_parents);
+    EXPECT_UINT64_EQ(doit_id, span->parent.single);
+
+    EXPECT_INT_ZERO(span_table_get(st, &span, "part2", prid));
+    EXPECT_INT_EQ(1, span->num_parents);
+    part2_id = span->span_id;
+    EXPECT_UINT64_EQ(doit_id, span->parent.single);
+
+    EXPECT_INT_ZERO(span_table_get(st, &span, "part2.5", prid));
+    EXPECT_INT_EQ(1, span->num_parents);
+    EXPECT_UINT64_EQ(part2_id, span->parent.single);
+
+    return EXIT_SUCCESS;
+}
+
+struct rtest g_rtestpp_simple = {
+    "rtestpp_simple",
+    rtestpp_simple_run,
+    rtestpp_simple_verify,
+};
+
+struct rtest * const g_rtests[] = {
+    &g_rtestpp_simple,
+    NULL
+};
+
+// vim: ts=4:sw=4:tw=79:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/sampler-unit.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/test/sampler-unit.c b/htrace-c/src/test/sampler-unit.c
new file mode 100644
index 0000000..2a42292
--- /dev/null
+++ b/htrace-c/src/test/sampler-unit.c
@@ -0,0 +1,138 @@
+/**
+ * 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.
+ */
+
+#include "core/conf.h"
+#include "core/htrace.h"
+#include "sampler/sampler.h"
+#include "test/test.h"
+#include "util/log.h"
+
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static struct htrace_conf *g_test_conf;
+
+static struct htrace_log *g_test_lg;
+
+static struct htracer *g_test_tracer;
+
+#define NUM_TEST_SAMPLES 1000
+
+static int test_simple_sampler(const char *name, int expected)
+{
+    struct htrace_conf *conf;
+    struct htrace_sampler *smp;
+    char confstr[256] = { 0 };
+    int i;
+
+    if (name) {
+        snprintf(confstr, sizeof(confstr), "%s=%s",
+                 HTRACE_SAMPLER_KEY, name);
+    }
+    conf = htrace_conf_from_strs(confstr, "");
+    EXPECT_NONNULL(conf);
+    smp = htrace_sampler_create(g_test_tracer, conf);
+    EXPECT_NONNULL(smp);
+    for (i = 0; i < NUM_TEST_SAMPLES; i++) {
+        int s = smp->ty->next(smp);
+        EXPECT_INT_EQ(expected, s);
+    }
+    htrace_conf_free(conf);
+    htrace_sampler_free(smp);
+    return EXIT_SUCCESS;
+}
+
+static int test_unconfigured_sampler()
+{
+    return test_simple_sampler(NULL, 0);
+}
+
+static int test_never_sampler()
+{
+    return test_simple_sampler("never", 0);
+}
+
+static int test_always_sampler()
+{
+    return test_simple_sampler("always", 1);
+}
+
+#define NUM_PROB_TEST_SAMPLES 500000
+
+static int test_prob_sampler(double target, double slop)
+{
+    struct htrace_conf *conf;
+    struct htrace_sampler *smp;
+    char confstr[256] = { 0 };
+    double actual, diff;
+
+    snprintf(confstr, sizeof(confstr),
+             "sampler=prob;prob.sampler.fraction=%g", target);
+    conf = htrace_conf_from_strs(confstr, "");
+    EXPECT_NONNULL(conf);
+    smp = htrace_sampler_create(g_test_tracer, conf);
+    EXPECT_NONNULL(smp);
+    do {
+        int i;
+        uint64_t total = 0;
+
+        for (i = 0; i < NUM_PROB_TEST_SAMPLES; i++) {
+            int val = smp->ty->next(smp);
+            if ((val != 0) && (val != 1)) {
+                htrace_log(g_test_lg, "Invalid return from sampler: "
+                               "expected 0 or 1, but got %d\n", val);
+                abort();
+            }
+            total += val;
+        }
+        actual = ((double)total) / (double)NUM_PROB_TEST_SAMPLES;
+        diff = fabs(target - actual);
+        htrace_log(g_test_lg, "After %d samples, fraction is %g.  Target "
+                    "was %g.  %s\n", NUM_PROB_TEST_SAMPLES, actual, target,
+                    (diff < slop) ? "Done. " : "Retrying.");
+    } while (diff >= slop);
+    htrace_conf_free(conf);
+    htrace_sampler_free(smp);
+    return EXIT_SUCCESS;
+}
+
+int main(void)
+{
+    g_test_conf = htrace_conf_from_strs("", HTRACE_PROCESS_ID"=sampler-unit");
+    EXPECT_NONNULL(g_test_conf);
+    g_test_lg = htrace_log_alloc(g_test_conf);
+    EXPECT_NONNULL(g_test_lg);
+    g_test_tracer = htracer_create("sampler-unit", g_test_conf);
+    EXPECT_NONNULL(g_test_tracer);
+
+    EXPECT_INT_ZERO(test_unconfigured_sampler());
+    EXPECT_INT_ZERO(test_never_sampler());
+    EXPECT_INT_ZERO(test_always_sampler());
+    EXPECT_INT_ZERO(test_prob_sampler(0.5, 0.001));
+    EXPECT_INT_ZERO(test_prob_sampler(0.01, 0.001));
+    EXPECT_INT_ZERO(test_prob_sampler(0.1, 0.001));
+
+    htracer_free(g_test_tracer);
+    htrace_log_free(g_test_lg);
+    htrace_conf_free(g_test_conf);
+    return EXIT_SUCCESS;
+}
+
+// vim: ts=4:sw=4:tw=79:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/span-unit.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/test/span-unit.c b/htrace-c/src/test/span-unit.c
new file mode 100644
index 0000000..da6ca66
--- /dev/null
+++ b/htrace-c/src/test/span-unit.c
@@ -0,0 +1,173 @@
+/**
+ * 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.
+ */
+
+#include "core/conf.h"
+#include "core/htrace.h"
+#include "core/htracer.h"
+#include "core/span.h"
+#include "sampler/sampler.h"
+#include "test/span_util.h"
+#include "test/test.h"
+#include "util/htable.h"
+#include "util/log.h"
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define MAX_SPAN_JSON_LEN 100000
+
+static struct htrace_conf *g_test_conf;
+
+static struct htrace_log *g_test_lg;
+
+static struct htracer *g_test_tracer;
+
+static struct htrace_span *create_span(const char *desc,
+        uint64_t begin_ms, uint64_t end_ms, uint64_t span_id,
+        const char *prid, ...) __attribute__((sentinel));
+
+static int test_span_to_json(const char *expected,
+                             struct htrace_span *span)
+{
+    int buf_len;
+    char *buf;
+    struct htrace_span *rspan = NULL;
+    char err[128];
+    size_t err_len = sizeof(err);
+
+    htrace_span_sort_and_dedupe_parents(span);
+    buf_len = span_json_size(span);
+    if ((0 > buf_len) || (buf_len > MAX_SPAN_JSON_LEN)) {
+        fprintf(stderr, "invalid span_json_size %d.\n", buf_len);
+        return EXIT_FAILURE;
+    }
+    buf = malloc(buf_len);
+    EXPECT_NONNULL(buf);
+    span_json_sprintf(span, buf_len, buf);
+    EXPECT_STR_EQ(expected, buf);
+    span_json_parse(buf, &rspan, err, err_len);
+    if (err[0]) {
+        fprintf(stderr, "Failed to parse span json %s: %s\n", buf, err);
+        return EXIT_FAILURE;
+    }
+    EXPECT_NONNULL(rspan);
+    if (span_compare(span, rspan) != 0) {
+        htrace_span_free(rspan);
+        fprintf(stderr, "Failed to parse the span json back into a span "
+                "which was identical to the input.  JSON: %s\n", buf);
+        return EXIT_FAILURE;
+    }
+    free(buf);
+    htrace_span_free(rspan);
+    return EXIT_SUCCESS;
+}
+
+static struct htrace_span *create_span(const char *desc,
+        uint64_t begin_ms, uint64_t end_ms, uint64_t span_id,
+        const char *prid, ...)
+{
+    struct htrace_span* span = NULL;
+    uint64_t *parents, parent;
+    int i, num_parents = 0;
+    va_list ap, ap2;
+
+    va_start(ap, prid);
+    va_copy(ap2, ap);
+    while (1) {
+        parent = va_arg(ap2, uint64_t);
+        if (!parent) {
+            break;
+        }
+        num_parents++;
+    } while (parent);
+    va_end(ap2);
+    if (num_parents > 0) {
+        parents = xcalloc(sizeof(uint64_t) * num_parents);
+        for (i = 0; i < num_parents; i++) {
+            parents[i] = va_arg(ap, uint64_t);
+        }
+    }
+    va_end(ap);
+    span = htrace_span_alloc(desc, begin_ms, span_id);
+    span->end_ms = end_ms;
+    span->span_id = span_id;
+    span->prid = xstrdup(prid);
+    span->num_parents = num_parents;
+    if (num_parents == 1) {
+        span->parent.single = parents[0];
+        free(parents);
+    } else if (num_parents > 1) {
+        span->parent.list = parents;
+    }
+    return span;
+}
+
+static int test_spans_to_str(void)
+{
+    struct htrace_span *span;
+
+    span = create_span("foo", 123LLU, 456LLU, 789LLU, "span-unit",
+                        NULL);
+    EXPECT_INT_ZERO(test_span_to_json(
+        "{\"s\":\"0000000000000315\",\"b\":123,\"e\":456,"
+            "\"d\":\"foo\",\"r\":\"span-unit\""
+            ",\"p\":[]}", span));
+    htrace_span_free(span);
+
+    span = create_span("myspan", 34359738368LLU,
+                        34359739368LLU, 68719476736LLU, "span-unit2",
+                        1LLU, 2LLU, 3LLU, NULL);
+    EXPECT_INT_ZERO(test_span_to_json(
+        "{\"s\":\"0000001000000000\",\"b\":34359738368,\"e\":34359739368,"
+        "\"d\":\"myspan\",\"r\":\"span-unit2\"," "\"p\":[\"0000000000000001\","
+        "\"0000000000000002\",\"0000000000000003\"]}", span));
+    htrace_span_free(span);
+
+    span = create_span("nextSpan", 14359739368LLU, 18719476736LLU,
+                       0x8000001000000000LLU, "span-unit3",
+                        1LLU, 1LLU, 1LLU, NULL);
+    EXPECT_INT_ZERO(test_span_to_json(
+        "{\"s\":\"8000001000000000\",\"b\":14359739368,\"e\":18719476736,"
+        "\"d\":\"nextSpan\",\"r\":\"span-unit3\"," "\"p\":[\"0000000000000001\"]"
+        "}", span));
+    htrace_span_free(span);
+    return EXIT_SUCCESS;
+}
+
+int main(void)
+{
+    g_test_conf = htrace_conf_from_strs("", HTRACE_PROCESS_ID"=span-unit");
+    EXPECT_NONNULL(g_test_conf);
+    g_test_lg = htrace_log_alloc(g_test_conf);
+    EXPECT_NONNULL(g_test_lg);
+    g_test_tracer = htracer_create("span-unit", g_test_conf);
+    EXPECT_NONNULL(g_test_tracer);
+
+    EXPECT_INT_ZERO(test_spans_to_str());
+
+    htracer_free(g_test_tracer);
+    htrace_log_free(g_test_lg);
+    htrace_conf_free(g_test_conf);
+    return EXIT_SUCCESS;
+}
+
+// vim: ts=4:sw=4:tw=79:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/span_table.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/test/span_table.c b/htrace-c/src/test/span_table.c
new file mode 100644
index 0000000..8840fbf
--- /dev/null
+++ b/htrace-c/src/test/span_table.c
@@ -0,0 +1,139 @@
+/**
+ * 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.
+ */
+
+#include "core/span.h"
+#include "test/span_table.h"
+#include "test/span_util.h"
+#include "test/test.h"
+#include "util/htable.h"
+#include "util/log.h"
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct span_table *span_table_alloc(void)
+{
+    struct htable *ht;
+
+    ht = htable_alloc(128, ht_hash_string, ht_compare_string);
+    if (!ht) {
+        return NULL;
+    }
+    return (struct span_table*)ht;
+}
+
+int span_table_get(struct span_table *st, struct htrace_span **out,
+                   const char *desc, const char *prid)
+{
+    struct htable *ht = (struct htable *)st;
+    struct htrace_span *span;
+
+    span = htable_get(ht, desc);
+    EXPECT_NONNULL(span);
+    EXPECT_STR_EQ(desc, span->desc);
+    EXPECT_UINT64_GE(span->begin_ms, span->end_ms);
+    EXPECT_UINT64_GT(0L, span->span_id);
+    EXPECT_NONNULL(span->prid);
+    EXPECT_STR_EQ(prid, span->prid);
+    *out = span;
+    return EXIT_SUCCESS;
+}
+
+int span_table_put(struct span_table *st, struct htrace_span *span)
+{
+    struct htable *ht = (struct htable *)st;
+    int res;
+
+    res = htable_put(ht, span->desc, span);
+    if (res) {
+        htrace_span_free(span);
+        return res;
+    }
+    return 0;
+}
+
+static void span_table_free_entry(void *tracer, void *key, void *val)
+{
+    struct htrace_span *span = val;
+    htrace_span_free(span);
+    // We don't need to free the key, since it's simply the span->desc string,
+    // which is already freed by htrace_span_free.
+}
+
+void span_table_free(struct span_table *st)
+{
+    struct htable *ht = (struct htable *)st;
+
+    // Free all entries
+    htable_visit(ht, span_table_free_entry, NULL);
+    // Free the table itself
+    htable_free(ht);
+}
+
+uint32_t span_table_size(struct span_table *st)
+{
+    struct htable *ht = (struct htable *)st;
+    return htable_used(ht);
+}
+
+int load_trace_span_file(const char *path, struct span_table *st)
+{
+    char line[8196], err[1024];
+    size_t err_len = sizeof(err);
+    FILE *fp = NULL;
+    int lineno = 0, ret = EXIT_FAILURE;
+    struct htrace_span *span;
+
+    fp = fopen(path, "r");
+    if (!fp) {
+        int res = errno;
+        fprintf(stderr, "failed to open %s: %s\n", path, terror(res));
+        return -1;
+    }
+    while (1) {
+        ++lineno;
+        if (!fgets(line, sizeof(line), fp)) {
+            if (ferror(fp)) {
+                int res = errno;
+                fprintf(stderr, "error reading from %s: %s\n",
+                        path, terror(res));
+                break;
+            }
+            ret = EXIT_SUCCESS;
+            break;
+        }
+        span_json_parse(line, &span, err, err_len);
+        if (err[0]) {
+            fprintf(stderr, "error parsing line %d.  Failed to parse %s "
+                    "from %s: %s\n", lineno, line, path, err);
+            break;
+        }
+        span_table_put(st, span);
+    }
+    fclose(fp);
+    if (ret == EXIT_SUCCESS) {
+        //fprintf(stderr, "loaded %d spans from %s\n", lineno - 1, path);
+        return lineno - 1;
+    }
+    return -1;
+}
+
+// vim: ts=4:sw=4:tw=79:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/span_table.h
----------------------------------------------------------------------
diff --git a/htrace-c/src/test/span_table.h b/htrace-c/src/test/span_table.h
new file mode 100644
index 0000000..b55cd92
--- /dev/null
+++ b/htrace-c/src/test/span_table.h
@@ -0,0 +1,95 @@
+/**
+ * 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.
+ */
+
+#ifndef APACHE_HTRACE_TEST_SPAN_TABLE_H
+#define APACHE_HTRACE_TEST_SPAN_TABLE_H
+
+/**
+ * @file span_table.h
+ *
+ * Implements a hash table containing trace spans.  They are indexed by span
+ * description.
+ *
+ * This is an internal header, not intended for external use.
+ */
+
+struct htrace_span;
+struct span_table;
+
+/**
+ * Allocate a span table.
+ *
+ * @return              NULL on OOM; the span table otherwise.
+ */
+struct span_table *span_table_alloc(void);
+
+/**
+ * Retrieve a span from the table.
+ *
+ * @param st            The span table.
+ * @param out           (out param) the span.  This pointer will be valid until
+ *                          the span table is freed.
+ * @param desc          The span description to look for.
+ * @param prid          The process ID to verify that the span has.
+ *
+ * @return              0 on success; nonzero otherwise.
+ */
+int span_table_get(struct span_table *st, struct htrace_span **out,
+                   const char *desc, const char *prid);
+
+/**
+ * Add a span to the table.
+ *
+ * @param st            The span table.
+ * @param span          The span to add.  Its memory will be managed by the
+ *                          span table after span_table_put is called.
+ *
+ * @return              0 on success; nonzero otherwise.
+ */
+int span_table_put(struct span_table *st, struct htrace_span *span);
+
+/**
+ * Free a span table.  All spans inside will be freed.
+ *
+ * @param st            The span table.
+ */
+void span_table_free(struct span_table *st);
+
+/**
+ * Get the size of the span table.
+ *
+ * @return              The number of entries in the span table.
+ */
+uint32_t span_table_size(struct span_table *st);
+
+/**
+ * Load a file with newline-separated trace spans in JSON format into a span
+ * table.  Note that this function assumes that every line contains a complete
+ * span, and that each line is less than 8196 bytes.
+ *
+ * @param path              The path to read the file from.
+ * @param st                The span table we will fill in.
+ *
+ * @return                  Negative numbers on failure; the number of lines we
+ *                              read otherwise.
+ */
+int load_trace_span_file(const char *path, struct span_table *st);
+
+#endif
+
+// vim: ts=4:sw=4:tw=79:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/span_util-unit.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/test/span_util-unit.c b/htrace-c/src/test/span_util-unit.c
new file mode 100644
index 0000000..fc2a56c
--- /dev/null
+++ b/htrace-c/src/test/span_util-unit.c
@@ -0,0 +1,74 @@
+/**
+ * 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.
+ */
+
+#include "test/span_util.h"
+#include "test/test.h"
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static int test_parse_hex_id_error(const char *in)
+{
+    char err[128];
+
+    err[0] = '\0';
+    parse_hex_id(in, err, sizeof(err));
+    if (!err[0]) {
+        fprintf(stderr, "test_parse_hex_id_error(%s): expected error, but "
+                "was successful.\n", in);
+        return EXIT_FAILURE;
+    }
+    return EXIT_SUCCESS;
+}
+
+static int test_parse_hex_id(uint64_t expected, const char *in)
+{
+    char err[128];
+    size_t err_len = sizeof(err);
+    uint64_t val;
+
+    err[0] = '\0';
+    val = parse_hex_id(in, err, err_len);
+    if (err[0]) {
+        fprintf(stderr, "test_parse_hex_id(%s): got error %s\n",
+                in, err);
+        return EXIT_FAILURE;
+    }
+    EXPECT_UINT64_EQ(expected, val);
+    return EXIT_SUCCESS;
+}
+
+int main(void)
+{
+    EXPECT_INT_ZERO(test_parse_hex_id_error(""));
+    EXPECT_INT_ZERO(test_parse_hex_id_error("z"));
+    EXPECT_INT_ZERO(test_parse_hex_id_error("achoo"));
+    EXPECT_INT_ZERO(test_parse_hex_id(1LLU, "00000000000000001"));
+    EXPECT_INT_ZERO(test_parse_hex_id(0xffffffffffffffffLLU,
+                                       "ffffffffffffffff"));
+    EXPECT_INT_ZERO(test_parse_hex_id(0x8000000000000000LLU,
+                                       "8000000000000000"));
+    EXPECT_INT_ZERO(test_parse_hex_id(0x6297421fe159345fLLU,
+                                       "6297421fe159345f"));
+    EXPECT_INT_ZERO(test_parse_hex_id_error("6297421fe159345fzoo"));
+    return EXIT_SUCCESS;
+}
+
+// vim: ts=4:sw=4:tw=79:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/span_util.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/test/span_util.c b/htrace-c/src/test/span_util.c
new file mode 100644
index 0000000..fc4b041
--- /dev/null
+++ b/htrace-c/src/test/span_util.c
@@ -0,0 +1,294 @@
+/**
+ * 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.
+ */
+
+#include "core/span.h"
+#include "test/span_util.h"
+#include "util/log.h"
+
+#include <errno.h>
+#include <json/json_object.h>
+#include <json/json_tokener.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+uint64_t parse_hex_id(const char *in, char *err, size_t err_len)
+{
+    char *endptr;
+    unsigned long long int ret;
+
+    err[0] = '\0';
+    errno = 0;
+    ret = strtoull(in, &endptr, 16);
+    if (errno) {
+        int e = errno;
+        snprintf(err, err_len, "parse_hex_id(%s) failed: error %s",
+                 in, terror(e));
+        return 0;
+    }
+    if (endptr == in) {
+        snprintf(err, err_len, "parse_hex_id(%s) failed: empty string "
+                 "found.", in);
+        return 0;
+    }
+    while (1) {
+        char c = *endptr++;
+        if (c == '\0') {
+            break;
+        }
+        if ((c != ' ') || (c != '\t')) {
+            snprintf(err, err_len, "parse_hex_id(%s) failed: garbage at end "
+                     "of string.", in);
+            return 0;
+        }
+    }
+    return ret;
+}
+
+static void span_json_parse_parents(struct json_object *root,
+                    struct htrace_span *span, char *err, size_t err_len)
+{
+    char err2[128];
+    struct json_object *p = NULL, *e = NULL;
+    int i, np;
+
+    if (!json_object_object_get_ex(root, "p", &p)) {
+        return; // no parents
+    }
+    if (json_object_get_type(p) != json_type_array) {
+        snprintf(err, err_len, "p element was not an array..");
+        return;
+    }
+    np = json_object_array_length(p);
+    if (np == 1) {
+        span->num_parents = 1;
+        e = json_object_array_get_idx(p, 0);
+        span->parent.single = parse_hex_id(json_object_get_string(e),
+                                            err2, sizeof(err2));
+        if (err2[0]) {
+            snprintf(err, err_len, "failed to parse parent ID 1/1: %s.", err2);
+            return;
+        }
+    } else if (np > 1) {
+        span->parent.list = malloc(sizeof(uint64_t) * np);
+        if (!span->parent.list) {
+            snprintf(err, err_len, "failed to allocate parent ID array of "
+                     "%d elements", np);
+            return;
+        }
+        span->num_parents = np;
+        for (i = 0; i < np; i++) {
+            e = json_object_array_get_idx(p, i);
+            span->parent.list[i] = parse_hex_id(json_object_get_string(e),
+                                            err2, sizeof(err2));
+            if (err2[0]) {
+                snprintf(err, err_len, "failed to parse parent ID %d/%d: %s",
+                         i + 1, np, err2);
+                return;
+            }
+        }
+    }
+}
+
+static void span_json_parse_impl(struct json_object *root,
+                    struct htrace_span *span, char *err, size_t err_len)
+{
+    char err2[128];
+    struct json_object *d = NULL, *b = NULL, *e = NULL, *s = NULL, *r = NULL;
+    int res;
+
+    err[0] = '\0';
+    if (!json_object_object_get_ex(root, "d", &d)) {
+        d = NULL;
+    }
+    span->desc = strdup(d ? json_object_get_string(d) : "");
+    if (!span->desc) {
+        snprintf(err, err_len, "out of memory allocating description");
+        return;
+    }
+    if (json_object_object_get_ex(root, "b", &b)) {
+        errno = 0;
+        span->begin_ms = json_object_get_int64(b);
+        res = errno;
+        if (res) {
+            snprintf(err, err_len, "error parsing begin_ms: %s", terror(res));
+            return;
+        }
+    }
+    if (json_object_object_get_ex(root, "e", &e)) {
+        errno = 0;
+        span->end_ms = json_object_get_int64(e);
+        res = errno;
+        if (res) {
+            snprintf(err, err_len, "error parsing end_ms: %s", terror(res));
+            return;
+        }
+    }
+    if (json_object_object_get_ex(root, "s", &s)) {
+        span->span_id = parse_hex_id(json_object_get_string(s),
+                                     err2, sizeof(err2));
+        if (err2[0]) {
+            snprintf(err, err_len, "error parsing span_id: %s", err2);
+            return;
+        }
+    }
+    if (json_object_object_get_ex(root, "r", &r)) {
+        span->prid = strdup(json_object_get_string(r));
+    } else {
+        span->prid = strdup("");
+    }
+    if (!span->prid) {
+        snprintf(err, err_len, "out of memory allocating process id");
+        return;
+    }
+    span_json_parse_parents(root, span, err, err_len);
+    if (err[0]) {
+        return;
+    }
+}
+
+void span_json_parse(const char *in, struct htrace_span **rspan,
+                     char *err, size_t err_len)
+{
+    struct json_object *root = NULL;
+    enum json_tokener_error jerr;
+    struct htrace_span *span = NULL;
+
+    err[0] = '\0';
+    root = json_tokener_parse_verbose(in, &jerr);
+    if (!root) {
+        snprintf(err, err_len, "json_tokener_parse_verbose failed: %s",
+                 json_tokener_error_desc(jerr));
+        goto done;
+    }
+    span = calloc(1, sizeof(*span));
+    if (!span) {
+        snprintf(err, err_len, "failed to malloc span.");
+        goto done;
+    }
+    span_json_parse_impl(root, span, err, err_len);
+
+done:
+    if (root) {
+        json_object_put(root);
+    }
+    if (err[0]) {
+        htrace_span_free(span);
+        *rspan = NULL;
+    } else {
+        *rspan = span;
+    }
+}
+
+/**
+ * Compare two 64-bit numbers.
+ *
+ * We don't use subtraction here in order to avoid numeric overflow.
+ */
+static int uint64_cmp(uint64_t a, uint64_t b)
+{
+    if (a < b) {
+        return -1;
+    } else if (a > b) {
+        return 1;
+    } else {
+        return 0;
+    }
+}
+
+static int strcmp_handle_null(const char *a, const char *b)
+{
+    if (a == NULL) {
+        a = "";
+    }
+    if (b == NULL) {
+        b = "";
+    }
+    return strcmp(a, b);
+}
+
+static int compare_parents(struct htrace_span *a, struct htrace_span *b)
+{
+    int na, nb, i;
+
+    htrace_span_sort_and_dedupe_parents(a);
+    na = a->num_parents;
+    htrace_span_sort_and_dedupe_parents(b);
+    nb = b->num_parents;
+
+    for (i = 0; ; i++) {
+        uint64_t sa, sb;
+
+        if (i >= na) {
+            if (i >= nb) {
+                return 0;
+            } else {
+                return -1;
+            }
+        } else if (i >= nb) {
+            return 1;
+        }
+        if ((i == 0) && (na == 1)) {
+            sa = a->parent.single;
+        } else {
+            sa = a->parent.list[i];
+        }
+        if ((i == 0) && (nb == 1)) {
+            sb = b->parent.single;
+        } else {
+            sb = b->parent.list[i];
+        }
+        // Use explicit comparison rather than subtraction to avoid numeric
+        // overflow issues.
+        if (sa < sb) {
+            return -1;
+        } else if (sa > sb) {
+            return 1;
+        }
+    }
+}
+
+int span_compare(struct htrace_span *a, struct htrace_span *b)
+{
+    int c;
+
+    c = uint64_cmp(a->span_id, b->span_id);
+    if (c) {
+        return c;
+    }
+    c = strcmp(a->desc, b->desc);
+    if (c) {
+        return c;
+    }
+    c = uint64_cmp(a->begin_ms, b->begin_ms);
+    if (c) {
+        return c;
+    }
+    c = uint64_cmp(a->end_ms, b->end_ms);
+    if (c) {
+        return c;
+    }
+    c = strcmp_handle_null(a->prid, b->prid);
+    if (c) {
+        return c;
+    }
+    return compare_parents(a, b);
+}
+
+// vim:ts=4:sw=4:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/span_util.h
----------------------------------------------------------------------
diff --git a/htrace-c/src/test/span_util.h b/htrace-c/src/test/span_util.h
new file mode 100644
index 0000000..393e213
--- /dev/null
+++ b/htrace-c/src/test/span_util.h
@@ -0,0 +1,66 @@
+/**
+ * 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.
+ */
+
+#ifndef APACHE_HTRACE_TEST_SPAN_UTIL_H
+#define APACHE_HTRACE_TEST_SPAN_UTIL_H
+
+#include <stdint.h>
+#include <unistd.h> /* for size_t */
+
+struct htrace_span;
+
+/**
+ * Parses 64-bit hex ID.
+ *
+ * @param in        The hex ID string.
+ * @param err       (out param) On error, where the error message will be
+ *                      written.  Will be set to the empty string on success.
+ * @param err_len   The length of the error buffer.  Must be nonzero.
+ */
+uint64_t parse_hex_id(const char *in, char *err, size_t err_len);
+
+/**
+ * Parses a span JSON entry back into a span.
+ *
+ * This function is just used in unit tests and is not optimized.
+ *
+ * @param in        The span string.
+ * @param span      (out param) On success, the dynamically allocated span
+ *                      object.
+ * @param err       (out param) On error, where the error message will be
+ *                      written.  Will be set to the empty string on success.
+ * @param err_len   The length of the error buffer.  Must be nonzero.
+ */
+void span_json_parse(const char *in, struct htrace_span **span,
+                     char *err, size_t err_len);
+
+/**
+ * Compare two spans.
+ *
+ * @param a         The first span.
+ * @param b         The second span.
+ *
+ * @return          A negative number if the first span is less;
+ *                  0 if the spans are equivalent;
+ *                  A positive number if the first span is greater.
+ */
+int span_compare(struct htrace_span *a, struct htrace_span *b);
+
+#endif
+
+// vim:ts=4:sw=4:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/string-unit.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/test/string-unit.c b/htrace-c/src/test/string-unit.c
new file mode 100644
index 0000000..56155ea
--- /dev/null
+++ b/htrace-c/src/test/string-unit.c
@@ -0,0 +1,73 @@
+/**
+ * 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.
+ */
+
+#include "test/test.h"
+#include "util/string.h"
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#pragma GCC diagnostic ignored "-Wformat-zero-length"
+
+static int test_fwdprintf(void)
+{
+    char *b, buf[8];
+    int rem = sizeof(buf);
+
+    memset(&buf, 0, sizeof(buf));
+    b = buf;
+    EXPECT_INT_EQ(3, fwdprintf(NULL, NULL, "ab%c", 'c'));
+    EXPECT_INT_EQ(3, fwdprintf(&b, &rem, "ab%c", 'c'));
+    EXPECT_STR_EQ("abc", buf);
+    EXPECT_INT_EQ(5, rem);
+    EXPECT_INT_EQ(0, fwdprintf(NULL, NULL, ""));
+    EXPECT_INT_EQ(0, fwdprintf(&b, &rem, ""));
+    EXPECT_INT_EQ(5, rem);
+    EXPECT_INT_EQ(2, fwdprintf(NULL, NULL, "de"));
+    EXPECT_INT_EQ(2, fwdprintf(&b, &rem, "de"));
+    EXPECT_STR_EQ("abcde", buf);
+    EXPECT_INT_EQ(3, rem);
+    EXPECT_INT_EQ(6, fwdprintf(NULL, NULL, "fghijk"));
+    EXPECT_INT_EQ(6, fwdprintf(&b, &rem, "fghijk"));
+    EXPECT_INT_EQ(0, rem);
+    EXPECT_STR_EQ("abcdefg", buf);
+    return EXIT_SUCCESS;
+}
+
+static int test_validate_json_string(void)
+{
+    EXPECT_INT_EQ(1, validate_json_string(NULL, ""));
+    EXPECT_INT_EQ(1, validate_json_string(NULL, "abc"));
+    EXPECT_INT_EQ(0, validate_json_string(NULL, "\\"));
+    EXPECT_INT_EQ(0, validate_json_string(NULL, "\"FooBar\""));
+    EXPECT_INT_EQ(1, validate_json_string(NULL, "Foo:bar:baz-whatever"));
+    EXPECT_INT_EQ(0, validate_json_string(NULL, "\x01"));
+    return EXIT_SUCCESS;
+}
+
+int main(void)
+{
+    EXPECT_INT_ZERO(test_fwdprintf());
+    EXPECT_INT_ZERO(test_validate_json_string());
+    return EXIT_SUCCESS;
+}
+
+// vim: ts=4:sw=4:tw=79:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/temp_dir-unit.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/test/temp_dir-unit.c b/htrace-c/src/test/temp_dir-unit.c
new file mode 100644
index 0000000..1c04dae
--- /dev/null
+++ b/htrace-c/src/test/temp_dir-unit.c
@@ -0,0 +1,89 @@
+/**
+ * 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.
+ */
+
+#include "test/temp_dir.h"
+#include "test/test.h"
+
+#include <limits.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+static int test_create_tempdir(void)
+{
+    char *tdir = NULL;
+    struct stat st_buf;
+    char err[128];
+    size_t err_len = sizeof(err);
+    int ret;
+
+    tdir = create_tempdir("test_create_tempdir", 0775,
+                          err, err_len);
+    EXPECT_STR_EQ("", err);
+    ret = register_tempdir_for_cleanup(tdir);
+    if (ret)
+        return EXIT_FAILURE;
+    if (stat(tdir, &st_buf) == -1) {
+        return EXIT_FAILURE;
+    }
+    if (!S_ISDIR(st_buf.st_mode)) {
+        return EXIT_FAILURE;
+    }
+    free(tdir);
+    return 0;
+}
+
+static int test_create_tempdir_and_delete(void)
+{
+    char *tdir = NULL;
+    struct stat st_buf;
+    int ret;
+    char err[128];
+    size_t err_len = sizeof(err);
+
+    tdir = create_tempdir("test_create_tempdir_and_delete", 0775,
+                          err, err_len);
+    EXPECT_STR_EQ("", err);
+    ret = register_tempdir_for_cleanup(tdir);
+    if (ret)
+        return EXIT_FAILURE;
+    if (stat(tdir, &st_buf) == -1) {
+        return EXIT_FAILURE;
+    }
+    if (!S_ISDIR(st_buf.st_mode)) {
+        return EXIT_FAILURE;
+    }
+    recursive_unlink(tdir);
+    unregister_tempdir_for_cleanup(tdir);
+    free(tdir);
+    return 0;
+}
+
+int main(void)
+{
+    EXPECT_INT_ZERO(test_create_tempdir());
+    EXPECT_INT_ZERO(test_create_tempdir());
+    EXPECT_INT_ZERO(test_create_tempdir());
+    EXPECT_INT_ZERO(test_create_tempdir_and_delete());
+    EXPECT_INT_ZERO(test_create_tempdir_and_delete());
+    EXPECT_INT_ZERO(test_create_tempdir_and_delete());
+    return EXIT_SUCCESS;
+}
+
+// vim: ts=4:sw=4:tw=79:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/temp_dir.c
----------------------------------------------------------------------
diff --git a/htrace-c/src/test/temp_dir.c b/htrace-c/src/test/temp_dir.c
new file mode 100644
index 0000000..b11c8b6
--- /dev/null
+++ b/htrace-c/src/test/temp_dir.c
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2011-2012 the Redfish authors
+ *
+ * Licensed 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.
+ */
+
+#include "test/temp_dir.h"
+#include "util/log.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+// Globals
+static int g_tempdir_nonce = 0;
+
+static int g_num_tempdirs = 0;
+
+static char **g_tempdirs = NULL;
+
+static pthread_mutex_t tempdir_lock = PTHREAD_MUTEX_INITIALIZER;
+
+// Functions
+static void cleanup_registered_tempdirs(void)
+{
+    int i;
+    const char *skip_cleanup;
+    skip_cleanup = getenv("SKIP_CLEANUP");
+    if (skip_cleanup)
+        return;
+    pthread_mutex_lock(&tempdir_lock);
+    for (i = 0; i < g_num_tempdirs; ++i) {
+        recursive_unlink(g_tempdirs[i]);
+        free(g_tempdirs[i]);
+    }
+    free(g_tempdirs);
+    g_tempdirs = NULL;
+    pthread_mutex_unlock(&tempdir_lock);
+}
+
+char *create_tempdir(const char *name, int mode, char *err, size_t err_len)
+{
+    char *tdir = NULL, tmp[PATH_MAX];
+    int nonce, pid;
+    const char *base = getenv("TMPDIR");
+
+    err[0] = '\0';
+    if (!base) {
+        base = "/tmp";
+    }
+    if (base[0] != '/') {
+        // canonicalize non-abosolute TMPDIR
+        if (realpath(base, tmp) == NULL) {
+            int e = errno;
+            snprintf(err, err_len, "realpath(%s) failed: %s",
+                     base, terror(e));
+            return NULL;
+        }
+        base = tmp;
+    }
+    pthread_mutex_lock(&tempdir_lock);
+    nonce = g_tempdir_nonce++;
+    pthread_mutex_unlock(&tempdir_lock);
+    pid = getpid();
+    if (asprintf(&tdir, "%s/%s.tmp.%08d.%08d",
+             base, name, pid, nonce) < 0) {
+        snprintf(err, err_len, "asprintf failed");
+        return NULL;
+    }
+    if (mkdir(tdir, mode) == -1) {
+        int e = errno;
+        snprintf(err, err_len, "mkdir(%s) failed: %s", tdir, terror(e));
+        free(tdir);
+        return NULL;
+    }
+    return tdir;
+}
+
+int register_tempdir_for_cleanup(const char *tdir)
+{
+    char **tempdirs;
+    pthread_mutex_lock(&tempdir_lock);
+    tempdirs = realloc(g_tempdirs, sizeof(char*) * (g_num_tempdirs + 1));
+    if (!tempdirs)
+        return -ENOMEM;
+    g_tempdirs = tempdirs;
+    g_tempdirs[g_num_tempdirs] = strdup(tdir);
+    if (g_num_tempdirs == 0)
+        atexit(cleanup_registered_tempdirs);
+    g_num_tempdirs++;
+    pthread_mutex_unlock(&tempdir_lock);
+    return 0;
+}
+
+void unregister_tempdir_for_cleanup(const char *tdir)
+{
+    int i;
+    char **tempdirs;
+    pthread_mutex_lock(&tempdir_lock);
+    if (g_num_tempdirs == 0) {
+        pthread_mutex_unlock(&tempdir_lock);
+        return;
+    }
+    for (i = 0; i < g_num_tempdirs; ++i) {
+        if (strcmp(g_tempdirs[i], tdir) == 0)
+            break;
+    }
+    if (i == g_num_tempdirs) {
+        pthread_mutex_unlock(&tempdir_lock);
+        return;
+    }
+    free(g_tempdirs[i]);
+    g_tempdirs[i] = g_tempdirs[g_num_tempdirs - 1];
+    tempdirs = realloc(g_tempdirs, sizeof(char*) * g_num_tempdirs - 1);
+    if (tempdirs) {
+        g_tempdirs = tempdirs;
+    }
+    g_num_tempdirs--;
+    pthread_mutex_unlock(&tempdir_lock);
+}
+
+static int recursive_unlink_helper(int dirfd, const char *name)
+{
+    int fd = -1, ret = 0;
+    DIR *dfd = NULL;
+    struct stat stat;
+    struct dirent *de;
+
+    if (dirfd >= 0) {
+        fd = openat(dirfd, name, O_RDONLY);
+    } else {
+        fd = open(name, O_RDONLY);
+    }
+    if (fd < 0) {
+        ret = errno;
+        fprintf(stderr, "error opening %s: %s\n", name, terror(ret));
+        goto done;
+    }
+    if (fstat(fd, &stat) < 0) {
+        ret = errno;
+        fprintf(stderr, "failed to stat %s: %s\n", name, terror(ret));
+        goto done;
+    }
+    if (!(S_ISDIR(stat.st_mode))) {
+        if (unlinkat(dirfd, name, 0)) {
+            ret = errno;
+            fprintf(stderr, "failed to unlink %s: %s\n", name, terror(ret));
+            goto done;
+        }
+    } else {
+        dfd = fdopendir(fd);
+        if (!dfd) {
+            ret = errno;
+            fprintf(stderr, "fopendir(%s) failed: %s\n", name, terror(ret));
+            goto done;
+        }
+        while ((de = readdir(dfd))) {
+            if (!strcmp(de->d_name, "."))
+                continue;
+            if (!strcmp(de->d_name, ".."))
+                continue;
+            ret = recursive_unlink_helper(fd, de->d_name);
+            if (ret)
+                goto done;
+        }
+        if (unlinkat(dirfd, name, AT_REMOVEDIR) < 0) {
+            ret = errno;
+            goto done;
+        }
+    }
+done:
+    if (fd >= 0) {
+        close(fd);
+    }
+    if (dfd) {
+        closedir(dfd);
+    }
+    return -ret;
+}
+
+int recursive_unlink(const char *path)
+{
+    return recursive_unlink_helper(-1, path);
+}
+
+// vim:ts=4:sw=4:et

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/temp_dir.h
----------------------------------------------------------------------
diff --git a/htrace-c/src/test/temp_dir.h b/htrace-c/src/test/temp_dir.h
new file mode 100644
index 0000000..d12e961
--- /dev/null
+++ b/htrace-c/src/test/temp_dir.h
@@ -0,0 +1,67 @@
+/**
+ * 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.
+ */
+
+#ifndef APACHE_HTRACE_TEST_TEMP_DIR_H
+#define APACHE_HTRACE_TEST_TEMP_DIR_H
+
+#include <unistd.h> /* for size_t */
+
+/**
+ * Create a temporary directory
+ *
+ * @param tempdir   The identifier to use for this temporary directory.  We will
+ *                      add a random string to this identifier to create the
+ *                      full path.
+ * @param mode      The mode to use in mkdir.  Typically 0755.
+ * @param err       The error buffer to be set on failure.
+ * @param err_len   Length of the error buffer.
+ *
+ * @return          A malloc'ed string with the path to the temporary directory,
+ *                      or NULL on failure.
+ */
+char *create_tempdir(const char *name, int mode, char *err, size_t err_len);
+
+/**
+ * Register a temporary directory to be deleted at the end of the program
+ *
+ * @param tempdir   The path of the temporary directory to register.  The string
+ *                      will be deep-copied.
+ *
+ * @return      0 on success; error code otherwise
+ */
+int register_tempdir_for_cleanup(const char *tempdir);
+
+/**
+ * Unregister a temporary directory to be deleted at the end of the program
+ *
+ * @param tempdir   The path of the temporary directory to unregister.
+ */
+void unregister_tempdir_for_cleanup(const char *tempdir);
+
+/**
+ * Recursively unlink a directory.
+ *
+ * @param tempdir   The directory to remove.
+ *
+ * @return          Zero on success; the error code otherwise.
+ */
+int recursive_unlink(const char *path);
+
+#endif
+
+// vim:ts=4:sw=4:et