Skip to content

perf(driver/modern_bpf): skip dynamic snaplen when it cannot change the outcome - #3075

Draft
therealbobo wants to merge 2 commits into
falcosecurity:masterfrom
therealbobo:perf/modern-bpf-skip-noop-dynamic-snaplen
Draft

perf(driver/modern_bpf): skip dynamic snaplen when it cannot change the outcome#3075
therealbobo wants to merge 2 commits into
falcosecurity:masterfrom
therealbobo:perf/modern-bpf-skip-noop-dynamic-snaplen

Conversation

@therealbobo

Copy link
Copy Markdown
Contributor

What type of PR is this?

Uncomment one (or more) /kind <> lines:

/kind bug

/kind cleanup

/kind design

/kind documentation

/kind failing-test

/kind test

/kind feature

/kind sync

Any specific area of the project related to this PR?

Uncomment one (or more) /area <> lines:

/area API-version

/area build

/area automation

/area drivers

/area driver-kmod

/area driver-modern-bpf

/area libscap-engine-kmod

/area libscap-engine-modern-bpf

/area libscap-engine-nodriver

/area libscap-engine-noop

/area libscap-engine-source-plugin

/area libscap-engine-savefile

/area libscap

/area libpman

/area libsinsp

/area tests

/area proposals

Does this PR require a change in the driver versions?

/version driver-API-version-major

/version driver-API-version-minor

/version driver-API-version-patch

/version driver-SCHEMA-version-major

/version driver-SCHEMA-version-minor

/version driver-SCHEMA-version-patch

What this PR does / why we need it:

perf(driver/modern_bpf): skip dynamic snaplen when it cannot change the outcome

apply_dynamic_snaplen() can only ever raise the snaplen: every assignment it
performs is a max() against one of the SNAPLEN_* constants, so the value
never goes down. (The constants are not necessarily above the configured
snaplen - it is user-settable with no cap, while SNAPLEN_DNS_UDP is 512 - but
the max() is what makes the logic monotonic, not their magnitude.) Callers
then clamp the result back down to the number of bytes they are actually going
to capture.

So whenever that number already fits within the configured snaplen, the final
value is that number regardless of what the dynamic snaplen logic computes, and
the whole computation is dead work: an fd -> file -> socket walk (several
kernel reads) plus a DPI_LOOKAHEAD_SIZE read of the user buffer. For the
sendmmsg/recvmmsg bpf_loop() callbacks this was repeated once per message in
the batch.

Introduce apply_dynamic_snaplen_if_relevant() (and the matching
_port_range_if_relevant() for the __noinline variant used inside the
bpf_loop() callbacks) which takes the number of bytes the caller will capture
and skips the call when it cannot affect the result. Callers that cannot know
that number upfront pass a negative value and always apply the logic:
writev/pwritev/sendmsg/sendmmsg do not clamp when ret <= 0
(resp. msg_len == 0), so a raised snaplen still matters there.

send/sendto only needed bytes_to_read to be computed before the call
instead of after it; the call has no side effects beyond writing *snaplen.

Measured with scap-open --modern_bpf --ppm_sc 3, 5M 64-byte reads pinned to
one CPU, reading run_time_ns off the sys_exit dispatcher (which includes the
tail-called handler) via kernel.bpf_stats_enabled, min of 8 runs:

dynamic snaplen enabled: 477.6 -> 349.7 ns/read (-26.8%)
dynamic snaplen disabled: 357.3 -> 355.6 ns/read (unchanged, within noise)

Event output was verified byte-identical against the unpatched probe across
read, write, sendto, writev, sendmsg, sendmmsg, recvmmsg and recvmsg, covering
payloads below and above the snaplen, regular-file and UDP-socket fds, failing
syscalls, and a payload that trips the HTTP heuristic so the dynamic snaplen is
genuinely raised.

Static instruction counts are flat (-6 to +15 out of 950-2830), and the probe
loads with no verifier issues.

Which issue(s) this PR fixes:

Fixes #

Special notes for your reviewer:

Does this PR introduce a user-facing change?:

NONE

…he outcome

`apply_dynamic_snaplen()` can only ever raise the snaplen: every assignment it
performs is a `max()` against one of the `SNAPLEN_*` constants, so the value
never goes down. (The constants are not necessarily above the configured
snaplen - it is user-settable with no cap, while `SNAPLEN_DNS_UDP` is 512 - but
the `max()` is what makes the logic monotonic, not their magnitude.) Callers
then clamp the result back down to the number of bytes they are actually going
to capture.

