在现代云原生基础设施中,网络性能的每一次抖动都可能引发连锁故障。传统抓包工具如 tcpdump 在高流量场景下往往成为瓶颈,而 eBPF(Extended Berkeley Packet Filter)技术的出现,让我们得以在不修改内核源码、不加载内核模块的情况下,以接近原生的性能对网络流量进行深度观测。本文将从 eBPF 的核心机制出发,逐步深入到 XDP、tc、Socket Filter、Kprobe/Uprobe 等各类 hook 点的使用方法,最终构建一套完整的生产级网络可观测性方案。

一、eBPF 网络观测的核心架构

1.1 为什么传统工具难以满足现代需求

tcpdump 基于 AF_PACKET 套接字,数据包需要从内核空间拷贝到用户空间,在高 PPS(Packets Per Second)场景下 CPU 开销极大。以一台处理 10Gbps 流量的服务器为例,若平均包大小为 512 字节,则 PPS 约为 244 万。使用 tcpdump 满配抓包时,单核 CPU 占用率轻松突破 80%。

eBPF 的解决思路完全不同:在数据包到达协议栈的关键路径上挂载 jit 编译的 verifier 安全字节码,在内核态直接完成过滤、统计、聚合,仅将结果通过 ring buffer 或 perf event 批量推送到用户空间。这种方式带来了两个革命性的优势:

  • 零拷贝:数据在内核态完成聚合,应用层只接收摘要信息
  • 低开销:eBPF 程序经过 JIT 编译,执行效率接近原生内核代码

1.2 网络观测的关键 Hook 点

eBPF 在网络栈中有多个挂载点,从底层到高层依次为:

Hook 点 挂载位置 典型用途 性能影响
XDP (eXpress Data Path) 网卡驱动层,协议栈之前 DDoS 防护、负载均衡、包过滤 最低,跳过整个网络协议栈
TC (Traffic Control) 内核流量控制层 流量整形、QoS、连接追踪 略高于 XDP,进入协议栈入口
Socket Filter 套接字层 应用级过滤、应用流量分析 已经过协议栈处理
Kprobe/Uprobe 内核/用户态函数 tcp_connect、tcp_sendmsg 等函数追踪 取决于挂载函数的调用频率
Tracepoint 静态内核追踪点 inet_sock_set_state、tcp_retransmit_skb 几乎无开销,生产安全
cgroup 控制组级别 容器/Pod 级别流量监控 可用于容器网络隔离审计

二、实战:用 XDP 构建高性能包计数器

2.1 编写 XDP eBPF 程序

下面是一个完整的 XDP 程序,按协议类型统计进出网卡的数据包数量和字节数:

// xdp_counter.bpf.c
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>

struct {
    __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
    __uint(max_entries, 6);
    __type(key, __u32);
    __type(value, __u64);
} stats_map SEC(".maps");

static __always_inline int proto_stat(struct xdp_md *ctx, __u32 proto) {
    void *data = (void *)(long)ctx->data;
    void *data_end = (void *)(long)ctx->data_end;
    
    __u32 key = proto;
    __u64 *counter = bpf_map_lookup_elem(&stats_map, &key);
    if (!counter)
        return XDP_PASS;
    
    __u64 pkt_len = data_end - data;
    __sync_fetch_and_add(counter, pkt_len);
    
    return XDP_PASS;
}

SEC("xdp")
int xdp_stats_func(struct xdp_md *ctx) {
    void *data = (void *)(long)ctx->data;
    void *data_end = (void *)(long)ctx->data_end;
    
    // 数据包边界检查
    struct ethhdr *eth = data;
    if ((void *)(eth + 1) > data_end)
        return XDP_DROP;
    
    __u32 proto = bpf_ntohs(eth->h_proto);
    
    switch (proto) {
    case ETH_P_IP:     // IPv4
        return proto_stat(ctx, 0);
    case ETH_P_IPV6:   // IPv6
        return proto_stat(ctx, 1);
    case ETH_P_ARP:    // ARP
        return proto_stat(ctx, 2);
    default:
        return proto_stat(ctx, 5); // Other
    }
}

char _license[] SEC("license") = "GPL";

2.2 Go 用户态加载程序

// xdp_loader.go
package main

