Skip to content

MONITOR TRACE: key-level access tracing with sampling#3655

Open
xdk-amz wants to merge 1 commit into
valkey-io:unstablefrom
xdk-amz:monitor-trace
Open

MONITOR TRACE: key-level access tracing with sampling#3655
xdk-amz wants to merge 1 commit into
valkey-io:unstablefrom
xdk-amz:monitor-trace

Conversation

@xdk-amz

@xdk-amz xdk-amz commented May 11, 2026

Copy link
Copy Markdown

MONITOR TRACE: Key-Level Workload Tracing for Valkey

Summary

Adds MONITOR TRACE — a new subcommand that streams key-level access events (read, write, delete) to subscribed clients in real-time. Unlike MONITOR which captures full command text, MONITOR TRACE emits structured per-key records with metadata (timestamps, sizes, access type, object encoding), enabling offline simulation of cache eviction policies and workload characterization without replaying full command streams.

Motivation

Cache tiering and eviction policy design requires understanding real workload access patterns — which keys are hot, how large they are, and what the read/write/delete ratio looks like. Existing tools (MONITOR, SLOWLOG, INFO) don't provide this. MONITOR TRACE fills the gap by producing a compact, structured trace suitable for feeding into simulators (e.g., MRC curve generators).

Feature Design

Command Syntax

MONITOR TRACE [SAMPLES <n>] [RATE <0.0-1.0>] [SEED <n>] [FORMAT CSV|RESP] [NEGATIVE-LOOKUPS YES|NO]

Example

127.0.0.1:6379> monitor trace format csv
OK
1778474345309397,76,0,set,"key",WRITE,1,string,32,160

Trace Record Fields (CSV format)

Field Description
ts_us Microsecond timestamp
seq Monotonic sequence number
db Database index
cmd Command name
key Key (RFC 4180 quoted, \xHH for non-printable bytes)
access_type READ, WRITE, or DELETE
key_exists Whether key existed at access time
obj_type Object type (string, list, hash, etc.)
key_bytes Key entry allocation size
value_bytes Value allocation size (excludes embedded values)

Implementation Hooks

  • READ: lookupKey() — emits on non-write lookups
  • WRITE: signalModifiedKey() — universal mutation point, catches in-place mutations (HSET, LPUSH, etc.)
  • DELETE: dbGenericDeleteWithDictIndex() — all deletion paths

Key-Hash Sampling

When RATE < 1.0, uses SipHash on the key to deterministically select which keys to trace. This ensures all accesses to the same key are either traced or skipped (no aliasing with request patterns).

Architecture

call()          → workloadTraceBeginCommand/EndCommand (sets trace context)
lookupKey()     → workloadTraceEmitRead
setKey/dbAdd()  → workloadTraceEmitWrite
dbDelete()      → workloadTraceEmitDelete

The trace context is thread-local (safe: Valkey is single-threaded for commands). A fast-path check (workloadTraceActive()) short-circuits when no tracers are subscribed — zero overhead in the common case.

Benchmark Results

Methodology

  • Instance: r7g.4xlarge EC2 (single Valkey server, single-threaded, loopback)
  • Tool: memtier_benchmark with --rate-limiting for controlled load
  • Pipeline: P1 (no pipelining) — one command per round-trip, realistic for most clients
  • Core pinning: Server on core 0, benchmark on cores 2-9, trace client on core 8
  • Clients: 50 parallel clients, 4 threads
  • Pre-population: 1M keys before each test
  • Duration: 30 seconds per phase
  • Payload: 256 bytes
  • Workload: 1:1 SET:GET ratio

Finding Saturation Point

With P1 (no pipelining) on loopback, the server saturates at ~175K ops/sec regardless of client count (even 5 clients saturate a single core at sub-millisecond RTT). Used memtier_benchmark --rate-limiting to cap at exactly 50% for the half-load phases.

Results (averaged over 3 runs)

Saturated (server CPU-bound):

