Skip to content
This repository has been archived by the owner on Aug 30, 2022. It is now read-only.

Commit

Permalink
Minimal demo in C
Browse files Browse the repository at this point in the history
  • Loading branch information
ringerc committed Feb 13, 2018
1 parent 0d999a0 commit 0eaa705
Show file tree
Hide file tree
Showing 6 changed files with 396 additions and 2 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,5 @@ if (BUILD_TESTING)
endif()

add_subdirectory(src/jaegertracing)
add_subdirectory(src/cdemo)
add_subdirectory(crossdock)
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,7 @@ so traces aren't lost.
It's possible to use `jaeger-cpp` from C with appropriate `extern "C"` thunks
and care around resource lifetime management etc. A real world example is the
support for opentracing and jaeger in nginx; see
https://github.com/opentracing-contrib/nginx-opentracing . A simplified example
would be welcomed.
https://github.com/opentracing-contrib/nginx-opentracing .

### Usage from C++98

Expand Down
10 changes: 10 additions & 0 deletions src/cdemo/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
cmake_minimum_required(VERSION 3.3)
PROJECT(cdemo C CXX)

add_executable(cdemo cdemo.c cdemo_tracing.cpp)

target_link_libraries(cdemo jaegertracing)

target_include_directories(cdemo PRIVATE
$<BUILD_INTERFACE:${jaegertracing_SOURCE_DIR}/src>
$<BUILD_INTERFACE:${jaegertracing_BINARY_DIR}/src>)
133 changes: 133 additions & 0 deletions src/cdemo/cdemo.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
#include "cdemo_tracing.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>

/*
* Fake a RPC call using a subprocess to demonstrate marshalling and unmarshalling
* of context info across process boundaries.
*/
static void
send_fake_rpc(CDemoTraceSpanContext *trace_ctx, const char *endpoint, int depth)
{
CDemoTraceSpan *span = NULL;
CDemoTraceSpanContext *innerctx = NULL;
char *ctx_arg_hexstr;
#define CDEMO_MAX_CMDLINE_LENGTH 300
char cmdline[CDEMO_MAX_CMDLINE_LENGTH];

span = cdemo_trace_start(trace_ctx, "send_fake_rpc", NULL);
innerctx = cdemo_trace_context_from_span(span);

/*
* Marshal the trace context for sending as a command line argument.
*
* But since this example doesn't know what your app needs, it just
* gets a binary context, then munges it to hex for sending on a command
* line.
*/
ctx_arg_hexstr = cdemo_trace_context_to_hex(trace_ctx);

if (strchr(endpoint, '\"'))
{
/* We should escape the endpoint path but meh, we'll bail out */
fprintf(stderr, "paths containing double quotes not handled here");
exit(1);
}

/*
* Make the command line to fake up the "RPC" and
* run the
*/
snprintf(cmdline, CDEMO_MAX_CMDLINE_LENGTH,
"\"%s\" childproc %d \"%s\"", endpoint, depth, ctx_arg_hexstr);
cmdline[CDEMO_MAX_CMDLINE_LENGTH-1] = '\0';

fprintf(stderr, "invoking %s\n", cmdline);

/*
* And invoke the child proc synchronously. We fire and forget here,
* and don't care if it succeeded or not.
*/
system(cmdline);

/*
* Hopefully whatever framework/tool you're using has cleanup hooks or
* other helpers so you don't have to do this manual cleanup, but this demo
* is pure C, so:
*/
free(ctx_arg_hexstr);
cdemo_trace_context_delete(innerctx);
cdemo_trace_done(span);
}

static void
usage_die(void)
{
fprintf(stderr, "usage: cdemo tagstring [ncalls [opentracing-context-as-hex]]\n");
exit(1);
}

int main(int argc, const char* argv[])
{
const char * tagval = NULL;
CDemoTraceSpan *span = NULL;
CDemoTraceSpanContext *imported_ctx = NULL;
CDemoTraceSpanContext *ctx = NULL;
int depth;

/* Bring up the tracer */
cdemo_tracing_start();

if (argc < 2)
usage_die();

tagval = argv[1];
fprintf(stderr, "cdemo invoked with tag \"%s\"", tagval);

if (argc > 2)
{
char *endptr;
depth = strtol(argv[2], &endptr, 10);
if (endptr == argv[2])
usage_die();
}
else
{
/* Default to single level calls */
depth = 1;
}
fprintf(stderr, " at depth %d", depth);

if (argc > 3)
{
/*
* We're being called with an exported trace context. Import it
* to use as the parent context for our trace.
*/
imported_ctx = cdemo_trace_context_from_hex(argv[3]);
assert(imported_ctx);
fprintf(stderr, " and with context \"%s\"", argv[3]);
}
fprintf(stderr, "\n");

span = cdemo_trace_start(imported_ctx, "main", tagval);
ctx = cdemo_trace_context_from_span(span);

if (depth > 0)
send_fake_rpc(ctx, argv[0], depth - 1);

cdemo_trace_context_delete(ctx);
cdemo_trace_done(span);

/* Work around bug #54 by sleeping for >> buffer flush interval */
fprintf(stderr, "sleeping to ensure buffers flushed... ");
sleep(5);
fprintf(stderr, "done\n");

/* Shut down the tracer */
cdemo_tracing_finish();
}
206 changes: 206 additions & 0 deletions src/cdemo/cdemo_tracing.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
#include "jaegertracing/Tracer.h"

#include "cdemo_tracing.h"

#include <iostream>
#include <sstream>

using std::string;