import (
    "fmt"
    "net"
    "os"
    "os/signal"
    "syscall"
    "time"

    "github.com/cilium/ebpf"
    "github.com/cilium/ebpf/link"
    "github.com/cilium/ebpf/rlimit"
)

//go:generate go run github.com/cilium/ebpf/cmd/bpf2go xdp_counter xdp_counter.bpf.c

func main() {
    if err := rlimit.RemoveMemlock(); err != nil {
        fmt.Fprintf(os.Stderr, "removeMemlock: %v\n", err)
    }

    // 指定网络接口,例如 "eth0"
    ifaceName := "eth0"
    iface, err := net.InterfaceByName(ifaceName)
    if err != nil {
        fmt.Fprintf(os.Stderr, "lookup iface %s: %v\n", ifaceName, err)
    }

    // 加载 eBPF 程序
    objs := xdp_counterObjects{}
    if err := loadXdp_counterObjects(&objs, nil); err != nil {
        fmt.Fprintf(os.Stderr, "loading objects: %v\n", err)
    }
    defer objs.Close()

    // 挂载 XDP 程序到网卡
    l, err := link.AttachXDP(link.XDPOptions{
        Program:   objs.XdpStatsFunc,
        Interface: iface.Index,
    })
    if err != nil {
        fmt.Fprintf(os.Stderr, "attaching XDP: %v\n", err)
    }
    defer l.Close()

    fmt.Printf("XDP 程序已挂载到 %s,按 Ctrl+C 退出...\n", ifaceName)

    // 定期读取统计信息
    ticker := time.NewTicker(2 * time.Second)
    defer ticker.Stop()

    sig := make(chan os.Signal, 1)
    signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)

    protoNames := []string{"IPv4", "IPv6", "ARP", "TCP", "UDP", "Other"}
    
    for {
        select {
        case <-ticker.C:
            fmt.Println("\n─── 网络流量统计 ───")
            for i := 0; i < 6; i++ {
                var val uint64
                key := uint32(i)
                if err := objs.StatsMap.Lookup(&key, &val); err != nil {
                    continue
                }
                fmt.Printf("  %-6s: %12d bytes (%s)\n", 
                    protoNames[i], val, humanBytes(val))
            }
        case <-sig:
            return
        }
    }
}

func humanBytes(b uint64) string {
    const unit = 1024
    if b < unit {
        return fmt.Sprintf("%d B", b)
    }
    div, exp := unit, 0
    for n := b / unit; n >= unit; n /= unit {
        div *= unit
        exp++
    }
    return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "KMGTPE"[exp])
}

编译并运行:

# 生成 CO-RE eBPF 代码
go generate ./...

# 编译
go build -o xdp_loader xdp_loader.go

# 需要 root 权限或 CAP_BPF
sudo ./xdp_loader

# 输出示例:
# ─── 网络流量统计 ───
#   IPv4 :  ********** bytes (***.** MB)
#   ARP  :       12450 bytes (12.2 KB)
#   Other:        8920 bytes (8.7 KB)

2.3 性能基准

在 10Gbps 线速环境下(Intel X710 网卡,单队列),XDP 包计数器的 CPU 开销通常低于 2%,相比 tcpdump 的 60%+ CPU 占用有数量级提升。

三、实战:用 Tracee 打造一体化可观测平台

3.1 Tracee 简介

Tracee 是 Aqua Security 开源的基于 eBPF 的运行时安全观测工具。它不需要你手写 eBPF C 代码,而是通过 signatures 的方式预定义了大量安全事件检测规则。对于网络可观测性,Tracee 提供了:

  • 网络连接事件(connect、accept、bind、listen)
  • DNS 查询事件
  • ICMP/TCP/UDP 流量签名
  • 网络策略违规检测

3.2 安装与启动

# 最低要求:Linux 4.18+,内核配置 CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y

# 预编译二进制下载
wget https://github.com/aquasecurity/tracee/releases/download/v0.20.0/tracee-v0.20.0.$(uname -m).tar.gz
tar xzf tracee-v0.20.0.$(uname -m).tar.gz

# 以网络追踪模式运行
sudo ./tracee \
  --trace container=new \
  --trace event=net_packet_tcp,net_packet_udp,net_packet_icmp,socket_connect,security_socket_connect \
  --output json \
  --output option:parse-arguments