Metric Baseline + MONITOR TRACE + MONITOR
Total Ops/sec 172,595 158,564 152,191
TPS overhead -8.1% -11.8%
Avg latency 1.159 ms 1.262 ms 1.315 ms
p50 latency 1.151 ms 1.239 ms 1.289 ms
p99 latency 2.202 ms 2.217 ms 2.057 ms
p99.9 latency 2.463 ms 2.665 ms 2.713 ms

Half rate (~85K ops/sec, rate-limited):

Metric Baseline + MONITOR TRACE + MONITOR
Total Ops/sec 84,596 84,595 84,595
TPS overhead ~0% ~0%
Avg latency 1.101 ms 1.165 ms 1.182 ms
p50 latency 1.194 ms 1.243 ms 1.243 ms
p99 latency 2.239 ms 2.308 ms 2.383 ms
p99.9 latency 2.548 ms 2.702 ms 2.793 ms
Avg latency overhead +5.8% +7.3%
p99.9 overhead +6.0% +9.6%

Analysis

  1. MONITOR TRACE is ~30% lighter than MONITOR at saturation: At full CPU load, MONITOR TRACE costs 8.1% throughput vs MONITOR's 11.8%. Both format output per-command, but MONITOR must serialize the entire command line (including large values) while MONITOR TRACE emits only compact key-level metadata.

  2. At half load (50% CPU): Both achieve zero throughput impact (rate-limited). MONITOR TRACE adds +5.8% average latency vs MONITOR's +7.3%. The tail (p99.9) shows a clearer gap: +6.0% for TRACE vs +9.6% for MONITOR.

  3. Zero overhead when inactive: The workloadTraceActive() inline check short-circuits immediately when no trace clients are connected — no branch misprediction cost in the hot path.

  4. Pipelining amplification note: Earlier tests with P16 pipelining showed ~43% overhead, but I believe this is misleading for the average use case. Pipelining batches 16 commands per event loop iteration, amplifying the per-command trace cost 16x within a single batch. Pipelining workloads should be aware that this will incur significantly more overhead when tracing.

Files Changed

File Change
src/workload_trace.h New — trace API, config structs, inline fast-path check
src/workload_trace.c New — trace emission, context management, size computation
src/server.h Add workload_tracers list, client flag, config pointer
src/server.c Init tracers list, call begin/end in call(), command gating for trace clients
src/db.c Emit hooks in lookupKey, signalModifiedKey, dbDelete
src/networking.c Init workload_tracer_config, detach tracer on client free/reset
src/commands/monitor.json Convert MONITOR to container command
src/commands/monitor-trace.json New — TRACE subcommand definition with arguments
src/commands.def Regenerated — registers MONITOR TRACE subcommand
cmake/Modules/SourceFiles.cmake Add workload_trace.c
src/Makefile Add workload_trace.o
tests/unit/introspection.tcl 16 new integration tests

Testing

16 integration tests in tests/unit/introspection.tcl covering:

  • Event emission: READ, WRITE, DELETE events with correct fields
  • Output formats: RESP (binary-safe bulk strings) and CSV (RFC 4180 quoting)
  • Sampling: RATE 0 (suppresses all), RATE 1 (emits all), deterministic RATE+SEED (asserts exact sampled key set)
  • Filtering: NEGATIVE-LOOKUPS yes/no for cache miss visibility
  • Size reporting: Non-zero key/value bytes, SAMPLES effect on complex types
  • Lifecycle: Client disconnect removes tracer from list
  • Multi-DB correctness: Correct db field for non-default DB, MOVE emits DELETE on source + WRITE on destination, correct value sizes per DB
  • Size tests: key_bytes + value_bytes == MEMORY USAGE verified for embedded strings, RAW strings, listpack hashes, hashtable hashes, sets, zsets, streams, and with SAMPLES 0/1/5/50

@xdk-amz xdk-amz force-pushed the monitor-trace branch 2 times, most recently from 9ec9667 to 1cdc63d Compare May 11, 2026 05:10
@xdk-amz xdk-amz force-pushed the monitor-trace branch 4 times, most recently from aeb81ff to 033f0e3 Compare May 12, 2026 17:49
@coderabbitai

