使用ebpf 监控golang 应用

news2025/2/26 1:30:37

一、背景

使用ebpf 监控grpc-go的应用,grpc-go http2 client的处理点


func (l *loopyWriter) writeHeader(streamID uint32, endStream bool, hf []hpack.HeaderField, onWrite func()) error {
......
}

// operateHeaders takes action on the decoded headers.
func (t *http2Client) operateHeaders(frame *http2.MetaHeadersFrame) {
}

使用ebpf 监控埋点:

SEC("uprobe/google.golang.org/grpc/internal/transport.(*loopyWriter).writeHeader")
int uprobe__probe_http2_client_operate_headers(struct pt_regs* ctx) {
}

运行流程:

 

我们在 uprobe__probe_http2_client_operate_headers 里做一些处理,统计grpc的数据,进行遥测。编写代码过程中我们需要关注的一些事情

1、golang 程序中 t *http2Client 和  frame *http2.MetaHeadersFrame 在寄存器中的位置是如何分配的?

type MetaHeadersFrame struct {
	*HeadersFrame

	// Fields are the fields contained in the HEADERS and
	// CONTINUATION frames. The underlying slice is owned by the
	// Framer and must not be retained after the next call to
	// ReadFrame.
	//
	// Fields are guaranteed to be in the correct http2 order and
	// not have unknown pseudo header fields or invalid header
	// field names or values. Required pseudo header fields may be
	// missing, however. Use the MetaHeadersFrame.Pseudo accessor
	// method access pseudo headers.
	Fields []hpack.HeaderField

	// Truncated is whether the max header list size limit was hit
	// and Fields is incomplete. The hpack decoder state is still
	// valid, however.
	Truncated bool
}

2、http2Client 和 MetaHeadersFrame 结构体中各个成员的偏移量如何确定?

type http2Client struct {
	lastRead  int64 // Keep this field 64-bit aligned. Accessed atomically.
	ctx       context.Context
	cancel    context.CancelFunc
	ctxDone   <-chan struct{} // Cache the ctx.Done() chan.
	userAgent string
	// address contains the resolver returned address for this transport.
	// If the `ServerName` field is set, it takes precedence over `CallHdr.Host`
	// passed to `NewStream`, when determining the :authority header.
	address    resolver.Address
	md         metadata.MD
.....
}

 以 MetaHeadersFrame 为例子

 我们在开发中需要找到 正确的offset 才能正确的取到内存。

二、我们需要做什么?

1、如何确定参数在寄存器中的位置?

我们使用llvm-dwarfdump-14 查看二进制中dwarf 构成:

llvm-dwarfdump-14 grpc-client |grep "loopyWriter).writeHeader" -C 100

返回格式:

0x0018c375:   DW_TAG_subprogram
                DW_AT_name	("google.golang.org/grpc/internal/transport.(*loopyWriter).writeHeader")
                DW_AT_low_pc	(0x00000000007139a0)
                DW_AT_high_pc	(0x0000000000713df2)
                DW_AT_frame_base	(DW_OP_call_frame_cfa)
                DW_AT_decl_file	("/home/zhanglei/data/grpc-demo/vendor/google.golang.org/grpc/internal/transport/controlbuf.go")
                DW_AT_external	(0x01)

0x0018c3d2:     DW_TAG_formal_parameter
                  DW_AT_name	("l")
                  DW_AT_variable_parameter	(0x00)
                  DW_AT_decl_line	(677)
                  DW_AT_type	(0x00000000000e6bc2 "google.golang.org/grpc/internal/transport.loopyWriter *")
                  DW_AT_location	(0x001a603f: 
                     [0x00000000007139a0, 0x00000000007139fc): DW_OP_reg0 RAX
                     [0x00000000007139fc, 0x0000000000713df2): DW_OP_call_frame_cfa)

0x0018c3e0:     DW_TAG_formal_parameter
                  DW_AT_name	("streamID")
                  DW_AT_variable_parameter	(0x00)
                  DW_AT_decl_line	(677)
                  DW_AT_type	(0x0000000000082ad4 "uint32")
                  DW_AT_location	(0x001a6085: 
                     [0x00000000007139a0, 0x0000000000713a02): DW_OP_reg3 RBX
                     [0x0000000000713a02, 0x0000000000713df2): DW_OP_fbreg +8)

