golang beyla采集trace程序原理源碼解析
一. 整體原理
trace采集時(shí),監(jiān)聽了golang應(yīng)用程序的net/http中的函數(shù):
- net/http.serverHandler.ServeHTTP;
- net/http.(*Transport).roundTrip;
監(jiān)聽ServeHTTP時(shí):
- 若request中沒有trace信息,則生成traceparent,存入go_trace_map結(jié)構(gòu)(key=goroutine地址,value=trace信息);
- 若request中有trace信息,則根據(jù)trace信息,重新生成span,存入go_trace_map結(jié)構(gòu);
監(jiān)聽roundTrip的調(diào)用:
- 首先,根據(jù)goroutine地址,讀go_trace_map結(jié)構(gòu),得到trace信息;
- 然后,將當(dāng)前連接的trace信息,存入ongoing_http_client_requests結(jié)構(gòu)(key=goroutine地址,value=trace信息);
監(jiān)聽roundTrip的調(diào)用返回:
- 首先,根據(jù)goroutine地址,讀ongoing_http_client_requests結(jié)構(gòu),得到trace信息;
- 然后,將當(dāng)前調(diào)用的trace信息,轉(zhuǎn)換為http_request_trace結(jié)構(gòu),保存到ringbuf中;
最終,ebpf用戶程序,讀取ringbuf中的trace信息,采集到trace信息。
二. 監(jiān)聽uprobe/ServeHTTP
處理流程:
- 首先,提取goroutine和request指針;
- 然后,通過server_trace_parent()函數(shù),處理trace信息,存入go_trace_map結(jié)構(gòu);
- 最后,將數(shù)據(jù)存入onging_http_server_requests結(jié)構(gòu);
// beyla/bpf/go_nethttp.c SEC("uprobe/ServeHTTP") int uprobe_ServeHTTP(struct pt_regs *ctx) { void *goroutine_addr = GOROUTINE_PTR(ctx); void *req = GO_PARAM4(ctx); http_func_invocation_t invocation = { .start_monotime_ns = bpf_ktime_get_ns(), .req_ptr = (u64)req, .tp = {0} }; if (req) { // 處理trace信息,存入go_trace_map server_trace_parent(goroutine_addr, &invocation.tp, (void*)(req + req_header_ptr_pos)); } // write event if (bpf_map_update_elem(&ongoing_http_server_requests, &goroutine_addr, &invocation, BPF_ANY)) { bpf_dbg_printk("can't update map element"); } return 0; }
重點(diǎn)看一下server_trace_parent()函數(shù):
首先,從req_header讀取traceparent:
- 若讀到了,則copy traceId,將parentId=上層的spanId;
- 否則,則生成trace_id,將parentId=0;
- 然后,使用urand,生成隨機(jī)的spanId;
- 最后,將trace信息存入go_trace_map結(jié)構(gòu),key=goroutine地址,value=trace信息;
// bpf/go_common.h static __always_inline void server_trace_parent(void *goroutine_addr, tp_info_t *tp, void *req_header) { // May get overriden when decoding existing traceparent, but otherwise we set sample ON tp->flags = 1; // Get traceparent from the Request.Header void *traceparent_ptr = extract_traceparent_from_req_headers(req_header); if (traceparent_ptr != NULL) { // 讀到了traceparent .... } else { // 未讀到traceparent bpf_dbg_printk("No traceparent in headers, generating"); urand_bytes(tp->trace_id, TRACE_ID_SIZE_BYTES); // 生成隨機(jī)的trace_id; *((u64 *)tp->parent_id) = 0; } urand_bytes(tp->span_id, SPAN_ID_SIZE_BYTES); bpf_map_update_elem(&go_trace_map, &goroutine_addr, tp, BPF_ANY); }
go_trace_map對(duì)象的定義:
struct { __uint(type, BPF_MAP_TYPE_LRU_HASH); __type(key, void *); // key: pointer to the goroutine __type(value, tp_info_t); // value: traceparent info __uint(max_entries, MAX_CONCURRENT_SHARED_REQUESTS); __uint(pinning, LIBBPF_PIN_BY_NAME); } go_trace_map SEC(".maps"); typedef struct tp_info { unsigned char trace_id[TRACE_ID_SIZE_BYTES]; unsigned char span_id[SPAN_ID_SIZE_BYTES]; unsigned char parent_id[SPAN_ID_SIZE_BYTES]; u64 ts; u8 flags; } tp_info_t;
三. 監(jiān)聽uprobe/roundTrip
roundTrip函數(shù),在使用http client發(fā)起請(qǐng)求時(shí),被調(diào)用。
處理流程:
- 首先,提取goroutine地址和request地址;
- 然后,根據(jù)goroutine_addr和request,查找trace信息;
- 最后,將trace信息寫入ongoing_http_client_requests對(duì)象;
// beyla/bpf/go_nethttp.c SEC("uprobe/roundTrip") int uprobe_roundTrip(struct pt_regs *ctx) { roundTripStartHelper(ctx); return 0; } static __always_inline void roundTripStartHelper(struct pt_regs *ctx) { void *goroutine_addr = GOROUTINE_PTR(ctx); void *req = GO_PARAM2(ctx); http_func_invocation_t invocation = { .start_monotime_ns = bpf_ktime_get_ns(), .req_ptr = (u64)req, .tp = {0} }; // 根據(jù)request和goroutine_addr,查找trace信息 __attribute__((__unused__)) u8 existing_tp = client_trace_parent(goroutine_addr, &invocation.tp, (void*)(req + req_header_ptr_pos)); // 將trace信息寫入ongoing_http_client_requests if (bpf_map_update_elem(&ongoing_http_client_requests, &goroutine_addr, &invocation, BPF_ANY)) { bpf_dbg_printk("can't update http client map element"); } }
重點(diǎn)看一下查找trace信息的client_trace_parent()函數(shù):
首先,嘗試從request的header中提取traceparent:
- 若找到了,則copy traceId,設(shè)置當(dāng)前span.parentId=上游span的spanId;
然后,再使用goroutine及其parent_goroutine,去go_trace_map中找:
- 若找到了,則copy traceId,設(shè)置當(dāng)前span.parentId=上游span的spanId;
// beyla/go_common.h static __always_inline u8 client_trace_parent(void *goroutine_addr, tp_info_t *tp_i, void *req_header) { u8 found_trace_id = 0; u8 trace_id_exists = 0; // May get overriden when decoding existing traceparent or finding a server span, but otherwise we set sample ON tp_i->flags = 1; // 首先嘗試從request的header中提取traceparent if (req_header) { ... } // 然后再使用goroutine去go_trace_map中找 if (!found_trace_id) { tp_info_t *tp = 0; u64 parent_id = find_parent_goroutine(goroutine_addr); if (parent_id) {// we found a parent request tp = (tp_info_t *)bpf_map_lookup_elem(&go_trace_map, &parent_id); } if (tp) { // 找到了,copy traceId,當(dāng)前span.parentId=上流span.spanId *((u64 *)tp_i->trace_id) = *((u64 *)tp->trace_id); *((u64 *)(tp_i->trace_id + 8)) = *((u64 *)(tp->trace_id + 8)); *((u64 *)tp_i->parent_id) = *((u64 *)tp->span_id); tp_i->flags = tp->flags; } ... // 生成當(dāng)前span.spanId urand_bytes(tp_i->span_id, SPAN_ID_SIZE_BYTES); } return trace_id_exists; }
這里有個(gè)隱形的假設(shè)條件:
- 一個(gè)goroutine及其child goroutine僅處理一個(gè)http請(qǐng)求;
- nethttp的框架在設(shè)計(jì)時(shí),就由一個(gè)goroutine去處理一個(gè)http請(qǐng)求,是符合這個(gè)假設(shè)的;
四. 監(jiān)聽uprobe/roundTrip_return
處理流程:
- 首先,使用goroutine_addr,從ongoing_http_client_requests中找trace信息;
然后,初始化http_request_trace:
- 從request中找method/host/url/content_length,賦值給http_request_trace;
- 將trace信息賦值到http_request_trace;
- 從response中找status,賦值給http_request_trace;
- 最后,將http_request_trace提交到ringbuf;
// beyla/bpf/go_nethttp.c SEC("uprobe/roundTrip_return") int uprobe_roundTripReturn(struct pt_regs *ctx) { void *goroutine_addr = GOROUTINE_PTR(ctx); // 使用goroutine_addr找ongoing_http_client_requests http_func_invocation_t *invocation = bpf_map_lookup_elem(&ongoing_http_client_requests, &goroutine_addr); bpf_map_delete_elem(&ongoing_http_client_requests, &goroutine_addr); http_request_trace *trace = bpf_ringbuf_reserve(&events, sizeof(http_request_trace), 0); // 初始化http_request_trace task_pid(&trace->pid); trace->type = EVENT_HTTP_CLIENT; trace->start_monotime_ns = invocation->start_monotime_ns; trace->go_start_monotime_ns = invocation->start_monotime_ns; trace->end_monotime_ns = bpf_ktime_get_ns(); void *req_ptr = (void *)invocation->req_ptr; void *resp_ptr = (void *)GO_PARAM1(ctx); // 從request中找method,賦值給trace->method if (!read_go_str("method", req_ptr, method_ptr_pos, &trace->method, sizeof(trace->method))) { ... } // 從request中找host,賦值給trace->host if (!read_go_str("host", req_ptr, host_ptr_pos, &trace->host, sizeof(trace->host))) { ... } // 從request中找url,賦值給trace->path void *url_ptr = 0; bpf_probe_read(&url_ptr, sizeof(url_ptr), (void *)(req_ptr + url_ptr_pos)); if (!url_ptr || !read_go_str("path", url_ptr, path_ptr_pos, &trace->path, sizeof(trace->path))) { ... } // 賦值trace信息 trace->tp = invocation->tp; // 從request中找content_length,賦值給trace->content_length bpf_probe_read(&trace->content_length, sizeof(trace->content_length), (void *)(req_ptr + content_length_ptr_pos)); // 從resp中找status,賦值給trace->status bpf_probe_read(&trace->status, sizeof(trace->status), (void *)(resp_ptr + status_code_ptr_pos)); // 提交trace到ringbuf bpf_ringbuf_submit(trace, get_flags()); return 0; }
參考:
1.https://github.com/grafana/beyla/issues/521
2.https://github.com/grafana/beyla/blob/main/docs/sources/distributed-traces.md
以上就是golang beyla采集trace程序原理源碼解析的詳細(xì)內(nèi)容,更多關(guān)于golang beyla采集trace的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go語言基礎(chǔ)單元測(cè)試與性能測(cè)試示例詳解
這篇文章主要為大家介紹了Go語言基礎(chǔ)單元測(cè)試與性能測(cè)試示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助祝大家多多進(jìn)步2021-11-11Golang 端口復(fù)用測(cè)試的實(shí)現(xiàn)
這篇文章主要介紹了Golang 端口復(fù)用測(cè)試的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03Golang遠(yuǎn)程調(diào)用框架RPC的具體使用
Remote Procedure Call (RPC) 是一種使用TCP協(xié)議從另一個(gè)系統(tǒng)調(diào)用應(yīng)用程序功能執(zhí)行的方法。Go有原生支持RPC服務(wù)器實(shí)現(xiàn),本文通過簡(jiǎn)單實(shí)例介紹RPC的實(shí)現(xiàn)過程2022-12-12go語言中使用ent做關(guān)聯(lián)查詢的示例詳解
go語言的ent框架是facebook開源的ORM框架,是go語言開發(fā)中的常用框架,而關(guān)聯(lián)查詢又是日常開發(fā)中的常見數(shù)據(jù)庫(kù)操作,故文本給出一個(gè)使用ent做關(guān)聯(lián)查詢的使用示例,需要的朋友可以參考下2024-02-02golang 實(shí)現(xiàn)對(duì)Map進(jìn)行鍵值自定義排序
這篇文章主要介紹了golang 實(shí)現(xiàn)對(duì)Map進(jìn)行鍵值自定義排序,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-04-04深入理解Go高級(jí)并發(fā)模式編寫更高效可擴(kuò)展的應(yīng)用程序
Go對(duì)并發(fā)提供了強(qiáng)大的原生支持,本文討論Go的高級(jí)并發(fā)模式,理解這些并發(fā)模式,可以幫助我們編寫高效的Go應(yīng)用程序,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-02-02