3.3 输出样例(单行格式化后)

{
  "timestamp": 1727842392.123456,
  "processorID": 1,
  "processId": 14832,
  "threadId": 14832,
  "hostName": "node-01",
  "containerId": "a1b2c3d4e5f6",
  "containerName": "nginx-prod",
  "eventName": "security_socket_connect",
  "args": [
    {"name": "sockfd", "type": "int", "value": 7},
    {"name": "remote_addr", "type": "struct sockaddr", "value": {
      "sa_family": "AF_INET",
      "sin_addr": "10.0.1.42",
      "sin_port": 6379
    }}
  ],
  "returnValue": 0
}

这意味着 nginx-prod 容器发起了一个到 10.0.1.42:6379 的 TCP 连接——可能是 Redis 后端。

3.4 过滤自定义事件

# 只追踪 Redis 端口连接(6379)
sudo ./tracee \
  --trace event=socket_connect \
  --trace event=security_socket_connect \
  --filter 'args.remote_addr.sin_port==6379' \
  --output json

四、实战:用 tc-bpf 实现容器网络 QoS

4.1 为什么需要 tc eBPF 而非 iptables

在 Kubernetes 环境中,一个节点上可能运行数百个 Pod。使用 iptables 做每 Pod 的流量整形时,规则数会随 Pod 数量线性增长,链式遍历导致匹配延迟急剧上升。eBPF 的 O(1) hash map 查找让大规模网络策略管理成为可能。

4.2 QoS 限速 eBPF 程序

// tc_qos.bpf.c
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>

#define RATE_LIMIT 10000000  // 10 Mbps

struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __uint(max_entries, 4096);
    __type(key, __u32);   // cgroup ID
    __type(value, __u64); // 当前桶 tokens (bytes)
} rate_limit_map SEC(".maps");

// 令牌桶: 每秒补充 RATE_LIMIT 字节
static __u64 last_update_ts = 0;

SEC("tc")
int tc_egress_QoS(struct __sk_buff *skb) {
    __u32 cgroup = bpf_get_cgroup_classid(skb);
    __u64 now = bpf_ktime_get_ns();
    __u64 *tokens;
    __u64 pkt_len = skb->len;

    // 检查是否在白名单中
    __u32 key = cgroup;
    tokens = bpf_map_lookup_elem(&rate_limit_map, &key);
    if (!tokens)
        return TC_ACT_OK; // 不在限速列表中,放行

    // 令牌桶算法
    __u64 new_tokens = *tokens;
    __u64 elapsed = now - last_update_ts;
    new_tokens += (elapsed * RATE_LIMIT) / 1000000000ULL;
    if (new_tokens > RATE_LIMIT)
        new_tokens = RATE_LIMIT; // 桶容量上限

    if (pkt_len > new_tokens) {
        return TC_ACT_SHOT; // 丢包: 令牌不足
    }

    new_tokens -= pkt_len;
    bpf_map_update_elem(&rate_limit_map, &key, &new_tokens, BPF_ANY);
    last_update_now = now;

    return TC_ACT_OK;
}

char _license[] = "GPL";

4.3 加载与社会定

# 加载到容器的 egress 方向
sudo tc qdisc add dev eth0 clsact
sudo tc filter add dev eth0 egress bpf da obj tc_qos.o sec tc

# 设置某个 cgroup 的限速
echo 12345 | sudo tee /sys/fs/cgroup/net_cls/k8s-probe/crio-abc123/net_cls.classid

# 通过 bpftool 配置限速参数
sudo bpftool map update pinned /sys/fs/bpf/rate_limit_map \
    key 0x39 0x30 0 0 value 0x00 0x00 0 0

五、实战:用 Kprobe 追踪 TCP 连接生命周期

5.1 为什么要追踪 TCP 生命周期

对于微服务架构,连接建立延迟和不健康的 Disconnected 状态往往是排查故障的关键。通过追踪内核函数 tcp_v4_connect(发起连接)、tcp_rcv_state_process(状态机转换)、tcp_fin(连接关闭),我们可以:

  • 精确测量 TCP 握手 RTT(三次握手完整时长)
  • 记录每个连接的源/目标五元组
  • 累积 er_reset 和 FIN 的数量,发现异常断开
  • 将追踪结果与业务 Trace 关联,构建全链路性能画像

