datadog 2.2.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +51 -2
  3. data/ext/datadog_profiling_loader/extconf.rb +15 -15
  4. data/ext/datadog_profiling_native_extension/clock_id.h +1 -0
  5. data/ext/datadog_profiling_native_extension/clock_id_from_pthread.c +1 -2
  6. data/ext/datadog_profiling_native_extension/clock_id_noop.c +1 -2
  7. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +113 -43
  8. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +49 -26
  9. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.h +34 -4
  10. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +4 -0
  11. data/ext/datadog_profiling_native_extension/collectors_stack.c +49 -37
  12. data/ext/datadog_profiling_native_extension/collectors_stack.h +2 -2
  13. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +81 -19
  14. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +1 -0
  15. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +110 -0
  16. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +57 -0
  17. data/ext/datadog_profiling_native_extension/extconf.rb +65 -60
  18. data/ext/datadog_profiling_native_extension/heap_recorder.c +34 -6
  19. data/ext/datadog_profiling_native_extension/heap_recorder.h +3 -1
  20. data/ext/datadog_profiling_native_extension/helpers.h +6 -17
  21. data/ext/datadog_profiling_native_extension/http_transport.c +3 -3
  22. data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +0 -86
  23. data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +2 -23
  24. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +61 -172
  25. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +64 -138
  26. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +17 -11
  27. data/ext/datadog_profiling_native_extension/profiling.c +0 -2
  28. data/ext/datadog_profiling_native_extension/ruby_helpers.c +0 -33
  29. data/ext/datadog_profiling_native_extension/ruby_helpers.h +1 -26
  30. data/ext/datadog_profiling_native_extension/setup_signal_handler.h +1 -0
  31. data/ext/datadog_profiling_native_extension/stack_recorder.c +14 -2
  32. data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -0
  33. data/ext/datadog_profiling_native_extension/time_helpers.c +0 -15
  34. data/ext/datadog_profiling_native_extension/time_helpers.h +36 -6
  35. data/ext/{datadog_profiling_native_extension → libdatadog_api}/crashtracker.c +19 -6
  36. data/ext/libdatadog_api/datadog_ruby_common.c +110 -0
  37. data/ext/libdatadog_api/datadog_ruby_common.h +57 -0
  38. data/ext/libdatadog_api/extconf.rb +108 -0
  39. data/ext/libdatadog_api/macos_development.md +26 -0
  40. data/ext/libdatadog_extconf_helpers.rb +130 -0
  41. data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +49 -0
  42. data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +73 -0
  43. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +68 -0
  44. data/lib/datadog/appsec/contrib/graphql/integration.rb +41 -0
  45. data/lib/datadog/appsec/contrib/graphql/patcher.rb +37 -0
  46. data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +59 -0
  47. data/lib/datadog/appsec/contrib/rack/gateway/request.rb +1 -1
  48. data/lib/datadog/appsec/processor/actions.rb +1 -1
  49. data/lib/datadog/appsec/response.rb +15 -1
  50. data/lib/datadog/appsec.rb +1 -0
  51. data/lib/datadog/core/configuration/components.rb +14 -12
  52. data/lib/datadog/core/configuration/settings.rb +54 -7
  53. data/lib/datadog/core/crashtracking/agent_base_url.rb +21 -0
  54. data/lib/datadog/core/crashtracking/component.rb +111 -0
  55. data/lib/datadog/core/crashtracking/tag_builder.rb +39 -0
  56. data/lib/datadog/core/diagnostics/environment_logger.rb +8 -11
  57. data/lib/datadog/core/telemetry/component.rb +49 -2
  58. data/lib/datadog/core/telemetry/emitter.rb +9 -11
  59. data/lib/datadog/core/telemetry/event.rb +32 -1
  60. data/lib/datadog/core/telemetry/ext.rb +1 -0
  61. data/lib/datadog/core/telemetry/http/adapters/net.rb +10 -12
  62. data/lib/datadog/core/telemetry/http/ext.rb +3 -0
  63. data/lib/datadog/core/telemetry/http/transport.rb +38 -9
  64. data/lib/datadog/core/telemetry/logging.rb +35 -0
  65. data/lib/datadog/core/utils/at_fork_monkey_patch.rb +102 -0
  66. data/lib/datadog/kit/appsec/events.rb +2 -4
  67. data/lib/datadog/opentelemetry/sdk/span_processor.rb +10 -0
  68. data/lib/datadog/opentelemetry/sdk/trace/span.rb +23 -0
  69. data/lib/datadog/profiling/collectors/code_provenance.rb +7 -7
  70. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +17 -17
  71. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +11 -13
  72. data/lib/datadog/profiling/collectors/info.rb +3 -3
  73. data/lib/datadog/profiling/collectors/thread_context.rb +4 -2
  74. data/lib/datadog/profiling/component.rb +69 -91
  75. data/lib/datadog/profiling/exporter.rb +3 -3
  76. data/lib/datadog/profiling/ext/dir_monkey_patches.rb +3 -3
  77. data/lib/datadog/profiling/ext.rb +21 -21
  78. data/lib/datadog/profiling/flush.rb +1 -1
  79. data/lib/datadog/profiling/http_transport.rb +8 -6
  80. data/lib/datadog/profiling/load_native_extension.rb +5 -5
  81. data/lib/datadog/profiling/preload.rb +1 -1
  82. data/lib/datadog/profiling/profiler.rb +5 -8
  83. data/lib/datadog/profiling/scheduler.rb +31 -25
  84. data/lib/datadog/profiling/tag_builder.rb +2 -2
  85. data/lib/datadog/profiling/tasks/exec.rb +5 -5
  86. data/lib/datadog/profiling/tasks/setup.rb +16 -35
  87. data/lib/datadog/profiling.rb +4 -5
  88. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -0
  89. data/lib/datadog/tracing/contrib/ext.rb +14 -0
  90. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +1 -1
  91. data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +4 -1
  92. data/lib/datadog/tracing/contrib/lograge/patcher.rb +16 -0
  93. data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +5 -0
  94. data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +17 -13
  95. data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +5 -0
  96. data/lib/datadog/tracing/contrib/pg/instrumentation.rb +4 -1
  97. data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +28 -0
  98. data/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb +5 -1
  99. data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +22 -10
  100. data/lib/datadog/tracing/contrib/trilogy/configuration/settings.rb +5 -0
  101. data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +4 -1
  102. data/lib/datadog/tracing/diagnostics/environment_logger.rb +14 -16
  103. data/lib/datadog/tracing/metadata/errors.rb +9 -1
  104. data/lib/datadog/tracing/metadata/ext.rb +4 -0
  105. data/lib/datadog/tracing/pipeline/span_filter.rb +2 -2
  106. data/lib/datadog/tracing/span.rb +9 -2
  107. data/lib/datadog/tracing/span_event.rb +41 -0
  108. data/lib/datadog/tracing/span_operation.rb +6 -2
  109. data/lib/datadog/tracing/transport/serializable_trace.rb +3 -0
  110. data/lib/datadog/version.rb +1 -1
  111. metadata +28 -10
  112. data/lib/datadog/profiling/crashtracker.rb +0 -91
  113. data/lib/datadog/profiling/ext/forking.rb +0 -98
