datadog 2.29.0 → 2.30.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 (103) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -2
  3. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +21 -12
  4. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +9 -7
  5. data/ext/datadog_profiling_native_extension/extconf.rb +4 -24
  6. data/ext/datadog_profiling_native_extension/heap_recorder.c +5 -6
  7. data/ext/datadog_profiling_native_extension/http_transport.c +51 -64
  8. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +0 -13
  9. data/ext/datadog_profiling_native_extension/profiling.c +3 -1
  10. data/ext/datadog_profiling_native_extension/setup_signal_handler.c +24 -8
  11. data/ext/datadog_profiling_native_extension/setup_signal_handler.h +1 -3
  12. data/ext/datadog_profiling_native_extension/stack_recorder.c +29 -43
  13. data/ext/libdatadog_api/crashtracker_report_exception.c +34 -144
  14. data/ext/libdatadog_api/extconf.rb +4 -21
  15. data/ext/libdatadog_extconf_helpers.rb +49 -11
  16. data/lib/datadog/ai_guard/configuration/settings.rb +3 -0
  17. data/lib/datadog/appsec/contrib/active_record/patcher.rb +3 -0
  18. data/lib/datadog/appsec/contrib/devise/integration.rb +1 -1
  19. data/lib/datadog/appsec/contrib/excon/patcher.rb +2 -0
  20. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +1 -1
  21. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +1 -1
  22. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +4 -4
  23. data/lib/datadog/appsec/contrib/rack/integration.rb +1 -1
  24. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +25 -2
  25. data/lib/datadog/appsec/contrib/rack/response_body.rb +36 -0
  26. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +2 -2
  27. data/lib/datadog/appsec/contrib/rails/integration.rb +1 -1
  28. data/lib/datadog/appsec/contrib/rest_client/patcher.rb +2 -0
  29. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +2 -2
  30. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +3 -3
  31. data/lib/datadog/appsec/event.rb +1 -17
  32. data/lib/datadog/appsec/instrumentation/gateway/middleware.rb +2 -3
  33. data/lib/datadog/appsec/instrumentation/gateway.rb +2 -2
  34. data/lib/datadog/appsec/monitor/gateway/watcher.rb +2 -2
  35. data/lib/datadog/core/configuration/option.rb +2 -1
  36. data/lib/datadog/core/configuration/options.rb +1 -1
  37. data/lib/datadog/core/configuration/settings.rb +27 -3
  38. data/lib/datadog/core/configuration/supported_configurations.rb +16 -0
  39. data/lib/datadog/core/crashtracking/component.rb +4 -12
  40. data/lib/datadog/core/process_discovery.rb +5 -0
  41. data/lib/datadog/core/runtime/metrics.rb +1 -2
  42. data/lib/datadog/core/utils/base64.rb +1 -1
  43. data/lib/datadog/core/workers/interval_loop.rb +13 -6
  44. data/lib/datadog/core/workers/queue.rb +0 -4
  45. data/lib/datadog/core/workers/runtime_metrics.rb +9 -1
  46. data/lib/datadog/data_streams/processor.rb +1 -0
  47. data/lib/datadog/di/boot.rb +1 -0
  48. data/lib/datadog/di/component.rb +16 -4
  49. data/lib/datadog/di/instrumenter.rb +10 -6
  50. data/lib/datadog/di/probe_manager.rb +79 -62
  51. data/lib/datadog/di/probe_notification_builder.rb +39 -32
  52. data/lib/datadog/di/probe_notifier_worker.rb +52 -6
  53. data/lib/datadog/di/probe_repository.rb +198 -0
  54. data/lib/datadog/di/remote.rb +5 -6
  55. data/lib/datadog/di/serializer.rb +127 -9
  56. data/lib/datadog/di/transport/http.rb +12 -3
  57. data/lib/datadog/di/transport/input.rb +46 -8
  58. data/lib/datadog/open_feature/configuration.rb +2 -0
  59. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +13 -0
  60. data/lib/datadog/profiling/component.rb +20 -0
  61. data/lib/datadog/profiling/http_transport.rb +5 -6
  62. data/lib/datadog/profiling/profiler.rb +15 -8
  63. data/lib/datadog/tracing/contrib/dalli/integration.rb +4 -1
  64. data/lib/datadog/tracing/contrib/ethon/configuration/settings.rb +5 -1
  65. data/lib/datadog/tracing/contrib/ethon/ext.rb +1 -0
  66. data/lib/datadog/tracing/contrib/excon/configuration/settings.rb +5 -2
  67. data/lib/datadog/tracing/contrib/excon/ext.rb +1 -0
  68. data/lib/datadog/tracing/contrib/faraday/configuration/settings.rb +5 -2
  69. data/lib/datadog/tracing/contrib/faraday/ext.rb +1 -0
  70. data/lib/datadog/tracing/contrib/grape/endpoint.rb +2 -2
  71. data/lib/datadog/tracing/contrib/grape/instrumentation.rb +13 -8
  72. data/lib/datadog/tracing/contrib/grape/patcher.rb +6 -1
  73. data/lib/datadog/tracing/contrib/grpc/configuration/settings.rb +5 -2
  74. data/lib/datadog/tracing/contrib/grpc/ext.rb +1 -0
  75. data/lib/datadog/tracing/contrib/http/configuration/settings.rb +5 -2
  76. data/lib/datadog/tracing/contrib/http/ext.rb +1 -0
  77. data/lib/datadog/tracing/contrib/httpclient/configuration/settings.rb +5 -2
  78. data/lib/datadog/tracing/contrib/httpclient/ext.rb +1 -0
  79. data/lib/datadog/tracing/contrib/httprb/configuration/settings.rb +5 -2
  80. data/lib/datadog/tracing/contrib/httprb/ext.rb +1 -0
  81. data/lib/datadog/tracing/contrib/karafka/configuration/settings.rb +5 -1
  82. data/lib/datadog/tracing/contrib/karafka/ext.rb +1 -0
  83. data/lib/datadog/tracing/contrib/que/configuration/settings.rb +5 -2
  84. data/lib/datadog/tracing/contrib/que/ext.rb +1 -0
  85. data/lib/datadog/tracing/contrib/rack/configuration/settings.rb +5 -1
  86. data/lib/datadog/tracing/contrib/rack/ext.rb +1 -0
  87. data/lib/datadog/tracing/contrib/rails/configuration/settings.rb +5 -2
  88. data/lib/datadog/tracing/contrib/rails/ext.rb +1 -0
  89. data/lib/datadog/tracing/contrib/rest_client/configuration/settings.rb +5 -2
  90. data/lib/datadog/tracing/contrib/rest_client/ext.rb +1 -0
  91. data/lib/datadog/tracing/contrib/sidekiq/configuration/settings.rb +5 -1
  92. data/lib/datadog/tracing/contrib/sidekiq/ext.rb +1 -0
  93. data/lib/datadog/tracing/contrib/sinatra/configuration/settings.rb +5 -1
  94. data/lib/datadog/tracing/contrib/sinatra/ext.rb +1 -0
  95. data/lib/datadog/tracing/contrib/waterdrop/configuration/settings.rb +5 -1
  96. data/lib/datadog/tracing/contrib/waterdrop/ext.rb +1 -0
  97. data/lib/datadog/tracing/metadata/ext.rb +4 -0
  98. data/lib/datadog/tracing/sync_writer.rb +0 -1
  99. data/lib/datadog/tracing/transport/trace_formatter.rb +11 -0
  100. data/lib/datadog/tracing/writer.rb +0 -1
  101. data/lib/datadog/version.rb +1 -1
  102. metadata +8 -7
  103. data/lib/datadog/tracing/workers/trace_writer.rb +0 -204
