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