From ce34056aea52c0d51be21a370599a7e9878d524c Mon Sep 17 00:00:00 2001 From: Nuno Luz Date: Mon, 27 Dec 2021 11:43:05 +0000 Subject: [PATCH] Upstream sentry-native (does not update crashpad and breakpad refs) (#15) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Add support for Qt 6 (#509) * fix: Windows SDK Compiler Warning (#511) * fix: Validate tm put into strftime (#510) * fix: Rewrite the Linux module finder (#431) It now works in memory, without requiring to mmap the libraries again, which should make it work correctly on android when loading libraries directly from apk or appbundle files. The new code will keep track of readable memory maps and will translate read requests based on the offset of those memory maps, making sure that we actually can read whatever we are trying to read. * build: Avoid building all targets (#512) It looks like cmake is broken and builds ALL the targets when the parallel option is specified first, lol * fix: Update Crashpad to 2021-04-12 and fix macOS universal build (#513) * feat: Invoke before_send hook when using Crashpad (#519) * feat: Add more Event Payload convenience methods (#517) Adds: * `sentry_value_new_exception` * `sentry_value_new_thread` * `sentry_value_new_stacktrace` * `sentry_event_add_exception` * `sentry_event_add_thread` Deprecates `sentry_event_value_add_stacktrace` * feat: Introduce `sentry_close` (#518) This replaces the former `sentry_shutdown`, which is being forwarded. * meta: Prepare Changelog for upcoming release (#522) * ref: Pass options to scope apply directly (#521) * fix: Further clean up platform libraries for static linking (#523) * fix: Better macOS availability checks (#524) This should allow building on older macOS versions as well as running on older versions by fixing the usage of __builtin_available, and adding a different clock source for older macOS versions. * release: 0.4.9 * fix: Avoid double-free on invalid DSN (#527) * meta: Use correct libunwindstack commit * fix: Allow for Unity builds (#536) * ref: Add more testcases that trigger crashes in various ways (#538) * ref(craft): Modernize Craft config (#543) * fix: Update venv and test-discovery Makefile targets (#544) * fix: Avoid recursion when using `sentry_reinstall_backend` (#548) Previously, the `inproc` and `crashpad` (on linux) backends didn’t correctly reset their signal handlers when doing `reinstall_backend` (or multiple `init` calls for that matter). This has led to an infinite loop generating crashes. The fix now correctly unregisters the inproc/crashpad signal handlers, and adds an integration test using `reinstall_backend` to make sure we do not end up in an infinite loop. Co-authored-by: Mischa Alff * fix: Address -Wundef warning for SENTRY_UNITTEST defines (#549) * build: Set 32-bit option for compiling assembly as well (#550) This fixes compilation of breakpad for 32-bit systems * meta: Update break/crashpad to 2021-06-14 (#552) * fix: Shorten/Split Locked sections to avoid deadlock (#551) We have received a report that the `sentry_get_modules_list` on mac can deadlock when other code concurrently does a `dlopen` and thus invokes the `add_image` callback from a different thread. We shorten/split the locked blocks in order to avoid holding a lock in the `get_modules` function whenever the `add_image` function is being invoked possibly from other threads. * fix: Tighten Stack Usage (#553) According to some docs, JVM/JNI stacks on Android can be as small as 32K, and our own sigaltstack is not much larger with 64K. Make sure to avoid large stack allocations as much as possible. We have especially seen the literal content of `/proc/self/maps` as well as formatted addresses inside corrupted release/environment attributes, which might point to overflows that write into a previously allocated release/environment string. * meta: Update Changelog (#556) * release: 0.4.10 * reformat * fix: Make Linux modulefinder/unwinder safer (#559) This is using the `process_vm_read` call to safely poke at random memory. It also makes sure to shim the libc provided call with a direct syscall for older Android devices. * docs: Try to better explain unwind API (#564) * fix: Make Crashpad Backend respect max_breadcrumbs setting (#566) * fix: Cancel slow winhttp requests on shutdown (#570) Co-authored-by: Gerhard Herbert * fix: Properly close the background worker thread on timeout (#571) * fix: Possible race conditions in init/close and sessions (#545) * meta: Draft Changelog (#572) * release: 0.4.11 * feat: Make shutdown timeout customizable (#577) Co-authored-by: Andrei Muraru * CMake: Link to the CURL::libcurl target when available (#579) Caters better for newer cmake versions. * meta: Update crashpad to 2021-07-14 (#580) * fix: Properly use `SENTRY_BUILD_RUNTIMESTATIC` for `sentry_fuzz_json` unit test (#583) * meta: Update break/crashpad to 2021-07-28 (#584) * release: 0.4.12 * fix: Increment CXX standard to 14 to allow crashpad build (#585) Fixes #574 * meta: Bump python dependencies (#600) The old version of pytest breaks with python 3.10 which changed a little how code object internals work. Since python 3.10 is now released it starts being used in CI. * fix: Ensure that a valid DSN has a public_key (#598) * feat: AIX support (#593) * CMake: Check whether libcurl was already found (#602) Currently when there is any other project that brings libcurl as a dependency, the build fails with “Could NOT find CURL (missing: CURL_LIBRARY CURL_INCLUDE_DIR)“, even though libcurl has already added as CURL::libcurl library. This patch adds a check for CURL_FOUND, to indicate that the library was already found, if set by another project. It also skips the additional find_package() step so it does not fail. Signed-off-by: Ladislav Macoun * CMake: fix `SENTRY_BACKEND` defined in outer scope (#603) * CMake: add ability to set solution folder name (#604) * [pull] master from getsentry:master (#14) * ci(codechecker): Workaround for code checker not building due to node issues (#615) * meta: Update breakpad/crashpad to 2021-12-03 (#614) * feat(tracing): Add config options (#613) * fix: Correct changelog entry (#622) * meta: Bump breakpad (#621) * feat: Add internal UUID types (#616) This adds in support for internal UUIDs needed by tracing, such as the trace ID and the span ID. The major difference between this and the "standard" UUID is that the hyphens are stripped during serialization. sentry appears to not consider the hyphenated representations of these UUIDs to be valid for certain fields in an event. * meta: Update changelog (#625) * release: 0.4.13 * feat(tracing): Groundwork to add tracing context to all events (#617) This adds the appropriate stubs and fields to start storing spans on the (universal) scope. No actual logic has been added to actually support setting spans on the scope itself. The focus of this is to begin including tracing info in the context on all events if there is a transaction set on the scope. It does this fairly naively right now as the tooling to merge `sentry_value_t`s are basically nonexistent. * ci: Make integration tests capable of reading the non-backwards compatible version number for Big Sur (#627) * feat(tracing): Basic transaction context creation (#619) This adds in the ability to create and manipulate transaction contexts as defined in https://develop.sentry.dev/sdk/performance/#new-span-and-transaction-classes, under Transaction Interface. Instead of defining several transaction constructor functions with varying names (since overloading doesn't exist), the decision has been made to have the user construct an "inactive" transaction which should be fed into the SDK's implementation of `start_transaction`. This follows an existing pattern in the SDK where exceptions, threads, messages, etc can be constructed but they must be explicitly added to an event to be sent to sentry. * feat(tracing): Support basic sampling of transactions (#620) If an event is a transaction, event flushing should determine discard or forward the transaction to sentry based on the sample rate as configured in sentry options. Follows the sampling rules as defined in https://develop.sentry.dev/sdk/performance/#sampling-context. This does not take into consideration parent sampling as that property is currently unimplemented on the transaction context. * feat(tracing): Introduce a helper that identifies events that are transactions (#628) * feat(tracing): Restrict `sentry_capture_event` so it only sends non-transaction events (#629) Prevent the public API from being used to send transaction events as another transaction-specific function is meant to be used to accomplish this. * fix: Avoid deadlocks with uninitialized options (#639) The `SENTRY_WITH_OPTIONS_MUT` was a footgun since it never unlocked when the options were NULL (uninitialized). This removes the macro and replaces its uses with explicit lock/unlock calls. * feat(tracing): Add in basic Envelope support for Transactions (#630) * feat(tracing): Allow manual creation and sending of spanless Transactions (#631) * feat(tracing): Defer some transaction validation and allow creation of internal spans (#633) Co-authored-by: relaxolotl <5597345+relaxolotl@users.noreply.github.com> Co-authored-by: Sebastian Zivota Co-authored-by: getsentry-bot Co-authored-by: Arpad Borsos Co-authored-by: Arpad Borsos Co-authored-by: Tor Arne Vestbø Co-authored-by: Arpad Borsos Co-authored-by: Luke Street Co-authored-by: getsentry-bot Co-authored-by: Sentry Bot Co-authored-by: Arpad Borsos Co-authored-by: bschatt <44769431+bschatt@users.noreply.github.com> Co-authored-by: Burak Yigit Kaya Co-authored-by: MikeRumplerSentry <85497711+MikeRumplerSentry@users.noreply.github.com> Co-authored-by: Mischa Alff Co-authored-by: Michał Janiszewski Co-authored-by: getsentry-bot Co-authored-by: Gerhard Herbert Co-authored-by: andrei-mu Co-authored-by: Andrei Muraru Co-authored-by: pastdue <30942300+past-due@users.noreply.github.com> Co-authored-by: Roshan Padaki Co-authored-by: mjvankampen Co-authored-by: Floris Bruynooghe Co-authored-by: Calvin Buckley Co-authored-by: Ladislav Co-authored-by: Mikhail Paulyshka Co-authored-by: pull[bot] <39814207+pull[bot]@users.noreply.github.com> Co-authored-by: relaxolotl <5597345+relaxolotl@users.noreply.github.com> Co-authored-by: Sebastian Zivota --- CHANGELOG.md | 26 ++ CMakeLists.txt | 51 +++- CONTRIBUTING.md | 13 +- Makefile | 4 + README.md | 10 + examples/example.c | 24 ++ include/sentry.h | 130 +++++++++- src/CMakeLists.txt | 6 + src/backends/sentry_backend_inproc.c | 2 + src/modulefinder/sentry_modulefinder_aix.c | 109 ++++++++ src/path/sentry_path_unix.c | 32 ++- src/sentry_core.c | 204 +++++++++++++-- src/sentry_core.h | 40 ++- src/sentry_database.c | 8 + src/sentry_envelope.c | 62 ++++- src/sentry_envelope.h | 6 + src/sentry_options.c | 55 +++- src/sentry_options.h | 4 + src/sentry_scope.c | 30 ++- src/sentry_scope.h | 12 + src/sentry_session.c | 16 +- src/sentry_sync.c | 5 +- src/sentry_sync.h | 16 ++ src/sentry_tracing.c | 33 +++ src/sentry_tracing.h | 14 ++ src/sentry_utils.c | 34 ++- src/sentry_uuid.c | 22 ++ src/sentry_uuid.h | 13 +- src/sentry_value.c | 95 +++++++ src/sentry_value.h | 21 ++ src/symbolizer/sentry_symbolizer_unix.c | 189 ++++++++++++++ src/unwinder/sentry_unwinder_libbacktrace.c | 10 +- tests/assertions.py | 4 +- tests/conditions.py | 9 +- tests/test_build_static.py | 2 +- tests/test_integration_http.py | 2 +- tests/unit/CMakeLists.txt | 2 + tests/unit/test_concurrency.c | 27 ++ tests/unit/test_envelopes.c | 32 +++ tests/unit/test_sampling.c | 36 +++ tests/unit/test_symbolizer.c | 12 + tests/unit/test_tracing.c | 262 ++++++++++++++++++++ tests/unit/test_unwinder.c | 12 +- tests/unit/test_utils.c | 35 +++ tests/unit/test_uuid.c | 22 +- tests/unit/tests.inc | 11 + 46 files changed, 1697 insertions(+), 67 deletions(-) create mode 100644 src/modulefinder/sentry_modulefinder_aix.c create mode 100644 src/sentry_tracing.c create mode 100644 src/sentry_tracing.h create mode 100644 tests/unit/test_sampling.c create mode 100644 tests/unit/test_tracing.c diff --git a/CHANGELOG.md b/CHANGELOG.md index eba5d6f3e..66ffbe090 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,31 @@ # Changelog +## 0.4.13 + +**Features** + +- Add client-side stackwalking on Linux, Windows, and macOS (disabled by default). +- CMake: add ability to set solution folder name. +- Add AIX support. + +**Fixes** + +- CMake: check whether libcurl was already found. +- Increment CXX standard version to 14 to allow crashpad to build. + +**Internal**: + +- Update Crashpad and Breakpad submodules to 2021-12-03. + +**Thank you**: + +Features, fixes and improvements in this release have been contributed by: + +- [@Mixaill](https://github.com/Mixaill) +- [@ladislavmacoun](https://github.com/ladislavmacoun) +- [@NattyNarwhal](https://github.com/NattyNarwhal) +- [@mjvankampen](https://github.com/mjvankampen) + ## 0.4.12 **Features**: diff --git a/CMakeLists.txt b/CMakeLists.txt index ac7311160..a98d63723 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,8 @@ set(CMAKE_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/sentry") if(CMAKE_SYSTEM_NAME STREQUAL "Linux") set(LINUX TRUE) +elseif(CMAKE_SYSTEM_NAME STREQUAL "AIX" OR CMAKE_SYSTEM_NAME STREQUAL "OS400") + set(AIX TRUE) endif() #setup sentry library type @@ -93,7 +95,7 @@ endif() if(WIN32) set(SENTRY_DEFAULT_TRANSPORT "winhttp") -elseif((APPLE AND NOT IOS) OR LINUX) +elseif((APPLE AND NOT IOS) OR LINUX OR AIX) set(SENTRY_DEFAULT_TRANSPORT "curl") else() set(SENTRY_DEFAULT_TRANSPORT "none") @@ -137,8 +139,10 @@ else() set(SENTRY_DEFAULT_BACKEND "inproc") endif() -set(SENTRY_BACKEND ${SENTRY_DEFAULT_BACKEND} CACHE STRING - "The sentry backend responsible for reporting crashes, can be either 'none', 'inproc', 'breakpad' or 'crashpad'.") +if(NOT DEFINED SENTRY_BACKEND) + set(SENTRY_BACKEND ${SENTRY_DEFAULT_BACKEND} CACHE STRING + "The sentry backend responsible for reporting crashes, can be either 'none', 'inproc', 'breakpad' or 'crashpad'.") +endif() if(SENTRY_BACKEND STREQUAL "crashpad") set(SENTRY_BACKEND_CRASHPAD TRUE) @@ -236,6 +240,10 @@ endif() set_target_properties(sentry PROPERTIES PUBLIC_HEADER "include/sentry.h") +if(DEFINED SENTRY_FOLDER) + set_target_properties(sentry PROPERTIES FOLDER ${SENTRY_FOLDER}) +endif() + # check size type include(CheckTypeSize) check_type_size("long" CMAKE_SIZEOF_LONG) @@ -257,8 +265,20 @@ else() endif() target_compile_definitions(sentry PRIVATE SIZEOF_LONG=${CMAKE_SIZEOF_LONG}) +# AIX needs libm for isnan used in test suite +if(CMAKE_SYSTEM_NAME STREQUAL "AIX" OR CMAKE_SYSTEM_NAME STREQUAL "OS400") + target_link_libraries(sentry PRIVATE m) +endif() +# On IBM i PASE, flock is in libutil. Here because "sentry" exists now. +if(CMAKE_SYSTEM_NAME STREQUAL "OS400") + target_link_libraries(sentry PRIVATE util) +endif() + if(SENTRY_TRANSPORT_CURL) - find_package(CURL REQUIRED) + if(NOT CURL_FOUND) # Some other lib might bring libcurl already + find_package(CURL REQUIRED) + endif() + if(TARGET CURL::libcurl) # Only available in cmake 3.12+ target_link_libraries(sentry PRIVATE CURL::libcurl) else() @@ -434,6 +454,20 @@ if(SENTRY_BACKEND_CRASHPAD) set_property(TARGET mini_chromium PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") endif() + if(DEFINED SENTRY_FOLDER) + set_target_properties(crashpad_client PROPERTIES FOLDER ${SENTRY_FOLDER}) + set_target_properties(crashpad_compat PROPERTIES FOLDER ${SENTRY_FOLDER}) + set_target_properties(crashpad_getopt PROPERTIES FOLDER ${SENTRY_FOLDER}) + set_target_properties(crashpad_handler PROPERTIES FOLDER ${SENTRY_FOLDER}) + set_target_properties(crashpad_handler_lib PROPERTIES FOLDER ${SENTRY_FOLDER}) + set_target_properties(crashpad_minidump PROPERTIES FOLDER ${SENTRY_FOLDER}) + set_target_properties(crashpad_snapshot PROPERTIES FOLDER ${SENTRY_FOLDER}) + set_target_properties(crashpad_tools PROPERTIES FOLDER ${SENTRY_FOLDER}) + set_target_properties(crashpad_util PROPERTIES FOLDER ${SENTRY_FOLDER}) + set_target_properties(crashpad_zlib PROPERTIES FOLDER ${SENTRY_FOLDER}) + set_target_properties(mini_chromium PROPERTIES FOLDER ${SENTRY_FOLDER}) + endif() + target_link_libraries(sentry PRIVATE $ $ @@ -462,6 +496,11 @@ elseif(SENTRY_BACKEND_BREAKPAD) target_link_libraries(sentry PRIVATE breakpad_client ) + + if(DEFINED SENTRY_FOLDER) + set_target_properties(breakpad_client PROPERTIES FOLDER ${SENTRY_FOLDER}) + endif() + if(NOT SENTRY_BUILD_SHARED_LIBS) sentry_install(TARGETS breakpad_client EXPORT sentry LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" @@ -538,5 +577,9 @@ if(SENTRY_BUILD_EXAMPLES) set_property(TARGET sentry_example PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") endif() + if(DEFINED SENTRY_FOLDER) + set_target_properties(sentry_example PROPERTIES FOLDER ${SENTRY_FOLDER}) + endif() + add_test(NAME sentry_example COMMAND sentry_example) endif() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0e388f56c..005c30435 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -37,7 +37,7 @@ be done manually. Creates a python virtualenv, and runs all the tests through `pytest`. -**Running integration-tests manually**: +**Running integration tests manually**: $ pytest --verbose --maxfail=1 --capture=no tests/ @@ -47,13 +47,20 @@ can also be invoked directly. The `maxfail` parameter will abort after the first failure, and `capture=no` will print the complete compiler output, and test log. -**Running unit-tests manually**: +**Running unit tests**: + + $ make test-unit + +Unit tests also have a dedicated `make` target, if they need to be run separately +from the integration tests. + +**Running unit tests manually**: $ cmake -B build -D CMAKE_RUNTIME_OUTPUT_DIRECTORY=$(pwd)/build $ cmake --build build --target sentry_test_unit $ ./build/sentry_test_unit -The unit-tests are a separate executable target and can be built and run on +The unit tests are a separate executable target and can be built and run on their own. ## How to interpret CI failures diff --git a/Makefile b/Makefile index cf14522f9..71e63094d 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,10 @@ build/sentry_test_unit: build test: update-test-discovery test-integration .PHONY: test +test-unit: update-test-discovery build/sentry_test_unit + ./build/sentry_test_unit +.PHONY: test-unit + test-integration: setup-venv .venv/bin/pytest tests --verbose .PHONY: test-integration diff --git a/README.md b/README.md index f3329adfb..8ddcb0fd4 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,7 @@ The SDK supports different features on the target platform: have the `curl` library available. On other platforms, library users need to implement their own transport, based on the `function transport` API. - **Crashpad Backend** is currently only supported on Linux, Windows and macOS. +- **Client-side stackwalking** is currently only supported on Linux, Windows, and macOS. ## Building and Installation @@ -277,6 +278,15 @@ Legend: - ✓ supported - unsupported +- `SENTRY_FOLDER` (Default: not defined): + Sets the sentry-native projects folder name for generators which support project hierarchy (like Microsoft Visual Studio). + To use this feature you need to enable hierarchy via [`USE_FOLDERS` property](https://cmake.org/cmake/help/latest/prop_gbl/USE_FOLDERS.html) + +- `CRASHPAD_ENABLE_STACKTRACE` (Default: OFF): + This enables client-side stackwalking when using the crashpad backend. Stack unwinding will happen on the client's machine + and the result will be submitted to Sentry attached to the generated minidump. + Note that this feature is still experimental. + ### Build Targets - `sentry`: This is the main library and the only default build target. diff --git a/examples/example.c b/examples/example.c index 1c1275975..8edca36fc 100644 --- a/examples/example.c +++ b/examples/example.c @@ -45,7 +45,14 @@ has_arg(int argc, char **argv, const char *arg) return false; } +#ifdef SENTRY_PLATFORM_AIX +// AIX has a null page mapped to the bottom of memory, which means null derefs +// don't segfault. try dereferencing the top of memory instead; the top nibble +// seems to be unusable. +static void *invalid_mem = (void *)0xFFFFFFFFFFFFFF9B; // -100 for memset +#else static void *invalid_mem = (void *)1; +#endif static void trigger_crash() @@ -86,6 +93,10 @@ main(int argc, char **argv) options, sentry_transport_new(print_envelope)); } + if (has_arg(argc, argv, "capture-transaction")) { + sentry_options_set_traces_sample_rate(options, 1.0); + } + sentry_init(options); if (!has_arg(argc, argv, "no-setup")) { @@ -201,6 +212,19 @@ main(int argc, char **argv) sentry_capture_event(event); } + if (has_arg(argc, argv, "capture-transaction")) { + sentry_value_t tx_ctx + = sentry_value_new_transaction_context("I'm a 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); + sentry_transaction_finish(tx); + } + // make sure everything flushes sentry_close(); diff --git a/include/sentry.h b/include/sentry.h index 163bce965..0ee1ad453 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -24,7 +24,7 @@ extern "C" { /* SDK Version */ #define SENTRY_SDK_NAME "sentry.native" -#define SENTRY_SDK_VERSION "0.4.12" +#define SENTRY_SDK_VERSION "0.4.13" #define SENTRY_SDK_USER_AGENT SENTRY_SDK_NAME "/" SENTRY_SDK_VERSION /* common platform detection */ @@ -46,6 +46,10 @@ extern "C" { #elif defined(__linux) || defined(__linux__) # define SENTRY_PLATFORM_LINUX # define SENTRY_PLATFORM_UNIX +#elif defined(_AIX) +/* IBM i PASE is also counted as AIX */ +# define SENTRY_PLATFORM_AIX +# define SENTRY_PLATFORM_UNIX #else # error unsupported platform #endif @@ -189,7 +193,7 @@ SENTRY_API sentry_value_t sentry_value_new_int32(int32_t value); SENTRY_API sentry_value_t sentry_value_new_double(double value); /** - * Creates a new boolen value. + * Creates a new boolean value. */ SENTRY_API sentry_value_t sentry_value_new_bool(int value); @@ -550,13 +554,21 @@ typedef struct sentry_envelope_s sentry_envelope_t; SENTRY_API void sentry_envelope_free(sentry_envelope_t *envelope); /** - * Given an envelope returns the embedded event if there is one. + * Given an Envelope, returns the embedded Event if there is one. * - * This returns a borrowed value to the event in the envelope. + * This returns a borrowed value to the Event in the Envelope. */ SENTRY_API sentry_value_t sentry_envelope_get_event( const sentry_envelope_t *envelope); +/** + * Given an Envelope, returns the embedded Transaction if there is one. + * + * This returns a borrowed value to the Transaction in the Envelope. + */ +SENTRY_EXPERIMENTAL_API sentry_value_t sentry_envelope_get_transaction( + const sentry_envelope_t *envelope); + /** * Serializes the envelope. * @@ -578,7 +590,7 @@ SENTRY_API int sentry_envelope_write_to_file( /** * The Sentry Client Options. * - * See https://docs.sentry.io/error-reporting/configuration/ + * See https://docs.sentry.io/platforms/native/configuration/ */ struct sentry_options_s; typedef struct sentry_options_s sentry_options_t; @@ -1137,7 +1149,8 @@ SENTRY_API void sentry_user_consent_reset(void); SENTRY_API sentry_user_consent_t sentry_user_consent_get(void); /** - * Sends a sentry event. + * Sends a sentry event. Returns a nil UUID if the event being passed in is a + * transaction; `sentry_transaction_finish` should be used to send transactions. */ SENTRY_API sentry_uuid_t sentry_capture_event(sentry_value_t event); @@ -1232,6 +1245,111 @@ SENTRY_API void sentry_start_session(void); */ SENTRY_API void sentry_end_session(void); +/** + * Sets the maximum number of spans that can be attached to a + * transaction. + */ +SENTRY_EXPERIMENTAL_API void sentry_options_set_max_spans( + sentry_options_t *opts, size_t max_spans); + +/** + * Gets the maximum number of spans that can be attached to a + * transaction. + */ +SENTRY_EXPERIMENTAL_API size_t sentry_options_get_max_spans( + sentry_options_t *opts); + +/** + * Sets the sample rate for transactions. Should be a double between + * `0.0` and `1.0`. Transactions will be randomly discarded during + * `sentry_transaction_finish` when the sample rate is < 1.0. + */ +SENTRY_EXPERIMENTAL_API void sentry_options_set_traces_sample_rate( + sentry_options_t *opts, double sample_rate); + +/** + * Returns the sample rate for transactions. + */ +SENTRY_EXPERIMENTAL_API double sentry_options_get_traces_sample_rate( + sentry_options_t *opts); + +/* -- Performance Monitoring/Tracing APIs -- */ + +/** + * Constructs a new Transaction Context. The returned value needs to be passed + * into `sentry_transaction_start` in order to be recorded and sent to sentry. + * + * See + * https://docs.sentry.io/platforms/native/enriching-events/transaction-name/ + * for an explanation of a Transaction's `name`, and + * https://develop.sentry.dev/sdk/performance/span-operations/ for conventions + * around an `operation`'s value. + * + * Also see https://develop.sentry.dev/sdk/event-payloads/transaction/#anatomy + * for an explanation of `operation`, in addition to other properties and + * actions that can be performed on a Transaction. + */ +SENTRY_EXPERIMENTAL_API sentry_value_t sentry_value_new_transaction_context( + const char *name, const char *operation); + +/** + * Sets the `name` on a Transaction Context, which will be used in the + * Transaction constructed off of the context. + */ +SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_name( + sentry_value_t transaction, const char *name); + +/** + * Sets the `operation` on a Transaction Context, which will be used in the + * Transaction constructed off of the context + * + * See https://develop.sentry.dev/sdk/performance/span-operations/ for + * conventions on `operation`s. + */ +SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_operation( + sentry_value_t transaction, const char *operation); + +/** + * Sets the `sampled` field on a Transaction Context, which will be used in the + * Transaction constructed off of the context. + * + * When passed any value above 0, the Transaction will bypass all sampling + * options and always be sent to sentry. If passed 0, this Transaction and its + * child spans will never be sent to sentry. + */ +SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_sampled( + sentry_value_t transaction, int sampled); + +/** + * Removes the sampled field on a Transaction Context, which will be used in the + * Transaction constructed off of the context. + * + * The Transaction will use the sampling rate as defined in `sentry_options`. + */ +SENTRY_EXPERIMENTAL_API void sentry_transaction_context_remove_sampled( + sentry_value_t transaction); + +/** + * 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. + * + * Takes ownership of `transaction_context`. + */ +SENTRY_EXPERIMENTAL_API sentry_value_t sentry_transaction_start( + sentry_value_t transaction_context); + +/** + * Finishes and sends a transaction to sentry. The event ID of the transaction + * will be returned if this was successful; A nil UUID will be returned + * otherwise. + * + * Always takes ownership of `transaction`, regardless of whether the operation + * was successful or not. + */ +SENTRY_EXPERIMENTAL_API sentry_uuid_t sentry_transaction_finish( + sentry_value_t transaction); + #ifdef __cplusplus } #endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6f8a7967e..536acd573 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -34,6 +34,8 @@ sentry_target_sources_cwd(sentry sentry_symbolizer.h sentry_sync.c sentry_sync.h + sentry_tracing.c + sentry_tracing.h sentry_transport.c sentry_transport.h sentry_utils.c @@ -81,6 +83,10 @@ elseif(LINUX OR ANDROID) sentry_target_sources_cwd(sentry modulefinder/sentry_modulefinder_linux.c ) +elseif(AIX) + sentry_target_sources_cwd(sentry + modulefinder/sentry_modulefinder_aix.c + ) endif() # transport diff --git a/src/backends/sentry_backend_inproc.c b/src/backends/sentry_backend_inproc.c index c15493dab..2318e2774 100644 --- a/src/backends/sentry_backend_inproc.c +++ b/src/backends/sentry_backend_inproc.c @@ -260,6 +260,8 @@ handle_ucontext(const sentry_ucontext_t *uctx) sentry_envelope_t *envelope = sentry__prepare_event(options, event, NULL); + // TODO(tracing): Revisit when investigating transaction flushing during + // hard crashes. sentry_session_t *session = sentry__end_current_session_with_status( SENTRY_SESSION_STATUS_CRASHED); diff --git a/src/modulefinder/sentry_modulefinder_aix.c b/src/modulefinder/sentry_modulefinder_aix.c new file mode 100644 index 000000000..9b2900e59 --- /dev/null +++ b/src/modulefinder/sentry_modulefinder_aix.c @@ -0,0 +1,109 @@ +#include "sentry_boot.h" + +#include "sentry_core.h" +#include "sentry_string.h" +#include "sentry_sync.h" +#include "sentry_value.h" + +#include +#include + +#define __XCOFF64__ +#include +#include + +/* library filename + ( + member file name + ) + NUL */ +#define AIX_PRINTED_LIB_LEN ((PATH_MAX * 2) + 3) + +static bool g_initialized = false; +static sentry_mutex_t g_mutex = SENTRY__MUTEX_INIT; +static sentry_value_t g_modules = { 0 }; + +static void +load_modules(void) +{ + char buf[10000]; + int r = loadquery(L_GETINFO, buf, 10000); + if (r == -1) { + return; + } + /* The loader info structures are also a linked list. */ + struct ld_info *cur = (struct ld_info *)buf; + do { + sentry_value_t module = sentry_value_new_object(); + sentry_value_set_by_key( + module, "type", sentry_value_new_string("xcoff")); + + char *tb = (char *)cur->ldinfo_textorg; // text includes XCOFF image + sentry_value_set_by_key( + module, "image_addr", sentry__value_new_addr((uint64_t)tb)); + // actually a 64-bit value on 64-bit AIX + uint64_t ts = (uint64_t)cur->ldinfo_textsize; + sentry_value_set_by_key( + module, "image_size", sentry_value_new_int32((uint32_t)ts)); + + /* + * Under AIX, there are no UUIDs for executables, but we can try to + * use some other fields as an ersatz substitute. + */ + FILHDR *xcoff_header = (FILHDR *)tb; + char timestamp[128]; + snprintf(timestamp, 128, "%x", xcoff_header->f_timdat); + sentry_value_set_by_key( + module, "debug_id", sentry_value_new_string(timestamp)); + + /* library filename + ( + member + ) + NUL */ + char libname[AIX_PRINTED_LIB_LEN]; + char *file_part = cur->ldinfo_filename; + char *member_part = file_part + strlen(file_part) + 1; + /* + * This can't be a const char*, because it exists from + * a stack allocated buffer. Also append the member. + * + * XXX: See if we can't frob usla's memory ranges for + * const strings; but is quite difficult. + */ + if (member_part[0] == '\0') { + /* Not an archive, just copy the file name. */ + snprintf(libname, AIX_PRINTED_LIB_LEN, "%s", file_part); + } else { + /* It's an archive with member. */ + snprintf( + libname, AIX_PRINTED_LIB_LEN, "%s(%s)", file_part, member_part); + } + // XXX: This is not an absolute path because AIX doesn't provide + // it. It will have the member name for library archives. + sentry_value_set_by_key( + module, "code_file", sentry_value_new_string(libname)); + + sentry_value_append(g_modules, module); + + cur = (struct ld_info *)((char *)cur + cur->ldinfo_next); + } while (cur->ldinfo_next != 0); +} + +sentry_value_t +sentry_get_modules_list(void) +{ + sentry__mutex_lock(&g_mutex); + if (!g_initialized) { + g_modules = sentry_value_new_list(); + g_initialized = true; + load_modules(); + sentry_value_freeze(g_modules); + } + sentry_value_t modules = g_modules; + sentry_value_incref(modules); + sentry__mutex_unlock(&g_mutex); + return modules; +} + +void +sentry_clear_modulecache(void) +{ + sentry__mutex_lock(&g_mutex); + sentry_value_decref(g_modules); + g_modules = sentry_value_new_null(); + g_initialized = false; + sentry__mutex_unlock(&g_mutex); +} diff --git a/src/path/sentry_path_unix.c b/src/path/sentry_path_unix.c index d92393114..041a23b41 100644 --- a/src/path/sentry_path_unix.c +++ b/src/path/sentry_path_unix.c @@ -19,6 +19,10 @@ # include #endif +#ifdef SENTRY_PLATFORM_AIX +# include +#endif + // only read this many bytes to memory ever static const size_t MAX_READ_TO_BUFFER = 134217728; @@ -50,7 +54,15 @@ sentry__filelock_try_lock(sentry_filelock_t *lock) { lock->is_locked = false; - int fd = open(lock->path->path, O_RDONLY | O_CREAT | O_TRUNC, + const int oflags = +#ifdef SENTRY_PLATFORM_AIX + // Under AIX, O_TRUNC can only be set if it can be written to, and + // flock (well, fcntl) will return EBADF if the fd is not read-write. + O_RDWR | O_CREAT | O_TRUNC; +#else + O_RDONLY | O_CREAT | O_TRUNC; +#endif + int fd = open(lock->path->path, oflags, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); if (fd < 0) { return false; @@ -130,6 +142,18 @@ sentry__path_current_exe(void) } buf[len] = 0; return sentry__path_from_str(buf); +#elif defined(SENTRY_PLATFORM_AIX) + // You can't get the full path to the current executable; the best is + // either argv[0], or getting the name of the current executable, which + // doesn't even include a path. Let's go with that for now. + // (Actually, AIX may be able to under procfs, but it's System V style, + // not like Linux. And it's not available under PASE anyways.) + struct procentry64 proc; + pid_t pid = getpid(); + if (getprocs64(&proc, sizeof(proc), NULL, 0, &pid, 1) < 1) { + return NULL; + } + return sentry__path_from_str(proc.pi_comm); #endif return NULL; } @@ -307,7 +331,7 @@ sentry__path_create_dir_all(const sentry_path_t *path) #define _TRY_MAKE_DIR \ do { \ int mrv = mkdir(p, 0700); \ - if (mrv != 0 && errno != EEXIST) { \ + if (mrv != 0 && errno != EEXIST && errno != EINVAL) { \ rv = 1; \ goto done; \ } \ @@ -452,7 +476,9 @@ write_buffer_with_flags( int fd = open( path->path, flags, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); if (fd < 0) { - SENTRY_TRACEF("failed to open file \"%s\" for writing", path->path); + SENTRY_TRACEF( + "failed to open file \"%s\" for writing (errno %d, flags %x)", + path->path, errno, flags); return 1; } diff --git a/src/sentry_core.c b/src/sentry_core.c index 2904ce7c2..f29575fc1 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -16,6 +16,7 @@ #include "sentry_session.h" #include "sentry_string.h" #include "sentry_sync.h" +#include "sentry_tracing.h" #include "sentry_transport.h" #include "sentry_value.h" @@ -353,36 +354,82 @@ event_is_considered_error(sentry_value_t event) return false; } +bool +sentry__event_is_transaction(sentry_value_t event) +{ + sentry_value_t event_type = sentry_value_get_by_key(event, "type"); + return sentry__string_eq("transaction", sentry_value_as_string(event_type)); +} + sentry_uuid_t sentry_capture_event(sentry_value_t event) +{ + if (sentry__event_is_transaction(event)) { + return sentry_uuid_nil(); + } else { + return sentry__capture_event(event); + } +} + +sentry_uuid_t +sentry__capture_event(sentry_value_t event) { sentry_uuid_t event_id; sentry_envelope_t *envelope = NULL; bool was_captured = false; + bool was_sent = false; SENTRY_WITH_OPTIONS (options) { was_captured = true; - envelope = sentry__prepare_event(options, event, &event_id); + if (sentry__event_is_transaction(event)) { + envelope = sentry__prepare_transaction(options, event, &event_id); + } else { + envelope = sentry__prepare_event(options, event, &event_id); + } if (envelope) { if (options->session) { - SENTRY_WITH_OPTIONS_MUT (mut_options) { - sentry__envelope_add_session( - envelope, mut_options->session); - // we're assuming that if a session is added to an envelope - // it will be sent onwards. This means we now need to set - // the init flag to false because we're no longer the - // initial session update. - mut_options->session->init = false; - } + sentry_options_t *mut_options = sentry__options_lock(); + sentry__envelope_add_session(envelope, mut_options->session); + // we're assuming that if a session is added to an envelope + // it will be sent onwards. This means we now need to set + // the init flag to false because we're no longer the + // initial session update. + mut_options->session->init = false; + sentry__options_unlock(); } - sentry__capture_envelope(options->transport, envelope); + was_sent = true; } } if (!was_captured) { sentry_value_decref(event); } - return was_captured ? event_id : sentry_uuid_nil(); + return was_sent ? event_id : sentry_uuid_nil(); +} + +bool +sentry__roll_dice(double probability) +{ + uint64_t rnd; + return probability >= 1.0 || sentry__getrandom(&rnd, sizeof(rnd)) + || ((double)rnd / (double)UINT64_MAX) <= probability; +} + +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); + } + + 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. + } + return send; } sentry_envelope_t * @@ -395,9 +442,8 @@ sentry__prepare_event(const sentry_options_t *options, sentry_value_t event, sentry__record_errors_on_current_session(1); } - uint64_t rnd; - if (options->sample_rate < 1.0 && !sentry__getrandom(&rnd, sizeof(rnd)) - && ((double)rnd / (double)UINT64_MAX) > options->sample_rate) { + bool should_skip = !sentry__roll_dice(options->sample_rate); + if (should_skip) { SENTRY_DEBUG("throwing away event due to sample rate"); goto fail; } @@ -452,6 +498,36 @@ sentry__prepare_event(const sentry_options_t *options, sentry_value_t event, return NULL; } +sentry_envelope_t * +sentry__prepare_transaction(const sentry_options_t *options, + sentry_value_t transaction, sentry_uuid_t *event_id) +{ + sentry_envelope_t *envelope = NULL; + + SENTRY_WITH_SCOPE (scope) { + SENTRY_TRACE("merging scope into event"); + // Don't include debugging info + sentry_scope_mode_t mode = SENTRY_SCOPE_ALL & ~SENTRY_SCOPE_MODULES + & ~SENTRY_SCOPE_STACKTRACES; + sentry__scope_apply_to_event(scope, options, transaction, mode); + } + + sentry__ensure_event_id(transaction, event_id); + envelope = sentry__envelope_new(); + if (!envelope || !sentry__envelope_add_transaction(envelope, transaction)) { + goto fail; + } + + // TODO(tracing): Revisit when adding attachment support for transactions. + + return envelope; + +fail: + sentry_envelope_free(envelope); + sentry_value_decref(transaction); + return NULL; +} + void sentry_handle_exception(const sentry_ucontext_t *uctx) { @@ -493,12 +569,12 @@ void sentry_set_user(sentry_value_t user) { if (!sentry_value_is_null(user)) { - SENTRY_WITH_OPTIONS_MUT (options) { - if (options->session) { - sentry__session_sync_user(options->session, user); - sentry__run_write_session(options->run, options->session); - } + sentry_options_t *options = sentry__options_lock(); + if (options && options->session) { + sentry__session_sync_user(options->session, user); + sentry__run_write_session(options->run, options->session); } + sentry__options_unlock(); } SENTRY_WITH_SCOPE_MUT (scope) { @@ -633,3 +709,91 @@ sentry_set_level(sentry_level_t level) scope->level = level; } } + +sentry_value_t +sentry_transaction_start(sentry_value_t tx_cxt) +{ + // TODO: it would be nice if we could just merge tx_cxt into tx. + // `sentry_value_new_transaction_event()` is also an option, but risks + // causing more confusion as there's already a + // `sentry_value_new_transaction`. The ending timestamp is stripped as well + // to avoid misleading ourselves later down the line. + sentry_value_t tx = sentry_value_new_event(); + sentry_value_remove_by_key(tx, "timestamp"); + + // TODO(tracing): stuff transaction into the scope + bool should_sample = sentry__should_send_transaction(tx_cxt); + sentry_value_set_by_key( + tx, "sampled", sentry_value_new_bool(should_sample)); + + // Avoid having this show up in the payload at all if it doesn't have a + // valid value + sentry_value_t parent_span + = sentry_value_get_by_key_owned(tx_cxt, "parent_span_id"); + if (sentry_value_get_length(parent_span) > 0) { + sentry_value_set_by_key(tx, "parent_span_id", parent_span); + } else { + sentry_value_decref(parent_span); + } + sentry_value_set_by_key( + tx, "trace_id", sentry_value_get_by_key_owned(tx_cxt, "trace_id")); + sentry_value_set_by_key( + tx, "span_id", sentry_value_get_by_key_owned(tx_cxt, "trace_id")); + sentry_value_set_by_key(tx, "transaction", + sentry_value_get_by_key_owned(tx_cxt, "transaction")); + sentry_value_set_by_key( + tx, "status", sentry_value_get_by_key_owned(tx_cxt, "status")); + sentry_value_set_by_key(tx, "start_timestamp", + sentry__value_new_string_owned( + sentry__msec_time_to_iso8601(sentry__msec_time()))); + + sentry_value_decref(tx_cxt); + + return tx; +} + +sentry_uuid_t +sentry_transaction_finish(sentry_value_t tx) +{ + // The sampling decision should already be made for transactions during + // their construction. No need to recalculate here. See + // `sentry__should_skip_transaction`. + sentry_value_t sampled = sentry_value_get_by_key(tx, "sampled"); + if (!sentry_value_is_null(sampled) && !sentry_value_is_true(sampled)) { + SENTRY_DEBUG("throwing away transaction due to sample rate or " + "user-provided sampling value in transaction context"); + sentry_value_decref(tx); + // TODO(tracing): remove from scope + return sentry_uuid_nil(); + } + + sentry_value_set_by_key(tx, "type", sentry_value_new_string("transaction")); + sentry_value_set_by_key(tx, "timestamp", + sentry__value_new_string_owned( + sentry__msec_time_to_iso8601(sentry__msec_time()))); + sentry_value_set_by_key(tx, "level", sentry_value_new_string("info")); + + sentry_value_t name = sentry_value_get_by_key(tx, "transaction"); + if (sentry_value_is_null(name) || sentry_value_get_length(name) == 0) { + sentry_value_set_by_key(tx, "transaction", + sentry_value_new_string("")); + } + + // TODO: add tracestate + sentry_value_t trace_context = sentry__span_get_trace_context(tx); + sentry_value_t contexts = sentry_value_new_object(); + sentry_value_set_by_key(contexts, "trace", trace_context); + sentry_value_set_by_key(tx, "contexts", contexts); + + // clean up trace context fields + sentry_value_remove_by_key(tx, "trace_id"); + sentry_value_remove_by_key(tx, "span_id"); + sentry_value_remove_by_key(tx, "parent_span_id"); + sentry_value_remove_by_key(tx, "op"); + sentry_value_remove_by_key(tx, "description"); + sentry_value_remove_by_key(tx, "status"); + + // This takes ownership of the transaction, generates an event ID, merges + // scope + return sentry__capture_event(tx); +} diff --git a/src/sentry_core.h b/src/sentry_core.h index 8040ef140..542691921 100644 --- a/src/sentry_core.h +++ b/src/sentry_core.h @@ -29,7 +29,14 @@ bool sentry__should_skip_upload(void); /** - * Convert the given event into an envelope. + * Given a well-formed event, returns whether an event is a transaction or not. + * Defaults to false, which will also be returned if the event is malformed. + */ +bool sentry__event_is_transaction(sentry_value_t event); + +/** + * Convert the given event into an envelope. This assumes that the event + * being passed in is not a transaction. * * More specifically, it will do the following things: * - sample the event, possibly discarding it, @@ -45,6 +52,29 @@ bool sentry__should_skip_upload(void); sentry_envelope_t *sentry__prepare_event(const sentry_options_t *options, sentry_value_t event, sentry_uuid_t *event_id); +/** + * Sends a sentry event, regardless of its type. + */ +sentry_uuid_t sentry__capture_event(sentry_value_t event); + +/** + * Convert the given transaction into an envelope. This assumes that the + * event being passed in is a transaction. + * + * It will do the following things: + * - discard the transaction if it is unsampled + * - apply the scope to the transaction + * - add the transaction to a new envelope + * - add any attachments to the envelope + * + * The function will ensure the transaction has a UUID and write it into the + * `event_id` out-parameter. This takes ownership of the transaction, which + * means that the caller no longer needs to call `sentry_value_decref` on the + * transaction. + */ +sentry_envelope_t *sentry__prepare_transaction(const sentry_options_t *options, + sentry_value_t transaction, sentry_uuid_t *event_id); + /** * This function will submit the `envelope` to the given `transport`, first * checking for consent. @@ -84,8 +114,10 @@ void sentry__options_unlock(void); for (const sentry_options_t *Options = sentry__options_getref(); Options; \ sentry_options_free((sentry_options_t *)Options), Options = NULL) -#define SENTRY_WITH_OPTIONS_MUT(Options) \ - for (sentry_options_t *Options = sentry__options_lock(); Options; \ - sentry__options_unlock(), Options = NULL) +// these for now are only needed for tests +#ifdef SENTRY_UNITTEST +bool sentry__roll_dice(double probability); +bool sentry__should_send_transaction(sentry_value_t tx_cxt); +#endif #endif diff --git a/src/sentry_database.c b/src/sentry_database.c index c1d823179..a2c146358 100644 --- a/src/sentry_database.c +++ b/src/sentry_database.c @@ -169,6 +169,14 @@ sentry__process_old_runs(const sentry_options_t *options, uint64_t last_crash) sentry__filelock_free(lock); continue; } + // make sure we don't delete ourselves if the lock check fails +#ifdef SENTRY_PLATFORM_WINDOWS + if (wcscmp(options->run->run_path->path, run_dir->path) == 0) { +#else + if (strcmp(options->run->run_path->path, run_dir->path) == 0) { +#endif + continue; + } sentry_pathiter_t *run_iter = sentry__path_iter_directory(run_dir); const sentry_path_t *file; while ((file = sentry__pathiter_next(run_iter)) != NULL) { diff --git a/src/sentry_envelope.c b/src/sentry_envelope.c index 71cef6ed6..7ea05ad98 100644 --- a/src/sentry_envelope.c +++ b/src/sentry_envelope.c @@ -41,6 +41,9 @@ envelope_add_item(sentry_envelope_t *envelope) if (envelope->contents.items.item_count >= SENTRY_MAX_ENVELOPE_ITEMS) { return NULL; } + // TODO: Envelopes may have at most one event item or one transaction item, + // and not one of both. Some checking should be done here or in + // `sentry__envelope_add_[transaction|event]` to ensure this can't happen. sentry_envelope_item_t *rv = &envelope->contents.items @@ -197,7 +200,25 @@ sentry_envelope_get_event(const sentry_envelope_t *envelope) return sentry_value_new_null(); } for (size_t i = 0; i < envelope->contents.items.item_count; i++) { - if (!sentry_value_is_null(envelope->contents.items.items[i].event)) { + if (!sentry_value_is_null(envelope->contents.items.items[i].event) + && !sentry__event_is_transaction( + envelope->contents.items.items[i].event)) { + return envelope->contents.items.items[i].event; + } + } + return sentry_value_new_null(); +} + +sentry_value_t +sentry_envelope_get_transaction(const sentry_envelope_t *envelope) +{ + if (envelope->is_raw) { + return sentry_value_new_null(); + } + for (size_t i = 0; i < envelope->contents.items.item_count; i++) { + if (!sentry_value_is_null(envelope->contents.items.items[i].event) + && sentry__event_is_transaction( + envelope->contents.items.items[i].event)) { return envelope->contents.items.items[i].event; } } @@ -234,6 +255,45 @@ sentry__envelope_add_event(sentry_envelope_t *envelope, sentry_value_t event) return item; } +sentry_envelope_item_t * +sentry__envelope_add_transaction( + sentry_envelope_t *envelope, sentry_value_t transaction) +{ + sentry_envelope_item_t *item = envelope_add_item(envelope); + if (!item) { + return NULL; + } + + sentry_jsonwriter_t *jw = sentry__jsonwriter_new(NULL); + if (!jw) { + return NULL; + } + + sentry_value_t event_id = sentry__ensure_event_id(transaction, NULL); + + item->event = transaction; + sentry__jsonwriter_write_value(jw, transaction); + item->payload = sentry__jsonwriter_into_string(jw, &item->payload_len); + + sentry__envelope_item_set_header( + item, "type", sentry_value_new_string("transaction")); + sentry_value_t length = sentry_value_new_int32((int32_t)item->payload_len); + sentry__envelope_item_set_header(item, "length", length); + + sentry_value_incref(event_id); + sentry__envelope_set_header(envelope, "event_id", event_id); + +#ifdef SENTRY_UNITTEST + sentry_value_t now = sentry_value_new_string("2021-12-16T05:53:59.343Z"); +#else + sentry_value_t now = sentry__value_new_string_owned( + sentry__msec_time_to_iso8601(sentry__msec_time())); +#endif + sentry__envelope_set_header(envelope, "sent_at", now); + + return item; +} + sentry_envelope_item_t * sentry__envelope_add_session( sentry_envelope_t *envelope, const sentry_session_t *session) diff --git a/src/sentry_envelope.h b/src/sentry_envelope.h index ac5c69136..b5a8f1ab0 100644 --- a/src/sentry_envelope.h +++ b/src/sentry_envelope.h @@ -36,6 +36,12 @@ sentry_uuid_t sentry__envelope_get_event_id(const sentry_envelope_t *envelope); sentry_envelope_item_t *sentry__envelope_add_event( sentry_envelope_t *envelope, sentry_value_t event); +/** + * Add a transaction to this envelope. + */ +sentry_envelope_item_t *sentry__envelope_add_transaction( + sentry_envelope_t *envelope, sentry_value_t transaction); + /** * Add a session to this envelope. */ diff --git a/src/sentry_options.c b/src/sentry_options.c index 190c4234b..df10cf956 100644 --- a/src/sentry_options.c +++ b/src/sentry_options.c @@ -38,7 +38,10 @@ sentry_options_new(void) opts->auto_session_tracking = true; opts->system_crash_reporter_enabled = false; opts->symbolize_stacktraces = -#ifdef SENTRY_PLATFORM_ANDROID + // AIX doesn't have reliable debug IDs for server-side symbolication, + // and the diversity of Android makes it infeasible to have access to debug + // files. +#if defined(SENTRY_PLATFORM_ANDROID) || defined(SENTRY_PLATFORM_AIX) true; #else false; @@ -48,6 +51,9 @@ sentry_options_new(void) opts->sample_rate = 1.0; opts->refcount = 1; opts->shutdown_timeout = SENTRY_DEFAULT_SHUTDOWN_TIMEOUT; + + opts->traces_sample_rate = 0.0; + opts->max_spans = 0; return opts; } @@ -383,3 +389,50 @@ sentry_options_set_database_pathw(sentry_options_t *opts, const wchar_t *path) opts->database_path = sentry__path_from_wstr(path); } #endif + +/** + * Sets the maximum number of spans that can be attached to a + * transaction. + */ +void +sentry_options_set_max_spans(sentry_options_t *opts, size_t max_spans) +{ + opts->max_spans = max_spans; +} + +/** + * Gets the maximum number of spans that can be attached to a + * transaction. + */ +size_t +sentry_options_get_max_spans(sentry_options_t *opts) +{ + return opts->max_spans; +} + +/** + * Sets the sample rate for transactions. Should be a double between + * `0.0` and `1.0`. Transactions will be randomly discarded during + * `sentry_transaction_finish` when the sample rate is < 1.0. + */ +void +sentry_options_set_traces_sample_rate( + sentry_options_t *opts, double sample_rate) +{ + + if (sample_rate < 0.0) { + sample_rate = 0.0; + } else if (sample_rate > 1.0) { + sample_rate = 1.0; + } + opts->traces_sample_rate = sample_rate; +} + +/** + * Returns the sample rate for transactions. + */ +double +sentry_options_get_traces_sample_rate(sentry_options_t *opts) +{ + return opts->traces_sample_rate; +} diff --git a/src/sentry_options.h b/src/sentry_options.h index 0bc935258..228d9ef16 100644 --- a/src/sentry_options.h +++ b/src/sentry_options.h @@ -56,6 +56,10 @@ typedef struct sentry_options_s { sentry_event_function_t before_send_func; void *before_send_data; + /* Experimentally exposed */ + double traces_sample_rate; + size_t max_spans; + /* everything from here on down are options which are stored here but not exposed through the options API */ struct sentry_backend_s *backend; diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 5f8992e7e..3edf49888 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -7,6 +7,7 @@ #include "sentry_string.h" #include "sentry_symbolizer.h" #include "sentry_sync.h" +#include "sentry_tracing.h" #include #ifdef SENTRY_BACKEND_CRASHPAD @@ -73,6 +74,7 @@ get_scope(void) g_scope.breadcrumbs = sentry_value_new_list(); g_scope.level = SENTRY_LEVEL_ERROR; g_scope.client_sdk = get_client_sdk(); + g_scope.span = sentry_value_new_null(); g_scope_initialized = true; @@ -93,6 +95,7 @@ sentry__scope_cleanup(void) sentry_value_decref(g_scope.contexts); sentry_value_decref(g_scope.breadcrumbs); sentry_value_decref(g_scope.client_sdk); + sentry_value_decref(g_scope.span); } sentry__mutex_unlock(&g_lock); } @@ -115,7 +118,7 @@ sentry__scope_flush_unlock() { sentry__scope_unlock(); SENTRY_WITH_OPTIONS (options) { - // we try to unlock the scope/session lock as soon as possible. The + // we try to unlock the scope as soon as possible. The // backend will do its own `WITH_SCOPE` internally. if (options->backend && options->backend->flush_scope_func) { options->backend->flush_scope_func(options->backend, options); @@ -224,6 +227,14 @@ sentry__symbolize_stacktrace(sentry_value_t stacktrace) } } +void +sentry__scope_set_span(sentry_value_t span) +{ + // TODO: implement this function and get rid of this line. + (void)span; + return; +} + void sentry__scope_apply_to_event(const sentry_scope_t *scope, const sentry_options_t *options, sentry_value_t event, @@ -257,7 +268,8 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, PLACE_STRING("dist", options->dist); PLACE_STRING("environment", options->environment); - if (IS_NULL("level")) { + // is not transaction and has no level + if (IS_NULL("type") && IS_NULL("level")) { SET("level", sentry__value_new_level(scope->level)); } @@ -269,7 +281,15 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, // TODO: these should merge PLACE_CLONED_VALUE("tags", scope->tags); PLACE_CLONED_VALUE("extra", scope->extra); - PLACE_CLONED_VALUE("contexts", scope->contexts); + + // TODO: better, more thorough deep merging + sentry_value_t contexts = sentry__value_clone(scope->contexts); + sentry_value_t trace = sentry__span_get_trace_context(scope->span); + if (!sentry_value_is_null(trace)) { + sentry_value_set_by_key(contexts, "trace", trace); + } + PLACE_VALUE("contexts", contexts); + sentry_value_decref(contexts); if (mode & SENTRY_SCOPE_BREADCRUMBS) { PLACE_CLONED_VALUE("breadcrumbs", scope->breadcrumbs); @@ -288,7 +308,9 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, sentry__foreach_stacktrace(event, sentry__symbolize_stacktrace); } +#undef PLACE_CLONED_VALUE +#undef PLACE_VALUE #undef PLACE_STRING -#undef IS_NULL #undef SET +#undef IS_NULL } diff --git a/src/sentry_scope.h b/src/sentry_scope.h index 250ff8200..393cf349d 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -19,6 +19,12 @@ typedef struct sentry_scope_s { sentry_value_t breadcrumbs; sentry_level_t level; sentry_value_t client_sdk; + // Not to be confused with transaction, which is a legacy value. This is + // also known as a transaction, but to maintain consistency with other SDKs + // and to avoid a conflict with the existing transaction field this is named + // span. Whenever possible, `transaction` should pull its value from the + // `name` property nested in this field. + sentry_value_t span; } sentry_scope_t; /** @@ -69,6 +75,12 @@ void sentry__scope_apply_to_event(const sentry_scope_t *scope, const sentry_options_t *options, sentry_value_t event, sentry_scope_mode_t mode); +/** + * Sets the span (actually transaction) on the scope. An internal way to pass + * around contextual information needed from a transaction into other events. + */ +void sentry__scope_set_span(sentry_value_t span); + /** * These are convenience macros to automatically lock/unlock a scope inside a * code block. diff --git a/src/sentry_session.c b/src/sentry_session.c index f58a88e88..1aa7dc796 100644 --- a/src/sentry_session.c +++ b/src/sentry_session.c @@ -215,35 +215,39 @@ sentry_start_session(void) { sentry_end_session(); SENTRY_WITH_SCOPE (scope) { - SENTRY_WITH_OPTIONS_MUT (options) { + sentry_options_t *options = sentry__options_lock(); + if (options) { options->session = sentry__session_new(); if (options->session) { sentry__session_sync_user(options->session, scope->user); sentry__run_write_session(options->run, options->session); } } + sentry__options_unlock(); } } void sentry__record_errors_on_current_session(uint32_t error_count) { - SENTRY_WITH_OPTIONS_MUT (options) { - if (options->session) { - options->session->errors += error_count; - } + sentry_options_t *options = sentry__options_lock(); + if (options && options->session) { + options->session->errors += error_count; } + sentry__options_unlock(); } static sentry_session_t * sentry__end_session_internal(void) { sentry_session_t *session = NULL; - SENTRY_WITH_OPTIONS_MUT (options) { + sentry_options_t *options = sentry__options_lock(); + if (options) { session = options->session; options->session = NULL; sentry__run_clear_session(options->run); } + sentry__options_unlock(); if (session && session->status == SENTRY_SESSION_STATUS_OK) { session->status = SENTRY_SESSION_STATUS_EXITED; diff --git a/src/sentry_sync.c b/src/sentry_sync.c index 3b8746beb..b2e566c4b 100644 --- a/src/sentry_sync.c +++ b/src/sentry_sync.c @@ -74,8 +74,11 @@ sentry__thread_setname(sentry_threadid_t thread_id, const char *thread_name) return 1; } return pthread_setname_np(thread_name); -# else +# elif defined(SENTRY_PLATFORM_LINUX) /* and possibly others (like BSDs) */ return pthread_setname_np(thread_id, thread_name); +# else + /* XXX: AIX doesn't have it, but PASE does via ILE APIs. */ + return 0; # endif } #endif diff --git a/src/sentry_sync.h b/src/sentry_sync.h index 5c3cbe3ef..96e353b86 100644 --- a/src/sentry_sync.h +++ b/src/sentry_sync.h @@ -252,6 +252,22 @@ typedef pthread_cond_t sentry_cond_t; } # endif # define SENTRY__MUTEX_INIT PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP +# elif defined(SENTRY_PLATFORM_AIX) +// AIX lacks PTHREAD_RECURSIVE_MUTEX_INITIALIZER, though it does have at least +// PTHREAD_MUTEX_INITIALIZER. Unfortunately, it means you must call the mutex +// init function with an initialized mutexattrs set to recursive. This isn't +// workable due to all the mutexes just sitting around not initialized but +// immediately used. The fields are basically guesswork from what happens when +// you initialize the mutex "properly" but changing the bare minimum from a +// static initialization. (Don't ask me what these fields mean, the struct is +// opaquely defined as long[n] fields.) +# define SENTRY__MUTEX_INIT \ + { \ + { \ + 0, 0, 0, 0, /*_PTH_FLAGS_INIT64*/ 1, \ + PTHREAD_MUTEX_RECURSIVE \ + } \ + } # else # define SENTRY__MUTEX_INIT PTHREAD_RECURSIVE_MUTEX_INITIALIZER # endif diff --git a/src/sentry_tracing.c b/src/sentry_tracing.c new file mode 100644 index 000000000..73e23c0d8 --- /dev/null +++ b/src/sentry_tracing.c @@ -0,0 +1,33 @@ +#include "sentry_sync.h" + +sentry_value_t +sentry__span_get_trace_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 trace_context = sentry_value_new_object(); + +#define PLACE_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); \ + } \ + } 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); + + return trace_context; + +#undef PLACE_VALUE +} diff --git a/src/sentry_tracing.h b/src/sentry_tracing.h new file mode 100644 index 000000000..d99afe828 --- /dev/null +++ b/src/sentry_tracing.h @@ -0,0 +1,14 @@ +#ifndef SENTRY_TRACING_H_INCLUDED +#define SENTRY_TRACING_H_INCLUDED + +#include "sentry_boot.h" +#include "sentry_value.h" + +/** + * Returns an object containing tracing information extracted from a + * transaction (/span) which should be included in an event. + * See https://develop.sentry.dev/sdk/event-payloads/transaction/#examples + */ +sentry_value_t sentry__span_get_trace_context(sentry_value_t span); + +#endif diff --git a/src/sentry_utils.c b/src/sentry_utils.c index 8e3300871..65f2a2415 100644 --- a/src/sentry_utils.c +++ b/src/sentry_utils.c @@ -267,7 +267,10 @@ sentry__dsn_new(const char *raw_dsn) *tmp = 0; dsn->path = url.path; url.path = NULL; - dsn->is_valid = true; + + if (dsn->public_key && dsn->host && dsn->path) { + dsn->is_valid = true; + } exit: sentry__url_cleanup(&url); @@ -434,6 +437,29 @@ sentry__iso8601_to_msec(const char *iso) tm.tm_sec = s; #ifdef SENTRY_PLATFORM_WINDOWS time_t time = _mkgmtime(&tm); +#elif defined(SENTRY_PLATFORM_AIX) + /* + * timegm is a GNU extension that AIX doesn't support. We'll have to fake + * it by setting TZ instead w/ mktime, then unsets it. Changes global env. + */ + time_t time; + char *tz_env; + tz_env = getenv("TZ"); + if (tz_env) { + /* make a copy of it, since it'll change when we set it to UTC */ + tz_env = strdup(tz_env); + } + setenv("TZ", "UTC", 1); + tzset(); + time = mktime(&tm); + /* revert */ + if (tz_env) { + setenv("TZ", tz_env, 1); + free(tz_env); + } else { + unsetenv("TZ"); + } + tzset(); #else time_t time = timegm(&tm); #endif @@ -476,7 +502,8 @@ sentry__strtod_c(const char *ptr, char **endptr) { #ifdef SENTRY_PLATFORM_WINDOWS return _strtod_l(ptr, endptr, c_locale()); -#elif defined(SENTRY_PLATFORM_ANDROID) || defined(SENTRY_PLATFORM_IOS) +#elif defined(SENTRY_PLATFORM_ANDROID) || defined(SENTRY_PLATFORM_IOS) \ + || defined(SENTRY_PLATFORM_AIX) return strtod(ptr, endptr); #else return strtod_l(ptr, endptr, c_locale()); @@ -492,7 +519,8 @@ sentry__snprintf_c(char *buf, size_t buf_size, const char *fmt, ...) int rv; #ifdef SENTRY_PLATFORM_WINDOWS rv = _vsnprintf_l(buf, buf_size, fmt, c_locale(), args); -#elif defined(SENTRY_PLATFORM_ANDROID) || defined(SENTRY_PLATFORM_IOS) +#elif defined(SENTRY_PLATFORM_ANDROID) || defined(SENTRY_PLATFORM_IOS) \ + || defined(SENTRY_PLATFORM_AIX) rv = vsnprintf(buf, buf_size, fmt, args); #elif defined(SENTRY_PLATFORM_DARWIN) rv = vsnprintf_l(buf, buf_size, c_locale(), fmt, args); diff --git a/src/sentry_uuid.c b/src/sentry_uuid.c index 1ebcd929b..ad5e34999 100644 --- a/src/sentry_uuid.c +++ b/src/sentry_uuid.c @@ -1,6 +1,7 @@ #include "sentry_boot.h" #include "sentry_random.h" +#include "sentry_uuid.h" #include #include @@ -101,6 +102,27 @@ sentry_uuid_as_string(const sentry_uuid_t *uuid, char str[37]) #undef B } +void +sentry__internal_uuid_as_string(const sentry_uuid_t *uuid, char str[37]) +{ +#define B(X) (unsigned char)uuid->bytes[X] + snprintf(str, 33, + "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%" + "02hhx%02hhx%02hhx%02hhx%02hhx%02hhx", + B(0), B(1), B(2), B(3), B(4), B(5), B(6), B(7), B(8), B(9), B(10), + B(11), B(12), B(13), B(14), B(15)); +#undef B +} + +void +sentry__span_uuid_as_string(const sentry_uuid_t *uuid, char str[17]) +{ +#define B(X) (unsigned char)uuid->bytes[X] + snprintf(str, 17, "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx", B(0), + B(1), B(2), B(3), B(4), B(5), B(6), B(7)); +#undef B +} + #ifdef SENTRY_PLATFORM_WINDOWS sentry_uuid_t sentry__uuid_from_native(const GUID *guid) diff --git a/src/sentry_uuid.h b/src/sentry_uuid.h index aacd6bbe6..00a17a27d 100644 --- a/src/sentry_uuid.h +++ b/src/sentry_uuid.h @@ -3,11 +3,22 @@ #include "sentry_boot.h" +/** + * Converts a sentry UUID to a string representation used for internal + * sentry UUIDs such as event IDs. + */ +void sentry__internal_uuid_as_string(const sentry_uuid_t *uuid, char str[37]); + +/** + * Converts a sentry UUID to a string representation used for span IDs. + */ +void sentry__span_uuid_as_string(const sentry_uuid_t *uuid, char str[17]); + #ifdef SENTRY_PLATFORM_WINDOWS /** * Create a new UUID from the windows-native GUID type. */ -sentry_uuid_t sentry__uuid_from_native(GUID *guid); +sentry_uuid_t sentry__uuid_from_native(const GUID *guid); #endif #endif diff --git a/src/sentry_value.c b/src/sentry_value.c index 0fb51df69..162648866 100644 --- a/src/sentry_value.c +++ b/src/sentry_value.c @@ -24,6 +24,7 @@ #include "sentry_string.h" #include "sentry_sync.h" #include "sentry_utils.h" +#include "sentry_uuid.h" #include "sentry_value.h" /** @@ -973,6 +974,30 @@ sentry__value_new_hexstring(const uint8_t *bytes, size_t len) return sentry__value_new_string_owned(buf); } +sentry_value_t +sentry__value_new_span_uuid(const sentry_uuid_t *uuid) +{ + char *buf = sentry_malloc(17); + if (!buf) { + return sentry_value_new_null(); + } + sentry__span_uuid_as_string(uuid, buf); + buf[16] = '\0'; + return sentry__value_new_string_owned(buf); +} + +sentry_value_t +sentry__value_new_internal_uuid(const sentry_uuid_t *uuid) +{ + char *buf = sentry_malloc(33); + if (!buf) { + return sentry_value_new_null(); + } + sentry__internal_uuid_as_string(uuid, buf); + buf[32] = '\0'; + return sentry__value_new_string_owned(buf); +} + sentry_value_t sentry__value_new_uuid(const sentry_uuid_t *uuid) { @@ -1099,6 +1124,76 @@ 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_value_set_by_key(span, "status", sentry_value_new_string("ok")); + + 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; +} + +sentry_value_t +sentry_value_new_transaction_context(const char *name, const char *operation) +{ + sentry_value_t transaction_context + = sentry__value_new_span(sentry_value_new_null(), operation); + sentry_transaction_context_set_name(transaction_context, name); + + 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); + + return transaction_context; +} + +void +sentry_transaction_context_set_name( + sentry_value_t transaction_context, const char *name) +{ + sentry_value_set_by_key( + transaction_context, "transaction", sentry_value_new_string(name)); +} + +void +sentry_transaction_context_set_operation( + sentry_value_t transaction_context, const char *operation) +{ + sentry_value_set_by_key( + transaction_context, "op", sentry_value_new_string(operation)); +} + +void +sentry_transaction_context_set_sampled( + sentry_value_t transaction_context, int sampled) +{ + sentry_value_set_by_key( + transaction_context, "sampled", sentry_value_new_bool(sampled)); +} + +void +sentry_transaction_context_remove_sampled(sentry_value_t transaction_context) +{ + sentry_value_remove_by_key(transaction_context, "sampled"); +} + static sentry_value_t sentry__get_or_insert_values_list(sentry_value_t parent, const char *key) { diff --git a/src/sentry_value.h b/src/sentry_value.h index 375cb70ca..cba785364 100644 --- a/src/sentry_value.h +++ b/src/sentry_value.h @@ -25,6 +25,20 @@ sentry_value_t sentry__value_new_addr(uint64_t addr); */ sentry_value_t sentry__value_new_hexstring(const uint8_t *bytes, size_t len); +/** + * Creates a new String Value from the `uuid` that conforms to + * the structure of a span ID. + * See also `sentry__span_uuid_as_string`. + */ +sentry_value_t sentry__value_new_span_uuid(const sentry_uuid_t *uuid); + +/** + * Creates a new String Value from the `uuid` in a form meant for + * ingestion as an internal ID. + * See also `sentry_internal_uuid_as_string`. + */ +sentry_value_t sentry__value_new_internal_uuid(const sentry_uuid_t *uuid); + /** * Creates a new String Value from the `uuid`. * See also `sentry_uuid_as_string`. @@ -47,6 +61,13 @@ 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`. diff --git a/src/symbolizer/sentry_symbolizer_unix.c b/src/symbolizer/sentry_symbolizer_unix.c index b4cbe453c..54e4e3f87 100644 --- a/src/symbolizer/sentry_symbolizer_unix.c +++ b/src/symbolizer/sentry_symbolizer_unix.c @@ -5,6 +5,187 @@ #include #include +/* XXX: Break into a separate file */ +#ifdef SENTRY_PLATFORM_AIX +/* + * AIX doesn't have dladdr and we must go through hoops instead. What we'll do + * instead is reimplement most of what dladdr does from what loader information + * AIX provides us. The following code is ripped from mono, but is under the + * same license, and I am also the author of it. + */ + +# include +# include +# include +# include + +/* AIX specific headers for loadquery and traceback structure */ +# include +# include + +/* library filename + ( + member file name + ) + NUL */ +# define AIX_PRINTED_LIB_LEN ((PATH_MAX * 2) + 3) + +/* + * The structure that holds information for dladdr. Unfortunately, on AIX, + * the information returned by loadquery lives in an allocated buffer, so it + * should be freed when no longer needed. Note that sname /is/ still constant + * (it points to the traceback info in the image), so don't free it. + */ +typedef struct dl_info { + // these aren't const* because they are allocated + char *dli_fname; + void *dli_fbase; + char *dli_sname; + void *dli_saddr; +} Dl_info; + +/** + * Gets the base address and name of a symbol. + * + * This uses the traceback table at the function epilogue to get the base + * address and the name of a symbol. As such, this means that the input must + * be a word-aligned address within the text section. + * + * The way to support non-text (data/bss/whatever) would be to use an XCOFF + * parser on the image loaded in memory and snarf its symbol table. However, + * that is much more complex, and presumably, most addresses passed would be + * code in the text section anyways (I hope so, anyways...) Unfortunately, + * this does mean that function descriptors, which live in data, won't work. + * The traceback approach actually works with JITted code too, provided it + * could be emitted with XCOFF traceback... + */ +static void +sym_from_tb(void **sbase, char **sname, void *where) +{ + /* The pointer must be word aligned as instructions are */ + unsigned int *s = (unsigned int *)((uintptr_t)where & ~3); + while (*s) { + /* look for zero word (invalid op) that begins epilogue */ + s++; + } + /* We're on a zero word now, seek after the traceback table. */ + struct tbtable_short *tb = (struct tbtable_short *)(s + 1); + /* The extended traceback is variable length, so more seeking. */ + char *ext = (char *)(tb + 1); + /* Skip a lot of cruft, in order according to the ext "structure". */ + if (tb->fixedparms || tb->floatparms) { + ext += sizeof(unsigned int); + } + if (tb->has_tboff) { + /* tb_offset */ + void *start = (char *)s - *((unsigned int *)ext); + ext += sizeof(unsigned int); + *sbase = (void *)start; + } else { + /* + * Can we go backwards instead until we hit a null word, + * that /precedes/ the block of code? + * Does the XCOFF/traceback format allow for that? + */ + *sbase = NULL; /* NULL base address as a sentinel */ + } + if (tb->int_hndl) { + ext += sizeof(int); + } + if (tb->has_ctl) { + /* array */ + int ctlnum = (*(int *)ext); + ext += sizeof(int) + (sizeof(int) * ctlnum); + } + if (tb->name_present) { + /* Oops! It does seem these can contain a null! */ + short name_len = (*(short *)ext); + ext += sizeof(short); + char *name = sentry_malloc(name_len + 1); + memcpy(name, (char *)ext, name_len); + name[name_len] = '\0'; + *sname = name; + } else { + *sname = NULL; + } +} + +/** + * Look for the base address and name of both a symbol and the corresponding + * executable in memory. This is a simplistic reimplementation for AIX. + * + * Returns 1 on failure and 0 on success. "s" is the address of the symbol, + * and "i" points to a Dl_info structure to fill. Note that i.dli_fname is + * not const, and should be freed. + */ +static int +dladdr(void *s, Dl_info *i) +{ + char buf[10000]; + i->dli_fbase = NULL; + i->dli_fname = NULL; + i->dli_saddr = NULL; + i->dli_sname = NULL; + int r = loadquery(L_GETINFO, buf, 10000); + if (r == -1) { + return 0; + } + /* The loader info structures are also a linked list. */ + struct ld_info *cur = (struct ld_info *)buf; + while (1) { + /* + * Check in text and data sections. Function descriptors are + * stored in the data section. + */ + char *db = (char *)cur->ldinfo_dataorg; + char *tb = (char *)cur->ldinfo_textorg; + char *de = db + cur->ldinfo_datasize; + char *te = tb + cur->ldinfo_textsize; + /* Just casting for comparisons. */ + char *cs = (char *)s; + + /* + * Find the symbol's name and base address. To make it + * easier, we use the traceback in the text section. + * See the function's comments above as to why. + * (Perhaps we could deref if a descriptor though...) + */ + if (cs >= tb && cs <= te) { + sym_from_tb(&i->dli_saddr, &i->dli_sname, s); + } + + if ((cs >= db && cs <= de) || (cs >= tb && cs <= te)) { + /* Look for file name and base address. */ + i->dli_fbase = tb; /* Includes XCOFF header */ + /* library filename + ( + member + ) + NUL */ + char *libname = (char *)sentry_malloc(AIX_PRINTED_LIB_LEN); + char *file_part = cur->ldinfo_filename; + char *member_part = file_part + strlen(file_part) + 1; + /* + * This can't be a const char*, because it exists from + * a stack allocated buffer. Also append the member. + * + * XXX: See if we can't frob usla's memory ranges for + * const strings; but is quite difficult. + */ + if (member_part[0] == '\0') { + /* Not an archive, just copy the file name. */ + snprintf(libname, AIX_PRINTED_LIB_LEN, "%s", file_part); + } else { + /* It's an archive with member. */ + snprintf(libname, AIX_PRINTED_LIB_LEN, "%s(%s)", file_part, + member_part); + } + i->dli_fname = libname; + + return 1; + } else if (cur->ldinfo_next == 0) { + /* Nothing. */ + return 0; + } else { + /* Try the next image in memory. */ + cur = (struct ld_info *)((char *)cur + cur->ldinfo_next); + } + } +} +#endif + bool sentry__symbolize( void *addr, void (*func)(const sentry_frame_info_t *, void *), void *data) @@ -23,6 +204,14 @@ sentry__symbolize( frame_info.symbol = info.dli_sname; frame_info.object_name = info.dli_fname; func(&frame_info, data); +#ifdef SENTRY_PLATFORM_AIX + // On AIX these must be freed. Hope the the callback doesn't use that + // buffer... + // XXX: We may just be able to stuff it into a fixed-length field of + // Dl_info? + free(info.dli_sname); + free(info.dli_fname); +#endif return true; } diff --git a/src/unwinder/sentry_unwinder_libbacktrace.c b/src/unwinder/sentry_unwinder_libbacktrace.c index 2c5c3b20b..dd671a958 100644 --- a/src/unwinder/sentry_unwinder_libbacktrace.c +++ b/src/unwinder/sentry_unwinder_libbacktrace.c @@ -1,6 +1,12 @@ #include "sentry_boot.h" -#if defined(SENTRY_PLATFORM_DARWIN) || defined(__GLIBC__) +// XXX: Make into a CMake check +// XXX: IBM i PASE offers libbacktrace in libutil, but not available in AIX +#if defined(SENTRY_PLATFORM_DARWIN) || defined(__GLIBC__) || defined(__PASE__) +# define HAS_EXECINFO_H +#endif + +#ifdef HAS_EXECINFO_H # include #endif @@ -19,7 +25,7 @@ sentry__unwind_stack_libbacktrace( } else if (uctx) { return 0; } -#if defined(SENTRY_PLATFORM_DARWIN) || defined(__GLIBC__) +#ifdef HAS_EXECINFO_H return (size_t)backtrace(ptrs, (int)max_frames); #else (void)ptrs; diff --git a/tests/assertions.py b/tests/assertions.py index 2be06c18f..379686f2e 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -44,9 +44,9 @@ def assert_meta(envelope, release="test-example-release", integration=None): } expected_sdk = { "name": "sentry.native", - "version": "0.4.12", + "version": "0.4.13", "packages": [ - {"name": "github:getsentry/sentry-native", "version": "0.4.12"}, + {"name": "github:getsentry/sentry-native", "version": "0.4.13"}, ], } if not is_android: diff --git a/tests/conditions.py b/tests/conditions.py index 39642a66e..325cd2d41 100644 --- a/tests/conditions.py +++ b/tests/conditions.py @@ -1,6 +1,7 @@ import sys import os +is_aix = sys.platform == "aix" or sys.platform == "os400" is_android = os.environ.get("ANDROID_API") is_x86 = os.environ.get("TEST_X86") is_asan = "asan" in os.environ.get("RUN_ANALYZER", "") @@ -14,12 +15,16 @@ has_breakpad = ( not is_valgrind and not is_kcov + # Needs porting + and not is_aix # XXX: we support building breakpad, and it captures minidumps when run through sentry-android, # however running it from an `adb shell` does not work correctly :-( and not is_android and not (is_asan and sys.platform == "darwin") ) -# crashpad requires http, and doesn’t work with kcov/valgrind either -has_crashpad = has_http and not is_valgrind and not is_kcov and not is_android +# crashpad requires http, needs porting to AIX, and doesn’t work with kcov/valgrind either +has_crashpad = ( + has_http and not is_valgrind and not is_kcov and not is_android and not is_aix +) # android has no local filesystem has_files = not is_android diff --git a/tests/test_build_static.py b/tests/test_build_static.py index 53a9a5880..161ac8bb5 100644 --- a/tests/test_build_static.py +++ b/tests/test_build_static.py @@ -55,7 +55,7 @@ def test_static_crashpad(cmake): ) -@pytest.mark.skipif(not has_breakpad, reason="test needs crashpad backend") +@pytest.mark.skipif(not has_breakpad, reason="test needs breakpad backend") def test_static_breakpad(cmake): tmp_path = cmake( ["sentry_example"], diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index ae640ec86..1e5f6f46d 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -21,7 +21,7 @@ pytestmark = pytest.mark.skipif(not has_http, reason="tests need http") -auth_header = "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.4.12" +auth_header = "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.4.13" def test_capture_http(cmake, httpserver): diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 86ffecaff..db18b0f95 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -29,10 +29,12 @@ add_executable(sentry_test_unit test_mpack.c test_path.c test_ratelimiter.c + test_sampling.c test_session.c test_slice.c test_symbolizer.c test_sync.c + test_tracing.c test_uninit.c test_unwinder.c test_utils.c diff --git a/tests/unit/test_concurrency.c b/tests/unit/test_concurrency.c index e2c1446bf..83f780240 100644 --- a/tests/unit/test_concurrency.c +++ b/tests/unit/test_concurrency.c @@ -95,3 +95,30 @@ SENTRY_TEST(concurrent_init) TEST_CHECK(called >= THREADS_NUM * 1); TEST_CHECK(called <= THREADS_NUM * 3); } + +SENTRY_THREAD_FN +thread_breadcrumb(void *UNUSED(arg)) +{ + sentry_value_t breadcrumb = sentry_value_new_breadcrumb("foo", "bar"); + sentry_add_breadcrumb(breadcrumb); + + return 0; +} + +SENTRY_TEST(concurrent_uninit) +{ + sentry_value_t user = sentry_value_new_object(); + sentry_set_user(user); + + sentry_threadid_t thread; + sentry__thread_init(&thread); + sentry__thread_spawn(&thread, &thread_breadcrumb, NULL); + + sentry_value_t breadcrumb = sentry_value_new_breadcrumb("foo", "bar"); + sentry_add_breadcrumb(breadcrumb); + + sentry__thread_join(thread); + sentry__thread_free(&thread); + + sentry_close(); +} diff --git a/tests/unit/test_envelopes.c b/tests/unit/test_envelopes.c index a579afa3e..2554be1c7 100644 --- a/tests/unit/test_envelopes.c +++ b/tests/unit/test_envelopes.c @@ -32,6 +32,38 @@ SENTRY_TEST(basic_http_request_preparation_for_event) sentry__dsn_decref(dsn); } +SENTRY_TEST(basic_http_request_preparation_for_transaction) +{ + sentry_dsn_t *dsn = sentry__dsn_new("https://foo@sentry.invalid/42"); + + sentry_uuid_t event_id + = sentry_uuid_from_string("c993afb6-b4ac-48a6-b61b-2558e601d65d"); + sentry_envelope_t *envelope = sentry__envelope_new(); + sentry_value_t transaction = sentry_value_new_object(); + sentry_value_set_by_key( + transaction, "event_id", sentry__value_new_uuid(&event_id)); + sentry_value_set_by_key( + transaction, "type", sentry_value_new_string("transaction")); + sentry__envelope_add_transaction(envelope, transaction); + + sentry_prepared_http_request_t *req + = sentry__prepare_http_request(envelope, dsn, NULL); + TEST_CHECK_STRING_EQUAL(req->method, "POST"); + TEST_CHECK_STRING_EQUAL( + req->url, "https://sentry.invalid:443/api/42/envelope/"); + TEST_CHECK_STRING_EQUAL(req->body, + "{\"event_id\":\"c993afb6-b4ac-48a6-b61b-2558e601d65d\",\"sent_at\":" + "\"2021-12-16T05:53:59.343Z\"}\n" + "{\"type\":\"transaction\",\"length\":72}\n" + "{\"event_id\":\"c993afb6-b4ac-48a6-b61b-2558e601d65d\",\"type\":" + "\"transaction\"}"); + + sentry__prepared_http_request_free(req); + sentry_envelope_free(envelope); + + sentry__dsn_decref(dsn); +} + SENTRY_TEST(basic_http_request_preparation_for_event_with_attachment) { sentry_dsn_t *dsn = sentry__dsn_new("https://foo@sentry.invalid/42"); diff --git a/tests/unit/test_sampling.c b/tests/unit/test_sampling.c new file mode 100644 index 000000000..bcedcbbbb --- /dev/null +++ b/tests/unit/test_sampling.c @@ -0,0 +1,36 @@ +#include "sentry_core.h" +#include "sentry_testsupport.h" + +SENTRY_TEST(sampling_decision) +{ + TEST_CHECK(sentry__roll_dice(0.0) == false); + TEST_CHECK(sentry__roll_dice(1.0)); + TEST_CHECK(sentry__roll_dice(2.0)); +} + +SENTRY_TEST(sampling_transaction) +{ + sentry_options_t *options = sentry_options_new(); + TEST_CHECK(sentry_init(options) == 0); + + sentry_value_t tx_cxt = sentry_value_new_transaction_context("honk", NULL); + + sentry_transaction_context_set_sampled(tx_cxt, 0); + TEST_CHECK(sentry__should_send_transaction(tx_cxt) == false); + + sentry_transaction_context_set_sampled(tx_cxt, 1); + TEST_CHECK(sentry__should_send_transaction(tx_cxt)); + + // fall back to default in sentry options (0.0) if sampled isn't there + sentry_transaction_context_remove_sampled(tx_cxt); + TEST_CHECK(sentry__should_send_transaction(tx_cxt) == false); + + options = sentry_options_new(); + sentry_options_set_traces_sample_rate(options, 1.0); + TEST_CHECK(sentry_init(options) == 0); + + TEST_CHECK(sentry__should_send_transaction(tx_cxt)); + + sentry_value_decref(tx_cxt); + sentry_close(); +} diff --git a/tests/unit/test_symbolizer.c b/tests/unit/test_symbolizer.c index e7d1963d1..9c18fea7c 100644 --- a/tests/unit/test_symbolizer.c +++ b/tests/unit/test_symbolizer.c @@ -17,14 +17,26 @@ asserter(const sentry_frame_info_t *info, void *data) TEST_CHECK(info->symbol && strstr(info->symbol, "test_function") != 0); TEST_CHECK(info->object_name && strstr(info->object_name, "sentry_test_unit") != 0); +#ifdef SENTRY_PLATFORM_AIX + // Again, function descriptors. Should be enabled for ELFv1 PPC too. + TEST_CHECK(info->symbol_addr == *(void **)&test_function); + TEST_CHECK( + info->instruction_addr == ((char *)*(void **)&test_function) + 1); +#else TEST_CHECK(info->symbol_addr == &test_function); TEST_CHECK(info->instruction_addr == ((char *)(void *)&test_function) + 1); +#endif *called += 1; } SENTRY_TEST(symbolizer) { int called = 0; +#ifdef SENTRY_PLATFORM_AIX + sentry__symbolize( + ((char *)*(void **)&test_function) + 1, asserter, &called); +#else sentry__symbolize(((char *)(void *)&test_function) + 1, asserter, &called); +#endif TEST_CHECK_INT_EQUAL(called, 1); } diff --git a/tests/unit/test_tracing.c b/tests/unit/test_tracing.c new file mode 100644 index 000000000..103d94675 --- /dev/null +++ b/tests/unit/test_tracing.c @@ -0,0 +1,262 @@ +#include "sentry_testsupport.h" +#include "sentry_tracing.h" +#include "sentry_uuid.h" + +#define IS_NULL(Src, Field) \ + sentry_value_is_null(sentry_value_get_by_key(Src, Field)) +#define CHECK_STRING_PROPERTY(Src, Field, Expected) \ + TEST_CHECK_STRING_EQUAL( \ + sentry_value_as_string(sentry_value_get_by_key(Src, Field)), Expected) + +SENTRY_TEST(basic_tracing_context) +{ + sentry_value_t span = sentry_value_new_object(); + TEST_CHECK(sentry_value_is_null(sentry__span_get_trace_context(span))); + + sentry_value_set_by_key(span, "op", sentry_value_new_string("honk.beep")); + TEST_CHECK(sentry_value_is_null(sentry__span_get_trace_context(span))); + + sentry_uuid_t trace_id = sentry_uuid_new_v4(); + sentry_value_set_by_key( + span, "trace_id", sentry__value_new_internal_uuid(&trace_id)); + TEST_CHECK(sentry_value_is_null(sentry__span_get_trace_context(span))); + + 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_t trace_context = sentry__span_get_trace_context(span); + TEST_CHECK(!sentry_value_is_null(trace_context)); + TEST_CHECK(!sentry_value_is_null( + sentry_value_get_by_key(trace_context, "trace_id"))); + TEST_CHECK(!sentry_value_is_null( + sentry_value_get_by_key(trace_context, "span_id"))); + + const char *span_op + = sentry_value_as_string(sentry_value_get_by_key(trace_context, "op")); + TEST_CHECK_STRING_EQUAL(span_op, "honk.beep"); + + sentry_value_decref(trace_context); + sentry_value_decref(span); +} + +SENTRY_TEST(basic_transaction) +{ + sentry_value_t tx_cxt = sentry_value_new_transaction_context(NULL, NULL); + TEST_CHECK(!sentry_value_is_null(tx_cxt)); + CHECK_STRING_PROPERTY(tx_cxt, "transaction", ""); + CHECK_STRING_PROPERTY(tx_cxt, "op", ""); + TEST_CHECK(!IS_NULL(tx_cxt, "trace_id")); + TEST_CHECK(!IS_NULL(tx_cxt, "span_id")); + + sentry_value_decref(tx_cxt); + tx_cxt = sentry_value_new_transaction_context("", ""); + TEST_CHECK(!sentry_value_is_null(tx_cxt)); + CHECK_STRING_PROPERTY(tx_cxt, "transaction", ""); + CHECK_STRING_PROPERTY(tx_cxt, "op", ""); + TEST_CHECK(!IS_NULL(tx_cxt, "trace_id")); + TEST_CHECK(!IS_NULL(tx_cxt, "span_id")); + + sentry_value_decref(tx_cxt); + tx_cxt = sentry_value_new_transaction_context("honk.beep", "beepbeep"); + CHECK_STRING_PROPERTY(tx_cxt, "transaction", "honk.beep"); + CHECK_STRING_PROPERTY(tx_cxt, "op", "beepbeep"); + TEST_CHECK(!IS_NULL(tx_cxt, "trace_id")); + TEST_CHECK(!IS_NULL(tx_cxt, "span_id")); + + sentry_transaction_context_set_name(tx_cxt, ""); + CHECK_STRING_PROPERTY(tx_cxt, "transaction", ""); + + sentry_transaction_context_set_operation(tx_cxt, ""); + CHECK_STRING_PROPERTY(tx_cxt, "op", ""); + + sentry_transaction_context_set_sampled(tx_cxt, 1); + TEST_CHECK( + sentry_value_is_true(sentry_value_get_by_key(tx_cxt, "sampled")) == 1); + + sentry_value_decref(tx_cxt); +} + +static void +check_backfilled_name(sentry_envelope_t *envelope, void *data) +{ + uint64_t *called = data; + *called += 1; + + sentry_value_t tx = sentry_envelope_get_transaction(envelope); + TEST_CHECK(!sentry_value_is_null(tx)); + CHECK_STRING_PROPERTY(tx, "transaction", ""); + + sentry_envelope_free(envelope); +} + +SENTRY_TEST(transaction_name_backfill_on_finish) +{ + uint64_t called = 0; + + sentry_options_t *options = sentry_options_new(); + sentry_options_set_dsn(options, "https://foo@sentry.invalid/42"); + + sentry_transport_t *transport = sentry_transport_new(check_backfilled_name); + sentry_transport_set_state(transport, &called); + sentry_options_set_transport(options, transport); + + sentry_options_set_traces_sample_rate(options, 1.0); + sentry_init(options); + + sentry_value_t tx_cxt = sentry_value_new_transaction_context(NULL, NULL); + sentry_value_t tx = sentry_transaction_start(tx_cxt); + sentry_uuid_t event_id = sentry_transaction_finish(tx); + TEST_CHECK(!sentry_uuid_is_nil(&event_id)); + + tx_cxt = sentry_value_new_transaction_context("", ""); + tx = sentry_transaction_start(tx_cxt); + event_id = sentry_transaction_finish(tx); + TEST_CHECK(!sentry_uuid_is_nil(&event_id)); + + sentry_close(); + TEST_CHECK_INT_EQUAL(called, 2); +} + +static void +send_transaction_envelope_test_basic(sentry_envelope_t *envelope, void *data) +{ + uint64_t *called = data; + *called += 1; + + sentry_value_t tx = sentry_envelope_get_transaction(envelope); + TEST_CHECK(!sentry_value_is_null(tx)); + const char *event_id + = sentry_value_as_string(sentry_value_get_by_key(tx, "event_id")); + TEST_CHECK_STRING_EQUAL(event_id, "4c035723-8638-4c3a-923f-2ab9d08b4018"); + + if (*called == 1) { + const char *type + = sentry_value_as_string(sentry_value_get_by_key(tx, "type")); + TEST_CHECK_STRING_EQUAL(type, "transaction"); + const char *name = sentry_value_as_string( + sentry_value_get_by_key(tx, "transaction")); + TEST_CHECK_STRING_EQUAL(name, "honk"); + } + + sentry_envelope_free(envelope); +} + +SENTRY_TEST(basic_function_transport_transaction) +{ + uint64_t called = 0; + + sentry_options_t *options = sentry_options_new(); + sentry_options_set_dsn(options, "https://foo@sentry.invalid/42"); + + sentry_transport_t *transport + = sentry_transport_new(send_transaction_envelope_test_basic); + sentry_transport_set_state(transport, &called); + sentry_options_set_transport(options, transport); + + sentry_options_set_traces_sample_rate(options, 1.0); + sentry_options_set_require_user_consent(options, true); + sentry_init(options); + + sentry_value_t tx_cxt = sentry_value_new_transaction_context( + "How could you", "Don't capture this."); + sentry_value_t tx = sentry_transaction_start(tx_cxt); + sentry_uuid_t event_id = sentry_transaction_finish(tx); + // TODO: `sentry_capture_event` acts as if the event was sent if user + // consent was not given + TEST_CHECK(!sentry_uuid_is_nil(&event_id)); + sentry_user_consent_give(); + + tx_cxt = sentry_value_new_transaction_context("honk", "beep"); + tx = sentry_transaction_start(tx_cxt); + event_id = sentry_transaction_finish(tx); + TEST_CHECK(!sentry_uuid_is_nil(&event_id)); + + sentry_user_consent_revoke(); + tx_cxt = sentry_value_new_transaction_context( + "How could you again", "Don't capture this either."); + tx = sentry_transaction_start(tx_cxt); + event_id = sentry_transaction_finish(tx); + // TODO: `sentry_capture_event` acts as if the event was sent if user + // consent was not given + TEST_CHECK(!sentry_uuid_is_nil(&event_id)); + + sentry_close(); + + TEST_CHECK_INT_EQUAL(called, 1); +} + +SENTRY_TEST(transport_sampling_transactions) +{ + uint64_t called_transport = 0; + + sentry_options_t *options = sentry_options_new(); + sentry_options_set_dsn(options, "https://foo@sentry.invalid/42"); + + sentry_transport_t *transport + = sentry_transport_new(send_transaction_envelope_test_basic); + sentry_transport_set_state(transport, &called_transport); + sentry_options_set_transport(options, transport); + + sentry_options_set_traces_sample_rate(options, 0.75); + sentry_init(options); + + uint64_t sent_transactions = 0; + for (int i = 0; i < 100; i++) { + sentry_value_t tx_cxt + = sentry_value_new_transaction_context("honk", "beep"); + sentry_value_t tx = sentry_transaction_start(tx_cxt); + sentry_uuid_t event_id = sentry_transaction_finish(tx); + if (!sentry_uuid_is_nil(&event_id)) { + sent_transactions += 1; + } + } + + sentry_close(); + + // well, its random after all + TEST_CHECK(called_transport > 50 && called_transport < 100); + TEST_CHECK(called_transport == sent_transactions); +} + +static sentry_value_t +before_send(sentry_value_t event, void *UNUSED(hint), void *data) +{ + uint64_t *called = data; + *called += 1; + + sentry_value_decref(event); + return sentry_value_new_null(); +} + +SENTRY_TEST(transactions_skip_before_send) +{ + uint64_t called_beforesend = 0; + uint64_t called_transport = 0; + + sentry_options_t *options = sentry_options_new(); + sentry_options_set_dsn(options, "https://foo@sentry.invalid/42"); + + sentry_transport_t *transport + = sentry_transport_new(send_transaction_envelope_test_basic); + sentry_transport_set_state(transport, &called_transport); + sentry_options_set_transport(options, transport); + + sentry_options_set_traces_sample_rate(options, 1.0); + sentry_options_set_before_send(options, before_send, &called_beforesend); + sentry_init(options); + + sentry_value_t tx_cxt + = sentry_value_new_transaction_context("honk", "beep"); + sentry_value_t tx = sentry_transaction_start(tx_cxt); + sentry_uuid_t event_id = sentry_transaction_finish(tx); + TEST_CHECK(!sentry_uuid_is_nil(&event_id)); + + sentry_close(); + + TEST_CHECK_INT_EQUAL(called_transport, 1); + TEST_CHECK_INT_EQUAL(called_beforesend, 0); +} + +#undef IS_NULL +#undef CHECK_STRING_PROPERTY diff --git a/tests/unit/test_unwinder.c b/tests/unit/test_unwinder.c index dd9547760..a5982ff7c 100644 --- a/tests/unit/test_unwinder.c +++ b/tests/unit/test_unwinder.c @@ -21,7 +21,17 @@ find_frame(const sentry_frame_info_t *info, void *data) { int *found_frame = data; // we will specifically check for the unwinder function - if (info->symbol_addr == &invoke_unwinder) { + void *unwinder_address = +#if defined(SENTRY_PLATFORM_AIX) + // AIX and ELFv1 SystemV ABI use function descriptors (a struct + // containing the pointers required to invoke). We need to dereference + // again to get the actual reference to code. + // XXX: Should apply on _CALL_ELF == 1 when on PowerPC i.e. Linux + *(void **)&invoke_unwinder; +#else + &invoke_unwinder; +#endif + if (info->symbol_addr == unwinder_address) { *found_frame += 1; } } diff --git a/tests/unit/test_utils.c b/tests/unit/test_utils.c index db36abe5d..bdc17f2aa 100644 --- a/tests/unit/test_utils.c +++ b/tests/unit/test_utils.c @@ -86,6 +86,20 @@ SENTRY_TEST(dsn_parsing_complete) TEST_CHECK_STRING_EQUAL(dsn->path, "/foo/bar"); TEST_CHECK_INT_EQUAL((int)dsn->project_id, 42); sentry__dsn_decref(dsn); + + dsn = sentry__dsn_new("https://username@example.com/42"); + TEST_CHECK(!!dsn); + if (!dsn) { + return; + } + TEST_CHECK(dsn->is_valid); + TEST_CHECK(dsn->is_secure); + TEST_CHECK_STRING_EQUAL(dsn->host, "example.com"); + TEST_CHECK_STRING_EQUAL(dsn->public_key, "username"); + TEST_CHECK(!dsn->secret_key); + TEST_CHECK_STRING_EQUAL(dsn->path, ""); + TEST_CHECK_INT_EQUAL((int)dsn->project_id, 42); + sentry__dsn_decref(dsn); } SENTRY_TEST(dsn_parsing_invalid) @@ -105,6 +119,27 @@ SENTRY_TEST(dsn_parsing_invalid) TEST_CHECK(!dsn->is_valid); sentry__dsn_decref(dsn); } + + dsn = sentry__dsn_new("https://key@"); + TEST_CHECK(!!dsn); + if (dsn) { + TEST_CHECK(!dsn->is_valid); + sentry__dsn_decref(dsn); + } + + dsn = sentry__dsn_new("https://key@sentry.io"); + TEST_CHECK(!!dsn); + if (dsn) { + TEST_CHECK(!dsn->is_valid); + sentry__dsn_decref(dsn); + } + + dsn = sentry__dsn_new("https://sentry.io/1234567"); + TEST_CHECK(!!dsn); + if (dsn) { + TEST_CHECK(!dsn->is_valid); + sentry__dsn_decref(dsn); + } } SENTRY_TEST(dsn_store_url_with_path) diff --git a/tests/unit/test_uuid.c b/tests/unit/test_uuid.c index ade91da99..6be5044f3 100644 --- a/tests/unit/test_uuid.c +++ b/tests/unit/test_uuid.c @@ -1,5 +1,6 @@ #include "sentry_testsupport.h" #include +#include SENTRY_TEST(uuid_api) { @@ -25,4 +26,23 @@ SENTRY_TEST(uuid_v4) sentry_uuid_as_bytes(&uuid, bytes); TEST_CHECK(bytes[6] >> 4 == 4); } -} \ No newline at end of file +} + +SENTRY_TEST(internal_uuid_api) +{ + sentry_uuid_t uuid + = sentry_uuid_from_string("f391fdc0bb2743b18c0c183bc217d42b"); + TEST_CHECK(!sentry_uuid_is_nil(&uuid)); + char ibuf[37]; + sentry__internal_uuid_as_string(&uuid, ibuf); + TEST_CHECK_STRING_EQUAL(ibuf, "f391fdc0bb2743b18c0c183bc217d42b"); + + char sbuf[17]; + sentry__span_uuid_as_string(&uuid, sbuf); + TEST_CHECK_STRING_EQUAL(sbuf, "f391fdc0bb2743b1"); + + sentry_uuid_t span_id = sentry_uuid_from_string("f391fdc0bb2743b1"); + TEST_CHECK(!sentry_uuid_is_nil(&span_id)); + sentry__span_uuid_as_string(&span_id, sbuf); + TEST_CHECK_STRING_EQUAL(sbuf, "f391fdc0bb2743b1"); +} diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index eb1626160..c85f2ac24 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -1,11 +1,16 @@ XX(background_worker) XX(basic_consent_tracking) XX(basic_function_transport) +XX(basic_function_transport_transaction) XX(basic_http_request_preparation_for_event) XX(basic_http_request_preparation_for_event_with_attachment) XX(basic_http_request_preparation_for_minidump) +XX(basic_http_request_preparation_for_transaction) +XX(basic_tracing_context) +XX(basic_transaction) XX(buildid_fallback) XX(concurrent_init) +XX(concurrent_uninit) XX(count_sampled_events) XX(custom_logger) XX(dsn_parsing_complete) @@ -15,6 +20,7 @@ XX(dsn_store_url_without_path) XX(empty_transport) XX(fuzz_json) XX(init_failure) +XX(internal_uuid_api) XX(invalid_dsn) XX(invalid_proxy) XX(iso_time) @@ -36,11 +42,16 @@ XX(procmaps_parser) XX(rate_limit_parsing) XX(recursive_paths) XX(sampling_before_send) +XX(sampling_decision) +XX(sampling_transaction) XX(serialize_envelope) XX(session_basics) XX(slice) XX(symbolizer) XX(task_queue) +XX(transaction_name_backfill_on_finish) +XX(transactions_skip_before_send) +XX(transport_sampling_transactions) XX(uninitialized) XX(unwinder) XX(url_parsing_complete)