@@ -20,6 +20,9 @@
20
20
 
21
21
  #define EMA_SMOOTHING_FACTOR 0.6
22
22
 
23
+ static void maybe_readjust(discrete_dynamic_sampler *sampler, long now_ns);
24
+ static inline bool should_readjust(discrete_dynamic_sampler *sampler, coarse_instant now);
25
+
23
26
  void discrete_dynamic_sampler_init(discrete_dynamic_sampler *sampler, const char *debug_name, long now_ns) {
24
27
  sampler->debug_name = debug_name;
25
28
  discrete_dynamic_sampler_set_overhead_target_percentage(sampler, BASE_OVERHEAD_PCT, now_ns);
@@ -54,27 +57,25 @@ void discrete_dynamic_sampler_set_overhead_target_percentage(discrete_dynamic_sa
54
57
  return discrete_dynamic_sampler_reset(sampler, now_ns);
55
58
  }
56
59
 
57
- static void maybe_readjust(discrete_dynamic_sampler *sampler, long now);
58
-
59
- bool discrete_dynamic_sampler_should_sample(discrete_dynamic_sampler *sampler, long now_ns) {
60
+ // NOTE: See header for an explanation of when this should get used
61
+ __attribute__((warn_unused_result))
62
+ bool discrete_dynamic_sampler_should_sample(discrete_dynamic_sampler *sampler) {
60
63
  // For efficiency reasons we don't do true random sampling but rather systematic
61
64
  // sampling following a sample interval/skip. This can be biased and hide patterns
62
- // but the dynamic interval and rather indeterministic pattern of allocations in
65
+ // but the dynamic interval and rather nondeterministic pattern of allocations in
63
66
  // most real applications should help reduce the bias impact.
64
67
  sampler->events_since_last_sample++;
65
68
  sampler->events_since_last_readjustment++;
66
- bool should_sample = sampler->sampling_interval > 0 && sampler->events_since_last_sample >= sampler->sampling_interval;
67
69
 
68
- if (should_sample) {
69
- sampler->sample_start_time_ns = now_ns;
70
- } else {
71
- // check if we should readjust our sampler after this event, even if we didn't sample it
72
- maybe_readjust(sampler, now_ns);
73
- }
70
+ return sampler->sampling_interval > 0 && sampler->events_since_last_sample >= sampler->sampling_interval;
71
+ }
74
72
 
75
- return should_sample;
73
+ // NOTE: See header for an explanation of when this should get used
74
+ void discrete_dynamic_sampler_before_sample(discrete_dynamic_sampler *sampler, long now_ns) {
75
+ sampler->sample_start_time_ns = now_ns;
76
76
  }
77
77
 
78
+ // NOTE: See header for an explanation of when this should get used
78
79
  long discrete_dynamic_sampler_after_sample(discrete_dynamic_sampler *sampler, long now_ns) {
79
80
  long last_sampling_time_ns = sampler->sample_start_time_ns == 0 ? 0 : long_max_of(0, now_ns - sampler->sample_start_time_ns);
80
81
  sampler->samples_since_last_readjustment++;
@@ -95,6 +96,11 @@ size_t discrete_dynamic_sampler_events_since_last_sample(discrete_dynamic_sample
95
96
  return sampler->events_since_last_sample;
96
97
  }
97
98
 
99
+ // NOTE: See header for an explanation of when this should get used
100
+ bool discrete_dynamic_sampler_skipped_sample(discrete_dynamic_sampler *sampler, coarse_instant now) {
101
+ return should_readjust(sampler, now);
102
+ }
103
+
98
104
  static double ewma_adj_window(double latest_value, double avg, long current_window_time_ns, bool is_first) {
99
105
  if (is_first) {
100
106
  return latest_value;
@@ -109,19 +115,27 @@ static double ewma_adj_window(double latest_value, double avg, long current_wind
109
115
  return (1-alpha) * avg + alpha * latest_value;
110
116
  }
111
117
 
112
- static void maybe_readjust(discrete_dynamic_sampler *sampler, long now) {
113
- long this_window_time_ns = sampler->last_readjust_time_ns == 0 ? ADJUSTMENT_WINDOW_NS : now - sampler->last_readjust_time_ns;
118
+ static void maybe_readjust(discrete_dynamic_sampler *sampler, long now_ns) {
119
+ if (should_readjust(sampler, to_coarse_instant(now_ns))) discrete_dynamic_sampler_readjust(sampler, now_ns);
120
+ }
121
+
122
+ static inline bool should_readjust(discrete_dynamic_sampler *sampler, coarse_instant now) {
123
+ long this_window_time_ns =
124
+ sampler->last_readjust_time_ns == 0 ? ADJUSTMENT_WINDOW_NS : now.timestamp_ns - sampler->last_readjust_time_ns;
114
125
 
115
126
  bool should_readjust_based_on_time = this_window_time_ns >= ADJUSTMENT_WINDOW_NS;
116
127
  bool should_readjust_based_on_samples = sampler->samples_since_last_readjustment >= ADJUSTMENT_WINDOW_SAMPLES;
117
128
 
118
- if (!should_readjust_based_on_time && !should_readjust_based_on_samples) {
119
- // not enough time or samples have passed to perform a readjustment
120
- return;
121
- }
129
+ return should_readjust_based_on_time || should_readjust_based_on_samples;
130
+ }
131
+
132
+ // NOTE: This method ASSUMES should_readjust == true
133
+ // NOTE: See header for an explanation of when this should get used
134
+ void discrete_dynamic_sampler_readjust(discrete_dynamic_sampler *sampler, long now_ns) {
135
+ long this_window_time_ns = sampler->last_readjust_time_ns == 0 ? ADJUSTMENT_WINDOW_NS : now_ns - sampler->last_readjust_time_ns;
122
136
 
123
- if (this_window_time_ns == 0) {
124
- // should not be possible given previous condition but lets protect against div by 0 below.
137
+ if (this_window_time_ns <= 0) {
138
+ // should not be possible given should_readjust but lets protect against div by 0 below.
125
139
  return;
126
140
  }
127
141
 
@@ -143,7 +157,7 @@ static void maybe_readjust(discrete_dynamic_sampler *sampler, long now) {
143
157
  // Lets update our average sampling time per event
144
158
  long avg_sampling_time_in_window_ns = sampler->samples_since_last_readjustment == 0 ? 0 : sampler->sampling_time_since_last_readjustment_ns / sampler->samples_since_last_readjustment;
145
159
  if (avg_sampling_time_in_window_ns > sampler->max_sampling_time_ns) {
146
- // If the average sampling time in the previous window was deemed unnacceptable, clamp it to the
160
+ // If the average sampling time in the previous window was deemed unacceptable, clamp it to the
147
161
  // maximum acceptable value and register this operation in our counter.
148
162
  // NOTE: This is important so that events like suspensions or system overloads do not lead us to
149
163
  // learn arbitrarily big sampling times which may then result in us not sampling anything
@@ -255,9 +269,7 @@ static void maybe_readjust(discrete_dynamic_sampler *sampler, long now) {
255
269
  double num_this_windows_in_60s = 60 * 1e9 / this_window_time_ns;
256
270
  double real_total_sampling_time_in_60s = sampler->sampling_time_since_last_readjustment_ns * num_this_windows_in_60s / 1e9;
257
271
 
258
- const char* readjustment_reason = should_readjust_based_on_time ? "time" : "samples";
259
-
260
- fprintf(stderr, "[dds.%s] readjusting due to %s...\n", sampler->debug_name, readjustment_reason);
272
+ fprintf(stderr, "[dds.%s] readjusting...\n", sampler->debug_name);
261
273
  fprintf(stderr, "events_since_last_readjustment=%ld\n", sampler->events_since_last_readjustment);
262
274
  fprintf(stderr, "samples_since_last_readjustment=%ld\n", sampler->samples_since_last_readjustment);
263
275
  fprintf(stderr, "this_window_time=%ld\n", this_window_time_ns);
@@ -286,7 +298,7 @@ static void maybe_readjust(discrete_dynamic_sampler *sampler, long now) {
286
298
  sampler->events_since_last_readjustment = 0;
287
299
  sampler->samples_since_last_readjustment = 0;
288
300
  sampler->sampling_time_since_last_readjustment_ns = 0;
289
- sampler->last_readjust_time_ns = now;
301
+ sampler->last_readjust_time_ns = now_ns;
290
302
  sampler->has_completed_full_adjustment_window = true;
291
303
  }
292
304
 
@@ -359,6 +371,10 @@ static VALUE _native_new(VALUE klass) {
359
371
  }
360
372
  discrete_dynamic_sampler_init(&state->sampler, "test sampler", now_ns);
361
373
 
374
+ // Note: As of this writing, no new Ruby objects get created and stored in the state. If that ever changes, remember
375
+ // to keep them on the stack and mark them with RB_GC_GUARD -- otherwise it's possible for a GC to run and
376
+ // since the instance representing the state does not yet exist, such objects will not get marked.
377
+
362
378
  return TypedData_Wrap_Struct(klass, &sampler_typed_data, state);
363
379
  }
364
380
 
@@ -402,7 +418,14 @@ VALUE _native_should_sample(VALUE self, VALUE now_ns) {
402
418
  sampler_state *state;
403
419
  TypedData_Get_Struct(self, sampler_state, &sampler_typed_data, state);
404
420
 
405
- return discrete_dynamic_sampler_should_sample(&state->sampler, NUM2LONG(now_ns)) ? Qtrue : Qfalse;
421
+ if (discrete_dynamic_sampler_should_sample(&state->sampler)) {
422
+ discrete_dynamic_sampler_before_sample(&state->sampler, NUM2LONG(now_ns));
423
+ return Qtrue;
424
+ } else {
425
+ bool needs_readjust = discrete_dynamic_sampler_skipped_sample(&state->sampler, to_coarse_instant(NUM2LONG(now_ns)));
426
+ if (needs_readjust) discrete_dynamic_sampler_readjust(&state->sampler, NUM2LONG(now_ns));
427
+ return Qfalse;
428
+ }
406
429
  }
407
430
 
408
431
  VALUE _native_after_sample(VALUE self, VALUE now_ns) {
@@ -5,6 +5,8 @@
5
5
 
6
6
  #include <ruby.h>
7
7
 
8
+ #include "time_helpers.h"
9
+
8
10
  // A sampler that will sample discrete events based on the overhead of their
9
11
  // sampling.
10
12
  //
@@ -62,7 +64,6 @@ typedef struct discrete_dynamic_sampler {
62
64
  unsigned long sampling_time_clamps;
63
65
  } discrete_dynamic_sampler;
64
66
 
65
-
66
67
  // Init a new sampler with sane defaults.
67
68
  void discrete_dynamic_sampler_init(discrete_dynamic_sampler *sampler, const char *debug_name, long now_ns);
68
69
 
@@ -80,9 +81,38 @@ void discrete_dynamic_sampler_set_overhead_target_percentage(discrete_dynamic_sa
80
81
  // @return True if the event associated with this decision should be sampled, false
81
82
  // otherwise.
82
83
  //
83
- // NOTE: If true is returned we implicitly assume the start of a sampling operation
84
- // and it is expected that a follow-up after_sample call is issued.
85
- bool discrete_dynamic_sampler_should_sample(discrete_dynamic_sampler *sampler, long now_ns);
84
+ // IMPORTANT: A call to this method MUST be followed by a call to either
85
+ // `discrete_dynamic_sampler_before_sample` if return is `true` or
86
+ // `discrete_dynamic_sampler_skipped_sample` if return is `false`.
87
+ //
88
+ // In the past, this method took care of before_sample/skipped_sample/readjust as well.
89
+ // We've had to split it up because we wanted to both use coarse time and regular monotonic time depending on the
90
+ // situation, but also wanted time to come as an argument from the outside.
91
+ //
92
+ // TL;DR here's how they should be used as Ruby code:
93
+ // if discrete_dynamic_sampler_should_sample
94
+ // discrete_dynamic_sampler_before_sample(monotonic_wall_time_now_ns())
95
+ // else
96
+ // needs_readjust = discrete_dynamic_sampler_skipped_sample(monotonic_coarse_wall_time_now_ns())
97
+ // discrete_dynamic_sampler_readjust(monotonic_wall_time_now_ns()) if needs_readjust
98
+ // end
99
+ __attribute__((warn_unused_result))
100
+ bool discrete_dynamic_sampler_should_sample(discrete_dynamic_sampler *sampler);
101
+
102
+ // Signal the start of a sampling operation.
103
+ // MUST be called after `discrete_dynamic_sampler_should_sample` returns `true`.
104
+ void discrete_dynamic_sampler_before_sample(discrete_dynamic_sampler *sampler, long now_ns);
105
+
106
+ // Signals that sampling did not happen
107
+ // MUST be called after `discrete_dynamic_sampler_skipped_sample` returns `false`.
108
+ //
109
+ // @return True if the sampler needs to be readjusted.
110
+ //
111
+ // IMPORTANT: A call to this method MUST be followed by a call to `discrete_dynamic_sampler_readjust` if return is `true`.
112
+ __attribute__((warn_unused_result))
113
+ bool discrete_dynamic_sampler_skipped_sample(discrete_dynamic_sampler *sampler, coarse_instant now);
114
+
115
+ void discrete_dynamic_sampler_readjust(discrete_dynamic_sampler *sampler, long now_ns);
86
116
 
87
117
  // Signal the end of a sampling operation.
88
118
  //
@@ -83,6 +83,10 @@ static VALUE _native_new(VALUE klass) {
83
83
 
84
84
  reset_state(state);
85
85
 
86
+ // Note: As of this writing, no new Ruby objects get created and stored in the state. If that ever changes, remember
87
+ // to keep them on the stack and mark them with RB_GC_GUARD -- otherwise it's possible for a GC to run and
88
+ // since the instance representing the state does not yet exist, such objects will not get marked.
89
+
86
90
  return TypedData_Wrap_Struct(klass, &idle_sampling_helper_typed_data, state);
87
91
  }
88
92
 
@@ -15,11 +15,9 @@ static VALUE missing_string = Qnil;
15
15
 
16
16
  // Used as scratch space during sampling
17
17
  struct sampling_buffer {
18
- unsigned int max_frames;
19
- VALUE *stack_buffer;
20
- int *lines_buffer;
21
- bool *is_ruby_frame;
18
+ uint16_t max_frames;
22
19
  ddog_prof_Location *locations;
20
+ frame_info *stack_buffer;
23
21
  }; // Note: typedef'd in the header to sampling_buffer
24
22
 
25
23
  static VALUE _native_sample(
@@ -33,9 +31,14 @@ static VALUE _native_sample(
33
31
  VALUE in_gc
34
32
  );
35
33
  static void maybe_add_placeholder_frames_omitted(VALUE thread, sampling_buffer* buffer, char *frames_omitted_message, int frames_omitted_message_size);
36
- static void record_placeholder_stack_in_native_code(sampling_buffer* buffer, VALUE recorder_instance, sample_values values, sample_labels labels);
34
+ static void record_placeholder_stack_in_native_code(VALUE recorder_instance, sample_values values, sample_labels labels);
37
35
  static void maybe_trim_template_random_ids(ddog_CharSlice *name_slice, ddog_CharSlice *filename_slice);
38
36
 
37
+ // These two functions are exposed as symbols by the VM but are not in any header.
38
+ // Their signatures actually take a `const rb_iseq_t *iseq` but it gets casted back and forth between VALUE.
39
+ extern VALUE rb_iseq_path(const VALUE);
40
+ extern VALUE rb_iseq_base_label(const VALUE);
41
+
39
42
  void collectors_stack_init(VALUE profiling_module) {
40
43
  VALUE collectors_module = rb_define_module_under(profiling_module, "Collectors");
41
44
  VALUE collectors_stack_class = rb_define_class_under(collectors_module, "Stack", rb_cObject);
@@ -65,6 +68,8 @@ static VALUE _native_sample(
65
68
  ENFORCE_TYPE(numeric_labels_array, T_ARRAY);
66
69
 
67
70
  VALUE zero = INT2NUM(0);
71
+ VALUE heap_sample = rb_hash_lookup2(metric_values_hash, rb_str_new_cstr("heap_sample"), Qfalse);
72
+ ENFORCE_BOOLEAN(heap_sample);
68
73
  sample_values values = {
69
74
  .cpu_time_ns = NUM2UINT(rb_hash_lookup2(metric_values_hash, rb_str_new_cstr("cpu-time"), zero)),
70
75
  .cpu_or_wall_samples = NUM2UINT(rb_hash_lookup2(metric_values_hash, rb_str_new_cstr("cpu-samples"), zero)),
@@ -72,6 +77,7 @@ static VALUE _native_sample(
72
77
  .alloc_samples = NUM2UINT(rb_hash_lookup2(metric_values_hash, rb_str_new_cstr("alloc-samples"), zero)),
73
78
  .alloc_samples_unscaled = NUM2UINT(rb_hash_lookup2(metric_values_hash, rb_str_new_cstr("alloc-samples-unscaled"), zero)),
74
79
  .timeline_wall_time_ns = NUM2UINT(rb_hash_lookup2(metric_values_hash, rb_str_new_cstr("timeline"), zero)),
80
+ .heap_sample = heap_sample == Qtrue,
75
81
  };
76
82
 
77
83
  long labels_count = RARRAY_LEN(labels_array) + RARRAY_LEN(numeric_labels_array);
@@ -102,13 +108,13 @@ static VALUE _native_sample(
102
108
  int max_frames_requested = NUM2INT(max_frames);
103
109
  if (max_frames_requested < 0) rb_raise(rb_eArgError, "Invalid max_frames: value must not be negative");
104
110
 
105
- sampling_buffer *buffer = sampling_buffer_new(max_frames_requested);
111
+ ddog_prof_Location *locations = ruby_xcalloc(max_frames_requested, sizeof(ddog_prof_Location));
112
+ sampling_buffer *buffer = sampling_buffer_new(max_frames_requested, locations);
106
113
 
107
114
  ddog_prof_Slice_Label slice_labels = {.ptr = labels, .len = labels_count};
108
115
 
109
116
  if (in_gc == Qtrue) {
110
117
  record_placeholder_stack(
111
- buffer,
112
118
  recorder_instance,
113
119
  values,
114
120
  (sample_labels) {.labels = slice_labels, .state_label = state_label},
@@ -124,6 +130,7 @@ static VALUE _native_sample(
124
130
  );
125
131
  }
126
132
 
133
+ ruby_xfree(locations);
127
134
  sampling_buffer_free(buffer);
128
135
 
129
136
  return Qtrue;
@@ -151,22 +158,28 @@ void sample_thread(
151
158
  thread,
152
159
  0 /* stack starting depth */,
153
160
  buffer->max_frames,
154
- buffer->stack_buffer,
155
- buffer->lines_buffer,
156
- buffer->is_ruby_frame
161
+ buffer->stack_buffer
157
162
  );
158
163
 
159
164
  if (captured_frames == PLACEHOLDER_STACK_IN_NATIVE_CODE) {
160
- record_placeholder_stack_in_native_code(buffer, recorder_instance, values, labels);
165
+ record_placeholder_stack_in_native_code(recorder_instance, values, labels);
161
166
  return;
162
167
  }
163
168
 
169
+ // if (captured_frames > 0) {
170
+ // int cache_hits = 0;
171
+ // for (int i = 0; i < captured_frames; i++) {
172
+ // if (buffer->stack_buffer[i].same_frame) cache_hits++;
173
+ // }
174
+ // fprintf(stderr, "Sampling cache hits: %f\n", ((double) cache_hits / captured_frames) * 100);
175
+ // }
176
+
164
177
  // Ruby does not give us path and line number for methods implemented using native code.
165
178
  // The convention in Kernel#caller_locations is to instead use the path and line number of the first Ruby frame
166
179
  // on the stack that is below (e.g. directly or indirectly has called) the native method.
167
180
  // Thus, we keep that frame here to able to replicate that behavior.
168
- // (This is why we also iterate the sampling buffers backwards below -- so that it's easier to keep the last_ruby_frame)
169
- VALUE last_ruby_frame = Qnil;
181
+ // (This is why we also iterate the sampling buffers backwards below -- so that it's easier to keep the last_ruby_frame_filename)
182
+ VALUE last_ruby_frame_filename = Qnil;
170
183
  int last_ruby_line = 0;
171
184
 
172
185
  ddog_prof_Label *state_label = labels.state_label;
@@ -182,16 +195,16 @@ void sample_thread(
182
195
  VALUE name, filename;
183
196
  int line;
184
197
 
185
- if (buffer->is_ruby_frame[i]) {
186
- last_ruby_frame = buffer->stack_buffer[i];
187
- last_ruby_line = buffer->lines_buffer[i];
198
+ if (buffer->stack_buffer[i].is_ruby_frame) {
199
+ name = rb_iseq_base_label(buffer->stack_buffer[i].as.ruby_frame.iseq);
200
+ filename = rb_iseq_path(buffer->stack_buffer[i].as.ruby_frame.iseq);
201
+ line = buffer->stack_buffer[i].as.ruby_frame.line;
188
202
 
189
- name = rb_profile_frame_base_label(buffer->stack_buffer[i]);
190
- filename = rb_profile_frame_path(buffer->stack_buffer[i]);
191
- line = buffer->lines_buffer[i];
203
+ last_ruby_frame_filename = filename;
204
+ last_ruby_line = line;
192
205
  } else {
193
- name = ddtrace_rb_profile_frame_method_name(buffer->stack_buffer[i]);
194
- filename = NIL_P(last_ruby_frame) ? Qnil : rb_profile_frame_path(last_ruby_frame);
206
+ name = rb_id2str(buffer->stack_buffer[i].as.native_frame.method_id);
207
+ filename = last_ruby_frame_filename;
195
208
  line = last_ruby_line;
196
209
  }
197
210
 
@@ -211,7 +224,7 @@ void sample_thread(
211
224
  // approximation, and in the future we hope to replace this with a more accurate approach (such as using the
212
225
  // GVL instrumentation API.)
213
226
  if (top_of_the_stack && only_wall_time) {
214
- if (!buffer->is_ruby_frame[i]) {
227
+ if (!buffer->stack_buffer[i].is_ruby_frame) {
215
228
  // We know that known versions of Ruby implement these using native code; thus if we find a method with the
216
229
  // same name that is not native code, we ignore it, as it's probably a user method that coincidentally
217
230
  // has the same name. Thus, even though "matching just by method name" is kinda weak,
@@ -352,13 +365,11 @@ static void maybe_add_placeholder_frames_omitted(VALUE thread, sampling_buffer*
352
365
  // with one containing a placeholder frame, so that these threads are properly represented in the UX.
353
366
 
354
367
  static void record_placeholder_stack_in_native_code(
355
- sampling_buffer* buffer,
356
368
  VALUE recorder_instance,
357
369
  sample_values values,
358
370
  sample_labels labels
359
371
  ) {
360
372
  record_placeholder_stack(
361
- buffer,
362
373
  recorder_instance,
363
374
  values,
364
375
  labels,
@@ -367,36 +378,39 @@ static void record_placeholder_stack_in_native_code(
367
378
  }
368
379
 
369
380
  void record_placeholder_stack(
370
- sampling_buffer* buffer,
371
381
  VALUE recorder_instance,
372
382
  sample_values values,
373
383
  sample_labels labels,
374
384
  ddog_CharSlice placeholder_stack
375
385
  ) {
376
- ddog_prof_Function placeholder = {.name = DDOG_CHARSLICE_C(""), .filename = placeholder_stack};
377
- buffer->locations[0] = (ddog_prof_Location) {.function = placeholder, .line = 0};
386
+ ddog_prof_Location placeholder_location = {
387
+ .function = {.name = DDOG_CHARSLICE_C(""), .filename = placeholder_stack},
388
+ .line = 0,
389
+ };
378
390
 
379
391
  record_sample(
380
392
  recorder_instance,
381
- (ddog_prof_Slice_Location) {.ptr = buffer->locations, .len = 1},
393
+ (ddog_prof_Slice_Location) {.ptr = &placeholder_location, .len = 1},
382
394
  values,
383
395
  labels
384
396
  );
385
397
  }
386
398
 
387
- sampling_buffer *sampling_buffer_new(unsigned int max_frames) {
399
+ uint16_t sampling_buffer_check_max_frames(int max_frames) {
388
400
  if (max_frames < 5) rb_raise(rb_eArgError, "Invalid max_frames: value must be >= 5");
389
401
  if (max_frames > MAX_FRAMES_LIMIT) rb_raise(rb_eArgError, "Invalid max_frames: value must be <= " MAX_FRAMES_LIMIT_AS_STRING);
402
+ return max_frames;
403
+ }
404
+
405
+ sampling_buffer *sampling_buffer_new(uint16_t max_frames, ddog_prof_Location *locations) {
406
+ sampling_buffer_check_max_frames(max_frames);
390
407
 
391
408
  // Note: never returns NULL; if out of memory, it calls the Ruby out-of-memory handlers
392
409
  sampling_buffer* buffer = ruby_xcalloc(1, sizeof(sampling_buffer));
393
410
 
394
411
  buffer->max_frames = max_frames;
395
-
396
- buffer->stack_buffer = ruby_xcalloc(max_frames, sizeof(VALUE));
397
- buffer->lines_buffer = ruby_xcalloc(max_frames, sizeof(int));
398
- buffer->is_ruby_frame = ruby_xcalloc(max_frames, sizeof(bool));
399
- buffer->locations = ruby_xcalloc(max_frames, sizeof(ddog_prof_Location));
412
+ buffer->locations = locations;
413
+ buffer->stack_buffer = ruby_xcalloc(max_frames, sizeof(frame_info));
400
414
 
401
415
  return buffer;
402
416
  }
@@ -404,10 +418,8 @@ sampling_buffer *sampling_buffer_new(unsigned int max_frames) {
404
418
  void sampling_buffer_free(sampling_buffer *buffer) {
405
419
  if (buffer == NULL) rb_raise(rb_eArgError, "sampling_buffer_free called with NULL buffer");
406
420
 
421
+ // buffer->locations are owned by whoever called sampling_buffer_new, not us
407
422
  ruby_xfree(buffer->stack_buffer);
408
- ruby_xfree(buffer->lines_buffer);
409
- ruby_xfree(buffer->is_ruby_frame);
410
- ruby_xfree(buffer->locations);
411
423
 
412
424
  ruby_xfree(buffer);
413
425
  }
@@ -17,11 +17,11 @@ void sample_thread(
17
17
  sample_labels labels
18
18
  );
19
19
  void record_placeholder_stack(
20
- sampling_buffer* buffer,
21
20
  VALUE recorder_instance,
22
21
  sample_values values,
23
22
  sample_labels labels,
24
23
  ddog_CharSlice placeholder_stack
25
24
  );
26
- sampling_buffer *sampling_buffer_new(unsigned int max_frames);
25
+ uint16_t sampling_buffer_check_max_frames(int max_frames);
26
+ sampling_buffer *sampling_buffer_new(uint16_t max_frames, ddog_prof_Location *locations);
27
27
  void sampling_buffer_free(sampling_buffer *buffer);