enhancement(app): drive memory limiting behavior through configurable modes#1591
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f392a77413
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| let limiter_grant = match configuration.memory_mode { | ||
| MemoryMode::Disabled => { | ||
| info!("Memory limiting disabled."); | ||
| Some(MemoryGrant::unbounded()) |
There was a problem hiding this comment.
Return a no-op limiter when disabled
When memory_mode is left at its new default disabled and enable_global_limiter is also left at its default true, returning Some(MemoryGrant::unbounded()) still drives the later MemoryLimiter::new(grant) path. That means a mode documented as disabled now spawns the limiter checker and can fail startup with Memory statistics cannot be gathered on this system on platforms where RSS stats are unavailable, whereas the previous no-limit path returned MemoryLimiter::noop() without touching process-memory APIs. The disabled branch should avoid creating an active limiter grant.
Useful? React with 👍 / 👎.
Regression Detector (Agent Data Plane)Regression Detector ResultsRun ID: 70e46712-32f4-44ea-bb1d-d7d64ca0e432 Baseline: 0f0001b Optimization Goals: ✅ No significant changes detected
|
| perf | experiment | goal | Δ mean % | Δ mean % CI | trials | links |
|---|---|---|---|---|---|---|
| ➖ | otlp_ingest_logs_5mb_memory | memory utilization | +1.65 | [+1.44, +1.87] | 1 | (metrics) (profiles) (logs) |
| ➖ | otlp_ingest_logs_5mb_cpu | % cpu utilization | +0.80 | [-3.68, +5.28] | 1 | (metrics) (profiles) (logs) |
| ➖ | otlp_ingest_logs_5mb_throughput | ingress throughput | -0.00 | [-0.12, +0.12] | 1 | (metrics) (profiles) (logs) |
Fine details of change detection per experiment
| perf | experiment | goal | Δ mean % | Δ mean % CI | trials | links |
|---|---|---|---|---|---|---|
| ➖ | dsd_uds_1mb_3k_contexts_cpu | % cpu utilization | +6.76 | [-47.22, +60.73] | 1 | (metrics) (profiles) (logs) |
| ➖ | dsd_uds_10mb_3k_contexts_cpu | % cpu utilization | +2.59 | [-27.22, +32.41] | 1 | (metrics) (profiles) (logs) |
| ➖ | otlp_ingest_logs_5mb_memory | memory utilization | +1.65 | [+1.44, +1.87] | 1 | (metrics) (profiles) (logs) |
| ➖ | otlp_ingest_traces_5mb_cpu | % cpu utilization | +1.41 | [-0.60, +3.42] | 1 | (metrics) (profiles) (logs) |
| ➖ | dsd_uds_512kb_3k_contexts_cpu | % cpu utilization | +1.35 | [-54.08, +56.78] | 1 | (metrics) (profiles) (logs) |
| ➖ | otlp_ingest_traces_ottl_filtering_5mb_throughput | ingress throughput | +1.16 | [+1.06, +1.26] | 1 | (metrics) (profiles) (logs) |
| ➖ | dsd_uds_500mb_3k_contexts_throughput | ingress throughput | +1.00 | [+0.88, +1.12] | 1 | (metrics) (profiles) (logs) |
| ➖ | otlp_ingest_metrics_5mb_memory | memory utilization | +0.82 | [+0.62, +1.02] | 1 | (metrics) (profiles) (logs) |
| ➖ | otlp_ingest_logs_5mb_cpu | % cpu utilization | +0.80 | [-3.68, +5.28] | 1 | (metrics) (profiles) (logs) |
| ➖ | dsd_uds_500mb_3k_contexts_cpu | % cpu utilization | +0.52 | [-0.98, +2.02] | 1 | (metrics) (profiles) (logs) |
| ➖ | otlp_ingest_traces_ottl_transform_5mb_memory | memory utilization | +0.24 | [+0.08, +0.40] | 1 | (metrics) (profiles) (logs) |
| ➖ | otlp_ingest_traces_ottl_filtering_5mb_memory | memory utilization | +0.21 | [-0.02, +0.45] | 1 | (metrics) (profiles) (logs) |
| ➖ | dsd_uds_500mb_3k_contexts_memory | memory utilization | +0.21 | [+0.06, +0.35] | 1 | (metrics) (profiles) (logs) |
| ➖ | quality_gates_rss_dsd_ultraheavy | memory utilization | +0.21 | [+0.07, +0.34] | 1 | (metrics) (profiles) (logs) |
| ➖ | dsd_uds_100mb_3k_contexts_cpu | % cpu utilization | +0.19 | [-5.61, +5.99] | 1 | (metrics) (profiles) (logs) |
| ➖ | quality_gates_rss_dsd_medium | memory utilization | +0.16 | [-0.01, +0.33] | 1 | (metrics) (profiles) (logs) |
| ➖ | dsd_uds_512kb_3k_contexts_memory | memory utilization | +0.13 | [-0.01, +0.27] | 1 | (metrics) (profiles) (logs) |
| ➖ | otlp_ingest_traces_ottl_filtering_5mb_cpu | % cpu utilization | +0.07 | [-2.09, +2.23] | 1 | (metrics) (profiles) (logs) |
| ➖ | quality_gates_rss_idle | memory utilization | +0.07 | [+0.03, +0.11] | 1 | (metrics) (profiles) (logs) |
| ➖ | dsd_uds_10mb_3k_contexts_throughput | ingress throughput | +0.02 | [-0.17, +0.21] | 1 | (metrics) (profiles) (logs) |
| ➖ | dsd_uds_100mb_3k_contexts_memory | memory utilization | +0.01 | [-0.14, +0.16] | 1 | (metrics) (profiles) (logs) |
| ➖ | otlp_ingest_metrics_5mb_throughput | ingress throughput | +0.00 | [-0.15, +0.16] | 1 | (metrics) (profiles) (logs) |
| ➖ | dsd_uds_512kb_3k_contexts_throughput | ingress throughput | +0.00 | [-0.05, +0.05] | 1 | (metrics) (profiles) (logs) |
| ➖ | dsd_uds_1mb_3k_contexts_throughput | ingress throughput | +0.00 | [-0.06, +0.06] | 1 | (metrics) (profiles) (logs) |
| ➖ | otlp_ingest_logs_5mb_throughput | ingress throughput | -0.00 | [-0.12, +0.12] | 1 | (metrics) (profiles) (logs) |
| ➖ | dsd_uds_100mb_3k_contexts_throughput | ingress throughput | -0.01 | [-0.05, +0.02] | 1 | (metrics) (profiles) (logs) |
| ➖ | otlp_ingest_traces_5mb_memory | memory utilization | -0.02 | [-0.18, +0.14] | 1 | (metrics) (profiles) (logs) |
| ➖ | dsd_uds_10mb_3k_contexts_memory | memory utilization | -0.08 | [-0.23, +0.08] | 1 | (metrics) (profiles) (logs) |
| ➖ | quality_gates_rss_dsd_heavy | memory utilization | -0.10 | [-0.23, +0.03] | 1 | (metrics) (profiles) (logs) |
| ➖ | otlp_ingest_traces_5mb_throughput | ingress throughput | -0.19 | [-0.26, -0.12] | 1 | (metrics) (profiles) (logs) |
| ➖ | dsd_uds_1mb_3k_contexts_memory | memory utilization | -0.21 | [-0.35, -0.07] | 1 | (metrics) (profiles) (logs) |
| ➖ | quality_gates_rss_dsd_low | memory utilization | -0.24 | [-0.40, -0.08] | 1 | (metrics) (profiles) (logs) |
| ➖ | otlp_ingest_traces_ottl_transform_5mb_throughput | ingress throughput | -0.32 | [-0.39, -0.24] | 1 | (metrics) (profiles) (logs) |
| ➖ | otlp_ingest_metrics_5mb_cpu | % cpu utilization | -0.75 | [-6.50, +5.01] | 1 | (metrics) (profiles) (logs) |
| ➖ | otlp_ingest_traces_ottl_transform_5mb_cpu | % cpu utilization | -1.24 | [-3.25, +0.78] | 1 | (metrics) (profiles) (logs) |
Bounds Checks: ✅ Passed
| perf | experiment | bounds_check_name | replicates_passed | observed_value | links |
|---|---|---|---|---|---|
| ✅ | quality_gates_rss_dsd_heavy | memory_usage | 10/10 | 123.60MiB ≤ 140MiB | (metrics) (profiles) (logs) |
| ✅ | quality_gates_rss_dsd_low | memory_usage | 10/10 | 40.71MiB ≤ 50MiB | (metrics) (profiles) (logs) |
| ✅ | quality_gates_rss_dsd_medium | memory_usage | 10/10 | 60.65MiB ≤ 75MiB | (metrics) (profiles) (logs) |
| ✅ | quality_gates_rss_dsd_ultraheavy | memory_usage | 10/10 | 177.42MiB ≤ 200MiB | (metrics) (profiles) (logs) |
| ✅ | quality_gates_rss_idle | memory_usage | 10/10 | 27.64MiB ≤ 40MiB | (metrics) (profiles) (logs) |
Explanation
Confidence level: 90.00%
Effect size tolerance: |Δ mean %| ≥ 5.00%
Performance changes are noted in the perf column of each table:
- ✅ = significantly better comparison variant performance
- ❌ = significantly worse comparison variant performance
- ➖ = no significant change in performance
A regression test is an A/B test of target performance in a repeatable rig, where "performance" is measured as "comparison variant minus baseline variant" for an optimization goal (e.g., ingress throughput). Due to intrinsic variability in measuring that goal, we can only estimate its mean value for each experiment; we report uncertainty in that value as a 90.00% confidence interval denoted "Δ mean % CI".
For each experiment, we decide whether a change in performance is a "regression" -- a change worth investigating further -- if all of the following criteria are true:
-
Its estimated |Δ mean %| ≥ 5.00%, indicating the change is big enough to merit a closer look.
-
Its 90.00% confidence interval "Δ mean % CI" does not contain zero, indicating that if our statistical model is accurate, there is at least a 90.00% chance there is a difference in performance between baseline and comparison variants.
-
Its configuration does not mark it "erratic".
lucastemb
left a comment
There was a problem hiding this comment.
I know you mentioned wanting to go over the doc comments, but the code looks good to me 👍
Binary Size Analysis (Agent Data Plane)Target: 0f0001b (baseline) vs 2490a72 (comparison) diff
|
| Module | File Size | Symbols |
|---|---|---|
tracing |
-8.14 KiB | 3 |
figment |
+7.92 KiB | 15 |
hashbrown |
+7.54 KiB | 33 |
core |
-6.60 KiB | 644 |
bytes |
+6.48 KiB | 13 |
h2 |
+5.89 KiB | 105 |
anyhow |
+5.71 KiB | 34 |
serde_json |
-4.44 KiB | 7 |
saluki_app::logging::build_output_stack |
+4.04 KiB | 2 |
rustls |
+3.82 KiB | 2 |
http_body_util |
-3.28 KiB | 1 |
tracing_appender |
-3.28 KiB | 3 |
crossbeam_channel |
-2.96 KiB | 10 |
tracing_subscriber |
+2.88 KiB | 26 |
saluki_app::logging::syslog |
-2.56 KiB | 4 |
[sections] |
+2.25 KiB | 7 |
serde_html_form |
-2.25 KiB | 4 |
tower |
+1.91 KiB | 17 |
tokio |
-1.89 KiB | 55 |
[Unmapped] |
+1.74 KiB | 1 |
Detailed Symbol Changes
FILE SIZE VM SIZE
-------------- --------------
+0.9% +34.0Ki +1.0% +28.6Ki [2656 Others]
[NEW] +26.4Ki [NEW] +26.2Ki _<figment::value::de::ConfiguredValueDe<I> as serde_core::de::Deserializer>::deserialize_struct::h025543ea3fec2e45
[NEW] +8.55Ki [NEW] +8.45Ki h2::proto::connection::Connection<T,P,B>::poll::h25f96fc3015fa649
[NEW] +8.34Ki [NEW] +8.24Ki h2::proto::connection::Connection<T,P,B>::poll::hf7363029696bd16e
[NEW] +6.17Ki [NEW] +6.04Ki _<core::future::poll_fn::PollFn<F> as core::future::future::Future>::poll::hf8fe7ccdd5dcfb77
[NEW] +5.14Ki [NEW] +5.04Ki saluki_app::memory::MemoryBoundsConfiguration::try_from_config::h3277a01cd29194c5
[NEW] +4.78Ki [NEW] +4.62Ki _<figment::value::de::ConfiguredValueDe<I> as serde_core::de::Deserializer>::deserialize_any::h2a207968b7c35151
+95% +4.25Ki +96% +4.25Ki saluki_app::logging::build_output_stack::h8c73f95efff6aca6
+42% +3.80Ki +43% +3.80Ki _<rustls::error::Error as core::clone::Clone>::clone::h5d268dcb651f6988
[NEW] +3.79Ki [NEW] +3.70Ki saluki_app::memory::initialize_memory_bounds::h838fd3bddba7987d
[NEW] +3.43Ki [NEW] +3.31Ki _<bytes::bytes_mut::BytesMut as bytes::buf::buf_mut::BufMut>::put::hb1205e20738e6a41
[NEW] +2.95Ki [NEW] +2.84Ki h2::proto::streams::streams::DynStreams<B>::recv_push_promise::h98b1756e4ccce078
-74.0% -3.25Ki -75.7% -3.25Ki h2::proto::streams::streams::StreamRef<B>::send_data::h6f07527fb0901ff4
-69.2% -3.28Ki -71.8% -3.28Ki _<http_body_util::util::BufList<T> as bytes::buf::buf_impl::Buf>::copy_to_bytes::hc11e5a80535d90e3
[DEL] -4.54Ki [DEL] -4.44Ki saluki_app::memory::MemoryBoundsConfiguration::try_from_config::h938a0705c0d1d5a1
[DEL] -4.95Ki [DEL] -4.76Ki serde_json::value::de::_<impl serde_core::de::Deserialize for serde_json::value::Value>::deserialize::hfd46eabc0227358d
-48.8% -7.02Ki -49.3% -7.02Ki _<tracing::instrument::Instrumented<T> as core::future::future::Future>::poll::hf6beb522713a97cb
[DEL] -14.4Ki [DEL] -14.3Ki h2::server::Connection<T,B>::poll_closed::hb373424f939b3e4c
-72.2% -14.7Ki -72.6% -14.7Ki h2::proto::connection::DynConnection<B>::recv_frame::h18bf0080f259e42b
[DEL] -15.3Ki [DEL] -15.2Ki h2::server::Connection<T,B>::poll_closed::h1236950a7f1f4ba4
[DEL] -23.9Ki [DEL] -23.7Ki _<figment::value::de::ConfiguredValueDe<I> as serde_core::de::Deserializer>::deserialize_struct::hcbf3c952b886a73f
+0.1% +20.2Ki +0.0% +14.3Ki TOTAL
Summary
This PR adds support for configuring memory limiting behavior, and by extension, the validation of memory bounds, through a set of configurable memory "modes."
Presently, memory limiting behavior is always on: if we detect a memory limit (either specified through configuration, or extracted from the ambient cgroups limits), we validate the bounds of the configured topology and either proceed or exit if we detect that we cannot firmly adhere to the configured bounds. Technically correct, but not particularly flexible... especially during the early stage of introducing ADP to the world.
In this PR, we address this by introducing the concept of a memory "mode": a way to specify how memory limiting and bounds validation should work. At the core, we expose three modes:
We default to disabled to ensure that users have an equivalent experience when using ADP as they're used to with the Datadog Agent. Permissive and strict are designed to allow for gradually opting in to memory limiting behavior: allowing things like the global memory limiter to actively influence component behavior, dialing in configuration settings to explore the resulting memory bounds, eventually culminating in (hopefully) switching to strict mode to lock in their tuning.
The global memory limiter is also left as-is, in terms of being configurable but defaulting to enabled. However, in the "disabled" mode, we simply set the memory limiter's grant to an (essentially) unbounded value. This was simply the expedient design choice but I'm thinking I may forcefully disable it when in the "disabled" memory mode just to be a little cleaner.
Change Type
How did you test this PR?
Existing tests, and five new integration tests that exercise the different memory modes.
References
DADP-2