소스 검색

Feature #TASK_QT-34185 适配buntdb

Carl 4 달 전
부모
커밋
12f026b72a

+ 23 - 0
containers/container_apm.go

@@ -25,6 +25,7 @@ import (
 	. "github.com/coroot/coroot-node-agent/utils/modelse"
 	. "github.com/coroot/coroot-node-agent/utils/modelse"
 	"github.com/pkg/errors"
 	"github.com/pkg/errors"
 	klog "github.com/sirupsen/logrus"
 	klog "github.com/sirupsen/logrus"
+	"go.opentelemetry.io/otel/attribute"
 	semconv "go.opentelemetry.io/otel/semconv/v1.18.0"
 	semconv "go.opentelemetry.io/otel/semconv/v1.18.0"
 	"inet.af/netaddr"
 	"inet.af/netaddr"
 )
 )
@@ -518,6 +519,28 @@ func (c *Container) onL7RequestApm(pid uint32, fd uint64, timestamp uint64, r *l
 			}
 			}
 		}
 		}
 
 
+	/**
+	 * BuntDB
+	 */
+	case l7.ProtocolBuntDB:
+		stats.observe(r.Status.String(), "", r.Duration)
+		if c.l7Attach && c.valuableTrace(r.TraceId) {
+			query := string(r.Payload)
+			if c.AppInfo.AppName != "" {
+				klog.Debugf("[%s] ->>>>> %s -> %s payload:[%s]", c.AppInfo.AppName, r.Protocol.String(), conn.ActualDest, query)
+			}
+
+			apmTrace, err := c.getOrInitTrace(r.TraceId)
+			if err == nil {
+				// BuntDB 是键值数据库,使用 NoSQLTraceQueryEvent,operation 从 payload 中提取(如 "Set: key_name")
+				operation := ""
+				statement := query
+				dbSystem := attribute.String("db.system", "buntdb")
+				defaultLocalAddr := netaddr.IPPortFrom(netaddr.MustParseIP("127.0.0.1"), 0)
+				apmTrace.NoSQLTraceQueryEvent(r.Protocol, dbSystem, operation, statement, r, defaultLocalAddr, defaultLocalAddr)
+				c.SendEvent(apmTrace, r.TraceId)
+			}
+		}
 	/**
 	/**
 	 * Kafka
 	 * Kafka
 	 */
 	 */

+ 1 - 0
ebpftracer/ebpf/ebpf.c

@@ -63,6 +63,7 @@
 #include "utrace/go/net/grpc.server.probe.bpf.c"
 #include "utrace/go/net/grpc.server.probe.bpf.c"
 #include "utrace/go/net/grpc.client.probe.bpf.c"
 #include "utrace/go/net/grpc.client.probe.bpf.c"
 #include "utrace/go/db/gocql.probe.bpf.c"
 #include "utrace/go/db/gocql.probe.bpf.c"
+#include "utrace/go/db/buntdb.probe.bpf.c"
 #include "utrace/go/mq/kafka/producer.probe.bpf.c"
 #include "utrace/go/mq/kafka/producer.probe.bpf.c"
 #include "utrace/go/mq/kafka/consumer.probe.bpf.c"
 #include "utrace/go/mq/kafka/consumer.probe.bpf.c"
 
 

+ 3 - 0
ebpftracer/ebpf/l7/l7.c

@@ -16,6 +16,9 @@
 #define PROTOCOL_DM        14
 #define PROTOCOL_DM        14
 #define PROTOCOL_MARIADB   15
 #define PROTOCOL_MARIADB   15
 #define PROTOCOL_GRPC      16
 #define PROTOCOL_GRPC      16
+#define PROTOCOL_ES        17
+#define PROTOCOL_TIDB      18
+#define PROTOCOL_BUNTDB    19
 
 
 
 
 
 

+ 538 - 0
ebpftracer/ebpf/utrace/go/db/buntdb.probe.bpf.c

