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