日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
一篇帶給你Node.js的Perf_Hooks

前言:perf_hooks 是 Node.js 中用于收集性能數(shù)據(jù)的模塊,Node.js 本身基于 perf_hooks 提供了性能數(shù)據(jù),同時也提供了機制給用戶上報性能數(shù)據(jù)。文本介紹一下 perk_hooks。

我們是成立于2013年的成都網(wǎng)站建設(shè)公司,提供網(wǎng)站建設(shè),電商網(wǎng)站設(shè)計開發(fā),成都外貿(mào)網(wǎng)站建設(shè),響應(yīng)式網(wǎng)頁設(shè)計,小程序開發(fā)、等服務(wù)。為客戶創(chuàng)造有價值的品牌營銷體驗,讓互聯(lián)網(wǎng)提升企業(yè)的競爭力!

一、 使用

首先看一下 perf_hooks 的基本使用。

const { PerformanceObserver } = require('perf_hooks');
const obs = new PerformanceObserver((items) => {
//
};
obs.observe({ type: 'http' });

通過 PerformanceObserver 可以創(chuàng)建一個觀察者,然后調(diào)用 observe 可以訂閱對哪種類型的性能數(shù)據(jù)感興趣。

下面看一下 C++ 層的實現(xiàn),C++ 層的實現(xiàn)首先是為了支持 C++ 層的代碼進行數(shù)據(jù)的上報,同時也為了支持 JS 層的功能。

二、 C++ 層實現(xiàn)

1、 PerformanceEntry

PerformanceEntry 是 perf_hooks 里的一個核心數(shù)據(jù)結(jié)構(gòu),PerformanceEntry 代表一次性能數(shù)據(jù)。下面來看一下它的定義。

template struct PerformanceEntry {
using Details = typename Traits::Details;
std::string name;
double start_time;
double duration;
Details details;
static v8::MaybeLocal GetDetails(
Environment* env,
const PerformanceEntry& entry) {
return Traits::GetDetails(env, entry);
}
};

PerformanceEntry 里面記錄了一次性能數(shù)據(jù)的信息,從定義中可以看到,里面記錄了類型,開始時間,持續(xù)時間,比如一個 HTTP 請求的開始時間,處理耗時。除了這些信息之外,性能數(shù)據(jù)還包括一些額外的信息,由 details 字段保存,比如 HTTP 請求的 url,不過目前還不支持這個能力,不同的性能數(shù)據(jù)會包括不同的額外信息,所以 PerformanceEntry 是一個類模版,具體的 details 由具體的性能數(shù)據(jù)生產(chǎn)者實現(xiàn)。下面我們看一個具體的例子。

struct GCPerformanceEntryTraits {
static constexpr PerformanceEntryType kType = NODE_PERFORMANCE_ENTRY_TYPE_GC;
struct Details {
PerformanceGCKind kind;
PerformanceGCFlags flags;
Details(PerformanceGCKind kind_, PerformanceGCFlags flags_)
: kind(kind_), flags(flags_) {}
};
static v8::MaybeLocal GetDetails(
Environment* env,
const PerformanceEntry& entry);
};
using GCPerformanceEntry = PerformanceEntry;

這是關(guān)于 gc 性能數(shù)據(jù)的實現(xiàn),我們看到它的 details 里包括了 kind 和 flags。接下來看一下 perf_hooks 是如何收集 gc 的性能數(shù)據(jù)的。首先通過 InstallGarbageCollectionTracking 注冊 gc 鉤子。

static void InstallGarbageCollectionTracking(const FunctionCallbackInfo& args) {
Environment* env = Environment::GetCurrent(args);
env->isolate()->AddGCPrologueCallback(MarkGarbageCollectionStart, static_cast(env));
env->isolate()->AddGCEpilogueCallback(MarkGarbageCollectionEnd, static_cast(env));
env->AddCleanupHook(GarbageCollectionCleanupHook, env);
}

InstallGarbageCollectionTracking 主要是使用了 V8 提供的兩個函數(shù)注冊了 gc 開始和 gc 結(jié)束階段的鉤子。我們看一下這兩個鉤子的邏輯。

void MarkGarbageCollectionStart(
Isolate* isolate,
GCType type,
GCCallbackFlags flags,
void* data) {
Environment* env = static_cast(data);
env->performance_state()->performance_last_gc_start_mark = PERFORMANCE_NOW();
}

MarkGarbageCollectionStart 在開始 gc 時被執(zhí)行,邏輯很簡單,主要是記錄了 gc 的開始時間。接著看 MarkGarbageCollectionEnd。

void MarkGarbageCollectionEnd(
Isolate* isolate,
GCType type,
GCCallbackFlags flags,
void* data) {
Environment* env = static_cast(data);
PerformanceState* state = env->performance_state();
double start_time = state->performance_last_gc_start_mark / 1e6;
double duration = (PERFORMANCE_NOW() / 1e6) - start_time;

std::unique_ptr entry =
std::make_unique(
"gc",
start_time,
duration,
GCPerformanceEntry::Details(
static_cast(type),
static_cast(flags)));
env->SetImmediate([entry = std::move(entry)](Environment* env) {
entry->Notify(env);
}, CallbackFlags::kUnrefed);

}

MarkGarbageCollectionEnd 根據(jù)剛才記錄 gc 開始時間,計算出 gc 的持續(xù)時間。然后產(chǎn)生一個性能數(shù)據(jù) GCPerformanceEntry。然后在事件循環(huán)的 check 階段通過 Notify 進行上報。

void Notify(Environment* env) {
v8::Local detail;
if (!Traits::GetDetails(env, *this).ToLocal(&detail)) {
// TODO(@jasnell): Handle the error here
return;
}
v8::Local argv[] = {
OneByteString(env->isolate(), name.c_str()),
OneByteString(env->isolate(), GetPerformanceEntryTypeName(Traits::kType)),
v8::Number::New(env->isolate(), start_time),
v8::Number::New(env->isolate(), duration),
detail
};
node::MakeSyncCallback(
env->isolate(),
env->context()->Global(),
env->performance_entry_callback(),
arraysize(argv),
argv);
}
};

