// Flags: --expose-gc --no-warnings --expose-internals 'use strict'; const common = require('../common'); common.skipIfWorker(); // https://github.com/nodejs/node/issues/22767 try { require('trace_events'); } catch { common.skip('missing trace events'); } const assert = require('assert'); const cp = require('child_process'); const fs = require('fs'); const tmpdir = require('../common/tmpdir'); const { createTracing, getEnabledCategories } = require('trace_events'); function getEnabledCategoriesFromCommandLine() { const indexOfCatFlag = process.execArgv.indexOf('--trace-event-categories'); if (indexOfCatFlag === -1) { return undefined; } return process.execArgv[indexOfCatFlag + 1]; } const isChild = process.argv[2] === 'child'; const enabledCategories = getEnabledCategoriesFromCommandLine(); assert.strictEqual(getEnabledCategories(), enabledCategories); for (const i of [1, 'foo', true, false, null, undefined]) { assert.throws(() => createTracing(i), { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError' }); assert.throws(() => createTracing({ categories: i }), { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError' }); } assert.throws( () => createTracing({ categories: [] }), { code: 'ERR_TRACE_EVENTS_CATEGORY_REQUIRED', name: 'TypeError' } ); const tracing = createTracing({ categories: [ 'node.perf' ] }); assert.strictEqual(tracing.categories, 'node.perf'); assert.strictEqual(tracing.enabled, false); assert.strictEqual(getEnabledCategories(), enabledCategories); tracing.enable(); tracing.enable(); // Purposefully enable twice to test calling twice assert.strictEqual(tracing.enabled, true); assert.strictEqual(getEnabledCategories(), [ ...[enabledCategories].filter((_) => !!_), 'node.perf', ].join(',')); tracing.disable(); assert.strictEqual(tracing.enabled, false); const tracing2 = createTracing({ categories: [ 'foo' ] }); tracing2.enable(); assert.strictEqual(getEnabledCategories(), 'foo'); tracing2.disable(); tracing2.disable(); // Purposefully disable twice to test calling twice assert.strictEqual(getEnabledCategories(), enabledCategories); if (isChild) { const { internalBinding } = require('internal/test/binding'); const { trace: { TRACE_EVENT_PHASE_NESTABLE_ASYNC_BEGIN: kBeforeEvent, TRACE_EVENT_PHASE_NESTABLE_ASYNC_END: kEndEvent, } } = internalBinding('constants'); const { trace } = internalBinding('trace_events'); tracing.enable(); trace(kBeforeEvent, 'foo', 'test1', 0, 'test'); setTimeout(() => { trace(kEndEvent, 'foo', 'test1'); }, 1); } else { // Test that enabled tracing references do not get garbage collected // until after they are disabled. { { let tracing3 = createTracing({ categories: [ 'abc' ] }); tracing3.enable(); assert.strictEqual(getEnabledCategories(), 'abc'); tracing3 = undefined; } global.gc(); assert.strictEqual(getEnabledCategories(), 'abc'); // Not able to disable the thing after this point, however. } { common.expectWarning( 'Warning', 'Possible trace_events memory leak detected. There are more than ' + '10 enabled Tracing objects.'); for (let n = 0; n < 10; n++) { const tracing = createTracing({ categories: [ `a${n}` ] }); tracing.enable(); } } testApiInChildProcess(['--trace-event-categories', 'foo'], () => { testApiInChildProcess(['--trace-event-categories', 'foo']); }); } function testApiInChildProcess(execArgs, cb) { tmpdir.refresh(); // Save the current directory so we can chdir back to it later const parentDir = process.cwd(); process.chdir(tmpdir.path); const expectedBegins = [{ cat: 'foo', name: 'test1' }]; const expectedEnds = [{ cat: 'foo', name: 'test1' }]; const proc = cp.fork(__filename, ['child'], { execArgv: [ '--expose-gc', '--expose-internals', '--no-warnings', ...execArgs, ] }); proc.once('exit', common.mustCall(() => { const file = tmpdir.resolve('node_trace.1.log'); assert(fs.existsSync(file)); fs.readFile(file, common.mustSucceed((data) => { const traces = JSON.parse(data.toString()).traceEvents .filter((trace) => trace.cat !== '__metadata'); assert.strictEqual( traces.length, expectedBegins.length + expectedEnds.length); for (const trace of traces) { assert.strictEqual(trace.pid, proc.pid); switch (trace.ph) { 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'); } } process.chdir(parentDir); cb && process.nextTick(cb); })); })); }