0x0018c3f5:     DW_TAG_formal_parameter
                  DW_AT_name	("endStream")
                  DW_AT_variable_parameter	(0x00)
                  DW_AT_decl_line	(677)
                  DW_AT_type	(0x0000000000082b87 "bool")
                  DW_AT_location	(0x001a60cc: 
                     [0x00000000007139a0, 0x0000000000713a02): DW_OP_reg2 RCX
                     [0x0000000000713a02, 0x0000000000713df2): DW_OP_fbreg +12)

0x0018c40b:     DW_TAG_formal_parameter
                  DW_AT_name	("hf")
                  DW_AT_variable_parameter	(0x00)
                  DW_AT_decl_line	(677)
                  DW_AT_type	(0x00000000000e2826 "[]golang.org/x/net/http2/hpack.HeaderField")
                  DW_AT_location	(0x001a6113: 
                     [0x00000000007139a0, 0x0000000000713a02): DW_OP_reg5 RDI, DW_OP_piece 0x8, DW_OP_reg4 RSI, DW_OP_piece 0x8, DW_OP_reg8 R8, DW_OP_piece 0x8
                     [0x0000000000713a02, 0x0000000000713a4e): DW_OP_fbreg +16, DW_OP_piece 0x8, DW_OP_fbreg +24, DW_OP_piece 0x8, DW_OP_piece 0x8
                     [0x0000000000713a4e, 0x0000000000713df2): DW_OP_piece 0x8, DW_OP_fbreg +24, DW_OP_piece 0x8, DW_OP_piece 0x8)

0x0018c41a:     DW_TAG_formal_parameter
                  DW_AT_name	("onWrite")
                  DW_AT_variable_parameter	(0x00)
                  DW_AT_decl_line	(677)
                  DW_AT_type	(0x00000000000844f2 "func()")
                  DW_AT_location	(0x001a6184: 
                     [0x00000000007139a0, 0x0000000000713a02): DW_OP_reg9 R9)

 

数据格式简介:

我们可以找到func (l *loopyWriter) writeHeader 的参数的所在寄存器的位置

DW_AT_name	("l")  的 [0x00000000007139a0, 0x00000000007139fc): DW_OP_reg0 RAX 在寄存器0 上
DW_AT_name	("streamID") : DW_OP_reg3 RBX 在寄存器3上
DW_AT_name	("endStream")  DW_OP_reg2 RCX 在寄存器2上
DW_AT_name	("hf") 在寄存器5 上 DW_OP_reg5 RDI, DW_OP_piece 0x8, DW_OP_reg4 RSI, DW_OP_piece 0x8, DW_OP_reg8 R8, DW_OP_piece 0x8
DW_AT_name	("onWrite")  DW_OP_reg9 R9 在寄存器9上

2、如何定位结构体中的成员

0x000e6c07:   DW_TAG_structure_type
                DW_AT_name	("google.golang.org/grpc/internal/transport.loopyWriter")
                DW_AT_byte_size	(88)
                DW_AT_GO_kind	(0x19)
                DW_AT_GO_runtime_type	(0x00000000000b9a20)

0x000e6c48:     DW_TAG_member
                  DW_AT_name	("side")
                  DW_AT_data_member_location	(0)
                  DW_AT_type	(0x00000000000e5e96 "google.golang.org/grpc/internal/transport.side")
                  DW_AT_GO_embedded_field	(0x00)

0x000e6c54:     DW_TAG_member
                  DW_AT_name	("cbuf")
                  DW_AT_data_member_location	(8)
                  DW_AT_type	(0x00000000000e681d "google.golang.org/grpc/internal/transport.controlBuffer *")
                  DW_AT_GO_embedded_field	(0x00)

0x000e6c60:     DW_TAG_member
                  DW_AT_name	("sendQuota")
                  DW_AT_data_member_location	(16)
                  DW_AT_type	(0x0000000000082ad4 "uint32")
                  DW_AT_GO_embedded_field	(0x00)

0x000e6c71:     DW_TAG_member
                  DW_AT_name	("oiws")
                  DW_AT_data_member_location	(20)
                  DW_AT_type	(0x0000000000082ad4 "uint32")
                  DW_AT_GO_embedded_field	(0x00)

0x000e6c7d:     DW_TAG_member
                  DW_AT_name	("estdStreams")
                  DW_AT_data_member_location	(24)
                  DW_AT_type	(0x00000000000e6d3c "map[uint32]*google.golang.org/grpc/internal/transport.outStream")
                  DW_AT_GO_embedded_field	(0x00)