Notify 進行進一步的處理,然后執(zhí)行 JS 的回調(diào)進行數(shù)據(jù)的上報。env->performance_entry_callback() 對應(yīng)的回調(diào)在 JS 設(shè)置。

2、 PerformanceState

PerformanceState 是 perf_hooks 的另一個核心數(shù)據(jù)結(jié)構(gòu),負責(zé)管理 perf_hooks 模塊的一些公共數(shù)據(jù)。

class PerformanceState {
public:
explicit PerformanceState(v8::Isolate* isolate, const SerializeInfo* info);
AliasedUint8Array root;
AliasedFloat64Array milestones;
AliasedUint32Array observers;
uint64_t performance_last_gc_start_mark = 0;
void Mark(enum PerformanceMilestone milestone,uint64_t ts = PERFORMANCE_NOW());
private:
struct performance_state_internal {
// Node.js 初始化時的性能數(shù)據(jù)
double milestones[NODE_PERFORMANCE_MILESTONE_INVALID];
// 記錄對不同類型性能數(shù)據(jù)感興趣的觀察者個數(shù)
uint32_t observers[NODE_PERFORMANCE_ENTRY_TYPE_INVALID];
};
};

PerformanceState 主要是記錄了 Node.js 初始化時的性能數(shù)據(jù),比如 Node.js 初始化完畢的時間,事件循環(huán)的開始時間等。還有就是記錄了觀察者的數(shù)據(jù)結(jié)構(gòu),比如對 HTTP 性能數(shù)據(jù)感興趣的觀察者,主要用于控制要不要上報相關(guān)類型的性能數(shù)據(jù)。比如如果沒有觀察者的話,那么就不需要上報這個數(shù)據(jù)。

三、 JS 層實現(xiàn)

接下來看一下 JS 的實現(xiàn)。首先看一下觀察者的實現(xiàn)。

class PerformanceObserver {
constructor(callback) {
// 性能數(shù)據(jù)
this[kBuffer] = [];
// 觀察者訂閱的性能數(shù)據(jù)類型
this[kEntryTypes] = new SafeSet();
// 觀察者對一個還是多個性能數(shù)據(jù)類型感興趣
this[kType] = undefined;
// 觀察者回調(diào)
this[kCallback] = callback;
}
observe(options = {}) {
const {
entryTypes,
type,
buffered,
} = { ...options };
// 清除之前的數(shù)據(jù)
maybeDecrementObserverCounts(this[kEntryTypes]);
this[kEntryTypes].clear();
// 重新訂閱當前設(shè)置的類型
for (let n = 0; n < entryTypes.length; n++) {
if (ArrayPrototypeIncludes(kSupportedEntryTypes, entryTypes[n])) {
this[kEntryTypes].add(entryTypes[n]);
maybeIncrementObserverCount(entryTypes[n]);
}
}
// 插入觀察者隊列
kObservers.add(this);
}
takeRecords() {
const list = this[kBuffer];
this[kBuffer] = [];
return list;
}
static get supportedEntryTypes() {
return kSupportedEntryTypes;
}
// 產(chǎn)生性能數(shù)據(jù)時被執(zhí)行的函數(shù)
[kMaybeBuffer](entry) {
if (!this[kEntryTypes].has(entry.entryType))
return;
// 保存性能數(shù)據(jù),遲點上報
ArrayPrototypePush(this[kBuffer], entry);
// 插入待上報隊列
kPending.add(this);
if (kPending.size)
queuePending();
}
// 執(zhí)行觀察者回調(diào)
[kDispatch]() {
this[kCallback](new PerformanceObserverEntryList(this.takeRecords()),
this);
}
}