@@ -0,0 +1,538 @@
+//
+// BuntDB uprobe instrumentation
+// Tracks individual operations: (*Tx).Set(), (*Tx).Get(), (*Tx).Delete()
+//
+
+#include "arguments.h"
+#include "span_context.h"
+#include "go_context.h"
+#include "go_types.h"
+#include "uprobe.h"
+#include "go_common.h"
+
+#define MAX_KEY_LEN 256       // max key length
+#define MAX_VALUE_LEN 512     // max value length
+
+#define MAX_CONCURRENT 50
+
+// ============================================================================
+// Per-operation spans (tx.Set/Get/Delete each creates its own span)
+// ============================================================================
+
+// Per-operation context (one span per tx.Set/Get/Delete)
+struct buntdb_op_context_t {
+    BASE_SPAN_PROPERTIES
+    char key[MAX_KEY_LEN];
+    char value[MAX_VALUE_LEN];
+    __u64 key_size;
+    __u64 value_size;
+    __u32 op_type;  // 0=Set, 1=Get, 2=Delete
+};
+
+struct {
+    __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
+    __uint(key_size, sizeof(u32));
+    __uint(value_size, sizeof(struct buntdb_op_context_t));
+    __uint(max_entries, 1);
+} buntdb_op_storage_map SEC(".maps");
+
+struct {
+    __uint(type, BPF_MAP_TYPE_HASH);
+    __type(key, void *);
+    __type(value, struct buntdb_op_context_t);
+    __uint(max_entries, MAX_CONCURRENT);
+} buntdb_op_events SEC(".maps");
+
+// This instrumentation attaches uprobe to the following function:
+// func (tx *Tx) Set(key, value string, opts *SetOptions) (previousValue string, replaced bool, err error)
+// Params: 1=tx (receiver), 2=key_ptr, 3=key_len, 4=value_ptr, 5=value_len, 6=opts
+// Returns: 1=previousValue_ptr, 2=previousValue_len, 3=replaced, 4=err_type_ptr, 5=err_data_ptr
+SEC("uprobe/Tx_Set")
+int uprobe_Tx_Set(struct pt_regs *ctx) {
+    __u64 pid_tgid = bpf_get_current_pid_tgid();
+    __u32 tgid = pid_tgid >> 32;
+    
+    struct ebpf_proc_info *info = bpf_map_lookup_elem(&proc_info_map, &tgid);
+    if (!info) {
+        return 0;
+    }
+
+    // Get consistent key (use goroutine address as key)
+    void *key = (void *)GOROUTINE(ctx);
+    
+    // Skip if the operation already exists
+    struct buntdb_op_context_t *existing = bpf_map_lookup_elem(&buntdb_op_events, &key);
+    if (existing != NULL) {
+        return 0;
+    }
+
+    // Allocate storage
+    u32 zero = 0;
+    struct buntdb_op_context_t *op_ctx = bpf_map_lookup_elem(&buntdb_op_storage_map, &zero);
+    if (op_ctx == NULL) {
+        return -1;
+    }
+
+    __builtin_memset(op_ctx, 0, sizeof(struct buntdb_op_context_t));
+    op_ctx->start_time = bpf_ktime_get_ns();
+    op_ctx->op_type = 0;
+
+    // Read key params: 1=tx receiver, 2=key pointer, 3=key length
+    // In Go calling convention, string is split into pointer and length
+    void *key_str_ptr = get_argument(ctx, 2);
+    __u64 key_len = (__u64)get_argument(ctx, 3);
+    
+    // Write operation prefix "set "
+    int off = 4;
+    char opr[4] = "set ";
+    bpf_probe_read(op_ctx->key, 4, opr);
+    if (key_str_ptr != 0 && key_len > 0) {
+        __u64 copy_key_len = key_len < (MAX_KEY_LEN - off) ? key_len : (MAX_KEY_LEN - off);
+        long res = bpf_probe_read(op_ctx->key + off, copy_key_len, key_str_ptr);
+        if (res == 0) {
+            op_ctx->key_size = copy_key_len + off;
+        }
+    }
+    
+    // Read value params: 4=value pointer, 5=value length
+    void *value_str_ptr = get_argument(ctx, 4);
+    __u64 value_len = (__u64)get_argument(ctx, 5);
+    
+    // Read string content directly (use bpf_probe_read)
+    if (value_str_ptr != 0 && value_len > 0) {
+        __u64 copy_value_len = value_len < MAX_VALUE_LEN ? value_len : MAX_VALUE_LEN;
+        long res = bpf_probe_read(op_ctx->value, copy_value_len, value_str_ptr);
+        if (res == 0) {
+            op_ctx->value_size = copy_value_len;
+        }
+    }
+    
+    // Store in map
+    bpf_map_update_elem(&buntdb_op_events, &key, op_ctx, 0);
+    
+    // Log SQL-like message: Set key=xxx
+    if (op_ctx->key_size > 0 && op_ctx->key_size < MAX_KEY_LEN) {
+        cw_bpf_debug("[buntdb] Set: %.*s", (int)op_ctx->key_size, op_ctx->key);
+    } else {
+        cw_bpf_debug("[buntdb] Set: key=unknown");
+    }
+    
+    return 0;
+}
+
+// This instrumentation attaches uprobe to the return of Tx.Set
+SEC("uprobe/Tx_Set")
+int uprobe_Tx_Set_Returns(struct pt_regs *ctx) {
+    __u64 pid_tgid = bpf_get_current_pid_tgid();
+    __u32 tgid = pid_tgid >> 32;
+    __u32 pid = pid_tgid;
+    
+    struct ebpf_proc_info *info = bpf_map_lookup_elem(&proc_info_map, &tgid);
+    if (!info) {
+        return 0;
+    }
+
+    // Get consistent key
+    void *key = (void *)GOROUTINE(ctx);
+    
+    // Lookup operation context
+    struct buntdb_op_context_t *op_ctx = bpf_map_lookup_elem(&buntdb_op_events, &key);
+    if (op_ctx == NULL) {
+        return 0;
+    }
+
+    // Update end time
+    op_ctx->end_time = bpf_ktime_get_ns();
+
+    // Read error info
+    // Returns: 1=previousValue_ptr, 2=previousValue_len, 3=replaced, 4=err_type_ptr, 5=err_data_ptr
+    // Go error interface is (type_ptr, data_ptr); non-nil data_ptr means error
+    void *error_data_ptr = get_argument(ctx, 5);  // error 接口的 data 指针
+    __u32 has_error = 0;
+    if (error_data_ptr != 0) {
+        has_error = 1;
+    }
+
+    __u32 zero = 0;
+    struct l7_event *e = bpf_map_lookup_elem(&l7_event_heap, &zero);
+    if (!e) {
+        bpf_map_delete_elem(&buntdb_op_events, &key);
+        return 0;
+    }
+
+    // Set event attributes
+    e->protocol = PROTOCOL_BUNTDB;
+    e->pid = tgid;
+    e->start_at = op_ctx->start_time;
+    e->end_at = op_ctx->end_time;
+    e->duration = e->end_at - e->start_at;
+    
+    // Use op_ctx->key as payload
+    __u64 payload_size = op_ctx->key_size > 0 ? op_ctx->key_size : 0;
+    if (payload_size > 1024) {
+        payload_size = 1024;
+    }
+    e->payload_size = payload_size;
+    
+    if (payload_size > 0) {
+        COPY_PAYLOAD(e->payload, payload_size, op_ctx->key);
+        // Log SQL-like message
+        cw_bpf_debug("[buntdb] SQL: Set: %.*s", (int)payload_size, op_ctx->key);
+    }
+
+    // Get trace info
+    struct apm_trace_key_t trace_key = get_apm_trace_key(120 * NS_PER_SEC, true);
+    struct apm_trace_info_t *trace_info = get_apm_trace_info_by_trace_key(trace_key);
+    
+    if (trace_info == 0) {
+        trace_info = get_apm_trace_info_v3(trace_key, pid_tgid, tgid, pid_tgid);
+    }
+    
+    if (trace_info) {
+        e->trace_id = trace_info->trace_id;
+    }
+    
+    if (e->trace_id == 0) {
+        e->trace_id = get_apm_trace_id(tgid, pid_tgid);
+    }
+
+    // Set error status
+    if (has_error) {
+        e->status = STATUS_FAILED;
+    } else {
+        e->status = STATUS_OK;
+    }
+
+    // Emit event (one span per Set)
+    SET_TRACE_METHOD(e);
+    long error = bpf_perf_event_output(ctx, &l7_events, BPF_F_CURRENT_CPU, e, sizeof(*e));
+    if (error == 0) {
+        cw_add_event_count(e->trace_id);
+        cw_bpf_debug("[buntdb] Set Returns: success, trace_id=%llu, key=%.*s", e->trace_id, (int)op_ctx->key_size, op_ctx->key);
+    }
+    
+    // Cleanup
+    bpf_map_delete_elem(&buntdb_op_events, &key);
+    return 0;
+}
+
+// This instrumentation attaches uprobe to the following function:
+// func (tx *Tx) Get(key string, ignoreExpired ...bool) (val string, err error)
+// Params: 1=tx (receiver), 2=key_ptr, 3=key_len
+// Note: ignoreExpired is variadic (...bool) and may be omitted
+// Returns: 1=val_ptr, 2=val_len, 3=err_type_ptr, 4=err_data_ptr
+SEC("uprobe/Tx_Get")
+int uprobe_Tx_Get(struct pt_regs *ctx) {
+	cw_bpf_debug("get");
+    __u64 pid_tgid = bpf_get_current_pid_tgid();
+    __u32 tgid = pid_tgid >> 32;
+    
+    struct ebpf_proc_info *info = bpf_map_lookup_elem(&proc_info_map, &tgid);
+    if (!info) {
+        return 0;
+    }
+
+    // Get consistent key
+    void *key = (void *)GOROUTINE(ctx);
+    
+    // Skip if the operation already exists
+    struct buntdb_op_context_t *existing = bpf_map_lookup_elem(&buntdb_op_events, &key);
+    if (existing != NULL) {
+        return 0;
+    }
+
+    // Allocate storage
+    u32 zero = 0;
+    struct buntdb_op_context_t *op_ctx = bpf_map_lookup_elem(&buntdb_op_storage_map, &zero);
+    if (op_ctx == NULL) {
+        return -1;
+    }
+
+    __builtin_memset(op_ctx, 0, sizeof(struct buntdb_op_context_t));
+    op_ctx->start_time = bpf_ktime_get_ns();
+    op_ctx->op_type = 1;  // Get
+
+    // Read key params: 1=tx receiver, 2=key pointer, 3=key length
+    // In Go calling convention, string is split into pointer and length
+    void *key_str_ptr = get_argument(ctx, 2);
+    __u64 key_len = (__u64)get_argument(ctx, 3);
+    
+    // Read string content directly (use bpf_probe_read)
+	int off = 4;
+	char opr[4] = "get ";
+	bpf_probe_read(op_ctx->key, 4, opr);
+	if (key_str_ptr != 0 && key_len > 0) {
+		__u64 copy_key_len = key_len < MAX_KEY_LEN ? key_len : MAX_KEY_LEN;
+		long res = bpf_probe_read(op_ctx->key + off, copy_key_len, key_str_ptr);
+		if (res == 0) {
+			op_ctx->key_size = copy_key_len + off;
+		}
+	}
+
+
+    // Store in map
+    bpf_map_update_elem(&buntdb_op_events, &key, op_ctx, 0);
+    
+    // Log SQL-like message: Get key=xxx
+    if (op_ctx->key_size > 0 && op_ctx->key_size < MAX_KEY_LEN) {
+        cw_bpf_debug("[buntdb] Get: %d,key=%s", (int)op_ctx->key_size, op_ctx->key);
+    } else {
+        cw_bpf_debug("[buntdb] Get: key=unknown");
+    }
+    return 0;
+}
+
+// This instrumentation attaches uprobe to the return of Tx.Get
+SEC("uprobe/Tx_Get")
+int uprobe_Tx_Get_Returns(struct pt_regs *ctx) {
+    __u64 pid_tgid = bpf_get_current_pid_tgid();
+    __u32 tgid = pid_tgid >> 32;
+
+    struct ebpf_proc_info *info = bpf_map_lookup_elem(&proc_info_map, &tgid);
+    if (!info) {
+        return 0;
+    }
+
+    // Get consistent key
+    void *key = (void *)GOROUTINE(ctx);
+    
+    // Lookup operation context
+    struct buntdb_op_context_t *op_ctx = bpf_map_lookup_elem(&buntdb_op_events, &key);
+    if (op_ctx == NULL) {
+        return 0;
+    }
+
+    // Update end time
+    op_ctx->end_time = bpf_ktime_get_ns();
+
+    // Read error info
+    // Returns: 1=val_ptr, 2=val_len, 3=err_type_ptr, 4=err_data_ptr
+    // Go error interface is (type_ptr, data_ptr); non-nil data_ptr means error
+    void *error_data_ptr = get_argument(ctx, 4);  // error 接口的 data 指针
+    __u32 has_error = 0;
+    if (error_data_ptr != 0) {
+        has_error = 1;
+    }
+
+    __u32 zero = 0;
+    struct l7_event *e = bpf_map_lookup_elem(&l7_event_heap, &zero);
+    if (!e) {
+        bpf_map_delete_elem(&buntdb_op_events, &key);
+        return 0;
+    }
+
+    // Set event attributes
+    e->protocol = PROTOCOL_BUNTDB;
+    e->pid = tgid;
+    e->start_at = op_ctx->start_time;
+    e->end_at = op_ctx->end_time;
+    e->duration = e->end_at - e->start_at;
+    
+    // Use op_ctx->key as payload
+    __u64 payload_size = op_ctx->key_size > 0 ? op_ctx->key_size : 0;
+    if (payload_size > 1024) {
+        payload_size = 1024;
+    }
+    e->payload_size = payload_size;
+    
+    if (payload_size > 0) {
+        COPY_PAYLOAD(e->payload, payload_size, op_ctx->key);
+        // Log SQL-like message
+        cw_bpf_debug("[buntdb] SQL: Get: %.*s", (int)payload_size, op_ctx->key);
+    }
+
+    // Get trace info
+    struct apm_trace_key_t trace_key = get_apm_trace_key(120 * NS_PER_SEC, true);
+    struct apm_trace_info_t *trace_info = get_apm_trace_info_by_trace_key(trace_key);
+    
+    if (trace_info == 0) {
+        trace_info = get_apm_trace_info_v3(trace_key, pid_tgid, tgid, pid_tgid);
+    }
+    
+    if (trace_info) {
+        e->trace_id = trace_info->trace_id;
+    }
+    
+    if (e->trace_id == 0) {
+        e->trace_id = get_apm_trace_id(tgid, pid_tgid);
+    }
+
+    // Set error status
+    if (has_error) {
+        e->status = STATUS_FAILED;
+    } else {
+        e->status = STATUS_OK;
+    }
+
+    // Emit event (one span per Get)
+    SET_TRACE_METHOD(e);
+    long error = bpf_perf_event_output(ctx, &l7_events, BPF_F_CURRENT_CPU, e, sizeof(*e));
+    if (error == 0) {
+        cw_add_event_count(e->trace_id);
+        cw_bpf_debug("[buntdb] Get Returns: success, trace_id=%llu, key=%.*s", e->trace_id, (int)op_ctx->key_size, op_ctx->key);
+    }
+    
+    // Cleanup
+    bpf_map_delete_elem(&buntdb_op_events, &key);
+    return 0;
+}
+
+// This instrumentation attaches uprobe to the following function:
+// func (tx *Tx) Delete(key string) (val string, err error)
+// Params: 1=tx (receiver), 2=key_ptr, 3=key_len
+// Returns: 1=val_ptr, 2=val_len, 3=err_type_ptr, 4=err_data_ptr
+SEC("uprobe/Tx_Delete")
+int uprobe_Tx_Delete(struct pt_regs *ctx) {
+    __u64 pid_tgid = bpf_get_current_pid_tgid();
+    __u32 tgid = pid_tgid >> 32;
+    
+    struct ebpf_proc_info *info = bpf_map_lookup_elem(&proc_info_map, &tgid);
+    if (!info) {
+        return 0;
+    }
+
+    // Get consistent key
+    void *key = (void *)GOROUTINE(ctx);
+    
+    // Skip if the operation already exists
+    struct buntdb_op_context_t *existing = bpf_map_lookup_elem(&buntdb_op_events, &key);
+    if (existing != NULL) {
+        return 0;
+    }
+
+    // Allocate storage
+    u32 zero = 0;
+    struct buntdb_op_context_t *op_ctx = bpf_map_lookup_elem(&buntdb_op_storage_map, &zero);
+    if (op_ctx == NULL) {
+        return -1;
+    }
+
+    __builtin_memset(op_ctx, 0, sizeof(struct buntdb_op_context_t));
+    op_ctx->start_time = bpf_ktime_get_ns();
+    op_ctx->op_type = 2;  // Delete
+
+    // Read key params: 1=tx receiver, 2=key pointer, 3=key length
+    // In Go calling convention, string is split into pointer and length
+    void *key_str_ptr = get_argument(ctx, 2);
+    __u64 key_len = (__u64)get_argument(ctx, 3);
+    
+    // Write operation prefix "delete "
+    int off = 7;
+    char opr[7] = "delete ";
+    bpf_probe_read(op_ctx->key, 7, opr);
+    if (key_str_ptr != 0 && key_len > 0) {
+        __u64 copy_key_len = key_len < (MAX_KEY_LEN - off) ? key_len : (MAX_KEY_LEN - off);
+        long res = bpf_probe_read(op_ctx->key + off, copy_key_len, key_str_ptr);
+        if (res == 0) {
+            op_ctx->key_size = copy_key_len + off;
+        }
+    }
+    
+    // Store in map
+    bpf_map_update_elem(&buntdb_op_events, &key, op_ctx, 0);
+    
+    // Log SQL-like message: Delete key=xxx
+    if (op_ctx->key_size > 0 && op_ctx->key_size < MAX_KEY_LEN) {
+        cw_bpf_debug("[buntdb] Delete: %.*s", (int)op_ctx->key_size, op_ctx->key);
+    } else {
+        cw_bpf_debug("[buntdb] Delete: key=unknown");
+    }
+    
+    return 0;
+}
+
+// This instrumentation attaches uprobe to the return of Tx.Delete
+SEC("uprobe/Tx_Delete")
+int uprobe_Tx_Delete_Returns(struct pt_regs *ctx) {
+    __u64 pid_tgid = bpf_get_current_pid_tgid();
+    __u32 tgid = pid_tgid >> 32;
+    __u32 pid = pid_tgid;
+    
+    struct ebpf_proc_info *info = bpf_map_lookup_elem(&proc_info_map, &tgid);
+    if (!info) {
+        return 0;
+    }
+
+    // Get consistent key
+    void *key = (void *)GOROUTINE(ctx);
+    
+    // Lookup operation context
+    struct buntdb_op_context_t *op_ctx = bpf_map_lookup_elem(&buntdb_op_events, &key);
+    if (op_ctx == NULL) {
+        return 0;
+    }
+
+    // Update end time
+    op_ctx->end_time = bpf_ktime_get_ns();
+
+    // Read error info
+    // Returns: 1=val_ptr, 2=val_len, 3=err_type_ptr, 4=err_data_ptr
+    // Go error interface is (type_ptr, data_ptr); non-nil data_ptr means error
+    void *error_data_ptr = get_argument(ctx, 4);  // error 接口的 data 指针
+    __u32 has_error = 0;
+    if (error_data_ptr != 0) {
+        has_error = 1;
+    }
+
+    __u32 zero = 0;
+    struct l7_event *e = bpf_map_lookup_elem(&l7_event_heap, &zero);
+    if (!e) {
+        bpf_map_delete_elem(&buntdb_op_events, &key);
+        return 0;
+    }
+
+    // Set event attributes
+    e->protocol = PROTOCOL_BUNTDB;
+    e->pid = tgid;
+    e->start_at = op_ctx->start_time;
+    e->end_at = op_ctx->end_time;
+    e->duration = e->end_at - e->start_at;
+    
+    // Use op_ctx->key as payload
+    __u64 payload_size = op_ctx->key_size > 0 ? op_ctx->key_size : 0;
+    if (payload_size > 1024) {
+        payload_size = 1024;
+    }
+    e->payload_size = payload_size;
+    
+    if (payload_size > 0) {
+        COPY_PAYLOAD(e->payload, payload_size, op_ctx->key);
+        // Log SQL-like message
+        cw_bpf_debug("[buntdb] SQL: Delete: %.*s", (int)payload_size, op_ctx->key);
+    }
+
+    // Get trace info
+    struct apm_trace_key_t trace_key = get_apm_trace_key(120 * NS_PER_SEC, true);
+    struct apm_trace_info_t *trace_info = get_apm_trace_info_by_trace_key(trace_key);
+    
+    if (trace_info == 0) {
+        trace_info = get_apm_trace_info_v3(trace_key, pid_tgid, tgid, pid_tgid);
+    }
+    
+    if (trace_info) {
+        e->trace_id = trace_info->trace_id;
+    }
+    
+    if (e->trace_id == 0) {
+        e->trace_id = get_apm_trace_id(tgid, pid_tgid);
+    }
+
+    // Set error status
+    if (has_error) {
+        e->status = STATUS_FAILED;
+    } else {
+        e->status = STATUS_OK;
+    }
+
+    // Emit event (one span per Delete)
+    SET_TRACE_METHOD(e);
+    long error = bpf_perf_event_output(ctx, &l7_events, BPF_F_CURRENT_CPU, e, sizeof(*e));
+    if (error == 0) {
+        cw_add_event_count(e->trace_id);
+        cw_bpf_debug("[buntdb] Delete Returns: success, trace_id=%llu, key=%.*s", e->trace_id, (int)op_ctx->key_size, op_ctx->key);
+    }
+    
+    // Cleanup
+    bpf_map_delete_elem(&buntdb_op_events, &key);
+    return 0;
+}