coderabbitai Bot commented May 12, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Implements MONITOR TRACE: adds TRACE subcommand and JSON schema, public tracer API and header, core tracer implementation with deterministic per-key sampling and RESP/CSV formatting, instruments DB read/write/delete hot paths, wires client/server lifecycle and command execution, updates build files, and adds extensive unit tests.

Changes

Workload Tracing Feature

Layer / File(s) Summary
Type contracts and public API
src/server.h, src/workload_trace.h
New ClientFlags.workload_tracer bit, client.workload_tracer_config pointer, and valkeyServer.workload_tracers list; workloadTracerConfig and workloadTraceContext structs, init/detach/context and emit prototypes, objectComputeSize prototype, and workloadTraceActive() inline predicate.
MONITOR command container with TRACE subcommand
src/commands.def, src/commands/monitor.json, src/commands/monitor-trace.json
MONITOR redefined as a subcommand container; new TRACE subcommand and JSON metadata accept samples, negative-lookups (yes/no), format (resp/csv), rate (double), and seed (integer).
Workload tracing core implementation
src/workload_trace.c
Core tracer: subscriber list and lifecycle, per-command context save/begin/end, object sizing (with SAMPLES traversal), RESP/CSV formatting and binary-safe escaping, deterministic SipHash-based key sampling, event dispatcher and emit wrappers, and monitorTraceSetup() option parsing.
Build system integration
cmake/Modules/SourceFiles.cmake, src/Makefile
CMake and Makefile updated to compile and link src/workload_trace.c into server targets (workload_trace.o).
Key-space operation instrumentation
src/db.c
Includes tracer header and emits READ events from non-write lookupKey, WRITE events from signalModifiedKey after value lookup, and DELETE events from dbGenericDeleteWithDictIndex before deallocation (excluding expired keys).
Client lifecycle and command execution wrapper
src/networking.c, src/server.c
Client initialization sets tracer pointer to NULL; clear/free detach tracer; server init calls tracer init; call() wraps command execution with trace context save/begin/end; tracer-mode clients limited to PING/QUIT/RESET; monitorCommand handles MONITOR TRACE setup and replies.
Comprehensive test coverage
tests/unit/introspection.tcl
Extensive tests for RESP/CSV output, binary escaping, RATE semantics and sampling determinism with SEED, NEGATIVE-LOOKUPS behavior, SAMPLES sizing, tracer cleanup, per-db attribution including MOVE, and size parity vs MEMORY USAGE across types and sample depths.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hop through keys at break of dawn,
Counting reads and writes until they're gone,
RESP or CSV, I sing the trace,
Sampling seeds set the steady pace,
A tiny rabbit watching your data run.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: addition of a new MONITOR TRACE subcommand for key-level access tracing with sampling support, matching the primary feature added in the changeset.
Description check ✅ Passed The description provides comprehensive context directly related to the changeset, covering feature design, implementation hooks, benchmarks, and testing for the MONITOR TRACE subcommand feature.
Docstring Coverage ✅ Passed Docstring coverage is 82.14% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
src/server.c (1)

3931-3934: ⚡ Quick win

Avoid unconditional tracing context operations on hot path

At lines 3931-3934, workloadTraceSaveContext() and workloadTraceEndCommand() are called unconditionally even when no tracers are subscribed. While workloadTraceBeginCommand() already guards its expensive operations via an early workloadTraceActive() check, adding a guard at the call site would prevent unnecessary struct copy/assignment operations when tracing is inactive.

Suggested guard
-    workloadTraceContext prev_trace_ctx = workloadTraceSaveContext();
-    workloadTraceBeginCommand(c);
+    workloadTraceContext prev_trace_ctx;
+    bool trace_active = workloadTraceActive();
+    if (trace_active) {
+        prev_trace_ctx = workloadTraceSaveContext();
+        workloadTraceBeginCommand(c);
+    }
     c->cmd->proc(c);