0x000e6c90:     DW_TAG_member
                  DW_AT_name	("activeStreams")
                  DW_AT_data_member_location	(32)
                  DW_AT_type	(0x00000000000e6eaa "google.golang.org/grpc/internal/transport.outStreamList *")
                  DW_AT_GO_embedded_field	(0x00)

0x000e6ca5:     DW_TAG_member
                  DW_AT_name	("framer")
                  DW_AT_data_member_location	(40)
                  DW_AT_type	(0x00000000000e6f8a "google.golang.org/grpc/internal/transport.framer *")
                  DW_AT_GO_embedded_field	(0x00)

 

 以 loopyWriter 结构体为例子:

type loopyWriter struct {
	side      side
	cbuf      *controlBuffer
	sendQuota uint32
	oiws      uint32 // outbound initial window size.
	// estdStreams is map of all established streams that are not cleaned-up yet.
	// On client-side, this is all streams whose headers were sent out.
	// On server-side, this is all streams whose headers were received.
	estdStreams map[uint32]*outStream // Established streams.
	// activeStreams is a linked-list of all streams that have data to send and some
	// stream-level flow control quota.
	// Each of these streams internally have a list of data items(and perhaps trailers
	// on the server-side) to be sent out.
	activeStreams *outStreamList
	framer        *framer
	hBuf          *bytes.Buffer  // The buffer for HPACK encoding.
	hEnc          *hpack.Encoder // HPACK encoder.
	bdpEst        *bdpEstimator
	draining      bool

	// Side-specific handlers
	ssGoAwayHandler func(*goAway) (bool, error)
}

side 的偏移量是  0 

cbuf 是 8 

sendQuota 是 16 

oiws 是 20

estdStreams 是 24

activeStreams 是 32 

framer 是 40

3、如何确定接口类型地址

nm grpc-client|grep "TCPConn,net.Conn"

000000000093d280 R go.itab.*net.TCPConn,net.Conn

 发现对应的符号表地址:

nm grpc-server|grep "TCPConn,net.Conn"
000000000093bfc0 R go.itab.*net.TCPConn,net.Conn

4、使用gdb 证明这些数据

(gdb) i args
l = 0xc00011c060
streamID = 1
endStream = false
hf = {array = 0xc00007e870, len = 2, cap = 2}
onWrite = {void (void)} 0x0
~r0 = <optimised out>
(gdb) i r
rax            0xc00011c060        824634884192
rbx            0x1                 1
rcx            0x0                 0
rdx            0xc0001b29c0        824635500992
rsi            0x2                 2
rdi            0xc00007e870        824634239088
rbp            0xc000129ec8        0xc000129ec8
rsp            0xc000129e78        0xc000129e78
r8             0x2                 2
r9             0xc000021f80        824633859968
r10            0xc00007e8c0        824634239168
r11            0x1                 1
r12            0x1                 1
r13            0xffffffffffffffff  -1
r14            0xc00019d520        824635413792
r15            0x0                 0
rip            0x7160c0            0x7160c0 <google.golang.org/grpc/internal/transport.(*loopyWriter).writeHeader>
eflags         0x246               [ PF ZF IF ]
cs             0x33                51
ss             0x2b                43
ds             0x0                 0
es             0x0                 0
fs             0x0                 0
gs             0x0                 0

发现l 位于寄存器 

l -> rax

streamID -> rbx

endStream -> rcx

hf -> rdi

接口类型:

(gdb) p (*l.framer.writer).conn
$8 = {tab = 0x93bfc0 <TCPConn,net.Conn>, data = 0xc000010290}

 发现对应的符号表地址:

nm grpc-server|grep "TCPConn,net.Conn"
000000000093bfc0 R go.itab.*net.TCPConn,net.Conn

三、发现golang应用

uprober 包含了 多个rule

每个rule 又包含Selector和 Checker

Selector 用来根据版本选择对应的Hooker

Checker 用来检查 动态库或者二进制文件是否满足匹配规则

 

运行流程:

 

四、golang 注册ebpf 钩子

 

五.使用偏移量获取golang变量

定义 location 结构体:

typedef struct {
    __s64 stack_offset; // 栈上的偏移量
    __s64 _register; // 寄存器的偏移量
    __u8 in_register; //是否在寄存器上
    __u8 exists;
} location_t;

// golang 切片
typedef struct {
    location_t ptr;
    location_t len;
    location_t cap;
} slice_location_t;

 

golang 参数变量在寄存器的布局:

