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 21, 2021

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent cbf156a commit d07e1f0
Showing 9 changed files with 349 additions and 17 deletions.
18 changes: 16 additions & 2 deletions examples/example.c
Original file line number Diff line number Diff line change
@@ -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")) {
@@ -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_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(grandchild_ctx);
sentry_span_finish(child_ctx);
}

sentry_transaction_finish();
}

53 changes: 50 additions & 3 deletions include/sentry.h
Original file line number Diff line number Diff line change
@@ -1295,17 +1295,64 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_remove_sampled(
* Starts a new Transaction based on the provided context, restored from an
* external integration (i.e. a span from a different SDK) or manually
* constructed by a user.
*
* `sentry_transaction_finish` should be called after this is invoked, otherwise
* the Transaction will not be sent to sentry. New spans cannot be created
* unless there exists an active Transaction.
*/
SENTRY_EXPERIMENTAL_API void sentry_transaction_start(
sentry_value_t transaction_context);

/**
* Finishes and sends the current transaction to sentry. The event ID of the
* transaction will be returned if this was successful; A nil UUID will be
* returned otherwise.
* Finishes and sends the current active Transaction to sentry. Any unfinished
* spans are removed from the Transaction before it is sent over.
*
* No new spans can be created after this is invoked unless a new Transaction is
* started via `sentry_transaction_start`.
*/
SENTRY_EXPERIMENTAL_API sentry_uuid_t sentry_transaction_finish();

/**
* Starts a new Span.
*
* If `parent_span` is `sentry_value_null`, then the current active Transaction
* is used as the parent for the new Span. An active Transaction must be created
* via `sentry_transaction_start` in order for the Span to be successfully
* created.
*
* If `parent_span` is another Span, it must belong to the current active
* Transaction in order for Span creation to succeed. This will take ownership
* of any `parent_span`s that do reference non-existent Spans in the current
* active Transaction.
*
* 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, and `parent_span`'s ownership
* will be taken if the child Span could not be created.
*/
SENTRY_EXPERIMENTAL_API sentry_value_t sentry_span_start_child(
sentry_value_t parent_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.
*
* 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 span);

#ifdef __cplusplus
}
#endif
88 changes: 88 additions & 0 deletions src/sentry_core.c
Original file line number Diff line number Diff line change
@@ -784,6 +784,8 @@ sentry_transaction_finish()
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"));

sentry_value_t name = sentry_value_get_by_key(tx, "transaction");
@@ -806,6 +808,92 @@ sentry_transaction_finish()
sentry_value_remove_by_key(tx, "description");
sentry_value_remove_by_key(tx, "status");

// TODO: prune unfinished child spans
// This decrefs for us, generates an event ID, merges scope
return sentry__capture_event(tx);
}

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

sentry_value_t child_span_context = sentry_value_new_null();
size_t span_count = 0;
SENTRY_WITH_SCOPE_MUT (scope) {
// There isn't an active transaction. This span has nothing to attach
// to.
if (sentry_value_is_null(scope->span)) {
return sentry_value_new_null();
}
// Aggressively discard spans if a transaction is unsampled to avoid
// wasting memory
sentry_value_t sampled
= sentry_value_get_by_key(scope->span, "sampled");
if (!sentry_value_is_true(sampled)) {
return sentry_value_new_null();
}
sentry_value_t spans = sentry_value_get_by_key(scope->span, "spans");
span_count = sentry_value_get_length(spans);
if (span_count >= max_spans) {
return sentry_value_new_null();
}
// TODO: if the parent span can't be found in the current active
// transaction, take ownership of the parent span context and return
// null.

sentry_value_t parent;
if (sentry_value_is_null(parent_span_context)) {
parent = scope->span;
} else {
parent = parent_span_context;
}

sentry_value_t child = sentry__value_new_span(parent, operation);
sentry_uuid_t span_id = sentry_uuid_new_v4();
sentry_value_set_by_key(
child, "span_id", sentry__value_new_span_uuid(&span_id));
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())));

if (sentry_value_is_null(spans)) {
spans = sentry_value_new_list();
sentry_value_set_by_key(scope->span, "spans", spans);
}
child_span_context = sentry__span_get_span_context(child);
sentry_value_append(spans, child);
}
sentry_value_set_by_key(
child_span_context, "index", sentry_value_new_int32((int)span_count));

return child_span_context;
}

void
sentry_span_finish(sentry_value_t span_context)
{
sentry_value_t sv_index = sentry_value_get_by_key(span_context, "index");
if (sentry_value_is_null(sv_index)) {
sentry_value_decref(span_context);
return;
}
int index = sentry_value_as_int32(sv_index);

SENTRY_WITH_SCOPE_MUT (scope) {
sentry_value_t spans = sentry_value_get_by_key(scope->span, "spans");
// TODO: maybe validate that to_update.span_id == span.span_id
sentry_value_t to_update = sentry_value_get_by_index(spans, index);
sentry_value_set_by_key(to_update, "timestamp",
sentry__value_new_string_owned(
sentry__msec_time_to_iso8601(sentry__msec_time())));
}

sentry_value_decref(span_context);
}
1 change: 1 addition & 0 deletions src/sentry_core.h
Original file line number Diff line number Diff line change
@@ -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))
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)
@@ -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
@@ -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
5 changes: 3 additions & 2 deletions src/sentry_value.c
Original file line number Diff line number Diff line change
@@ -1132,13 +1132,14 @@ sentry__value_new_span(sentry_value_t parent, const char *operation)
sentry_transaction_context_set_operation(span, operation);
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"));
sentry_value_set_by_key(
span, "sampled", sentry_value_get_by_key_owned(parent, "sampled"));
}

return span;
Loading

0 comments on commit d07e1f0

Please sign in to comment.