Upgrade to Pro — share decks privately, control downloads, hide ads and more …

eBPF Can Do It! A 5-Minute Tour of 5 Real-World...

eBPF Can Do It! A 5-Minute Tour of 5 Real-World PHP Issues Solved with eBPF

- What is eBPF?
- Cases
- #1 Memcached Performance Issue Diagnosis
- #2 Dead Code Detection
- #3 Inspecting C extension behavior
- #4 Measuring the time taken for legacy batch jobs
- #5 Long-Term Internal Metrics
- Recap

Avatar for Sohei Iwahori

Sohei Iwahori

May 25, 2026

More Decks by Sohei Iwahori

Other Decks in Technology

Transcript

  1. eBPF Can Do It! A 5-Minute Tour of 5 Real-World

    PHP Issues Solved with eBPF 2026/05/26 Laravel Live Japan Sohei Iwahori (GREE, Inc.)
  2. Agenda • What is eBPF? • Cases • #1 Memcached

    Performance Issue Diagnosis • #2 Dead Code Detection • #3 Inspecting C extension behavior • #4 Measuring the time taken for legacy batch jobs • #5 Long-Term Internal Metrics • Recap
  3. What the actual problem was • Huge session key mapping

    object created by the FW • The object is updated every time items are stored • So.. We just stop using it!
  4. Problem(caused by dead code) • Barriers to version upgrades •

    Slow deployment process • Extremely hard to tell whether code is actually unused • Not only web but also batch jobs, etc. • Some jobs might run once a month or more
  5. php-dcr Overview (Data Flow) php-dcr (Go) Linux Kernel PHP Processes

    USDT probe USDT probe USDT probe BPF Map Scan HTTP Apache (mod_php) PHP-FPM PHP CLI eBPF USDT: compile__file__return → Record file path + timestamp BPF map reader (polls every 5 seconds) HTTP API :8080 /v1/report, /v1/stats Target directory *.php External client
  6. Problem • Storing data in APCu fails even when there

    is enough memory available • We hit this issue in some apps in production, caused issues like hitting old cache • apcu_store should return false on failure
  7. bpftrace with uretprobe Still we can see the return value

    on the APCu extension side with eBPF # bpftrace -e 'uretprobe:/usr/lib/php/20190902/apcu.so:apc_cache_store {printf ("%d\n", retval)}' 1 1 1 0 ...
  8. What happened? • APCu had a bug where storing data

    required contiguous free memory space, but the implementation was only checking the total available size rather than verifying that a contiguous block was actually available3 • This meant APCu could determine there was sufficient space and proceed with the store operation, only to fail afterward when no contiguous region large enough was available. • This issue has been fixed in v5.1.25. • Use fixed version! 3 https://github.com/krakjoe/apcu/pull/532
  9. Problem - Added latency during migration Before migration Network location

    A App / API Batch jobs DB Low latency (co-located) Migrating During migration to location B Network location A App / API Batch jobs Network location B (target) DB +1.6 ms latency
  10. Problem - Added latency during migration Before migration Network location

    A App / API Batch jobs DB TC command inject delay Co-located: delay added artificially to simulate the move Migrating During migration to location B Network location A App / API Batch jobs Network location B (target) DB +1.6 ms latency
  11. bpftrace(script) again #!/usr/bin/bpftrace --unsafe #ifndef BPFTRACE_HAVE_BTF #include <linux/sched.h> #endif BEGIN

    { printf("Starting process monitoring...\n"); printf("%-8s %-20s %-8s %-8s %-8s %s\n", "TIME", "EVENT", "PID", "ELAPSED_SEC", "COMMAND", "CODE"); } tracepoint:sched:sched_process_exec /comm == "php"/ { $pid = pid; // not available with kernel 5.15.0 //printf("cmdpath: %s", args->filename); // Update map with process ID as key and current time as value @start_time[$pid] = nsecs; time("%Y-%m-%d %H:%M:%S "); // Get the command line (read from /proc/<pid>/cmdline) printf("%-20s %-8d %-8s %s / ", "EXEC", $pid, "", comm); // Get command line arguments system("tr '\\0' ' ' </proc/%d/cmdline", $pid); printf("\n"); } // Monitor process exit tracepoint:sched:sched_process_exit /comm == "php"/ { $task = (struct task_struct *)curtask; if ($task->pid == $task->tgid) { // If the map entry exists for this process ID, retrieve the time if (@start_time[pid]) { $duration_ns = nsecs - @start_time[pid]; $duration_sec = $duration_ns / 1000000000; time("%Y-%m-%d %H:%M:%S "); printf("%-20s %-8d %-8d %s %d", //printf("%d %-7d exit(%d) %s (execution time: %d seconds)\n", "EXIT", pid, $duration_sec, comm, $task->exit_code >> 8 ); printf("\n"); // Delete the map entry on exit delete(@start_time[pid]); } } } END { printf("\nStopping monitoring\n"); // Clear any remaining map entries clear(@start_time); }
  12. ebpf_exporter • cloudflare/ebpf_exporter4 • Prometheus exporter for custom eBPF metrics

    and OpenTelemetry traces • Collect metrics with a small custom eBPF program and YAML configuration 4 https://github.com/cloudflare/ebpf_exporter
  13. eBPF Code and YAML Example - name: php_request_time_sec help: PHP

    ELAPSED TIME SEC from startup to shutdown bucket_type: exp2 bucket_min: 0 bucket_max: 27 bucket_multiplier: 0.000001 # microseconds to seconds labels: - name: request_uri size: 256 decoders: - name: string - name: request_method size: 8 decoders: - name: string - name: bucket size: 8 decoders: - name: uint SEC("usdt//usr/lib/apache2/modules/libphp8.1.so:php:request__startup") int BPF_USDT(request_startup, char *arg0, char *arg1, char *arg2) { u64 ts = bpf_ktime_get_ns(); u32 pid = bpf_get_current_pid_tgid(); struct php_req_key key; key.pid = pid; bpf_probe_read_user_str(&key.request_uri, sizeof(key.request_uri), arg1); bpf_probe_read_user_str(&key.request_method, sizeof(key.request_method), arg2); bpf_map_update_elem(&php_req, &pid, &ts, BPF_ANY); return 0; } SEC("usdt//usr/lib/apache2/modules/libphp8.1.so:php:request__shutdown") int BPF_USDT(request_shutdown, char *arg0, char *arg1, char *arg2) { u64 *tsp, delta_us, ts = bpf_ktime_get_ns(); u32 pid = bpf_get_current_pid_tgid(); struct php_req_hist_key_t hist_key = {}; bpf_probe_read_user_str(&hist_key.request_uri, sizeof(hist_key.request_uri), arg1); bpf_probe_read_user_str(&hist_key.request_method, sizeof(hist_key.request_method), arg2); tsp = bpf_map_lookup_elem(&php_req, &pid); if (!tsp) { return 0; } delta_us = (ts - *tsp) / 1000; increment_exp2_histogram(&php_request_time_sec, hist_key, delta_us, MAX_SLOT_PHP_REQ); return 0; }
  14. Recap • eBPF can be used for solving issues in

    the PHP world • For ad-hoc purposes • bpftrace • For long-term solutions • ebpf_exporter • dedicated tools • Write your own!
  15. About Me • Sohei Iwahori • X/bsky/GitHub: @egmc • Senior

    Lead Engineer at GREE, Inc. • Leading Monitoring Unit • Community • eBPF Japan Meetup