+ 4 - 2
ebpftracer/ebpf/utrace/go/include/go_types.h

@@ -214,14 +214,16 @@ static __always_inline bool get_go_string_from_user_ptr(void *user_str_ptr, char
 
 
     struct go_string_ot user_str = {0};
     struct go_string_ot user_str = {0};
     long success = 0;
     long success = 0;
-    success = bpf_probe_read(&user_str, sizeof(struct go_string_ot), user_str_ptr);
+    // 读取 go_string_ot 结构体(也在用户空间)
+    success = bpf_probe_read_user(&user_str, sizeof(struct go_string_ot), user_str_ptr);
     if (success != 0 || user_str.len < 1)
     if (success != 0 || user_str.len < 1)
     {
     {
         return false;
         return false;
     }
     }
 
 
     u64 size_to_read = user_str.len > max_len ? max_len : user_str.len;
     u64 size_to_read = user_str.len > max_len ? max_len : user_str.len;
-    success = bpf_probe_read(dst, size_to_read, user_str.str);
+    // 读取字符串内容(用户空间指针,使用 bpf_probe_read_user)
+    success = bpf_probe_read_user(dst, size_to_read, user_str.str);
     if (success != 0)
     if (success != 0)
     {
     {
         return false;
         return false;

+ 5 - 0
ebpftracer/l7/l7.go

@@ -30,6 +30,7 @@ const (
 	ProtocolGrpc      Protocol = 16
 	ProtocolGrpc      Protocol = 16
 	ProtocolES        Protocol = 17
 	ProtocolES        Protocol = 17
 	ProtocolTiDB      Protocol = 18
 	ProtocolTiDB      Protocol = 18
+	ProtocolBuntDB    Protocol = 19
 )
 )
 
 
 func (p Protocol) Int() int {
 func (p Protocol) Int() int {
@@ -74,6 +75,8 @@ func (p Protocol) String() string {
 		return "TiDB"
 		return "TiDB"
 	case ProtocolGrpc:
 	case ProtocolGrpc:
 		return "GRPC"
 		return "GRPC"
+	case ProtocolBuntDB:
+		return "BuntDB"
 	case ProtocolES:
 	case ProtocolES:
 		return "Elasticsearch"
 		return "Elasticsearch"
 	}
 	}
@@ -118,6 +121,8 @@ func (p Protocol) ServiceNameString() string {
 		return "TIDB"
 		return "TIDB"
 	case ProtocolGrpc:
 	case ProtocolGrpc:
 		return "GRPC"
 		return "GRPC"
+	case ProtocolBuntDB:
+		return "BUNTDB"
 	case ProtocolES:
 	case ProtocolES:
 		return "ELASTICSEARCH"
 		return "ELASTICSEARCH"
 	}
 	}

+ 42 - 0
ebpftracer/tls.go

@@ -45,6 +45,9 @@ const (
 	goGocqlSessionExecuteQueryV2   = "github.com/apache/cassandra-gocql-driver/v2.(*Session).executeQuery"
 	goGocqlSessionExecuteQueryV2   = "github.com/apache/cassandra-gocql-driver/v2.(*Session).executeQuery"
 	goKafkaWriterWriteMessages     = "github.com/segmentio/kafka-go.(*Writer).WriteMessages"
 	goKafkaWriterWriteMessages     = "github.com/segmentio/kafka-go.(*Writer).WriteMessages"
 	goKafkaReaderFetchMessage      = "github.com/segmentio/kafka-go.(*Reader).FetchMessage"
 	goKafkaReaderFetchMessage      = "github.com/segmentio/kafka-go.(*Reader).FetchMessage"
+	goBuntdbTxSet                   = "github.com/tidwall/buntdb.(*Tx).Set"
+	goBuntdbTxGet                   = "github.com/tidwall/buntdb.(*Tx).Get"
+	goBuntdbTxDelete                = "github.com/tidwall/buntdb.(*Tx).Delete"
 )
 )
 
 
 var (
 var (
@@ -618,6 +621,15 @@ func (t *Tracer) AttachGoTlsUprobes(pid uint32, appInfo *AppInfo, codeType uint1
 		case goKafkaReaderFetchMessage:
 		case goKafkaReaderFetchMessage:
 			matchedSymbols++
 			matchedSymbols++
 			klog.Infof("[AttachGoTlsUprobes] STEP 19.14: Matched kafka Reader.FetchMessage symbol (index=%d)", i)
 			klog.Infof("[AttachGoTlsUprobes] STEP 19.14: Matched kafka Reader.FetchMessage symbol (index=%d)", i)
+		case goBuntdbTxSet:
+			matchedSymbols++
+			klog.Infof("[AttachGoTlsUprobes] STEP 19.17: Matched buntdb Tx.Set symbol (index=%d)", i)
+		case goBuntdbTxGet:
+			matchedSymbols++
+			klog.Infof("[AttachGoTlsUprobes] STEP 19.18: Matched buntdb Tx.Get symbol (index=%d)", i)
+		case goBuntdbTxDelete:
+			matchedSymbols++
+			klog.Infof("[AttachGoTlsUprobes] STEP 19.19: Matched buntdb Tx.Delete symbol (index=%d)", i)
 		case goReadContinuedLineSlice:
 		case goReadContinuedLineSlice:
 		default:
 		default:
 			continue
 			continue
@@ -823,6 +835,36 @@ func (t *Tracer) AttachGoTlsUprobes(pid uint32, appInfo *AppInfo, codeType uint1
 				klog.Infoln("uprobe_FetchMessage ok")
 				klog.Infoln("uprobe_FetchMessage ok")
 				links = append(links, retLinks...)
 				links = append(links, retLinks...)
 			}
 			}
+		case goBuntdbTxSet:
+			// Tx.Set 需要入口和返回探针,每个操作生成独立的 span
+			retLinks, err := attachUprobeWithReturns(exe, s.Name, "uprobe_Tx_Set", "uprobe_Tx_Set_Returns", t.uprobes, address, s, textSection, textSectionLen, textSectionData, ef.Machine, "failed to attach uprobe_Tx_Set uprobe", true)
+			if err != nil {
+				return nil, err
+			}
+			if retLinks != nil {
+				klog.Infoln("uprobe_Tx_Set ok")
+				links = append(links, retLinks...)
+			}
+		case goBuntdbTxGet:
+			// Tx.Get 需要入口和返回探针,每个操作生成独立的 span
+			retLinks, err := attachUprobeWithReturns(exe, s.Name, "uprobe_Tx_Get", "uprobe_Tx_Get_Returns", t.uprobes, address, s, textSection, textSectionLen, textSectionData, ef.Machine, "failed to attach uprobe_Tx_Get uprobe", true)
+			if err != nil {
+				return nil, err
+			}
+			if retLinks != nil {
+				klog.Infoln("uprobe_Tx_Get ok")
+				links = append(links, retLinks...)
+			}
+		case goBuntdbTxDelete:
+			// Tx.Delete 需要入口和返回探针,每个操作生成独立的 span
+			retLinks, err := attachUprobeWithReturns(exe, s.Name, "uprobe_Tx_Delete", "uprobe_Tx_Delete_Returns", t.uprobes, address, s, textSection, textSectionLen, textSectionData, ef.Machine, "failed to attach uprobe_Tx_Delete uprobe", true)
+			if err != nil {
+				return nil, err
+			}
+			if retLinks != nil {
+				klog.Infoln("uprobe_Tx_Delete ok")
+				links = append(links, retLinks...)
+			}
 		}
 		}
 	}
 	}
 	klog.Infof("[AttachGoTlsUprobes] Symbol processing completed, matched symbols=%d, total links=%d", matchedSymbols, len(links))
 	klog.Infof("[AttachGoTlsUprobes] Symbol processing completed, matched symbols=%d, total links=%d", matchedSymbols, len(links))