So whenever that number already fits within the configured snaplen, the final
value is that number regardless of what the dynamic snaplen logic computes, and
the whole computation is dead work: an `fd -> file -> socket` walk (several
kernel reads) plus a `DPI_LOOKAHEAD_SIZE` read of the user buffer. For the
sendmmsg/recvmmsg `bpf_loop()` callbacks this was repeated once per message in
the batch.

Introduce `apply_dynamic_snaplen_if_relevant()` (and the matching
`_port_range_if_relevant()` for the `__noinline` variant used inside the
`bpf_loop()` callbacks) which takes the number of bytes the caller will capture
and skips the call when it cannot affect the result. Callers that cannot know
that number upfront pass a negative value and always apply the logic:
`writev`/`pwritev`/`sendmsg`/`sendmmsg` do not clamp when `ret <= 0`
(resp. `msg_len == 0`), so a raised snaplen still matters there.

`send`/`sendto` only needed `bytes_to_read` to be computed before the call
instead of after it; the call has no side effects beyond writing `*snaplen`.

Measured with `scap-open --modern_bpf --ppm_sc 3`, 5M 64-byte reads pinned to
one CPU, reading `run_time_ns` off the `sys_exit` dispatcher (which includes the
tail-called handler) via `kernel.bpf_stats_enabled`, min of 8 runs:

  dynamic snaplen enabled:   477.6 -> 349.7 ns/read  (-26.8%)
  dynamic snaplen disabled:  357.3 -> 355.6 ns/read  (unchanged, within noise)

Event output was verified byte-identical against the unpatched probe across
read, write, sendto, writev, sendmsg, sendmmsg, recvmmsg and recvmsg, covering
payloads below and above the snaplen, regular-file and UDP-socket fds, failing
syscalls, and a payload that trips the HTTP heuristic so the dynamic snaplen is
genuinely raised.

Static instruction counts are flat (-6 to +15 out of 950-2830), and the probe
loads with no verifier issues.

Signed-off-by: Roberto Scolaro <roberto.scolaro21@gmail.com>
The dynamic snaplen logic is now skipped whenever the data already fits within
the configured snaplen. Add tests pinning both sides of that boundary, plus the
paths that had no dynamic snaplen coverage at all.

- `sendmmsg`/`recvmmsg` had no dynamic snaplen tests despite running the logic
  once per message inside a `bpf_loop()` callback. Two tests each: a message
  longer than the snaplen must be captured in full via the fullcapture port,
  and a message shorter than the snaplen must be captured exactly.

- `writev` had no dynamic snaplen tests either. It only applies the port-range
  part of the logic, so it is covered with a fullcapture port.

- `dynamic_snaplen_writev_fullcapture_port_failed_syscall` covers the case
  where the syscall failed: the snaplen is then never clamped down to `ret`, so
  the logic has to run unconditionally. Skipping it there would silently
  truncate the captured data to DEFAULT_SNAPLEN.

- `dynamic_snaplen_HTTP_below_snaplen` pins that a payload shorter than the
  snaplen is captured exactly, whether or not the HTTP heuristic runs.

Every new test was checked to fail against a deliberately broken guard: making
the logic always skip breaks the three `not_truncated`/`fullcapture_port` tests
(80 bytes captured instead of 100/121/160), and dropping the negative-sentinel
check breaks the failed-syscall test. The existing suite did not catch that last
mutation.

Full drivers suite passes against the modern BPF probe (403 tests).

Signed-off-by: Roberto Scolaro <roberto.scolaro21@gmail.com>
@poiana

poiana commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: therealbobo

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@poiana poiana added size/XXL and removed size/L labels Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

Perf diff from master - unit tests

    10.71%    +11.01%  [.] sinsp_thread_manager::create_thread_dependencies(std::shared_ptr<sinsp_threadinfo> const&)
    18.85%     -9.47%  [.] std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_add_ref_lock_nothrow()
    19.78%     -8.63%  [.] sinsp_threadinfo::get_main_thread()
    14.23%     +5.55%  [.] std::__shared_ptr<sinsp_threadinfo, (__gnu_cxx::_Lock_policy)2>::__shared_ptr(std::__weak_ptr<sinsp_threadinfo, (__gnu_cxx::_Lock_policy)2> const&, std::nothrow_t)
     4.58%     +4.48%  [.] std::__shared_count<(__gnu_cxx::_Lock_policy)2>::_M_get_use_count() const
     7.22%     -2.04%  [.] std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count(std::__weak_count<(__gnu_cxx::_Lock_policy)2> const&, std::nothrow_t)
     9.24%     -0.47%  [.] std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release()
     0.50%     -0.27%  [.] scap_event_encode_params_v
     0.63%     -0.13%  [.] sinsp_thread_manager::clear()
     3.62%     +0.12%  [.] sinsp_threadinfo::get_fd_table() const

