Skip to content

Commit

Permalink
feat(tracing): Basic span support with nesting
Browse files Browse the repository at this point in the history
  • Loading branch information
relaxolotl committed Dec 23, 2021
1 parent 8b92955 commit 14ec096
Show file tree
Hide file tree
Showing 10 changed files with 457 additions and 18 deletions.
18 changes: 16 additions & 2 deletions examples/example.c
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ main(int argc, char **argv)
sentry_options_set_traces_sample_rate(options, 1.0);
}

if (has_arg(argc, argv, "child-spans")) {
sentry_options_set_max_spans(options, 5);
}

sentry_init(options);

if (!has_arg(argc, argv, "no-setup")) {
Expand Down Expand Up @@ -214,14 +218,24 @@ main(int argc, char **argv)

if (has_arg(argc, argv, "capture-transaction")) {
sentry_value_t tx_ctx
= sentry_value_new_transaction_context("I'm a little teapot",
= sentry_value_new_transaction_context("little.teapot",
"Short and stout here is my handle and here is my spout");

if (has_arg(argc, argv, "unsample-tx")) {
sentry_transaction_context_set_sampled(tx_ctx, 0);
}

sentry_value_t tx = sentry_transaction_start(tx_ctx);

if (has_arg(argc, argv, "child-spans")) {
sentry_value_t child_ctx = sentry_span_start_child(
sentry_value_new_null(), "littler.teapot", NULL);
sentry_value_t grandchild_ctx
= sentry_span_start_child(child_ctx, "littlest.teapot", NULL);

sentry_span_finish(&tx, grandchild_ctx);
sentry_span_finish(&tx, child_ctx);
}

sentry_transaction_finish(tx);
}

Expand Down
39 changes: 39 additions & 0 deletions include/sentry.h
Original file line number Diff line number Diff line change
Expand Up @@ -1335,6 +1335,45 @@ SENTRY_EXPERIMENTAL_API sentry_uuid_t sentry_transaction_finish(
SENTRY_EXPERIMENTAL_API sentry_value_t sentry_set_span(
sentry_value_t transaction);

/**
* Starts a new Span.
*
* Either the return value of `sentry_start_transaction`, `sentry_set_span`, or
* `sentry_span_start_child` may be passed in as `transaction_or_span`.
*
* Both operation and description can be null, but it is recommended to supply
* the former. See https://develop.sentry.dev/sdk/performance/span-operations/
* for conventions around operations.
*
* See https://develop.sentry.dev/sdk/event-payloads/span/ for a description of
* the created Span's properties and expectations for operation and description.
*
* Returns a value that should be passed into `sentry_span_finish`. Not
* finishing the Span means it will be discarded, and will not be sent to
* sentry. `sentry_value_null` will be returned if the child Span could not be
* created.
*/
SENTRY_EXPERIMENTAL_API sentry_value_t sentry_span_start_child(
sentry_value_t transaction_or_span, char *operation, char *description);

/**
* Finishes a Span.
*
* Returns a value that should be passed into `sentry_span_finish`. Not
* finishing the Span means it will be discarded, and will not be sent to
* sentry.
*
* `root_transaction` is either the parent Transaction of the Span, or
* the ancestor Transaction of the Span if the Span is not a direct descendant
* of a Transaction.
*
* This takes ownership of `span`, as child Spans must always occur within the
* total duration of a parent span and cannot take a longer amount of time to
* complete than the parent span they belong to.
*/
SENTRY_EXPERIMENTAL_API void sentry_span_finish(
sentry_value_t *root_transaction, sentry_value_t span);

#ifdef __cplusplus
}
#endif
Expand Down
132 changes: 132 additions & 0 deletions src/sentry_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,8 @@ sentry_transaction_finish(sentry_value_t tx)
sentry_value_set_by_key(tx, "timestamp",
sentry__value_new_string_owned(
sentry__msec_time_to_iso8601(sentry__msec_time())));
// TODO: This might not actually be necessary. Revisit after talking to
// the relay team about this.
sentry_value_set_by_key(tx, "level", sentry_value_new_string("info"));

// TODO(tracing): add tracestate
Expand Down Expand Up @@ -816,3 +818,133 @@ sentry_set_span(sentry_value_t transaction)
}
return sentinel;
}

