#!/usr/bin/env bpftrace /* * kvm_churn_breakdown.bt (v3) — KVM VM churn(create/exit 两路径)内核侧耗时分解 * * v3 改版(相对 v2): * - v2 的致命 bug:`elapsed` 的语义是"脚本启动以来的墙钟时间",不是"函数 * 入口→出口",v2 所有 kretfunc 直方图(全是秒级且形状雷同)因此全废。 * v3 一律手动 kfunc/kretfunc 配对(kfunc 记起点、kretfunc 必然配对删除, * 无 maxactive miss、无 @s_* 残留刷屏)。 * - v2 的 netlink_broadcast 锚作废:FC 上下文的 netlink 广播高达 ~4k 次/VM * (17M 次/窗),根本不是 uevent 专属。uevent 路径在这套内核上全部不可探 * (kvm_uevent_notify_change 通配符静默、kobject_uevent_env 运行时 notrace、 * uevent_net_broadcast_* 全内联),destroy 侧 uevent 改用 * kvm_vm_release − kvm_destroy_vm 的差值近似。 * - v1 的教训保留:kretprobe 侧 func 做 map key 不配平 → 数万行刷屏。 * * 用法:目标机(root,velinux 6.6.122,未打补丁 = 基线口径,全程不改内核): * sudo bpftrace scripts/kvm_churn_breakdown.bt * 压测窗口结束后 Ctrl-C,直方图自动打印(单位 us)。 * * 读数对照: * ── load/resume 路径(每 VM 一次)────────────────────────────── * @us_kvm_create_vm KVM_CREATE_VM ioctl 全程(含 create uevent) * @us_irqbypass["register"] irqfd ×8 过 irqbypass 全局锁 * ── stop/exit 路径(SIGKILL 后)──────────────────────────────── * @us_signal_to_exit 信号送达 → do_exit(调度/收尾延迟) * @us_exit_to_reap leader do_exit → waitpid 回收(整个内核拆除窗) * @us_exit_files fd 表关闭(dup3 风暴遗留的大 fdtable 在这) * @us_exit_mmap 地址空间页表 zap(含 Guest RAM) * @us_kvm_vm_release VM fd release 全程(含下面三项,勿重复相加) * ├ @us_kvm_destroy_vm VM 销毁主体(与 release 的差值 ≈ destroy uevent 等杂项) * ├ @us_kvm_destroy_vcpus vcpu 销毁(destroy_vm 的子集) * └ @us_stage2_destroy arm64 stage2 页表销毁(destroy_vm 的子集) * @us_irqbypass["unregister"] irqfd ×8 注销过全局锁(kworker 上下文) * ── 机制性证据 ──────────────────────────────────────────────── * @us_reclaim_direct / @us_reclaim_memcg FC 上下文回收 stall * @n_* 事件计数 * * 补丁口径:sum(@us_irqbypass) 两侧 = 补丁 1(kvm.irq_bypass)可省上限; * destroy uevent 精确单项不可探(见上),用 kvm_vm_release − kvm_destroy_vm * 差值兜底,将来可用补丁 2 的在线开关 A/B 直测(可选项,与本脚本无关)。 */ BEGIN { printf("Tracing KVM churn (create + exit)... Ctrl-C to stop and print.\n"); } /* ============ FC 退出生命周期 ============ */ /* T0: 致命信号送达(SIGTERM/SIGKILL;接收任务上下文,按 tgid 记) */ tracepoint:signal:signal_deliver /comm == "firecracker" && (args->sig == 15 || args->sig == 9) && !@t0[curtask->tgid]/ { @t0[curtask->tgid] = nsecs; } tracepoint:signal:signal_deliver /comm == "firecracker" && args->sig == 15/ { @n_sigterm = count(); } tracepoint:signal:signal_deliver /comm == "firecracker" && args->sig == 9/ { @n_sigkill = count(); } /* T1: leader 进入 do_exit(curtask->tgid == pid 只匹配主线程) */ tracepoint:sched:sched_process_exit /comm == "firecracker" && curtask->tgid == args->pid/ { if (@t0[args->pid]) { @us_signal_to_exit = hist((nsecs - @t0[args->pid]) / 1000); delete(@t0[args->pid]); } @t_dying[args->pid] = nsecs; @n_exit = count(); } /* T2: leader 被 waitpid 回收(curtask 是回收者,用 args->comm 过滤) */ tracepoint:sched:sched_process_free /args->comm == "firecracker" && @t_dying[args->pid]/ { @us_exit_to_reap = hist((nsecs - @t_dying[args->pid]) / 1000); delete(@t_dying[args->pid]); @n_reap = count(); } /* ============ do_exit 内部 ============ */ /* fd 表关闭(共享 CLONE_FILES 时只在最后一持有时真正 close) */ kfunc:exit_files /comm == "firecracker"/ { @s_ef[pid] = nsecs; } kretfunc:exit_files /@s_ef[pid]/ { @us_exit_files = hist((nsecs - @s_ef[pid]) / 1000); delete(@s_ef[pid]); } /* 地址空间整体 zap(8GB Guest RAM 走这里) */ kfunc:exit_mmap /comm == "firecracker"/ { @s_em[pid] = nsecs; } kretfunc:exit_mmap /@s_em[pid]/ { @us_exit_mmap = hist((nsecs - @s_em[pid]) / 1000); delete(@s_em[pid]); } /* ============ VM 销毁路径 ============ */ /* VM fd release:VM 销毁全程(static 函数,靠 BTF) */ kfunc:kvm_vm_release /comm == "firecracker"/ { @s_rel[pid] = nsecs; } kretfunc:kvm_vm_release /@s_rel[pid]/ { @us_kvm_vm_release = hist((nsecs - @s_rel[pid]) / 1000); delete(@s_rel[pid]); } /* VM 销毁主体:kvm_vm_release 与它的差值 ≈ destroy uevent + 杂项 */ kfunc:kvm_destroy_vm /comm == "firecracker"/ { @s_dvm[pid] = nsecs; } kretfunc:kvm_destroy_vm /@s_dvm[pid]/ { @us_kvm_destroy_vm = hist((nsecs - @s_dvm[pid]) / 1000); delete(@s_dvm[pid]); } kfunc:kvm_destroy_vcpus /comm == "firecracker"/ { @s_vcpus[pid] = nsecs; } kretfunc:kvm_destroy_vcpus /@s_vcpus[pid]/ { @us_kvm_destroy_vcpus = hist((nsecs - @s_vcpus[pid]) / 1000); delete(@s_vcpus[pid]); } kfunc:kvm_pgtable_stage2_destroy /comm == "firecracker"/ { @s_s2[pid] = nsecs; } kretfunc:kvm_pgtable_stage2_destroy /@s_s2[pid]/ { @us_stage2_destroy = hist((nsecs - @s_s2[pid]) / 1000); delete(@s_s2[pid]); } /* irqfd ×8 注销过 irqbypass 全局锁:irqfd_shutdown 在 kworker 上下文, * 无法按 comm 归属,全局聚合(本负载全部来自 FC) */ kfunc:irq_bypass_unregister_consumer { @s_ibu[tid] = nsecs; } kretfunc:irq_bypass_unregister_consumer /@s_ibu[tid]/ { @us_irqbypass["unregister"] = hist((nsecs - @s_ibu[tid]) / 1000); @n_irqbypass["unregister"] = count(); delete(@s_ibu[tid]); } /* ============ create 路径(load/resume 侧) ============ */ /* KVM_CREATE_VM ioctl 全程(含 create uevent)。kvm_create_vm 在 6.6.122 已 * 内联,锚在 file_operations 回调 kvm_dev_ioctl(地址被取用,必有符号)+ cmd * 过滤;KVM_CREATE_VM = _IO(0xAE, 0x01) = 0xAE01。kprobe 记起点(与 kretfunc * 共存已实测无冲突)、kretfunc 保证配对。 */ kprobe:kvm_dev_ioctl /comm == "firecracker" && arg1 == 0xae01/ { @s_cvm[tid] = nsecs; } kretfunc:kvm_dev_ioctl /@s_cvm[tid]/ { @us_kvm_create_vm = hist((nsecs - @s_cvm[tid]) / 1000); delete(@s_cvm[tid]); } /* irqfd ×8 注册过 irqbypass 全局锁(FC ioctl 上下文) */ kfunc:irq_bypass_register_consumer { @s_ibr[tid] = nsecs; } kretfunc:irq_bypass_register_consumer /@s_ibr[tid]/ { @us_irqbypass["register"] = hist((nsecs - @s_ibr[tid]) / 1000); @n_irqbypass["register"] = count(); delete(@s_ibr[tid]); } /* ============ 回收 stall(两条路径共用) ============ */ tracepoint:vmscan:mm_vmscan_direct_reclaim_begin /comm == "firecracker"/ { @s_rc[pid] = nsecs; } tracepoint:vmscan:mm_vmscan_direct_reclaim_end /comm == "firecracker" && @s_rc[pid]/ { @us_reclaim_direct = hist((nsecs - @s_rc[pid]) / 1000); delete(@s_rc[pid]); } tracepoint:vmscan:mm_vmscan_memcg_reclaim_begin /comm == "firecracker"/ { @s_mrc[pid] = nsecs; } tracepoint:vmscan:mm_vmscan_memcg_reclaim_end /comm == "firecracker" && @s_mrc[pid]/ { @us_reclaim_memcg = hist((nsecs - @s_mrc[pid]) / 1000); delete(@s_mrc[pid]); } END { printf("\n配对核对:@n_exit 应≈ @n_reap;@n_irqbypass 两侧应≈ 8~11×VM 数且对称。\n"); printf("对账:@us_exit_to_reap ≈ stop.wait_process 的 SIGKILL 段(stop 含 2s SIGTERM 超时,见 pool.rs:24)。\n"); }