5.2 eBPF Kprobe 程序

// tcp_trace.bpf.c
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>

struct event {
    __u32 pid;
    __u32 uid;
    __u32 saddr;
    __u32 daddr;
    __u16 dport;
    __u8  state;    // 1=SYN_SENT, 2=ESTABLISHED, 3=CLOSE_WAIT, 4=TIME_WAIT
    __u8  reserved;
    __u64 timestamp;
    __u64 duration_us; // 从 SYN 到 ESTABLISHED 的耗时
};

struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __uint(max_entries, 65536);
    __type(key, struct sock *);
    __type(value, __u64); // SYN_SENT 时的时间戳
} start_map SEC(".maps");

struct {
    __uint(type, BPF_MAP_TYPE_RINGBUF);
    __uint(max_entries, 256 * 1024); // 256KB
} events SEC(".maps");

// kprobe: tcp_v4_connect —— TCP 连接发起
SEC("kprobe/tcp_v4_connect")
int BPF_KPROBE(trace_tcp_connect, struct sock *sk) {
    __u64 ts = bpf_ktime_get_ns();
    bpf_map_update_elem(&start_map, &sk, &ts, BPF_ANY);
    return 0;
}

// kretprobe: tcp_v4_connect —— 连接建立完成
SEC("kretprobe/tcp_v4_connect")
int BPF_KRETPROBE(trace_tcp_connect_ret, int ret) {
    struct sock *sk = (struct sock *)PT_REGS_PARM1(ctx);
    __u64 *start_ts = bpf_map_lookup_elem(&start_map, &sk);
    
    struct event *e;
    e = bpf_ringbuf_reserve(&events, sizeof(*e), 0);
    if (!e) {
        if (start_ts)
            bpf_map_delete_elem(&start_map, &sk);
        return 0;
    }

    e->pid = bpf_get_current_pid_tgid() >> 32;
    e->uid = bpf_get_current_uid_gid();
    bpf_probe_read(&e->saddr, sizeof(__u32), &sk->__sk_common.skc_rcv_saddr);
    bpf_probe_read(&e->daddr, sizeof(__u32), &sk->__sk_common.skc_daddr);
    bpf_probe_read(&e->dport, sizeof(__u16), &sk->__sk_common.skc_dport);
    e->dport = bpf_ntohs(e->dport);
    e->state = (ret == 0) ? 2 : 1; // ESTABLISHED : SYN_FAILED
    e->timestamp = bpf_ktime_get_ns();
    e->duration_us = start_ts ? (e->timestamp - *start_ts) / 1000 : 0;

    bpf_ringbuf_submit(e, 0);

    if (start_ts)
        bpf_map_delete_elem(&start_map, &sk);
    return 0;
}

char _license[] = "GPL";

5.3 用户态消费者

# tcp_trace.py - 使用 bcc 或 libbpf 的消费端
import json
import socket
import struct
import time
from datetime import datetime
from bcc import BPF

bpf_text = """
... (嵌入上面的 eBPF C 代码) ...
"""

b = BPF(text=bpf_text)

def process_event(cpu, data, size):
    event = b["events"].event(data)
    
    src_ip = socket.inet_ntoa(struct.pack("I", event.saddr))
    dst_ip = socket.inet_ntoa(struct.pack("I", event.daddr))
    
    state_name = {1: "SYN_FAILED", 2: "ESTABLISHED"}.get(event.state, "UNKNOWN")
    
    duration_str = f"{event.duration_us}μs" if event.duration_us > 0 else "N/A"
    
    print(f"[{datetime.now():%H:%M:%S.%f}] PID={event.pid} "
          f"{src_ip}:* -> {dst_ip}:{event.dport} "
          f"state={state_name} (handshake: {duration_str})")

b["events"].open_ring_buffer(process_event)

print("── TCP 连接追踪启动 (Ctrl+C 退出) ──")
while True:
    try:
        b.ring_buffer_poll()
    except KeyboardInterrupt:
        break

运行输出示例:

