日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

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

新聞中心

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

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

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

一、 使用

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

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

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

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

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

1、 PerformanceEntry

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

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

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ù)的實(shí)現(xiàn),我們看到它的 details 里包括了 kind 和 flags。接下來(lái)看一下 perf_hooks 是如何收集 gc 的性能數(shù)據(jù)的。首先通過(guò) InstallGarbageCollectionTracking 注冊(cè) 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 提供的兩個(gè)函數(shù)注冊(cè)了 gc 開(kāi)始和 gc 結(jié)束階段的鉤子。我們看一下這兩個(gè)鉤子的邏輯。

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 在開(kāi)始 gc 時(shí)被執(zhí)行,邏輯很簡(jiǎn)單,主要是記錄了 gc 的開(kāi)始時(shí)間。接著看 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 開(kāi)始時(shí)間,計(jì)算出 gc 的持續(xù)時(shí)間。然后產(chǎn)生一個(gè)性能數(shù)據(jù) GCPerformanceEntry。然后在事件循環(huán)的 check 階段通過(guò) Notify 進(jìn)行上報(bào)。

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 進(jìn)行進(jìn)一步的處理,然后執(zhí)行 JS 的回調(diào)進(jìn)行數(shù)據(jù)的上報(bào)。env->performance_entry_callback() 對(duì)應(yīng)的回調(diào)在 JS 設(shè)置。

2、 PerformanceState

PerformanceState 是 perf_hooks 的另一個(gè)核心數(shù)據(jù)結(jié)構(gòu),負(fù)責(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í)的性能數(shù)據(jù)
double milestones[NODE_PERFORMANCE_MILESTONE_INVALID];
// 記錄對(duì)不同類型性能數(shù)據(jù)感興趣的觀察者個(gè)數(shù)
uint32_t observers[NODE_PERFORMANCE_ENTRY_TYPE_INVALID];
};
};

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

三、 JS 層實(shí)現(xiàn)

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

class PerformanceObserver {
constructor(callback) {
// 性能數(shù)據(jù)
this[kBuffer] = [];
// 觀察者訂閱的性能數(shù)據(jù)類型
this[kEntryTypes] = new SafeSet();
// 觀察者對(duì)一個(gè)還是多個(gè)性能數(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();
// 重新訂閱當(dāng)前設(shè)置的類型
for (let n = 0; n < entryTypes.length; n++) {
if (ArrayPrototypeIncludes(kSupportedEntryTypes, entryTypes[n])) {
this[kEntryTypes].add(entryTypes[n]);
maybeIncrementObserverCount(entryTypes[n]);
}
}
// 插入觀察者隊(duì)列
kObservers.add(this);
}
takeRecords() {
const list = this[kBuffer];
this[kBuffer] = [];
return list;
}
static get supportedEntryTypes() {
return kSupportedEntryTypes;
}
// 產(chǎn)生性能數(shù)據(jù)時(shí)被執(zhí)行的函數(shù)
[kMaybeBuffer](entry) {
if (!this[kEntryTypes].has(entry.entryType))
return;
// 保存性能數(shù)據(jù),遲點(diǎn)上報(bào)
ArrayPrototypePush(this[kBuffer], entry);
// 插入待上報(bào)隊(duì)列
kPending.add(this);
if (kPending.size)
queuePending();
}
// 執(zhí)行觀察者回調(diào)
[kDispatch]() {
this[kCallback](new PerformanceObserverEntryList(this.takeRecords()),
this);
}
}

觀察者的實(shí)現(xiàn)比較簡(jiǎn)單,首先有一個(gè)全局的變量記錄了所有的觀察者,然后每個(gè)觀察者記錄了自己訂閱的類型。當(dāng)產(chǎn)生性能數(shù)據(jù)時(shí),生產(chǎn)者就會(huì)通知觀察者,接著觀察者執(zhí)行回調(diào)。這里需要額外介紹的一個(gè)是 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++ 層支持的類型,因?yàn)?perf_hooks 在 C++ 和 JS 層都定義了不同的性能類型,如果是涉及到底層的類型,就會(huì)操作 observerCounts 記錄當(dāng)前類型的觀察者數(shù)量,observerCounts 就是剛才分析 C++ 層的 observers 變量,它是一個(gè)數(shù)組,每個(gè)索引對(duì)應(yīng)一個(gè)類型,數(shù)組元素的值是觀察者的個(gè)數(shù)。另外如果訂閱的是 gc 類型,并且是第一個(gè)訂閱者,那就 JS 層就會(huì)操作 C++ 層往 V8 里注冊(cè) gc 回調(diào)。

了解了 perf_hooks 提供的機(jī)制后,我們來(lái)看一個(gè)具體的性能數(shù)據(jù)上報(bào)例子。這里以 HTTP Server 處理請(qǐng)求的耗時(shí)為例。

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 處理完一個(gè)請(qǐng)求時(shí)上報(bào)性能數(shù)據(jù)的邏輯。首先創(chuàng)建一個(gè) InternalPerformanceEntry 對(duì)象,這個(gè)和剛才介紹的 C++ 對(duì)象是一樣的,是表示一個(gè)性能數(shù)據(jù)的對(duì)象。接著調(diào)用 enqueue 函數(shù)。

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

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

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

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

四、 總結(jié)

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

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

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

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

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


本文標(biāo)題:一篇帶給你Node.js的Perf_Hooks
當(dāng)前路徑:http://www.dlmjj.cn/article/dhgpjco.html