溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何使用ebpf監控Node.js事件循環的耗時

發布時間:2022-01-21 10:41:34 來源:億速云 閱讀:242 作者:iii 欄目:web開發

本篇內容介紹了“如何使用ebpf監控Node.js事件循環的耗時”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

前言:

強大的 ebpf 使用越來越廣,能做的事情也越來越多,尤其是無侵入的優雅方式更加是技術選型的好選擇。本文介紹如何使用 ebpf 來監控 Node.js 的耗時,從而了解 Node.js 事件循環的執行情況。不過這只是粗粒度的監控,想要精細地了解 Node.js 的運行情況,需要做的事情還很多。

Node.js 里,我們可以通過 V8 Inspector 的 cpuprofile 來了解 JS 的執行耗時,但是 cpuprofile 無法看到 C、C++ 代碼的執行耗時,通常我們可以使用 perf 工具來或許 C、C++ 代碼的耗時,不過這里介紹的是通過 ebpf 來實現,不失為一種探索。

首先來看一下對 poll io 階段的監控。先定義一個結構體用于記錄耗時。

struct event 

{

__u64 start_time;

__u64 end_time; 

};

接著寫 bpf 程序。

#include <linux/bpf.h>

#include <linux/ptrace.h>

#include <bpf/bpf_helpers.h>

#include <bpf/bpf_tracing.h>

#include "uv.h"

#include "uv_uprobe.h"

char LICENSE[] SEC("license") = "Dual BSD/GPL";

#define MAX_ENTRIES 10240

// 用于記錄數據

struct {

__uint(type, BPF_MAP_TYPE_HASH);

__uint(max_entries, MAX_ENTRIES);

__type(key, __u32);

__type(value, const char *);

} values SEC(".maps");

// 用于輸入數據到用戶層

struct {

__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);

__uint(key_size, sizeof(__u32));

__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)

{

__u64 current_id = id;

__u64 time = bpf_ktime_get_ns();

bpf_map_update_elem(&values, &current_id, &time, BPF_ANY);

return 0;

}

SEC("uretprobe/uv__io_poll")

int BPF_KRETPROBE(uretprobe_uv__io_poll)

{

__u64 current_id = id;

__u64 *time = bpf_map_lookup_elem(&values, &current_id);

if (!time) {

return 0;

}

struct event e;

// 記錄開始時間和結束時間

e.start_time = *time;

e.end_time = bpf_ktime_get_ns();

// 輸出到用戶層

bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &e, sizeof(e));

bpf_map_delete_elem(&values, &current_id);

id++;

return 0;

}

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

#include <errno.h>

#include <stdio.h>

#include <unistd.h>

#include <sys/resource.h>

#include <bpf/libbpf.h>

#include "uv_uprobe.skel.h"

#include "uprobe_helper.h"

#include <signal.h>

#include <bpf/bpf.h>

#include "uv_uprobe.h"

// 輸出結果函數

static void handle_event(void *ctx, int cpu, void *data, __u32 data_sz)

{

const struct event *e = (const struct event *)data;

printf("%s %llu\n", "poll io", (e->end_time - e->start_time) / 1000 / 1000);

}

int main(int argc, char **argv)

