Skip to content

refactor(ipc)!: make the shared memory table generic over a slot schema - #2673

Merged
arjunguhaswe merged 11 commits into
feat/arjun.guha/dsm-php-integrationfrom
arjun.guha/shm-table-generic
Oct 27, 2026
Merged

arjunguhaswe merged 11 commits into
feat/arjun.guha/dsm-php-integrationfrom
arjun.guha/shm-table-generic

Conversation

@arjunguhaswe

@arjunguhaswe arjunguhaswe commented Oct 14, 2026 •

Copy link
Copy Markdown

What does this PR do?

Splits the lock-free shared memory table out of libdd-ipc/src/shm_stats.rs into a new shm_table module that is generic over what a slot holds.

  • ShmTable<S: Schema> holds everything that doesn't depend on slot contents: the header, layout validation, slot claiming, the two buckets, the string pool, and reload/replacement. This code is moved, not rewritten.
  • A Schema trait defines a slot's key and value, and how an input is hashed, matched and recorded. It is an unsafe trait: keys and values must be #[repr(C)] integer atomics, because they live in memory every peer can write.
  • APM client stats become the first schema. ShmSpanConcentrator is now an alias for ShmTable<SpanStats> with the same API, so the sidecar and the PHP tracer don't change.
  • The header records the schema ID and version, and open refuses a segment written with another schema. This changes the header layout, so SHM_VERSION goes from 3 to 4.

Motivation

We're bringing Data Streams Monitoring to the PHP tracer. DSM needs the same shared memory table as client stats, but with a different slot: a pathway key and three histograms. Without this split we'd need a second copy of code that is both lock-free and hardened against untrusted memory. The DSM schema is in the stacked PR on top of this one.

Additional Notes

  • No behavior change for client stats. The 18 existing shm_stats tests are byte-for-byte unchanged.
  • Marked breaking (!) because of the SHM_VERSION bump, and because ShmSpanConcentrator is now a type alias rather than a struct.
  • Found while doing this, but not changed here: a bucket's start_nanos is only set when it is drained. So the first two flushes of a new concentrator report start = 0, and later buckets are dated one interval early. I reproduced this on the parent commit and can open a separate issue.
  • Found while testing, but not caused by this PR: the curl_exec does not appropriate tag http.status_code, it appears as N/A

How to test the change?

cargo test -p libdd-ipc --lib
cargo clippy -p libdd-ipc -p datadog-sidecar --all-targets -- -D warnings

New tests in shm_table use a small test-only schema. They check that a segment only opens as the schema and version that created it, and that a schema can record into the table and drain it.

Furthermore, this PR was tested in comparison with the current release branch 1.26.0, with several operations for APM client stats as noted in the table below. In all cases, number of stats points sent, their groups and their tags were all equivalent.

┌──────────────────────────────────────────────────────────┬───────────────┬───────────────┬──────────────────────────────────────────┐
│                   Span (stats metric)                    │ This PR build │    Release    │          Same groups and tags?           │
├──────────────────────────────────────────────────────────┼───────────────┼───────────────┼──────────────────────────────────────────┤
│ producer.iteration (root, internal)                      │ 2,757         │ 2,764         │ ✅                                       │
├──────────────────────────────────────────────────────────┼───────────────┼───────────────┼──────────────────────────────────────────┤
│ kafka.produce (producer, peer.messaging.destination)     │ 2,757         │ 2,764         │ ✅                                       │
├──────────────────────────────────────────────────────────┼───────────────┼───────────────┼──────────────────────────────────────────┤
│ kafka.consume                                            │ 2,758         │ 2,765         │ ✅                                       │
│                                                          │               │               │                                          │
├──────────────────────────────────────────────────────────┼───────────────┼───────────────┼──────────────────────────────────────────┤
│ Redis.incr / set / get (peer.db.system:redis,            │ 2,757 each    │ 2,764 each    │ ✅                                       │
│ peer.hostname:redis)                                     │               │               │                                          │
├──────────────────────────────────────────────────────────┼───────────────┼───────────────┼──────────────────────────────────────────┤
│ PDO.prepare / PDOStatement.execute / PDO.query (mysql,   │ 2,757 each    │ 2,764 each    │ ✅                                       │
│ peer.db.name:dsm)                                        │               │               │                                          │
├──────────────────────────────────────────────────────────┼───────────────┼───────────────┼──────────────────────────────────────────┤
│ Backend web.request 200 / 404 / 500                      │ 2,341 / 283 / │ 2,358 / 261 / │ ✅                                       │
│                                                          │  131          │  140          │                                          │
├──────────────────────────────────────────────────────────┼───────────────┼───────────────┼──────────────────────────────────────────┤
│ web.request errors (the 500s)                            │ 131           │ 140           │ ✅                                       │
├──────────────────────────────────────────────────────────┼───────────────┼───────────────┼──────────────────────────────────────────┤

🤖 Generated with Claude Code

Split the lock-free shared memory table out of `shm_stats` into
`shm_table`, so that products other than APM client stats can store a
different slot layout without duplicating the table.

`ShmTable<S: Schema>` holds everything that does not depend on what a
slot contains: the header, layout validation, slot claiming, the two
buckets, the string pool, and reload/replacement. A `Schema` defines
the slot's key and value and how an input is hashed, matched and
recorded. APM client stats become the first schema; `ShmSpanConcentrator`
is now an alias with the same API, so callers do not change.

The header records the schema ID and version, and opening a segment
written with another schema fails. This changes the header layout, so
`SHM_VERSION` goes from 3 to 4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

📚 Documentation Check Results

⚠️ 1038 documentation warning(s) found

📦 libdd-ipc - 1038 warning(s)


Updated: 2026-10-27 15:00:47 UTC | Commit: 87f6ded | missing-docs job results

@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

🔒 Cargo Deny Results

⚠️ 6 issue(s) found, showing only errors (advisories, bans, sources)

📦 libdd-ipc - 6 error(s)

