偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

排查 Node.js 耗時代碼的一種方案

開發(fā) 前端
盡管代碼陷入了死循環(huán),但是這種方式仍然可以采集到當前的調用棧信息,這樣就可以知道是哪個代碼比較耗時了。這種方案的底層實現并不復雜,主要是依賴操作系統的信號機制,在收到信號時采集收集當前 JS 調用棧即可,因為信號有非常高的優(yōu)先級,所以盡管死循環(huán)也可以被處理。

我們知道 Node.js 是單線程應用,代碼都是按序執(zhí)行的,如果某段代碼非常耗時那么后面的代碼就會被一直堵住無法執(zhí)行。所以在寫 Node.js 應用時我們需要非常謹慎,避免出現這種情況。然而很多時候,問題還是無法避免,那么應該如何排查這種問題呢?我們都知道可以通過采集 CPU Profile 來分析代碼的執(zhí)行情況。但是在主線程被堵住的情況下,已經沒有機會開啟采集動作了,當然也有其他辦法,不過總的來說,CPU Profile 雖然方便排查問題,但是在實際環(huán)境中,如何采集 CPU Profile 是一個問題,這里不討論這個,只介紹一種輕量級的方案。

例子

先看一個例子。

while(1) {}

執(zhí)行 node --trace-sigint demo.mjs,然后按下 ctrl+c,會看到如下輸入。

KEYBOARD_INTERRUPT: Script execution was interrupted by `SIGINT`
    at /node/demo.mjs:1:1

可以看到,盡管代碼陷入了死循環(huán),但是這種方式仍然可以采集到當前的調用棧信息,這樣就可以知道是哪個代碼比較耗時了。這種方案的底層實現并不復雜,主要是依賴操作系統的信號機制,在收到信號時采集收集當前 JS 調用棧即可,因為信號有非常高的優(yōu)先級,所以盡管死循環(huán)也可以被處理。

實現原理

但是這種方式只能收集 JS 的調用棧,不能收集底層 C/C++ 代碼的調用棧,這個主要是因為在信號里收集 C/C++ 調用棧嚴重來說不是一個安全的操作,盡管這樣做很多時候也不會出現問題。所以只能在第三方的庫里實現這個能力,下面簡單介紹下實現并看一下效果。

首先提供一個 install 函數來開啟這個能力。

void Initialize( Local<Object> exports, Local<Value> module, Local<Context> context) {
  NODE_SET_METHOD(exports, "install", Install);
}

install 實現如下:

void Install(const FunctionCallbackInfo<Value>& args) {
  // 創(chuàng)建線程
  pthread_t thread;
  int ret = pthread_create(&thread, nullptr, Worker, isolate);
  // 注冊信號
  struct sigaction sa;
  memset(&sa, 0, sizeof(sa));
  sa.sa_sigaction = handler;
  sa.sa_flags = 0;
  sigfillset(&sa.sa_mask);
  sigaction(SIGINT, &sa, nullptr);
}

install 就做了兩件事情,一個是創(chuàng)建一個子線程,一個是注冊信號。其原理也很簡單,在收到信號時,信號處理函數就會給子線程發(fā)送信號,子線程利用 V8 的 API 請求收集 JS 調用棧。接著看一下信號處理函數的實現。

void handler(int signum,
  siginfo_t* info,
  void* ucontext) {
  int fd = open("./trace_cpp_call_stack.log", O_RDWR | O_TRUNC | O_CREAT, 0777);

  void* buffer[256];
  // 獲取 C/C++ 調用棧
  const int size = backtrace(buffer, 256);
  // 獲取代碼信息,demangle 后寫入文件
  for (int i = 1; i < size; i++) {
    void* frame = buffer[i];
    std::string data = LookupSymbol(frame).Display() + "\n";
    write(fd,data.c_str(),data.size());
  }
  close(fd);
  // 通知子線程
  uv_sem_post(&sem);
}

信號處理函數首先收集了 C/C++ 代碼的調用棧,然后通知子線程收集 JS 調用棧,接著看一下子線程的實現。

void* Worker(void* arg) {
  Isolate* isolate = static_cast<Isolate*>(arg);
  do {
    // 等待通知
    uv_sem_wait(&sem);
    // 請求 V8 執(zhí)行注冊的函數
    isolate->RequestInterrupt(
      [](v8::Isolate* isolate, void* data) {
        // 收集 JS 調用棧
        PrintStackTrace(isolate,
                    v8::StackTrace::CurrentStackTrace(
                        isolate, 10, v8::StackTrace::kDetailed));
      },
      isolate);
  } while (1);

  return nullptr;
}

