• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/* eslint-disable node-core/require-common-first, node-core/required-modules */
2'use strict';
3const assert = require('assert');
4const fs = require('fs');
5const net = require('net');
6const os = require('os');
7const path = require('path');
8const util = require('util');
9const cpus = os.cpus();
10
11function findReports(pid, dir) {
12  // Default filenames are of the form
13  // report.<date>.<time>.<pid>.<tid>.<seq>.json
14  const format = '^report\\.\\d+\\.\\d+\\.' + pid + '\\.\\d+\\.\\d+\\.json$';
15  const filePattern = new RegExp(format);
16  const files = fs.readdirSync(dir);
17  const results = [];
18
19  files.forEach((file) => {
20    if (filePattern.test(file))
21      results.push(path.join(dir, file));
22  });
23
24  return results;
25}
26
27function validate(filepath, fields) {
28  const report = fs.readFileSync(filepath, 'utf8');
29  if (process.report.compact) {
30    const end = report.indexOf('\n');
31    assert.strictEqual(end, report.length - 1);
32  }
33  validateContent(JSON.parse(report), fields);
34}
35
36function validateContent(report, fields = []) {
37  if (typeof report === 'string') {
38    try {
39      report = JSON.parse(report);
40    } catch {
41      throw new TypeError(
42        'validateContent() expects a JSON string or JavaScript Object');
43    }
44  }
45  try {
46    _validateContent(report, fields);
47  } catch (err) {
48    try {
49      err.stack += util.format('\n------\nFailing Report:\n%O', report);
50    } catch {}
51    throw err;
52  }
53}
54
55function _validateContent(report, fields = []) {
56  const isWindows = process.platform === 'win32';
57
58  // Verify that all sections are present as own properties of the report.
59  const sections = ['header', 'javascriptStack', 'nativeStack',
60                    'javascriptHeap', 'libuv', 'environmentVariables',
61                    'sharedObjects', 'resourceUsage', 'workers'];
62  if (!isWindows)
63    sections.push('userLimits');
64
65  if (report.uvthreadResourceUsage)
66    sections.push('uvthreadResourceUsage');
67
68  checkForUnknownFields(report, sections);
69  sections.forEach((section) => {
70    assert(report.hasOwnProperty(section));
71    assert(typeof report[section] === 'object' && report[section] !== null);
72  });
73
74  fields.forEach((field) => {
75    function checkLoop(actual, rest, expect) {
76      actual = actual[rest.shift()];
77      if (rest.length === 0 && actual !== undefined) {
78        assert.strictEqual(actual, expect);
79      } else {
80        assert(actual);
81        checkLoop(actual, rest, expect);
82      }
83    }
84    let actual, expect;
85    if (Array.isArray(field)) {
86      [actual, expect] = field;
87    } else {
88      actual = field;
89      expect = undefined;
90    }
91    checkLoop(report, actual.split('.'), expect);
92  });
93
94  // Verify the format of the header section.
95  const header = report.header;
96  const headerFields = ['event', 'trigger', 'filename', 'dumpEventTime',
97                        'dumpEventTimeStamp', 'processId', 'commandLine',
98                        'nodejsVersion', 'wordSize', 'arch', 'platform',
99                        'componentVersions', 'release', 'osName', 'osRelease',
100                        'osVersion', 'osMachine', 'cpus', 'host',
101                        'glibcVersionRuntime', 'glibcVersionCompiler', 'cwd',
102                        'reportVersion', 'networkInterfaces', 'threadId'];
103  checkForUnknownFields(header, headerFields);
104  assert.strictEqual(header.reportVersion, 2);  // Increment as needed.
105  assert.strictEqual(typeof header.event, 'string');
106  assert.strictEqual(typeof header.trigger, 'string');
107  assert(typeof header.filename === 'string' || header.filename === null);
108  assert.notStrictEqual(new Date(header.dumpEventTime).toString(),
109                        'Invalid Date');
110  assert(String(+header.dumpEventTimeStamp), header.dumpEventTimeStamp);
111  assert(Number.isSafeInteger(header.processId));
112  assert(Number.isSafeInteger(header.threadId) || header.threadId === null);
113  assert.strictEqual(typeof header.cwd, 'string');
114  assert(Array.isArray(header.commandLine));
115  header.commandLine.forEach((arg) => {
116    assert.strictEqual(typeof arg, 'string');
117  });
118  assert.strictEqual(header.nodejsVersion, process.version);
119  assert(Number.isSafeInteger(header.wordSize));
120  assert.strictEqual(header.arch, os.arch());
121  assert.strictEqual(header.platform, os.platform());
122  assert.deepStrictEqual(header.componentVersions, process.versions);
123  assert.deepStrictEqual(header.release, process.release);
124  assert.strictEqual(header.osName, os.type());
125  assert.strictEqual(header.osRelease, os.release());
126  assert.strictEqual(typeof header.osVersion, 'string');
127  assert.strictEqual(typeof header.osMachine, 'string');
128  assert(Array.isArray(header.cpus));
129  assert.strictEqual(header.cpus.length, cpus.length);
130  header.cpus.forEach((cpu) => {
131    assert.strictEqual(typeof cpu.model, 'string');
132    assert.strictEqual(typeof cpu.speed, 'number');
133    assert.strictEqual(typeof cpu.user, 'number');
134    assert.strictEqual(typeof cpu.nice, 'number');
135    assert.strictEqual(typeof cpu.sys, 'number');
136    assert.strictEqual(typeof cpu.idle, 'number');
137    assert.strictEqual(typeof cpu.irq, 'number');
138    assert(cpus.some((c) => {
139      return c.model === cpu.model;
140    }));
141  });
142
143  assert(Array.isArray(header.networkInterfaces));
144  header.networkInterfaces.forEach((iface) => {
145    assert.strictEqual(typeof iface.name, 'string');
146    assert.strictEqual(typeof iface.internal, 'boolean');
147    assert(/^([0-9A-F][0-9A-F]:){5}[0-9A-F]{2}$/i.test(iface.mac));
148
149    if (iface.family === 'IPv4') {
150      assert.strictEqual(net.isIPv4(iface.address), true);
151      assert.strictEqual(net.isIPv4(iface.netmask), true);
152      assert.strictEqual(iface.scopeid, undefined);
153    } else if (iface.family === 'IPv6') {
154      assert.strictEqual(net.isIPv6(iface.address), true);
155      assert.strictEqual(net.isIPv6(iface.netmask), true);
156      assert(Number.isInteger(iface.scopeid));
157    } else {
158      assert.strictEqual(iface.family, 'unknown');
159      assert.strictEqual(iface.address, undefined);
160      assert.strictEqual(iface.netmask, undefined);
161      assert.strictEqual(iface.scopeid, undefined);
162    }
163  });
164  assert.strictEqual(header.host, os.hostname());
165
166  // Verify the format of the javascriptStack section.
167  checkForUnknownFields(report.javascriptStack,
168                        ['message', 'stack', 'errorProperties']);
169  assert.strictEqual(typeof report.javascriptStack.errorProperties,
170                     'object');
171  assert.strictEqual(typeof report.javascriptStack.message, 'string');
172  if (report.javascriptStack.stack !== undefined) {
173    assert(Array.isArray(report.javascriptStack.stack));
174    report.javascriptStack.stack.forEach((frame) => {
175      assert.strictEqual(typeof frame, 'string');
176    });
177  }
178
179  // Verify the format of the nativeStack section.
180  assert(Array.isArray(report.nativeStack));
181  report.nativeStack.forEach((frame) => {
182    assert(typeof frame === 'object' && frame !== null);
183    checkForUnknownFields(frame, ['pc', 'symbol']);
184    assert.strictEqual(typeof frame.pc, 'string');
185    assert(/^0x[0-9a-f]+$/.test(frame.pc));
186    assert.strictEqual(typeof frame.symbol, 'string');
187  });
188
189  // Verify the format of the javascriptHeap section.
190  const heap = report.javascriptHeap;
191  const jsHeapFields = ['totalMemory', 'totalCommittedMemory', 'usedMemory',
192                        'availableMemory', 'memoryLimit', 'heapSpaces'];
193  checkForUnknownFields(heap, jsHeapFields);
194  assert(Number.isSafeInteger(heap.totalMemory));
195  assert(Number.isSafeInteger(heap.totalCommittedMemory));
196  assert(Number.isSafeInteger(heap.usedMemory));
197  assert(Number.isSafeInteger(heap.availableMemory));
198  assert(Number.isSafeInteger(heap.memoryLimit));
199  assert(typeof heap.heapSpaces === 'object' && heap.heapSpaces !== null);
200  const heapSpaceFields = ['memorySize', 'committedMemory', 'capacity', 'used',
201                           'available'];
202  Object.keys(heap.heapSpaces).forEach((spaceName) => {
203    const space = heap.heapSpaces[spaceName];
204    checkForUnknownFields(space, heapSpaceFields);
205    heapSpaceFields.forEach((field) => {
206      assert(Number.isSafeInteger(space[field]));
207    });
208  });
209
210  // Verify the format of the resourceUsage section.
211  const usage = report.resourceUsage;
212  const resourceUsageFields = ['userCpuSeconds', 'kernelCpuSeconds',
213                               'cpuConsumptionPercent', 'maxRss',
214                               'pageFaults', 'fsActivity'];
215  checkForUnknownFields(usage, resourceUsageFields);
216  assert.strictEqual(typeof usage.userCpuSeconds, 'number');
217  assert.strictEqual(typeof usage.kernelCpuSeconds, 'number');
218  assert.strictEqual(typeof usage.cpuConsumptionPercent, 'number');
219  assert(Number.isSafeInteger(usage.maxRss));
220  assert(typeof usage.pageFaults === 'object' && usage.pageFaults !== null);
221  checkForUnknownFields(usage.pageFaults, ['IORequired', 'IONotRequired']);
222  assert(Number.isSafeInteger(usage.pageFaults.IORequired));
223  assert(Number.isSafeInteger(usage.pageFaults.IONotRequired));
224  assert(typeof usage.fsActivity === 'object' && usage.fsActivity !== null);
225  checkForUnknownFields(usage.fsActivity, ['reads', 'writes']);
226  assert(Number.isSafeInteger(usage.fsActivity.reads));
227  assert(Number.isSafeInteger(usage.fsActivity.writes));
228
229  // Verify the format of the uvthreadResourceUsage section, if present.
230  if (report.uvthreadResourceUsage) {
231    const usage = report.uvthreadResourceUsage;
232    const threadUsageFields = ['userCpuSeconds', 'kernelCpuSeconds',
233                               'cpuConsumptionPercent', 'fsActivity'];
234    checkForUnknownFields(usage, threadUsageFields);
235    assert.strictEqual(typeof usage.userCpuSeconds, 'number');
236    assert.strictEqual(typeof usage.kernelCpuSeconds, 'number');
237    assert.strictEqual(typeof usage.cpuConsumptionPercent, 'number');
238    assert(typeof usage.fsActivity === 'object' && usage.fsActivity !== null);
239    checkForUnknownFields(usage.fsActivity, ['reads', 'writes']);
240    assert(Number.isSafeInteger(usage.fsActivity.reads));
241    assert(Number.isSafeInteger(usage.fsActivity.writes));
242  }
243
244  // Verify the format of the libuv section.
245  assert(Array.isArray(report.libuv));
246  report.libuv.forEach((resource) => {
247    assert.strictEqual(typeof resource.type, 'string');
248    assert.strictEqual(typeof resource.address, 'string');
249    assert(/^0x[0-9a-f]+$/.test(resource.address));
250    assert.strictEqual(typeof resource.is_active, 'boolean');
251    assert.strictEqual(typeof resource.is_referenced,
252                       resource.type === 'loop' ? 'undefined' : 'boolean');
253  });
254
255  // Verify the format of the environmentVariables section.
256  for (const [key, value] of Object.entries(report.environmentVariables)) {
257    assert.strictEqual(typeof key, 'string');
258    assert.strictEqual(typeof value, 'string');
259  }
260
261  // Verify the format of the userLimits section on non-Windows platforms.
262  if (!isWindows) {
263    const userLimitsFields = ['core_file_size_blocks', 'data_seg_size_kbytes',
264                              'file_size_blocks', 'max_locked_memory_bytes',
265                              'max_memory_size_kbytes', 'open_files',
266                              'stack_size_bytes', 'cpu_time_seconds',
267                              'max_user_processes', 'virtual_memory_kbytes'];
268    checkForUnknownFields(report.userLimits, userLimitsFields);
269    for (const [type, limits] of Object.entries(report.userLimits)) {
270      assert.strictEqual(typeof type, 'string');
271      assert(typeof limits === 'object' && limits !== null);
272      checkForUnknownFields(limits, ['soft', 'hard']);
273      assert(typeof limits.soft === 'number' || limits.soft === 'unlimited',
274             `Invalid ${type} soft limit of ${limits.soft}`);
275      assert(typeof limits.hard === 'number' || limits.hard === 'unlimited',
276             `Invalid ${type} hard limit of ${limits.hard}`);
277    }
278  }
279
280  // Verify the format of the sharedObjects section.
281  assert(Array.isArray(report.sharedObjects));
282  report.sharedObjects.forEach((sharedObject) => {
283    assert.strictEqual(typeof sharedObject, 'string');
284  });
285
286  // Verify the format of the workers section.
287  assert(Array.isArray(report.workers));
288  report.workers.forEach((worker) => _validateContent(worker));
289}
290
291function checkForUnknownFields(actual, expected) {
292  Object.keys(actual).forEach((field) => {
293    assert(expected.includes(field), `'${field}' not expected in ${expected}`);
294  });
295}
296
297module.exports = { findReports, validate, validateContent };
298