ddtrace 1.19.0 → 1.20.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (125) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -1
  3. data/ext/ddtrace_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +38 -23
  4. data/ext/ddtrace_profiling_native_extension/collectors_discrete_dynamic_sampler.c +349 -0
  5. data/ext/ddtrace_profiling_native_extension/collectors_discrete_dynamic_sampler.h +89 -0
  6. data/ext/ddtrace_profiling_native_extension/extconf.rb +3 -0
  7. data/ext/ddtrace_profiling_native_extension/helpers.h +4 -0
  8. data/ext/ddtrace_profiling_native_extension/profiling.c +16 -0
  9. data/ext/ddtrace_profiling_native_extension/time_helpers.h +2 -0
  10. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +2 -1
  11. data/lib/datadog/core/configuration/settings.rb +22 -7
  12. data/lib/datadog/core/environment/class_count.rb +6 -6
  13. data/lib/datadog/core/remote/component.rb +25 -12
  14. data/lib/datadog/core/remote/ext.rb +1 -0
  15. data/lib/datadog/core/remote/tie/tracing.rb +39 -0
  16. data/lib/datadog/core/remote/tie.rb +27 -0
  17. data/lib/datadog/opentelemetry/sdk/propagator.rb +3 -2
  18. data/lib/datadog/opentelemetry.rb +3 -0
  19. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +0 -2
  20. data/lib/datadog/profiling/component.rb +3 -17
  21. data/lib/datadog/tracing/configuration/ext.rb +0 -1
  22. data/lib/datadog/tracing/configuration/settings.rb +2 -1
  23. data/lib/datadog/tracing/contrib/action_cable/configuration/settings.rb +1 -0
  24. data/lib/datadog/tracing/contrib/action_cable/ext.rb +1 -0
  25. data/lib/datadog/tracing/contrib/action_mailer/configuration/settings.rb +1 -0
  26. data/lib/datadog/tracing/contrib/action_mailer/ext.rb +1 -0
  27. data/lib/datadog/tracing/contrib/action_pack/configuration/settings.rb +1 -0
  28. data/lib/datadog/tracing/contrib/action_pack/ext.rb +1 -0
  29. data/lib/datadog/tracing/contrib/action_view/configuration/settings.rb +1 -0
  30. data/lib/datadog/tracing/contrib/action_view/ext.rb +1 -0
  31. data/lib/datadog/tracing/contrib/active_job/configuration/settings.rb +1 -0
  32. data/lib/datadog/tracing/contrib/active_job/ext.rb +1 -0
  33. data/lib/datadog/tracing/contrib/active_model_serializers/configuration/settings.rb +1 -0
  34. data/lib/datadog/tracing/contrib/active_model_serializers/ext.rb +1 -0
  35. data/lib/datadog/tracing/contrib/active_record/configuration/settings.rb +1 -0
  36. data/lib/datadog/tracing/contrib/active_record/ext.rb +1 -0
  37. data/lib/datadog/tracing/contrib/active_support/configuration/settings.rb +1 -0
  38. data/lib/datadog/tracing/contrib/active_support/ext.rb +1 -0
  39. data/lib/datadog/tracing/contrib/analytics.rb +0 -1
  40. data/lib/datadog/tracing/contrib/aws/configuration/settings.rb +1 -0
  41. data/lib/datadog/tracing/contrib/aws/ext.rb +1 -0
  42. data/lib/datadog/tracing/contrib/dalli/configuration/settings.rb +1 -0
  43. data/lib/datadog/tracing/contrib/dalli/ext.rb +1 -0
  44. data/lib/datadog/tracing/contrib/delayed_job/configuration/settings.rb +1 -0
  45. data/lib/datadog/tracing/contrib/delayed_job/ext.rb +1 -0
  46. data/lib/datadog/tracing/contrib/elasticsearch/configuration/settings.rb +1 -0
  47. data/lib/datadog/tracing/contrib/elasticsearch/ext.rb +1 -0
  48. data/lib/datadog/tracing/contrib/ethon/configuration/settings.rb +1 -0
  49. data/lib/datadog/tracing/contrib/ethon/ext.rb +1 -0
  50. data/lib/datadog/tracing/contrib/excon/configuration/settings.rb +1 -0
  51. data/lib/datadog/tracing/contrib/excon/ext.rb +1 -0
  52. data/lib/datadog/tracing/contrib/faraday/configuration/settings.rb +7 -0
  53. data/lib/datadog/tracing/contrib/faraday/ext.rb +1 -0
  54. data/lib/datadog/tracing/contrib/faraday/middleware.rb +1 -1
  55. data/lib/datadog/tracing/contrib/grape/configuration/settings.rb +1 -0
  56. data/lib/datadog/tracing/contrib/grape/ext.rb +1 -0
  57. data/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +1 -0
  58. data/lib/datadog/tracing/contrib/graphql/ext.rb +1 -0
  59. data/lib/datadog/tracing/contrib/grpc/configuration/settings.rb +1 -0
  60. data/lib/datadog/tracing/contrib/grpc/ext.rb +1 -0
  61. data/lib/datadog/tracing/contrib/http/configuration/settings.rb +1 -0
  62. data/lib/datadog/tracing/contrib/http/distributed/fetcher.rb +2 -2
  63. data/lib/datadog/tracing/contrib/http/ext.rb +1 -0
  64. data/lib/datadog/tracing/contrib/httpclient/configuration/settings.rb +1 -0
  65. data/lib/datadog/tracing/contrib/httpclient/ext.rb +1 -0
  66. data/lib/datadog/tracing/contrib/httprb/configuration/settings.rb +1 -0
  67. data/lib/datadog/tracing/contrib/httprb/ext.rb +1 -0
  68. data/lib/datadog/tracing/contrib/kafka/configuration/settings.rb +1 -0
  69. data/lib/datadog/tracing/contrib/kafka/ext.rb +1 -0
  70. data/lib/datadog/tracing/contrib/mongodb/configuration/settings.rb +1 -0
  71. data/lib/datadog/tracing/contrib/mongodb/ext.rb +1 -0
  72. data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +1 -0
  73. data/lib/datadog/tracing/contrib/mysql2/ext.rb +1 -0
  74. data/lib/datadog/tracing/contrib/opensearch/configuration/settings.rb +1 -0
  75. data/lib/datadog/tracing/contrib/opensearch/ext.rb +1 -0
  76. data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +1 -0
  77. data/lib/datadog/tracing/contrib/pg/ext.rb +1 -0
  78. data/lib/datadog/tracing/contrib/presto/configuration/settings.rb +1 -0
  79. data/lib/datadog/tracing/contrib/presto/ext.rb +1 -0
  80. data/lib/datadog/tracing/contrib/qless/configuration/settings.rb +1 -0
  81. data/lib/datadog/tracing/contrib/qless/ext.rb +1 -0
  82. data/lib/datadog/tracing/contrib/que/configuration/settings.rb +1 -0
  83. data/lib/datadog/tracing/contrib/que/ext.rb +1 -0
  84. data/lib/datadog/tracing/contrib/racecar/configuration/settings.rb +1 -0
  85. data/lib/datadog/tracing/contrib/racecar/ext.rb +1 -0
  86. data/lib/datadog/tracing/contrib/rack/configuration/settings.rb +1 -0
  87. data/lib/datadog/tracing/contrib/rack/ext.rb +1 -0
  88. data/lib/datadog/tracing/contrib/rack/middlewares.rb +9 -2
  89. data/lib/datadog/tracing/contrib/rails/configuration/settings.rb +1 -0
  90. data/lib/datadog/tracing/contrib/rails/ext.rb +1 -0
  91. data/lib/datadog/tracing/contrib/rake/configuration/settings.rb +1 -0
  92. data/lib/datadog/tracing/contrib/rake/ext.rb +1 -0
  93. data/lib/datadog/tracing/contrib/redis/configuration/settings.rb +1 -0
  94. data/lib/datadog/tracing/contrib/redis/ext.rb +1 -0
  95. data/lib/datadog/tracing/contrib/redis/instrumentation.rb +2 -2
  96. data/lib/datadog/tracing/contrib/redis/patcher.rb +34 -21
  97. data/lib/datadog/tracing/contrib/resque/configuration/settings.rb +1 -0
  98. data/lib/datadog/tracing/contrib/resque/ext.rb +1 -0
  99. data/lib/datadog/tracing/contrib/rest_client/configuration/settings.rb +1 -0
  100. data/lib/datadog/tracing/contrib/rest_client/ext.rb +1 -0
  101. data/lib/datadog/tracing/contrib/roda/configuration/settings.rb +1 -0
  102. data/lib/datadog/tracing/contrib/roda/ext.rb +1 -0
  103. data/lib/datadog/tracing/contrib/sequel/configuration/settings.rb +1 -0
  104. data/lib/datadog/tracing/contrib/sequel/ext.rb +1 -0
  105. data/lib/datadog/tracing/contrib/shoryuken/configuration/settings.rb +1 -0
  106. data/lib/datadog/tracing/contrib/shoryuken/ext.rb +1 -0
  107. data/lib/datadog/tracing/contrib/sidekiq/configuration/settings.rb +1 -0
  108. data/lib/datadog/tracing/contrib/sidekiq/ext.rb +1 -0
  109. data/lib/datadog/tracing/contrib/sinatra/configuration/settings.rb +1 -0
  110. data/lib/datadog/tracing/contrib/sinatra/ext.rb +1 -0
  111. data/lib/datadog/tracing/contrib/sneakers/configuration/settings.rb +1 -0
  112. data/lib/datadog/tracing/contrib/sneakers/ext.rb +1 -0
  113. data/lib/datadog/tracing/contrib/stripe/configuration/settings.rb +1 -0
  114. data/lib/datadog/tracing/contrib/stripe/ext.rb +1 -0
  115. data/lib/datadog/tracing/contrib/sucker_punch/configuration/settings.rb +1 -0
  116. data/lib/datadog/tracing/contrib/sucker_punch/ext.rb +1 -0
  117. data/lib/datadog/tracing/contrib/trilogy/configuration/settings.rb +58 -0
  118. data/lib/datadog/tracing/contrib/trilogy/ext.rb +27 -0
  119. data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +94 -0
  120. data/lib/datadog/tracing/contrib/trilogy/integration.rb +43 -0
  121. data/lib/datadog/tracing/contrib/trilogy/patcher.rb +31 -0
  122. data/lib/datadog/tracing/contrib.rb +1 -0
  123. data/lib/datadog/tracing.rb +8 -2
  124. data/lib/ddtrace/version.rb +1 -1
  125. metadata +14 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 37ea5c2fe193569e17d13e026b4477dd8806c00df50250fb5f69854c23e6e6a5
