datadog 2.2.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +87 -2
  3. data/ext/datadog_profiling_loader/datadog_profiling_loader.c +9 -1
  4. data/ext/datadog_profiling_loader/extconf.rb +14 -26
  5. data/ext/datadog_profiling_native_extension/clock_id.h +1 -0
  6. data/ext/datadog_profiling_native_extension/clock_id_from_pthread.c +1 -2
  7. data/ext/datadog_profiling_native_extension/clock_id_noop.c +1 -2
  8. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +257 -69
  9. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +53 -28
  10. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.h +34 -4
  11. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +4 -0
  12. data/ext/datadog_profiling_native_extension/collectors_stack.c +136 -81
  13. data/ext/datadog_profiling_native_extension/collectors_stack.h +2 -2
  14. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +661 -48
  15. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +10 -1
  16. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +83 -0
  17. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +53 -0
  18. data/ext/datadog_profiling_native_extension/extconf.rb +91 -69
  19. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.c +50 -0
  20. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.h +75 -0
  21. data/ext/datadog_profiling_native_extension/heap_recorder.c +54 -12
  22. data/ext/datadog_profiling_native_extension/heap_recorder.h +3 -1
  23. data/ext/datadog_profiling_native_extension/helpers.h +6 -17
  24. data/ext/datadog_profiling_native_extension/http_transport.c +41 -9
  25. data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +0 -86
  26. data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +2 -23
  27. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +61 -172
  28. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +116 -139
  29. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +20 -11
  30. data/ext/datadog_profiling_native_extension/profiling.c +1 -3
  31. data/ext/datadog_profiling_native_extension/ruby_helpers.c +0 -33
  32. data/ext/datadog_profiling_native_extension/ruby_helpers.h +1 -26
  33. data/ext/datadog_profiling_native_extension/setup_signal_handler.h +1 -0
  34. data/ext/datadog_profiling_native_extension/stack_recorder.c +14 -2
  35. data/ext/datadog_profiling_native_extension/stack_recorder.h +2 -0
  36. data/ext/datadog_profiling_native_extension/time_helpers.c +0 -15
  37. data/ext/datadog_profiling_native_extension/time_helpers.h +36 -6
  38. data/ext/{datadog_profiling_native_extension → libdatadog_api}/crashtracker.c +37 -22
  39. data/ext/libdatadog_api/datadog_ruby_common.c +83 -0
  40. data/ext/libdatadog_api/datadog_ruby_common.h +53 -0
  41. data/ext/libdatadog_api/extconf.rb +108 -0
  42. data/ext/libdatadog_api/macos_development.md +26 -0
  43. data/ext/libdatadog_extconf_helpers.rb +130 -0
  44. data/lib/datadog/appsec/assets/waf_rules/recommended.json +2184 -108
  45. data/lib/datadog/appsec/assets/waf_rules/strict.json +1430 -2
  46. data/lib/datadog/appsec/component.rb +29 -8
  47. data/lib/datadog/appsec/configuration/settings.rb +2 -2
  48. data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +1 -0
  49. data/lib/datadog/appsec/contrib/devise/patcher/rememberable_patch.rb +21 -0
  50. data/lib/datadog/appsec/contrib/devise/patcher.rb +12 -2
  51. data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +35 -0
  52. data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +109 -0
  53. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +71 -0
  54. data/lib/datadog/appsec/contrib/graphql/integration.rb +54 -0
  55. data/lib/datadog/appsec/contrib/graphql/patcher.rb +37 -0
  56. data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +59 -0
  57. data/lib/datadog/appsec/contrib/rack/gateway/request.rb +3 -6
  58. data/lib/datadog/appsec/event.rb +1 -1
  59. data/lib/datadog/appsec/processor/actions.rb +1 -1
  60. data/lib/datadog/appsec/processor/rule_loader.rb +3 -1
  61. data/lib/datadog/appsec/processor/rule_merger.rb +33 -15
  62. data/lib/datadog/appsec/processor.rb +36 -37
  63. data/lib/datadog/appsec/rate_limiter.rb +25 -40
  64. data/lib/datadog/appsec/remote.rb +7 -3
  65. data/lib/datadog/appsec/response.rb +15 -1
  66. data/lib/datadog/appsec.rb +3 -2
  67. data/lib/datadog/core/configuration/components.rb +18 -15
  68. data/lib/datadog/core/configuration/settings.rb +135 -9
  69. data/lib/datadog/core/crashtracking/agent_base_url.rb +21 -0
  70. data/lib/datadog/core/crashtracking/component.rb +111 -0
  71. data/lib/datadog/core/crashtracking/tag_builder.rb +39 -0
  72. data/lib/datadog/core/diagnostics/environment_logger.rb +8 -11
  73. data/lib/datadog/core/environment/execution.rb +5 -5
  74. data/lib/datadog/core/metrics/client.rb +7 -0
  75. data/lib/datadog/core/rate_limiter.rb +183 -0
  76. data/lib/datadog/core/remote/client/capabilities.rb +4 -3
  77. data/lib/datadog/core/remote/component.rb +4 -2
  78. data/lib/datadog/core/remote/negotiation.rb +4 -4
  79. data/lib/datadog/core/remote/tie.rb +2 -0
  80. data/lib/datadog/core/runtime/metrics.rb +1 -1
  81. data/lib/datadog/core/telemetry/component.rb +51 -2
  82. data/lib/datadog/core/telemetry/emitter.rb +9 -11
  83. data/lib/datadog/core/telemetry/event.rb +37 -1
  84. data/lib/datadog/core/telemetry/ext.rb +1 -0
  85. data/lib/datadog/core/telemetry/http/adapters/net.rb +10 -12
  86. data/lib/datadog/core/telemetry/http/ext.rb +3 -0
  87. data/lib/datadog/core/telemetry/http/transport.rb +38 -9
  88. data/lib/datadog/core/telemetry/logger.rb +51 -0
  89. data/lib/datadog/core/telemetry/logging.rb +71 -0
  90. data/lib/datadog/core/telemetry/request.rb +13 -1
  91. data/lib/datadog/core/utils/at_fork_monkey_patch.rb +102 -0
  92. data/lib/datadog/core/utils/time.rb +12 -0
  93. data/lib/datadog/di/code_tracker.rb +168 -0
  94. data/lib/datadog/di/configuration/settings.rb +163 -0
  95. data/lib/datadog/di/configuration.rb +11 -0
  96. data/lib/datadog/di/error.rb +31 -0
  97. data/lib/datadog/di/extensions.rb +16 -0
  98. data/lib/datadog/di/probe.rb +133 -0
  99. data/lib/datadog/di/probe_builder.rb +41 -0
  100. data/lib/datadog/di/redactor.rb +188 -0
  101. data/lib/datadog/di/serializer.rb +193 -0
  102. data/lib/datadog/di.rb +14 -0
  103. data/lib/datadog/kit/appsec/events.rb +2 -4
  104. data/lib/datadog/opentelemetry/sdk/propagator.rb +2 -0
  105. data/lib/datadog/opentelemetry/sdk/span_processor.rb +10 -0
  106. data/lib/datadog/opentelemetry/sdk/trace/span.rb +23 -0
  107. data/lib/datadog/profiling/collectors/code_provenance.rb +7 -7
  108. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +28 -26
  109. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +11 -13
  110. data/lib/datadog/profiling/collectors/info.rb +15 -6
  111. data/lib/datadog/profiling/collectors/thread_context.rb +30 -2
  112. data/lib/datadog/profiling/component.rb +89 -95
  113. data/lib/datadog/profiling/exporter.rb +3 -3
  114. data/lib/datadog/profiling/ext/dir_monkey_patches.rb +3 -3
  115. data/lib/datadog/profiling/ext.rb +21 -21
  116. data/lib/datadog/profiling/flush.rb +1 -1
  117. data/lib/datadog/profiling/http_transport.rb +14 -7
  118. data/lib/datadog/profiling/load_native_extension.rb +5 -5
  119. data/lib/datadog/profiling/preload.rb +1 -1
  120. data/lib/datadog/profiling/profiler.rb +5 -8
  121. data/lib/datadog/profiling/scheduler.rb +33 -25
  122. data/lib/datadog/profiling/stack_recorder.rb +3 -0
  123. data/lib/datadog/profiling/tag_builder.rb +2 -2
  124. data/lib/datadog/profiling/tasks/exec.rb +5 -5
  125. data/lib/datadog/profiling/tasks/setup.rb +16 -35
  126. data/lib/datadog/profiling.rb +4 -5
  127. data/lib/datadog/single_step_instrument.rb +12 -0
  128. data/lib/datadog/tracing/contrib/action_cable/instrumentation.rb +8 -12
  129. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +5 -0
  130. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +78 -0
  131. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/patcher.rb +33 -0
  132. data/lib/datadog/tracing/contrib/action_pack/patcher.rb +2 -0
  133. data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +4 -0
  134. data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +3 -1
  135. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +4 -1
  136. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +5 -1
  137. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +5 -0
  138. data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +6 -1
  139. data/lib/datadog/tracing/contrib/ext.rb +14 -0
  140. data/lib/datadog/tracing/contrib/faraday/middleware.rb +9 -0
  141. data/lib/datadog/tracing/contrib/grape/endpoint.rb +19 -0
  142. data/lib/datadog/tracing/contrib/graphql/patcher.rb +9 -12
  143. data/lib/datadog/tracing/contrib/graphql/trace_patcher.rb +3 -3
  144. data/lib/datadog/tracing/contrib/graphql/tracing_patcher.rb +3 -3
  145. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +14 -10
  146. data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +10 -4
  147. data/lib/datadog/tracing/contrib/http/instrumentation.rb +18 -15
  148. data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +6 -5
  149. data/lib/datadog/tracing/contrib/httpclient/patcher.rb +1 -14
  150. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +5 -0
  151. data/lib/datadog/tracing/contrib/httprb/patcher.rb +1 -14
  152. data/lib/datadog/tracing/contrib/lograge/patcher.rb +15 -0
  153. data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +2 -0
  154. data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +5 -0
  155. data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +17 -13
  156. data/lib/datadog/tracing/contrib/opensearch/patcher.rb +13 -6
  157. data/lib/datadog/tracing/contrib/patcher.rb +2 -1
  158. data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +5 -0
  159. data/lib/datadog/tracing/contrib/pg/instrumentation.rb +4 -1
  160. data/lib/datadog/tracing/contrib/presto/patcher.rb +1 -13
  161. data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +28 -0
  162. data/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb +5 -1
  163. data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +22 -10
  164. data/lib/datadog/tracing/contrib/rack/middlewares.rb +27 -0
  165. data/lib/datadog/tracing/contrib/redis/tags.rb +4 -0
  166. data/lib/datadog/tracing/contrib/sinatra/tracer.rb +4 -0
  167. data/lib/datadog/tracing/contrib/stripe/request.rb +3 -2
  168. data/lib/datadog/tracing/contrib/trilogy/configuration/settings.rb +5 -0
  169. data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +4 -1
  170. data/lib/datadog/tracing/diagnostics/environment_logger.rb +14 -16
  171. data/lib/datadog/tracing/distributed/propagation.rb +7 -0
  172. data/lib/datadog/tracing/metadata/errors.rb +9 -1
  173. data/lib/datadog/tracing/metadata/ext.rb +6 -0
  174. data/lib/datadog/tracing/pipeline/span_filter.rb +2 -2
  175. data/lib/datadog/tracing/remote.rb +5 -2
  176. data/lib/datadog/tracing/sampling/matcher.rb +6 -1
  177. data/lib/datadog/tracing/sampling/rate_sampler.rb +1 -1
  178. data/lib/datadog/tracing/sampling/rule.rb +2 -0
  179. data/lib/datadog/tracing/sampling/rule_sampler.rb +9 -5
  180. data/lib/datadog/tracing/sampling/span/ext.rb +1 -1
  181. data/lib/datadog/tracing/sampling/span/rule.rb +2 -2
  182. data/lib/datadog/tracing/span.rb +9 -2
  183. data/lib/datadog/tracing/span_event.rb +41 -0
  184. data/lib/datadog/tracing/span_operation.rb +6 -2
  185. data/lib/datadog/tracing/trace_operation.rb +26 -2
  186. data/lib/datadog/tracing/tracer.rb +14 -12
  187. data/lib/datadog/tracing/transport/http/client.rb +1 -0
  188. data/lib/datadog/tracing/transport/io/client.rb +1 -0
  189. data/lib/datadog/tracing/transport/serializable_trace.rb +3 -0
  190. data/lib/datadog/tracing/workers/trace_writer.rb +1 -1
  191. data/lib/datadog/tracing/workers.rb +1 -1
  192. data/lib/datadog/version.rb +1 -1
  193. metadata +46 -11
  194. data/lib/datadog/profiling/crashtracker.rb +0 -91
  195. data/lib/datadog/profiling/ext/forking.rb +0 -98
  196. data/lib/datadog/tracing/sampling/rate_limiter.rb +0 -185
