自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

使用 Ebpf 監(jiān)控 Node.js 事件循環(huán)的耗時(shí)

運(yùn)維 系統(tǒng)運(yùn)維
本文介紹如何使用 ebpf 來監(jiān)控 Node.js 的耗時(shí),從而了解 Node.js 事件循環(huán)的執(zhí)行情況。不過這只是粗粒度的監(jiān)控,想要精細(xì)地了解 Node.js 的運(yùn)行情況,需要做的事情還很多。

[[441086]]

前言:強(qiáng)大的 ebpf 使用越來越廣,能做的事情也越來越多,尤其是無侵入的優(yōu)雅方式更加是技術(shù)選型的好選擇。本文介紹如何使用 ebpf 來監(jiān)控 Node.js 的耗時(shí),從而了解 Node.js 事件循環(huán)的執(zhí)行情況。不過這只是粗粒度的監(jiān)控,想要精細(xì)地了解 Node.js 的運(yùn)行情況,需要做的事情還很多。

在 Node.js 里,我們可以通過 V8 Inspector 的 cpuprofile 來了解 JS 的執(zhí)行耗時(shí),但是 cpuprofile 無法看到 C、C++ 代碼的執(zhí)行耗時(shí),通常我們可以使用 perf 工具來或許 C、C++ 代碼的耗時(shí),不過這里介紹的是通過 ebpf 來實(shí)現(xiàn),不失為一種探索。首先來看一下對(duì) poll io 階段的監(jiān)控。先定義一個(gè)結(jié)構(gòu)體用于記錄耗時(shí)。

  1. struct event  
  2.     __u64 start_time; 
  3.     __u64 end_time;  
  4. }; 

