Skip to content

Commit

Permalink
tracing: use perfetto for tracing
Browse files Browse the repository at this point in the history
This is still experimental. This enables the use of perfetto as a
replacement for the existing trace events mechanism in Node.js.
It's a significant change that impacts both v8 and core.

The build is still imperfect and needs some work. To enable
perfetto, use `./configure --use-perfetto`. Running `make` after
will likely fail the first time through, but should pass the second.

* The tracing format is changed from json to streamed protobufs.
* The existing tracing categories are changed.
* The JavaScript trace API has been changed completely.

This will be a breaking change to any code that builds on the
existing trace system.

There is still more here to do before this is ready to land.
  • Loading branch information
jasnell committed Nov 8, 2020
1 parent df76c87 commit 11030f6
Show file tree
Hide file tree
Showing 48 changed files with 2,116 additions and 151 deletions.
3 changes: 0 additions & 3 deletions common.gypi
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,6 @@

'v8_win64_unwinding_info': 1,

# TODO(refack): make v8-perfetto happen
'v8_use_perfetto': 0,

##### end V8 defaults #####

'conditions': [
Expand Down
10 changes: 10 additions & 0 deletions configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,11 @@
dest='shared_cares_libpath',
help='a directory to search for the shared cares DLL')

shared_optgroup.add_option('--use-perfetto',
action='store_true',
dest='use_perfetto',
help='use perfetto for trace events')

parser.add_option_group(shared_optgroup)

parser.add_option('--systemtap-includes',
Expand Down Expand Up @@ -1244,6 +1249,11 @@ def configure_node(o):
else:
o['variables']['experimental_quic'] = 'false'

if options.use_perfetto:
o['variables']['v8_use_perfetto'] = 1
else:
o['variables']['v8_use_perfetto'] = 0

o['variables']['node_no_browser_globals'] = b(options.no_browser_globals)

o['variables']['node_shared'] = b(options.shared)
Expand Down
5 changes: 2 additions & 3 deletions lib/internal/bootstrap/pre_execution.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,8 @@ function initializeHeapSnapshotSignalHandlers() {
}

function setupTraceCategoryState() {
const { isTraceCategoryEnabled } = internalBinding('trace_events');
const { toggleTraceCategoryState } = require('internal/process/per_thread');
toggleTraceCategoryState(isTraceCategoryEnabled('node.async_hooks'));
const {toggleTraceCategoryState } = require('internal/process/per_thread');
toggleTraceCategoryState();
}

function setupInspectorHooks() {
Expand Down
12 changes: 8 additions & 4 deletions lib/internal/process/per_thread.js
Original file line number Diff line number Diff line change
Expand Up @@ -348,14 +348,18 @@ function buildAllowedFlags() {
// trace event category is enabled.
let traceEventsAsyncHook;
// Dynamically enable/disable the traceEventsAsyncHook
function toggleTraceCategoryState(asyncHooksEnabled) {
if (asyncHooksEnabled) {
if (!traceEventsAsyncHook) {
function toggleTraceCategoryState() {
let { isTraceCategoryEnabled } = internalBinding('trace_events');
if (typeof isTraceCategoryEnabled !== 'function')
isTraceCategoryEnabled = require('internal/util').isTraceCategoryEnabled;

if (isTraceCategoryEnabled('node.async_hooks')) {
if (traceEventsAsyncHook === undefined) {
traceEventsAsyncHook =
require('internal/trace_events_async_hooks').createHook();
}
traceEventsAsyncHook.enable();
} else if (traceEventsAsyncHook) {
} else if (traceEventsAsyncHook !== undefined) {
traceEventsAsyncHook.disable();
}
}
Expand Down
63 changes: 61 additions & 2 deletions lib/internal/trace_events_async_hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ const {
Symbol,
} = primordials;

const { trace } = internalBinding('trace_events');
const {
trace,
asyncHooksTraceInit,
asyncHooksTraceDestroy,
asyncHooksTraceBefore,
asyncHooksTraceAfter,
} = internalBinding('trace_events');
const async_wrap = internalBinding('async_wrap');
const async_hooks = require('async_hooks');
const {
Expand Down Expand Up @@ -93,4 +99,57 @@ function createHook() {
};
}

exports.createHook = createHook;
function createPerfettoHook() {
// In traceEvents it is not only the id but also the name that needs to be
// repeated. Since async_hooks doesn't expose the provider type in the
// non-init events, use a map to manually map the asyncId to the type name.
const hook = async_hooks.createHook({
init(asyncId, type, triggerAsyncId, resource) {
if (nativeProviders.has(type)) return;
typeMemory.set(asyncId, type);
asyncHooksTraceInit(type, asyncId, triggerAsyncId);
},

before(asyncId) {
const type = typeMemory.get(asyncId);
if (type === undefined) return;
asyncHooksTraceBefore(type, asyncId);
},

after(asyncId) {
const type = typeMemory.get(asyncId);
if (type === undefined) return;
asyncHooksTraceAfter(asyncId);
},

destroy(asyncId) {
const type = typeMemory.get(asyncId);
if (type === undefined) return;
asyncHooksTraceDestroy(asyncId);

// Cleanup asyncId to type map
typeMemory.delete(asyncId);
}
});

return {
enable() {
if (this[kEnabled])
return;
this[kEnabled] = true;
hook.enable();
},

disable() {
if (!this[kEnabled])
return;
this[kEnabled] = false;
hook.disable();
typeMemory.clear();
}
};
}


exports.createHook =
typeof trace === 'function' ? createHook : createPerfettoHook;
21 changes: 21 additions & 0 deletions lib/internal/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,26 @@ const {
} = internalBinding('util');
const { isNativeError } = internalBinding('types');

const {
getSharedCategoryArrayBuffer,
getDynamicCategoryEnabled,
} = internalBinding('trace_events');

let sharedCategoryState;
let isTraceCategoryEnabled;
if (typeof getSharedCategoryArrayBuffer === 'function') {
isTraceCategoryEnabled = function(name) {
if (sharedCategoryState === undefined)
sharedCategoryState = new Uint8Array(getSharedCategoryArrayBuffer());
switch (name) {
case 'node.async_hooks': return sharedCategoryState[0] === 1;
case 'node.console': return sharedCategoryState[1] === 1;
default:
return getDynamicCategoryEnabled(name);
}
}
}

const noCrypto = !process.versions.openssl;

const experimentalWarnings = new Set();
Expand Down Expand Up @@ -424,6 +444,7 @@ module.exports = {
sleep,
spliceOne,
removeColors,
isTraceCategoryEnabled,

// Symbol used to customize promisify conversion
customPromisifyArgs: kCustomPromisifyArgsSymbol,
Expand Down
Loading

0 comments on commit 11030f6

Please sign in to comment.