── TCP 连接追踪启动 (Ctrl+C 退出) ──
[14:32:01.123456] PID=14832 10.0.1.10:* -> 10.0.1.42:6379 state=ESTABLISHED (handshake: 87μs)
[14:32:01.234567] PID=14832 10.0.1.10:* -> 10:0:1.55:5432 state=ESTABLISHED (handshake: 103μs)
[14:32:01.456789] PID=14832 10.0.1.10:* -> 10.0.1.60:443   state=SYN_FAILED (handshake: 3000123μs)

六、实战:基于 eBPF 的微服务网络拓扑自动发现

6.1 问题场景

在 Kubernetes 集群中,服务之间的调用关系往往难以直观感知。传统的依赖解析依靠静态配置或分布式追踪的采样数据。而 eBPF 可以在不引入 SDK、不改应用代码的前提下,自动构建实时的服务调用拓扑。

6.2 方案:Retina —— 微软开源的网络可观测性平台

Retina 是微软基于 eBPF 和 Hubble 构建的 Kubernetes 网络观测平台,支持:

  • 数据包级别 Metrics:PPS、延迟、丢包率
  • DNS 解析追踪:域名到 Pod 的映射
  • API Server 交互观测:etcd watch 延迟
  • CRD 驱动观测策略:声明式指定观测目标

安装 Retina:

helm repo add retina https://microsoft.github.io/retina
helm install retina retina/retina --namespace kube-system --create-namespace

# 启用所有观测模块
helm upgrade retina retina/retina --namespace kube-system \
  --set operator.enabled=true \
  --set metrics.enableTCPRetransmetrics=true \
  --set metrics.enablePacketParse=true

查看观测数据:

# 暴露 Prometheus 端口转发
kubectl port-forward svc/retina-kube-retina -n kube-system 9090:9090

# 查询 TCP 重传率高的 Pod
curl -s 'http://localhost:9090/api/v1/query?query=rate(retina_advanced_tcp_retransmissions[5m])' | jq

# 输出:TOP 5 高重传 Pod 列表

6.3 自定义 eBPF 程序集成到 Retina

apiVersion: retina.sh/v1alpha1
kind: RetinaEndpoint
metadata:
  name: custom-tcp-trace
spec:
  trace:
    hooks:
      - name: tcp_connect
        type: kprobe
        function: tcp_v4_connect
        args:
          - sockfd
          - remote_addr
    filter:
      pods:
        - namespace: production
      ports:
        - 5432  # PostgreSQL
        - 6379  # Redis
        - 9090  # gRPC
  metrics:
    - name: tcp_handshake_duration_us
      type: histogram
      buckets: [50, 100, 250, 500, 1000, 5000, 10000]
    - name: tcp_connections_total
      type: counter
      labels: [source_pod, dest_pod, dest_port]

七、生产环境部署的最佳实践

7.1 内核版本要求

特性 最低内核版本 推荐版本
TC BPF 4.1 5.4+
XDP 4.8 5.4+
Ring Buffer 5.8 5.8+
Kprobe CO-RE 5.5 5.8+
Batch Map API 5.6 5.8+

检查内核是否就绪:

# 内核配置检查
for conf in CONFIG_BPF CONFIG_BPF_SYSCALL CONFIG_BPF_JIT CONFIG_BPF_EVENTS CONFIG_KPROBES CONFIG_TRACEPOINTS; do
    printf "%-25s: " "$conf"
    zgrep "=y" /proc/config.gz | grep "$conf" >/dev/null && echo "✓" || echo "✗"
done

