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:20 UTC
[2/4] incubator-htrace git commit: HTRACE-106: htrace: add C / C++
native client (cmccabe)
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