Show output
info: syncing channel updates for 1.87.0-x86_64-unknown-linux-gnu
info: latest update on 2025-05-15 for version 1.87.0 (17067e9ac 2025-05-09)
info: downloading 6 components
warn: the missing active toolchain `1.87.0-x86_64-unknown-linux-gnu` has been auto-installed
warn: this might cause rustup commands to take longer time to finish than expected
info: you may opt out with `RUSTUP_AUTO_INSTALL=0` or `rustup set auto-install disable`
error[unmaintained]: Bincode is unmaintained
   ┌─ /home/runner/work/libdatadog/libdatadog/Cargo.lock:24:1
   │
24 │ bincode 1.3.3 registry+https://github.com/rust-lang/crates.io-index
   │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ unmaintained advisory detected
   │
   ├ ID: RUSTSEC-2025-0141
   ├ Advisory: https://rustsec.org/advisories/RUSTSEC-2025-0141
   ├ Due to a doxxing and harassment incident, the bincode team has taken the decision to cease development permanently.
     
     The team considers version 1.3.3 a complete version of bincode that is not in need of any updates.
     
     ## Alternatives to consider
     
     * [wincode](https://crates.io/crates/wincode)
     * [postcard](https://crates.io/crates/postcard)
     * [bitcode](https://crates.io/crates/bitcode)
     * [rkyv](https://crates.io/crates/rkyv)
   ├ Announcement: https://git.sr.ht/~stygianentity/bincode/tree/v3.0/item/README.md
   ├ Solution: No safe upgrade is available!
   ├ bincode v1.3.3
     └── libdd-ipc v2.0.0

error[vulnerability]: TLS 1.3 handshake messages incorrectly accepted across encryption level boundaries
    ┌─ /home/runner/work/libdatadog/libdatadog/Cargo.lock:230:1
    │
230 │ rustls 0.23.37 registry+https://github.com/rust-lang/crates.io-index
    │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ security vulnerability detected
    │
    ├ ID: RUSTSEC-2026-0285
    ├ Advisory: https://rustsec.org/advisories/RUSTSEC-2026-0285
    ├ Rustls accepted TLS 1.3 handshake messages sent at the wrong encryption level
      when they followed a key-changing message in the same record. For example,
      a plaintext `EncryptedExtensions` message packed into the same record as the
      `ServerHello` was accepted.
      
      RFC 8446 section 5.1 requires that handshake messages do not span key changes,
      and that implementations terminate the connection with an "unexpected_message"
      alert if they do.
      
      The handshake transcript is still authenticated, so a network-position attacker
      cannot use this to alter or complete a handshake; the practical effect is that
      a peer could send handshake messages that should be encrypted in plaintext
      without rustls rejecting the connection.
      
      This is functionally the same bug as Go's
      [GO-2026-4340](https://pkg.go.dev/vuln/GO-2026-4340) (CVE-2025-61730).
    ├ Announcement: https://github.com/rustls/rustls/pull/3265
    ├ Solution: Upgrade to >=0.23.45 (try `cargo update -p rustls`)
    ├ rustls v0.23.37
      ├── hyper-rustls v0.27.7
      │   └── libdd-common v8.0.0
      │       ├── libdd-capabilities-impl v7.0.0
      │       │   ├── libdd-shared-runtime v6.0.0
      │       │   │   └── libdd-trace-stats v11.0.0
      │       │   │       └── libdd-ipc v2.0.0
      │       │   ├── libdd-trace-stats v11.0.0 (*)
      │       │   └── libdd-trace-utils v14.0.0
      │       │       ├── libdd-trace-obfuscation v10.0.0
      │       │       │   └── libdd-trace-stats v11.0.0 (*)
      │       │       ├── libdd-trace-stats v11.0.0 (*)
      │       │       └── (dev) libdd-trace-utils v14.0.0 (*)
      │       ├── libdd-ipc v2.0.0 (*)
      │       ├── libdd-shared-runtime v6.0.0 (*)
      │       ├── libdd-trace-obfuscation v10.0.0 (*)
      │       ├── libdd-trace-stats v11.0.0 (*)
      │       └── libdd-trace-utils v14.0.0 (*)
      ├── libdd-common v8.0.0 (*)
      ├── rustls-platform-verifier v0.6.2
      │   └── libdd-common v8.0.0 (*)
      └── tokio-rustls v0.26.0
          └── hyper-rustls v0.27.7 (*)

error[vulnerability]: `Debug` impls for client session values expose TLS secrets
    ┌─ /home/runner/work/libdatadog/libdatadog/Cargo.lock:230:1
    │
230 │ rustls 0.23.37 registry+https://github.com/rust-lang/crates.io-index
    │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ security vulnerability detected
    │
    ├ ID: RUSTSEC-2026-0346
    ├ Advisory: https://rustsec.org/advisories/RUSTSEC-2026-0346
    ├ The `Debug` implementations of `Tls12ClientSessionValue` and
      `Tls13ClientSessionValue` were derived, and so included the TLS 1.2 master
      secret or TLS 1.3 resumption PSK in their output.
      
      Custom `ClientSessionStore` implementations that format these values with
      `Debug` (for example, when logging) could disclose these secrets to readers of
      the logs. This conflicts with rustls's
      [key-material redaction policy](https://github.com/rustls/rustls/blob/99f2358cae2954837dbb866faf6727de75489ab9/SECURITY.md#boundary-public-api).
      
      Ordinary connection and built-in session cache `Debug` output is redacted and
      is not affected.
    ├ Announcement: https://github.com/rustls/rustls/pull/3299
    ├ Solution: Upgrade to >=0.23.46 (try `cargo update -p rustls`)
    ├ rustls v0.23.37
      ├── hyper-rustls v0.27.7
      │   └── libdd-common v8.0.0
      │       ├── libdd-capabilities-impl v7.0.0
      │       │   ├── libdd-shared-runtime v6.0.0
      │       │   │   └── libdd-trace-stats v11.0.0
      │       │   │       └── libdd-ipc v2.0.0
      │       │   ├── libdd-trace-stats v11.0.0 (*)
      │       │   └── libdd-trace-utils v14.0.0
      │       │       ├── libdd-trace-obfuscation v10.0.0
      │       │       │   └── libdd-trace-stats v11.0.0 (*)
      │       │       ├── libdd-trace-stats v11.0.0 (*)
      │       │       └── (dev) libdd-trace-utils v14.0.0 (*)
      │       ├── libdd-ipc v2.0.0 (*)
      │       ├── libdd-shared-runtime v6.0.0 (*)
      │       ├── libdd-trace-obfuscation v10.0.0 (*)
      │       ├── libdd-trace-stats v11.0.0 (*)
      │       └── libdd-trace-utils v14.0.0 (*)
      ├── libdd-common v8.0.0 (*)
      ├── rustls-platform-verifier v0.6.2
      │   └── libdd-common v8.0.0 (*)
      └── tokio-rustls v0.26.0
          └── hyper-rustls v0.27.7 (*)

error[vulnerability]: Brotli certificate decompression can allocate up to 1 GiB before authentication
    ┌─ /home/runner/work/libdatadog/libdatadog/Cargo.lock:230:1
    │
230 │ rustls 0.23.37 registry+https://github.com/rust-lang/crates.io-index
    │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ security vulnerability detected
    │
    ├ ID: RUSTSEC-2026-0348
    ├ Advisory: https://rustsec.org/advisories/RUSTSEC-2026-0348
    ├ The built-in Brotli certificate decompressor (enabled by the non-default
      `brotli` crate feature) bounded the size of the decompressed output, but
      accepted a peer-selected history window. Brotli's large-window extension was
      enabled, so a five-byte header could cause an allocation of 1 GiB. A peer can
      abort a process whose allocator cannot satisfy that request.
      
      No trusted certificate is required by the attacker. Affected configurations are:
      
      - clients that enable the `brotli` feature and connect to a malicious server, and
      - servers that enable the `brotli` feature and request client certificates.
      
      Applications that do not enable the `brotli` feature are not affected.
    ├ Announcement: https://github.com/rustls/rustls/pull/3297
    ├ Solution: Upgrade to >=0.23.46 (try `cargo update -p rustls`)
    ├ rustls v0.23.37
      ├── hyper-rustls v0.27.7
      │   └── libdd-common v8.0.0
      │       ├── libdd-capabilities-impl v7.0.0
      │       │   ├── libdd-shared-runtime v6.0.0
      │       │   │   └── libdd-trace-stats v11.0.0
      │       │   │       └── libdd-ipc v2.0.0
      │       │   ├── libdd-trace-stats v11.0.0 (*)
      │       │   └── libdd-trace-utils v14.0.0
      │       │       ├── libdd-trace-obfuscation v10.0.0
      │       │       │   └── libdd-trace-stats v11.0.0 (*)
      │       │       ├── libdd-trace-stats v11.0.0 (*)
      │       │       └── (dev) libdd-trace-utils v14.0.0 (*)
      │       ├── libdd-ipc v2.0.0 (*)
      │       ├── libdd-shared-runtime v6.0.0 (*)
      │       ├── libdd-trace-obfuscation v10.0.0 (*)
      │       ├── libdd-trace-stats v11.0.0 (*)
      │       └── libdd-trace-utils v14.0.0 (*)
      ├── libdd-common v8.0.0 (*)
      ├── rustls-platform-verifier v0.6.2
      │   └── libdd-common v8.0.0 (*)
      └── tokio-rustls v0.26.0
          └── hyper-rustls v0.27.7 (*)

error[vulnerability]: Unauthenticated ServerHello can downgrade later connections to classical key exchange
    ┌─ /home/runner/work/libdatadog/libdatadog/Cargo.lock:230:1
    │
230 │ rustls 0.23.37 registry+https://github.com/rust-lang/crates.io-index
    │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ security vulnerability detected
    │
    ├ ID: RUSTSEC-2026-0349
    ├ Advisory: https://rustsec.org/advisories/RUSTSEC-2026-0349
    ├ The client recorded the server-selected key exchange group for each server
      name when processing the ServerHello, before the server was authenticated.
      This record is used to predict which key share to send in future ClientHellos
      to that server name.
      
      A network attacker can answer one connection attempt with a ServerHello
      selecting X25519, and then close the connection. Later ClientHellos for that
      name carry only an X25519 key share, while `supported_groups` still lists
      X25519MLKEM768 first. If the genuine server accepts that share rather than
      sending a HelloRetryRequest for its preferred hybrid group, the handshake
      completes with classical-only key exchange and no error. This persists for as
      long as the entry stays in the client's session store.
      
      The impact is loss of post-quantum confidentiality for affected connections
      ("harvest now, decrypt later"). There is no key compromise and no
      authentication bypass.
      
      The following are not affected:
      
      - connections to servers which send a HelloRetryRequest for their preferred group,
      - clients with resumption disabled (as nothing is stored), and
      - other server names.
      
      This behaviour is discussed in
      [draft-ietf-tls-key-share-prediction](https://www.ietf.org/archive/id/draft-ietf-tls-key-share-prediction-01.html#name-security-considerations),
      so the practical impact depends largely on server policy.
    ├ Announcement: https://github.com/rustls/rustls/pull/3355
    ├ Solution: Upgrade to >=0.23.46 (try `cargo update -p rustls`)
    ├ rustls v0.23.37
      ├── hyper-rustls v0.27.7
      │   └── libdd-common v8.0.0
      │       ├── libdd-capabilities-impl v7.0.0
      │       │   ├── libdd-shared-runtime v6.0.0
      │       │   │   └── libdd-trace-stats v11.0.0
      │       │   │       └── libdd-ipc v2.0.0
      │       │   ├── libdd-trace-stats v11.0.0 (*)
      │       │   └── libdd-trace-utils v14.0.0
      │       │       ├── libdd-trace-obfuscation v10.0.0
      │       │       │   └── libdd-trace-stats v11.0.0 (*)
      │       │       ├── libdd-trace-stats v11.0.0 (*)
      │       │       └── (dev) libdd-trace-utils v14.0.0 (*)
      │       ├── libdd-ipc v2.0.0 (*)
      │       ├── libdd-shared-runtime v6.0.0 (*)
      │       ├── libdd-trace-obfuscation v10.0.0 (*)
      │       ├── libdd-trace-stats v11.0.0 (*)
      │       └── libdd-trace-utils v14.0.0 (*)
      ├── libdd-common v8.0.0 (*)
      ├── rustls-platform-verifier v0.6.2
      │   └── libdd-common v8.0.0 (*)
      └── tokio-rustls v0.26.0
          └── hyper-rustls v0.27.7 (*)

error[unsound]: `AsyncFd::new` and related constructors violate I/O safety
    ┌─ /home/runner/work/libdatadog/libdatadog/Cargo.lock:280:1
    │
280 │ tokio 1.49.0 registry+https://github.com/rust-lang/crates.io-index
    │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ unsound advisory detected
    │
    ├ ID: RUSTSEC-2026-0386
    ├ Advisory: https://rustsec.org/advisories/RUSTSEC-2026-0386
    ├ `tokio::io::unix::AsyncFd` provides safe constructors (`new`, `with_interest`, `try_new`, and `try_with_interest`) that accept any `T: AsRawFd` rather than requiring proof of ownership or validity of the file descriptor. Because `RawFd` implements `AsRawFd`, safe code can construct an `AsyncFd` from an arbitrary or closed file descriptor, or close/replace the underlying file descriptor while the `AsyncFd` is still alive.
      
      This violates I/O safety in two ways:
      
      1. `AsyncFd<T>` implements `AsFd` for all `T: AsRawFd` by calling `BorrowedFd::borrow_raw(self.as_raw_fd())`, allowing safe code to obtain an arbitrary `BorrowedFd<'_>` without owning the file descriptor.
      2. Tokio's I/O driver stores a pointer to an internal allocation in the `epoll`/`kqueue` user data for registered file descriptors. If the underlying file descriptor is duplicated and then closed while an `AsyncFd` is alive, and the OS reuses that file descriptor number for a another IO resource on the same runtime, dropping the first `AsyncFd` deregisters the reused file descriptor number and frees the first while the original file description remains registered in `epoll`, leading to a use-after-free when a readiness event fires.
      
      Starting in Tokio 1.51.6, 1.53.3, and 1.54.0, `AsyncFd::new`, `AsyncFd::with_interest`, `AsyncFd::try_new`, and `AsyncFd::try_with_interest` are deprecated in favor of the `unsafe` constructors `AsyncFd::register` and `AsyncFd::register_with_interest`. Callers must ensure that the file descriptor remains valid, open, and unchanged for the lifetime of the `AsyncFd`.
    ├ Announcement: https://github.com/tokio-rs/tokio/pull/8587
    ├ Solution: Upgrade to >=1.51.6, <1.52.0 OR >=1.53.3 (try `cargo update -p tokio`)
    ├ tokio v1.49.0
      ├── h2 v0.4.19
      │   └── hyper v1.11.1
      │       ├── httpmock v0.8.0-alpha.1
      │       │   ├── (dev) libdd-trace-stats v11.0.0
      │       │   │   └── libdd-ipc v2.0.0
      │       │   └── libdd-trace-utils v14.0.0
      │       │       ├── libdd-trace-obfuscation v10.0.0
      │       │       │   └── libdd-trace-stats v11.0.0 (*)
      │       │       ├── libdd-trace-stats v11.0.0 (*)
      │       │       └── (dev) libdd-trace-utils v14.0.0 (*)
      │       ├── hyper-rustls v0.27.7
      │       │   └── libdd-common v8.0.0
      │       │       ├── libdd-capabilities-impl v7.0.0
      │       │       │   ├── libdd-shared-runtime v6.0.0
      │       │       │   │   └── libdd-trace-stats v11.0.0 (*)
      │       │       │   ├── libdd-trace-stats v11.0.0 (*)
      │       │       │   └── libdd-trace-utils v14.0.0 (*)
      │       │       ├── libdd-ipc v2.0.0 (*)
      │       │       ├── libdd-shared-runtime v6.0.0 (*)
      │       │       ├── libdd-trace-obfuscation v10.0.0 (*)
      │       │       ├── libdd-trace-stats v11.0.0 (*)
      │       │       └── libdd-trace-utils v14.0.0 (*)
      │       ├── hyper-util v0.1.17
      │       │   ├── httpmock v0.8.0-alpha.1 (*)
      │       │   ├── hyper-rustls v0.27.7 (*)
      │       │   └── libdd-common v8.0.0 (*)
      │       ├── libdd-common v8.0.0 (*)
      │       └── libdd-trace-utils v14.0.0 (*)
      ├── httpmock v0.8.0-alpha.1 (*)
      ├── hyper v1.11.1 (*)
      ├── hyper-rustls v0.27.7 (*)
      ├── hyper-util v0.1.17 (*)
      ├── (dev) libdd-capabilities-impl v7.0.0 (*)
      ├── (dev) libdd-common v8.0.0 (*)
      ├── (dev) libdd-ipc v2.0.0 (*)
      ├── libdd-shared-runtime v6.0.0 (*)
      ├── (dev) libdd-trace-protobuf v5.1.0
      │   ├── libdd-ipc v2.0.0 (*)
      │   ├── libdd-trace-normalization v4.2.0
      │   │   └── libdd-trace-utils v14.0.0 (*)
      │   ├── libdd-trace-obfuscation v10.0.0 (*)
      │   ├── libdd-trace-stats v11.0.0 (*)
      │   └── libdd-trace-utils v14.0.0 (*)
      ├── (dev) libdd-trace-stats v11.0.0 (*)
      ├── libdd-trace-utils v14.0.0 (*)
      ├── tokio-rustls v0.26.0
      │   └── hyper-rustls v0.27.7 (*)
      └── tokio-util v0.7.12
          ├── h2 v0.4.19 (*)
          └── libdd-shared-runtime v6.0.0 (*)

advisories FAILED, bans ok, sources ok

Updated: 2026-10-27 15:00:09 UTC | Commit: 87f6ded | dependency-check job results

@pr-commenter

pr-commenter Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Benchmarks

Comparison

Benchmark execution time: 2026-10-27 14:48:19

Comparing candidate commit 3473333 in PR branch arjun.guha/shm-table-generic with baseline commit 9c3464f in branch feat/arjun.guha/dsm-php-integration.

📊 Benchmarking dashboard

Found 0 performance improvements and 0 performance regressions! Performance is the same for 2 metrics, 0 unstable metrics.

Explanation

This is an A/B test comparing a candidate commit's performance against that of a baseline commit. Performance changes are noted in the tables below as:

  • 🟩 = significantly better candidate vs. baseline
  • 🟥 = significantly worse candidate vs. baseline

We compute a confidence interval (CI) over the relative difference of means between metrics from the candidate and baseline commits, considering the baseline as the reference.

If the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD), the change is considered significant.

Feel free to reach out to #apm-benchmarking-platform on Slack if you have any questions.

More details about the CI and significant changes

You can imagine this CI as a range of values that is likely to contain the true difference of means between the candidate and baseline commits.

CIs of the difference of means are often centered around 0%, because often changes are not that big:

---------------------------------(------|---^--------)-------------------------------->
                              -0.6%    0%  0.3%     +1.2%
                                 |          |        |
         lower bound of the CI --'          |        |
sample mean (center of the CI) -------------'        |
         upper bound of the CI ----------------------'

As described above, a change is considered significant if the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD).

For instance, for an execution time metric, this confidence interval indicates a significantly worse performance:

----------------------------------------|---------|---(---------^---------)---------->
                                       0%        1%  1.3%      2.2%      3.1%
                                                  |   |         |         |
       significant impact threshold --------------'   |         |         |
                      lower bound of CI --------------'         |         |
       sample mean (center of the CI) --------------------------'         |
                      upper bound of CI ----------------------------------'

Candidate

Candidate benchmark details

Group 1

cpu_model git_commit_sha git_commit_date git_branch
Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz 3473333 1793111888 arjun.guha/shm-table-generic
scenario metric min mean ± sd median ± mad p75 p95 p99 max peak_to_median_ratio skewness kurtosis cv sem runs sample_size
two way interface execution_time 13.493µs 13.915µs ± 0.191µs 13.908µs ± 0.090µs 13.992µs 14.263µs 14.581µs 14.672µs 5.49% 0.966 2.083 1.37% 0.013µs 1 200
scenario metric 95% CI mean Shapiro-Wilk pvalue Ljung-Box pvalue (lag=1) Dip test pvalue
two way interface execution_time [13.889µs; 13.942µs] or [-0.190%; +0.190%] None None None

Group 2

cpu_model git_commit_sha git_commit_date git_branch
Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz 3473333 1793111888 arjun.guha/shm-table-generic
scenario metric min mean ± sd median ± mad p75 p95 p99 max peak_to_median_ratio skewness kurtosis cv sem runs sample_size
write only interface execution_time 474.428ns 510.599ns ± 8.100ns 514.159ns ± 0.887ns 514.728ns 515.269ns 516.074ns 517.864ns 0.72% -2.815 8.194 1.58% 0.573ns 1 200
scenario metric 95% CI mean Shapiro-Wilk pvalue Ljung-Box pvalue (lag=1) Dip test pvalue
write only interface execution_time [509.477ns; 511.722ns] or [-0.220%; +0.220%] None None None

Baseline

Baseline benchmark details

Group 1

cpu_model git_commit_sha git_commit_date git_branch
Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz 9c3464f 1792756400 feat/arjun.guha/dsm-php-integration
scenario metric min mean ± sd median ± mad p75 p95 p99 max peak_to_median_ratio skewness kurtosis cv sem runs sample_size
two way interface execution_time 13.281µs 13.611µs ± 0.218µs 13.571µs ± 0.080µs 13.671µs 14.042µs 14.438µs 15.012µs 10.62% 2.449 10.170 1.60% 0.015µs 1 200
scenario metric 95% CI mean Shapiro-Wilk pvalue Ljung-Box pvalue (lag=1) Dip test pvalue
two way interface execution_time [13.581µs; 13.642µs] or [-0.222%; +0.222%] None None None

Group 2

cpu_model git_commit_sha git_commit_date git_branch
Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz 9c3464f 1792756400 feat/arjun.guha/dsm-php-integration
scenario metric min mean ± sd median ± mad p75 p95 p99 max peak_to_median_ratio skewness kurtosis cv sem runs sample_size
write only interface execution_time 476.768ns 508.109ns ± 13.624ns 514.846ns ± 2.361ns 516.459ns 517.722ns 523.802ns 527.833ns 2.52% -1.369 0.444 2.67% 0.963ns 1 200
scenario metric 95% CI mean Shapiro-Wilk pvalue Ljung-Box pvalue (lag=1) Dip test pvalue
write only interface execution_time [506.221ns; 509.997ns] or [-0.372%; +0.372%] None None None

@datadog-datadog-prod-us1-2

datadog-datadog-prod-us1-2 Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Pipelines  Tests

🎉 All green!

🧪 All tests passed
❄️ No new flaky tests detected

🎯 Code Coverage (details)
• Patch Coverage: 92.74%
• Overall Coverage: 79.14% (-3.27%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 3473333 | Docs | View more details | Give us feedback!

@arjunguhaswe

Copy link
Copy Markdown
Author

Linking DSM2-464

Comment thread libdd-ipc/src/shm_stats.rs Outdated
…s module

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
arjunguhaswe added a commit to DataDog/dd-trace-php that referenced this pull request Oct 14, 2026
Picks up the review fix to the shared memory table refactor
(DataDog/libdatadog#2673); no functional change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The sidecar only logs a warning when a bucket passes this ratio. It never
recreates or grows the table, which the old comment implied.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@arjunguhaswe
arjunguhaswe marked this pull request as ready for review October 16, 2026 04:15
@arjunguhaswe
arjunguhaswe requested a review from a team as a code owner October 16, 2026 04:15
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-16T04:19:08.744773Z d7e2241 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@arjunguhaswe

arjunguhaswe commented Oct 16, 2026 •

Copy link
Copy Markdown
Author

@bwoebi This looks good from my POV. Can't make a determination on "code style" since I don't have experience in rust, but the division of responsibility between [PHP callers] <----> [Product Specific Schema Definition] <-----> [Shared Table Code] is quite reasonable.

Marking "Ready for Review" but feel free to raise any concerns on structure/approach.

(I'll also find some time to cleanup the PR desc and add test links before merging and ask for re-review, but the code is good to look at as-is).

@arjunguhaswe
arjunguhaswe changed the base branch from main to feat/arjun.guha/dsm-php-integration October 19, 2026 14:54

@yannham yannham left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No hard blocker, but some safety improvement suggestions.

Comment thread libdd-ipc/src/shm_table.rs Outdated
Comment thread libdd-ipc/src/shm_table.rs Outdated
Comment thread libdd-ipc/src/shm_table.rs Outdated
/// byte expanding to a three-byte replacement character.
pub(crate) fn pool_string(pool: &Pool, sr: StringRef, budget: &mut usize) -> String {
let Some(bytes) = pool_slice(pool, sr) else {
return String::new();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning an empty string on error is a bit fishy. Should this option return an Option<String> instead? They both have the same layout and same size thanks to the niche optimization.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see no downside to this approach, but it looks like None can only be returned in situations where the writer thread exceeds the budget of the string pool -- which given it's sizing in respect to the slot limit can't happen unless there's some form of corruption.

Will make the change anyway since it appears to be more idiomatic.

Some(bytes) if bytes.len() == expected.len() => {
// Deliberately use non-atomic byte equality for this fixed, checked range.
// A peer may change the match result; no byte is interpreted as metadata or UTF-8.
let bytes =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is technically UB to materialize a slice to memory that can be mutated by someone else. The naive method would be to compare byte per byte atomically but that is costly, or to use extern "C" memcmp to perform the comparison.

Note sure this is worth doing here. But I would still write an explicit safety disclaimer (we know this is technically UB, but there's no easy way to go around it while maintaining performance in Rust).

@bwoebi bwoebi Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's also UB to perform a libc::memcmp on something which gets concurrently modified. (just not Rusts UB.)
The SHM protocol expects that, once placed there, there's no writing access until a minute has passed and the sections are swapped and cleared.

@yannham yannham Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I had the impression that recent C++ had an atomic memcpy version of some sort, and that Rust was lagging behind. But apparently not 🤔 so I guess it would either be atomic byte-per-byte copy or unportable assembly. Anyway I think you add useful information @bwoebi , I leave this comment unresolved because some of it might be useful to include in the code comment.

Comment thread libdd-ipc/src/shm_table.rs Outdated
if old.as_slice().len() >= size_of::<ShmHeader>() {
// SAFETY: page-aligned, in bounds, and all fields are integer atomics.
// Touch only `ready`; the old segment's layout is untrusted.
let hdr = unsafe { &*(old.as_slice().as_ptr() as *const ShmHeader) };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still relies on ready being at the same place in the new and old layout, but ready could theoretically move in future versions, right? I would suggest to add a little compile-time test to ensure the offset of ready is fixed. Just slap const { assert!(std::mem::offset_of!(ShmHeader, ready) == 4) } somewhere in this module file, for example.

Also, instead of materializing &ShmHeader, which has some additional safety requirements, I think it would be safer to just use raw pointers all the way down, something like:

    const READY_OFFSET: usize = std::mem::offset_of!(ShmHeader, ready);

     // SAFETY: we always access the header through atomics, so there's no interleaved atomic and non-atomic accesses.
     // The location is allocated and valid for read and writes.
     AtomicU8::from_ptr(old.as_slice().as_ptr().cast_mut().cast::<u8>().add(READY_OFFSET))
     .store(0, Relaxed);

arjunguhaswe and others added 5 commits October 21, 2026 09:31
`Schema` was an unsafe trait whose safety contract was that `Key` and
`Value` are made only of integer atomics. Encode that at the type level
instead, with the `ShmSafe` trait from the rate limiter: `Key` and
`Value` must now be `ShmSafe`, and `Schema` itself becomes a safe trait.

Add `ShmSafe` impls for `AtomicU8`, `AtomicU32` and arrays of `ShmSafe`
types, which the span stats key and value are built from.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
rustfmt wraps the import that adding ShmSafe made too long.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…m-table-generic

Keeps this branch's shm_stats.rs, whose module doc no longer has the
Untrusted contents section that #2694 edited on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ShmSpanConcentrator is now an alias of the shared table type, which lives
in the private shm_table module, so rustdoc treats links to its methods,
to ShmKeyHeader::scalars and to crate::shm_table as private and the new
doc lint job (#2695) fails on them. Name them in code spans instead, as
#2694 did for Layout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
arjunguhaswe added a commit that referenced this pull request Oct 24, 2026
…s table key

Merging arjun.guha/dsm-shm-table brought two changes the backlog code
predates:

- Schema is no longer an unsafe trait: its Key and Value must be ShmSafe
  instead (#2673). ShmBacklogKey and ShmBacklogValue now implement ShmSafe,
  and DataStreamsBacklogs implements Schema without unsafe, as DataStreams
  does.
- DsmTableKey has no product any more (#2674), so add_dsm_backlog builds
  its key without one.

Also name ShmDsmBacklogTable::add_backlog in code spans. It aliases the
shared table type of the private shm_table module, so links to its methods
fail the doc lint, as #2674 found for ShmDsmTable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
arjunguhaswe added a commit to DataDog/dd-trace-php that referenced this pull request Oct 26, 2026
…ration (#4314)

* feat(dsm): add Data Streams Monitoring checkpoints to the Kafka integration

Set a DSM checkpoint when rdkafka produces or consumes a message,
propagate the pathway context in the `dd-pathway-ctx-base64` header,
and record each checkpoint's stats point in the sidecar's shared-memory
DSM table (falling back to IPC until it exists).

- `DD_DATA_STREAMS_ENABLED` (default off) turns checkpoints and header
  injection on. Stats points are recorded once the agent's /info lists
  `/v0.1/pipeline_stats`.
- components-rs/data_streams.rs: pathway hashing as in the Go tracer and
  as system-tests expects (FNV-1 64 over service, env, sorted edge tags,
  process tags and container tags hash, then over the parent hash), the
  Java/Go wire format, the request's current pathway, and recording.
  A consume takes its parent from the message; a produce from the
  request's pathway, with consecutive produces sharing one parent.
- `DDTrace\Internal\dsm_set_checkpoint()` for integrations to call.
- Kafka: checkpoints on `ProducerTopic::producev` (direction, topic,
  type) and on `KafkaConsumer::consume` / `Queue::consume` (direction,
  group, topic, type). `produce` is rerouted through `producev` when
  DSM is on, even with distributed tracing off.
- Bump libdatadog to the DSM shared-memory table branch, and regenerate
  the C headers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: bump libdatadog to the DSM branch head

Picks up the review fix to the shared memory table refactor
(DataDog/libdatadog#2673); no functional change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: bump libdatadog to the DSM branch head

Picks up a test fix in DataDog/libdatadog#2674; no functional change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: bump libdatadog to the DSM branch head

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: bump libdatadog to the DSM branch head

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(kafka): use the PHPUnit-version-aware regex assertion in the DSM test

`assertMatchesRegularExpression` only exists from PHPUnit 9, so the
Data Streams test errored on the PHP 7.0-7.2 jobs. Use BaseTestCase's
`assertRegularExpression`, which falls back to `assertRegExp`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(dsm): group stats points by pathway hash

Bump libdatadog to e9439c4b2, which drops the separate aggregation
hash from DSM stats points: the pathway hash already folds in the
parent's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: bump libdatadog to the DSM branch head

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(dsm): hash the process tags the sidecar reports, svc.* tag included

The sidecar reports the process tags in the DSM payload followed by a
service-source tag - svc.user:true when DD_SERVICE is set, svc.auto with
the normalized default service name otherwise - while the pathway hash
only covered the process tags. Append the same tag when hashing, under
the same conditions: neither is sent while process tags are disabled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Revert "fix(dsm): hash the process tags the sidecar reports, svc.* tag included"

This reverts commit e9d87ed. The svc.*
tag in the pathway hash moves to a stacked branch for now: under
Kubernetes SSI the injector sets DD_SERVICE, so svc.user:true does not
reliably mean the user chose the service.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(dsm): hash the DBM base hash instead of the process tags

The process tags and the container tags hash that went into the node
hash are already hashed together for DBM by
datadog_process_tags_get_base_hash. Feed that base hash into the node
hash in their place, and drop the container tags hash getter that only
DSM used. With process tags off the base hash is empty, so those hashes
do not change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(dsm): drop the product from the DSM table key

Bump libdatadog to the DSM branch head, whose DsmTableKey no longer has a
product field.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(dsm): start the sidecar when DSM is enabled, and require it

The sidecar owns the shared memory tables DSM writes its stats points to,
and sends them, so DD_DATA_STREAMS_ENABLED now starts it, as the flagging
provider and OTel metrics do. Checkpoints still require it
(ddtrace_dsm_enabled), for when its setup fails or DSM is enabled for a
single request only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: bump libdatadog to the DSM branch head

The DSM branch now includes libdatadog main up to #2717, so mirror its new
workspace dependencies (httpdate, webpki-roots, webpki-root-certs) and its
rustls-webpki 0.103.16 floor in the workspace manifest, and lock
rustls-webpki 0.103.16, webpki-roots and webpki-root-certs 1.0.11 and the
new libdd-trace-model crate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(dsm): call DSM stats points checkpoints

Follow libdatadog's rename of the DSM stats table to the checkpoint
table (DataDog/libdatadog#2674): pin libdatadog to it, use its new
names, and call what a checkpoint records a checkpoint rather than a
stats point.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(dsm): refresh agent info before each checkpoint

AGENT_SUPPORTS_DSM was only updated as a side effect of RINIT, root-span
sampling or stats computation, so long-running workers whose agent /info
arrived after RINIT could skip every DSM checkpoint. Re-read the agent info
(a cheap no-op when unchanged) at the start of ddtrace_dsm_set_checkpoint so
DSM support, env and the container tags hash are current.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(dsm): remove unused DDTRACE_DSM_CONTEXT_KEY macro

Nothing references it; the Kafka integration uses its own
DSM_CONTEXT_HEADER constant for the pathway context header.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(kafka): give DSM produce and consume checkpoints matching methods

Move the produce checkpoint out of setupKafkaProduceSpan into
setDataStreamsProduceCheckpoint, which returns the pathway context, and
move the DD_DATA_STREAMS_ENABLED and RD_KAFKA_RESP_ERR_NO_ERROR checks
from the consume hook closure into setDataStreamsConsumeCheckpoint, so
each method owns when its checkpoint is set. No behaviour change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: bump libdatadog to shrink the DSM checkpoint table

Pick up DataDog/libdatadog@fc0ebc3ba: 128 checkpoint slots per bucket and
1 KiB of edge tag text per checkpoint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: bump libdatadog to #2674's head

Pick up DataDog/libdatadog@39a0c80af, #2674 with #2709 merged in, so the
whole stack pins the same libdatadog commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(dsm): report Kafka offsets and payload sizes to Data Streams Monitoring (#4352)

* feat(dsm): report Kafka offsets and payload sizes to Data Streams Monitoring

Completes the rdkafka DSM integration with the throughput and lag data
(DSM2-450):

- Checkpoints now carry the message size (value, key and headers), which
  fills the payload size sketch the sidecar already keeps.
- Produce offsets are recorded from the delivery report callback, and
  committed offsets (automatic or not) from the offset commit callback,
  as DSM backlogs. The integration wraps the application's callbacks,
  and installs its own on Producer and KafkaConsumer where there are
  none.
- New internal function DDTrace\Internal\dsm_track_backlog(), backed by
  ddog_dsm_track_backlog(), which goes into the sidecar's new backlog
  table (libdatadog) through shared memory, or IPC until that is open.

Bumps libdatadog to the backlog table commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(dsm): only track backlogs when the sidecar is set up for DSM

#4314 gates checkpoints on ddtrace_dsm_enabled(), which also requires the
sidecar that aggregates and sends DSM data. Gate backlogs the same way, so
an offset is never handed to a sidecar that is not there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(dsm): add Data Streams Monitoring checkpoints to the AMQP integration (#4347)

* feat(dsm): add Data Streams Monitoring checkpoints to the AMQP integration

Set a DSM checkpoint when php-amqplib publishes or consumes a message
(DSM2-449), with the Java tracer's RabbitMQ edge tags.

- basic_publish: direction:out with topic:<routing key> for the default
  exchange, and exchange:<name> plus has_routing_key otherwise. The
  context goes in the dd-pathway-ctx-base64 header, set on a copy of
  the application's header table so its entries keep their AMQP types.
  It is sent whether or not distributed tracing is on.
- basic_consume: a delivery names its consumer but not the queue, and
  basic_deliver reads the consumer only right before calling back. So
  the callback is wrapped: it knows its queue, and sets the
  direction:in, topic:<queue> checkpoint before the application's code
  runs. Each message runs on its own pathway, so what the callback
  publishes links to that message and nothing after it does.
- basic_get: the checkpoint is set when the message is returned and
  stays the request's pathway, as for a Kafka consume.
- Consumes are checkpointed with or without an incoming context.
- New DDTrace\Internal\dsm_push_pathway() / dsm_pop_pathway() set the
  request's pathway aside and bring it back, for integrations that hand
  messages to callbacks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(amqp): don't call str_starts_with() in formatQueueName (PHP 7)

str_starts_with() only exists from PHP 8.0. On PHP 7.x every AMQP hook that
formats a queue name (basic_get, queue_declare, basic_consume, ...) threw
"Call to undefined function", which the tracer swallows: those spans lost
their tags, and basic_get's DSM consume checkpoint never ran, so a publish
after basic_get started a new pathway instead of continuing the consumed one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(dsm): report RabbitMQ payload sizes to Data Streams Monitoring

Pass the message size with each AMQP checkpoint, which fills the
payload size sketch the sidecar keeps for throughput (count is
messages, sum is bytes), as the Kafka integration does since the
previous commit.

The size is the body plus the application headers, counted as the
Node.js tracer counts them (the Java tracer reports none for RabbitMQ):
header names and string values, nested tables and arrays included;
numbers, booleans, timestamps and decimals count as 0. A publish is
sized without the dd-pathway-ctx-base64 header the checkpoint computes,
and a consume with it, as for Kafka.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(dsm): size RabbitMQ headers by their AMQP encoding

Count each header entry's encoded bytes (name, type and value, with
nested tables and arrays) with php-amqplib's own AMQPWriter, instead of
only header names and string values. Numbers, booleans, timestamps and
decimals now count at their wire width. The table's 4-byte length is
left out, as is the context header on publish.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(dsm): note that pushed pathways aren't fiber safe

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* test(dsm): use the PHPUnit-compatible string assertion in KafkaTest

assertStringContainsString() is missing from PHPUnit 6, which PHP 7.0
runs, so testDataStreamsContextPropagation errored there. Use
BaseTestCase::assertStringContains(), which falls back to assertContains().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
pub(crate) const SLOT_INIT: u64 = u64::MAX;

/// The sidecar should recreate the SHM when slot utilisation exceeds this ratio.
pub const RELOAD_FILL_RATIO: f64 = 0.80;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could see some of these values being product specific instead of base table properties.

Comment thread libdd-ipc/src/shm_table.rs Outdated

@bwoebi bwoebi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me, thanks!

arjunguhaswe and others added 2 commits October 27, 2026 10:15
A string reference that does not resolve, or does not fit the flush
budget, now yields None instead of an empty string, so it can no longer
be mistaken for a field that is really empty. The span stats schema drops
the entry, as it already does for a key whose scalars fail to decode,
rather than exporting the group under a blank field.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pool_eq and pool_string read pool bytes through a plain slice while a
peer may be writing them. Say that this is technically undefined
behaviour, why the protocol makes it safe in practice, and why there is
no cheap sound alternative.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…through it

Retiring a replaced segment stored to `ready` through a `&ShmHeader`
built from a shared slice over the old mapping. That relied on `ready`
being where this version's layout puts it, and wrote through a pointer
derived from a `&[u8]`, which may only read.

Assert at compile time that `version` and `ready` keep their offsets,
and store through an `AtomicU8` at that offset, using the mapping's own
pointer so no reference over the shared bytes is made.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@arjunguhaswe
arjunguhaswe merged commit 1dd3a27 into feat/arjun.guha/dsm-php-integration Oct 27, 2026
66 checks passed
@arjunguhaswe
arjunguhaswe deleted the arjun.guha/shm-table-generic branch October 27, 2026 15:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants