perf_hooks: emit trace events for marks, measures, and timerify

Adds the `node.perf.usertiming` trace events category for recording
usertiming marks and measures (e.g. `perf_hooks.performance.mark()`)
in the trace events timeline.

Adds the `node.perf.function` trace events category for recording
`perf_hooks.performance.timerify()` durations in the trace events
timeline.

PR-URL: https://github.com/nodejs/node/pull/18789
Reviewed-By: Ali Ijaz Sheikh <ofrobots@google.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
This commit is contained in:
James M Snell 2018-02-14 11:06:32 -08:00
parent aca8e764da
commit 9e509b622b
6 changed files with 131 additions and 11 deletions

View File

@ -268,8 +268,8 @@ added: v8.5.0
* {string} * {string}
The type of the performance entry. Current it may be one of: `'node'`, `'mark'`, The type of the performance entry. Currently it may be one of: `'node'`,
`'measure'`, `'gc'`, or `'function'`. `'mark'`, `'measure'`, `'gc'`, or `'function'`.
### performanceEntry.kind ### performanceEntry.kind
<!-- YAML <!-- YAML

View File

@ -10,8 +10,20 @@ starting a Node.js application.
The set of categories for which traces are recorded can be specified using the The set of categories for which traces are recorded can be specified using the
`--trace-event-categories` flag followed by a list of comma separated category `--trace-event-categories` flag followed by a list of comma separated category
names. By default the `node`, `node.async_hooks`, and `v8` categories are names.
enabled.
The available categories are:
* `node`
* `node.async_hooks` - Enables capture of detailed async_hooks trace data.
* `node.perf` - Enables capture of [Performance API] measurements.
* `node.perf.usertiming` - Enables capture of only Performance API User Timing
measures and marks.
* `node.perf.timerify` - Enables capture of only Performance API timerify
measurements.
* `v8`
By default the `node`, `node.async_hooks`, and `v8` categories are enabled.
```txt ```txt
node --trace-events-enabled --trace-event-categories v8,node,node.async_hooks server.js node --trace-events-enabled --trace-event-categories v8,node,node.async_hooks server.js
@ -24,3 +36,5 @@ tab of Chrome.
Starting with Node 10.0.0, the tracing system uses the same time source as the Starting with Node 10.0.0, the tracing system uses the same time source as the
one used by `process.hrtime()` however the trace-event timestamps are expressed one used by `process.hrtime()` however the trace-event timestamps are expressed
in microseconds, unlike `process.hrtime()` which returns nanoseconds. in microseconds, unlike `process.hrtime()` which returns nanoseconds.
[Performance API]: perf_hooks.html

View File

@ -105,8 +105,8 @@ void Mark(const FunctionCallbackInfo<Value>& args) {
auto marks = env->performance_marks(); auto marks = env->performance_marks();
(*marks)[*name] = now; (*marks)[*name] = now;
// TODO(jasnell): Once Tracing API is fully implemented, this should TRACE_EVENT_COPY_MARK_WITH_TIMESTAMP(
// record a trace event also. "node.perf,node.perf.usertiming", *name, now / 1000);
PerformanceEntry entry(env, *name, "mark", now, now); PerformanceEntry entry(env, *name, "mark", now, now);
Local<Object> obj = entry.ToObject(); Local<Object> obj = entry.ToObject();
@ -153,8 +153,10 @@ void Measure(const FunctionCallbackInfo<Value>& args) {
if (endTimestamp < startTimestamp) if (endTimestamp < startTimestamp)
endTimestamp = startTimestamp; endTimestamp = startTimestamp;
// TODO(jasnell): Once Tracing API is fully implemented, this should TRACE_EVENT_COPY_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0(
// record a trace event also. "node.perf,node.perf.usertiming", *name, *name, startTimestamp / 1000);
TRACE_EVENT_COPY_NESTABLE_ASYNC_END_WITH_TIMESTAMP0(
"node.perf,node.perf.usertiming", *name, *name, endTimestamp / 1000);
PerformanceEntry entry(env, *name, "measure", startTimestamp, endTimestamp); PerformanceEntry entry(env, *name, "measure", startTimestamp, endTimestamp);
Local<Object> obj = entry.ToObject(); Local<Object> obj = entry.ToObject();
@ -269,10 +271,15 @@ void TimerFunctionCall(const FunctionCallbackInfo<Value>& args) {
v8::TryCatch try_catch(isolate); v8::TryCatch try_catch(isolate);
if (args.IsConstructCall()) { if (args.IsConstructCall()) {
start = PERFORMANCE_NOW(); start = PERFORMANCE_NOW();
TRACE_EVENT_COPY_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0(
"node.perf,node.perf.timerify", *name, *name, start / 1000);
v8::MaybeLocal<Object> ret = fn->NewInstance(context, v8::MaybeLocal<Object> ret = fn->NewInstance(context,
call_args.size(), call_args.size(),
call_args.data()); call_args.data());
end = PERFORMANCE_NOW(); end = PERFORMANCE_NOW();
TRACE_EVENT_COPY_NESTABLE_ASYNC_END_WITH_TIMESTAMP0(
"node.perf,node.perf.timerify", *name, *name, end / 1000);
if (ret.IsEmpty()) { if (ret.IsEmpty()) {
try_catch.ReThrow(); try_catch.ReThrow();
return; return;
@ -280,11 +287,16 @@ void TimerFunctionCall(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(ret.ToLocalChecked()); args.GetReturnValue().Set(ret.ToLocalChecked());
} else { } else {
start = PERFORMANCE_NOW(); start = PERFORMANCE_NOW();
TRACE_EVENT_COPY_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0(
"node.perf,node.perf.timerify", *name, *name, start / 1000);
v8::MaybeLocal<Value> ret = fn->Call(context, v8::MaybeLocal<Value> ret = fn->Call(context,
args.This(), args.This(),
call_args.size(), call_args.size(),
call_args.data()); call_args.data());
end = PERFORMANCE_NOW(); end = PERFORMANCE_NOW();
TRACE_EVENT_COPY_NESTABLE_ASYNC_END_WITH_TIMESTAMP0(
"node.perf,node.perf.timerify", *name, *name, end / 1000);
if (ret.IsEmpty()) { if (ret.IsEmpty()) {
try_catch.ReThrow(); try_catch.ReThrow();
return; return;

View File

@ -4,6 +4,7 @@
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "node.h" #include "node.h"
#include "node_internals.h"
#include "node_perf_common.h" #include "node_perf_common.h"
#include "env.h" #include "env.h"
#include "base_object-inl.h" #include "base_object-inl.h"

View File

@ -246,7 +246,18 @@ enum CategoryGroupEnabledFlags {
// Implemented. // Implemented.
#define INTERNAL_TRACE_EVENT_ADD_WITH_ID_TID_AND_TIMESTAMP( \ #define INTERNAL_TRACE_EVENT_ADD_WITH_ID_TID_AND_TIMESTAMP( \
phase, category_group, name, id, thread_id, timestamp, flags, ...) \ phase, category_group, name, id, thread_id, timestamp, flags, ...) \
UNIMPLEMENTED() do { \
INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \
if (INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE()) { \
unsigned int trace_event_flags = flags | TRACE_EVENT_FLAG_HAS_ID; \
node::tracing::TraceID trace_event_trace_id(id, \
&trace_event_flags); \
node::tracing::AddTraceEventWithTimestamp( \
phase, INTERNAL_TRACE_EVENT_UID(category_group_enabled), name, \
trace_event_trace_id.scope(), trace_event_trace_id.raw_id(), \
node::tracing::kNoId, trace_event_flags, timestamp, ##__VA_ARGS__);\
} \
} while (0)
// Enter and leave a context based on the current scope. // Enter and leave a context based on the current scope.
#define INTERNAL_TRACE_EVENT_SCOPED_CONTEXT(category_group, name, context) \ #define INTERNAL_TRACE_EVENT_SCOPED_CONTEXT(category_group, name, context) \

View File

@ -0,0 +1,82 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const cp = require('child_process');
const path = require('path');
const fs = require('fs');
const tmpdir = require('../common/tmpdir');
if (process.argv[2] === 'child') {
const { performance } = require('perf_hooks');
// Will emit mark and measure trace events
performance.mark('A');
setTimeout(() => {
performance.mark('B');
performance.measure('A to B', 'A', 'B');
}, 1);
// Intentional non-op, part of the test
function f() {}
const ff = performance.timerify(f);
ff(); // Will emit a timerify trace event
} else {
tmpdir.refresh();
process.chdir(tmpdir.path);
const expectedMarks = ['A', 'B'];
const expectedBegins = [
{ cat: 'node.perf,node.perf.timerify', name: 'f' },
{ cat: 'node.perf,node.perf.usertiming', name: 'A to B' }
];
const expectedEnds = [
{ cat: 'node.perf,node.perf.timerify', name: 'f' },
{ cat: 'node.perf,node.perf.usertiming', name: 'A to B' }
];
const proc = cp.fork(__filename,
[
'child'
], {
execArgv: [
'--trace-events-enabled',
'--trace-event-categories',
'node.perf'
]
});
proc.once('exit', common.mustCall(() => {
const file = path.join(tmpdir.path, 'node_trace.1.log');
assert(common.fileExists(file));
fs.readFile(file, common.mustCall((err, data) => {
const traces = JSON.parse(data.toString()).traceEvents;
assert.strictEqual(traces.length,
expectedMarks.length +
expectedBegins.length +
expectedEnds.length);
traces.forEach((trace) => {
assert.strictEqual(trace.pid, proc.pid);
switch (trace.ph) {
case 'R':
assert.strictEqual(trace.cat, 'node.perf,node.perf.usertiming');
assert.strictEqual(trace.name, expectedMarks.shift());
break;
case 'b':
const expectedBegin = expectedBegins.shift();
assert.strictEqual(trace.cat, expectedBegin.cat);
assert.strictEqual(trace.name, expectedBegin.name);
break;
case 'e':
const expectedEnd = expectedEnds.shift();
assert.strictEqual(trace.cat, expectedEnd.cat);
assert.strictEqual(trace.name, expectedEnd.name);
break;
default:
assert.fail('Unexpected trace event phase');
}
});
}));
}));
}