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