觀察者的實現(xiàn)比較簡單,首先有一個全局的變量記錄了所有的觀察者,然后每個觀察者記錄了自己訂閱的類型。當產(chǎn)生性能數(shù)據(jù)時,生產(chǎn)者就會通知觀察者,接著觀察者執(zhí)行回調(diào)。這里需要額外介紹的一個是 maybeDecrementObserverCounts 和 maybeIncrementObserverCount。

function getObserverType(type) {
switch (type) {
case 'gc': return NODE_PERFORMANCE_ENTRY_TYPE_GC;
case 'http2': return NODE_PERFORMANCE_ENTRY_TYPE_HTTP2;
case 'http': return NODE_PERFORMANCE_ENTRY_TYPE_HTTP;
}

}
function maybeDecrementObserverCounts(entryTypes) {
for (const type of entryTypes) {
const observerType = getObserverType(type);
if (observerType !== undefined) {
observerCounts[observerType]--;
if (observerType === NODE_PERFORMANCE_ENTRY_TYPE_GC &&
observerCounts[observerType] === 0) {
removeGarbageCollectionTracking();
gcTrackingInstalled = false;
}
}
}
}

maybeDecrementObserverCounts 主要用于操作 C++ 層的邏輯,首先根據(jù)訂閱類型判斷是不是 C++ 層支持的類型,因為 perf_hooks 在 C++ 和 JS 層都定義了不同的性能類型,如果是涉及到底層的類型,就會操作 observerCounts 記錄當前類型的觀察者數(shù)量,observerCounts 就是剛才分析 C++ 層的 observers 變量,它是一個數(shù)組,每個索引對應(yīng)一個類型,數(shù)組元素的值是觀察者的個數(shù)。另外如果訂閱的是 gc 類型,并且是第一個訂閱者,那就 JS 層就會操作 C++ 層往 V8 里注冊 gc 回調(diào)。

了解了 perf_hooks 提供的機制后,我們來看一個具體的性能數(shù)據(jù)上報例子。這里以 HTTP Server 處理請求的耗時為例。

function emitStatistics(statistics) {
const startTime = statistics.startTime;
const diff = process.hrtime(startTime);
const entry = new InternalPerformanceEntry(
statistics.type,
'http',
startTime[0] * 1000 + startTime[1] / 1e6,
diff[0] * 1000 + diff[1] / 1e6,
undefined,
);
enqueue(entry);
}

下面是 HTTP Server 處理完一個請求時上報性能數(shù)據(jù)的邏輯。首先創(chuàng)建一個 InternalPerformanceEntry 對象,這個和剛才介紹的 C++ 對象是一樣的,是表示一個性能數(shù)據(jù)的對象。接著調(diào)用 enqueue 函數(shù)。

function enqueue(entry) {
// 通知觀察者有性能數(shù)據(jù),觀察者自己判斷是否訂閱了這個類型的數(shù)據(jù)
for (const obs of kObservers) {
obs[kMaybeBuffer](entry);
}
// 如果是 mark 或 measure 類型,則插入一個全局隊列。
const entryType = entry.entryType;
let buffer;
if (entryType === 'mark') {
buffer = markEntryBuffer; // mark 性能數(shù)據(jù)隊列
} else if (entryType === 'measure') {
buffer = measureEntryBuffer; // measure 性能數(shù)據(jù)隊列
} else {
return;
}
ArrayPrototypePush(buffer, entry);
}

enqueue 會把性能數(shù)據(jù)上報到觀察者,然后觀察者如果訂閱這個類型的數(shù)據(jù)則執(zhí)行用戶回調(diào)通知用戶。我們看一下 obs[kMaybeBuffer] 的邏輯。