{

struct uv_uprobe_bpf *skel;

long base_addr, uprobe_offset;

int err, i;

struct perf_buffer_opts pb_opts;

struct perf_buffer *pb = NULL;

// 監控哪個 Node.js 進程

char * pid_str = argv[1];

pid_t pid = (pid_t)atoi(pid_str);

char execpath[500];

// 根據 pid 找到 Node.js 的可執行文件

int ret = get_pid_binary_path(pid, execpath, 500);

// 需要監控的函數,uv__io_poll 是處理 poll io 階段的函數

char * func = "uv__io_poll";

// 通過可執行文件獲得函數的地址

uprobe_offset = get_elf_func_offset(execpath, func);

// 加載 bpf 程序到內核

skel = uv_uprobe_bpf__open();

err = uv_uprobe_bpf__load(skel);

// 掛載監控點

skel->links.uprobe_uv__io_poll = bpf_program__attach_uprobe(skel->progs.uprobe_uv__io_poll,

false /* not uretprobe */,

-1,

execpath,

uprobe_offset);

skel->links.uretprobe_uv__io_poll = bpf_program__attach_uprobe(skel->progs.uretprobe_uv__io_poll,

   true /* uretprobe */,

   -1 /* any pid */,

   execpath,

   uprobe_offset);

// 設置回調處理 bpf 的輸出

pb_opts.sample_cb = handle_event;

pb_opts.lost_cb = handle_lost_events;

pb = perf_buffer__new(bpf_map__fd(skel->maps.events), PERF_BUFFER_PAGES,

      &pb_opts);

printf("%-7s %-7s\n", "phase", "interval");   

for (i = 0; ; i++) {

// 等待 bpf 的輸出,然后執行回調處理,基于 epoll 實現

perf_buffer__poll(pb, PERF_POLL_TIMEOUT_MS);

}

}

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

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

1

我們可以看到 poll io 耗時在 3s 左右,因為有定時器時,poll io 最多等待 3s 后就會返回,也就是整個 poll io 階段的耗時。了解了基本的實現后,我們來監控整個事件循環每個階段的耗時。原理是類似的。先定義一個處理多個階段的宏。

#define PHASE(uprobe) \

uprobe(uv__run_timers) \ 

uprobe(uv__run_pending) \

uprobe(uv__run_idle) \

uprobe(uv__run_prepare) \

uprobe(uv__io_poll) \

uprobe(uv__run_check) \

uprobe(uv__run_closing_handles)

接著改一下 bpf 代碼。

#define PROBE(type) \

SEC("uprobe/" #type) \

int BPF_KPROBE(uprobe_##type) \

{ \

char key[20] = #type; \

__u64 time = bpf_ktime_get_ns(); \

bpf_map_update_elem(&values, &key, &time, BPF_ANY); \

return 0; \

} \

SEC("uretprobe/" #type) \

int BPF_KRETPROBE(uretprobe_##type) \

{ \

char key[20] = #type; \

__u64 *time = bpf_map_lookup_elem(&values, &key); \

if (!time) { \

return 0; \

} \

struct event e = { \

.name=#type \

}; \

e.start_time = *time; \

e.end_time = bpf_ktime_get_ns(); \

bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &e, sizeof(e)); \

bpf_map_delete_elem(&values, key); \

return 0; \

}

PHASE(PROBE)

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

#define ATTACH_UPROBE(type)  \

do \

{ char * func_##type = #type; \

uprobe_offset = get_elf_func_offset(execpath, func_##type); \

if (uprobe_offset == -1) { \

fprintf(stderr, "invalid function &s: %s\n", func_##type); \

break; \

} \

fprintf(stderr, "uprobe_offset: %ld\n", uprobe_offset);\

skel->links.uprobe_##type = bpf_program__attach_uprobe(skel->progs.uprobe_##type,\

false /* not uretprobe */,\

pid,\

execpath,\

uprobe_offset);\

skel->links.uretprobe_##type = bpf_program__attach_uprobe(skel->progs.uretprobe_##type,\

true /* uretprobe */,\

pid /* any pid */,\

execpath,\

uprobe_offset);\

} while(false); 

PHASE(ATTACH_UPROBE)

同樣,代碼還是一樣的,只是變成了宏定義,然后通過 PHASE(ATTACH_UPROBE) 定義重復代碼。這里使用了 do while(false) 是因為如果某個階段的處理過程有問題,則忽略,因為我們不能直接 return,所以 do while 是比較好的實現方式。因為在我測試的時候,有兩個階段是失敗的,原因是找不到對應函數的地址。最后寫個測試代碼。

function compute() {

    let sum = 0;

    for(let i = 0; i < 10000000; i++) {

        sum += i;

    }

}

setInterval(() => {

    compute();

    setImmediate(() => {

        compute();

    });

}, 10000)

“如何使用ebpf監控Node.js事件循環的耗時”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女