@@ -182,7 +182,7 @@ uint64_t native_thread_id_for(VALUE thread) {
182
182
  #if !defined(NO_THREAD_TID) && defined(RB_THREAD_T_HAS_NATIVE_ID)
183
183
  #ifndef NO_RB_NATIVE_THREAD
184
184
  struct rb_native_thread* native_thread = thread_struct_from_object(thread)->nt;
185
- if (native_thread == NULL) rb_raise(rb_eRuntimeError, "BUG: rb_native_thread* is null. Is this Ruby running with RUBY_MN_THREADS=1?");
185
+ if (native_thread == NULL) return 0;
186
186
  return native_thread->tid;
187
187
  #else
188
188
  return thread_struct_from_object(thread)->tid;
@@ -311,7 +311,7 @@ VALUE thread_name_for(VALUE thread) {
311
311
  // with diagnostic stuff. See https://nelkinda.com/blog/suppress-warnings-in-gcc-and-clang/#d11e364 for details.
312
312
  #pragma GCC diagnostic push
313
313
  #pragma GCC diagnostic ignored "-Wunused-parameter"
314
- inline static int
314
+ static inline int
315
315
  calc_pos(const rb_iseq_t *iseq, const VALUE *pc, int *lineno, int *node_id)
316
316
  {
317
317
  VM_ASSERT(iseq);
@@ -364,7 +364,7 @@ calc_pos(const rb_iseq_t *iseq, const VALUE *pc, int *lineno, int *node_id)
364
364
  // Copyright (C) 1993-2012 Yukihiro Matsumoto
365
365
  // to support our custom rb_profile_frames (see below)
366
366
  // Modifications: None
367
- inline static int
367
+ static inline int
368
368
  calc_lineno(const rb_iseq_t *iseq, const VALUE *pc)
369
369
  {
370
370
  int lineno;
@@ -376,8 +376,8 @@ calc_lineno(const rb_iseq_t *iseq, const VALUE *pc)
376
376
  // Copyright (C) 1993-2012 Yukihiro Matsumoto
377
377
  // Modifications:
378
378
  // * Renamed rb_profile_frames => ddtrace_rb_profile_frames
379
- // * Add thread argument
380
- // * Add is_ruby_frame argument
379
+ // * Add thread argument (this is now upstream, actually!)
380
+ // * Add frame_flags.is_ruby_frame argument
381
381
  // * Removed `if (lines)` tests -- require/assume that like `buff`, `lines` is always specified
382
382
  // * Skip dummy frame that shows up in main thread
383
383
  // * Add `end_cfp == NULL` and `end_cfp <= cfp` safety checks. These are used in a bunch of places in
@@ -392,6 +392,9 @@ calc_lineno(const rb_iseq_t *iseq, const VALUE *pc)
392
392
  // was called from.
393
393
  // * Imported fix from https://github.com/ruby/ruby/pull/7116 to avoid sampling threads that are still being created
394
394
  // * Imported fix from https://github.com/ruby/ruby/pull/8415 to avoid potential crash when using YJIT.
395
+ // * Add frame_flags.same_frame and logic to skip redoing work if the buffer already contains the same data we're collecting
396
+ // * Skipped use of rb_callable_method_entry_t (cme) for Ruby frames as it doesn't impact us.
397
+ // * Imported fix from https://github.com/ruby/ruby/pull/8280 to keep us closer to upstream
395
398
  //
396
399
  // What is rb_profile_frames?
397
400
  // `rb_profile_frames` is a Ruby VM debug API added for use by profilers for sampling the stack trace of a Ruby thread.
@@ -421,8 +424,7 @@ calc_lineno(const rb_iseq_t *iseq, const VALUE *pc)
421
424
  // and friends). We've found quite a few situations where the data from rb_profile_frames and the reference APIs
422
425
  // disagree, and quite a few of them seem oversights/bugs (speculation from my part) rather than deliberate
423
426
  // decisions.
424
- int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, VALUE *buff, int *lines, bool* is_ruby_frame)
425
- {
427
+ int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, frame_info *stack_buffer) {
426
428
  int i;
427
429
  // Modified from upstream: Instead of using `GET_EC` to collect info from the current thread,
428
430
  // support sampling any thread (including the current) passed as an argument
@@ -466,7 +468,7 @@ int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, VALUE *buff, i
466
468
  // See comment on `record_placeholder_stack_in_native_code` for a full explanation of what this means (and why we don't just return 0)
467
469
  if (end_cfp <= cfp) return PLACEHOLDER_STACK_IN_NATIVE_CODE;
468
470
 
469
- for (i=0; i<limit && cfp != end_cfp;) {
471
+ for (i=0; i<limit && cfp != end_cfp; cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp)) {
470
472
  if (cfp->iseq && !cfp->pc) {
471
473
  // Fix: Do nothing -- this frame should not be used
472
474
  //
@@ -479,164 +481,88 @@ int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, VALUE *buff, i
479
481
  continue;
480
482
  }
481
483
 
482
- /* record frame info */
483
- cme = rb_vm_frame_method_entry(cfp);
484
+ stack_buffer[i].same_frame =
485
+ stack_buffer[i].is_ruby_frame &&
486
+ stack_buffer[i].as.ruby_frame.iseq == (VALUE) cfp->iseq &&
487
+ stack_buffer[i].as.ruby_frame.caching_pc == cfp->pc;
484
488
 
485
- if (cme && cme->def->type == VM_METHOD_TYPE_ISEQ &&
486
- // Fix: Do not use callable method entry when iseq is for an eval.
487
- // TL;DR: This fix is needed for us to match the Ruby reference API information in the
488
- // "when sampling an eval/instance eval inside an object" spec.
489
- //
490
- // Longer note:
491
- // When a frame is a ruby frame (VM_FRAME_RUBYFRAME_P above), we can get information about it
492
- // by introspecting both the callable method entry, as well as the iseq directly.
493
- // Often they match... but sometimes they provide different info (as in the "iseq for an eval" situation
494
- // here).
495
- // If my reading of vm_backtrace.c is correct, the actual Ruby stack trace API **never** uses the
496
- // callable method entry for Ruby frames, but only for VM_METHOD_TYPE_CFUNC (see `backtrace_each` method
497
- // on that file).
498
- // So... why does `rb_profile_frames` do something different? Is it a bug? Is it because it exposes
499
- // more information than the Ruby stack frame API?
500
- // As a final note, the `backtracie` gem (https://github.com/ivoanjo/backtracie) can be used to introspect
501
- // the full metadata provided by both the callable method entry as well as the iseq, and is really useful
502
- // to debug and learn more about these differences.
503
- cfp->iseq->body->type != ISEQ_TYPE_EVAL) {
504
- buff[i] = (VALUE)cme;
505
- }
506
- else {
507
- buff[i] = (VALUE)cfp->iseq;
489
+ if (stack_buffer[i].same_frame) { // Nothing to do, buffer already contains this frame
490
+ i++;
491
+ continue;
508
492
  }
509
493
 
494
+ // dd-trace-rb NOTE:
495
+ // Upstream Ruby has code here to retrieve the rb_callable_method_entry_t (cme) and in some cases to use it
496
+ // instead of the iseq.
497
+ // In practice, they are usually the same; the difference is that when you have e.g. block, one gets you a
498
+ // reference to the block, and the other to the method containing the block.
499
+ // This would be important if we used `rb_profile_frame_label` and wanted the "block in foo" label instead
500
+ // of just "foo". But we're currently using `rb_profile_frame_base_label` which I believe is always the same
501
+ // between the rb_callable_method_entry_t and the iseq. Thus, to simplify a bit our logic and reduce a bit
502
+ // the overhead, we always use the iseq here.
503
+ //
504
+ // @ivoanjo: I've left the upstream Ruby code commented out below for reference, so it's more obvious that
505
+ // we're diverging, and we can easily compare and experiment with the upstream version in the future.
506
+ //
507
+ // cme = rb_vm_frame_method_entry(cfp);
508
+
509
+ // if (cme && cme->def->type == VM_METHOD_TYPE_ISEQ &&
510
+ // // Fix: Do not use callable method entry when iseq is for an eval.
511
+ // // TL;DR: This fix is needed for us to match the Ruby reference API information in the
512
+ // // "when sampling an eval/instance eval inside an object" spec.
513
+ // cfp->iseq->body->type != ISEQ_TYPE_EVAL) {
514
+ // buff[i] = (VALUE)cme;
515
+ // }
516
+ // else {
517
+ stack_buffer[i].as.ruby_frame.iseq = (VALUE)cfp->iseq;
518
+ stack_buffer[i].as.ruby_frame.caching_pc = (void *) cfp->pc;
519
+ // }
520
+
510
521
  // The topmost frame may not have an updated PC because the JIT
511
522
  // may not have set one. The JIT compiler will update the PC
512
523
  // before entering a new function (so that `caller` will work),
513
524
  // so only the topmost frame could possibly have an out of date PC
514
525
  #ifndef NO_JIT_RETURN
515
526
  if (cfp == top && cfp->jit_return) {
516
- lines[i] = 0;
527
+ stack_buffer[i].as.ruby_frame.line = 0;
517
528
  } else {
518
- lines[i] = calc_lineno(cfp->iseq, cfp->pc);
529
+ stack_buffer[i].as.ruby_frame.line = calc_lineno(cfp->iseq, cfp->pc);
519
530
  }
520
531
  #else // Ruby < 3.1
521
- lines[i] = calc_lineno(cfp->iseq, cfp->pc);
532
+ stack_buffer[i].as.ruby_frame.line = calc_lineno(cfp->iseq, cfp->pc);
522
533
  #endif
523
534
 
524
- is_ruby_frame[i] = true;
535
+ stack_buffer[i].is_ruby_frame = true;
525
536
  i++;
526
537
  }
527
538
  else {
528
539
  cme = rb_vm_frame_method_entry(cfp);
529
540
  if (cme && cme->def->type == VM_METHOD_TYPE_CFUNC) {
530
- buff[i] = (VALUE)cme;
531
- lines[i] = 0;
532
- is_ruby_frame[i] = false;
541
+ if (start > 0) {
542
+ start--;
543
+ continue;
544
+ }
545
+
546
+ stack_buffer[i].same_frame =
547
+ !stack_buffer[i].is_ruby_frame &&
548
+ stack_buffer[i].as.native_frame.caching_cme == (VALUE) cme;
549
+
550
+ if (stack_buffer[i].same_frame) { // Nothing to do, buffer already contains this frame
551
+ i++;
552
+ continue;
553
+ }
554
+
555
+ stack_buffer[i].as.native_frame.caching_cme = (VALUE)cme;
556
+ stack_buffer[i].as.native_frame.method_id = cme->def->original_id;
557
+ stack_buffer[i].is_ruby_frame = false;
533
558
  i++;
534
559
  }
535
560
  }
536
- cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);
537
561
  }
538
562
 
539
563
  return i;
540
564
  }
541
565
 
542
- #ifdef USE_BACKPORTED_RB_PROFILE_FRAME_METHOD_NAME
543
-
544
- // Taken from upstream vm_backtrace.c at commit 5f10bd634fb6ae8f74a4ea730176233b0ca96954 (March 2022, Ruby 3.2 trunk)
545
- // Copyright (C) 1993-2012 Yukihiro Matsumoto
546
- // to support our custom rb_profile_frame_method_name (see below)
547
- // Modifications: None
548
- static VALUE
549
- id2str(ID id)
550
- {
551
- VALUE str = rb_id2str(id);
552
- if (!str) return Qnil;
553
- return str;
554
- }
555
- #define rb_id2str(id) id2str(id)
556
-
557
- // Taken from upstream vm_backtrace.c at commit 5f10bd634fb6ae8f74a4ea730176233b0ca96954 (March 2022, Ruby 3.2 trunk)
558
- // Copyright (C) 1993-2012 Yukihiro Matsumoto
559
- // to support our custom rb_profile_frame_method_name (see below)
560
- // Modifications: None
561
- static const rb_iseq_t *
562
- frame2iseq(VALUE frame)
563
- {
564
- if (NIL_P(frame)) return NULL;
565
-
566
- if (RB_TYPE_P(frame, T_IMEMO)) {
567
- switch (imemo_type(frame)) {
568
- case imemo_iseq:
569
- return (const rb_iseq_t *)frame;
570
- case imemo_ment:
571
- {
572
- const rb_callable_method_entry_t *cme = (rb_callable_method_entry_t *)frame;
573
- switch (cme->def->type) {
574
- case VM_METHOD_TYPE_ISEQ:
575
- return cme->def->body.iseq.iseqptr;
576
- default:
577
- return NULL;
578
- }
579
- }
580
- default:
581
- break;
582
- }
583
- }
584
- rb_bug("frame2iseq: unreachable");
585
- }
586
-
587
- // Taken from upstream vm_backtrace.c at commit 5f10bd634fb6ae8f74a4ea730176233b0ca96954 (March 2022, Ruby 3.2 trunk)
588
- // Copyright (C) 1993-2012 Yukihiro Matsumoto
589
- // to support our custom rb_profile_frame_method_name (see below)
590
- // Modifications: None
591
- static const rb_callable_method_entry_t *
592
- cframe(VALUE frame)
593
- {
594
- if (NIL_P(frame)) return NULL;
595
-
596
- if (RB_TYPE_P(frame, T_IMEMO)) {
597
- switch (imemo_type(frame)) {
598
- case imemo_ment:
599
- {
600
- const rb_callable_method_entry_t *cme = (rb_callable_method_entry_t *)frame;
601
- switch (cme->def->type) {
602
- case VM_METHOD_TYPE_CFUNC:
603
- return cme;
604
- default:
605
- return NULL;
606
- }
607
- }
608
- default:
609
- return NULL;
610
- }
611
- }
612
-
613
- return NULL;
614
- }
615
-
616
- // Taken from upstream vm_backtrace.c at commit 5f10bd634fb6ae8f74a4ea730176233b0ca96954 (March 2022, Ruby 3.2 trunk)
617
- // Copyright (C) 1993-2012 Yukihiro Matsumoto
618
- //
619
- // Ruby 3.0 finally added support for showing CFUNC frames (frames for methods written using native code)
620
- // in stack traces gathered via `rb_profile_frames` (https://github.com/ruby/ruby/pull/3299).
621
- // To access this information on older Rubies, beyond using our custom `ddtrace_rb_profile_frames` above, we also need
622
- // to backport the Ruby 3.0+ version of `rb_profile_frame_method_name`.
623
- //
624
- // Modifications:
625
- // * Renamed rb_profile_frame_method_name => ddtrace_rb_profile_frame_method_name
626
- VALUE
627
- ddtrace_rb_profile_frame_method_name(VALUE frame)
628
- {
629
- const rb_callable_method_entry_t *cme = cframe(frame);
630
- if (cme) {
631
- ID mid = cme->def->original_id;
632
- return id2str(mid);
633
- }
634
- const rb_iseq_t *iseq = frame2iseq(frame);
635
- return iseq ? rb_iseq_method_name(iseq) : Qnil;
636
- }
637
-
638
- #endif // USE_BACKPORTED_RB_PROFILE_FRAME_METHOD_NAME
639
-
640
566
  // Support code for older Rubies that cannot use the MJIT header
641
567
  #ifndef RUBY_MJIT_HEADER
642
568
 
@@ -829,3 +755,54 @@ static inline int ddtrace_imemo_type(VALUE imemo) {
829
755
  return GET_VM()->objspace;
830
756
  }
831
757
  #endif
758
+
759
+ #ifdef USE_GVL_PROFILING_3_2_WORKAROUNDS // Ruby 3.2
760
+ #include "gvl_profiling_helper.h"
761
+
762
+ gvl_profiling_thread thread_from_thread_object(VALUE thread) {
763
+ return (gvl_profiling_thread) {.thread = thread_struct_from_object(thread)};
764
+ }
765
+
766
+ // Hack: In Ruby 3.3+ we attach gvl profiling state to Ruby threads using the
767
+ // rb_internal_thread_specific_* APIs. These APIs did not exist on Ruby 3.2. On Ruby 3.2 we instead store the
768
+ // needed data inside the `rb_thread_t` structure, specifically in `stat_insn_usage` as a Ruby FIXNUM.
769
+ //
770
+ // Why `stat_insn_usage`? We needed some per-thread storage, and while looking at the Ruby VM sources I noticed
771
+ // that `stat_insn_usage` has been in `rb_thread_t` for a long time, but is not used anywhere in the VM
772
+ // code. There's a comment attached to it "/* statistics data for profiler */" but other than marking this
773
+ // field for GC, I could not find any place in the VM commit history or on GitHub where this has ever been used.
774
+ //
775
+ // Thus, since this hack is only for 3.2, which presumably will never see this field either removed or used
776
+ // during its remaining maintenance release period we... kinda take it for our own usage. It's ugly, I know...
777
+ intptr_t gvl_profiling_state_get(gvl_profiling_thread thread) {
778
+ if (thread.thread == NULL) return 0;
779
+
780
+ VALUE current_value = ((rb_thread_t *)thread.thread)->stat_insn_usage;
781
+ intptr_t result = current_value == Qnil ? 0 : FIX2LONG(current_value);
782
+ return result;
783
+ }
784
+
785
+ void gvl_profiling_state_set(gvl_profiling_thread thread, intptr_t value) {
786
+ if (thread.thread == NULL) return;
787
+ ((rb_thread_t *)thread.thread)->stat_insn_usage = LONG2FIX(value);
788
+ }
789
+
790
+ // Because Ruby 3.2 does not give us the current thread when calling the RUBY_INTERNAL_THREAD_EVENT_READY and
791
+ // RUBY_INTERNAL_THREAD_EVENT_RESUMED APIs, we need to figure out this info ourselves.
792
+ //
793
+ // Specifically, this method was created to be called from a RUBY_INTERNAL_THREAD_EVENT_RESUMED callback --
794
+ // when it's triggered, we know the thread the code gets executed on is holding the GVL, so we use this
795
+ // opportunity to initialize our thread-local value.
796
+ gvl_profiling_thread gvl_profiling_state_maybe_initialize(void) {
797
+ gvl_profiling_thread current_thread = gvl_waiting_tls;
798
+
799
+ if (current_thread.thread == NULL) {
800
+ // threads.sched.running is the thread currently holding the GVL, which when this gets executed is the
801
+ // current thread!
802
+ current_thread = (gvl_profiling_thread) {.thread = (void *) rb_current_ractor()->threads.sched.running};
803
+ gvl_waiting_tls = current_thread;
804
+ }
805
+
806
+ return current_thread;
807
+ }
808
+ #endif
@@ -18,6 +18,22 @@ typedef struct {
18
18
  rb_nativethread_id_t owner;
19
19
  } current_gvl_owner;
20
20
 
21
+ typedef struct frame_info {
22
+ union {
23
+ struct {
24
+ VALUE iseq;
25
+ void *caching_pc; // For caching only
26
+ int line;
27
+ } ruby_frame;
28
+ struct {
29
+ VALUE caching_cme; // For caching only
30
+ ID method_id;
31
+ } native_frame;
32
+ } as;
33
+ bool is_ruby_frame : 1;
34
+ bool same_frame : 1;
35
+ } frame_info;
36
+
21
37
  rb_nativethread_id_t pthread_id_for(VALUE thread);
22
38
  bool is_current_thread_holding_the_gvl(void);
23
39
  current_gvl_owner gvl_owner(void);
@@ -27,20 +43,10 @@ void ddtrace_thread_list(VALUE result_array);
27
43
  bool is_thread_alive(VALUE thread);
28
44
  VALUE thread_name_for(VALUE thread);
29
45
 
30
- int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, VALUE *buff, int *lines, bool* is_ruby_frame);
46
+ int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, frame_info *stack_buffer);
31
47
  // Returns true if the current thread belongs to the main Ractor or if Ruby has no Ractor support
