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