接著寫 bpf 程序。

  1. #include <linux/bpf.h> 
  2.  
  3. #include <linux/ptrace.h> 
  4.  
  5. #include <bpf/bpf_helpers.h> 
  6.  
  7. #include <bpf/bpf_tracing.h> 
  8.  
  9. #include "uv.h" 
  10.  
  11. #include "uv_uprobe.h" 
  12.  
  13.  
  14.  
  15. char LICENSE[] SEC("license") = "Dual BSD/GPL"
  16.  
  17. #define MAX_ENTRIES 10240 
  18.  
  19. // 用于記錄數(shù)據(jù) 
  20.  
  21. struct { 
  22.  
  23.     __uint(type, BPF_MAP_TYPE_HASH); 
  24.     __uint(max_entries, MAX_ENTRIES); 
  25.     __type(key, __u32); 
  26.     __type(value, const char *);} values SEC(".maps"); 
  27.  
  28.  
  29.  
  30. // 用于輸入數(shù)據(jù)到用戶層 
  31.  
  32. struct { 
  33.  
  34.     __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); 
  35.     __uint(key_size, sizeof(__u32)); 
  36.     __uint(value_size, sizeof(__u32));} events SEC(".maps");static __u64 id = 0;SEC("uprobe/uv__io_poll")int BPF_KPROBE(uprobe_uv__io_poll, uv_loop_t* loop, int timeout){ 
  37.     __u64 current_id = id; 
  38.     __u64 time = bpf_ktime_get_ns(); 
  39.     bpf_map_update_elem(&values, &current_id, &time, BPF_ANY); 
  40.     return 0; 
  41.  
  42.  
  43.  
  44.  
  45. SEC("uretprobe/uv__io_poll"
  46.  
  47. int BPF_KRETPROBE(uretprobe_uv__io_poll){    
  48.     __u64 current_id  
  49.  
  50.     __u64 current_id = id; 
  51.     __u64 *time = bpf_map_lookup_elem(&values, &current_id); 
  52.     if (!time) { 
  53.         return 0; 
  54.     } 
  55.     struct event e; 
  56.     // 記錄開始時(shí)間和結(jié)束時(shí)間 
  57.     e.start_time = *time
  58.     e.end_time = bpf_ktime_get_ns(); 
  59.     // 輸出到用戶層 
  60.     bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &e, sizeof(e)); 
  61.     bpf_map_delete_elem(&values, &current_id); 
  62.     id++; 
  63.     return 0; 
  64.  

最后編寫使用 ebpf 程序的代碼,只列出核心代碼。

  1. #include <errno.h> 
  2.  
  3. #include <stdio.h> 
  4.  
  5. #include <unistd.h> 
  6.  
  7. #include <sys/resource.h> 
  8.  
  9. #include <bpf/libbpf.h> 
  10.  
  11. #include "uv_uprobe.skel.h" 
  12.  
  13. #include "uprobe_helper.h" 
  14.  
  15. #include <signal.h> 
  16.  
  17. #include <bpf/bpf.h> 
  18.  
  19. #include "uv_uprobe.h" 
  20.  
  21. // 輸出結(jié)果函數(shù) 
  22.  
  23. static void handle_event(void *ctx, int cpu, void *data, __u32 data_sz){ 
  24.  
  25.     const struct event *e = (const struct event *)data; 
  26.     printf("%s %llu\n""poll io", (e->end_time - e->start_time) / 1000 / 1000); 
  27.  
  28.  
  29.  
  30.  
  31. int main(int argc, char **argv){ 
  32.  
  33.     struct uv_uprobe_bpf *skel; 
  34.     long base_addr, uprobe_offset; 
  35.     int err, i; 
  36.     struct perf_buffer_opts pb_opts; 
  37.     struct perf_buffer *pb = NULL
  38.     // 監(jiān)控哪個(gè) Node.js 進(jìn)程 
  39.     char * pid_str = argv[1]; 
  40.     pid_t pid = (pid_t)atoi(pid_str); 
  41.     char execpath[500]; 
  42.     // 根據(jù) pid 找到 Node.js 的可執(zhí)行文件 
  43.     int ret = get_pid_binary_path(pid, execpath, 500); 
  44.     // 需要監(jiān)控的函數(shù),uv__io_poll 是處理 poll io 階段的函數(shù) 
  45.     char * func = "uv__io_poll"
  46.     // 通過可執(zhí)行文件獲得函數(shù)的地址 
  47.     uprobe_offset = get_elf_func_offset(execpath, func); 
  48.     // 加載 bpf 程序到內(nèi)核 
  49.     skel = uv_uprobe_bpf__open(); 
  50.     err = uv_uprobe_bpf__load(skel); 
  51.     // 掛載監(jiān)控點(diǎn) 
  52.     skel->links.uprobe_uv__io_poll = bpf_program__attach_uprobe(skel->progs.uprobe_uv__io_poll, 
  53.                             false /* not uretprobe */, 
  54.                             -1, 
  55.                             execpath, 
  56.                             uprobe_offset); 
  57.     skel->links.uretprobe_uv__io_poll = bpf_program__attach_uprobe(skel->progs.uretprobe_uv__io_poll, 
  58.                                true /* uretprobe */, 
  59.                                -1 /* any pid */, 
  60.                                execpath, 
  61.                                uprobe_offset); 
  62.     // 設(shè)置回調(diào)處理 bpf 的輸出 
  63.     pb_opts.sample_cb = handle_event; 
  64.     pb_opts.lost_cb = handle_lost_events; 
  65.     pb = perf_buffer__new(bpf_map__fd(skel->maps.events), PERF_BUFFER_PAGES, 
  66.                   &pb_opts); 
  67.     printf("%-7s %-7s\n""phase""interval");            
  68.     for (i = 0; ; i++) { 
  69.         // 等待 bpf 的輸出,然后執(zhí)行回調(diào)處理,基于 epoll 實(shí)現(xiàn) 
  70.         perf_buffer__poll(pb, PERF_POLL_TIMEOUT_MS); 
  71.     } 
  72.  

編譯以上代碼,然后啟動(dòng)一個(gè) Node.js 進(jìn)程,接著把 Node.js 進(jìn)程的 pid 作為參數(shù)執(zhí)行上面代碼,就可以看到 poll io 階段的耗時(shí),通常,如果 Node.js 里沒有任務(wù)會(huì)阻塞到 epoll_wait 中,所以我們無法觀察到耗時(shí)。我們只需要在代碼里寫個(gè)定時(shí)器就行。

  1. setInterval(() => {}, 3000); 

我們可以看到 poll io 耗時(shí)在 3s 左右,因?yàn)橛卸〞r(shí)器時(shí),poll io 最多等待 3s 后就會(huì)返回,也就是整個(gè) poll io 階段的耗時(shí)。了解了基本的實(shí)現(xiàn)后,我們來監(jiān)控整個(gè)事件循環(huán)每個(gè)階段的耗時(shí)。原理是類似的。先定義一個(gè)處理多個(gè)階段的宏。

  1. #define PHASE(uprobe) \ 
  2.     uprobe(uv__run_timers) \  
  3.     uprobe(uv__run_pending) \ 
  4.     uprobe(uv__run_idle) \ 
  5.     uprobe(uv__run_prepare) \ 
  6.     uprobe(uv__io_poll) \ 
  7.     uprobe(uv__run_check) \ 
  8.     uprobe(uv__run_closing_handles) 

接著改一下 bpf 代碼。

  1. #define PROBE(type) \ 
  2. SEC("uprobe/" #type) \ 
  3. int BPF_KPROBE(uprobe_##type) \ 
  4. { \ 
  5.     char key[20] = #type; \ 
  6.     __u64 time = bpf_ktime_get_ns(); \ 
  7.     bpf_map_update_elem(&values, &key, &time, BPF_ANY); \ 
  8.     return 0; \ 
  9. } \ 
  10. SEC("uretprobe/" #type) \ 
  11. int BPF_KRETPROBE(uretprobe_##type) \ 
  12. {   \ 
  13.     char key[20] = #type; \ 
  14.     __u64 *time = bpf_map_lookup_elem(&values, &key); \ 
  15.     if (!time) { \ 
  16.         return 0; \ 
  17.     } \ 
  18.     struct event e = { \ 
  19.         .name=#type \ 
  20.     }; \ 
  21.     e.start_time = *time; \ 
  22.     e.end_time = bpf_ktime_get_ns(); \ 
  23.     bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &e, sizeof(e)); \ 
  24.     bpf_map_delete_elem(&valueskey); \ 
  25.     return 0; \ 
  26.  
  27. PHASE(PROBE) 

我們看到代碼和之前的 bpf 代碼是一樣的,只是通過宏的方式,方便定義多個(gè)階段,避免重復(fù)代碼。主要了使用 C 的一些知識(shí)。#a 等于 "a",a##b 等于ab,"a" "b" 等于 "ab"("a" "b" 中間有個(gè)空格)。同樣,寫完 bpf 代碼后,再改一下主程序的代碼。

  1. #define ATTACH_UPROBE(type)  \ 
  2.       do \ 
  3.       {   char * func_##type = #type; \ 
  4.           uprobe_offset = get_elf_func_offset(execpath, func_##type); \ 
  5.           if (uprobe_offset == -1) { \ 
  6.               fprintf(stderr, "invalid function &s: %s\n", func_##type); \ 
  7.               break; \ 
  8.           } \ 
  9.           fprintf(stderr, "uprobe_offset: %ld\n", uprobe_offset);\ 
  10.           skel->links.uprobe_##type = bpf_program__attach_uprobe(skel->progs.uprobe_##type,\ 
  11.                                   false /* not uretprobe */,\ 
  12.                                   pid,\ 
  13.                                   execpath,\ 
  14.                                   uprobe_offset);\ 
  15.           skel->links.uretprobe_##type = bpf_program__attach_uprobe(skel->progs.uretprobe_##type,\ 
  16.                                   true /* uretprobe */,\ 
  17.                                   pid /* any pid */,\ 
  18.                                   execpath,\ 
  19.                                   uprobe_offset);\ 
  20.       } while(false);  
  21.  
  22.      PHASE(ATTACH_UPROBE) 

同樣,代碼還是一樣的,只是變成了宏定義,然后通過 PHASE(ATTACH_UPROBE) 定義重復(fù)代碼。這里使用了 do while(false) 是因?yàn)槿绻硞€(gè)階段的處理過程有問題,則忽略,因?yàn)槲覀儾荒苤苯?return,所以 do while 是比較好的實(shí)現(xiàn)方式。因?yàn)樵谖覝y(cè)試的時(shí)候,有兩個(gè)階段是失敗的,原因是找不到對(duì)應(yīng)函數(shù)的地址。最后寫個(gè)測(cè)試代碼。

  1. function compute() { 
  2.     let sum = 0; 
  3.     for(let i = 0; i < 10000000; i++) { 
  4.         sum += i; 
  5.     } 
  6.  
  7.  
  8. setInterval(() => { 
  9.  
  10.     compute(); 
  11.     setImmediate(() => { 
  12.         compute(); 
  13.     }); 
  14.  
  15. }, 10000) 

執(zhí)行后看到輸出。

后記:本文大致介紹了基于 ebpf 實(shí)現(xiàn)對(duì) Node.js 事件循環(huán)的耗時(shí)監(jiān)控,這只是非常初步的探索。

 

責(zé)任編輯:姜華 來源: 編程雜技
相關(guān)推薦

2021-05-27 09:00:00

Node.js開發(fā)線程

2024-01-05 08:49:15

Node.js異步編程

2021-12-01 00:05:03

Js應(yīng)用Ebpf

2012-02-03 09:25:39

Node.js

2011-09-08 13:46:14

node.js

2023-01-31 16:43:31

?Node.js事件循環(huán)

2021-10-22 08:29:14

JavaScript事件循環(huán)

2011-09-08 14:16:12

Node.js

2021-12-08 07:55:41

EventLoop瀏覽器事件

2021-06-10 07:51:07

Node.js循環(huán)機(jī)制

2022-08-28 16:30:34

Node.jsDocker指令

2017-08-16 10:36:10

JavaScriptNode.js事件驅(qū)動(dòng)

2023-01-10 14:11:26

2015-03-10 10:59:18

Node.js開發(fā)指南基礎(chǔ)介紹

2013-11-01 09:34:56

Node.js技術(shù)

2020-02-25 12:27:59

Node.jsWeb開發(fā)前端

2020-05-29 15:33:28

Node.js框架JavaScript

2021-12-25 22:29:57

Node.js 微任務(wù)處理事件循環(huán)

2017-04-20 12:30:57

聲明式爬蟲網(wǎng)絡(luò)

2021-01-14 10:48:34

Docker CompNode.js開發(fā)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)