32
48
  bool ddtrace_rb_ractor_main_p(void);
33
49
 
34
- // Ruby 3.0 finally added support for showing CFUNC frames (frames for methods written using native code)
35
- // in stack traces gathered via `rb_profile_frames` (https://github.com/ruby/ruby/pull/3299).
36
- // To access this information on older Rubies, beyond using our custom `ddtrace_rb_profile_frames` above, we also need
37
- // to backport the Ruby 3.0+ version of `rb_profile_frame_method_name`.
38
- #ifdef USE_BACKPORTED_RB_PROFILE_FRAME_METHOD_NAME
39
- VALUE ddtrace_rb_profile_frame_method_name(VALUE frame);
40
- #else // Ruby > 3.0, just use the stock functionality
41
- #define ddtrace_rb_profile_frame_method_name rb_profile_frame_method_name
42
- #endif
43
-
44
50
  // See comment on `record_placeholder_stack_in_native_code` for a full explanation of what this means (and why we don't just return 0)
45
51
  #define PLACEHOLDER_STACK_IN_NATIVE_CODE -1
46
52
 
@@ -59,3 +65,6 @@ const char *imemo_kind(VALUE imemo);
59
65
  #ifdef NO_POSTPONED_TRIGGER
60
66
  void *objspace_ptr_for_gc_finalize_deferred_workaround(void);
