1// Flags: --expose-gc --no-warnings --expose-internals 2'use strict'; 3 4const common = require('../common'); 5common.skipIfWorker(); // https://github.com/nodejs/node/issues/22767 6 7try { 8 require('trace_events'); 9} catch { 10 common.skip('missing trace events'); 11} 12 13const assert = require('assert'); 14const cp = require('child_process'); 15const path = require('path'); 16const fs = require('fs'); 17const tmpdir = require('../common/tmpdir'); 18const { 19 createTracing, 20 getEnabledCategories 21} = require('trace_events'); 22 23function getEnabledCategoriesFromCommandLine() { 24 const indexOfCatFlag = process.execArgv.indexOf('--trace-event-categories'); 25 if (indexOfCatFlag === -1) { 26 return undefined; 27 } 28 return process.execArgv[indexOfCatFlag + 1]; 29} 30 31const isChild = process.argv[2] === 'child'; 32const enabledCategories = getEnabledCategoriesFromCommandLine(); 33 34assert.strictEqual(getEnabledCategories(), enabledCategories); 35[1, 'foo', true, false, null, undefined].forEach((i) => { 36 assert.throws(() => createTracing(i), { 37 code: 'ERR_INVALID_ARG_TYPE', 38 name: 'TypeError' 39 }); 40 assert.throws(() => createTracing({ categories: i }), { 41 code: 'ERR_INVALID_ARG_TYPE', 42 name: 'TypeError' 43 }); 44}); 45 46assert.throws( 47 () => createTracing({ categories: [] }), 48 { 49 code: 'ERR_TRACE_EVENTS_CATEGORY_REQUIRED', 50 name: 'TypeError' 51 } 52); 53 54const tracing = createTracing({ categories: [ 'node.perf' ] }); 55 56assert.strictEqual(tracing.categories, 'node.perf'); 57assert.strictEqual(tracing.enabled, false); 58 59assert.strictEqual(getEnabledCategories(), enabledCategories); 60tracing.enable(); 61tracing.enable(); // Purposefully enable twice to test calling twice 62assert.strictEqual(tracing.enabled, true); 63 64assert.strictEqual(getEnabledCategories(), 65 [ 66 ...[enabledCategories].filter((_) => !!_), 'node.perf', 67 ].join(',')); 68 69tracing.disable(); 70assert.strictEqual(tracing.enabled, false); 71 72const tracing2 = createTracing({ categories: [ 'foo' ] }); 73tracing2.enable(); 74assert.strictEqual(getEnabledCategories(), 'foo'); 75 76tracing2.disable(); 77tracing2.disable(); // Purposefully disable twice to test calling twice 78assert.strictEqual(getEnabledCategories(), enabledCategories); 79 80if (isChild) { 81 const { internalBinding } = require('internal/test/binding'); 82 83 const { 84 trace: { 85 TRACE_EVENT_PHASE_NESTABLE_ASYNC_BEGIN: kBeforeEvent, 86 TRACE_EVENT_PHASE_NESTABLE_ASYNC_END: kEndEvent, 87 } 88 } = internalBinding('constants'); 89 90 const { trace } = internalBinding('trace_events'); 91 92 tracing.enable(); 93 94 trace(kBeforeEvent, 'foo', 'test1', 0, 'test'); 95 setTimeout(() => { 96 trace(kEndEvent, 'foo', 'test1'); 97 }, 1); 98} else { 99 // Test that enabled tracing references do not get garbage collected 100 // until after they are disabled. 101 { 102 { 103 let tracing3 = createTracing({ categories: [ 'abc' ] }); 104 tracing3.enable(); 105 assert.strictEqual(getEnabledCategories(), 'abc'); 106 tracing3 = undefined; 107 } 108 global.gc(); 109 assert.strictEqual(getEnabledCategories(), 'abc'); 110 // Not able to disable the thing after this point, however. 111 } 112 113 { 114 common.expectWarning( 115 'Warning', 116 'Possible trace_events memory leak detected. There are more than ' + 117 '10 enabled Tracing objects.'); 118 for (let n = 0; n < 10; n++) { 119 const tracing = createTracing({ categories: [ `a${n}` ] }); 120 tracing.enable(); 121 } 122 } 123 124 testApiInChildProcess(['--trace-event-categories', 'foo'], () => { 125 testApiInChildProcess(['--trace-event-categories', 'foo']); 126 }); 127} 128 129function testApiInChildProcess(execArgs, cb) { 130 tmpdir.refresh(); 131 // Save the current directory so we can chdir back to it later 132 const parentDir = process.cwd(); 133 process.chdir(tmpdir.path); 134 135 const expectedBegins = [{ cat: 'foo', name: 'test1' }]; 136 const expectedEnds = [{ cat: 'foo', name: 'test1' }]; 137 138 const proc = cp.fork(__filename, 139 ['child'], 140 { 141 execArgv: [ 142 '--expose-gc', 143 '--expose-internals', 144 '--no-warnings', 145 ...execArgs, 146 ] 147 }); 148 149 proc.once('exit', common.mustCall(() => { 150 const file = path.join(tmpdir.path, 'node_trace.1.log'); 151 152 assert(fs.existsSync(file)); 153 fs.readFile(file, common.mustSucceed((data) => { 154 const traces = JSON.parse(data.toString()).traceEvents 155 .filter((trace) => trace.cat !== '__metadata'); 156 157 assert.strictEqual( 158 traces.length, 159 expectedBegins.length + expectedEnds.length); 160 161 traces.forEach((trace) => { 162 assert.strictEqual(trace.pid, proc.pid); 163 switch (trace.ph) { 164 case 'b': { 165 const expectedBegin = expectedBegins.shift(); 166 assert.strictEqual(trace.cat, expectedBegin.cat); 167 assert.strictEqual(trace.name, expectedBegin.name); 168 break; 169 } 170 case 'e': { 171 const expectedEnd = expectedEnds.shift(); 172 assert.strictEqual(trace.cat, expectedEnd.cat); 173 assert.strictEqual(trace.name, expectedEnd.name); 174 break; 175 } 176 default: 177 assert.fail('Unexpected trace event phase'); 178 } 179 }); 180 process.chdir(parentDir); 181 cb && process.nextTick(cb); 182 })); 183 })); 184} 185