@@ -9,26 +9,30 @@
9
9
 
10
10
  // Used by Collectors::CpuAndWallTimeWorker to setup SIGPROF signal handlers used for cpu/wall-time profiling.
11
11
 
12
+ static VALUE existing_signal_handler_exception_class = Qnil;
13
+
12
14
  static void install_sigprof_signal_handler_internal(
13
15
  void (*signal_handler_function)(int, siginfo_t *, void *),
14
16
  const char *handler_pretty_name,
15
- void (*signal_handler_to_replace)(int, siginfo_t *, void *)
17
+ void (*signal_handler_to_replace)(int, siginfo_t *, void *),
18
+ bool should_raise_on_failure
16
19
  );
17
20
 
18
21
  void empty_signal_handler(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED siginfo_t *_info, DDTRACE_UNUSED void *_ucontext) { }
19
22
 
20
23
  void install_sigprof_signal_handler(void (*signal_handler_function)(int, siginfo_t *, void *), const char *handler_pretty_name) {
21
- install_sigprof_signal_handler_internal(signal_handler_function, handler_pretty_name, NULL);
24
+ install_sigprof_signal_handler_internal(signal_handler_function, handler_pretty_name, NULL, true);
22
25
  }
23
26
 
24
- void replace_sigprof_signal_handler_with_empty_handler(void (*expected_existing_handler)(int, siginfo_t *, void *)) {
25
- install_sigprof_signal_handler_internal(empty_signal_handler, "empty_signal_handler", expected_existing_handler);
27
+ void replace_sigprof_signal_handler_with_empty_handler(void (*expected_existing_handler)(int, siginfo_t *, void *), bool should_raise_on_failure) {
28
+ install_sigprof_signal_handler_internal(empty_signal_handler, "empty_signal_handler", expected_existing_handler, should_raise_on_failure);
26
29
  }
27
30
 
28
31
  static void install_sigprof_signal_handler_internal(
29
32
  void (*signal_handler_function)(int, siginfo_t *, void *),
30
33
  const char *handler_pretty_name,
31
- void (*signal_handler_to_replace)(int, siginfo_t *, void *)
34
+ void (*signal_handler_to_replace)(int, siginfo_t *, void *),
35
+ bool should_raise_on_failure
32
36
  ) {
33
37
  struct sigaction existing_signal_handler_config = {.sa_sigaction = NULL};
34
38
  struct sigaction signal_handler_config = {
@@ -52,8 +56,7 @@ static void install_sigprof_signal_handler_internal(
52
56
  ) { return; }
53
57
 
54
58
  if (existing_signal_handler_config.sa_handler != NULL || existing_signal_handler_config.sa_sigaction != NULL) {
55
- // An unexpected/unknown signal handler already existed. Currently we don't support this situation, so let's just back out
56
- // of the installation.
59
+ // An unexpected/unknown signal handler already existed.
57
60
 
58
61
  if (sigaction(SIGPROF, &existing_signal_handler_config, NULL) != 0) {
59
62
  rb_exc_raise(
@@ -71,8 +74,12 @@ static void install_sigprof_signal_handler_internal(
71
74
  );
72
75
  }
73
76
 
77
+ // If should_raise_on_failure is false (e.g., during cleanup), just return silently.
78
+ // This can happen if we failed to install our handler earlier due to a pre-existing handler.
79
+ if (!should_raise_on_failure) return;
80
+
74
81
  raise_error(
75
- rb_eRuntimeError,
82
+ existing_signal_handler_exception_class,
76
83
  "Could not install profiling signal handler (%s): There's a pre-existing SIGPROF signal handler",
77
84
  handler_pretty_name
78
85
  );
@@ -119,3 +126,12 @@ VALUE is_sigprof_blocked_in_current_thread(void) {
119
126
  ENFORCE_SUCCESS_GVL(pthread_sigmask(0, NULL, &current_signals));
120
127
  return sigismember(&current_signals, SIGPROF) ? Qtrue : Qfalse;
121
128
  }
129
+
130
+ void setup_signal_handler_init(VALUE profiling_module) {
131
+ existing_signal_handler_exception_class = rb_define_class_under(
132
+ profiling_module,
133
+ "ExistingSignalHandler",
134
+ rb_eRuntimeError
135
+ );
136
+ rb_gc_register_mark_object(existing_signal_handler_exception_class);
137
+ }
@@ -3,12 +3,10 @@
3
3
  #include <signal.h>
4
4
  #include "datadog_ruby_common.h"
5
5
 
6
-
7
-
8
6
  void empty_signal_handler(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED siginfo_t *_info, DDTRACE_UNUSED void *_ucontext);
9
7
 
10
8
  void install_sigprof_signal_handler(void (*signal_handler_function)(int, siginfo_t *, void *), const char *handler_pretty_name);
11
- void replace_sigprof_signal_handler_with_empty_handler(void (*expected_existing_handler)(int, siginfo_t *, void *));
9
+ void replace_sigprof_signal_handler_with_empty_handler(void (*expected_existing_handler)(int, siginfo_t *, void *), bool should_raise_on_failure);
12
10
  void remove_sigprof_signal_handler(void);
13
11
  void block_sigprof_signal_handler_from_running_in_current_thread(void);
14
12
  void unblock_sigprof_signal_handler_from_running_in_current_thread(void);
@@ -131,46 +131,32 @@
131
131
  static VALUE ok_symbol = Qnil; // :ok in Ruby
132
132
  static VALUE error_symbol = Qnil; // :error in Ruby
133
133
 
134
- // Note: Please DO NOT use `VALUE_STRING` anywhere else, instead use `DDOG_CHARSLICE_C`.
135
- // `VALUE_STRING` is only needed because older versions of gcc (4.9.2, used in our Ruby 2.2 CI test images)
136
- // tripped when compiling `enabled_value_types` using `-std=gnu99` due to the extra cast that is included in
137
- // `DDOG_CHARSLICE_C` with the following error:
138
- //
139
- // ```
140
- // compiling ../../../../ext/ddtrace_profiling_native_extension/stack_recorder.c
141
- // ../../../../ext/ddtrace_profiling_native_extension/stack_recorder.c:23:1: error: initializer element is not constant
142
- // static const ddog_prof_ValueType enabled_value_types[] = {CPU_TIME_VALUE, CPU_SAMPLES_VALUE, WALL_TIME_VALUE};
143
- // ^
144
- // ```
145
- #define VALUE_STRING(string) {.ptr = "" string, .len = sizeof(string) - 1}
146
-
147
- #define CPU_TIME_VALUE {.type_ = VALUE_STRING("cpu-time"), .unit = VALUE_STRING("nanoseconds")}
148
134
  #define CPU_TIME_VALUE_ID 0
149
- #define CPU_SAMPLES_VALUE {.type_ = VALUE_STRING("cpu-samples"), .unit = VALUE_STRING("count")}
150
135
  #define CPU_SAMPLES_VALUE_ID 1
151
- #define WALL_TIME_VALUE {.type_ = VALUE_STRING("wall-time"), .unit = VALUE_STRING("nanoseconds")}
152
136
  #define WALL_TIME_VALUE_ID 2
153
- #define ALLOC_SAMPLES_VALUE {.type_ = VALUE_STRING("alloc-samples"), .unit = VALUE_STRING("count")}
154
137
  #define ALLOC_SAMPLES_VALUE_ID 3
155
- #define ALLOC_SAMPLES_UNSCALED_VALUE {.type_ = VALUE_STRING("alloc-samples-unscaled"), .unit = VALUE_STRING("count")}
156
138
  #define ALLOC_SAMPLES_UNSCALED_VALUE_ID 4
157
- #define HEAP_SAMPLES_VALUE {.type_ = VALUE_STRING("heap-live-samples"), .unit = VALUE_STRING("count")}
158
139
  #define HEAP_SAMPLES_VALUE_ID 5
159
- #define HEAP_SIZE_VALUE {.type_ = VALUE_STRING("heap-live-size"), .unit = VALUE_STRING("bytes")}
160
140
  #define HEAP_SIZE_VALUE_ID 6
161
- #define TIMELINE_VALUE {.type_ = VALUE_STRING("timeline"), .unit = VALUE_STRING("nanoseconds")}
162
141
  #define TIMELINE_VALUE_ID 7
163
142
 
164
- static const ddog_prof_ValueType all_value_types[] =
165
- {CPU_TIME_VALUE, CPU_SAMPLES_VALUE, WALL_TIME_VALUE, ALLOC_SAMPLES_VALUE, ALLOC_SAMPLES_UNSCALED_VALUE, HEAP_SAMPLES_VALUE, HEAP_SIZE_VALUE, TIMELINE_VALUE};
143
+ static const ddog_prof_SampleType all_sample_types[] = {
144
+ DDOG_PROF_SAMPLE_TYPE_CPU_TIME,
145
+ DDOG_PROF_SAMPLE_TYPE_CPU_SAMPLES,
146
+ DDOG_PROF_SAMPLE_TYPE_WALL_TIME,
147
+ DDOG_PROF_SAMPLE_TYPE_ALLOC_SAMPLES,
148
+ DDOG_PROF_SAMPLE_TYPE_ALLOC_SAMPLES_UNSCALED,
149
+ DDOG_PROF_SAMPLE_TYPE_HEAP_LIVE_SAMPLES,
150
+ DDOG_PROF_SAMPLE_TYPE_HEAP_LIVE_SIZE,
151
+ DDOG_PROF_SAMPLE_TYPE_TIMELINE,
152
+ };
166
153
 
167
- // This array MUST be kept in sync with all_value_types above and is intended to act as a "hashmap" between VALUE_ID and the position it
168
- // occupies on the all_value_types array.
169
- // E.g. all_value_types_positions[CPU_TIME_VALUE_ID] => 0, means that CPU_TIME_VALUE was declared at position 0 of all_value_types.
154
+ // This array MUST be kept in sync with all_sample_types above and is intended to act as a "hashmap" between VALUE_ID and the position it
155
+ // occupies on the all_sample_types array.
170
156
  static const uint8_t all_value_types_positions[] =
171
157
  {CPU_TIME_VALUE_ID, CPU_SAMPLES_VALUE_ID, WALL_TIME_VALUE_ID, ALLOC_SAMPLES_VALUE_ID, ALLOC_SAMPLES_UNSCALED_VALUE_ID, HEAP_SAMPLES_VALUE_ID, HEAP_SIZE_VALUE_ID, TIMELINE_VALUE_ID};
172
158
 
173
- #define ALL_VALUE_TYPES_COUNT (sizeof(all_value_types) / sizeof(ddog_prof_ValueType))
159
+ #define ALL_VALUE_TYPES_COUNT (sizeof(all_sample_types) / sizeof(ddog_prof_SampleType))
174
160
 
175
161
  // Struct for storing stats related to a profile in a particular slot.
176
162
  // These stats will share the same lifetime as the data in that profile slot.
@@ -242,8 +228,8 @@ typedef struct {
242
228
 
243
229
  static VALUE _native_new(VALUE klass);
244
230
  static void initialize_slot_concurrency_control(stack_recorder_state *state);
245
- static void initialize_profiles(stack_recorder_state *state, ddog_prof_Slice_ValueType sample_types);
246
231
  static void stack_recorder_typed_data_mark(void *data);
232
+ static void initialize_profiles(stack_recorder_state *state, ddog_prof_Slice_SampleType sample_types);
247
233
  static void stack_recorder_typed_data_free(void *data);
248
234
  static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self);
249
235
  static VALUE _native_serialize(VALUE self, VALUE recorder_instance);
@@ -335,7 +321,7 @@ static VALUE _native_new(VALUE klass) {
335
321
 
336
322
  state->heap_clean_after_gc_enabled = false;
337
323
 
338
- ddog_prof_Slice_ValueType sample_types = {.ptr = all_value_types, .len = ALL_VALUE_TYPES_COUNT};
324
+ ddog_prof_Slice_SampleType sample_types = {.ptr = all_sample_types, .len = ALL_VALUE_TYPES_COUNT};
339
325
 
340
326
  initialize_slot_concurrency_control(state);
341
327
  for (uint8_t i = 0; i < ALL_VALUE_TYPES_COUNT; i++) { state->position_for[i] = all_value_types_positions[i]; }
@@ -379,7 +365,7 @@ static void initialize_slot_concurrency_control(stack_recorder_state *state) {
379
365
  state->active_slot = 1;
380
366
  }
381
367
 
382
- static void initialize_profiles(stack_recorder_state *state, ddog_prof_Slice_ValueType sample_types) {
368
+ static void initialize_profiles(stack_recorder_state *state, ddog_prof_Slice_SampleType sample_types) {
383
369
  ddog_Timespec start_timestamp = system_epoch_now_timespec();
384
370
 
385
371
  ddog_prof_Profile_NewResult slot_one_profile_result =
@@ -468,30 +454,30 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
468
454
 
469
455
  state->enabled_values_count = requested_values_count;
470
456
 
471
- ddog_prof_ValueType enabled_value_types[ALL_VALUE_TYPES_COUNT];
457
+ ddog_prof_SampleType enabled_sample_types[ALL_VALUE_TYPES_COUNT];
472
458
  uint8_t next_enabled_pos = 0;
473
459
  uint8_t next_disabled_pos = requested_values_count;
474
460
 
475
- // CPU_SAMPLES_VALUE is always enabled
476
- enabled_value_types[next_enabled_pos] = (ddog_prof_ValueType) CPU_SAMPLES_VALUE;
461
+ // CPU_SAMPLES is always enabled
462
+ enabled_sample_types[next_enabled_pos] = DDOG_PROF_SAMPLE_TYPE_CPU_SAMPLES;
477
463
  state->position_for[CPU_SAMPLES_VALUE_ID] = next_enabled_pos++;
478
464
 
479
- // WALL_TIME_VALUE is always enabled
480
- enabled_value_types[next_enabled_pos] = (ddog_prof_ValueType) WALL_TIME_VALUE;
465
+ // WALL_TIME is always enabled
466
+ enabled_sample_types[next_enabled_pos] = DDOG_PROF_SAMPLE_TYPE_WALL_TIME;
481
467
  state->position_for[WALL_TIME_VALUE_ID] = next_enabled_pos++;
482
468
 
483
469
  if (cpu_time_enabled == Qtrue) {
484
- enabled_value_types[next_enabled_pos] = (ddog_prof_ValueType) CPU_TIME_VALUE;
470
+ enabled_sample_types[next_enabled_pos] = DDOG_PROF_SAMPLE_TYPE_CPU_TIME;
485
471
  state->position_for[CPU_TIME_VALUE_ID] = next_enabled_pos++;
486
472
  } else {
487
473
  state->position_for[CPU_TIME_VALUE_ID] = next_disabled_pos++;
488
474
  }
489
475
 
490
476
  if (alloc_samples_enabled == Qtrue) {
491
- enabled_value_types[next_enabled_pos] = (ddog_prof_ValueType) ALLOC_SAMPLES_VALUE;
477
+ enabled_sample_types[next_enabled_pos] = DDOG_PROF_SAMPLE_TYPE_ALLOC_SAMPLES;
492
478
  state->position_for[ALLOC_SAMPLES_VALUE_ID] = next_enabled_pos++;
493
479
 
494
- enabled_value_types[next_enabled_pos] = (ddog_prof_ValueType) ALLOC_SAMPLES_UNSCALED_VALUE;
480
+ enabled_sample_types[next_enabled_pos] = DDOG_PROF_SAMPLE_TYPE_ALLOC_SAMPLES_UNSCALED;
495
481
  state->position_for[ALLOC_SAMPLES_UNSCALED_VALUE_ID] = next_enabled_pos++;
496
482
  } else {
497
483
  state->position_for[ALLOC_SAMPLES_VALUE_ID] = next_disabled_pos++;
@@ -499,14 +485,14 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
499
485
  }
500
486
 
501
487
  if (heap_samples_enabled == Qtrue) {
502
- enabled_value_types[next_enabled_pos] = (ddog_prof_ValueType) HEAP_SAMPLES_VALUE;
488
+ enabled_sample_types[next_enabled_pos] = DDOG_PROF_SAMPLE_TYPE_HEAP_LIVE_SAMPLES;
503
489
  state->position_for[HEAP_SAMPLES_VALUE_ID] = next_enabled_pos++;
504
490
  } else {
505
491
  state->position_for[HEAP_SAMPLES_VALUE_ID] = next_disabled_pos++;
506
492
  }
507
493
 
508
494
  if (heap_size_enabled == Qtrue) {
509
- enabled_value_types[next_enabled_pos] = (ddog_prof_ValueType) HEAP_SIZE_VALUE;
495
+ enabled_sample_types[next_enabled_pos] = DDOG_PROF_SAMPLE_TYPE_HEAP_LIVE_SIZE;
510
496
  state->position_for[HEAP_SIZE_VALUE_ID] = next_enabled_pos++;
511
497
  } else {
512
498
  state->position_for[HEAP_SIZE_VALUE_ID] = next_disabled_pos++;
@@ -521,7 +507,7 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
521
507
  }
522
508
 
523
509
  if (timeline_enabled == Qtrue) {
524
- enabled_value_types[next_enabled_pos] = (ddog_prof_ValueType) TIMELINE_VALUE;
510
+ enabled_sample_types[next_enabled_pos] = DDOG_PROF_SAMPLE_TYPE_TIMELINE;
525
511
  state->position_for[TIMELINE_VALUE_ID] = next_enabled_pos++;
526
512
  } else {
527
513
  state->position_for[TIMELINE_VALUE_ID] = next_disabled_pos++;
@@ -530,7 +516,7 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
530
516
  ddog_prof_Profile_drop(&state->profile_slot_one.profile);
531
517
  ddog_prof_Profile_drop(&state->profile_slot_two.profile);
532
518
 
533
- ddog_prof_Slice_ValueType sample_types = {.ptr = enabled_value_types, .len = state->enabled_values_count};
519
+ ddog_prof_Slice_SampleType sample_types = {.ptr = enabled_sample_types, .len = state->enabled_values_count};
534
520
  initialize_profiles(state, sample_types);
535
521
 
536
522
  return Qtrue;
@@ -1079,7 +1065,7 @@ static VALUE _native_test_managed_string_storage_produces_valid_profiles(DDTRACE
1079
1065
  raise_error(rb_eRuntimeError, "Failed to initialize string storage: %"PRIsVALUE, get_error_details_and_drop(&string_storage.err));
1080
1066
  }
1081
1067
 
1082
- ddog_prof_Slice_ValueType sample_types = {.ptr = all_value_types, .len = ALL_VALUE_TYPES_COUNT};
1068
+ ddog_prof_Slice_SampleType sample_types = {.ptr = all_sample_types, .len = ALL_VALUE_TYPES_COUNT};
1083
1069
  ddog_prof_Profile_NewResult profile = ddog_prof_Profile_with_string_storage(sample_types, NULL, string_storage.ok);
1084
1070
 
1085
1071
  if (profile.tag == DDOG_PROF_PROFILE_NEW_RESULT_ERR) {
@@ -1,53 +1,57 @@
1
1
  #include <datadog/common.h>
2
2
  #include <datadog/crashtracker.h>
3
3
  #include <ruby.h>
4
- #include <sys/types.h>
5
- #include <unistd.h>
6
- #include <string.h>
7
4
 
8
5
  #include "datadog_ruby_common.h"
9
6
 
10
- static VALUE _native_report_ruby_exception(VALUE _self, VALUE agent_base_url,
11
- VALUE message, VALUE frames_data,
12
- VALUE tags_as_array, VALUE library_version);
7
+ static VALUE _native_report_ruby_exception(VALUE _self, VALUE exception_type,
8
+ VALUE message, VALUE frames_data);
13
9
 
14
10
  static bool process_crash_frames(VALUE frames_data, ddog_crasht_Handle_StackTrace *stack_trace);
15
- static bool build_and_send_crash_report(ddog_crasht_Metadata metadata,
16
- ddog_Endpoint *endpoint,
17
- VALUE message,
18
- VALUE frames_data);
19
11
 
20
12
  void crashtracker_report_exception_init(VALUE crashtracker_class) {
21
13
  rb_define_singleton_method(crashtracker_class, "_native_report_ruby_exception",
22
- _native_report_ruby_exception, 5);
14
+ _native_report_ruby_exception, 3);
23
15
  }
24
16
 
25
- static VALUE _native_report_ruby_exception(DDTRACE_UNUSED VALUE _self, VALUE agent_base_url,
26
- VALUE message, VALUE frames_data,
27
- VALUE tags_as_array, VALUE library_version) {
28
- ENFORCE_TYPE(agent_base_url, T_STRING);
17
+ static VALUE _native_report_ruby_exception(DDTRACE_UNUSED VALUE _self, VALUE exception_type,
18
+ VALUE message, VALUE frames_data) {
19
+ ENFORCE_TYPE(exception_type, T_STRING);
29
20
  ENFORCE_TYPE(message, T_STRING);
30
21
  ENFORCE_TYPE(frames_data, T_ARRAY);
31
- ENFORCE_TYPE(tags_as_array, T_ARRAY);
32
- ENFORCE_TYPE(library_version, T_STRING);
33
22
 
34
- ddog_Endpoint *endpoint = ddog_endpoint_from_url(char_slice_from_ruby_string(agent_base_url));
35
- if (!endpoint) return Qfalse;
23
+ // Build stack trace
24
+ ddog_crasht_StackTrace_NewResult stack_result = ddog_crasht_StackTrace_new();
25
+ if (stack_result.tag != DDOG_CRASHT_STACK_TRACE_NEW_RESULT_OK) {
26
+ ddog_Error_drop(&stack_result.err);
27
+ return Qfalse;
28
+ }
36
29
 
37
- ddog_Vec_Tag tags = convert_tags(tags_as_array);
30
+ ddog_crasht_Handle_StackTrace *stack_trace = &stack_result.ok;
31
+
32
+ bool frames_ok = process_crash_frames(frames_data, stack_trace);
33
+ if (frames_ok) {
34
+ ddog_VoidResult complete_result = ddog_crasht_StackTrace_set_complete(stack_trace);
35
+ if (complete_result.tag != DDOG_VOID_RESULT_OK) {
36
+ ddog_crasht_StackTrace_drop(stack_trace);
37
+ ddog_Error_drop(&complete_result.err);
38
+ return Qfalse;
39
+ }
40
+ }
38
41
 
39
- ddog_crasht_Metadata metadata = {
40
- .library_name = DDOG_CHARSLICE_C("dd-trace-rb"),
41
- .library_version = char_slice_from_ruby_string(library_version),
42
- .family = DDOG_CHARSLICE_C("ruby"),
43
- .tags = &tags,
44
- };
42
+ // ddog_crasht_report_unhandled_exception takes ownership of stack_trace
43
+ ddog_VoidResult result = ddog_crasht_report_unhandled_exception(
44
+ char_slice_from_ruby_string(exception_type),
45
+ char_slice_from_ruby_string(message),
46
+ stack_trace
47
+ );
45
48
 
46
- bool success = build_and_send_crash_report(metadata, endpoint, message, frames_data);
47
- ddog_Vec_Tag_drop(tags);
48
- ddog_endpoint_drop(endpoint);
49
+ if (result.tag != DDOG_VOID_RESULT_OK) {
50
+ ddog_Error_drop(&result.err);
51
+ return Qfalse;
52
+ }
49
53
 
50
- return success ? Qtrue : Qfalse;
54
+ return Qtrue;
51
55
  }
52
56
 
53
57
  static bool process_crash_frames(VALUE frames_data, ddog_crasht_Handle_StackTrace *stack_trace) {
@@ -120,117 +124,3 @@ static bool process_crash_frames(VALUE frames_data, ddog_crasht_Handle_StackTrac
120
124
 
121
125
  return true;
122
126
  }
123
-
124
- static bool build_and_send_crash_report(ddog_crasht_Metadata metadata,
125
- ddog_Endpoint *endpoint,
126
- VALUE message,
127
- VALUE frames_data) {
128
- ddog_crasht_Handle_StackTrace *stack_trace = NULL;
129
-
130
- ddog_crasht_CrashInfoBuilder_NewResult builder_result = ddog_crasht_CrashInfoBuilder_new();
131
- if (builder_result.tag != DDOG_CRASHT_CRASH_INFO_BUILDER_NEW_RESULT_OK) {
132
- ddog_Error_drop(&builder_result.err);
133
- return false;
134
- }
135
-
136
- ddog_crasht_Handle_CrashInfoBuilder *builder = &builder_result.ok;
137
-
138
- // Setup builder metadata and configuration
139
- ddog_VoidResult metadata_result = ddog_crasht_CrashInfoBuilder_with_metadata(builder, metadata);
140
- if (metadata_result.tag != DDOG_VOID_RESULT_OK) {
141
- ddog_crasht_CrashInfoBuilder_drop(builder);
142
- ddog_Error_drop(&metadata_result.err);
143
- return false;
144
- }
145
-
146
- ddog_VoidResult kind_result = ddog_crasht_CrashInfoBuilder_with_kind(builder, DDOG_CRASHT_ERROR_KIND_UNHANDLED_EXCEPTION);
147
- if (kind_result.tag != DDOG_VOID_RESULT_OK) {
148
- ddog_crasht_CrashInfoBuilder_drop(builder);
149
- ddog_Error_drop(&kind_result.err);
150
- return false;
151
- }
152
-
153
- // Send ping first
154
- ddog_VoidResult ping_result = ddog_crasht_CrashInfoBuilder_upload_ping_to_endpoint(builder, endpoint);
155
- if (ping_result.tag != DDOG_VOID_RESULT_OK) {
156
- ddog_crasht_CrashInfoBuilder_drop(builder);
157
- ddog_Error_drop(&ping_result.err);
158
- return false;
159
- }
160
-
161
- ddog_crasht_ProcInfo proc_info = { .pid = (uint32_t)getpid() };
162
- ddog_VoidResult proc_info_result = ddog_crasht_CrashInfoBuilder_with_proc_info(builder, proc_info);
163
- if (proc_info_result.tag != DDOG_VOID_RESULT_OK) {
164
- ddog_crasht_CrashInfoBuilder_drop(builder);
165
- ddog_Error_drop(&proc_info_result.err);
166
- return false;
167
- }
168
-
169
- ddog_VoidResult os_info_result = ddog_crasht_CrashInfoBuilder_with_os_info_this_machine(builder);
170
- if (os_info_result.tag != DDOG_VOID_RESULT_OK) {
171
- ddog_crasht_CrashInfoBuilder_drop(builder);
172
- ddog_Error_drop(&os_info_result.err);
173
- return false;
174
- }
175
-
176
- ddog_VoidResult message_result = ddog_crasht_CrashInfoBuilder_with_message(builder, char_slice_from_ruby_string(message));
177
- if (message_result.tag != DDOG_VOID_RESULT_OK) {
178
- ddog_crasht_CrashInfoBuilder_drop(builder);
179
- ddog_Error_drop(&message_result.err);
180
- return false;
181
- }
182
-
183
- // Create and populate stack trace
184
- ddog_crasht_StackTrace_NewResult stack_result = ddog_crasht_StackTrace_new();
185
- if (stack_result.tag != DDOG_CRASHT_STACK_TRACE_NEW_RESULT_OK) {
186
- ddog_crasht_CrashInfoBuilder_drop(builder);
187
- ddog_Error_drop(&stack_result.err);
188
- return false;
189
- }
190
-
191
- stack_trace = &stack_result.ok;
192
-
193
- bool frames_processed_successfully = process_crash_frames(frames_data, stack_trace);
194
-
195
- // Only mark as complete if we successfully processed all frames
196
- if (frames_processed_successfully) {
197
- ddog_VoidResult complete_result = ddog_crasht_StackTrace_set_complete(stack_trace);
198
- if (complete_result.tag != DDOG_VOID_RESULT_OK) {
199
- ddog_crasht_StackTrace_drop(stack_trace);
200
- ddog_crasht_CrashInfoBuilder_drop(builder);
201
- ddog_Error_drop(&complete_result.err);
202
- return false;
203
- }
204
- }
205
- // If frames processing failed, we still include the stack trace (which may be empty or partial)
206
- // but don't mark it as complete, indicating it's incomplete
207
-
208
- ddog_VoidResult with_stack_result = ddog_crasht_CrashInfoBuilder_with_stack(builder, stack_trace);
209
- if (with_stack_result.tag != DDOG_VOID_RESULT_OK) {
210
- ddog_crasht_StackTrace_drop(stack_trace);
211
- ddog_crasht_CrashInfoBuilder_drop(builder);
212
- ddog_Error_drop(&with_stack_result.err);
213
- return false;
214
- }
215
-
216
- // Builder takes ownership of stack_trace, so we don't need to clean it up anymore
217
- stack_trace = NULL;
218
-
219
- // Build and upload crash info
220
- ddog_crasht_CrashInfo_NewResult crash_info_result = ddog_crasht_CrashInfoBuilder_build(builder);
221
- if (crash_info_result.tag != DDOG_CRASHT_RESULT_HANDLE_CRASH_INFO_OK_HANDLE_CRASH_INFO) {
222
- ddog_crasht_CrashInfoBuilder_drop(builder);
223
- ddog_Error_drop(&crash_info_result.err);
224
- return false;
225
- }
226
-
227
- ddog_crasht_Handle_CrashInfo *crash_info = &crash_info_result.ok;
228
- ddog_VoidResult upload_result = ddog_crasht_CrashInfo_upload_to_endpoint(crash_info, endpoint);
229
- bool success = (upload_result.tag == DDOG_VOID_RESULT_OK);
230
- if (!success) {
231
- ddog_Error_drop(&upload_result.err);
232
- }
233
-
234
- ddog_crasht_CrashInfo_drop(crash_info);
235
- return success;
236
- }
@@ -40,7 +40,8 @@ append_cflags '-Werror' if ENV['DATADOG_GEM_CI'] == 'true'
40
40
  # * by upstream Ruby -- search for gnu99 in the codebase
41
41
  # * by msgpack, another datadog gem dependency
42
42
  # (https://github.com/msgpack/msgpack-ruby/blob/18ce08f6d612fe973843c366ac9a0b74c4e50599/ext/msgpack/extconf.rb#L8)
43
- append_cflags '-std=gnu99'
43
+ # @ivoanjo: We could probably start using C11/gnu11 for non macOS-too but it's somewhat hard to validate so I chickened out for now
44
+ append_cflags RUBY_PLATFORM.include?('darwin') ? '-std=gnu11' : '-std=gnu99'
44
45
 
45
46
  # Allow defining variables at any point in a function
46
47
  append_cflags '-Wno-declaration-after-statement'
@@ -74,30 +75,12 @@ if ENV['DDTRACE_DEBUG'] == 'true'
74
75
  end
75
76
 
76
77
  # If we got here, libdatadog is available and loaded
77
- ENV['PKG_CONFIG_PATH'] = "#{ENV["PKG_CONFIG_PATH"]}:#{Libdatadog.pkgconfig_folder}"
78
- Logging.message("[datadog] PKG_CONFIG_PATH set to #{ENV["PKG_CONFIG_PATH"].inspect}\n")
79
78
  $stderr.puts("Using libdatadog #{Libdatadog::VERSION} from #{Libdatadog.pkgconfig_folder}")
80
79
 
81
- unless pkg_config('datadog_profiling_with_rpath')
82
- Logging.message("[datadog] Ruby detected the pkg-config command is #{$PKGCONFIG.inspect}\n")
83
-
84
- if Datadog::LibdatadogExtconfHelpers.pkg_config_missing?
85
- skip_building_extension!('the `pkg-config` system tool is missing')
86
- else
87
- skip_building_extension!('there was a problem in setting up the `libdatadog` dependency')
88
- end
80
+ unless Datadog::LibdatadogExtconfHelpers.configure_libdatadog(extconf_folder: __dir__)
81
+ skip_building_extension!('there was a problem in setting up the `libdatadog` dependency')
89
82
  end
90
83
 
91
- # See comments on the helper methods being used for why we need to additionally set this.
92
- # The extremely excessive escaping around ORIGIN below seems to be correct and was determined after a lot of
93
- # experimentation. We need to get these special characters across a lot of tools untouched...
94
- extra_relative_rpaths = [
95
- Datadog::LibdatadogExtconfHelpers.libdatadog_folder_relative_to_native_lib_folder(current_folder: __dir__),
96
- *Datadog::LibdatadogExtconfHelpers.libdatadog_folder_relative_to_ruby_extensions_folders,
97
- ]
98
- extra_relative_rpaths.each { |folder| $LDFLAGS += " -Wl,-rpath,$$$\\\\{ORIGIN\\}/#{folder.to_str}" }
99
- Logging.message("[datadog] After pkg-config $LDFLAGS were set to: #{$LDFLAGS.inspect}\n")
100
-
101
84
  # Tag the native extension library with the Ruby version and Ruby platform.
102
85
  # This makes it easier for development (avoids "oops I forgot to rebuild when I switched my Ruby") and ensures that
103
86
  # the wrong library is never loaded.
@@ -10,7 +10,7 @@ module Datadog
10
10
  module LibdatadogExtconfHelpers
11
11
  # Used to make sure the correct gem version gets loaded, as extconf.rb does not get run with "bundle exec" and thus
12
12
  # may see multiple libdatadog versions. See https://github.com/DataDog/dd-trace-rb/pull/2531 for the horror story.
13
- LIBDATADOG_VERSION = '~> 25.0.0.1.0'
13
+ LIBDATADOG_VERSION = '~> 29.0.0.1.0'
14
14
 
15
15
  # Used as an workaround for a limitation with how dynamic linking works in environments where the datadog gem and
16
16
  # libdatadog are moved after the extension gets compiled.
@@ -23,9 +23,9 @@ module Datadog
23
23
  # Linux: e.g. `readelf -d datadog_profiling_native_extension.2.7.3_x86_64-linux.so`.
24
24
  #
25
25
  # In older versions of the datadog gem, we only set as runpath an absolute path to libdatadog.
26
- # (This gets set automatically by the call
27
- # to `pkg_config('datadog_profiling_with_rpath')` in `extconf.rb`). This worked fine as long as libdatadog was **NOT**
28
- # moved from the folder it was present at datadog gem installation/linking time.
26
+ # (This gets set automatically by the call to `configure_libdatadog` in `extconf.rb`).
27
+ # This worked fine as long as libdatadog was **NOT** moved from the folder it was present at
28
+ # datadog gem installation/linking time.
29
29
  #
30
30
  # Unfortunately, environments such as Heroku and AWS Elastic Beanstalk move gems around in the filesystem after
31
31
  # installation. Thus, the profiling native extension could not be loaded in these environments
@@ -47,12 +47,12 @@ module Datadog
47
47
  # we could setup when doing a `require`.
48
48
  #
49
49
  def self.libdatadog_folder_relative_to_native_lib_folder(
50
- current_folder:,
50
+ extconf_folder:,
51
51
  libdatadog_pkgconfig_folder: Libdatadog.pkgconfig_folder
52
52
  )
53
53
  return unless libdatadog_pkgconfig_folder
54
54
 
55
- native_lib_folder = "#{current_folder}/../../lib/"
55
+ native_lib_folder = "#{extconf_folder}/../../lib/"
56
56
  libdatadog_lib_folder = "#{libdatadog_pkgconfig_folder}/../"
57
57
 
58
58
  Pathname.new(libdatadog_lib_folder).relative_path_from(Pathname.new(native_lib_folder)).to_s
@@ -104,13 +104,51 @@ module Datadog
104
104
  end
105
105
  end
106
106
 
107
- # mkmf sets $PKGCONFIG after the `pkg_config` gets used in extconf.rb. When `pkg_config` is unsuccessful, we use
108
- # this helper to decide if we can show more specific error message vs a generic "something went wrong".
109
- def self.pkg_config_missing?(command: $PKGCONFIG) # standard:disable Style/GlobalVars
110
- pkg_config_available = command && xsystem("#{command} --version")
107
+ # Directly configures mkmf to link against libdatadog by setting $INCFLAGS, $LDFLAGS, and $libs.
108
+ #
109
+ # This replaces the previous use of mkmf's `pkg_config("datadog_profiling_with_rpath")`, removing the need for
110
+ # the pkg-config system tool to be installed.
111
+ #
112
+ # The extconf_folder argument should be the __dir__ of the calling extconf.rb, used to compute relative rpaths.
113
+ # The logger argument is the mkmf Logging module, dependency-injected to make testing easier.
114
+ # rubocop:disable Style/GlobalVars
115
+ def self.configure_libdatadog(
116
+ extconf_folder:,
117
+ libdatadog_pkgconfig_folder: Libdatadog.pkgconfig_folder,
118
+ gem_dir: Gem.dir,
119
+ logger: Logging
120
+ )
121
+ return unless libdatadog_pkgconfig_folder
122
+
123
+ # The lib and include folders are at a fixed relative path from pkgconfig_folder
124
+ libdir = "#{libdatadog_pkgconfig_folder}/../../lib"
125
+ includedir = "#{libdatadog_pkgconfig_folder}/../../include"
126
+
127
+ # Set mkmf global variables
128
+ $INCFLAGS << " -I#{includedir}"
129
+ $LDFLAGS << " -L#{libdir} -Wl,-rpath,#{libdir}"
130
+ $libs << " -ldatadog_profiling"
131
+
132
+ # Add extra relative rpaths using $ORIGIN to handle environments where gems are moved after installation.
133
+ # The excessive escaping is needed to get these special characters through Make and the shell untouched.
134
+ extra_relative_rpaths = [
135
+ libdatadog_folder_relative_to_native_lib_folder(
136
+ extconf_folder: extconf_folder,
137
+ libdatadog_pkgconfig_folder: libdatadog_pkgconfig_folder,
138
+ ),
139
+ *libdatadog_folder_relative_to_ruby_extensions_folders(
140
+ gem_dir: gem_dir,
141
+ libdatadog_pkgconfig_folder: libdatadog_pkgconfig_folder,
142
+ ),
143
+ ]
144
+ extra_relative_rpaths.each { |folder| $LDFLAGS << " -Wl,-rpath,$$$\\\\{ORIGIN\\}/#{folder}" }
145
+
146
+ logger.message("linking with libdatadog (include=#{includedir}, lib=#{libdir})\n")
147
+ logger.message("[datadog] $LDFLAGS were set to: #{$LDFLAGS.inspect}\n")
111
148
 
112
- pkg_config_available != true
149
+ true
113
150
  end
151
+ # rubocop:enable Style/GlobalVars
114
152
 
115
153
  def self.try_loading_libdatadog
116
154
  gem 'libdatadog', LIBDATADOG_VERSION
@@ -17,6 +17,9 @@ module Datadog
17
17
  base.class_eval do
18
18
  # AI Guard specific configurations.
19
19
  # @public_api
20
+ #
21
+ # Steep does not update `self` for this `class_eval` block.
22
+ # @type self: Datadog::Core::Configuration::Base::_DslContext
20
23
  settings :ai_guard do
21
24
  # Enable AI Guard.
22
25
  #
@@ -61,6 +61,7 @@ module Datadog
61
61
  end
62
62
 
63
63
  ::ActiveRecord::ConnectionAdapters::SQLite3Adapter.prepend(instrumentation_module)
64
+ Patcher.instance_variable_set(:@patched, true)
64
65
  end
65
66
 
66
67
  def patch_mysql2_adapter
@@ -73,6 +74,7 @@ module Datadog
73
74
  end
74
75
 
75
76
  ::ActiveRecord::ConnectionAdapters::Mysql2Adapter.prepend(instrumentation_module)
77
+ Patcher.instance_variable_set(:@patched, true)
76
78
  end
77
79
 
78
80
  def patch_postgresql_adapter
@@ -93,6 +95,7 @@ module Datadog
93
95
  end
94
96
 
95
97
  ::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(instrumentation_module)
98
+ Patcher.instance_variable_set(:@patched, true)
96
99
  end
97
100
  end
98
101
  end
@@ -24,7 +24,7 @@ module Datadog
24
24
  end
25
25
 
26
26
  def self.compatible?
27
- super && version >= MINIMUM_VERSION
27
+ !!(super && (version&.>= MINIMUM_VERSION))
28
28
  end
29
29
 
30
30
  def self.auto_instrument?
@@ -20,6 +20,8 @@ module Datadog
20
20
  require_relative 'ssrf_detection_middleware'
21
21
 
22
22
  ::Excon.defaults[:middlewares].insert(0, SSRFDetectionMiddleware)
23
+
24
+ Patcher.instance_variable_set(:@patched, true)
23
25
  end
24
26
  end
25
27
  end