4
- data.tar.gz: 858b756d1ef6baddb66f85fb44f3301b317e151d58e4e299390f819621d4ecb8
3
+ metadata.gz: 1baa733b2ad3335ab214de620e849f1eab598effb15d24f336aa20147e6e34be
4
+ data.tar.gz: 684bbf501475346f6e2a32b5a9284d59e193344e23d63b912624de77ac8bbbad
5
5
  SHA512:
6
- metadata.gz: 3d86acc37f0bcb7d3b680c4ee8698b45eeb10c026d7dbcdf65ca5c7991eeb561ab35b40b02e98aa24dd984fd7871da8c4906ccc1aa64f9b8c2d8ad86aded199b
7
- data.tar.gz: 4463963285c39c09e2d1d090fe0f80518107b7b60fdb58a0bd993d11dfbaf64e0131cd774b16483d6bd84b5a7faf4256eb8304c3bf4b08b4b90dadfaa513adb9
6
+ metadata.gz: a10dcafb4ff9e9655e06e149ae83a872a0b614f44d69db15ed120f6aa6990f858bb94897d7b8fe30c1b41fe74dc71f054bdf7f0387ded0c0b66058d0d9a15492
7
+ data.tar.gz: 87be0dc85c2fc044a0f95096d147dbc79dda101babad85bfdfd29a37c42cd9cb70180fdedae00ff95321fe6eec0e4d61a6aaab852c9c6d3f10fc39ff7f06d87c
data/CHANGELOG.md CHANGED
@@ -2,6 +2,27 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [1.20.0] - 2024-02-05
6
+
7
+ ### Added
8
+
9
+ * Tracing: Add `Trilogy` instrumentation ([#3274][])
10
+ * Rack: Add remote configuration boot tags ([#3315][])
11
+ * Faraday: Add `on_error` option ([#3431][])
12
+ * Profiling: Add dynamic allocation sampling ([#3395][])
13
+
14
+ ### Changed
15
+
16
+ * Bump `datadog-ci` dependency to 0.7.0 ([#3408][])
17
+ * Improve performance of gathering ClassCount metric ([#3386][])
18
+
19
+ ### Fixed
20
+
21
+ * OpenTelemetry: Fix internal loading ([#3400][])
22
+ * Core: Fix logger deadlock ([#3426][])
23
+ * Rack: Fix missing active trace ([#3420][])
24
+ * Redis: Fix instance configuration ([#3278][])
25
+
5
26
  ## [1.19.0] - 2024-01-10
6
27
 
7
28
  ### Highlights
@@ -2707,7 +2728,8 @@ Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.3.1
2707
2728
  Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
2708
2729
 
2709
2730
 
2710
- [Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v1.19.0...master
2731
+ [Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v1.20.0...master
2732
+ [1.20.0]: https://github.com/DataDog/dd-trace-rb/compare/v1.19.0...v1.20.0
2711
2733
  [1.19.0]: https://github.com/DataDog/dd-trace-rb/compare/v1.18.0...v1.19.0
2712
2734
  [1.18.0]: https://github.com/DataDog/dd-trace-rb/compare/v1.17.0...v1.18.0
2713
2735
  [1.17.0]: https://github.com/DataDog/dd-trace-rb/compare/v1.16.2...v1.17.0
@@ -3936,6 +3958,8 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
3936
3958
  [#3270]: https://github.com/DataDog/dd-trace-rb/issues/3270
3937
3959
  [#3271]: https://github.com/DataDog/dd-trace-rb/issues/3271
3938
3960
  [#3273]: https://github.com/DataDog/dd-trace-rb/issues/3273
3961
+ [#3274]: https://github.com/DataDog/dd-trace-rb/issues/3274
3962
+ [#3278]: https://github.com/DataDog/dd-trace-rb/issues/3278
3939
3963
  [#3279]: https://github.com/DataDog/dd-trace-rb/issues/3279
3940
3964
  [#3280]: https://github.com/DataDog/dd-trace-rb/issues/3280
3941
3965
  [#3281]: https://github.com/DataDog/dd-trace-rb/issues/3281
@@ -3948,6 +3972,7 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
3948
3972
  [#3308]: https://github.com/DataDog/dd-trace-rb/issues/3308
3949
3973
  [#3310]: https://github.com/DataDog/dd-trace-rb/issues/3310
3950
3974
  [#3313]: https://github.com/DataDog/dd-trace-rb/issues/3313
3975
+ [#3315]: https://github.com/DataDog/dd-trace-rb/issues/3315
3951
3976
  [#3316]: https://github.com/DataDog/dd-trace-rb/issues/3316
3952
3977
  [#3317]: https://github.com/DataDog/dd-trace-rb/issues/3317
3953
3978
  [#3320]: https://github.com/DataDog/dd-trace-rb/issues/3320
@@ -3965,6 +3990,13 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
3965
3990
  [#3366]: https://github.com/DataDog/dd-trace-rb/issues/3366
3966
3991
  [#3373]: https://github.com/DataDog/dd-trace-rb/issues/3373
3967
3992
  [#3374]: https://github.com/DataDog/dd-trace-rb/issues/3374
3993
+ [#3386]: https://github.com/DataDog/dd-trace-rb/issues/3386
3994
+ [#3395]: https://github.com/DataDog/dd-trace-rb/issues/3395
3995
+ [#3400]: https://github.com/DataDog/dd-trace-rb/issues/3400
3996
+ [#3408]: https://github.com/DataDog/dd-trace-rb/issues/3408
3997
+ [#3420]: https://github.com/DataDog/dd-trace-rb/issues/3420
3998
+ [#3426]: https://github.com/DataDog/dd-trace-rb/issues/3426
3999
+ [#3431]: https://github.com/DataDog/dd-trace-rb/issues/3431
3968
4000
  [@AdrianLC]: https://github.com/AdrianLC
3969
4001
  [@Azure7111]: https://github.com/Azure7111
3970
4002
  [@BabyGroot]: https://github.com/BabyGroot
@@ -12,10 +12,14 @@
12
12
  #include "collectors_thread_context.h"
13
13
  #include "collectors_dynamic_sampling_rate.h"
14
14
  #include "collectors_idle_sampling_helper.h"
15
+ #include "collectors_discrete_dynamic_sampler.h"
15
16
  #include "private_vm_api_access.h"
16
17
  #include "setup_signal_handler.h"
17
18
  #include "time_helpers.h"
18
19
 
20
+ // Maximum allowed value for an allocation weight. Attempts to use higher values will result in clamping.
21
+ unsigned int MAX_ALLOC_WEIGHT = 65535;
22
+
19
23
  // Used to trigger the execution of Collectors::ThreadState, which implements all of the sampling logic
20
24
  // itself; this class only implements the "when to do it" part.
21
25
  //
@@ -89,13 +93,13 @@ struct cpu_and_wall_time_worker_state {
89
93
  bool gc_profiling_enabled;
90
94
  bool no_signals_workaround_enabled;
91
95
  bool dynamic_sampling_rate_enabled;
92
- int allocation_sample_every;
93
96
  bool allocation_profiling_enabled;
94
97
  VALUE self_instance;
95
98
  VALUE thread_context_collector_instance;
96
99
  VALUE idle_sampling_helper_instance;
97
100
  VALUE owner_thread;
98
- dynamic_sampling_rate_state dynamic_sampling_rate;
101
+ dynamic_sampling_rate_state cpu_dynamic_sampling_rate;
102
+ discrete_dynamic_sampler allocation_sampler;
99
103
  VALUE gc_tracepoint; // Used to get gc start/finish information
100
104
  VALUE object_allocation_tracepoint; // Used to get allocation counts and allocation profiling
101
105
 
@@ -159,7 +163,6 @@ static VALUE _native_initialize(
159
163
  VALUE no_signals_workaround_enabled,
160
164
  VALUE dynamic_sampling_rate_enabled,
161
165
  VALUE dynamic_sampling_rate_overhead_target_percentage,
162
- VALUE allocation_sample_every,
163
166
  VALUE allocation_profiling_enabled
164
167
  );
165
168
  static void cpu_and_wall_time_worker_typed_data_mark(void *state_ptr);
@@ -244,7 +247,7 @@ void collectors_cpu_and_wall_time_worker_init(VALUE profiling_module) {
244
247
  // https://bugs.ruby-lang.org/issues/18007 for a discussion around this.
245
248
  rb_define_alloc_func(collectors_cpu_and_wall_time_worker_class, _native_new);
246
249
 
247
- rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_initialize", _native_initialize, 9);
250
+ rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_initialize", _native_initialize, 8);
248
251
  rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_sampling_loop", _native_sampling_loop, 1);
249
252
  rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_stop", _native_stop, 2);
250
253
  rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_reset_after_fork", _native_reset_after_fork, 1);
@@ -284,12 +287,12 @@ static VALUE _native_new(VALUE klass) {
284
287
  state->gc_profiling_enabled = false;
285
288
  state->no_signals_workaround_enabled = false;
286
289
  state->dynamic_sampling_rate_enabled = true;
287
- state->allocation_sample_every = 0;
288
290
  state->allocation_profiling_enabled = false;
289
291
  state->thread_context_collector_instance = Qnil;
290
292
  state->idle_sampling_helper_instance = Qnil;
291
293
  state->owner_thread = Qnil;
292
- dynamic_sampling_rate_init(&state->dynamic_sampling_rate);
294
+ dynamic_sampling_rate_init(&state->cpu_dynamic_sampling_rate);
295
+ discrete_dynamic_sampler_init(&state->allocation_sampler, "allocation");
293
296
  state->gc_tracepoint = Qnil;
294
297
  state->object_allocation_tracepoint = Qnil;
295
298
 
@@ -313,13 +316,11 @@ static VALUE _native_initialize(
313
316
  VALUE no_signals_workaround_enabled,
314
317
  VALUE dynamic_sampling_rate_enabled,
315
318
  VALUE dynamic_sampling_rate_overhead_target_percentage,
316
- VALUE allocation_sample_every,
317
319
  VALUE allocation_profiling_enabled
318
320
  ) {
319
321
  ENFORCE_BOOLEAN(gc_profiling_enabled);
320
322
  ENFORCE_BOOLEAN(no_signals_workaround_enabled);
321
323
  ENFORCE_BOOLEAN(dynamic_sampling_rate_enabled);
322
- ENFORCE_TYPE(allocation_sample_every, T_FIXNUM);
323
324
  ENFORCE_TYPE(dynamic_sampling_rate_overhead_target_percentage, T_FLOAT);
324
325
  ENFORCE_BOOLEAN(allocation_profiling_enabled);
325
326
 
@@ -329,12 +330,16 @@ static VALUE _native_initialize(
329
330
  state->gc_profiling_enabled = (gc_profiling_enabled == Qtrue);
330
331
  state->no_signals_workaround_enabled = (no_signals_workaround_enabled == Qtrue);
331
332
  state->dynamic_sampling_rate_enabled = (dynamic_sampling_rate_enabled == Qtrue);
332
- dynamic_sampling_rate_set_overhead_target_percentage(&state->dynamic_sampling_rate, NUM2DBL(dynamic_sampling_rate_overhead_target_percentage));
333
- state->allocation_sample_every = NUM2INT(allocation_sample_every);
334
333
  state->allocation_profiling_enabled = (allocation_profiling_enabled == Qtrue);
335
334
 
336
- if (state->allocation_sample_every <= 0) {
337
- rb_raise(rb_eArgError, "Unexpected value for allocation_sample_every: %d. This value must be > 0.", state->allocation_sample_every);
335
+ double total_overhead_target_percentage = NUM2DBL(dynamic_sampling_rate_overhead_target_percentage);
336
+ if (!state->allocation_profiling_enabled) {
337
+ dynamic_sampling_rate_set_overhead_target_percentage(&state->cpu_dynamic_sampling_rate, total_overhead_target_percentage);
338
+ } else {
339
+ // TODO: May be nice to offer customization here? Distribute available "overhead" margin with a bias towards one or the other
340
+ // sampler.
341
+ dynamic_sampling_rate_set_overhead_target_percentage(&state->cpu_dynamic_sampling_rate, total_overhead_target_percentage / 2);
342
+ discrete_dynamic_sampler_set_overhead_target_percentage(&state->allocation_sampler, total_overhead_target_percentage / 2);
338
343
  }
339
344
 
340
345
  state->thread_context_collector_instance = enforce_thread_context_collector_instance(thread_context_collector_instance);
@@ -387,7 +392,8 @@ static VALUE _native_sampling_loop(DDTRACE_UNUSED VALUE _self, VALUE instance) {
387
392
  if (state->stop_thread == rb_thread_current()) return Qnil;
388
393
 
389
394
  // Reset the dynamic sampling rate state, if any (reminder: the monotonic clock reference may change after a fork)
390
- dynamic_sampling_rate_reset(&state->dynamic_sampling_rate);
395
+ dynamic_sampling_rate_reset(&state->cpu_dynamic_sampling_rate);
396
+ discrete_dynamic_sampler_reset(&state->allocation_sampler);
391
397
 
392
398
  // This write to a global is thread-safe BECAUSE we're still holding on to the global VM lock at this point
393
399
  active_sampler_instance_state = state;
@@ -560,7 +566,7 @@ static void *run_sampling_trigger_loop(void *state_ptr) {
560
566
  // Note that we deliberately should NOT combine this sleep_for with the one above because the result of
561
567
  // `dynamic_sampling_rate_get_sleep` may have changed while the above sleep was ongoing.
562
568
  uint64_t extra_sleep =
563
- dynamic_sampling_rate_get_sleep(&state->dynamic_sampling_rate, monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE));
569
+ dynamic_sampling_rate_get_sleep(&state->cpu_dynamic_sampling_rate, monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE));
564
570
  if (state->dynamic_sampling_rate_enabled && extra_sleep > 0) sleep_for(extra_sleep);
565
571
  }
566
572
 
@@ -600,7 +606,7 @@ static VALUE rescued_sample_from_postponed_job(VALUE self_instance) {
600
606
 
601
607
  long wall_time_ns_before_sample = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
602
608
 
603
- if (state->dynamic_sampling_rate_enabled && !dynamic_sampling_rate_should_sample(&state->dynamic_sampling_rate, wall_time_ns_before_sample)) {
609
+ if (state->dynamic_sampling_rate_enabled && !dynamic_sampling_rate_should_sample(&state->cpu_dynamic_sampling_rate, wall_time_ns_before_sample)) {
604
610
  state->stats.skipped_sample_because_of_dynamic_sampling_rate++;
605
611
  return Qnil;
606
612
  }
@@ -620,7 +626,7 @@ static VALUE rescued_sample_from_postponed_job(VALUE self_instance) {
620
626
  state->stats.sampling_time_ns_max = uint64_max_of(sampling_time_ns, state->stats.sampling_time_ns_max);
621
627
  state->stats.sampling_time_ns_total += sampling_time_ns;
622
628
 
623
- dynamic_sampling_rate_after_sample(&state->dynamic_sampling_rate, wall_time_ns_after_sample, sampling_time_ns);
629
+ dynamic_sampling_rate_after_sample(&state->cpu_dynamic_sampling_rate, wall_time_ns_after_sample, sampling_time_ns);
624
630
 
625
631
  // Return a dummy VALUE because we're called from rb_rescue2 which requires it
626
632
  return Qnil;
@@ -931,18 +937,20 @@ static void on_newobj_event(VALUE tracepoint_data, DDTRACE_UNUSED void *unused)
931
937
  return;
932
938
  }
933
939
 
940
+ if (state->dynamic_sampling_rate_enabled && !discrete_dynamic_sampler_should_sample(&state->allocation_sampler)) {
941
+ return;
942
+ }
943
+
934
944
  // @ivoanjo: Strictly speaking, this is not needed because Ruby should not call the same tracepoint while a previous
935
945
  // invocation is still pending, (e.g. it wouldn't call `on_newobj_event` while it's already running), but I decided
936
946
  // to keep this here for consistency -- every call to the thread context (other than the special gc calls which are
937
947
  // defined as not being able to allocate) sets this.
938
948
  state->during_sample = true;
939
949
 
940
- // TODO: This is a placeholder sampling decision strategy. We plan to replace it with a better one soon (e.g. before
941
- // beta), and having something here allows us to test the rest of feature, sampling decision aside.
942
- if (allocation_count % state->allocation_sample_every == 0) {
943
- // Rescue against any exceptions that happen during sampling
944
- safely_call(rescued_sample_allocation, tracepoint_data, state->self_instance);
945
- }
950
+ // Rescue against any exceptions that happen during sampling
951
+ safely_call(rescued_sample_allocation, tracepoint_data, state->self_instance);
952
+
953
+ discrete_dynamic_sampler_after_sample(&state->allocation_sampler);
946
954
 
947
955
  state->during_sample = false;
948
956
  }
@@ -974,7 +982,14 @@ static VALUE rescued_sample_allocation(VALUE tracepoint_data) {
974
982
  rb_trace_arg_t *data = rb_tracearg_from_tracepoint(tracepoint_data);
975
983
  VALUE new_object = rb_tracearg_object(data);
976
984
 
977
- thread_context_collector_sample_allocation(state->thread_context_collector_instance, state->allocation_sample_every, new_object);
985
+ unsigned long allocations_since_last_sample = state->dynamic_sampling_rate_enabled ?
986
+ // if we're doing dynamic sampling, ask the sampler how many events since last sample
987
+ discrete_dynamic_sampler_events_since_last_sample(&state->allocation_sampler) :
988
+ // if we aren't, then we're sampling every event
989
+ 1;
990
+ // TODO: Signal in the profile that clamping happened?
991
+ unsigned int weight = allocations_since_last_sample > MAX_ALLOC_WEIGHT ? MAX_ALLOC_WEIGHT : (unsigned int) allocations_since_last_sample;
992
+ thread_context_collector_sample_allocation(state->thread_context_collector_instance, weight, new_object);
978
993
 
979
994
  // Return a dummy VALUE because we're called from rb_rescue2 which requires it
980
995
  return Qnil;
@@ -0,0 +1,349 @@
1
+ #include "collectors_discrete_dynamic_sampler.h"
2
+
3
+ #include <ruby.h>
4
+ #include "helpers.h"
5
+ #include "time_helpers.h"
6
+ #include "ruby_helpers.h"
7
+
8
+ #define BASE_OVERHEAD_PCT 1.0
9
+ #define BASE_SAMPLING_INTERVAL 50
10
+
11
+ #define ADJUSTMENT_WINDOW_NS SECONDS_AS_NS(1)
12
+
13
+ #define EMA_SMOOTHING_FACTOR 0.6
14
+ #define EXP_MOVING_AVERAGE(last, avg, first) first ? last : (1-EMA_SMOOTHING_FACTOR) * avg + EMA_SMOOTHING_FACTOR * last
15
+
16
+ void discrete_dynamic_sampler_init(discrete_dynamic_sampler *sampler, const char *debug_name) {
17
+ sampler->debug_name = debug_name;
18
+ discrete_dynamic_sampler_set_overhead_target_percentage(sampler, BASE_OVERHEAD_PCT);
19
+ }
20
+
21
+ static void _discrete_dynamic_sampler_reset(discrete_dynamic_sampler *sampler, long now_ns) {
22
+ const char *debug_name = sampler->debug_name;
23
+ double target_overhead = sampler->target_overhead;
24
+ (*sampler) = (discrete_dynamic_sampler) {
25
+ .debug_name = debug_name,
26
+ .target_overhead = target_overhead,
27
+ // Act as if a reset is a readjustment (it kinda is!) and wait for a full adjustment window
28
+ // to compute stats. Otherwise, we'd readjust on the next event that comes and thus be operating
29
+ // with very incomplete information
30
+ .last_readjust_time_ns = now_ns,
31
+ // This fake readjustment will use a hardcoded sampling interval
32
+ .sampling_interval = BASE_SAMPLING_INTERVAL,
33
+ .sampling_probability = 1.0 / BASE_SAMPLING_INTERVAL,
34
+ // But we want to make sure we sample at least once in the next window so that our first
35
+ // real readjustment has some notion of how heavy sampling is. Therefore, we'll make it so that
36
+ // the next event is automatically sampled by artificially locating it in the interval threshold.
37
+ .events_since_last_sample = BASE_SAMPLING_INTERVAL - 1,
38
+ };
39
+ }
40
+
41
+ void discrete_dynamic_sampler_reset(discrete_dynamic_sampler *sampler) {
42
+ long now = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
43
+ _discrete_dynamic_sampler_reset(sampler, now);
44
+ }
45
+
46
+ static void _discrete_dynamic_sampler_set_overhead_target_percentage(discrete_dynamic_sampler *sampler, double target_overhead, long now_ns) {
47
+ if (target_overhead <= 0 || target_overhead > 100) {
48
+ rb_raise(rb_eArgError, "Target overhead must be a double between ]0,100] was %f", target_overhead);
49
+ }
50
+ sampler->target_overhead = target_overhead;
51
+ _discrete_dynamic_sampler_reset(sampler, now_ns);
52
+ }
53
+
54
+ void discrete_dynamic_sampler_set_overhead_target_percentage(discrete_dynamic_sampler *sampler, double target_overhead) {
55
+ long now = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
56
+ _discrete_dynamic_sampler_set_overhead_target_percentage(sampler, target_overhead, now);
57
+ }
58
+
59
+ static void maybe_readjust(discrete_dynamic_sampler *sampler, long now);
60
+
61
+ static bool _discrete_dynamic_sampler_should_sample(discrete_dynamic_sampler *sampler, long now_ns) {
62
+ // For efficiency reasons we don't do true random sampling but rather systematic
63
+ // sampling following a sample interval/skip. This can be biased and hide patterns
64
+ // but the dynamic interval and rather indeterministic pattern of allocations in
65
+ // most real applications should help reduce the bias impact.
66
+ sampler->events_since_last_sample++;
67
+ sampler->events_since_last_readjustment++;
68
+ bool should_sample = sampler->sampling_interval > 0 && sampler->events_since_last_sample >= sampler->sampling_interval;
69
+
70
+ if (should_sample) {
71
+ sampler->sample_start_time_ns = now_ns;
72
+ } else {
73
+ // check if we should readjust our sampler after this event, even if we didn't sample it
74
+ maybe_readjust(sampler, now_ns);
75
+ }
76
+
77
+ return should_sample;
78
+ }
79
+
80
+ bool discrete_dynamic_sampler_should_sample(discrete_dynamic_sampler *sampler) {
81
+ long now = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
82
+ return _discrete_dynamic_sampler_should_sample(sampler, now);
83
+ }
84
+
85
+ static long _discrete_dynamic_sampler_after_sample(discrete_dynamic_sampler *sampler, long now_ns) {
86
+ long last_sampling_time_ns = sampler->sample_start_time_ns == 0 ? 0 : long_max_of(0, now_ns - sampler->sample_start_time_ns);
87
+ sampler->samples_since_last_readjustment++;
88
+ sampler->sampling_time_since_last_readjustment_ns += last_sampling_time_ns;
89
+ sampler->events_since_last_sample = 0;
90
+
91
+ // check if we should readjust our sampler after this sample
92
+ maybe_readjust(sampler, now_ns);
93
+
94
+ return last_sampling_time_ns;
95
+ }
96
+
97
+ long discrete_dynamic_sampler_after_sample(discrete_dynamic_sampler *sampler) {
98
+ long now = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
99
+ return _discrete_dynamic_sampler_after_sample(sampler, now);
100
+ }
101
+
102
+ double discrete_dynamic_sampler_probability(discrete_dynamic_sampler *sampler) {
103
+ return sampler->sampling_probability * 100.;
104
+ }
105
+
106
+ size_t discrete_dynamic_sampler_events_since_last_sample(discrete_dynamic_sampler *sampler) {
107
+ return sampler->events_since_last_sample;
108
+ }
109
+
110
+ static void maybe_readjust(discrete_dynamic_sampler *sampler, long now) {
111
+ long window_time_ns = sampler->last_readjust_time_ns == 0 ? ADJUSTMENT_WINDOW_NS : now - sampler->last_readjust_time_ns;
112
+
113
+ if (window_time_ns < ADJUSTMENT_WINDOW_NS) {
114
+ // not enough time has passed to perform a readjustment
115
+ return;
116
+ }
117
+
118
+ // If we got this far, lets recalculate our sampling params based on new observations
119
+ bool first_readjustment = !sampler->has_completed_full_adjustment_window;
120
+
121
+ // Update our running average of events/sec with latest observation
122
+ sampler->events_per_ns = EXP_MOVING_AVERAGE(
123
+ (double) sampler->events_since_last_readjustment / window_time_ns,
124
+ sampler->events_per_ns,
125
+ first_readjustment
126
+ );
127
+
128
+ // Update our running average of sampling time for a specific event
129
+ long sampling_window_time_ns = sampler->sampling_time_since_last_readjustment_ns;
130
+ long sampling_overshoot_time_ns = -1;
131
+ if (sampler->samples_since_last_readjustment > 0) {
132
+ // We can only update sampling-related stats if we actually sampled on the last window...
133
+
134
+ // Lets update our average sampling time per event
135
+ long avg_sampling_time_in_window_ns = sampler->samples_since_last_readjustment == 0 ? 0 : sampling_window_time_ns / sampler->samples_since_last_readjustment;
136
+ sampler->sampling_time_ns = EXP_MOVING_AVERAGE(
137
+ avg_sampling_time_in_window_ns,
138
+ sampler->sampling_time_ns,
139
+ first_readjustment
140
+ );
141
+ }
142
+
143
+ // Are we meeting our target in practice? If we're consistently overshooting our estimate due to non-uniform allocation patterns lets
144
+ // adjust our overhead target.
145
+ // NOTE: Updating this even when no samples occur is a conscious choice which enables us to cooldown extreme adjustments over time.
146
+ // If we didn't do this, whenever a big spike caused target_overhead_adjustment to equal target_overhead, we'd get stuck
147
+ // in a "probability = 0" state.
148
+ long reference_target_sampling_time_ns = window_time_ns * (sampler->target_overhead / 100.);
149
+ // Overshoot by definition is always >= 0. < 0 would be undershooting!
150
+ sampling_overshoot_time_ns = long_max_of(0, sampler->sampling_time_since_last_readjustment_ns - reference_target_sampling_time_ns);
151
+ // Our overhead adjustment should always be between [-target_overhead, 0]. Higher adjustments would lead to negative overhead targets
152
+ // which don't make much sense.
153
+ double last_target_overhead_adjustment = -double_min_of(sampler->target_overhead, sampling_overshoot_time_ns * 100. / window_time_ns);
154
+ sampler->target_overhead_adjustment = EXP_MOVING_AVERAGE(
155
+ last_target_overhead_adjustment,
156
+ sampler->target_overhead_adjustment,
157
+ first_readjustment
158
+ );
159
+
160
+ // Apply our overhead adjustment to figure out our real targets for this readjustment.
161
+ double target_overhead = double_max_of(0, sampler->target_overhead + sampler->target_overhead_adjustment);
162
+ long target_sampling_time_ns = window_time_ns * (target_overhead / 100.);
163
+
164
+ // Recalculate target sampling probability so that the following 2 hold:
165
+ // * window_time_ns = working_window_time_ns + sampling_window_time_ns
166
+ // │ │ │
167
+ // │ │ └ how much time is spent sampling
168
+ // │ └── how much time is spent doing actual app stuff
169
+ // └── total (wall) time in this adjustment window
170
+ // * sampling_window_time_ns <= window_time_ns * target_overhead / 100
171
+ //
172
+ // Note that
173
+ //
174
+ // sampling_window_time_ns = samples_in_window * sampling_time_ns =
175
+ // ┌─ assuming no events will be emitted during sampling
176
+ // │
177
+ // = events_per_ns * working_window_time_ns * sampling_probability * sampling_time_ns
178
+ //
179
+ // Re-ordering for sampling_probability and solving for the upper-bound of sampling_window_time_ns:
180
+ //
181
+ // sampling_window_time_ns = window_time_ns * target_overhead / 100
182
+ // sampling_probability = window_time_ns * target_overhead / 100 / (events_per_ns * working_window_time_ns * sampling_time_ns) =
183
+ //
184
+ // Which you can intuitively understand as:
185
+ //
186
+ // sampling_probability = max_allowed_time_for_sampling_ns / time_to_sample_all_events_ns
187
+ //
188
+ // As a quick sanity check:
189
+ // * If app is eventing very little or we're sampling very fast, so that time_to_sample_all_events_ns < max_allowed_time_for_sampling_ns
190
+ // then probability will be > 1 (but we should clamp to 1 since probabilities higher than 1 don't make sense).
191
+ // * If app is eventing a lot or our sampling overhead is big, then as time_to_sample_all_events_ns grows, sampling_probability will
192
+ // tend to 0.
193
+ long working_window_time_ns = long_max_of(0, window_time_ns - sampling_window_time_ns);
194
+ double max_allowed_time_for_sampling_ns = target_sampling_time_ns;
195
+ long time_to_sample_all_events_ns = sampler->events_per_ns * working_window_time_ns * sampler->sampling_time_ns;
196
+ if (max_allowed_time_for_sampling_ns == 0) {
197
+ // if we aren't allowed any sampling time at all, probability has to be 0
198
+ sampler->sampling_probability = 0;
199
+ } else {
200
+ // otherwise apply the formula described above (protecting against div by 0)
201
+ sampler->sampling_probability = time_to_sample_all_events_ns == 0 ? 1. :
202
+ double_min_of(1., max_allowed_time_for_sampling_ns / time_to_sample_all_events_ns);
203
+ }
204
+
205
+ // Doing true random selection would involve "tossing a coin" on every allocation. Lets do systematic sampling instead so that our
206
+ // sampling decision can rely solely on a sampling skip/interval (i.e. more efficient).
207
+ //
208
+ // sampling_interval = events / samples =
209
+ // = event_rate * working_window_time_ns / (event_rate * working_window_time_ns * sampling_probability)
210
+ // = 1 / sampling_probability
211
+ //
212
+ // NOTE: The sampling interval has to be an integer since we're dealing with discrete events here. This means that there'll be
213
+ // a loss of precision (and thus control) when adjusting between probabilities that lead to non-integer granularity
214
+ // changes (e.g. probabilities in the range of ]50%, 100%[ which map to intervals in the range of ]1, 2[). Our approach
215
+ // when the sampling interval is a non-integer is to ceil it (i.e. we'll always choose to sample less often).
216
+ // NOTE: Overhead target adjustments or very big sampling times can in theory bring probability so close to 0 as to effectively
217
+ // round down to full 0. This means we have to be careful to handle div-by-0 as well as resulting double intervals that
218
+ // are so big they don't fit into the sampling_interval. In both cases lets just disable sampling until next readjustment
219
+ // by setting interval to 0.
220
+ double sampling_interval = sampler->sampling_probability == 0 ? 0 : ceil(1.0 / sampler->sampling_probability);
221
+ sampler->sampling_interval = sampling_interval > ULONG_MAX ? 0 : sampling_interval;
222
+
223
+ #ifdef DD_DEBUG
224
+ double allocs_in_60s = sampler->events_per_ns * 1e9 * 60;
225
+ double samples_in_60s = allocs_in_60s * sampler->sampling_probability;
226
+ double expected_total_sampling_time_in_60s =
227
+ samples_in_60s * sampler->sampling_time_ns / 1e9;
228
+ double real_total_sampling_time_in_60s = sampling_window_time_ns / 1e9 * 60 / (window_time_ns / 1e9);
229
+
230
+ fprintf(stderr, "[dds.%s] readjusting...\n", sampler->debug_name);
231
+ fprintf(stderr, "samples_since_last_readjustment=%ld\n", sampler->samples_since_last_readjustment);
232
+ fprintf(stderr, "window_time=%ld\n", window_time_ns);
233
+ fprintf(stderr, "events_per_sec=%f\n", sampler->events_per_ns * 1e9);
234
+ fprintf(stderr, "sampling_time=%ld\n", sampler->sampling_time_ns);
235
+ fprintf(stderr, "sampling_window_time=%ld\n", sampling_window_time_ns);
236
+ fprintf(stderr, "sampling_target_time=%ld\n", reference_target_sampling_time_ns);
237
+ fprintf(stderr, "sampling_overshoot_time=%ld\n", sampling_overshoot_time_ns);
238
+ fprintf(stderr, "working_window_time=%ld\n", working_window_time_ns);
239
+ fprintf(stderr, "sampling_interval=%zu\n", sampler->sampling_interval);
240
+ fprintf(stderr, "sampling_probability=%f\n", sampler->sampling_probability);
241
+ fprintf(stderr, "expected allocs in 60s=%f\n", allocs_in_60s);
242
+ fprintf(stderr, "expected samples in 60s=%f\n", samples_in_60s);
243
+ fprintf(stderr, "expected sampling time in 60s=%f (previous real=%f)\n", expected_total_sampling_time_in_60s, real_total_sampling_time_in_60s);
244
+ fprintf(stderr, "target_overhead=%f\n", sampler->target_overhead);
245
+ fprintf(stderr, "target_overhead_adjustment=%f\n", sampler->target_overhead_adjustment);
246
+ fprintf(stderr, "target_sampling_time=%ld\n", target_sampling_time_ns);
247
+ fprintf(stderr, "expected max overhead in 60s=%f\n", target_overhead / 100.0 * 60);
248
+ fprintf(stderr, "-------\n");
249
+ #endif
250
+
251
+ sampler->events_since_last_readjustment = 0;
252
+ sampler->samples_since_last_readjustment = 0;
253
+ sampler->sampling_time_since_last_readjustment_ns = 0;
254
+ sampler->last_readjust_time_ns = now;
255
+ sampler->has_completed_full_adjustment_window = true;
256
+ }
257
+
258
+ // ---
259
+ // Below here is boilerplate to expose the above code to Ruby so that we can test it with RSpec as usual.
260
+
261
+ static VALUE _native_new(VALUE klass);
262
+ static VALUE _native_reset(VALUE self, VALUE now);
263
+ static VALUE _native_set_overhead_target_percentage(VALUE self, VALUE target_overhead, VALUE now);
264
+ static VALUE _native_should_sample(VALUE self, VALUE now);
265
+ static VALUE _native_after_sample(VALUE self, VALUE now);
266
+ static VALUE _native_probability(VALUE self);
267
+
268
+ typedef struct sampler_state {
269
+ discrete_dynamic_sampler sampler;
270
+ } sampler_state;
271
+
272
+ void collectors_discrete_dynamic_sampler_init(VALUE profiling_module) {
273
+ VALUE collectors_module = rb_define_module_under(profiling_module, "Collectors");
274
+ VALUE discrete_sampler_module = rb_define_module_under(collectors_module, "DiscreteDynamicSampler");
275
+ VALUE testing_module = rb_define_module_under(discrete_sampler_module, "Testing");
276
+ VALUE sampler_class = rb_define_class_under(testing_module, "Sampler", rb_cObject);
277
+
278
+ rb_define_alloc_func(sampler_class, _native_new);
279
+
280
+ rb_define_method(sampler_class, "_native_reset", _native_reset, 1);
281
+ rb_define_method(sampler_class, "_native_set_overhead_target_percentage", _native_set_overhead_target_percentage, 2);
282
+ rb_define_method(sampler_class, "_native_should_sample", _native_should_sample, 1);
283
+ rb_define_method(sampler_class, "_native_after_sample", _native_after_sample, 1);
284
+ rb_define_method(sampler_class, "_native_probability", _native_probability, 0);
285
+ }
286
+
287
+ static const rb_data_type_t sampler_typed_data = {
288
+ .wrap_struct_name = "Datadog::Profiling::DiscreteDynamicSampler::Testing::Sampler",
289
+ .function = {
290
+ .dfree = RUBY_DEFAULT_FREE,
291
+ .dsize = NULL,
292
+ },
293
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY
294
+ };
295
+
296
+ static VALUE _native_new(VALUE klass) {
297
+ sampler_state *state = ruby_xcalloc(sizeof(sampler_state), 1);
298
+
299
+ discrete_dynamic_sampler_init(&state->sampler, "test sampler");
300
+
301
+ return TypedData_Wrap_Struct(klass, &sampler_typed_data, state);
302
+ }
303
+
304
+ static VALUE _native_reset(VALUE self, VALUE now_ns) {
305
+ ENFORCE_TYPE(now_ns, T_FIXNUM);
306
+
307
+ sampler_state *state;
308
+ TypedData_Get_Struct(self, sampler_state, &sampler_typed_data, state);
309
+
310
+ _discrete_dynamic_sampler_reset(&state->sampler, NUM2LONG(now_ns));
311
+ return Qtrue;
312
+ }
313
+
314
+ static VALUE _native_set_overhead_target_percentage(VALUE self, VALUE target_overhead, VALUE now_ns) {
315
+ ENFORCE_TYPE(target_overhead, T_FLOAT);
316
+ ENFORCE_TYPE(now_ns, T_FIXNUM);
317
+
318
+ sampler_state *state;
319
+ TypedData_Get_Struct(self, sampler_state, &sampler_typed_data, state);
320
+
321
+ _discrete_dynamic_sampler_set_overhead_target_percentage(&state->sampler, NUM2DBL(target_overhead), NUM2LONG(now_ns));
322
+
323
+ return Qnil;
324
+ }
325
+
326
+ VALUE _native_should_sample(VALUE self, VALUE now_ns) {
327
+ ENFORCE_TYPE(now_ns, T_FIXNUM);
328
+
329
+ sampler_state *state;
330
+ TypedData_Get_Struct(self, sampler_state, &sampler_typed_data, state);
331
+
332
+ return _discrete_dynamic_sampler_should_sample(&state->sampler, NUM2LONG(now_ns)) ? Qtrue : Qfalse;
333
+ }
334
+
335
+ VALUE _native_after_sample(VALUE self, VALUE now_ns) {
336
+ ENFORCE_TYPE(now_ns, T_FIXNUM);
337
+
338
+ sampler_state *state;
339
+ TypedData_Get_Struct(self, sampler_state, &sampler_typed_data, state);
340
+
341
+ return LONG2NUM(_discrete_dynamic_sampler_after_sample(&state->sampler, NUM2LONG(now_ns)));
342
+ }
343
+
344
+ VALUE _native_probability(VALUE self) {
345
+ sampler_state *state;
346
+ TypedData_Get_Struct(self, sampler_state, &sampler_typed_data, state);
347
+
348
+ return DBL2NUM(discrete_dynamic_sampler_probability(&state->sampler));
349
+ }