首页 » SEO优化 » nodejsphpserialize技巧_一篇带给你Nodejs 的Perf_Hooks

nodejsphpserialize技巧_一篇带给你Nodejs 的Perf_Hooks

访客 2024-11-27 0

扫一扫用手机浏览

文章目录 [+]

序言:perf_hooks 是 Node.js 中用于网络性能数据的模块,Node.js 本身基于 perf_hooks 供应了性能数据,同时也供应了机制给用户上报性能数据。
文本先容一下 perk_hooks。

一、 利用

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

nodejsphpserialize技巧_一篇带给你Nodejs 的Perf_Hooks

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

通过 PerformanceObserver 可以创建一个不雅观察者,然后调用 observe 可以订阅对哪种类型的性能数据感兴趣。

nodejsphpserialize技巧_一篇带给你Nodejs 的Perf_Hooks
(图片来自网络侵删)

下面看一下 C++ 层的实现,C++ 层的实现首先是为了支持 C++ 层的代码进行数据的上报,同时也为了支持 JS 层的功能。

二、 C++ 层实现1、 PerformanceEntry

PerformanceEntry 是 perf_hooks 里的一个核心数据构造,PerformanceEntry 代表一次性能数据。
下面来看一下它的定义。

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

PerformanceEntry 里面记录了一次性能数据的信息,从定义中可以看到,里面记录了类型,开始韶光,持续韶光,比如一个 HTTP 要求的开始韶光,处理耗时。
除了这些信息之外,性能数据还包括一些额外的信息,由 details 字段保存,比如 HTTP 要求的 url,不过目前还不支持这个能力,不同的性能数据会包括不同的额外信息,以是 PerformanceEntry 是一个类模版,详细的 details 由详细的性能数据生产者实现。
下面我们看一个详细的例子。

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<v8::Object> GetDetails( Environment env, const PerformanceEntry<GCPerformanceEntryTraits>& entry);};using GCPerformanceEntry = PerformanceEntry<GCPerformanceEntryTraits>;1.2.3.4.5.6.7.8.9.10.11.12.13.

这是关于 gc 性能数据的实现,我们看到它的 details 里包括了 kind 和 flags。
接下来看一下 perf_hooks 是如何网络 gc 的性能数据的。
首先通过 InstallGarbageCollectionTracking 注册 gc 钩子。

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

InstallGarbageCollectionTracking 紧张是利用了 V8 供应的两个函数注册了 gc 开始和 gc 结束阶段的钩子。
我们看一下这两个钩子的逻辑。

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

MarkGarbageCollectionStart 在开始 gc 时被实行,逻辑很大略,紧张是记录了 gc 的开始韶光。
接着看 MarkGarbageCollectionEnd。