-    workloadTraceEndCommand(&prev_trace_ctx);
+    if (trace_active) {
+        workloadTraceEndCommand(&prev_trace_ctx);
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server.c` around lines 3931 - 3934, The code currently calls
workloadTraceSaveContext() and workloadTraceEndCommand() unconditionally around
command execution; wrap these trace-context operations with a
workloadTraceActive() check so they only run when tracing is enabled: call
workloadTraceSaveContext(), workloadTraceBeginCommand(c), invoke
c->cmd->proc(c), then workloadTraceEndCommand(&prev_trace_ctx) only if
workloadTraceActive() returns true (keep workloadTraceBeginCommand's internal
guard intact), avoiding unnecessary struct copies when tracing is inactive.
src/server.h (1)

4153-4153: ⚡ Quick win

Add a short contract comment for objectComputeSize.

Please document why this API exists and its key assumptions (sampling semantics and DB context) near Line 4153.

Proposed change
+/* Computes estimated allocation size for MONITOR TRACE event reporting. */
 size_t objectComputeSize(robj *key, robj *o, size_t sample_size, int dbid);

As per coding guidelines, "Document why code exists, not just what it does; document all functions in C code".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server.h` at line 4153, Add a short contract comment above the
declaration of objectComputeSize that explains why the API exists and its key
assumptions: state that objectComputeSize computes an estimated memory footprint
of a Redis object using sampling (describe what sample_size means and that it
may inspect only part of the object for large containers), clarify that dbid
indicates the DB context used for auxiliary lookups/encoding semantics, and note
any preconditions (e.g., key and o must be non-NULL, caller responsibility for
synchronization) and the meaning of the return value (estimated bytes).
Reference the function signature objectComputeSize(robj *key, robj *o, size_t
sample_size, int dbid) in the comment so callers understand sampling semantics
and DB-context implications.
src/workload_trace.c (1)

21-23: ⚡ Quick win

Clarify "thread-local" comment - these are file-local statics, not TLS.

The comment "Thread-local trace context" is misleading. These are file-scoped static variables, not thread-local storage (which would use __thread or _Thread_local). The safety comes from Valkey's single-threaded command execution model, not from TLS.

📝 Proposed clarification
-/* Thread-local trace context (safe: valkey is single-threaded for commands) */
+/* Per-command trace context (file-scoped static, safe due to single-threaded command execution) */
 static workloadTraceContext trace_ctx = {0};
 static uint64_t trace_seq_counter = 0;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/workload_trace.c` around lines 21 - 23, The comment above the statics
trace_ctx and trace_seq_counter incorrectly calls them "Thread-local"; update
the comment to say these are file-scoped static variables (not TLS) and that
their safety relies on Valkey's single-threaded command execution model; if
actual thread-local storage is intended, switch to using __thread or
_Thread_local for trace_ctx/trace_seq_counter—otherwise simply rephrase the
comment to: "File-local trace context and sequence counter (safe because Valkey
runs commands single-threaded), not C thread-local storage."
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/workload_trace.h`:
- Around line 38-58: Add doc comments for every public API declaration in this
header: workloadTraceInit, workloadTraceDetachClient, monitorTraceSetup,
workloadTraceSaveContext, workloadTraceBeginCommand, workloadTraceEndCommand,
workloadTraceEmitRead, workloadTraceEmitWrite, workloadTraceEmitDelete, and
workloadTraceActive. For each function include a short purpose summary,
parameter descriptions (ownership/nullable), return value semantics, important
behavioral notes (thread-safety, when to call, lifecycle/ordering constraints
such as save/begin/end pairing), and any side effects; ensure monitorTraceSetup
documents arg_start meaning and workloadTraceSaveContext/workloadTraceEndCommand
document how the context must be used. Keep comments concise and follow the
project's doc style.

In `@tests/unit/introspection.tcl`:
- Around line 1161-1170: The test "MONITOR TRACE CSV format escapes
binary-unsafe keys" currently uses an ASCII-safe key `normalkey`; update the
test to use a binary-unsafe key (e.g., include quotes, commas, newlines or
non-printable bytes) when calling `r set` (the client returned by
`valkey_deferring_client`) so the CSV-escaping/hex-encoding behavior is
exercised; also adjust the `assert_match` for the read `line` from the `$rd
read` (after `$rd MONITOR TRACE FORMAT csv`) to check for the expected
escaped/quoted or hex-encoded representation of that binary key rather than
matching `"normalkey"`, ensuring the test still closes `$rd` at the end.

---

Nitpick comments:
In `@src/server.c`:
- Around line 3931-3934: The code currently calls workloadTraceSaveContext() and
workloadTraceEndCommand() unconditionally around command execution; wrap these
trace-context operations with a workloadTraceActive() check so they only run
when tracing is enabled: call workloadTraceSaveContext(),
workloadTraceBeginCommand(c), invoke c->cmd->proc(c), then
workloadTraceEndCommand(&prev_trace_ctx) only if workloadTraceActive() returns
true (keep workloadTraceBeginCommand's internal guard intact), avoiding
unnecessary struct copies when tracing is inactive.

In `@src/server.h`:
- Line 4153: Add a short contract comment above the declaration of
objectComputeSize that explains why the API exists and its key assumptions:
state that objectComputeSize computes an estimated memory footprint of a Redis
object using sampling (describe what sample_size means and that it may inspect
only part of the object for large containers), clarify that dbid indicates the
DB context used for auxiliary lookups/encoding semantics, and note any
preconditions (e.g., key and o must be non-NULL, caller responsibility for
synchronization) and the meaning of the return value (estimated bytes).
Reference the function signature objectComputeSize(robj *key, robj *o, size_t
sample_size, int dbid) in the comment so callers understand sampling semantics
and DB-context implications.

In `@src/workload_trace.c`:
- Around line 21-23: The comment above the statics trace_ctx and
trace_seq_counter incorrectly calls them "Thread-local"; update the comment to
say these are file-scoped static variables (not TLS) and that their safety
relies on Valkey's single-threaded command execution model; if actual
thread-local storage is intended, switch to using __thread or _Thread_local for
trace_ctx/trace_seq_counter—otherwise simply rephrase the comment to:
"File-local trace context and sequence counter (safe because Valkey runs
commands single-threaded), not C thread-local storage."
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4048686d-b2b4-4667-af4b-ae2ff4488d8a

📥 Commits

Reviewing files that changed from the base of the PR and between d4337d6 and 033f0e3.

📒 Files selected for processing (12)
  • cmake/Modules/SourceFiles.cmake
  • src/Makefile
  • src/commands.def
  • src/commands/monitor-trace.json
  • src/commands/monitor.json
  • src/db.c
  • src/networking.c
  • src/server.c
  • src/server.h
  • src/workload_trace.c
  • src/workload_trace.h
  • tests/unit/introspection.tcl

Comment thread src/workload_trace.h
Comment thread tests/unit/introspection.tcl

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/workload_trace.c (1)

106-106: 💤 Low value

Consider breaking long function signatures for readability.

This signature exceeds 90 characters. While functional, breaking it across multiple lines would improve readability.

♻️ Suggested formatting
-static void emitEventResp(client *tracer, long long ts_us, uint64_t seq, int db_id, const char *cmd, robj *key, int access_type, int key_exists, const char *obj_type, size_t key_bytes, size_t value_bytes) {
+static void emitEventResp(client *tracer, long long ts_us, uint64_t seq, int db_id,
+                          const char *cmd, robj *key, int access_type, int key_exists,
+                          const char *obj_type, size_t key_bytes, size_t value_bytes) {

As per coding guidelines: "Keep line length below 90 characters when reasonable in C code".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/workload_trace.c` at line 106, The function signature for emitEventResp
is too long; reformat its declaration and definition by breaking the parameter
list across multiple lines for readability — put the return type and function
name on one line and place each logical group of parameters (e.g.,
tracer/timestamp/seq, db_id/cmd/key/access_type,
key_exists/obj_type/key_bytes/value_bytes) on their own indented lines so the
line length stays under ~90 chars; update both the prototype and any
corresponding definition/usage of emitEventResp to match the new multi-line
signature.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/workload_trace.c`:
- Line 106: The function signature for emitEventResp is too long; reformat its
declaration and definition by breaking the parameter list across multiple lines
for readability — put the return type and function name on one line and place
each logical group of parameters (e.g., tracer/timestamp/seq,
db_id/cmd/key/access_type, key_exists/obj_type/key_bytes/value_bytes) on their
own indented lines so the line length stays under ~90 chars; update both the
prototype and any corresponding definition/usage of emitEventResp to match the
new multi-line signature.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: daaa1085-ea6c-4b95-aa35-d654ec157076

📥 Commits

Reviewing files that changed from the base of the PR and between 033f0e3 and c2eadde.

📒 Files selected for processing (12)
  • cmake/Modules/SourceFiles.cmake
  • src/Makefile
  • src/commands.def
  • src/commands/monitor-trace.json
  • src/commands/monitor.json
  • src/db.c
  • src/networking.c
  • src/server.c
  • src/server.h
  • src/workload_trace.c
  • src/workload_trace.h
  • tests/unit/introspection.tcl
🚧 Files skipped from review as they are similar to previous changes (11)
  • src/commands/monitor.json
  • src/Makefile
  • cmake/Modules/SourceFiles.cmake
  • src/workload_trace.h
  • src/commands/monitor-trace.json
  • src/server.h
  • src/commands.def
  • src/server.c
  • src/db.c
  • src/networking.c
  • tests/unit/introspection.tcl

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/workload_trace.c`:
- Around line 84-87: The subtraction total - zmalloc_size(val) can underflow if
objectComputeSize(...) returns less than zmalloc_size(val); in the block where
samples >= 0 (using sample_size and total from objectComputeSize), clamp the
result to avoid wrapping by returning 0 when total <= zmalloc_size(val),
otherwise return total - zmalloc_size(val); update the code around
objectComputeSize, sample_size, and zmalloc_size(val) to perform this check
before returning.
- Around line 288-322: The seed bytes are derived via memcpy from the native
uint64_t which yields different SipHash keys on big- vs little-endian hosts;
normalize the seed to a uint64_t (e.g. uint64_t seed = (uint64_t)sample_seed)
and populate cfg->sample_seed_bytes in a fixed byte order (explicitly emit the 8
bytes with shifts/masks into sample_seed_bytes[0..7], then duplicate them into
[8..15]) instead of memcpy; this preserves the deterministic RATE/SEED sampling
and handles the -1 wrap consistently; locate this change around
workloadTracerConfig initialization where sample_seed and cfg->sample_seed_bytes
are set (workloadTraceDetachClient and workloadTracerConfig usage).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9e07f303-bb78-4313-9893-f6132c859b12

📥 Commits

Reviewing files that changed from the base of the PR and between c2eadde and 8df3f95.

📒 Files selected for processing (12)
  • cmake/Modules/SourceFiles.cmake
  • src/Makefile
  • src/commands.def
  • src/commands/monitor-trace.json
  • src/commands/monitor.json
  • src/db.c
  • src/networking.c
  • src/server.c
  • src/server.h
  • src/workload_trace.c
  • src/workload_trace.h
  • tests/unit/introspection.tcl
✅ Files skipped from review due to trivial changes (3)
  • src/commands/monitor-trace.json
  • src/workload_trace.h
  • src/commands.def
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/Makefile
  • cmake/Modules/SourceFiles.cmake
  • src/networking.c
  • src/db.c
  • src/server.h
  • tests/unit/introspection.tcl
  • src/server.c

Comment thread src/workload_trace.c Outdated
Comment thread src/workload_trace.c Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/workload_trace.c (2)

84-87: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clamp the sampled-size subtraction.

If objectComputeSize(...) ever returns less than the top-level robj allocation, Line 87 wraps size_t and TRACE emits a huge value_bytes.

Suggested fix
     if (samples >= 0) {
         size_t sample_size = (samples == 0) ? LLONG_MAX : (size_t)samples;
         size_t total = objectComputeSize(key, val, sample_size, dbid);
-        return total - zmalloc_size(val);
+        size_t robj_size = zmalloc_size(val);
+        return total > robj_size ? total - robj_size : 0;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/workload_trace.c` around lines 84 - 87, The subtraction of
zmalloc_size(val) from total can underflow because both are size_t; modify the
logic in the samples-handling branch (the block using objectComputeSize,
sample_size, total and zmalloc_size) to clamp the computed value_bytes to zero
when total is <= zmalloc_size(val). In other words, compute value_bytes as
(total > zmalloc_size(val)) ? total - zmalloc_size(val) : 0 before
returning/emitting, so you never return a wrapped huge size.

288-322: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Normalize SEED before deriving the SipHash key.

SEED -1 silently becomes UINT64_MAX, and the native-endian memcpy makes the same RATE/SEED pair sample different keys on little- vs big-endian hosts. That breaks the deterministic sampling contract.

Suggested fix
         } else if (!strcasecmp(objectGetVal(c->argv[i]), "seed") && i + 1 < c->argc) {
             long long s;
             if (getLongLongFromObjectOrReply(c, c->argv[i + 1], &s, NULL) != C_OK) return C_ERR;
-            sample_seed = (uint64_t)s;
+            if (s < 0) {
+                addReplyError(c, "SEED must be >= 0");
+                return C_ERR;
+            }
+            sample_seed = (uint64_t)s;
             i++;
         } else {
             addReplyErrorObject(c, shared.syntaxerr);
             return C_ERR;
         }
@@
-    memset(cfg->sample_seed_bytes, 0, 16);
-    memcpy(cfg->sample_seed_bytes, &sample_seed, 8);
-    memcpy(cfg->sample_seed_bytes + 8, &sample_seed, 8);
+    for (int j = 0; j < 8; j++) {
+        uint8_t byte = (sample_seed >> (j * 8)) & 0xff;
+        cfg->sample_seed_bytes[j] = byte;
+        cfg->sample_seed_bytes[j + 8] = byte;
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/workload_trace.c` around lines 288 - 322, The seed is copied via
native-endian memcpy causing different SipHash keys across endianness and signed
-1 wrapping; instead normalize the 64-bit sample_seed to a uint64_t (e.g.,
uint64_t useed = (uint64_t)sample_seed) and write its bytes into
cfg->sample_seed_bytes in a fixed big-endian order (populate bytes [0..7] using
shifts (use (useed >> 56) & 0xFF, etc.), then copy the same 8 bytes into
[8..15]) so workloadTracerConfig->sample_seed_bytes is deterministic regardless
of host endianness or negative input handling; update the code around
workloadTracerConfig initialization where sample_seed and sample_seed_bytes are
set and remove the native memcpy calls.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/server.c`:
- Around line 3931-3939: The code samples workloadTraceActive() twice and fails
to restore the saved TLS trace context if the command disables tracing; fix by
capturing the entry state in a boolean (e.g. bool was_active =
workloadTraceActive()), call workloadTraceSaveContext() and
workloadTraceBeginCommand(c) only if was_active, and then call
workloadTraceEndCommand(&prev_trace_ctx) only if was_active after
c->cmd->proc(c); use the existing symbols workloadTraceActive,
workloadTraceSaveContext, workloadTraceBeginCommand, workloadTraceEndCommand,
prev_trace_ctx and c->cmd->proc to locate and update the logic so the saved
context is restored based on the entry state.

---

Duplicate comments:
In `@src/workload_trace.c`:
- Around line 84-87: The subtraction of zmalloc_size(val) from total can
underflow because both are size_t; modify the logic in the samples-handling
branch (the block using objectComputeSize, sample_size, total and zmalloc_size)
to clamp the computed value_bytes to zero when total is <= zmalloc_size(val). In
other words, compute value_bytes as (total > zmalloc_size(val)) ? total -
zmalloc_size(val) : 0 before returning/emitting, so you never return a wrapped
huge size.
- Around line 288-322: The seed is copied via native-endian memcpy causing
different SipHash keys across endianness and signed -1 wrapping; instead
normalize the 64-bit sample_seed to a uint64_t (e.g., uint64_t useed =
(uint64_t)sample_seed) and write its bytes into cfg->sample_seed_bytes in a
fixed big-endian order (populate bytes [0..7] using shifts (use (useed >> 56) &
0xFF, etc.), then copy the same 8 bytes into [8..15]) so
workloadTracerConfig->sample_seed_bytes is deterministic regardless of host
endianness or negative input handling; update the code around
workloadTracerConfig initialization where sample_seed and sample_seed_bytes are
set and remove the native memcpy calls.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9d38fd12-024a-4279-ad92-7d037663dfec

📥 Commits

Reviewing files that changed from the base of the PR and between 8df3f95 and a0b6220.

📒 Files selected for processing (12)
  • cmake/Modules/SourceFiles.cmake
  • src/Makefile
  • src/commands.def
  • src/commands/monitor-trace.json
  • src/commands/monitor.json
  • src/db.c
  • src/networking.c
  • src/server.c
  • src/server.h
  • src/workload_trace.c
  • src/workload_trace.h
  • tests/unit/introspection.tcl
✅ Files skipped from review due to trivial changes (1)
  • cmake/Modules/SourceFiles.cmake
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/Makefile
  • src/commands/monitor.json
  • src/commands/monitor-trace.json
  • src/networking.c
  • src/db.c
  • src/server.h
  • tests/unit/introspection.tcl

Comment thread src/server.c

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/workload_trace.c`:
- Around line 45-55: The current workloadTraceEndCommand restores the entire
saved trace_ctx (trace_ctx = *prev) which reverts trace_ctx.seq to an older
value and breaks TRACE monotonicity; change workloadTraceEndCommand to restore
all fields from *prev except the seq (i.e., preserve the current trace_ctx.seq
or use the larger of prev->seq and current seq) so nested calls don't reset
sequence numbers—modify workloadTraceEndCommand to copy *prev but keep
trace_ctx.seq as the existing incremented value from
workloadTraceBeginCommand/trace_seq_counter.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 13e5e461-e911-41ac-9c6a-863c112d3016

📥 Commits

Reviewing files that changed from the base of the PR and between a0b6220 and f627992.

📒 Files selected for processing (12)
  • cmake/Modules/SourceFiles.cmake
  • src/Makefile
  • src/commands.def
  • src/commands/monitor-trace.json
  • src/commands/monitor.json
  • src/db.c
  • src/networking.c
  • src/server.c
  • src/server.h
  • src/workload_trace.c
  • src/workload_trace.h
  • tests/unit/introspection.tcl
✅ Files skipped from review due to trivial changes (1)
  • src/commands.def
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/server.h
  • src/commands/monitor.json
  • src/commands/monitor-trace.json
  • src/workload_trace.h
  • src/db.c
  • src/server.c
  • tests/unit/introspection.tcl

Comment thread src/workload_trace.c
@xdk-amz xdk-amz force-pushed the monitor-trace branch 5 times, most recently from ce0395c to fde5fb4 Compare May 22, 2026 22:05
@xdk-amz xdk-amz force-pushed the monitor-trace branch 2 times, most recently from e307989 to b8848e3 Compare June 2, 2026 17:44
…alysis

Add MONITOR TRACE subcommand that streams key access events (READ, WRITE,
DELETE) to subscribed clients for offline workload analysis

Features:
- Hooks in lookupKey/signalModifiedKey/dbDelete emit per-key events
- RESP and CSV output formats with object type and size metadata
- Deterministic key-hash sampling (SipHash + threshold) for long traces
- RATE 0.0-1.0 and SEED parameters for reproducible subset tracing
- Zero overhead when no tracer subscribed (inline list length check)
- ~8.1% overhead at 175K ops/sec with single tracer connected on r7g.4xlarge

Usage:
  MONITOR TRACE [SAMPLES n] [FORMAT resp|csv] [RATE r] [SEED s]
  MONITOR TRACE RATE 0.01 SEED 42   -- trace 1% of keys, reproducible
Signed-off-by: Dante Knowles <xdk@amazon.com>
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.

1 participant