61
67
  #endif
68
+
69
+ #define ENFORCE_THREAD(value) \
70
+ { if (RB_UNLIKELY(!rb_typeddata_is_kind_of(value, RTYPEDDATA_TYPE(rb_thread_current())))) raise_unexpected_type(value, ADD_QUOTES(value), "Thread", __FILE__, __LINE__, __func__); }
@@ -19,7 +19,6 @@ void collectors_dynamic_sampling_rate_init(VALUE profiling_module);
19
19
  void collectors_idle_sampling_helper_init(VALUE profiling_module);
20
20
  void collectors_stack_init(VALUE profiling_module);
21
21
  void collectors_thread_context_init(VALUE profiling_module);
22
- void crashtracker_init(VALUE profiling_module);
23
22
  void http_transport_init(VALUE profiling_module);
24
23
  void stack_recorder_init(VALUE profiling_module);
25
24
 
@@ -54,7 +53,6 @@ void DDTRACE_EXPORT Init_datadog_profiling_native_extension(void) {
54
53
  collectors_idle_sampling_helper_init(profiling_module);
55
54
  collectors_stack_init(profiling_module);
56
55
  collectors_thread_context_init(profiling_module);
57
- crashtracker_init(profiling_module);
58
56
  http_transport_init(profiling_module);
59
57
  stack_recorder_init(profiling_module);
60
58
 
@@ -255,7 +253,7 @@ static VALUE _native_enforce_success(DDTRACE_UNUSED VALUE _self, VALUE syserr_er
255
253
 
256
254
  static void *trigger_enforce_success(void *trigger_args) {
257
255
  intptr_t syserr_errno = (intptr_t) trigger_args;
258
- ENFORCE_SUCCESS_NO_GVL(syserr_errno);
256
+ ENFORCE_SUCCESS_NO_GVL((int) syserr_errno);
259
257
  return NULL;
260
258
  }
261
259
 
@@ -20,29 +20,6 @@ void ruby_helpers_init(void) {
20
20
  to_s_id = rb_intern("to_s");
21
21
  }
22
22
 
23
- void raise_unexpected_type(
24
- VALUE value,
25
- const char *value_name,
26
- const char *type_name,
27
- const char *file,
28
- int line,
29
- const char* function_name
30
- ) {
31
- rb_exc_raise(
32
- rb_exc_new_str(
33
- rb_eTypeError,
34
- rb_sprintf("wrong argument %"PRIsVALUE" for '%s' (expected a %s) at %s:%d:in `%s'",
35
- rb_inspect(value),
36
- value_name,
37
- type_name,
38
- file,
39
- line,
40
- function_name
41
- )
42
- )
43
- );
44
- }
45
-
46
23
  #define MAX_RAISE_MESSAGE_SIZE 256
47
24
 
48
25
  struct raise_arguments {
@@ -255,13 +232,3 @@ VALUE ruby_safe_inspect(VALUE obj) {
255
232
  return rb_str_new_cstr("(Not inspectable)");
256
233
  }
257
234
  }
258
-
259
- VALUE ddtrace_version(void) {
260
- VALUE ddtrace_module = rb_const_get(rb_cObject, rb_intern("Datadog"));
261
- ENFORCE_TYPE(ddtrace_module, T_MODULE);
262
- VALUE version_module = rb_const_get(ddtrace_module, rb_intern("VERSION"));
263
- ENFORCE_TYPE(version_module, T_MODULE);
264
- VALUE version_string = rb_const_get(version_module, rb_intern("STRING"));
265
- ENFORCE_TYPE(version_string, T_STRING);
266
- return version_string;
267
- }
@@ -1,9 +1,7 @@
1
1
  #pragma once
2
2
 
3
- #include <ruby.h>
4
3
  #include <stdbool.h>
5
-
6
- #include "helpers.h"
4
+ #include "datadog_ruby_common.h"
7
5
 
8
6
  // Initialize internal data needed by some ruby helpers. Should be called during start, before any actual
9
7
  // usage of ruby helpers.
@@ -39,27 +37,6 @@ static inline int check_if_pending_exception(void) {
39
37
  return pending_exception;
40
38
  }
41
39
 
42
- #define ADD_QUOTES_HELPER(x) #x
43
- #define ADD_QUOTES(x) ADD_QUOTES_HELPER(x)
44
-
45
- // Ruby has a Check_Type(value, type) that is roughly equivalent to this BUT Ruby's version is rather cryptic when it fails
46
- // e.g. "wrong argument type nil (expected String)". This is a replacement that prints more information to help debugging.
47
- #define ENFORCE_TYPE(value, type) \
48
- { if (RB_UNLIKELY(!RB_TYPE_P(value, type))) raise_unexpected_type(value, ADD_QUOTES(value), ADD_QUOTES(type), __FILE__, __LINE__, __func__); }
49
-
50
- #define ENFORCE_BOOLEAN(value) \
51
- { if (RB_UNLIKELY(value != Qtrue && value != Qfalse)) raise_unexpected_type(value, ADD_QUOTES(value), "true or false", __FILE__, __LINE__, __func__); }
52
-
53
- // Called by ENFORCE_TYPE; should not be used directly
54
- NORETURN(void raise_unexpected_type(
55
- VALUE value,
56
- const char *value_name,
57
- const char *type_name,
58
- const char *file,
59
- int line,
60
- const char *function_name
61
- ));
62
-
63
40
  #define VALUE_COUNT(array) (sizeof(array) / sizeof(VALUE))
64
41
 
65
42
  NORETURN(
@@ -113,5 +90,3 @@ size_t ruby_obj_memsize_of(VALUE obj);
113
90
  // return a string with the result of that call. Elsif the object responds to
114
91
  // 'to_s', return a string with the result of that call. Otherwise, return Qnil.
115
92
  VALUE ruby_safe_inspect(VALUE obj);
116
-
117
- VALUE ddtrace_version(void);
@@ -1,6 +1,7 @@
1
1
  #pragma once
2
2
 
3
3
  #include <signal.h>
4
+ #include "datadog_ruby_common.h"
4
5
 
5
6
  void empty_signal_handler(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED siginfo_t *_info, DDTRACE_UNUSED void *_ucontext);
6
7
  void install_sigprof_signal_handler(void (*signal_handler_function)(int, siginfo_t *, void *), const char *handler_pretty_name);
@@ -342,6 +342,10 @@ static VALUE _native_new(VALUE klass) {
342
342
  // Note: At this point, slot_one_profile and slot_two_profile contain null pointers. Libdatadog validates pointers
343
343
  // before using them so it's ok for us to go ahead and create the StackRecorder object.
344
344
 
345
+ // Note: As of this writing, no new Ruby objects get created and stored in the state. If that ever changes, remember
346
+ // to keep them on the stack and mark them with RB_GC_GUARD -- otherwise it's possible for a GC to run and
347
+ // since the instance representing the state does not yet exist, such objects will not get marked.
348
+
345
349
  VALUE stack_recorder = TypedData_Wrap_Struct(klass, &stack_recorder_typed_data, state);
346
350
 
347
351
  // NOTE: We initialize this because we want a new recorder to be operational even without initialization and our
@@ -612,12 +616,20 @@ void record_sample(VALUE recorder_instance, ddog_prof_Slice_Location locations,
612
616
  metric_values[position_for[ALLOC_SAMPLES_UNSCALED_VALUE_ID]] = values.alloc_samples_unscaled;
613
617
  metric_values[position_for[TIMELINE_VALUE_ID]] = values.timeline_wall_time_ns;
614
618
 
615
- if (values.alloc_samples != 0) {
619
+ if (values.heap_sample) {
616
620
  // If we got an allocation sample end the heap allocation recording to commit the heap sample.
617
621
  // FIXME: Heap sampling currently has to be done in 2 parts because the construction of locations is happening
618
622
  // very late in the allocation-sampling path (which is shared with the cpu sampling path). This can
619
623
  // be fixed with some refactoring but for now this leads to a less impactful change.
620
- end_heap_allocation_recording(state->heap_recorder, locations);
624
+ //
625
+ // NOTE: The heap recorder is allowed to raise exceptions if something's wrong. But we also need to handle it
626
+ // on this side to make sure we properly unlock the active slot mutex on our way out. Otherwise, this would
627
+ // later lead to deadlocks (since the active slot mutex is not expected to be locked forever).
628
+ int exception_state = end_heap_allocation_recording_with_rb_protect(state->heap_recorder, locations);
629
+ if (exception_state) {
630
+ sampler_unlock_active_profile(active_slot);
631
+ rb_jump_tag(exception_state);
632
+ }
621
633
  }
622
634
 
623
635
  ddog_prof_Profile_Result result = ddog_prof_Profile_add(
@@ -9,6 +9,7 @@ typedef struct {
9
9
  uint32_t cpu_or_wall_samples;
10
10
  uint32_t alloc_samples;
11
11
  uint32_t alloc_samples_unscaled;
12
+ bool heap_sample;
12
13
  int64_t timeline_wall_time_ns;
13
14
  } sample_values;
14
15
 
@@ -18,6 +19,7 @@ typedef struct sample_labels {
18
19
  // This is used to allow the `Collectors::Stack` to modify the existing label, if any. This MUST be NULL or point
19
20
  // somewhere inside the labels slice above.
20
21
  ddog_prof_Label *state_label;
22
+ bool is_gvl_waiting_state;
21
23
 
22
24
  int64_t end_timestamp_ns;
23
25
  } sample_labels;
@@ -4,21 +4,6 @@
4
4
  #include "ruby_helpers.h"
5
5
  #include "time_helpers.h"
6
6
 
7
- // Safety: This function is assumed never to raise exceptions by callers when raise_on_failure == false
8
- long retrieve_clock_as_ns(clockid_t clock_id, bool raise_on_failure) {
9
- struct timespec clock_value;
10
-
11
- if (clock_gettime(clock_id, &clock_value) != 0) {
12
- if (raise_on_failure) ENFORCE_SUCCESS_GVL(errno);
13
- return 0;
14
- }
15
-
16
- return clock_value.tv_nsec + SECONDS_AS_NS(clock_value.tv_sec);
17
- }
18
-
19
- long monotonic_wall_time_now_ns(bool raise_on_failure) { return retrieve_clock_as_ns(CLOCK_MONOTONIC, raise_on_failure); }
20
- long system_epoch_time_now_ns(bool raise_on_failure) { return retrieve_clock_as_ns(CLOCK_REALTIME, raise_on_failure); }
21
-
22
7
  // Design: The monotonic_to_system_epoch_state struct is kept somewhere by the caller, and MUST be initialized to
23
8
  // MONOTONIC_TO_SYSTEM_EPOCH_INITIALIZER.
24
9
  //
@@ -1,12 +1,16 @@
1
1
  #pragma once
2
2
 
3
3
  #include <stdbool.h>
4
+ #include <errno.h>
5
+ #include <time.h>
6
+
7
+ #include "extconf.h" // Needed for HAVE_CLOCK_MONOTONIC_COARSE
8
+ #include "ruby_helpers.h"
4
9
 
5
10
  #define SECONDS_AS_NS(value) (value * 1000 * 1000 * 1000L)
6
11
  #define MILLIS_AS_NS(value) (value * 1000 * 1000L)
7
12
 
8
- #define RAISE_ON_FAILURE true
9
- #define DO_NOT_RAISE_ON_FAILURE false
13
+ typedef enum { RAISE_ON_FAILURE, DO_NOT_RAISE_ON_FAILURE } raise_on_failure_setting;
10
14
 
11
15
  #define INVALID_TIME -1
12
16
 
@@ -17,10 +21,36 @@ typedef struct {
17
21
 
18
22
  #define MONOTONIC_TO_SYSTEM_EPOCH_INITIALIZER {.system_epoch_ns_reference = INVALID_TIME, .delta_to_epoch_ns = INVALID_TIME}
19
23
 
20
- // Safety: This function is assumed never to raise exceptions by callers when raise_on_failure == false
21
- long monotonic_wall_time_now_ns(bool raise_on_failure);
24
+ static inline long retrieve_clock_as_ns(clockid_t clock_id, raise_on_failure_setting raise_on_failure) {
25
+ struct timespec clock_value;
26
+
27
+ if (clock_gettime(clock_id, &clock_value) != 0) {
28
+ if (raise_on_failure == RAISE_ON_FAILURE) ENFORCE_SUCCESS_GVL(errno);
29
+ return 0;
30
+ }
31
+
32
+ return clock_value.tv_nsec + SECONDS_AS_NS(clock_value.tv_sec);
33
+ }
34
+
35
+ static inline long monotonic_wall_time_now_ns(raise_on_failure_setting raise_on_failure) { return retrieve_clock_as_ns(CLOCK_MONOTONIC, raise_on_failure); }
36
+ static inline long system_epoch_time_now_ns(raise_on_failure_setting raise_on_failure) { return retrieve_clock_as_ns(CLOCK_REALTIME, raise_on_failure); }
37
+
38
+ // Coarse instants use CLOCK_MONOTONIC_COARSE on Linux which is expected to provide resolution in the millisecond range:
39
+ // https://docs.redhat.com/en/documentation/red_hat_enterprise_linux_for_real_time/7/html/reference_guide/sect-posix_clocks#Using_clock_getres_to_compare_clock_resolution
40
+ // We introduce here a separate type for it, so as to make it harder to misuse/more explicit when these timestamps are used
41
+
42
+ typedef struct coarse_instant {
43
+ long timestamp_ns;
44
+ } coarse_instant;
45
+
46
+ static inline coarse_instant to_coarse_instant(long timestamp_ns) { return (coarse_instant) {.timestamp_ns = timestamp_ns}; }
22
47
 
23
- // Safety: This function is assumed never to raise exceptions by callers when raise_on_failure == false
24
- long system_epoch_time_now_ns(bool raise_on_failure);
48
+ static inline coarse_instant monotonic_coarse_wall_time_now_ns(void) {
49
+ #ifdef HAVE_CLOCK_MONOTONIC_COARSE // Linux
50
+ return to_coarse_instant(retrieve_clock_as_ns(CLOCK_MONOTONIC_COARSE, DO_NOT_RAISE_ON_FAILURE));
51
+ #else // macOS
52
+ return to_coarse_instant(retrieve_clock_as_ns(CLOCK_MONOTONIC, DO_NOT_RAISE_ON_FAILURE));
53
+ #endif
54
+ }
25
55
 
26
56
  long monotonic_to_system_epoch_ns(monotonic_to_system_epoch_state *state, long monotonic_wall_time_ns);