// This function was adapted from https://github.com/go-delve/delve:
// - https://github.com/go-delve/delve/blob/cd9e6c02a6ca5f0d66c1f770ee10a0d8f4419333/pkg/proc/internal/ebpf/bpf/trace.bpf.c#L43
// which is licensed under MIT.
static __always_inline int read_register(struct pt_regs* ctx, int64_t regnum, void* dest) {
    // This volatile temporary variable is need when building with clang-14,
    // or the verifier will complain that we dereference a modified context
    // pointer.
    //
    // What happened in this case, is that the compiler tried to be smart by
    // incrementing the context pointer, before jumping to code that will
    // copy the value pointed to by the new pointer to `dest`. The generated
    // code looked like this:
    //
    //      r1 += 40           // Increment the ptr
    //      goto +3 <LBB0_9>   // goto __builtin_memcpy
    //
    // What the memcpy does is deference the resulting pointer to get the
    // CPU register value (that’s where the bug was), then put it in the
    // dest location:
    //
    //      r1 = *(u64 *)(r1 + 0)  // BUG: Get the register value.
    //                             // This is the "modified context pointer"
    //      *(u64 *)(r3 + 0) = r1  // Put it in dest
    //
    // By incrementing the pointer before dereferencing it, the verifier no
    // longer considering r1 to be a pointer to the context, but as a
    // pointer to some random memory address (even though it is in the
    // memory the range of the context struct).
    //
    // What we want the compiler to generate is something like this:
    //
    //      // Switch branch:
    //      r1 = *(u64 *)(r1 + 40) // read value to tmp var
    //      goto +30 <LBB0_39>     // goto *dest = tmp
    //
    //      // *dest = tmp
    //      *(u64 *)(r3 + 0) = r1
    //
    // This volatile `tmp` variable makes the compiler generate the code above.
    volatile u64 tmp = 0;
    switch (regnum) {
        case 0: // RAX
            tmp = ctx->ax;
            break;
        case 1: // RDX
            tmp = ctx->dx;
            break;
        case 2: // RCX
            tmp = ctx->cx;
            break;
        case 3: // RBX
            tmp = ctx->bx;
            break;
        case 4: // RSI
            tmp = ctx->si;
            break;
        case 5: // RDI
            tmp = ctx->di;
            break;
        case 6: // RBP
            tmp = ctx->bp;
            break;
        case 7: // RSP
            tmp = ctx->sp;
            break;
        case 8: // R8
            tmp = ctx->r8;
            break;
        case 9: // R9
            tmp = ctx->r9;
            break;
        case 10: // R10
            tmp = ctx->r10;
            break;
        case 11: // R11
            tmp = ctx->r11;
            break;
        case 12: // R12
            tmp = ctx->r12;
            break;
        case 13: // R13
            tmp = ctx->r13;
            break;
        case 14: // R14
            tmp = ctx->r14;
            break;
        case 15: // R15
            tmp = ctx->r15;
            break;
        default:
                  return 1;
    }
    *(u64*)dest = tmp;
    return 0;
}

 读取偏移量:

static __always_inline int read_location(struct pt_regs* ctx, location_t* loc, size_t size, void* dest) {
    if (!loc->exists) {
        return 0;
    }

    if (loc->in_register) {
        if (size != REG_SIZE) {
            return 1;
        }

        return read_register(ctx, loc->_register, dest);
    } else {
        return read_stack(ctx, loc->stack_offset, size, dest);
    }
}
static __always_inline int read_stack(struct pt_regs* ctx, int64_t stack_offset, size_t size, void* dest) {
    // `ctx->sp` is correct for both x86_64 and ARM64
    uintptr_t stack_pointer = (uintptr_t) ctx->sp;
    uintptr_t address = stack_pointer + stack_offset;
    return bpf_probe_read_user(dest, size, (void*) address);
}

举一个例子:

SEC("uprobe/google.golang.org/grpc/internal/transport.(*loopyWriter).writeHeader")
int uprobe__probe_loopy_writer_write_header(struct pt_regs* ctx) {
    return 0;
}

 从map读取偏移量:

uint32_t pid = bpf_get_current_pid_tgid() >> 32;
  go_http2_symaddrs_t* symaddrs = bpf_map_lookup_elem(&http2_symaddrs_map, &pid);
  if (symaddrs == NULL) {
    log_trace("uprobe__probe_loopy_writer_write_header:writeHeader:symaddrs is NULL\n");
    return 0;
  }

 偏移量存储结构体:

