Skip to content

Commit

Permalink
feat: add traces_sampler (#1108)
Browse files Browse the repository at this point in the history
* add traces_sampler option

* update CHANGELOG.md

* introduced `sentry_traces_sampler_function` typedef

* added sampling context

* format

* eof newlines

* feat: pass sampling_context into sampling decision

* remove unnecessary commented include

* add memory freeing/incref-decref

* change custom sampling context type to sentry_value_t

* add decref

* removed unneeded creation function

* format

* set codeql runner to ubuntu-22.04

* take ownership of custom_sampling_ctx

* add parent sampling decision

* add traces_sampler and parent sampling decision tests

* decref

* change callback arg from struct to parameter list

* format

* mark unused parameters

* format

* cleanup test + docs

* apply PR feedback

* format

* cleanup example

* add getters for transaction_ctx name + operation

* remove (void) on transaction_ctx in example.c

* add transaction_ctx getter tests

* dont expose sampling_context_s in sentry.h

* change bool to int

* cleanup example.c

* refactor tx_cxt to tx_ctx convention

* change sampled_int init

---------

Co-authored-by: Mischan Toosarani-Hausberger <mischan@abovevacant.com>
  • Loading branch information
JoshuaMoelans and supervacuus authored Jan 14, 2025
1 parent 65c109d commit bce6e8b
Show file tree
Hide file tree
Showing 14 changed files with 397 additions and 171 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- Add support for Xbox Series X/S. ([#1100](https://github.com/getsentry/sentry-native/pull/1100))
- Add option to set debug log level. ([#1107](https://github.com/getsentry/sentry-native/pull/1107))
- Add `traces_sampler` ([#1108](https://github.com/getsentry/sentry-native/pull/1108))
- Provide support for C++17 compilers when using the `crashpad` backend. ([#1110](https://github.com/getsentry/sentry-native/pull/1110), [crashpad#116](https://github.com/getsentry/crashpad/pull/116), [mini_chromium#1](https://github.com/getsentry/mini_chromium/pull/1))

## 0.7.17
Expand Down
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ The example currently supports the following commands:
- `stack-overflow`: Provokes a stack-overflow.
- `http-proxy`: Uses a localhost `HTTP` proxy on port 8080.
- `socks5-proxy`: Uses a localhost `SOCKS5` proxy on port 1080.
- `capture-transaction`: Captures a transaction.
- `traces-sampler`: Installs a traces sampler callback function when used alongside `capture-transaction`.

Only on Windows using crashpad with its WER handler module:

Expand Down
36 changes: 35 additions & 1 deletion examples/example.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,32 @@
# define sleep_s(SECONDS) sleep(SECONDS)
#endif

static double
traces_sampler_callback(const sentry_transaction_context_t *transaction_ctx,
sentry_value_t custom_sampling_ctx, const int *parent_sampled)
{
if (parent_sampled != NULL) {
if (*parent_sampled) {
return 0.8; // high sample rate for children of sampled transactions
}
return 0; // parent is not sampled
}
if (strcmp(sentry_transaction_context_get_name(transaction_ctx),
"little.teapot")
== 0) {
if (strcmp(sentry_transaction_context_get_operation(transaction_ctx),
"Short and stout here is my handle and here is my spout")
== 0) {
if (sentry_value_as_int32(
sentry_value_get_by_key(custom_sampling_ctx, "b"))
== 42) {
return 1;
}
}
}
return 0;
}

static sentry_value_t
before_send_callback(sentry_value_t event, void *hint, void *closure)
{
Expand Down Expand Up @@ -234,6 +260,10 @@ main(int argc, char **argv)
options, discarding_on_crash_callback, NULL);
}

if (has_arg(argc, argv, "traces-sampler")) {
sentry_options_set_traces_sampler(options, traces_sampler_callback);
}

if (has_arg(argc, argv, "override-sdk-name")) {
sentry_options_set_sdk_name(options, "sentry.native.android.flutter");
}
Expand Down Expand Up @@ -405,8 +435,12 @@ main(int argc, char **argv)
if (has_arg(argc, argv, "unsample-tx")) {
sentry_transaction_context_set_sampled(tx_ctx, 0);
}

sentry_value_t custom_sampling_ctx = sentry_value_new_object();
sentry_value_set_by_key(
custom_sampling_ctx, "b", sentry_value_new_int32(42));
sentry_transaction_t *tx
= sentry_transaction_start(tx_ctx, sentry_value_new_null());
= sentry_transaction_start(tx_ctx, custom_sampling_ctx);

sentry_transaction_set_data(
tx, "url", sentry_value_new_string("https://example.com"));
Expand Down
62 changes: 43 additions & 19 deletions include/sentry.h
Original file line number Diff line number Diff line change
Expand Up @@ -1533,6 +1533,26 @@ SENTRY_EXPERIMENTAL_API void sentry_options_set_traces_sample_rate(
SENTRY_EXPERIMENTAL_API double sentry_options_get_traces_sample_rate(
sentry_options_t *opts);

/**
* A sentry Transaction Context.
*
* See Transaction Interface under
* https://develop.sentry.dev/sdk/performance/#new-span-and-transaction-classes
*/
struct sentry_transaction_context_s;
typedef struct sentry_transaction_context_s sentry_transaction_context_t;
typedef double (*sentry_traces_sampler_function)(
const sentry_transaction_context_t *transaction_ctx,
sentry_value_t custom_sampling_ctx, const int *parent_sampled);

/**
* Sets the traces sampler callback. Should be a function that returns a double
* and takes in a sentry_transaction_context_t pointer, a sentry_value_t for
* a custom sampling context and a int pointer for the parent sampled flag.
*/
SENTRY_EXPERIMENTAL_API void sentry_options_set_traces_sampler(
sentry_options_t *opts, sentry_traces_sampler_function callback);

#ifdef SENTRY_PLATFORM_LINUX

/**
Expand Down Expand Up @@ -1590,15 +1610,6 @@ SENTRY_EXPERIMENTAL_API void sentry_end_session_with_status(

/* -- Performance Monitoring/Tracing APIs -- */

/**
* A sentry Transaction Context.
*
* See Transaction Interface under
* https://develop.sentry.dev/sdk/performance/#new-span-and-transaction-classes
*/
struct sentry_transaction_context_s;
typedef struct sentry_transaction_context_s sentry_transaction_context_t;

/**
* A sentry Transaction.
*
Expand Down Expand Up @@ -1649,9 +1660,14 @@ sentry_transaction_context_new_n(const char *name, size_t name_len,
* setting a name on it.
*/
SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_name(
sentry_transaction_context_t *tx_cxt, const char *name);
sentry_transaction_context_t *tx_ctx, const char *name);
SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_name_n(
sentry_transaction_context_t *tx_cxt, const char *name, size_t name_len);
sentry_transaction_context_t *tx_ctx, const char *name, size_t name_len);
/**
* Gets the `name` of a Transaction Context.
*/
SENTRY_EXPERIMENTAL_API const char *sentry_transaction_context_get_name(
const sentry_transaction_context_t *tx_ctx);

/**
* Sets the `operation` on a Transaction Context, which will be used in the
Expand All @@ -1664,10 +1680,15 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_name_n(
* setting an operation on it.
*/
SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_operation(
sentry_transaction_context_t *tx_cxt, const char *operation);
sentry_transaction_context_t *tx_ctx, const char *operation);
SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_operation_n(
sentry_transaction_context_t *tx_cxt, const char *operation,
sentry_transaction_context_t *tx_ctx, const char *operation,
size_t operation_len);
/**
* Gets the `operation` of a Transaction Context.
*/
SENTRY_EXPERIMENTAL_API const char *sentry_transaction_context_get_operation(
const sentry_transaction_context_t *tx_ctx);

/**
* Sets the `sampled` field on a Transaction Context, which will be used in the
Expand All @@ -1681,7 +1702,7 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_operation_n(
* setting `sampled` on it.
*/
SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_sampled(
sentry_transaction_context_t *tx_cxt, int sampled);
sentry_transaction_context_t *tx_ctx, int sampled);

/**
* Removes the `sampled` field on a Transaction Context, which will be used in
Expand All @@ -1693,7 +1714,7 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_sampled(
* removing `sampled`.
*/
SENTRY_EXPERIMENTAL_API void sentry_transaction_context_remove_sampled(
sentry_transaction_context_t *tx_cxt);
sentry_transaction_context_t *tx_ctx);

/**
* Update the Transaction Context with the given HTTP header key/value pair.
Expand All @@ -1704,9 +1725,9 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_remove_sampled(
* upstream service.
*/
SENTRY_EXPERIMENTAL_API void sentry_transaction_context_update_from_header(
sentry_transaction_context_t *tx_cxt, const char *key, const char *value);
sentry_transaction_context_t *tx_ctx, const char *key, const char *value);
SENTRY_EXPERIMENTAL_API void sentry_transaction_context_update_from_header_n(
sentry_transaction_context_t *tx_cxt, const char *key, size_t key_len,
sentry_transaction_context_t *tx_ctx, const char *key, size_t key_len,
const char *value, size_t value_len);

/**
Expand Down Expand Up @@ -1735,14 +1756,17 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_update_from_header_n(
* Takes ownership of `transaction_context`. A Transaction Context cannot be
* modified or re-used after it is used to start a Transaction.
*
* Takes ownership of `custom_sampling_ctx`. A Sampling Context cannot be
* modified or re-used after it is used to start a Transaction.
*
* The returned value is not thread-safe. Users are expected to ensure that
* appropriate locking mechanisms are implemented over the Transaction if it
* needs to be mutated across threads. Methods operating on the Transaction will
* mention what kind of expectations they carry if they need to mutate or access
* the object in a thread-safe way.
*/
SENTRY_EXPERIMENTAL_API sentry_transaction_t *sentry_transaction_start(
sentry_transaction_context_t *tx_cxt, sentry_value_t sampling_ctx);
sentry_transaction_context_t *tx_ctx, sentry_value_t custom_sampling_ctx);
/**
* Also starts a transaction like the regular `sentry_transaction_start`
* function, but has an additional timestamp parameter to let the user provide
Expand All @@ -1751,7 +1775,7 @@ SENTRY_EXPERIMENTAL_API sentry_transaction_t *sentry_transaction_start(
* The timestamp should be provided in microseconds since the Unix epoch.
*/
SENTRY_EXPERIMENTAL_API sentry_transaction_t *sentry_transaction_start_ts(
sentry_transaction_context_t *tx_cxt, sentry_value_t sampling_ctx,
sentry_transaction_context_t *tx_ctx, sentry_value_t custom_sampling_ctx,
uint64_t timestamp);

/**
Expand Down
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ sentry_target_sources_cwd(sentry
sentry_random.h
sentry_ratelimiter.c
sentry_ratelimiter.h
sentry_sampling_context.h
sentry_scope.c
sentry_scope.h
sentry_session.c
Expand Down
73 changes: 46 additions & 27 deletions src/sentry_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -448,18 +448,39 @@ sentry__capture_event(sentry_value_t event)
}

bool
sentry__should_send_transaction(sentry_value_t tx_cxt)
{
sentry_value_t context_setting = sentry_value_get_by_key(tx_cxt, "sampled");
if (!sentry_value_is_null(context_setting)) {
return sentry_value_is_true(context_setting);
}

sentry__should_send_transaction(
sentry_value_t tx_ctx, sentry_sampling_context_t *sampling_ctx)
{
sentry_value_t context_setting = sentry_value_get_by_key(tx_ctx, "sampled");
bool sampled = sentry_value_is_null(context_setting)
? false
: sentry_value_is_true(context_setting);
sampling_ctx->parent_sampled
= sentry_value_is_null(context_setting) ? NULL : &sampled;

const int parent_sampled_int = sampling_ctx->parent_sampled
? (int)*sampling_ctx->parent_sampled
: -1; // -1 signifies no parent sampling decision
bool send = false;
SENTRY_WITH_OPTIONS (options) {
send = sentry__roll_dice(options->traces_sample_rate);
// TODO(tracing): Run through traces sampler function if rate is
// unavailable.
if (options->traces_sampler) {
const double result
= ((sentry_traces_sampler_function)options->traces_sampler)(
sampling_ctx->transaction_context,
sampling_ctx->custom_sampling_context,
sampling_ctx->parent_sampled == NULL ? NULL
: &parent_sampled_int);
send = sentry__roll_dice(result);
} else {
if (sampling_ctx->parent_sampled != NULL) {
send = *sampling_ctx->parent_sampled;
} else {
send = sentry__roll_dice(options->traces_sample_rate);
}
}
}
if (sampling_ctx->parent_sampled != NULL) {
sampling_ctx->parent_sampled = NULL;
}
return send;
}
Expand Down Expand Up @@ -840,32 +861,28 @@ sentry_set_level(sentry_level_t level)
}

sentry_transaction_t *
sentry_transaction_start(
sentry_transaction_context_t *opaque_tx_cxt, sentry_value_t sampling_ctx)
sentry_transaction_start(sentry_transaction_context_t *opaque_tx_ctx,
sentry_value_t custom_sampling_ctx)
{
return sentry_transaction_start_ts(
opaque_tx_cxt, sampling_ctx, sentry__usec_time());
opaque_tx_ctx, custom_sampling_ctx, sentry__usec_time());
}

sentry_transaction_t *
sentry_transaction_start_ts(sentry_transaction_context_t *opaque_tx_cxt,
sentry_value_t sampling_ctx, uint64_t timestamp)
sentry_transaction_start_ts(sentry_transaction_context_t *opaque_tx_ctx,
sentry_value_t custom_sampling_ctx, uint64_t timestamp)
{
// Just free this immediately until we implement proper support for
// traces_sampler.
sentry_value_decref(sampling_ctx);

if (!opaque_tx_cxt) {
if (!opaque_tx_ctx) {
return NULL;
}

sentry_value_t tx_cxt = opaque_tx_cxt->inner;
sentry_value_t tx_ctx = opaque_tx_ctx->inner;

// If the parent span ID is some empty-ish value, just remove it
sentry_value_t parent_span
= sentry_value_get_by_key(tx_cxt, "parent_span_id");
= sentry_value_get_by_key(tx_ctx, "parent_span_id");
if (sentry_value_get_length(parent_span) < 1) {
sentry_value_remove_by_key(tx_cxt, "parent_span_id");
sentry_value_remove_by_key(tx_ctx, "parent_span_id");
}

// The ending timestamp is stripped to avoid misleading ourselves later
Expand All @@ -874,17 +891,19 @@ sentry_transaction_start_ts(sentry_transaction_context_t *opaque_tx_cxt,
sentry_value_t tx = sentry_value_new_event();
sentry_value_remove_by_key(tx, "timestamp");

sentry__value_merge_objects(tx, tx_cxt);

bool should_sample = sentry__should_send_transaction(tx_cxt);
sentry__value_merge_objects(tx, tx_ctx);
sentry_sampling_context_t sampling_ctx
= { opaque_tx_ctx, custom_sampling_ctx, NULL };
bool should_sample = sentry__should_send_transaction(tx_ctx, &sampling_ctx);
sentry_value_set_by_key(
tx, "sampled", sentry_value_new_bool(should_sample));
sentry_value_decref(custom_sampling_ctx);

sentry_value_set_by_key(tx, "start_timestamp",
sentry__value_new_string_owned(
sentry__usec_time_to_iso8601(timestamp)));

sentry__transaction_context_free(opaque_tx_cxt);
sentry__transaction_context_free(opaque_tx_ctx);
return sentry__transaction_new(tx);
}

Expand Down
4 changes: 3 additions & 1 deletion src/sentry_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include "sentry_boot.h"
#include "sentry_logger.h"
#include "sentry_sampling_context.h"

#define SENTRY_BREADCRUMBS_MAX 100
#define SENTRY_SPANS_MAX 1000
Expand Down Expand Up @@ -117,7 +118,8 @@ void sentry__options_unlock(void);
// these for now are only needed outside of core for tests
#ifdef SENTRY_UNITTEST
bool sentry__roll_dice(double probability);
bool sentry__should_send_transaction(sentry_value_t tx_cxt);
bool sentry__should_send_transaction(
sentry_value_t tx_ctx, sentry_sampling_context_t *sampling_ctx);
#endif

#endif
7 changes: 7 additions & 0 deletions src/sentry_options.c
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,13 @@ sentry_options_get_traces_sample_rate(sentry_options_t *opts)
return opts->traces_sample_rate;
}

void
sentry_options_set_traces_sampler(
sentry_options_t *opts, sentry_traces_sampler_function callback)
{
opts->traces_sampler = callback;
}

void
sentry_options_set_backend(sentry_options_t *opts, sentry_backend_t *backend)
{
Expand Down
1 change: 1 addition & 0 deletions src/sentry_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ typedef struct sentry_options_s {

/* Experimentally exposed */
double traces_sample_rate;
sentry_traces_sampler_function traces_sampler;
size_t max_spans;

/* everything from here on down are options which are stored here but
Expand Down
13 changes: 13 additions & 0 deletions src/sentry_sampling_context.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// sentry_sampling_context.h
#ifndef SENTRY_SAMPLING_CONTEXT_H_INCLUDED
#define SENTRY_SAMPLING_CONTEXT_H_INCLUDED

#include "sentry_tracing.h"

typedef struct sentry_sampling_context_s {
sentry_transaction_context_t *transaction_context;
sentry_value_t custom_sampling_context;
bool *parent_sampled;
} sentry_sampling_context_t;

#endif // SENTRY_SAMPLING_CONTEXT_H_INCLUDED
Loading

0 comments on commit bce6e8b

Please sign in to comment.