nodejs-info
Version:
Node info on package, os, process memory, timezone, versions, environment, request
190 lines (165 loc) • 8.48 kB
JavaScript
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* nodeinfo() (c) Chris Veness 2016-2020 MIT Licence */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
'use strict';
const os = require('os'); // nodejs.org/api/os.html
const http = require('http'); // nodejs.org/api/http.html
const fs = require('fs'); // nodejs.org/api/fs.html
const handlebars = require('handlebars'); // handlebars templating
const prettysize = require('prettysize'); // convert bytes to other sizes
const humanizeDuration = require('humanize-duration'); // convert millisecond durations to English
const cookie = require('cookie'); // cookie parsing and serialization
let packageJson = null; // inspect package.json from parent package;
try { packageJson = require('../../package.json'); } catch (e) {} // ignore it if not available
const template = `
<html>
<head>
<title>NodeInfo</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/4.1.0/sanitize.min.css">
<style>
main { color: #333333; font: 80% Verdana, Arial, Helvetica, sans-serif; line-height: 1.6; margin: 0.4em; }
h1 { font-size: 1.6em; }
h2 { margin: 1em 0; }
h3 { margin: 0.4em 0; }
table { background-color: #f4ede2; border: 1px solid #adadad; font-size: 0.8em; width: 100%; }
td, th { padding: 0.2em 0.8em; vertical-align: top; border-top: 1px solid #adadad; border-bottom: 1px solid #adadad; }
table table { border: none; }
table table td { padding: 0 0.4em 0 0; border: none; font-size: 125%; }
{{{styleOverride}}}
</style>
</head>
<body>
<main>
<h1>Node.js {{process.version}}</h1>
<table>
{{#if package}}
<tr><td colspan="2"><h2>Package</h2></td></tr>
<tr><td>name</td><td>{{package.name}}</td></tr>
{{#if package.description}}
<tr><td>description</td><td>{{package.description}}</td></tr>
{{/if}}
<tr><td>version</td><td>{{package.version}}</td></tr>
{{#if package.dependencies}}
<tr><td>dependencies</td><td>{{package.dependencies}}</td></tr>
{{/if}}
{{/if}}
<tr><td colspan="2"><h2>OS</h2></td></tr>
<tr><td>hostname</td><td>{{os.hostname}}</td></tr>
<tr><td>type</td><td>{{os.type}}</td></tr>
<tr><td>platform</td><td>{{os.platform}}</td></tr>
<tr><td>release</td><td>{{os.release}}</td></tr>
<tr><td>cpu models</td><td>{{os.cpus.model}}</td></tr>
<tr><td>cpu speeds</td><td>{{os.cpus.speed}}</td></tr>
<tr><td>uptime</td><td>{{os.uptime}}</td></tr>
<tr><td>load avg</td><td>{{os.loadavg}}</td></tr>
<tr><td>total memory</td><td>{{os.totalmem}}</td></tr>
<tr><td>free memory</td><td>{{os.freemem}} ({{os.freemempercent}}%)</td></tr>
<tr><td colspan="2"><h3>process</h3></td></tr>
<tr><td>resident set size</td><td>{{process.rss}}</td></tr>
<tr><td>v8 heap total</td><td>{{process.heapTotal}}</td></tr>
<tr><td>v8 heap used</td><td>{{process.heapUsed}}</td></tr>
<tr><td colspan="2"><h3>timezone</h3></td></tr>
<tr><td>timezone</td><td>{{intlTimezone}}</td></tr>
<tr><td>offset</td><td>{{dateTzOffset}}</td></tr>
<tr><td colspan="2"><h2>Node versions</h2></td></tr>
{{#each process.versions}}
<tr><td>{{@key}}</td><td>{{this}}</td></tr>
{{/each}}
{{#if process.env.NODE_ENV}}
<tr><td colspan="2"> </td></tr>
<tr><td>NODE_ENV</td><td>{{process.env.NODE_ENV}}</td></tr>
{{/if}}
{{#if request}}
<tr><td colspan="2"><h2>Request</h2></td></tr>
<tr><td>method</td><td>{{request.method}}</td></tr>
<tr><td>href</td><td>{{request.href}}</td></tr>
<tr><td>remote ip</td><td>{{request.ip_addr}}</td></tr>
<tr><td colspan="2"><h3>headers</h3></td></tr>
{{#if request.showOriginalUrl}}
<tr><td>original url</td><td>{{request.originalUrl}}</td></tr>
{{/if}}
{{#each request.headers}}
<tr><td style="white-space:nowrap">{{@key}}</td><td>{{this}}</td></tr>
{{/each}}
<tr><td>cookies</td><td>
<table>
{{#each request.cookies}}
<tr><td>{{@key}}</td><td>:</td><td style="word-break:break-all">{{this}}</td></tr>
{{/each}}
</table>
</td></tr>
{{else}}
<tr><td colspan="2"><h2>No request object supplied</h2></td></tr>
{{/if}}
</table>
</main>
</body>
</html>
`;
/**
* Returns Node.js configuration information as an HTML page.
*
* @param {Object} req - Node request object.
* @param {Object} [options]
* @param {string} [options.style] - Optional CSS which can be used to style output.
* @returns {string} HTML page with configuration information.
*/
function nodeinfo(req, options) {
const defaults = { style: '' };
const opt = Object.assign(defaults, options);
const context = {};
context.styleOverride = opt.style;
// package.dependencies
context.package = packageJson ? Object.assign({}, packageJson) : null;
if (context.package && context.package.dependencies) { // convert dependencies to list
context.package.dependencies = Object.keys(context.package.dependencies).join(', ');
}
// useful functions from 'os'
const osFunctions = [ 'cpus', 'freemem', 'hostname', 'loadavg', 'platform', 'release', 'totalmem', 'type', 'uptime' ];
context.os = {}; // results of executing os functions
for (const fn of osFunctions) { context.os[fn] = os[fn](); }
// and some formatting
context.os.uptime = humanizeDuration(context.os.uptime*1000, { units: [ 'd', 'h', 'm' ], round: true });
context.os.loadavg.forEach(function(l, i, loadavg) { loadavg[i] = l.toFixed(2); });
context.os.loadavg = context.os.loadavg.join(' ');
context.os.freemempercent = Math.round(context.os.freemem/context.os.totalmem*100);
context.os.totalmem = prettysize(context.os.totalmem);
context.os.freemem = prettysize(context.os.freemem);
// bit of magic to generate nice presentation for CPUs info
const cpus = { model: {}, speed: {} };
for (let c=0; c<os.cpus().length; c++) {
cpus.model[os.cpus()[c].model] = (cpus.model[os.cpus()[c].model] || 0) + 1;
cpus.speed[os.cpus()[c].speed] = (cpus.speed[os.cpus()[c].speed] || 0) + 1;
}
context.os.cpus = { model: [], speed: [] };
for (const p in cpus.model) context.os.cpus.model.push(cpus.model[p]+' × '+p);
for (const p in cpus.speed) context.os.cpus.speed.push(cpus.speed[p]+' × '+p);
context.os.cpus.model = context.os.cpus.model.join(', ');
context.os.cpus.speed = context.os.cpus.speed.join(', ') + ' MHz';
// everything from process (for process memory and Node versions)
context.process = process;
context.process.rss = prettysize(process.memoryUsage().rss);
context.process.heapTotal = prettysize(process.memoryUsage().heapTotal);
context.process.heapUsed = prettysize(process.memoryUsage().heapUsed);
context.intlTimezone = Intl.DateTimeFormat('en').resolvedOptions().timeZone;
context.dateTzOffset = Date().match(/([A-Z]+[\+-][0-9]{4}.*)/)[1];
if (req.headers) {
const protocol = req.socket.encrypted || req.headers['x-forwarded-proto']=='https' ? 'https' : 'http';
context.request = req;
// add in href & remote IP address (stackoverflow.com/questions/8107856)
context.request.href = protocol + '://' + req.headers.host + req.url;
context.request.ip_addr = req.headers['x-forwarded-for']
|| req.connection.remoteAddress
|| req.socket.remoteAddress
|| req.connection.socket.remoteAddress;
// cookies go in separate nested table
context.request.cookies = req.headers.cookie ? cookie.parse(req.headers.cookie) : {};
delete context.request.headers.cookie;
}
const templateFn = handlebars.compile(template);
const html = templateFn(context);
return html;
}
module.exports = nodeinfo; // ≡ export default nodeinfo