+ 1 - 1
pkg/go.opentelemetry.io/otel/exporters/otlp/otlptrace/apm_exporter.go

@@ -198,7 +198,7 @@ func tracetransformData(sdl []tracesdk.ReadOnlySpan) map[int][]RootDataT {
 					buildDNSMapEvent(&mNode, event)
 					buildDNSMapEvent(&mNode, event)
 				case l7.ProtocolMysql, l7.ProtocolMariaDB, l7.ProtocolTiDB, l7.ProtocolPostgres, l7.ProtocolDM:
 				case l7.ProtocolMysql, l7.ProtocolMariaDB, l7.ProtocolTiDB, l7.ProtocolPostgres, l7.ProtocolDM:
 					buildSQLMapEvent(&mNode, event)
 					buildSQLMapEvent(&mNode, event)
-				case l7.ProtocolRedis, l7.ProtocolMemcached, l7.ProtocolCassandra, l7.ProtocolES:
+				case l7.ProtocolRedis, l7.ProtocolMemcached, l7.ProtocolCassandra, l7.ProtocolES, l7.ProtocolBuntDB:
 					buildNoSqlMapEvent(&mNode, event)
 					buildNoSqlMapEvent(&mNode, event)
 				case l7.ProtocolMongo:
 				case l7.ProtocolMongo:
 					buildMongoMapEvent(&mNode, event)
 					buildMongoMapEvent(&mNode, event)