void MarkGarbageCollectionEnd( Isolate isolate, GCType type, GCCallbackFlags flags, void data) { Environment env = static_cast<Environment>(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<GCPerformanceEntry> entry = std::make_unique<GCPerformanceEntry>( "gc", start_time, duration, GCPerformanceEntry::Details( static_cast<PerformanceGCKind>(type), static_cast<PerformanceGCFlags>(flags))); env->SetImmediate([entry = std::move(entry)](Environment env) { entry->Notify(env); }, CallbackFlags::kUnrefed);}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.

MarkGarbageCollectionEnd 根据刚才记录 gc 开始韶光,打算出 gc 的持续韶光。
然后产生一个性能数据 GCPerformanceEntry。
然后在事宜循环的 check 阶段通过 Notify 进行上报。

void Notify(Environment env) { v8::Local<v8::Object> detail; if (!Traits::GetDetails(env, this).ToLocal(&detail)) { // TODO(@jasnell): Handle the error here return; } v8::Local<v8::Value> 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); }};1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.

Notify 进行进一步的处理,然后实行 JS 的回调进行数据的上报。
env->performance_entry_callback() 对应的回调在 JS 设置。

2、 PerformanceState

PerformanceState 是 perf_hooks 的另一个核心数据构造,卖力管理 perf_hooks 模块的一些公共数据。

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 初始化时的性能数据 double milestones[NODE_PERFORMANCE_MILESTONE_INVALID]; // 记录对不同类型性能数据感兴趣的不雅观察者个数 uint32_t observers[NODE_PERFORMANCE_ENTRY_TYPE_INVALID]; };};1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.

PerformanceState 紧张是记录了 Node.js 初始化时的性能数据,比如 Node.js 初始化完毕的韶光,事宜循环的开始韶光等。
还有便是记录了不雅观察者的数据构造,比如对 HTTP 性能数据感兴趣的不雅观察者,紧张用于掌握要不要上报干系类型的性能数据。
比如如果没有不雅观察者的话,那么就不须要上报这个数据。

三、 JS 层实现

接下来看一下 JS 的实现。
首先看一下不雅观察者的实现。

class PerformanceObserver { constructor(callback) { // 性能数据 this[kBuffer] = []; // 不雅观察者订阅的性能数据类型 this[kEntryTypes] = new SafeSet(); // 不雅观察者对一个还是多个性能数据类型感兴趣 this[kType] = undefined; // 不雅观察者回调 this[kCallback] = callback; } observe(options = {}) { const { entryTypes, type, buffered, } = { ...options }; // 打消之前的数据 maybeDecrementObserverCounts(this[kEntryTypes]); this[kEntryTypes].clear(); // 重新订阅当前设置的类型 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; } // 产生性能数据时被实行的函数 [kMaybeBuffer](entry) { if (!this[kEntryTypes].has(entry.entryType)) return; // 保存性能数据,迟点上报 ArrayPrototypePush(this[kBuffer], entry); // 插入待上报行列步队 kPending.add(this); if (kPending.size) queuePending(); } // 实行不雅观察者回调 [kDispatch]() { this[kCallback](new PerformanceObserverEntryList(this.takeRecords()), this); }}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28 .29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.

不雅观察者的实现比较大略,首先有一个全局的变量记录了所有的不雅观察者,然后每个不雅观察者记录了自己订阅的类型。
当产生性能数据时,生产者就会关照不雅观察者,接着不雅观察者实行回调。
这里须要额外先容的一个是 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; } } }}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.

maybeDecrementObserverCounts 紧张用于操作 C++ 层的逻辑,首先根据订阅类型判断是不是 C++ 层支持的类型,由于 perf_hooks 在 C++ 和 JS 层都定义了不同的性能类型,如果是涉及到底层的类型,就会操作 observerCounts 记录当前类型的不雅观察者数量,observerCounts 便是刚才剖析 C++ 层的 observers 变量,它是一个数组,每个索引对应一个类型,数组元素的值是不雅观察者的个数。
其余如果订阅的是 gc 类型,并且是第一个订阅者,那就 JS 层就会操作 C++ 层往 V8 里注册 gc 回调。

理解了 perf_hooks 供应的机制后,我们来看一个详细的性能数据上报例子。
这里以 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);}1.2.3.4.5.6.7.8.9.10.11.12.

下面是 HTTP Server 处理完一个要求时上报性能数据的逻辑。
首先创建一个 InternalPerformanceEntry 工具,这个和刚才先容的 C++ 工具是一样的,是表示一个性能数据的工具。
接着调用 enqueue 函数。