# 检查 BTF 支持(CO-RE 必需)
ls /sys/kernel/bf/*.btf 2>/dev/null && echo "BTF: 已启用" || echo "BTF: 未启用"

7.2 性能优化技巧

eBPF 程序在生产环境中的性能关键在于 eBPF Map 的设计 和 ring buffer 的吞吐量。以下是几条经验法则:

1. Per-CPU Map 替代全局 Map:数据格式相似时,使用 BPF_MAP_TYPE_PERCPU_* 系列避免原子锁竞争,性能可提升 3-10 倍。

2. Ring Buffer 平滑消费:当事件速率过高时,ring buffer 可能溢出(丢事件)。在用户侧使用多 goroutine 并发消费批处理:

// 多 worker 消费 ring buffer
workers := runtime.NumCPU()
for i := 0; i < workers; i++ {
    go func() {
        reader := bufio.NewReader(rb)
        for {
            record, err := reader.ReadRecord()
            if err != nil {
                if errors.Is(err, io.EOF) {
                    return
                }
            }
            batchSize++
            if batchSize >= 256 {
                flushToStorage(batch)
                batchSize = 0
            }
        }
    }()
}

3. 批量化 Map 操作:5.6+ 内核支持 bpf_map_lookup_batch、bpf_map_update_batch,减少用户态/内核态切换次数。

4. 选择合适的 hash algorithm:默认的 jhash 在高冲突场景下表现不佳,考虑配置 FPMap 或自定义 hash。

7.3 安全性考量

eBPF 虽然号称安全沙箱,但在网络观测场景下仍需注意:

  • Verifier 拒绝无限循环:确保程序正确性,避免 verifier 拒绝加载
  • 特权需求:大多数 BPF 系统调用需要 CAP_SYS_ADMIN 或 CAP_BPF(kernel 5.8+)
  • 敏感信息过滤:数据包内容可能包含业务数据,确保观测程序不泄露 payload
  • Doocker 容器逃逸风险:如果允许容器内挂载 eBPF 程序,可能被用于容器逃逸攻击,需通过 seccomp 策略限制

八、前沿演进:eBPF + 网络的可观测性未来

8.1 eBPF 与 P4 编程的融合

P4(Programming Protocol-independent Packet Processors)是一种数据平面编程语言,适合描述复杂的包处理逻辑。目前 P4C-XDP 编译器可以将 P4 程序编译为 eBPF/XDP 字节码,这意味着网络工程师可以用 P4 描述策略后直接部署到 Linux 内核硬件卸载路径。

8.2 eBPF 硬件卸载

NVIDIA ConnectX-5+ 系列网卡和 Intel E810 系列网卡支持 XDP 硬件卸载。这意味着 eBPF 程序可以直接在网卡上执行,完全不占用主机 CPU。对于 DDoS 过滤和网络前端加速场景,99 分位延迟可降至亚微秒级别。

8.3 eBPF 与 WireGuard 的结合

在 WireGuard 隧道中启动 eBPF 观测,可以无缝追踪加密隧道内的流量元数据。最新的 wg 工具已提供 BPF hooks,允许用户在隧道入口/出口处挂载观测逻辑,而无需解密数据包内容。

8.4 可观测性网格(Observability Mesh)

下一代网络可观测性的方向是将 eBPF 探针以 DaemonSet 方式部署在整个 Kubernetes 集群,配合 OpenTelemetry Collector 实现统一的 telemetry 管线:

应用 Pod ↔ eBPF Agent ↔ Kafka/OTLP ↔ ClickHouse ↔ Grafana

这种方案的优势是:对应用完全透明,支持全量观测(非采样),且开销可控。已经在 Uber、Netflix 等公司的超大规模集群中得到验证。

九、总结

eBPF 为 Linux 网络可观测性带来了一场范式革命:

维度 传统方案 eBPF 方案
侵入性 需要改代码/加依赖 零代码侵入
数据完整性 采样可能遗漏全量 100% 捕获
性能开销 高(>50% CPU) 低(<5% CPU)
部署复杂度 需重新发布应用 仅加载 eBPF 程序
数据灵活性 仅输出设计时指标 运行时动态定义

在生产环境中落地 eBPF 观测时,建议遵循循序渐进的路径:从 xdp_stats 这样简单的计数器开始,逐步过渡到 tc-bpf QoS、kprobe 追踪,最终构建覆盖 Pod-to-Pod 的全栈可观测性平台。内核升级、工具链演进、厂商硬件适配是这个过程中需要持续关注的三个核心维度。

相关命令参考:bpftool prog list、bpftool map show、bpftool net show、tc filter show,可以通过 bpftool 完整查看所有已加载的 eBPF 对象和其性能状态。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿
网站二维码

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部
/* 跳过导航链接 (无障碍) */ position: absolute; top: -100px; left: 15px; z-index: 99999; padding: 8px 16px; background: #007bff; color: #fff; font-size: 14px; border-radius: 0 0 4px 4px; text-decoration: none; transition: top 0.2s; } top: 0; outline: 3px solid #0056b3; }