sentry_value_t
sentry_span_start_child(
sentry_value_t transaction_or_span, char *operation, char *description)
{
size_t max_spans = SENTRY_SPANS_MAX;
SENTRY_WITH_OPTIONS (options) {
max_spans = options->max_spans;
}

sentry_value_t parent;
// Parent is transaction on scope.
bool tx_on_scope = sentry_value_get_type(transaction_or_span)
== SENTRY_VALUE_TYPE_STRING;
if (tx_on_scope) {
// Changes to spans on a transaction requires no flushing.
SENTRY_WITH_SCOPE_MUT_NO_FLUSH (scope) {
// There isn't an active transaction. This span has nothing to
// attach to.
if (sentry_value_is_null(scope->span)) {
SENTRY_DEBUG(
"no transaction available to create a child under");
return sentry_value_new_null();
}
parent = sentry__value_clone(scope->span);
}
} else {
parent = transaction_or_span;
}

// Aggressively discard spans if a transaction is unsampled to avoid
// wasting memory
sentry_value_t sampled = sentry_value_get_by_key(parent, "sampled");
if (!sentry_value_is_true(sampled)) {
SENTRY_DEBUG("span's parent is unsampled, not creating span");
return sentry_value_new_null();
}
sentry_value_t spans = sentry_value_get_by_key(parent, "spans");
// This only checks that the number of _completed_ spans matches the number
// of max spans. This means that the number of in-flight spans can exceed
// the max number of spans.
if (sentry_value_get_length(spans) >= max_spans) {
SENTRY_DEBUG("reached maximum number of spans for transaction, not "
"creating span");
return sentry_value_new_null();
}

sentry_value_t child = sentry__value_new_span(parent, operation);
sentry_value_set_by_key(
child, "description", sentry_value_new_string(description));
sentry_value_set_by_key(child, "start_timestamp",
sentry__value_new_string_owned(
sentry__msec_time_to_iso8601(sentry__msec_time())));
sentry_value_set_by_key(child, "sampled", sentry_value_new_bool(1));

if (tx_on_scope) {
sentry_value_decref(parent);
}

return child;

fail:
if (tx_on_scope) {
sentry_value_decref(parent);
}
return sentry_value_new_null();
}

void
sentry_span_finish(sentry_value_t *root_transaction, sentry_value_t span)
{
if (sentry_value_is_null(*root_transaction) || sentry_value_is_null(span)) {
SENTRY_DEBUG(
"missing root transaction or span to finish, aborting span finish");
goto fail;
}

// tough luck if this span actually doesn't belong on the specified
// transaction, i.e. its trace id doesn't match the root transaction's trace
// id
sentry_value_set_by_key(span, "timestamp",
sentry__value_new_string_owned(
sentry__msec_time_to_iso8601(sentry__msec_time())));
sentry_value_remove_by_key(span, "sampled");

size_t max_spans = SENTRY_SPANS_MAX;
SENTRY_WITH_OPTIONS (options) {
max_spans = options->max_spans;
}

if (sentry_value_get_type(*root_transaction) == SENTRY_VALUE_TYPE_STRING) {
// Changes to spans does not require a flush.
SENTRY_WITH_SCOPE_MUT_NO_FLUSH (scope) {
sentry_value_t spans
= sentry_value_get_by_key(scope->span, "spans");

if (sentry_value_get_length(spans) >= max_spans) {
SENTRY_DEBUG("reached maximum number of spans for transaction, "
"discarding span");
goto fail;
}

if (sentry_value_is_null(spans)) {
spans = sentry_value_new_list();
sentry_value_set_by_key(scope->span, "spans", spans);
}
sentry_value_append(spans, span);
}
} else {
sentry_value_t spans
= sentry_value_get_by_key(*root_transaction, "spans");

if (sentry_value_get_length(spans) >= max_spans) {
SENTRY_DEBUG("reached maximum number of spans for transaction, "
"discarding span");
goto fail;
}

if (sentry_value_is_null(spans)) {
spans = sentry_value_new_list();
sentry_value_set_by_key(*root_transaction, "spans", spans);
}
sentry_value_append(spans, span);
}
return;

fail:
sentry_value_decref(span);
return;
}
1 change: 1 addition & 0 deletions src/sentry_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "sentry_logger.h"

#define SENTRY_BREADCRUMBS_MAX 100
#define SENTRY_SPANS_MAX 1000

#if defined(__GNUC__) && (__GNUC__ >= 4)
# define MUST_USE __attribute__((warn_unused_result))
Expand Down
51 changes: 41 additions & 10 deletions src/sentry_tracing.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "sentry_sync.h"
#include "sentry_value.h"

sentry_value_t
sentry__span_get_trace_context(sentry_value_t span)
Expand All @@ -11,23 +12,53 @@ sentry__span_get_trace_context(sentry_value_t span)

sentry_value_t trace_context = sentry_value_new_object();