Worker 利用了 V8 RequestInterrupt 函數向 V8 請求執(zhí)行收集 JS 調用棧的任務。這里面還有一些細節(jié)就不一一介紹了,主要理解大致原理就行。

效果

JS 耗時代碼

接下來看下 JS 層代碼耗時時的分析效果。

const { install } = require('...');

install();

function a() {
    b();
}

function b() {
    c();
}

function c() {
    for (let i = 0; i < 1000000000; i++) {
        
    }
}

while(1) {
    a();
}

執(zhí)行上面的代碼按下 ctrl+c 會看到輸入當前的 JS 調用棧,這樣就可以知道是哪里的代碼有問題了。

at c (/code/nodejs/trace-call-stack/test/test_js/index.js:14:5)
    at b (/code/nodejs/trace-call-stack/test/test_js/index.js:10:5)
    at a (/code/nodejs/trace-call-stack/test/test_js/index.js:6:5)
    at /code/nodejs/trace-call-stack/test/test_js/index.js:20:5
    at Module._compile (node:internal/modules/cjs/loader:1529:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1613:10)
    at Module.load (node:internal/modules/cjs/loader:1275:32)
    at Module._load (node:internal/modules/cjs/loader:1096:12)
    at executeUserEntryPoint (node:internal/modules/run_main:164:12)
    at node:internal/main/run_main_module:28:49

C/C++ 耗時代碼

接下來看一下 C/C++ 代碼耗時時的效果。

Matrix multiply(const Matrix& a, const Matrix& b) {
    int n = a.size();
    int m = a[0].size();
    int k = b[0].size();
    Matrix c(n, std::vector<double>(k, 0.0));

    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < k; ++j) {
            for (int p = 0; p < m; ++p) {
                c[i][j] += a[i][p] * b[p][j]; // 三重循環(huán),計算量極大
            }
        }
    }
    return c;
}

void Compute(const FunctionCallbackInfo<Value>& args) {
  Matrix a(1000, std::vector<double>(1000, 5.0));
  Matrix b(1000, std::vector<double>(1000, 10.0));
  auto c = multiply(a, b);
}

上面是一個非常耗時的 C/C++ addon,通過在 JS 層執(zhí)行該函數,然后執(zhí)行 ctrl+c 可以看到 C/C++調用棧包含了 multiply 函數,說明可以正確采集到調用棧。

test::multiply(...) [/code/nodejs/trace-call-stack/test/test_cpu_addon/build/Release/test.node]
test::Compute(...) [/code/nodejs/trace-call-stack/test/test_cpu_addon/build/Release/test.node]

C/C++ 代碼阻塞

接著再看一下 C/C++ 層阻塞到情況,比如加鎖失敗,主動睡眠等。

void Wait(const FunctionCallbackInfo<Value>& args) {
  uv_sem_init(&sem, 0);
  uv_sem_wait(&sem);
}

在 addon 里執(zhí)行上面的代碼會導致線程進入睡眠狀態(tài),這時候按 ctrl+c 可以看到輸出中包含 uv_sem_wait 函數。

uv_sem_wait [/node/v20.19.2/bin/node]

總結

通過這個工具我們就可以方便收集各個語言層面的代碼調用棧,從而方便問題的排查,不過目前只是做了簡單的實現和驗證,后續(xù)有時間再優(yōu)化。另外我也嘗試提交了一個 PR 把 trace-sigint 作為運行時 API 暴露出來,方便用戶靈活調用,尤其是 APM 的開發(fā)者。

1. https://github.com/nodejs/node/pull/59040

2. https://github.com/theanarkh/trace-call-stack

責任編輯:武曉燕 來源: 編程雜技
相關推薦

2024-07-12 14:54:48

2022-06-23 06:34:56

Node.js子線程

2021-12-18 07:42:15

Ebpf 監(jiān)控 Node.js

2025-03-10 00:00:22

PDF圖片表格

2011-12-23 13:58:57

node.js

2012-02-02 15:14:29

Node.js

2013-11-01 09:34:56

Node.js技術

2015-03-10 10:59:18

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

2022-04-02 06:04:03

Node.js代碼緩存V8

2020-05-29 15:33:28

Node.js框架JavaScript

2012-02-03 09:25:39

Node.js

2021-12-25 22:29:57

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

2019-08-05 09:45:19

Node.jsWeb開發(fā)前端

2021-06-05 06:49:54

LibuvN-API進程

2011-09-09 14:23:13

Node.js

2011-09-02 14:47:48

Node

2011-11-01 10:30:36

Node.js

2011-09-08 13:46:14

node.js

2012-10-24 14:56:30

IBMdw

2011-11-10 08:55:00

Node.js
點贊
收藏

51CTO技術棧公眾號