datadog 2.35.0 → 2.37.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +85 -1
- data/ext/datadog_profiling_native_extension/clock_id_from_mach.c +5 -12
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +71 -31
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -1
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +18 -4
- data/ext/datadog_profiling_native_extension/collectors_stack.c +37 -18
- data/ext/datadog_profiling_native_extension/collectors_stack.h +8 -2
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +596 -341
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +11 -7
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +7 -8
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +0 -12
- data/ext/datadog_profiling_native_extension/extconf.rb +13 -2
- data/ext/datadog_profiling_native_extension/gvl_profiling_helper.c +4 -43
- data/ext/datadog_profiling_native_extension/gvl_profiling_helper.h +15 -47
- data/ext/datadog_profiling_native_extension/heap_recorder.c +44 -26
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +14 -35
- data/ext/datadog_profiling_native_extension/profiling.c +41 -4
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +40 -34
- data/ext/datadog_profiling_native_extension/stack_recorder.c +24 -3
- data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -0
- data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +4 -2
- data/ext/libdatadog_api/datadog_ruby_common.c +7 -8
- data/ext/libdatadog_api/datadog_ruby_common.h +0 -12
- data/ext/libdatadog_api/di.c +76 -0
- data/ext/libdatadog_api/extconf.rb +6 -0
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/ai_guard/api_client.rb +6 -4
- data/lib/datadog/appsec/api_security/route_extractor.rb +13 -4
- data/lib/datadog/appsec/component.rb +1 -1
- data/lib/datadog/appsec/configuration.rb +13 -0
- data/lib/datadog/appsec/context.rb +4 -2
- data/lib/datadog/appsec/contrib/aws_lambda/waf_addresses.rb +37 -4
- data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +59 -4
- data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +59 -5
- data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +64 -19
- data/lib/datadog/appsec/contrib/graphql/integration.rb +1 -0
- data/lib/datadog/appsec/contrib/rack/buffered_input.rb +83 -0
- data/lib/datadog/appsec/contrib/rack/gateway/request.rb +41 -3
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +24 -7
- data/lib/datadog/appsec/contrib/rack/input_peeker.rb +79 -0
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +21 -0
- data/lib/datadog/appsec/contrib/rails/gateway/request.rb +35 -1
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +17 -1
- data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +60 -7
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +24 -3
- data/lib/datadog/appsec/default_header_tags.rb +10 -6
- data/lib/datadog/appsec/ext.rb +1 -0
- data/lib/datadog/appsec/metrics/collector.rb +18 -1
- data/lib/datadog/appsec/metrics/exporter.rb +9 -3
- data/lib/datadog/appsec/remote.rb +9 -3
- data/lib/datadog/appsec/route_normalizer/rails_route_pattern.rb +176 -0
- data/lib/datadog/appsec/route_normalizer/route_pattern.rb +378 -0
- data/lib/datadog/appsec/route_normalizer/route_text.rb +57 -0
- data/lib/datadog/appsec/route_normalizer.rb +80 -0
- data/lib/datadog/appsec/utils/http/body_reader.rb +61 -0
- data/lib/datadog/core/configuration/components.rb +83 -2
- data/lib/datadog/core/configuration/components_state.rb +6 -1
- data/lib/datadog/core/configuration/settings.rb +1 -5
- data/lib/datadog/core/configuration/supported_configurations.rb +4 -0
- data/lib/datadog/core/evp.rb +11 -0
- data/lib/datadog/core/remote/client/capabilities.rb +34 -7
- data/lib/datadog/core/remote/component.rb +1 -1
- data/lib/datadog/core/telemetry/event/app_started.rb +8 -22
- data/lib/datadog/core/utils/at_fork_monkey_patch.rb +1 -1
- data/lib/datadog/core/utils/forking.rb +3 -1
- data/lib/datadog/core/utils/spawn_monkey_patch.rb +3 -1
- data/lib/datadog/core.rb +3 -0
- data/lib/datadog/di/base.rb +7 -2
- data/lib/datadog/di/code_tracker.rb +5 -2
- data/lib/datadog/di/component.rb +110 -36
- data/lib/datadog/di/error.rb +10 -0
- data/lib/datadog/di/fatal_exceptions.rb +26 -0
- data/lib/datadog/di/instrumenter.rb +381 -165
- data/lib/datadog/di/probe.rb +14 -0
- data/lib/datadog/di/probe_file_loader.rb +8 -3
- data/lib/datadog/di/probe_manager.rb +67 -9
- data/lib/datadog/di/probe_notification_builder.rb +4 -1
- data/lib/datadog/di/probe_notifier_worker.rb +52 -32
- data/lib/datadog/di/redactor.rb +16 -1
- data/lib/datadog/di/remote.rb +175 -14
- data/lib/datadog/di/serializer.rb +12 -5
- data/lib/datadog/di/transport/input.rb +8 -4
- data/lib/datadog/di.rb +63 -0
- data/lib/datadog/error_tracking/collector.rb +2 -1
- data/lib/datadog/error_tracking/component.rb +2 -2
- data/lib/datadog/kit/appsec/events/v2.rb +60 -2
- data/lib/datadog/kit/tracing/method_tracer.rb +4 -1
- data/lib/datadog/open_feature/component.rb +29 -6
- data/lib/datadog/open_feature/configuration.rb +8 -0
- data/lib/datadog/open_feature/ext.rb +2 -0
- data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +286 -0
- data/lib/datadog/open_feature/flag_evaluation/writer.rb +433 -0
- data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +101 -0
- data/lib/datadog/open_feature/hooks/{flag_eval_hook.rb → flag_eval_metrics_hook.rb} +1 -1
- data/lib/datadog/open_feature/native_evaluator.rb +22 -0
- data/lib/datadog/open_feature/provider.rb +59 -27
- data/lib/datadog/open_feature/transport.rb +55 -1
- data/lib/datadog/opentelemetry/sdk/logs_exporter.rb +5 -12
- data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +4 -11
- data/lib/datadog/opentelemetry/sdk/propagator.rb +9 -3
- data/lib/datadog/opentelemetry/sdk/span_processor.rb +4 -1
- data/lib/datadog/opentelemetry/sdk.rb +19 -0
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +1 -1
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +5 -3
- data/lib/datadog/profiling/collectors/thread_context.rb +1 -0
- data/lib/datadog/profiling/component.rb +13 -15
- data/lib/datadog/profiling/ext/dir_monkey_patches.rb +3 -3
- data/lib/datadog/ruby_version.rb +25 -0
- data/lib/datadog/symbol_database/component.rb +453 -128
- data/lib/datadog/symbol_database/configuration.rb +12 -4
- data/lib/datadog/symbol_database/extensions.rb +19 -0
- data/lib/datadog/symbol_database/extractor.rb +326 -150
- data/lib/datadog/symbol_database/file_hash.rb +3 -1
- data/lib/datadog/symbol_database/remote.rb +8 -3
- data/lib/datadog/symbol_database/scope_batcher.rb +7 -3
- data/lib/datadog/symbol_database/uploader.rb +9 -2
- data/lib/datadog/symbol_database.rb +22 -0
- data/lib/datadog/tracing/configuration/ext.rb +13 -0
- data/lib/datadog/tracing/configuration/settings.rb +17 -0
- data/lib/datadog/tracing/contrib/configuration/resolver.rb +7 -0
- data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +2 -0
- data/lib/datadog/tracing/contrib/grpc.rb +1 -0
- data/lib/datadog/tracing/contrib/http/distributed/propagation.rb +2 -0
- data/lib/datadog/tracing/contrib/http.rb +1 -0
- data/lib/datadog/tracing/contrib/karafka/distributed/propagation.rb +2 -0
- data/lib/datadog/tracing/contrib/karafka.rb +1 -0
- data/lib/datadog/tracing/contrib/rack/middlewares.rb +3 -1
- data/lib/datadog/tracing/contrib/rack/route_inference.rb +3 -1
- data/lib/datadog/tracing/contrib/sidekiq/distributed/propagation.rb +2 -0
- data/lib/datadog/tracing/contrib/sidekiq.rb +1 -0
- data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +2 -0
- data/lib/datadog/tracing/contrib/waterdrop.rb +1 -0
- data/lib/datadog/tracing/distributed/baggage.rb +2 -1
- data/lib/datadog/tracing/distributed/propagation.rb +33 -1
- data/lib/datadog/tracing/distributed/trace_context.rb +11 -2
- data/lib/datadog/tracing/ext.rb +9 -0
- data/lib/datadog/tracing/remote.rb +34 -3
- data/lib/datadog/tracing/trace_digest.rb +7 -0
- data/lib/datadog/tracing/trace_operation.rb +4 -1
- data/lib/datadog/tracing/tracer.rb +1 -0
- data/lib/datadog/version.rb +1 -1
- data/lib/datadog.rb +5 -1
- metadata +22 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5435fe8fe48f8cd33010ad82ee5fe6f969077a86cdcca6a96cc9d8633c89eb0e
|
|
4
|
+
data.tar.gz: 3681864939e4601b11bb94fa4b6647d1505efc621eae82ac9664fcc3819b34a3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: de74ddeb7271f1ced1c21dde221b2bdd10fec9b7c9f8663cdf01f146c6a242d93dcde9ac7c63828e41045565f4870359469124a55ef999847f1d02416e36b919
|
|
7
|
+
data.tar.gz: 2d7d44bb00879fa869c5ec630654632632fd74719c7cb276a9a5310606a952cf3eece0bd52c70b25a2a75cb6e69949a1f3ab9597badfb89e17175905b5e08f3e
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,57 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [2.37.0] - 2026-07-06
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
* AppSec: Add sign-up event tracking to `Datadog::Kit::AppSec::Events::V2` ([#5973][])
|
|
10
|
+
* AppSec: Integrations: Add normalized HTTP route for API Security Testing ([#5834][])
|
|
11
|
+
* Dynamic Instrumentation: DI can now be enabled from the Datadog UI without setting `DD_DYNAMIC_INSTRUMENTATION_ENABLED` or restarting the application. Setting `DD_DYNAMIC_INSTRUMENTATION_ENABLED=false` explicitly still disables it and blocks UI-driven enablement ([#5525][])
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
* Profiling: Reduce profiler overhead by sampling profiler-internal threads only once per minute ([#5955][])
|
|
16
|
+
* Dynamic Instrumentation: Reject method probes that target `Kernel#lambda`, which cannot be safely instrumented ([#5954][])
|
|
17
|
+
* Dynamic Instrumentation: Reject method probes that target classes or modules in the Datadog namespace ([#5907][])
|
|
18
|
+
* Dynamic Instrumentation: Enable Symbol Database by default when Dynamic Instrumentation is enabled ([#5828][])
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
* AppSec: AI Guard: Fix endpoint configuration via `DD_SITE` environment variable ([#5940][])
|
|
23
|
+
* AppSec: Fix API Security schema extraction timeout and telemetry ([#5975][])
|
|
24
|
+
* AppSec: Fix remote configuration errors when the ruleset name changes ([#5970][])
|
|
25
|
+
* AppSec: Integrations: Add configurable size limit for downstream HTTP request and response body analysis ([#5990][])
|
|
26
|
+
* AppSec: Integrations: Fix WAF analysis for requests with cached or parsed bodies in Rack and Sinatra ([#5991][])
|
|
27
|
+
* AppSec: Integrations: Fix an exception for unauthorized requests when using Devise with Rails. ([#5987][])
|
|
28
|
+
* Tracing: Fix a `SIGSEGV` crash that could happen with experimental heap profiling enabled on Ruby 4.0 ([#5938][]) ([@navidemad][])
|
|
29
|
+
* Dynamic Instrumentation: Normalize Rack CGI header prefix (`HTTP_<HEADER>`) in redactor so snapshot captures redact sensitive headers like their bare names ([#5911][])
|
|
30
|
+
* Dynamic Instrumentation: Symbol Database and DI background threads no longer stop when application code raises a non-StandardError during introspection ([#5945][])
|
|
31
|
+
|
|
32
|
+
## [2.36.0] - 2026-06-24
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
* Tracing: Add `DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT` to control trace extraction behavior with `continue`, `restart`, and `ignore` modes ([#5844][])
|
|
37
|
+
* AppSec: Add `DD_APPSEC_BODY_PARSING_SIZE_LIMIT` to control processing of request and response body size; set to 0 to disable ([#5877][])
|
|
38
|
+
* AppSec: Detect attacks from inline fragments in GraphQL queries ([#5916][])
|
|
39
|
+
* Dynamic Instrumentation: Show lazily loaded classes in UI ([#5697][])
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
* Dynamic Instrumentation: Reduce peak memory usage during Symbol Database extraction ([#5883][])
|
|
44
|
+
* Profiling: Reduce profiler overhead by up to 50% by skipping redundant samples for threads without the GVL ([#5777][])
|
|
45
|
+
* Profiling: Remove overhead when cleaning up dead threads ([#5816][])
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
* Tracing: Workaround Ruby VM bug causing segmentation faults inside CachingResolver ([#5719][], [#5890][])
|
|
50
|
+
* Dynamic Instrumentation: Prevent uploading stale class definitions for apps using `remove_const`-then-redefine patterns ([#5872][])
|
|
51
|
+
* Profiling: Fix GC profiling being incorrectly disabled on Ruby 3.2.10 and 3.2.11 ([#5894][])
|
|
52
|
+
* Profiling: Fix over-counting of the first allocation sample at profiler startup ([#5881][])
|
|
53
|
+
* Profiling: Fix rare profiler crash during shutdown in heap profiling cleanup ([#5920][])
|
|
54
|
+
* Core: Fix exception message formatting from native extensions ([#5857][])
|
|
55
|
+
|
|
5
56
|
## [2.35.0] - 2026-06-03
|
|
6
57
|
|
|
7
58
|
### Added
|
|
@@ -3634,7 +3685,9 @@ Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.3.1
|
|
|
3634
3685
|
Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
3635
3686
|
|
|
3636
3687
|
|
|
3637
|
-
[Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.
|
|
3688
|
+
[Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.37.0...master
|
|
3689
|
+
[2.37.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.36.0...v2.37.0
|
|
3690
|
+
[2.36.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.35.0...v2.36.0
|
|
3638
3691
|
[2.35.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.34.0...v2.35.0
|
|
3639
3692
|
[2.34.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.33.0...v2.34.0
|
|
3640
3693
|
[2.33.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.32.0...v2.33.0
|
|
@@ -5360,6 +5413,7 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
|
5360
5413
|
[#5499]: https://github.com/DataDog/dd-trace-rb/issues/5499
|
|
5361
5414
|
[#5501]: https://github.com/DataDog/dd-trace-rb/issues/5501
|
|
5362
5415
|
[#5509]: https://github.com/DataDog/dd-trace-rb/issues/5509
|
|
5416
|
+
[#5525]: https://github.com/DataDog/dd-trace-rb/issues/5525
|
|
5363
5417
|
[#5531]: https://github.com/DataDog/dd-trace-rb/issues/5531
|
|
5364
5418
|
[#5564]: https://github.com/DataDog/dd-trace-rb/issues/5564
|
|
5365
5419
|
[#5569]: https://github.com/DataDog/dd-trace-rb/issues/5569
|
|
@@ -5379,8 +5433,10 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
|
5379
5433
|
[#5681]: https://github.com/DataDog/dd-trace-rb/issues/5681
|
|
5380
5434
|
[#5687]: https://github.com/DataDog/dd-trace-rb/issues/5687
|
|
5381
5435
|
[#5689]: https://github.com/DataDog/dd-trace-rb/issues/5689
|
|
5436
|
+
[#5697]: https://github.com/DataDog/dd-trace-rb/issues/5697
|
|
5382
5437
|
[#5705]: https://github.com/DataDog/dd-trace-rb/issues/5705
|
|
5383
5438
|
[#5717]: https://github.com/DataDog/dd-trace-rb/issues/5717
|
|
5439
|
+
[#5719]: https://github.com/DataDog/dd-trace-rb/issues/5719
|
|
5384
5440
|
[#5723]: https://github.com/DataDog/dd-trace-rb/issues/5723
|
|
5385
5441
|
[#5724]: https://github.com/DataDog/dd-trace-rb/issues/5724
|
|
5386
5442
|
[#5750]: https://github.com/DataDog/dd-trace-rb/issues/5750
|
|
@@ -5389,10 +5445,37 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
|
5389
5445
|
[#5762]: https://github.com/DataDog/dd-trace-rb/issues/5762
|
|
5390
5446
|
[#5768]: https://github.com/DataDog/dd-trace-rb/issues/5768
|
|
5391
5447
|
[#5773]: https://github.com/DataDog/dd-trace-rb/issues/5773
|
|
5448
|
+
[#5777]: https://github.com/DataDog/dd-trace-rb/issues/5777
|
|
5392
5449
|
[#5811]: https://github.com/DataDog/dd-trace-rb/issues/5811
|
|
5393
5450
|
[#5812]: https://github.com/DataDog/dd-trace-rb/issues/5812
|
|
5451
|
+
[#5816]: https://github.com/DataDog/dd-trace-rb/issues/5816
|
|
5452
|
+
[#5828]: https://github.com/DataDog/dd-trace-rb/issues/5828
|
|
5394
5453
|
[#5830]: https://github.com/DataDog/dd-trace-rb/issues/5830
|
|
5454
|
+
[#5834]: https://github.com/DataDog/dd-trace-rb/issues/5834
|
|
5395
5455
|
[#5836]: https://github.com/DataDog/dd-trace-rb/issues/5836
|
|
5456
|
+
[#5844]: https://github.com/DataDog/dd-trace-rb/issues/5844
|
|
5457
|
+
[#5857]: https://github.com/DataDog/dd-trace-rb/issues/5857
|
|
5458
|
+
[#5872]: https://github.com/DataDog/dd-trace-rb/issues/5872
|
|
5459
|
+
[#5877]: https://github.com/DataDog/dd-trace-rb/issues/5877
|
|
5460
|
+
[#5881]: https://github.com/DataDog/dd-trace-rb/issues/5881
|
|
5461
|
+
[#5883]: https://github.com/DataDog/dd-trace-rb/issues/5883
|
|
5462
|
+
[#5890]: https://github.com/DataDog/dd-trace-rb/issues/5890
|
|
5463
|
+
[#5894]: https://github.com/DataDog/dd-trace-rb/issues/5894
|
|
5464
|
+
[#5907]: https://github.com/DataDog/dd-trace-rb/issues/5907
|
|
5465
|
+
[#5911]: https://github.com/DataDog/dd-trace-rb/issues/5911
|
|
5466
|
+
[#5916]: https://github.com/DataDog/dd-trace-rb/issues/5916
|
|
5467
|
+
[#5920]: https://github.com/DataDog/dd-trace-rb/issues/5920
|
|
5468
|
+
[#5938]: https://github.com/DataDog/dd-trace-rb/issues/5938
|
|
5469
|
+
[#5940]: https://github.com/DataDog/dd-trace-rb/issues/5940
|
|
5470
|
+
[#5945]: https://github.com/DataDog/dd-trace-rb/issues/5945
|
|
5471
|
+
[#5954]: https://github.com/DataDog/dd-trace-rb/issues/5954
|
|
5472
|
+
[#5955]: https://github.com/DataDog/dd-trace-rb/issues/5955
|
|
5473
|
+
[#5970]: https://github.com/DataDog/dd-trace-rb/issues/5970
|
|
5474
|
+
[#5973]: https://github.com/DataDog/dd-trace-rb/issues/5973
|
|
5475
|
+
[#5975]: https://github.com/DataDog/dd-trace-rb/issues/5975
|
|
5476
|
+
[#5987]: https://github.com/DataDog/dd-trace-rb/issues/5987
|
|
5477
|
+
[#5990]: https://github.com/DataDog/dd-trace-rb/issues/5990
|
|
5478
|
+
[#5991]: https://github.com/DataDog/dd-trace-rb/issues/5991
|
|
5396
5479
|
[@AdrianLC]: https://github.com/AdrianLC
|
|
5397
5480
|
[@Azure7111]: https://github.com/Azure7111
|
|
5398
5481
|
[@BabyGroot]: https://github.com/BabyGroot
|
|
@@ -5507,6 +5590,7 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
|
5507
5590
|
[@mscrivo]: https://github.com/mscrivo
|
|
5508
5591
|
[@mstruve]: https://github.com/mstruve
|
|
5509
5592
|
[@mustela]: https://github.com/mustela
|
|
5593
|
+
[@navidemad]: https://github.com/navidemad
|
|
5510
5594
|
[@nic-lan]: https://github.com/nic-lan
|
|
5511
5595
|
[@noma4i]: https://github.com/noma4i
|
|
5512
5596
|
[@norbertnytko]: https://github.com/norbertnytko
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
#ifdef HAVE_MACH_THREAD_INFO
|
|
6
6
|
|
|
7
7
|
#include <pthread.h>
|
|
8
|
-
#include <time.h>
|
|
9
8
|
#include <mach/mach.h>
|
|
10
9
|
#include <mach/thread_info.h>
|
|
11
10
|
|
|
@@ -47,17 +46,11 @@ thread_cpu_time thread_cpu_time_for(thread_cpu_time_id time_id) {
|
|
|
47
46
|
|
|
48
47
|
if (!time_id.valid) return error;
|
|
49
48
|
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts) == 0) {
|
|
56
|
-
return (thread_cpu_time) {.valid = true, .result_ns = SECONDS_AS_NS(ts.tv_sec) + ts.tv_nsec};
|
|
57
|
-
}
|
|
58
|
-
// Fall through to thread_info on the unlikely failure case.
|
|
59
|
-
}
|
|
60
|
-
|
|
49
|
+
// We intentionally use thread_info() for all threads, including the current one, even though
|
|
50
|
+
// clock_gettime(CLOCK_THREAD_CPUTIME_ID) is ~5x faster for the current thread. Mixing the two
|
|
51
|
+
// sources causes CPU time to appear to go backwards: thread_info() has microsecond precision
|
|
52
|
+
// while clock_gettime() has nanosecond precision, and comparing values across sources on
|
|
53
|
+
// successive samples produces small negative deltas.
|
|
61
54
|
struct thread_basic_info info;
|
|
62
55
|
mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT;
|
|
63
56
|
kern_return_t kr = thread_info(time_id.clock_id, THREAD_BASIC_INFO, (thread_info_t)&info, &count);
|
|
@@ -182,6 +182,11 @@ typedef struct {
|
|
|
182
182
|
unsigned int allocations_during_sample;
|
|
183
183
|
|
|
184
184
|
// # GVL profiling stats
|
|
185
|
+
// Note that this tracks two kind of samples from RESUMED:
|
|
186
|
+
// * samples when Waiting for GVL for more than the threshold
|
|
187
|
+
// * samples for the skip-samples-while-without-GVL optimization,
|
|
188
|
+
// which are needed to attribute the time without GVL to the correct Ruby stack
|
|
189
|
+
|
|
185
190
|
// How many times we triggered the after_gvl_running sampling
|
|
186
191
|
unsigned int after_gvl_running;
|
|
187
192
|
// How many times we skipped the after_gvl_running sampling
|
|
@@ -525,6 +530,8 @@ static VALUE _native_sampling_loop(DDTRACE_UNUSED VALUE _self, VALUE instance) {
|
|
|
525
530
|
// situation we stop immediately and never even start the sampling trigger loop.
|
|
526
531
|
if (state->stop_thread == rb_thread_current()) return Qnil;
|
|
527
532
|
|
|
533
|
+
thread_context_collector_profiler_internal_thread_started();
|
|
534
|
+
|
|
528
535
|
// Reset the dynamic sampling rate state, if any (reminder: the monotonic clock reference may change after a fork)
|
|
529
536
|
dynamic_sampling_rate_reset(&state->cpu_dynamic_sampling_rate);
|
|
530
537
|
long now = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
|
@@ -659,7 +666,7 @@ static void handle_sampling_signal(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED si
|
|
|
659
666
|
if (sample_from_signal_handler) {
|
|
660
667
|
// Buffer current stack trace. Note that this will not actually record the sample, for that we still need to wait
|
|
661
668
|
// until the postponed job below gets run.
|
|
662
|
-
bool prepared = thread_context_collector_prepare_sample_inside_signal_handler(
|
|
669
|
+
bool prepared = thread_context_collector_prepare_sample_inside_signal_handler();
|
|
663
670
|
|
|
664
671
|
if (prepared) state->stats.signal_handler_prepared_sample++;
|
|
665
672
|
}
|
|
@@ -808,8 +815,7 @@ static VALUE rescued_sample_from_postponed_job(VALUE self_instance) {
|
|
|
808
815
|
|
|
809
816
|
state->stats.cpu_sampled++;
|
|
810
817
|
|
|
811
|
-
|
|
812
|
-
thread_context_collector_sample(state->thread_context_collector_instance, wall_time_ns_before_sample, profiler_overhead_stack_thread);
|
|
818
|
+
thread_context_collector_sample(state->thread_context_collector_instance, wall_time_ns_before_sample);
|
|
813
819
|
|
|
814
820
|
long wall_time_ns_after_sample = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
|
815
821
|
long delta_ns = wall_time_ns_after_sample - wall_time_ns_before_sample;
|
|
@@ -865,15 +871,12 @@ static VALUE release_gvl_and_run_sampling_trigger_loop(VALUE instance) {
|
|
|
865
871
|
|
|
866
872
|
if (state->gvl_profiling_enabled) {
|
|
867
873
|
#ifndef NO_GVL_INSTRUMENTATION
|
|
868
|
-
#ifdef USE_GVL_PROFILING_3_2_WORKAROUNDS
|
|
869
|
-
gvl_profiling_state_thread_tracking_workaround();
|
|
870
|
-
#endif
|
|
871
|
-
|
|
872
874
|
state->gvl_profiling_hook = rb_internal_thread_add_event_hook(
|
|
873
875
|
on_gvl_event,
|
|
874
876
|
(
|
|
875
877
|
// For now we're only asking for these events, even though there's more
|
|
876
878
|
// (e.g. check docs or gvl-tracing gem)
|
|
879
|
+
RUBY_INTERNAL_THREAD_EVENT_SUSPENDED | /* released gvl */
|
|
877
880
|
RUBY_INTERNAL_THREAD_EVENT_READY /* waiting for gvl */ |
|
|
878
881
|
RUBY_INTERNAL_THREAD_EVENT_RESUMED /* running/runnable */
|
|
879
882
|
),
|
|
@@ -892,6 +895,8 @@ static VALUE release_gvl_and_run_sampling_trigger_loop(VALUE instance) {
|
|
|
892
895
|
// If we stopped sampling due to an exception, re-raise it (now in the worker thread)
|
|
893
896
|
if (state->failure_exception != Qnil) rb_exc_raise(state->failure_exception);
|
|
894
897
|
|
|
898
|
+
thread_context_collector_profiler_internal_thread_done(state->thread_context_collector_instance);
|
|
899
|
+
|
|
895
900
|
return Qnil;
|
|
896
901
|
}
|
|
897
902
|
|
|
@@ -1055,6 +1060,9 @@ static VALUE _native_simulate_sample_from_postponed_job(DDTRACE_UNUSED VALUE sel
|
|
|
1055
1060
|
//
|
|
1056
1061
|
// In the future, if we add more other components with tracepoints, we will need to coordinate stopping all such
|
|
1057
1062
|
// tracepoints before doing the other cleaning steps.
|
|
1063
|
+
//
|
|
1064
|
+
// Note that tests call this method directly in the same process without forking,
|
|
1065
|
+
// and in such a case non-current Threads keep running.
|
|
1058
1066
|
static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE instance) {
|
|
1059
1067
|
cpu_and_wall_time_worker_state *state;
|
|
1060
1068
|
TypedData_Get_Struct(instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
|
|
@@ -1129,6 +1137,9 @@ static VALUE _native_stats(DDTRACE_UNUSED VALUE self, VALUE instance) {
|
|
|
1129
1137
|
ID2SYM(rb_intern("gvl_waiting_time_ns_total")), /* => */ state->gvl_profiling_enabled ? ULL2NUM(state->stats.vm_metrics.gvl_waiting_time_ns_total) : Qnil,
|
|
1130
1138
|
};
|
|
1131
1139
|
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(stats_as_hash, arguments[i], arguments[i+1]);
|
|
1140
|
+
|
|
1141
|
+
thread_context_collector_stats(state->thread_context_collector_instance, stats_as_hash);
|
|
1142
|
+
|
|
1132
1143
|
return stats_as_hash;
|
|
1133
1144
|
}
|
|
1134
1145
|
|
|
@@ -1136,6 +1147,7 @@ static VALUE _native_stats_reset_not_thread_safe(DDTRACE_UNUSED VALUE self, VALU
|
|
|
1136
1147
|
cpu_and_wall_time_worker_state *state;
|
|
1137
1148
|
TypedData_Get_Struct(instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
|
|
1138
1149
|
reset_stats_not_thread_safe(state);
|
|
1150
|
+
thread_context_collector_stats_reset_not_thread_safe(state->thread_context_collector_instance);
|
|
1139
1151
|
return Qnil;
|
|
1140
1152
|
}
|
|
1141
1153
|
|
|
@@ -1245,13 +1257,22 @@ static void on_newobj_event(DDTRACE_UNUSED VALUE unused1, DDTRACE_UNUSED void *u
|
|
|
1245
1257
|
return;
|
|
1246
1258
|
}
|
|
1247
1259
|
|
|
1260
|
+
VALUE current_thread = rb_thread_current();
|
|
1261
|
+
|
|
1248
1262
|
// If Ruby is in the middle of raising an exception, we don't want to try to sample. This is because if we accidentally
|
|
1249
1263
|
// trigger an exception inside the profiler code, bad things will happen (specifically, Ruby will try to kill off the
|
|
1250
1264
|
// thread even though we may try to catch the exception).
|
|
1251
1265
|
//
|
|
1252
1266
|
// Note that "in the middle of raising an exception" means the exception itself has already been allocated.
|
|
1253
1267
|
// What's getting allocated now is probably the backtrace objects (@ivoanjo or at least that's what I've observed)
|
|
1254
|
-
if (is_raised_flag_set(
|
|
1268
|
+
if (is_raised_flag_set(current_thread)) {
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
per_thread_context *thread_context = get_per_thread_context(current_thread);
|
|
1273
|
+
if (!thread_context) {
|
|
1274
|
+
// Context is created eagerly via on_thread_begin_event, so this should not normally be NULL.
|
|
1275
|
+
// We keep the guard since we can't allocate here (inside on_newobj_event).
|
|
1255
1276
|
return;
|
|
1256
1277
|
}
|
|
1257
1278
|
|
|
@@ -1286,7 +1307,7 @@ static void on_newobj_event(DDTRACE_UNUSED VALUE unused1, DDTRACE_UNUSED void *u
|
|
|
1286
1307
|
// Rescue against any exceptions that happen during sampling
|
|
1287
1308
|
safely_call(
|
|
1288
1309
|
rescued_sample_allocation,
|
|
1289
|
-
|
|
1310
|
+
(VALUE) thread_context,
|
|
1290
1311
|
state->self_instance,
|
|
1291
1312
|
handle_sampling_failure_rescued_sample_allocation
|
|
1292
1313
|
);
|
|
@@ -1338,7 +1359,8 @@ static VALUE _native_with_blocked_sigprof(DDTRACE_UNUSED VALUE self) {
|
|
|
1338
1359
|
}
|
|
1339
1360
|
}
|
|
1340
1361
|
|
|
1341
|
-
static VALUE rescued_sample_allocation(
|
|
1362
|
+
static VALUE rescued_sample_allocation(VALUE arg) {
|
|
1363
|
+
per_thread_context *thread_context = (per_thread_context*) arg;
|
|
1342
1364
|
cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
|
|
1343
1365
|
|
|
1344
1366
|
// This should not happen in a normal situation because on_newobj_event already checked for this, but just in case...
|
|
@@ -1357,7 +1379,7 @@ static VALUE rescued_sample_allocation(DDTRACE_UNUSED VALUE unused) {
|
|
|
1357
1379
|
// To control bias from sampling, we clamp the maximum weight attributed to a single allocation sample. This avoids
|
|
1358
1380
|
// assigning a very large number to a sample, if for instance the dynamic sampling mechanism chose a really big interval.
|
|
1359
1381
|
unsigned int weight = allocations_since_last_sample > MAX_ALLOC_WEIGHT ? MAX_ALLOC_WEIGHT : (unsigned int) allocations_since_last_sample;
|
|
1360
|
-
bool needs_after_allocation = thread_context_collector_sample_allocation(state->thread_context_collector_instance, weight, new_object);
|
|
1382
|
+
bool needs_after_allocation = thread_context_collector_sample_allocation(state->thread_context_collector_instance, thread_context, weight, new_object);
|
|
1361
1383
|
// ...but we still represent the skipped samples in the profile, thus the data will account for all allocations.
|
|
1362
1384
|
if (weight < allocations_since_last_sample) {
|
|
1363
1385
|
uint32_t skipped_samples = (uint32_t) uint64_min_of(allocations_since_last_sample - weight, UINT32_MAX);
|
|
@@ -1414,30 +1436,49 @@ static VALUE _native_resume_signals(DDTRACE_UNUSED VALUE self) {
|
|
|
1414
1436
|
#ifndef NO_GVL_INSTRUMENTATION
|
|
1415
1437
|
static void on_gvl_event(rb_event_flag_t event_id, const rb_internal_thread_event_data_t *event_data, DDTRACE_UNUSED void *_unused) {
|
|
1416
1438
|
// Be very careful about touching the `state` here or doing anything at all:
|
|
1417
|
-
// This function gets called without the GVL, and potentially from
|
|
1418
|
-
//
|
|
1419
|
-
// In fact, the `target_thread` that this event is about may not even be the current thread. (So be careful with thread locals that
|
|
1420
|
-
// are not directly tied to the `target_thread` object and the like)
|
|
1421
|
-
gvl_profiling_thread target_thread = thread_from_event(event_data);
|
|
1439
|
+
// This function gets called without the GVL, and potentially from non-main Ractors!
|
|
1422
1440
|
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
// Interesting note: A RUBY_INTERNAL_THREAD_EVENT_RESUMED is guaranteed to be called with the GVL being acquired.
|
|
1427
|
-
// (And... I think target_thread will be == rb_thread_current()?)
|
|
1428
|
-
//
|
|
1429
|
-
// But we're not sure if we're on the main Ractor yet. The thread context collector can actually help here:
|
|
1430
|
-
// it tags threads it's tracking, so if a thread is tagged then by definition we know that thread belongs to the main
|
|
1431
|
-
// Ractor. Thus, if we get a ON_GVL_RUNNING_UNKNOWN result we shouldn't touch any state, but otherwise we're good to go.
|
|
1441
|
+
// The thread that this event is about may not be the current thread
|
|
1442
|
+
// (as documented on rb_internal_thread_add_event_hook(), and this is notably the case for READY on Ruby 4.0),
|
|
1443
|
+
// so be careful with native thread locals that are not directly tied to the thread object and the like.
|
|
1432
1444
|
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1445
|
+
// On Ruby 3.2 the event does not carry the thread, but all events always fire on the event thread on Ruby 3.2.
|
|
1446
|
+
// However, during early thread startup rb_thread_current() can crash because the execution context (Fiber) isn't
|
|
1447
|
+
// stored in TLS yet; ruby_native_thread_p() guards against this.
|
|
1448
|
+
#ifdef HAVE_RUBY_THREAD_STORAGE_API
|
|
1449
|
+
VALUE target_thread = event_data->thread;
|
|
1450
|
+
#else
|
|
1451
|
+
if (!ruby_native_thread_p()) return;
|
|
1452
|
+
VALUE target_thread = rb_thread_current();
|
|
1453
|
+
#endif
|
|
1436
1454
|
|
|
1455
|
+
per_thread_context* thread_context = get_per_thread_context(target_thread);
|
|
1456
|
+
if (!thread_context) return;
|
|
1457
|
+
// If non-NULL the thread is profiled and from the main Ractor
|
|
1458
|
+
|
|
1459
|
+
if (event_id == RUBY_INTERNAL_THREAD_EVENT_SUSPENDED) { /* released gvl */
|
|
1460
|
+
thread_context_collector_on_gvl_released(thread_context);
|
|
1461
|
+
} else if (event_id == RUBY_INTERNAL_THREAD_EVENT_READY) { /* waiting for gvl */
|
|
1462
|
+
thread_context_collector_on_gvl_waiting(thread_context);
|
|
1463
|
+
} else if (event_id == RUBY_INTERNAL_THREAD_EVENT_RESUMED) { /* running/runnable */
|
|
1464
|
+
// Interesting note: A RUBY_INTERNAL_THREAD_EVENT_RESUMED is guaranteed to be called with the GVL being acquired
|
|
1465
|
+
// and on the event thread.
|
|
1466
|
+
// However, on_gvl_event() is called while holding the scheduler lock, so we do as little work as possible here,
|
|
1467
|
+
// and perform the sample in a postponed_job.
|
|
1437
1468
|
cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
|
|
1438
1469
|
if (state == NULL) return; // This should not happen, but just in case...
|
|
1439
1470
|
|
|
1440
|
-
|
|
1471
|
+
// on_gvl_running prepares a stack sample so we need to gate it with `during_sample_enter` to avoid a
|
|
1472
|
+
// SIGPROF signal coming in during that function and potentially causing a corrupted final state.
|
|
1473
|
+
//
|
|
1474
|
+
// TODO: Currently, the sample prepared in on_gvl_running can still be clobbered if the signal handler runs
|
|
1475
|
+
// after `during_sample_exit` but before `after_gvl_running_from_postponed_job` gets to run. We'll need to fix
|
|
1476
|
+
// that next.
|
|
1477
|
+
during_sample_enter(state);
|
|
1478
|
+
|
|
1479
|
+
on_gvl_running_result result = thread_context_collector_on_gvl_running(state->thread_context_collector_instance, target_thread, thread_context);
|
|
1480
|
+
|
|
1481
|
+
during_sample_exit(state);
|
|
1441
1482
|
|
|
1442
1483
|
if (result.waiting_for_gvl_duration_ns > 0) {
|
|
1443
1484
|
state->stats.vm_metrics.gvl_waiting_time_ns_total += (uint64_t) result.waiting_for_gvl_duration_ns;
|
|
@@ -1453,8 +1494,7 @@ static VALUE _native_resume_signals(DDTRACE_UNUSED VALUE self) {
|
|
|
1453
1494
|
state->stats.gvl_dont_sample++;
|
|
1454
1495
|
}
|
|
1455
1496
|
} else {
|
|
1456
|
-
|
|
1457
|
-
fprintf(stderr, "[ddtrace] Unexpected value in on_gvl_event (%d)\n", event_id);
|
|
1497
|
+
rb_bug("[ddtrace] Unexpected value in on_gvl_event (%d)\n", event_id);
|
|
1458
1498
|
}
|
|
1459
1499
|
}
|
|
1460
1500
|
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
#include "helpers.h"
|
|
7
7
|
#include "ruby_helpers.h"
|
|
8
8
|
#include "collectors_idle_sampling_helper.h"
|
|
9
|
+
#include "collectors_thread_context.h"
|
|
9
10
|
|
|
10
11
|
// Used by the Collectors::CpuAndWallTimeWorker to gather samples when the Ruby process is idle.
|
|
11
12
|
//
|
|
@@ -30,7 +31,9 @@ typedef struct {
|
|
|
30
31
|
|
|
31
32
|
static VALUE _native_new(VALUE klass);
|
|
32
33
|
static void reset_state(idle_sampling_loop_state *state);
|
|
33
|
-
static VALUE _native_idle_sampling_loop(DDTRACE_UNUSED VALUE self, VALUE self_instance);
|
|
34
|
+
static VALUE _native_idle_sampling_loop(DDTRACE_UNUSED VALUE self, VALUE self_instance, VALUE thread_context_collector_instance);
|
|
35
|
+
static VALUE idle_sampling_loop_body(VALUE self_instance);
|
|
36
|
+
static VALUE idle_sampling_loop_ensure(VALUE thread_context_collector_instance);
|
|
34
37
|
static VALUE _native_stop(DDTRACE_UNUSED VALUE self, VALUE self_instance);
|
|
35
38
|
static void *run_idle_sampling_loop(void *state_ptr);
|
|
36
39
|
static void interrupt_idle_sampling_loop(void *state_ptr);
|
|
@@ -56,7 +59,7 @@ void collectors_idle_sampling_helper_init(VALUE profiling_module) {
|
|
|
56
59
|
// https://bugs.ruby-lang.org/issues/18007 for a discussion around this.
|
|
57
60
|
rb_define_alloc_func(collectors_idle_sampling_helper_class, _native_new);
|
|
58
61
|
|
|
59
|
-
rb_define_singleton_method(collectors_idle_sampling_helper_class, "_native_idle_sampling_loop", _native_idle_sampling_loop,
|
|
62
|
+
rb_define_singleton_method(collectors_idle_sampling_helper_class, "_native_idle_sampling_loop", _native_idle_sampling_loop, 2);
|
|
60
63
|
rb_define_singleton_method(collectors_idle_sampling_helper_class, "_native_stop", _native_stop, 1);
|
|
61
64
|
rb_define_singleton_method(collectors_idle_sampling_helper_class, "_native_reset", _native_reset, 1);
|
|
62
65
|
rb_define_singleton_method(testing_module, "_native_idle_sampling_helper_request_action", _native_idle_sampling_helper_request_action, 1);
|
|
@@ -109,16 +112,27 @@ static VALUE _native_reset(DDTRACE_UNUSED VALUE self, VALUE self_instance) {
|
|
|
109
112
|
return Qtrue;
|
|
110
113
|
}
|
|
111
114
|
|
|
112
|
-
static VALUE _native_idle_sampling_loop(DDTRACE_UNUSED VALUE self, VALUE self_instance) {
|
|
115
|
+
static VALUE _native_idle_sampling_loop(DDTRACE_UNUSED VALUE self, VALUE self_instance, VALUE thread_context_collector_instance) {
|
|
116
|
+
return rb_ensure(idle_sampling_loop_body, self_instance, idle_sampling_loop_ensure, thread_context_collector_instance);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
static VALUE idle_sampling_loop_body(VALUE self_instance) {
|
|
113
120
|
idle_sampling_loop_state *state;
|
|
114
121
|
TypedData_Get_Struct(self_instance, idle_sampling_loop_state, &idle_sampling_helper_typed_data, state);
|
|
115
122
|
|
|
123
|
+
thread_context_collector_profiler_internal_thread_started();
|
|
124
|
+
|
|
116
125
|
// Release GVL and run the loop waiting for requests
|
|
117
126
|
rb_thread_call_without_gvl(run_idle_sampling_loop, state, interrupt_idle_sampling_loop, state);
|
|
118
127
|
|
|
119
128
|
return Qtrue;
|
|
120
129
|
}
|
|
121
130
|
|
|
131
|
+
static VALUE idle_sampling_loop_ensure(VALUE thread_context_collector_instance) {
|
|
132
|
+
thread_context_collector_profiler_internal_thread_done(thread_context_collector_instance);
|
|
133
|
+
return Qnil;
|
|
134
|
+
}
|
|
135
|
+
|
|
122
136
|
static void *run_idle_sampling_loop(void *state_ptr) {
|
|
123
137
|
idle_sampling_loop_state *state = (idle_sampling_loop_state *) state_ptr;
|
|
124
138
|
int error = 0;
|
|
@@ -208,7 +222,7 @@ void idle_sampling_helper_request_action(VALUE self_instance, void (*run_action_
|
|
|
208
222
|
if (!rb_typeddata_is_kind_of(self_instance, &idle_sampling_helper_typed_data)) {
|
|
209
223
|
grab_gvl_and_raise(rb_eTypeError, "Wrong argument for idle_sampling_helper_request_action");
|
|
210
224
|
}
|
|
211
|
-
// This should never fail
|
|
225
|
+
// This should never fail when the above check passes
|
|
212
226
|
TypedData_Get_Struct(self_instance, idle_sampling_loop_state, &idle_sampling_helper_typed_data, state);
|
|
213
227
|
|
|
214
228
|
ENFORCE_SUCCESS_NO_GVL(pthread_mutex_lock(&state->wakeup_mutex));
|
|
@@ -40,7 +40,7 @@ static void set_file_info_for_cfunc(
|
|
|
40
40
|
st_table *native_filenames_cache
|
|
41
41
|
);
|
|
42
42
|
static const char *get_or_compute_native_filename(void *function, st_table *native_filenames_cache);
|
|
43
|
-
static void add_truncated_frames_placeholder(
|
|
43
|
+
static void add_truncated_frames_placeholder(ddog_prof_Location *locations);
|
|
44
44
|
static void record_placeholder_stack_in_native_code(VALUE recorder_instance, sample_values values, sample_labels labels);
|
|
45
45
|
static void maybe_trim_template_random_ids(ddog_CharSlice *name_slice, ddog_CharSlice *filename_slice);
|
|
46
46
|
|
|
@@ -98,7 +98,7 @@ typedef struct {
|
|
|
98
98
|
sample_values values;
|
|
99
99
|
sample_labels labels;
|
|
100
100
|
VALUE thread;
|
|
101
|
-
|
|
101
|
+
sample_locations locations;
|
|
102
102
|
sampling_buffer *buffer;
|
|
103
103
|
bool native_filenames_enabled;
|
|
104
104
|
st_table *native_filenames_cache;
|
|
@@ -175,7 +175,7 @@ static VALUE _native_sample(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self) {
|
|
|
175
175
|
|
|
176
176
|
ddog_prof_Location *locations = ruby_xcalloc(max_frames_requested, sizeof(ddog_prof_Location));
|
|
177
177
|
sampling_buffer buffer;
|
|
178
|
-
sampling_buffer_initialize(&buffer, max_frames_requested
|
|
178
|
+
sampling_buffer_initialize(&buffer, max_frames_requested);
|
|
179
179
|
|
|
180
180
|
ddog_prof_Slice_Label slice_labels = {.ptr = labels, .len = labels_count};
|
|
181
181
|
|
|
@@ -185,7 +185,7 @@ static VALUE _native_sample(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self) {
|
|
|
185
185
|
.values = values,
|
|
186
186
|
.labels = (sample_labels) {.labels = slice_labels, .state_label = state_label, .is_gvl_waiting_state = is_gvl_waiting_state == Qtrue},
|
|
187
187
|
.thread = thread,
|
|
188
|
-
.locations = locations,
|
|
188
|
+
.locations = (sample_locations) {.ptr = locations, .len = max_frames_requested},
|
|
189
189
|
.buffer = &buffer,
|
|
190
190
|
.native_filenames_enabled = native_filenames_enabled == Qtrue,
|
|
191
191
|
.native_filenames_cache = st_init_numtable(),
|
|
@@ -208,6 +208,7 @@ static VALUE native_sample_do(VALUE args) {
|
|
|
208
208
|
sample_thread(
|
|
209
209
|
args_struct->thread,
|
|
210
210
|
args_struct->buffer,
|
|
211
|
+
args_struct->locations,
|
|
211
212
|
args_struct->recorder_instance,
|
|
212
213
|
args_struct->values,
|
|
213
214
|
args_struct->labels,
|
|
@@ -222,7 +223,7 @@ static VALUE native_sample_do(VALUE args) {
|
|
|
222
223
|
static VALUE native_sample_ensure(VALUE args) {
|
|
223
224
|
native_sample_args *args_struct = (native_sample_args *) args;
|
|
224
225
|
|
|
225
|
-
ruby_xfree(args_struct->locations);
|
|
226
|
+
ruby_xfree(args_struct->locations.ptr);
|
|
226
227
|
sampling_buffer_free(args_struct->buffer);
|
|
227
228
|
st_free_table(args_struct->native_filenames_cache);
|
|
228
229
|
|
|
@@ -243,6 +244,7 @@ static VALUE native_sample_ensure(VALUE args) {
|
|
|
243
244
|
void sample_thread(
|
|
244
245
|
VALUE thread,
|
|
245
246
|
sampling_buffer* buffer,
|
|
247
|
+
sample_locations locations,
|
|
246
248
|
VALUE recorder_instance,
|
|
247
249
|
sample_values values,
|
|
248
250
|
sample_labels labels,
|
|
@@ -250,11 +252,21 @@ void sample_thread(
|
|
|
250
252
|
st_table *native_filenames_cache
|
|
251
253
|
) {
|
|
252
254
|
// If we already prepared a sample, we use it below; if not, we prepare it now.
|
|
253
|
-
if (!buffer->pending_sample)
|
|
255
|
+
if (!buffer->pending_sample) {
|
|
256
|
+
// Reconcile the sampling_buffer's max_frames with the locations size
|
|
257
|
+
if (buffer->max_frames != locations.len) {
|
|
258
|
+
sampling_buffer_reinitialize(buffer, locations.len);
|
|
259
|
+
}
|
|
260
|
+
prepare_sample_thread(thread, buffer);
|
|
261
|
+
}
|
|
254
262
|
|
|
255
263
|
buffer->pending_sample = false;
|
|
256
264
|
int captured_frames = buffer->pending_sample_result;
|
|
257
265
|
|
|
266
|
+
// The per_thread_context's sampling_buffer may have been created by a previous collector with a
|
|
267
|
+
// different (larger) max_frames. Cap to the locations array size to prevent out-of-bounds writes.
|
|
268
|
+
if (captured_frames > (int) locations.len) captured_frames = (int) locations.len;
|
|
269
|
+
|
|
258
270
|
if (captured_frames == PLACEHOLDER_STACK_IN_NATIVE_CODE) {
|
|
259
271
|
record_placeholder_stack_in_native_code(recorder_instance, values, labels);
|
|
260
272
|
return;
|
|
@@ -389,25 +401,30 @@ void sample_thread(
|
|
|
389
401
|
|
|
390
402
|
int libdatadog_stores_stacks_flipped_from_rb_profile_frames_index = top_of_stack_position - i;
|
|
391
403
|
|
|
392
|
-
|
|
404
|
+
locations.ptr[libdatadog_stores_stacks_flipped_from_rb_profile_frames_index] = (ddog_prof_Location) {
|
|
393
405
|
.mapping = {.filename = DDOG_CHARSLICE_C(""), .build_id = DDOG_CHARSLICE_C(""), .build_id_id = {}},
|
|
394
406
|
.function = (ddog_prof_Function) {.name = name_slice, .filename = filename_slice},
|
|
395
407
|
.line = line,
|
|
396
408
|
};
|
|
397
409
|
}
|
|
398
410
|
|
|
399
|
-
// If we filled up the
|
|
411
|
+
// If we filled up the locations, some frames may have been omitted. In that case, we'll add a placeholder frame
|
|
400
412
|
// with that info.
|
|
401
|
-
if (captured_frames == (long)
|
|
402
|
-
add_truncated_frames_placeholder(
|
|
413
|
+
if (captured_frames == (long) locations.len) {
|
|
414
|
+
add_truncated_frames_placeholder(locations.ptr);
|
|
403
415
|
}
|
|
404
416
|
|
|
405
417
|
record_sample(
|
|
406
418
|
recorder_instance,
|
|
407
|
-
(ddog_prof_Slice_Location) {.ptr =
|
|
419
|
+
(ddog_prof_Slice_Location) {.ptr = locations.ptr, .len = captured_frames},
|
|
408
420
|
values,
|
|
409
421
|
labels
|
|
410
422
|
);
|
|
423
|
+
|
|
424
|
+
// Reconcile the sampling_buffer's max_frames with the locations size for future samples
|
|
425
|
+
if (buffer->max_frames != locations.len) {
|
|
426
|
+
sampling_buffer_reinitialize(buffer, locations.len);
|
|
427
|
+
}
|
|
411
428
|
}
|
|
412
429
|
|
|
413
430
|
#if (defined(HAVE_DLADDR1) && HAVE_DLADDR1) || (defined(HAVE_DLADDR) && HAVE_DLADDR)
|
|
@@ -538,10 +555,10 @@ static void maybe_trim_template_random_ids(ddog_CharSlice *name_slice, ddog_Char
|
|
|
538
555
|
name_slice->len = pos;
|
|
539
556
|
}
|
|
540
557
|
|
|
541
|
-
static void add_truncated_frames_placeholder(
|
|
558
|
+
static void add_truncated_frames_placeholder(ddog_prof_Location *locations) {
|
|
542
559
|
// Important note: The strings below are static so we don't need to worry about their lifetime. If we ever want to change
|
|
543
560
|
// this to non-static strings, don't forget to check that lifetimes are properly respected.
|
|
544
|
-
|
|
561
|
+
locations[0] = (ddog_prof_Location) {
|
|
545
562
|
.mapping = {.filename = DDOG_CHARSLICE_C(""), .build_id = DDOG_CHARSLICE_C(""), .build_id_id = {}},
|
|
546
563
|
.function = {.name = DDOG_CHARSLICE_C("Truncated Frames"), .filename = DDOG_CHARSLICE_C(""), .filename_id = {}},
|
|
547
564
|
.line = 0,
|
|
@@ -618,27 +635,29 @@ uint16_t sampling_buffer_check_max_frames(int max_frames) {
|
|
|
618
635
|
return max_frames;
|
|
619
636
|
}
|
|
620
637
|
|
|
621
|
-
void sampling_buffer_initialize(sampling_buffer *buffer, uint16_t max_frames
|
|
638
|
+
void sampling_buffer_initialize(sampling_buffer *buffer, uint16_t max_frames) {
|
|
622
639
|
sampling_buffer_check_max_frames(max_frames);
|
|
623
640
|
|
|
624
641
|
buffer->max_frames = max_frames;
|
|
625
|
-
buffer->locations = locations;
|
|
626
642
|
buffer->stack_buffer = ruby_xcalloc(max_frames, sizeof(frame_info));
|
|
627
643
|
buffer->pending_sample = false;
|
|
628
644
|
buffer->is_marking = false;
|
|
629
645
|
buffer->pending_sample_result = 0;
|
|
630
646
|
}
|
|
631
647
|
|
|
648
|
+
void sampling_buffer_reinitialize(sampling_buffer *buffer, uint16_t max_frames) {
|
|
649
|
+
sampling_buffer_free(buffer);
|
|
650
|
+
sampling_buffer_initialize(buffer, max_frames);
|
|
651
|
+
}
|
|
652
|
+
|
|
632
653
|
void sampling_buffer_free(sampling_buffer *buffer) {
|
|
633
|
-
if (buffer->max_frames == 0 || buffer->
|
|
654
|
+
if (buffer->max_frames == 0 || buffer->stack_buffer == NULL) {
|
|
634
655
|
raise_error(rb_eArgError, "sampling_buffer_free called with invalid buffer");
|
|
635
656
|
}
|
|
636
657
|
|
|
637
658
|
ruby_xfree(buffer->stack_buffer);
|
|
638
|
-
// Note: buffer->locations are owned by whoever called sampling_buffer_initialize, not by the buffer itself
|
|
639
659
|
|
|
640
660
|
buffer->max_frames = 0;
|
|
641
|
-
buffer->locations = NULL;
|
|
642
661
|
buffer->stack_buffer = NULL;
|
|
643
662
|
buffer->pending_sample = false;
|
|
644
663
|
buffer->is_marking = false;
|