Heap diff from master - unit tests

peak heap memory consumption: 0B
peak RSS (including heaptrack overhead): 0B
total memory leaked: 0B

Heap diff from master - scap file

peak heap memory consumption: 0B
peak RSS (including heaptrack overhead): 0B
total memory leaked: 0B

Benchmarks diff from master

Comparing gbench_data.json to /root/actions-runner/_work/libs/libs/build/gbench_data.json
Benchmark                                                                             Time             CPU      Time Old      Time New       CPU Old       CPU New
------------------------------------------------------------------------------------------------------------------------------------------------------------------
BM_sinsp_split_mean                                                                +0.0041         +0.0042           288           289           288           289
BM_sinsp_split_median                                                              +0.0074         +0.0077           287           289           287           289
BM_sinsp_split_stddev                                                              -0.4408         -0.4430             5             3             5             3
BM_sinsp_split_cv                                                                  -0.4431         -0.4453             0             0             0             0
BM_sinsp_concatenate_paths_relative_path_mean                                      +0.0099         +0.0101            67            68            67            68
BM_sinsp_concatenate_paths_relative_path_median                                    +0.0121         +0.0127            67            68            67            68
BM_sinsp_concatenate_paths_relative_path_stddev                                    +8.9959         +9.0483             0             2             0             2
BM_sinsp_concatenate_paths_relative_path_cv                                        +8.8977         +8.9478             0             0             0             0
BM_sinsp_concatenate_paths_empty_path_mean                                         +0.0218         +0.0219            45            46            45            46
BM_sinsp_concatenate_paths_empty_path_median                                       +0.0280         +0.0281            44            46            44            46
BM_sinsp_concatenate_paths_empty_path_stddev                                       +0.3096         +0.3096             1             2             1             2
BM_sinsp_concatenate_paths_empty_path_cv                                           +0.2816         +0.2815             0             0             0             0
BM_sinsp_concatenate_paths_absolute_path_mean                                      +0.1816         +0.1816            66            78            66            78
BM_sinsp_concatenate_paths_absolute_path_median                                    +0.1904         +0.1906            66            79            66            79
BM_sinsp_concatenate_paths_absolute_path_stddev                                    +3.2161         +3.2053             0             1             0             1
BM_sinsp_concatenate_paths_absolute_path_cv                                        +2.5680         +2.5590             0             0             0             0
BM_sinsp_utf8_sanitize_fast_path_ascii_short_mean                                  +0.0022         +0.0022            15            15            15            15
BM_sinsp_utf8_sanitize_fast_path_ascii_short_median                                +0.0008         +0.0009            15            15            15            15
BM_sinsp_utf8_sanitize_fast_path_ascii_short_stddev                                +9.9766         +9.6619             0             0             0             0
BM_sinsp_utf8_sanitize_fast_path_ascii_short_cv                                    +9.9528         +9.6385             0             0             0             0
BM_sinsp_utf8_sanitize_fast_path_ascii_long_mean                                   +0.0013         +0.0013            87            87            87            87
BM_sinsp_utf8_sanitize_fast_path_ascii_long_median                                 +0.0015         +0.0014            87            87            87            87
BM_sinsp_utf8_sanitize_fast_path_ascii_long_stddev                                 +0.0310         -0.0346             0             0             0             0
BM_sinsp_utf8_sanitize_fast_path_ascii_long_cv                                     +0.0296         -0.0359             0             0             0             0
BM_sinsp_utf8_sanitize_fast_path_multibyte_short_mean                              -0.0080         -0.0080            12            12            12            12
BM_sinsp_utf8_sanitize_fast_path_multibyte_short_median                            -0.0207         -0.0208            12            12            12            12
BM_sinsp_utf8_sanitize_fast_path_multibyte_short_stddev                          +156.3913       +153.9631             0             1             0             1
BM_sinsp_utf8_sanitize_fast_path_multibyte_short_cv                              +157.6675       +155.2150             0             0             0             0
BM_sinsp_utf8_sanitize_fast_path_multibyte_long_mean                               +0.0060         +0.0059          3801          3824          3800          3823
BM_sinsp_utf8_sanitize_fast_path_multibyte_long_median                             +0.0006         +0.0006          3801          3804          3800          3802
BM_sinsp_utf8_sanitize_fast_path_multibyte_long_stddev                            +72.7411        +61.3870             1            64             1            64
BM_sinsp_utf8_sanitize_fast_path_multibyte_long_cv                                +72.3030        +61.0194             0             0             0             0
BM_sinsp_utf8_sanitize_fast_path_mixed_long_mean                                   -0.0008         -0.0008          1676          1675          1675          1674
BM_sinsp_utf8_sanitize_fast_path_mixed_long_median                                 -0.0024         -0.0024          1683          1679          1683          1679
BM_sinsp_utf8_sanitize_fast_path_mixed_long_stddev                                 -0.0595         -0.0590            25            24            25            24
BM_sinsp_utf8_sanitize_fast_path_mixed_long_cv                                     -0.0587         -0.0582             0             0             0             0
BM_sinsp_utf8_sanitize_slow_path_c1_controls_long_alloc_mean                       +0.0296         +0.0296          1115          1148          1114          1147
BM_sinsp_utf8_sanitize_slow_path_c1_controls_long_alloc_median                     +0.0272         +0.0272          1114          1144          1114          1144
BM_sinsp_utf8_sanitize_slow_path_c1_controls_long_alloc_stddev                    +15.5040        +16.0680             2            27             2            27
BM_sinsp_utf8_sanitize_slow_path_c1_controls_long_alloc_cv                        +15.0294        +15.5766             0             0             0             0
BM_sinsp_utf8_sanitize_slow_path_c1_controls_long_noalloc_mean                     +0.0104         +0.0103          1112          1123          1111          1123
BM_sinsp_utf8_sanitize_slow_path_c1_controls_long_noalloc_median                   +0.0074         +0.0073          1111          1120          1111          1119
BM_sinsp_utf8_sanitize_slow_path_c1_controls_long_noalloc_stddev                  +11.8220        +11.5194             1            13             1            13
BM_sinsp_utf8_sanitize_slow_path_c1_controls_long_noalloc_cv                      +11.6904        +11.3912             0             0             0             0
BM_sinsp_utf8_sanitize_slow_path_sparse_invalid_long_alloc_mean                    -0.0166         -0.0165           235           231           235           231
BM_sinsp_utf8_sanitize_slow_path_sparse_invalid_long_alloc_median                  -0.0181         -0.0178           234           229           234           229
BM_sinsp_utf8_sanitize_slow_path_sparse_invalid_long_alloc_stddev                  +0.4157         +0.4165             4             5             4             5
BM_sinsp_utf8_sanitize_slow_path_sparse_invalid_long_alloc_cv                      +0.4396         +0.4403             0             0             0             0
BM_sinsp_utf8_sanitize_slow_path_sparse_invalid_long_noalloc_mean                  -0.0032         -0.0030           143           143           143           143
BM_sinsp_utf8_sanitize_slow_path_sparse_invalid_long_noalloc_median                +0.0014         +0.0016           142           142           142           142
BM_sinsp_utf8_sanitize_slow_path_sparse_invalid_long_noalloc_stddev                -0.4884         -0.4863             2             1             2             1
BM_sinsp_utf8_sanitize_slow_path_sparse_invalid_long_noalloc_cv                    -0.4868         -0.4848             0             0             0             0
BM_sinsp_utf8_sanitize_slow_path_all_invalid_long_alloc_mean                       -0.0043         -0.0042         11817         11766         11812         11762
BM_sinsp_utf8_sanitize_slow_path_all_invalid_long_alloc_median                     +0.0008         +0.0007         11749         11758         11744         11752
BM_sinsp_utf8_sanitize_slow_path_all_invalid_long_alloc_stddev                     -0.7587         -0.7580           187            45           186            45
BM_sinsp_utf8_sanitize_slow_path_all_invalid_long_alloc_cv                         -0.7576         -0.7570             0             0             0             0
BM_sinsp_utf8_sanitize_slow_path_all_invalid_long_noalloc_mean                     +0.0198         +0.0199         11311         11535         11307         11531
BM_sinsp_utf8_sanitize_slow_path_all_invalid_long_noalloc_median                   +0.0319         +0.0320         11178         11534         11174         11531
BM_sinsp_utf8_sanitize_slow_path_all_invalid_long_noalloc_stddev                   -0.9856         -0.9858          1056            15          1056            15
BM_sinsp_utf8_sanitize_slow_path_all_invalid_long_noalloc_cv                       -0.9859         -0.9861             0             0             0             0

@codecov

codecov Bot commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 76.56%. Comparing base (1a1d1e7) to head (c08cdd5).

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #3075   +/-   ##
=======================================
  Coverage   76.56%   76.56%           
=======================================
  Files         301      301           
  Lines       33693    33693           
  Branches     5038     5038           
=======================================
  Hits        25797    25797           
  Misses       7896     7896           
Flag Coverage Δ
libsinsp 76.56% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

2 participants