#define PLACE_VALUE(Key, Source) \
#define PLACE_CLONED_VALUE(Key, Source) \
do { \
sentry_value_t src = sentry_value_get_by_key(Source, Key); \
if (!sentry_value_is_null(src)) { \
sentry_value_incref(src); \
sentry_value_set_by_key(trace_context, Key, src); \
sentry_value_set_by_key( \
trace_context, Key, sentry__value_clone(src)); \
} \
} while (0)

PLACE_VALUE("trace_id", span);
PLACE_VALUE("span_id", span);
PLACE_VALUE("parent_span_id", span);
PLACE_VALUE("op", span);
PLACE_VALUE("description", span);
PLACE_VALUE("status", span);
PLACE_CLONED_VALUE("trace_id", span);
PLACE_CLONED_VALUE("span_id", span);
PLACE_CLONED_VALUE("parent_span_id", span);
PLACE_CLONED_VALUE("op", span);
PLACE_CLONED_VALUE("description", span);
PLACE_CLONED_VALUE("status", span);

// TODO: freeze this
return trace_context;

#undef PLACE_VALUE
#undef PLACE_CLONED_VALUE
}

sentry_value_t
sentry__span_get_span_context(sentry_value_t span)
{
if (sentry_value_is_null(span)
|| sentry_value_is_null(sentry_value_get_by_key(span, "trace_id"))
|| sentry_value_is_null(sentry_value_get_by_key(span, "span_id"))) {
return sentry_value_new_null();
}

sentry_value_t span_context = sentry_value_new_object();

#define PLACE_CLONED_VALUE(Key, Source) \
do { \
sentry_value_t src = sentry_value_get_by_key(Source, Key); \
if (!sentry_value_is_null(src)) { \
sentry_value_set_by_key( \
span_context, Key, sentry__value_clone(src)); \
} \
} while (0)

PLACE_CLONED_VALUE("trace_id", span);
PLACE_CLONED_VALUE("span_id", span);
PLACE_CLONED_VALUE("status", span);

return span_context;

#undef PLACE_CLONED_VALUE
}
1 change: 1 addition & 0 deletions src/sentry_tracing.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@
*/
sentry_value_t sentry__span_get_trace_context(sentry_value_t span);

sentry_value_t sentry__span_get_span_context(sentry_value_t span);
#endif
33 changes: 28 additions & 5 deletions src/sentry_value.c
Original file line number Diff line number Diff line change
Expand Up @@ -1124,19 +1124,42 @@ sentry_value_new_stacktrace(void **ips, size_t len)
return stacktrace;
}

sentry_value_t
sentry__value_new_span(sentry_value_t parent, const char *operation)
{
sentry_value_t span = sentry_value_new_object();

sentry_transaction_context_set_operation(span, operation);

sentry_uuid_t span_id = sentry_uuid_new_v4();
sentry_value_set_by_key(
span, "span_id", sentry__value_new_span_uuid(&span_id));

sentry_value_set_by_key(span, "status", sentry_value_new_string("ok"));

// Span creation is currently aggressively pruned prior to this function so
// once we're in here we definitely know that the span and its parent
// transaction are sampled.
if (!sentry_value_is_null(parent)) {
sentry_value_set_by_key(span, "trace_id",
sentry_value_get_by_key_owned(parent, "trace_id"));
sentry_value_set_by_key(span, "parent_span_id",
sentry_value_get_by_key_owned(parent, "span_id"));
}

return span;
}

sentry_value_t
sentry_value_new_transaction_context(const char *name, const char *operation)
{
sentry_value_t transaction_context = sentry_value_new_object();
sentry_value_t transaction_context
= sentry__value_new_span(sentry_value_new_null(), operation);

sentry_uuid_t trace_id = sentry_uuid_new_v4();
sentry_value_set_by_key(transaction_context, "trace_id",
sentry__value_new_internal_uuid(&trace_id));

sentry_uuid_t span_id = sentry_uuid_new_v4();
sentry_value_set_by_key(
transaction_context, "span_id", sentry__value_new_span_uuid(&span_id));

sentry_transaction_context_set_name(transaction_context, name);
sentry_transaction_context_set_operation(transaction_context, operation);

Expand Down
6 changes: 6 additions & 0 deletions src/sentry_value.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ sentry_value_t sentry__value_new_list_with_size(size_t size);
*/
sentry_value_t sentry__value_new_object_with_size(size_t size);

/**
* Constructs a new Span.
*/
sentry_value_t sentry__value_new_span(
sentry_value_t parent, const char *operation);

/**
* This will parse the Value into a UUID, or return a `nil` UUID on error.
* See also `sentry_uuid_from_string`.
Expand Down
Loading

0 comments on commit 14ec096

Please sign in to comment.