typedef struct {
  // ---- itable symbols ----

  // io.Writer interface types.
  __s64 http_http2bufferedWriter;  // "go.itab.*net/http.http2bufferedWriter,io.Writer
  __s64 transport_bufWriter;  // "google.golang.org/grpc/internal/transport.bufWriter,io.Writer

  // ---- function argument locations ----

  // Arguments of net/http.(*http2Framer).WriteDataPadded.
  location_t http2Framer_WriteDataPadded_f_loc;          // 8
  location_t http2Framer_WriteDataPadded_streamID_loc;   // 16
  location_t http2Framer_WriteDataPadded_endStream_loc;  // 20
  location_t http2Framer_WriteDataPadded_data_ptr_loc;   // 24
  location_t http2Framer_WriteDataPadded_data_len_loc;   // 32

  // Arguments of golang.org/x/net/http2.(*Framer).WriteDataPadded.
  location_t http2_WriteDataPadded_f_loc;          // 8
  location_t http2_WriteDataPadded_streamID_loc;   // 16
  location_t http2_WriteDataPadded_endStream_loc;  // 20
  location_t http2_WriteDataPadded_data_ptr_loc;   // 24
  location_t http2_WriteDataPadded_data_len_loc;   // 32

  // Arguments of net/http.(*http2Framer).checkFrameOrder.
  location_t http2Framer_checkFrameOrder_fr_loc;  // 8
  location_t http2Framer_checkFrameOrder_f_loc;   // 16

  // Arguments of golang.org/x/net/http2.(*Framer).checkFrameOrder.
  location_t http2_checkFrameOrder_fr_loc;  // 8
  location_t http2_checkFrameOrder_f_loc;   // 16

  // Arguments of net/http.(*http2writeResHeaders).writeFrame.
  location_t writeFrame_w_loc;    // 8
  location_t writeFrame_ctx_loc;  // 16

  // Arguments of golang.org/x/net/http2/hpack.(*Encoder).WriteField.
  location_t WriteField_e_loc;  // 8
  // Note that the HeaderField `f` is further broken down to its name and value members.
  // This is done so we can better control the location of these members from user-space.
  // In theory, there could be an ABI that splits these two members across stack and registers.
  location_t WriteField_f_name_loc;   // 16
  location_t WriteField_f_value_loc;  // 32

  // Arguments of net/http.(*http2serverConn).processHeaders.
  location_t processHeaders_sc_loc;  // 8
  location_t processHeaders_f_loc;   // 16

  // Arguments of google.golang.org/grpc/internal/transport.(*http2Server).operateHeaders.
  location_t http2Server_operateHeaders_t_loc;      // 8
  location_t http2Server_operateHeaders_frame_loc;  // 16

  // Arguments of google.golang.org/grpc/internal/transport.(*http2Client).operateHeaders.
  location_t http2Client_operateHeaders_t_loc;      // 8
  location_t http2Client_operateHeaders_frame_loc;  // 16

  // Arguments of google.golang.org/grpc/internal/transport.(*loopyWriter).writeHeader.
  location_t writeHeader_l_loc;          // 8
  location_t writeHeader_streamID_loc;   // 16
  location_t writeHeader_endStream_loc;  // 20
  slice_location_t writeHeader_hf_loc;     // 24

  // ---- struct member offsets ----

  // Struct member offsets.
  // Naming maintains golang style: <struct>_<member>_offset
  // Note: values in comments represent known offsets, in case we need to fall back.
  //       Eventually, they should be removed, because they are not reliable.

  // Members of golang.org/x/net/http2/hpack.HeaderField.
  int32_t HeaderField_Name_offset;   // 0
  int32_t HeaderField_Value_offset;  // 16

  // Members of google.golang.org/grpc/internal/transport.http2Server.
  int32_t http2Server_conn_offset;  // 16 or 24

  // Members of google.golang.org/grpc/internal/transport.http2Client.
  int32_t http2Client_conn_offset;  // 64

  // Members of google.golang.org/grpc/internal/transport.loopyWriter.
  int32_t loopyWriter_framer_offset;  // 40

  // Members of golang.org/x/net/net/http2.Framer.
  int32_t Framer_w_offset;  // 112

  // Members of golang.org/x/net/http2.MetaHeadersFrame.
  int32_t MetaHeadersFrame_HeadersFrame_offset;  // 0
  int32_t MetaHeadersFrame_Fields_offset;        // 0

  // Members of golang.org/x/net/http2.HeadersFrame.
  int32_t HeadersFrame_FrameHeader_offset;  // 0

  // Members of golang.org/x/net/http2.FrameHeader.
  int32_t FrameHeader_Type_offset;      // 1
  int32_t FrameHeader_Flags_offset;     // 2
  int32_t FrameHeader_StreamID_offset;  // 8

  // Members of golang.org/x/net/http2.DataFrame.
  int32_t DataFrame_data_offset;  // 16

  // Members of google.golang.org/grpc/internal/transport.bufWriter.
  int32_t bufWriter_conn_offset;  // 40

  // Members of net/http.http2serverConn.
  int32_t http2serverConn_conn_offset;          // 16
  int32_t http2serverConn_hpackEncoder_offset;  // 360

  // Members of net/http.http2HeadersFrame
  int32_t http2HeadersFrame_http2FrameHeader_offset;  // 0

  // Members of net/http.http2FrameHeader.
  int32_t http2FrameHeader_Type_offset;      // 1
  int32_t http2FrameHeader_Flags_offset;     // 2
  int32_t http2FrameHeader_StreamID_offset;  // 8

  // Members of golang.org/x/net/http2.DataFrame.
  int32_t http2DataFrame_data_offset;  // 16

  // Members of net/http.http2writeResHeaders.
  int32_t http2writeResHeaders_streamID_offset;   // 0
  int32_t http2writeResHeaders_endStream_offset;  // 48

  // Members of net/http.http2MetaHeadersFrame.
  int32_t http2MetaHeadersFrame_http2HeadersFrame_offset;  // 0
  int32_t http2MetaHeadersFrame_Fields_offset;             // 8

  // Members of net/http.http2Framer.
  int32_t http2Framer_w_offset;  // 112

  // Members of net/http.http2bufferedWriter
  int32_t http2bufferedWriter_w_offset;  // 0
} go_http2_symaddrs_t;