[kMaybeBuffer](entry) {
if (!this[kEntryTypes].has(entry.entryType))
return;
ArrayPrototypePush(this[kBuffer], entry);
// this 是觀察者實例
kPending.add(this);
if (kPending.size)
queuePending();
}
function queuePending() {
if (isPending) return;
isPending = true;
setImmediate(() => {
isPending = false;
const pendings = ArrayFrom(kPending.values());
kPending.clear();
// 遍歷觀察者隊列,執(zhí)行 kDispatch
for (const pending of pendings)
pending[kDispatch]();
});
}
// 下面是觀察者中的邏輯,觀察者把當前保存的數(shù)據(jù)上報給用戶
[kDispatch]() {
this[kCallback](new PerformanceObserverEntryList(this.takeRecords()),this);
}

另外 mark 和 measure 類型的性能數(shù)據(jù)比較特殊,它不僅會通知觀察者,還會插入到全局的一個隊列中。所以對于其他類型的性能數(shù)據(jù),如果沒有觀察者的話就會被丟棄(通常在調(diào)用 enqueue 之前會先判斷是否有觀察者),對于 mark 和 measure 類型的性能數(shù)據(jù),不管有沒有觀察者都會被保存下來,所以我們需要顯式清除。

四、 總結(jié)

以上就是 perf_hooks 中核心的實現(xiàn),除此之外,perf_hooks 還提供了其他的功能,本文就先不介紹了??梢钥吹?perf_hooks 的實現(xiàn)是一個訂閱發(fā)布的模式,看起來貌似沒什么特別的。但是它的強大之處在于是由 Node.js 內(nèi)置實現(xiàn)的, 這樣 Node.js 的其他模塊就可以基于 perf_hooks 這個框架上報各種類型的性能數(shù)據(jù)。相比來說雖然我們也能在用戶層實現(xiàn)這樣的邏輯,但是我們拿不到或者沒有辦法優(yōu)雅地方法拿到 Node.js 內(nèi)核里面的數(shù)據(jù),比如我們想拿到 gc 的性能數(shù)據(jù),我們只能寫 addon 實現(xiàn)。又比如我們想拿到 HTTP Server 處理請求的耗時,雖然可以通過監(jiān)聽 reqeust 或者 response 對象的事件實現(xiàn),但是這樣一來我們就會耦合到業(yè)務(wù)代碼里,每個開發(fā)者都需要處理這樣的邏輯,如果我們想收攏這個邏輯,就只能劫持 HTTP 模塊來實現(xiàn),這些不是優(yōu)雅但是是不得已的解決方案。有了 perf_hooks 機制,我們就可以以一種結(jié)耦的方式來收集這些性能數(shù)據(jù),實現(xiàn)寫一個 SDK,大家只需要簡單引入就行。

最近在研究 perf_hooks 代碼的時候發(fā)現(xiàn)目前 perf_hooks 的功能還不算完善,很多性能數(shù)據(jù)并沒有上報,目前只支持 HTTP Server 的請求耗時、HTTP 2 和 gc 耗時這些性能數(shù)據(jù)。所以最近提交了兩個 PR 支持了更多性能數(shù)據(jù)的上報。第一個 PR 是用于支持收集 HTTP Client 的耗時,第二個 PR 是用于支持收集 TCP 連接和 DNS 解析的耗時。在第二個 PR 里,實現(xiàn)了兩個通用的方法,方便后續(xù)其他模塊做性能上報。另外后續(xù)有時間的話,希望可以去不斷完善 perf_hooks 機制和性能數(shù)據(jù)收集這塊的能力。在從事 Node.js 調(diào)試和診斷這個方向的這段時間里,深感到應(yīng)用層能力的局限,因為我們不是業(yè)務(wù)方,而是基礎(chǔ)能力的提供者,就像前面提到的,哪怕想提供一個收集 HTTP 請求耗時的數(shù)據(jù)都是非常困難的,而作為基礎(chǔ)能力的提供者,我們一直希望我們的能力對業(yè)務(wù)來說是無感知,無侵入并且是穩(wěn)定可靠的。所以我們需要不斷深入地了解 Node.js 在這方面提供的能力,如果 Node.js 沒有提供我們想要的功能,我們只能寫 addon 或者嘗試給社區(qū)提交 PR 來解決。另外我們也在慢慢了解和學(xué)習(xí) ebpf,希望能利用 ebpf 從另外一個層面幫助我們解決所碰到的問題。

最后附上兩個 PR 的地址,有興趣的同學(xué)可以了解下。在內(nèi)核層面這塊還是有很多事情可以做,希望未來 Node.js 在這塊能力越來也強大。

https://github.com/nodejs/node/pull/42345。

https://github.com/nodejs/node/pull/42390。


網(wǎng)站標題:一篇帶給你Node.js的Perf_Hooks
URL分享:http://www.5511xx.com/article/dhgpjco.html