@@ -31,6 +31,11 @@ The ABSL build benchmark (`BenchmarkBuildABSL` in
3131` execve ` and general syscall overhead, as Bazel orchestrates thousands of
3232short-lived compiler processes.
3333
34+ ** Tracepoint Selection Note** : The default ` null_bench_config.json ` and
35+ ` remote_bench_config.json ` files are tuned specifically for this
36+ ` BenchmarkBuildABSL ` workload and are hard-coded to trace only
37+ ` syscall/execve/enter ` .
38+
3439``` shell
3540$ make run-benchmark-seccheck \
3641 BENCHMARKS_TARGETS=" //test/benchmarks/fs:bazel_test" \
@@ -108,7 +113,7 @@ binaries repeatedly).
108113
109114- ** Baseline:** provides IPC and compilation performance inside the generic
110115 Sentry sandbox unburdened by syscall interception telemetry.
111- - ** Cache enabled:** the Sentry only incurs the heavy disk-read and SHA-256
116+ - ** Cache enabled:** the Sentry only incurs the heavy disk-read and SHA256
112117 calculation mathematically on the very first compilation hit. On subsequent
113118 invocations, the Sentry detects the identical ELF binary footprint and
114119 serves the hash instantly.
@@ -140,3 +145,182 @@ network variability can inject variance. While repeating loops (`-test.count=5`)
140145statistically filters out the largest massive spikes by discarding the extremes,
141146minor host-level CPU priority jitter can still bleed into the isolated true
142147medians.
148+
149+ ## Example: Profiling Seccheck Memory Overheads (Redis)
150+
151+ ` redis-benchmark ` can be used to answer concerns about the memory allocation
152+ (` allocs/op ` ) and garbage collection (GC) pressure that ` seccheck ` may inflict,
153+ Redis is heavily dependent on ` syscall ` throughput (specifically thousands of
154+ socket loops per second), making it an ideal candidate to flush out Sentry
155+ memory leaks or GC spikes.
156+
157+ ** Tracepoint Selection Note** : Unlike the ABSL benchmark which forks processes
158+ (` execve ` ), the Redis benchmark does endless I/O over the network and executes
159+ almost no ` execve ` calls. The ` redis_*_bench_config.json ` files are explicitly
160+ overwritten to capture ` syscall/read/enter ` and ` syscall/write/enter ` to ensure
161+ high-frequency trace generation during this test.
162+
163+ ### Pass 1: Baseline (Disabled)
164+
165+ This runs the Redis macro-benchmark under a standard ` runsc ` sandbox without any
166+ telemetry configurations initialized. We enable native profiling to collect a
167+ baseline heap profile and execution trace:
168+
169+ ``` shell
170+ make setup-seccheck \
171+ SECCHECK_BENCH_CONFIG=$( pwd) /test/benchmarks/seccheck/empty_bench_config.json \
172+ RUNTIME_ARGS=" --debug --profile=true --profile-heap=/tmp/redis-baseline-heap.prof --trace=/tmp/redis-baseline-trace.out" && \
173+ make run-benchmark-seccheck \
174+ BENCHMARKS_TARGETS=" //test/benchmarks/database:redis_test" \
175+ BENCHMARKS_OPTIONS=" -test.benchtime=2s -test.count=6" \
176+ BENCHMARKS_FILTER=" BenchmarkRedis" 2>&1 | tee redis_baseline.log
177+ ```
178+
179+ ** Tip for GC Tracing:** To explicitly count Garbage Collection cycles, you can
180+ prefix any of the following ` make run-benchmark-seccheck ` commands with
181+ ` GODEBUG=gctrace=1 ` . This prints raw GC statistics (and Stop-The-World pause
182+ timings) directly to the logs. * Note: Doing this consumes extra CPU cycles and
183+ introduces a minor observer effect penalty on max throughput.*
184+
185+ ```
186+ GODEBUG=gctrace=1 make run-benchmark-seccheck ...
187+ ```
188+
189+ ### Pass 2: Base Instrumentation (Null Sink)
190+
191+ This enables ` seccheck ` using a ` null ` sink configuration (to discard the
192+ serialized events instantly, eliminating remote IPC networking lag). This tests
193+ the absolute memory allocation cost strictly inside the Sentry when it
194+ dynamically intercepts high-frequency network events and generates Protobuf
195+ payload context structures.
196+
197+ ``` shell
198+ make setup-seccheck \
199+ SECCHECK_BENCH_CONFIG=$( pwd) /test/benchmarks/seccheck/redis_null_bench_config.json \
200+ RUNTIME_ARGS=" --debug --profile=true --profile-heap=/tmp/redis-null-heap.prof --trace=/tmp/redis-null-trace.out" && \
201+ make run-benchmark-seccheck \
202+ BENCHMARKS_TARGETS=" //test/benchmarks/database:redis_test" \
203+ BENCHMARKS_OPTIONS=" -test.benchtime=2s -test.count=6" \
204+ BENCHMARKS_FILTER=" BenchmarkRedis" 2>&1 | tee redis_null_sink.log
205+ ```
206+
207+ ### Pass 3: IPC Transfer Overhead (Remote Sink)
208+
209+ This enables ` seccheck ` using a ` remote ` sink. To isolate the extreme cost of
210+ the Sentry serializing and pushing bytes over the inter-process boundary (IPC),
211+ we launch a dummy listener in the background that instantly accepts and discards
212+ the Unix Domain Socket bytes.
213+
214+ ``` shell
215+ # Start a dummy UDS listener that discards the IPC bytes (SOCK_SEQPACKET type)
216+ python3 $( pwd) /test/benchmarks/seccheck/dummy_uds_server.py &
217+ DUMMY_PID=$!
218+
219+ # Configure and run the benchmark sending to the socket
220+ make setup-seccheck \
221+ SECCHECK_BENCH_CONFIG=$( pwd) /test/benchmarks/seccheck/redis_remote_bench_config.json \
222+ RUNTIME_ARGS=" --profile=true --profile-heap=/tmp/redis-remote-heap.prof --trace=/tmp/redis-remote-trace.out" && \
223+ make run-benchmark-seccheck \
224+ BENCHMARKS_TARGETS=" //test/benchmarks/database:redis_test" \
225+ BENCHMARKS_OPTIONS=" -test.benchtime=2s -test.count=6" \
226+ BENCHMARKS_FILTER=" BenchmarkRedis" 2>&1 | tee redis_remote_sink.log
227+
228+ # Kill the dummy listener and clean up the socket lock
229+ kill $DUMMY_PID
230+ rm -f /tmp/seccheck.sock
231+ ```
232+
233+ ### Analysis and Comparison
234+
235+ #### Comparing Application Throughput (QPS)
236+
237+ To compare the performance logs and see the exact QPS drop between the baseline
238+ and the instrumentation, use the official Go ` benchstat ` tool:
239+
240+ ``` shell
241+ # Install benchstat if you don't already have it
242+ go install golang.org/x/perf/cmd/benchstat@latest
243+
244+ ~ /go/bin/benchstat baseline=redis_baseline.log null=redis_null_sink.log remote=redis_remote_sink.log
245+ ```
246+
247+ #### Comparing Memory Overhead (Profiles)
248+
249+ Instead of manually analyzing raw text logs, we use Go's built-in tooling to
250+ subtract the baseline memory profile from the seccheck profile. This isolates
251+ the exact code paths and memory overhead introduced exclusively by the
252+ telemetry.
253+
254+ ** Option 1: Terminal Text Output** For a quick look directly in your terminal,
255+ output a ranked diff table:
256+
257+ ``` shell
258+ # Compare Data Generation Overhead (Null Sink target)
259+ go tool pprof -top -diff_base=/tmp/redis-baseline-heap.prof /tmp/redis-null-heap.prof
260+
261+ # Compare Total IPC Overhead (Remote Sink target)
262+ go tool pprof -top -diff_base=/tmp/redis-baseline-heap.prof /tmp/redis-remote-heap.prof
263+ ```
264+
265+ ** Option 2: Interactive Web UI** To view a visual flamegraph or the timeline
266+ trace, start a local web server:
267+
268+ ``` shell
269+ # View the heap flamegraph for Null Sink (Internal CPU Cost)
270+ go tool pprof -no_browser -http=0.0.0.0:8080 -diff_base=/tmp/redis-baseline-heap.prof /tmp/redis-null-heap.prof
271+
272+ # View the heap flamegraph for Remote Sink (Cross-Boundary IPC Cost)
273+ go tool pprof -no_browser -http=0.0.0.0:8080 -diff_base=/tmp/redis-baseline-heap.prof /tmp/redis-remote-heap.prof
274+
275+ # View the execution timeline trace for Null Sink
276+ # (safely ignore the X11 error if it appears, trace doesn't support -no_browser)
277+ go tool trace -http=0.0.0.0:8081 /tmp/redis-null-trace.out
278+ ```
279+
280+ * (Navigate to ` http://<your-machine-ip-or-hostname>:8080 ` in your browser).*
281+
282+ ### What to expect
283+
284+ - ** What it measures:** The ` redis-benchmark ` isolates operations throughput
285+ (in Queries Per Second) across workloads (e.g., ` SET ` , ` LPUSH ` ) whilst
286+ simultaneously recording all memory allocations (` allocs/op ` ) and Garbage
287+ Collection sweep pauses natively into the ` .prof ` and ` .out ` files.
288+ - ** What to look for:** You should compare the memory profiles (` pprof ` ) to
289+ see if the sheer act of generating the ` seccheck ` context objects is
290+ creating a high volume of garbage. Then, compare the execution traces
291+ (` trace ` ) to see if frequent GC "Stop The World" (STW) pauses are actively
292+ starving the Redis socket loops of CPU time.
293+
294+ ### Interpreting the Benchmark Results
295+
296+ By comparing the three generated logs (Baseline vs. Null Sink vs. Remote Sink),
297+ we can decompose the performance cost of Seccheck tracing into two distinct
298+ architectural phases: ** Data Generation** and ** Data Export** .
299+
300+ Here is an example real-world result from ` redis_test.go ` tracing constant
301+ ` read ` and ` write ` activity:
302+
303+ ``` text
304+ │ baseline │ null │ remote │
305+ │ SET.rps │ SET.rps vs base │ SET.rps vs base │
306+ Redis/operation.SET-96 39.22k ± 3% 37.62k ± 3% -4.08% (p=0.015 n=6) 28.16k ± 0% -28.20% (p=0.002 n=6)
307+ ```
308+
309+ #### Data Generation Overhead (Baseline vs. Null Sink)
310+
311+ The Null Sink measures strictly the CPU and Memory cost of intercepting the
312+ syscalls, gathering context data, and formatting the Protobuf trace objects
313+ in-memory (before instantly discarding them).
314+
315+ ** Result:** The overhead of structuring traces inside the Sentry drops
316+ operations throughput by ** ~ 4%** . Future optimizations should aim to bring this
317+ down to ** <1%** .
318+
319+ #### Data Export Overhead (Baseline vs. Remote Sink)
320+
321+ The Remote Sink measures the total end-to-end burden of tracing, which heavily
322+ includes writing the serialized Protobufs over a Unix Domain Socket (UDS) and
323+ crossing the Inter-Process Communication boundary to an external daemon.
324+
325+ ** Result:** Serializing and moving the data out of the socket drops operations
326+ throughput by a massive ** ~ 28%** .
0 commit comments