读取偏移量:

  void* loopy_writer_ptr = NULL;
  if (read_location(ctx, &symaddrs->writeHeader_l_loc,
  sizeof(loopy_writer_ptr), &loopy_writer_ptr)) {
     log_trace("uprobe__probe_loopy_writer_write_header:5\n");
     return 0;
  }

 

六、如何记录错误调试

1、打日志调试

#define log_trace(fmt, ...)                                        \
    ({                                                             \
        char ____fmt[] = fmt;                                      \
        bpf_trace_printk(____fmt, sizeof(____fmt), ##__VA_ARGS__); \
    })

/* Macro to output debug logs to /sys/kernel/debug/tracing/trace_pipe
 */
#if DEBUG == 1
#define log_debug(fmt, ...)                                        \
    ({                                                             \
        char ____fmt[] = fmt;                                      \
        bpf_trace_printk(____fmt, sizeof(____fmt), ##__VA_ARGS__); \
    })
#else

使用:

log_debug("[grpc-c:lookup_version:]grpc-c version is not support;pid:%d;version:%d;\n", pid, *version);

 

2、记录metrics

主要记录行号和文件

#ifndef TRACER_TELEMETRY
#define TRACER_TELEMETRY

#ifndef TRACER_TELEMETRY_KEY_LIMIT
#define TRACER_TELEMETRY_KEY_LIMIT 4096
#endif

typedef struct {
    char file[TRACER_TELEMETRY_KEY_LIMIT];
    uint64_t line;
} tracer_telemetry_key;

BPF_HASH_MAP(tracer_telemetry, tracer_telemetry_key, uint64_t, 1024);
BPF_PERCPU_ARRAY_MAP(tracer_telemetry_heap, __u32, tracer_telemetry_key, 1);

static __always_inline tracer_telemetry_key* alloc_tracer_telemetry_key() {
    uint32_t kZero = 0;
    tracer_telemetry_key* value = bpf_map_lookup_elem(&tracer_telemetry_heap, &kZero);
    if (value == NULL) {
        return NULL;
    }

    value->line = 0;
    return value;
}

static __always_inline void increment_tracer_telemetry_count(const char* file, uint64_t line) {
    tracer_telemetry_key* key = alloc_tracer_telemetry_key();
    if (key == NULL) {
        return;
    }

    if (bpf_probe_read_str(&key->file, sizeof(key->file), file) == -1) {
        return;
    }

    key->line = line;

    uint64_t *val = NULL;
    val = bpf_map_lookup_elem(&tracer_telemetry, key);
    if (val == NULL) {
        log_debug("tracer:key->file:%s;file:%s\n", key->file, file);
        uint64_t tmp_value = 0;
        bpf_map_update_with_telemetry(tracer_telemetry, key, &tmp_value, BPF_NOEXIST);
        return;
    }

    val = bpf_map_lookup_elem(&tracer_telemetry, key);
    if (val == NULL) {
        return;
    }

    __sync_fetch_and_add(val, 1);
}

#ifndef INCR_TRACER_COUNT
#define  INCR_TRACER_COUNT  increment_tracer_telemetry_count(__FILE__, __LINE__)
#endif

#endif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/427278.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大数据 | Hadoop集群搭建(完全分布式)

知识目录一、前言二、配置三台虚拟机2.1 克隆三台虚拟机2.2 配置克隆的虚拟机2.3 使用Xshell连接虚拟机2.4 配置SSH免密登录三、Hadoop集群准备3.1 安装 rsync3.2 安装xsync分发脚本3.3 安装JDK和安装Hadoop3.4 配置环境变量3.5 分发四、Hadoop集群搭建4.1 修改配置文件4.2 配置…

Python数据分析案例24——基于深度学习的锂电池寿命预测

本期开始案例较为硬核起来了&#xff0c;适合理工科的硕士&#xff0c;人文社科的同学可以看前面的案例。 案例背景 这篇文章是去年就发了&#xff0c;刊物也印刷了&#xff0c;现在分享一部分代码作为案例给需要的同学。 原文链接&#xff08;知网文章 C核&#xff09;&…

OSPF的优化

O_ASE --- 标志域外路由信息 --- 因为域外的路由信息不可控性较强&#xff0c;所以&#xff0c;信任程度较低&#xff0c;我们将其优先级设置为150。 LSA --- 链路状态通告 --- OSPF协议在不同网络环境下产生的用于携带和传递不同的信息。 LSDB --- 链路状态数据库 SPF --- 最短…

【数据分析实战】基于python对Airbnb房源进行数据分析

文章目录&#x1f4da;引言&#x1f4d6;数据加载以及基本观察&#x1f4c3;缺失值观察及处理&#x1f516;缺失值观察以及可视化&#x1f516;缺失值处理&#x1f4c3;异常值观察及处理&#x1f4d6;数据探索&#x1f4a1;哪个区域的房源最受欢迎&#xff1f;&#x1f4a1;哪种…

基于opencv的边缘检测方法

1、梯度运算 用OpenCV的形态变换&#xff08; 膨胀、腐蚀、开运算和闭运算&#xff09;函数morphologyEx 梯度运算即膨胀结果-腐蚀结果&#xff1a; 【注意】对于二值图像来说&#xff0c;必须是前景图像为白色&#xff0c;背景为黑色&#xff0c;否则需要进行反二值化处理 …

Mybatis【第一个 Mybatis 程序】

目录 一、Maven 环境配置 1、配置 pom.xml 1.1、依赖的 jar包 1.2、防止资源导出失败 2、在resources下编写 Mybatis核心配置文件 二、搭建结构 1、编写mybatis工具类&#xff08;utils&#xff09; 2、编写实体类&#xff08;pojo&#xff09; 3、Mybatis 的实现&…

【Vue3实践】(六)Vue3使用vite处理环境变量、打包部署、nginx配置

文章目录1.前言2.环境变量2.1.环境变量文件(.env)2.2.环境变量变量定义与使用3.打包部署3.1.nginx配置3.2.静态站点根路径配置4.总结1.前言 由于在日常开发中会有一部分前端的开发任务&#xff0c;会涉及到Vue的项目的搭建、迭代、构建发布等操作&#xff0c;所以想系统的学习…

Linux:主机USB设备驱动简析

文章目录1. 前言2. 分析背景3. USB 总线硬件拓扑4. USB 协议栈概览4.1 Linux USB 子系统概览4.2 USB外设(如U盘)固件基础5. Linux USB 子系统初始化6. Linux USB 主机控制器(HCD) 驱动6.1 USB 主机控制器驱动初始化6.2 USB 主机控制器设备对象注册和驱动加载7. Linux USB 设备驱…

Chatgpt接入Csdn:实现自动回复、评论、点赞

背景 起初&#xff0c;我只是想自己弄个工具&#xff0c;用来处理一下大佬们的三连支持&#xff0c;后面我发现大家都在讨论chatgpt&#xff0c;于是我将自动回复和评论消息接入到了Csdn中&#xff0c;不知道这篇文章能不能发出来&#xff0c;代码的话暂时不开源&#xff0c;后…

【从零开始】Docker Desktop:听说你小子要玩我

前言 &#x1f34a;缘由 捡起遗忘的Docker知识 由于本狗近期项目紧任务重&#xff0c;高强度的搬砖导致摸鱼时间下降。在上线项目时&#xff0c;看到运维大神一系列骚操作&#xff0c;dockerk8s的知识如过眼云烟&#xff0c;忘得干净的很。所以想重新恶补一下docker知识&…

深度学习中的卷积神经网络

博主简介 博主是一名大二学生&#xff0c;主攻人工智能研究。感谢让我们在CSDN相遇&#xff0c;博主致力于在这里分享关于人工智能&#xff0c;c&#xff0c;Python&#xff0c;爬虫等方面知识的分享。 如果有需要的小伙伴可以关注博主&#xff0c;博主会继续更新的&#xff0c…

OpenCV实战(17)——FAST特征点检测

OpenCV实战&#xff08;17&#xff09;——FAST特征点检测0. 前言1. FAST 特征点检测2. 自适应特征检测3. 完整代码小结系列链接0. 前言 Harris 算子根据两个垂直方向上的强度变化率给出了角点(或更一般地说&#xff0c;兴趣点)的数学定义。但使用这种定义需要计算图像导数&am…

Android 14 新 API:直接监听截屏操作,不用再观察媒体文件了~

截屏可以说是手机设备最常用的功能了&#xff0c;Android 系统非常重视截屏方面的体验&#xff0c;近几年的更新都不忘去优化这方面的体验。 从一开始仅在通知栏提醒已截屏&#xff0c;到 Android 11 支持在左下角生成截屏缩略图供编辑或分享&#xff0c;再到 Android 12 支持…

计算机图形学 | 变换与观察

计算机图形学 | 变换与观察计算机图形学 | 变换与观察6.1 神奇的齐次坐标回顾几何阶段几何变换平移比例旋转对称错切齐次坐标的引入齐次坐标的概念和相关问题基于齐次坐标的变换6.2 三维模型&#xff0c;动起来&#xff01;基本三维变换平移比例旋转对称错切整体比例变换逆变换…

《计算机网络——自顶向下方法》精炼——1.4到1.7

三更灯火五更鸡&#xff0c;努力学习永不止。无惧困难与挑战&#xff0c;砥砺前行向成功。 文章目录引言正文时延排队时延吞吐量协议层次&#xff0c;服务模型&#xff08;重点&#xff09;封装&#xff08;重点&#xff09;网络安全&#xff08;选看&#xff09;恶意软件的分类…

【数据分析与挖掘】数据预处理

目录概述一、数据清洗1.1 缺失值处理1.1.1 拉格朗日插值法1.1.2 牛顿插值法1.2 异常值处理二、数据集成2.1 实体识别2.2 冗余属性识别三、数据变换3.1 简单函数变换3.2 规范化3.3 连续属性离散化3.4 属性构造3.5 小波变换四、数据规约4.1 属性规约4.2 数值规约概述 数据挖掘过…

Spring Boot中使用Redis

目录 1.依赖 2.依赖关系 3.配置 4.RedisTemplate 5.基础操作 6.事务 1.依赖 maven依赖如下&#xff0c;需要说明的是&#xff0c;spring-boot-starter-data-redis里默认是使用lettuce作为redis客户端的驱动&#xff0c;但是lettuce其实用的比较少&#xff0c;我们常用的…

如何在 Web 实现支持虚拟背景的视频会议

前言 众所周知&#xff0c;市面上有比如飞书会议、腾讯会议等实现视频会议功能的应用&#xff0c;而且随着这几年大环境的影响&#xff0c;远程协作办公越来越成为常态&#xff0c;关于视频会议的应用也会越来越多&#xff0c;且在远程办公的沟通协作中对沟通软件的使用要求会…

ARMv8-A非对齐数据访问支持(Alignment support)

目录 1&#xff0c;对齐传输和非对齐传输 2&#xff0c;AArch32 Alignment support 2.1 Instruction alignment 指令对齐 2.2 Unaligned data access 非对齐数据访问 2.3 SCTLR.A Alignment check enable 3&#xff0c;AArch64 Alignment support 3.1 Instruction align…

Text to image论文精读GigaGAN: 生成对抗网络仍然是文本生成图像的可行选择

GigaGAN是Adobe和卡内基梅隆大学学者们提出的一种新的GAN架构&#xff0c;作者设计了一种新的GAN架构&#xff0c;推理速度、合成高分辨率、扩展性都极其有优势&#xff0c;其证明GAN仍然是文本生成图像的可行选择之一。 文章链接&#xff1a;https://arxiv.org/abs/2303.0551…