function enqueue(entry) { // 关照不雅观察者有性能数据,不雅观察者自己判断是否订阅了这个类型的数据 for (const obs of kObservers) { obs[kMaybeBuffer](entry); } // 如果是 mark 或 measure 类型,则插入一个全局行列步队。
const entryType = entry.entryType; let buffer; if (entryType === 'mark') { buffer = markEntryBuffer; // mark 性能数据行列步队 } else if (entryType === 'measure') { buffer = measureEntryBuffer; // measure 性能数据行列步队 } else { return; } ArrayPrototypePush(buffer, entry);}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.

enqueue 会把性能数据上报到不雅观察者,然后不雅观察者如果订阅这个类型的数据则实行用户回调关照用户。
我们看一下 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(); // 遍历不雅观察者行列步队,实行 kDispatch for (const pending of pendings) pending[kDispatch](); });}// 下面是不雅观察者中的逻辑,不雅观察者把当前保存的数据上报给用户[kDispatch]() { this[kCallback](new PerformanceObserverEntryList(this.takeRecords()),this);}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.

其余 mark 和 measure 类型的性能数据比较分外,它不仅会关照不雅观察者,还会插入到全局的一个行列步队中。
以是对付其他类型的性能数据,如果没有不雅观察者的话就会被丢弃(常日在调用 enqueue 之前会先判断是否有不雅观察者),对付 mark 和 measure 类型的性能数据,不管有没有不雅观察者都会被保存下来,以是我们须要显式打消。

四、 总结

以上便是 perf_hooks 中核心的实现,除此之外,perf_hooks 还供应了其他的功能,本文就先不先容了。
可以看到 perf_hooks 的实现是一个订阅发布的模式,看起来貌似没什么特殊的。
但是它的强大之处在于是由 Node.js 内置实现的, 这样 Node.js 的其他模块就可以基于 perf_hooks 这个框架上报各种类型的性能数据。
比较来说虽然我们也能在用户层实现这样的逻辑,但是我们拿不到或者没有办法优雅地方法拿到 Node.js 内核里面的数据,比如我们想拿到 gc 的性能数据,我们只能写 addon 实现。
又比如我们想拿到 HTTP Server 处理要求的耗时,虽然可以通过监听 reqeust 或者 response 工具的事宜实现,但是这样一来我们就会耦合到业务代码里,每个开拓者都须要处理这样的逻辑,如果我们想收拢这个逻辑,就只能挟制 HTTP 模块来实现,这些不是优雅但是是不得已的办理方案。
有了 perf_hooks 机制,我们就可以以一种结耦的办法来网络这些性能数据,实现写一个 SDK,大家只须要大略引入就行。

最近在研究 perf_hooks 代码的时候创造目前 perf_hooks 的功能还不算完善,很多性能数据并没有上报,目前只支持 HTTP Server 的要求耗时、HTTP 2 和 gc 耗时这些性能数据。
以是最近提交了两个 PR 支持了更多性能数据的上报。
第一个 PR 是用于支持网络 HTTP Client 的耗时,第二个 PR 是用于支持网络 TCP 连接和 DNS 解析的耗时。
在第二个 PR 里,实现了两个通用的方法,方便后续其他模块做性能上报。
其余后续有韶光的话,希望可以去不断完善 perf_hooks 机制和性能数据网络这块的能力。
在从事 Node.js 调试和诊断这个方向的这段韶光里,深感到运用层能力的局限,由于我们不是业务方,而是根本能力的供应者,就像前面提到的,哪怕想供应一个网络 HTTP 要求耗时的数据都是非常困难的,而作为根本能力的供应者,我们一贯希望我们的能力对业务来说是无感知,无侵入并且是稳定可靠的。
以是我们须要不断深入地理解 Node.js 在这方面供应的能力,如果 Node.js 没有供应我们想要的功能,我们只能写 addon 或者考试测验给社区提交 PR 来办理。
其余我们也在逐步理解和学习 ebpf,希望能利用 ebpf 从其余一个层面帮助我们办理所碰到的问题。

末了附上两个 PR 的地址,有兴趣的同学可以理解下。
在内核层面这块还是有很多事情可以做,希望未来 Node.js 在这块能力越来也强大。

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

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

任务编辑:姜华来源: 编程杂技

相关文章

我国土地利用分类代码的构建与应用

土地利用分类代码是我国土地管理的重要组成部分,是土地资源调查、规划、利用和保护的依据。土地利用分类代码的构建与应用显得尤为重要。本...

SEO优化 2025-02-18 阅读1 评论0

微信跳转微信支付便捷支付体验的秘密武器

移动支付已成为人们日常生活中不可或缺的一部分。作为我国领先的社交平台,微信支付凭借其便捷、安全的支付方式,深受广大用户的喜爱。而微...

SEO优化 2025-02-18 阅读0 评论0

探寻会计科目代码背后的奥秘分类与

会计科目代码是会计信息系统中不可或缺的组成部分,它将企业的经济活动进行分类和归纳,为会计核算、财务分析和决策提供重要依据。本文将从...

SEO优化 2025-02-18 阅读1 评论0