/*
* You'll want to load your configuration from a file
* or use the C++ programmatic configuration interfaces, but
* this is a demo:
*/

const string cfgstr {R"endyaml(
disabled: false
sampler:
type: const
param: 1
reporter:
queueSize: 100
bufferFlushInterval: 2
logSpans: false
localAgentHostPort: 127.0.0.1:6831
headers:
jaegerDebugHeader: debug-id
jaegerBaggageHeader: baggage
TraceContextHeaderName: trace-id
traceBaggageHeaderPrefix: "testctx-"
baggage_restrictions:
denyBaggageOnInitializationFailure: false
hostPort: 127.0.0.1:5778
refreshInterval: 60
)endyaml"};

/*
* You'll probably want a C++ class or set of classes to contain your trace
* functionality, logic around application scopes, etc. But we're thunking
* opentracing to C++ as thinly as we can, so:
*/
static std::shared_ptr<opentracing::Tracer> tracer;

extern "C" void
cdemo_tracing_start(void)
{
const auto config = jaegertracing::Config::parse(YAML::Load(cfgstr));
tracer = jaegertracing::Tracer::make("cdemo", config);
}

extern "C" void
cdemo_tracing_finish(void)
{
if (tracer)
{
tracer->Close();
tracer.reset();
}
}

/*
* You'll probably want multiple trace functions to take different arguments.
* Code generation for the thunks would be wise.
*
* You'll likely also need some integration with some sort of application
* context/scope information. Or a Span stack you keep in the tracing module.
*
* But for demo purposes we pass around the info manually.
*
* Look into StartSpanWithOptions for how to set up relationships dynamically,
* pass sets of tags generated from helper functions, etc.
*/
extern "C" CDemoTraceSpan*
cdemo_trace_start(CDemoTraceSpanContext *ctx,
const char *oprname, const char *something_for_a_tag)
{
assert(oprname != NULL);
auto span = tracer->StartSpan(oprname, {opentracing::ChildOf(ctx)});
if (something_for_a_tag)
span->SetTag("some_tag", something_for_a_tag);

/* It's a std::unique_ptr, and we're taking over memory management now */
return span.release();
}

/*
* Finishing a span will schedule it for sending to the trace collector. You
* may add additional tags before finishing if desired, as well as Log entries
* associated with the span, etc.
*/
extern "C" void
cdemo_trace_done(CDemoTraceSpan *span)
{
assert(span);
span->Finish();
delete span;
}

extern "C" char*
cdemo_trace_context_to_hex(CDemoTraceSpanContext *ctx)
{
std::stringstream ss (std::ios::out | std::ios::binary);
char *hexout, *hexout_it;

assert(tracer);

if (!ctx)
return nullptr;

/*
* Inject doesn't accept an output-itertor so we must copy the context
* into a stream.
*/
if (!tracer->Inject(*ctx, ss))
return nullptr;

/*
* We want the resulting memory to be free()able so malloc() the buffer. We'd
* have to make a copy at some point to hex-format it; if we used C++ streams
* we'd have to copy the resulting std::string's c_str() again.
*/
hexout = (char*)malloc(ss.str().length() * 2 + 1);
hexout_it = hexout;

for (auto it = ss.str().begin(); it != ss.str().end(); ++it)
{
/*
* Not efficient, but your real app won't be using hex strings,
* you'll be sending around binary or using base64 or using
* http headers or whatever.
*/
snprintf(hexout_it, 3, "%02hhX", (unsigned char)(*it));
hexout_it += 2;
}
*hexout_it = '\0';

return hexout;
}

extern "C" CDemoTraceSpanContext*
cdemo_trace_context_from_hex(const char *hexbuf)
{
std::stringstream ss (std::ios::out | std::ios::in | std::ios::binary);
const char * endhexbuf = strchr(hexbuf, '\0');

assert(tracer);

if (hexbuf == NULL)
return nullptr;

for ( ; hexbuf != endhexbuf; hexbuf += 2)
{
unsigned char val;
if (sscanf(hexbuf, "%hhX", &val) != 1)
{
assert(false);
std::cerr << "bad parse of " << hexbuf << " as hex" << std::endl;
return nullptr;
}
ss << val;
}

assert(ss.str().length() == 37);

ss.seekg(0);
auto ctx = tracer->Extract(ss);
if (!ctx)
{
assert(false);
return nullptr;
}

assert ((*ctx) != nullptr);

return (*ctx).release();
}

extern "C" CDemoTraceSpanContext*
cdemo_trace_context_from_span(CDemoTraceSpan *span)
{
/*
* This is a bit of an API defect in opentracing: it doesn't require a copy
* ctor for SpanContext, and the context returned from
* opentracing::Span::context() is returned by-value so it won't be valid
* outside this scope.
*/
assert(span);
jaegertracing::Span * const jspan = static_cast<jaegertracing::Span*>(span);
return new jaegertracing::SpanContext(jspan->context());
}

/*
* You MUST NOT free() memory allocated from C++. This is true even if there's
* no dtor, as it can corrupt the C and C++ runtime libraries' states on some
* platforms. But it'll also fail to fire any dtors. So just don't do it.
*
* (In fact, you must also avoid free()ing memory that was malloc()'d in
* another shared library on some platforms, so this is a good habit even
* without C/C++ mixing).
*/
extern "C" void
cdemo_trace_context_delete(CDemoTraceSpanContext* ctx)
{
if (ctx)
delete ctx;
}
Loading

0 comments on commit 0eaa705

Please sign in to comment.