@contrast/route-metrics
Version:
`route-metrics` allows server performance, exclusive of network time, to be compared on a route-by-route basis. It was created to compare server performance with and without `@contrast/agent` being loaded and active.
104 lines (89 loc) • 2.86 kB
JavaScript
;
const Emitter = require('events');
const fs = require('node:fs');
const path = require('node:path');
const M = require('node:module');
const Require = module.constructor.prototype.require;
const patchmap = new Map();
const emitter = new Emitter();
// this is set when enable() is called
let logAllLoads = false;
// build the library of patchers at startup. it's only http code, but this is
// extensible if ever needed.
const patchers = fs.readdirSync(`${__dirname}/patchers`);
for (let i = 0; i < patchers.length; i++) {
if (!patchers[i].endsWith('.js')) {
continue;
}
const name = path.basename(patchers[i], '.js');
const patchInfo = {name, isPatched: false};
try {
patchInfo.patcher = require(`${__dirname}/patchers/${name}`);
patchmap.set(name, patchInfo);
} catch (e) {
emitter.emit('patch', {name, error: e});
}
}
/**
* @function patcher
*
* patcher replaces the module constructor's prototype require function. when a
* module is required, patcher is invoked. it uses the original require function
* to load the module and, if the module is in the patch map, invokes the code to
* patch the module.
*
* N.B. as of node 16.19.1 this will not be invoked when node internally loads a
* module via '-r' or '--require' on the command line. neither this nor the esm
* hooks are invoked with a module is loaded via --import.
*
* @param {string} name name of the module to be loaded
* @returns {object} the loaded module, possibly patched
*/
function patcher(name) {
let resolvedName;
try {
resolvedName = M._resolveFilename(name, this, false);
} catch (e) {
// not sure try matters here, so skip it? or let the error
// come from require, as it normally would? net, i don't think
// we can hit the following line AND not throw an error on the
// following require because it eventually makes a
// _resolveFilename call.
resolvedName = name;
}
const m = Require.call(this, name);
// if it's not present in the map then we don't want to patch it.
const patchInfo = patchmap.get(name);
if (!patchInfo || patchInfo.isPatched) {
// emit a "load" event for every require call if CSI_RM_LOG_ALL_LOADS is
// set
if (logAllLoads && !patchInfo) {
emitter.emit('load', {name: resolvedName});
}
return m;
}
try {
patchInfo.patcher(m, {name});
patchInfo.isPatched = true;
emitter.emit('patch', {name});
} catch (e) {
emitter.emit('error', {name, error: e});
}
return m;
}
patcher.enable = function(options = {}) {
if (options.logAllLoads) {
logAllLoads = true;
}
module.constructor.prototype.require = patcher;
};
patcher.disable = function() {
module.constructor.prototype.require = Require;
};
patcher.getPatchMap = function() {
return patchmap;
};
module.exports = {
patcher,
emitter,
};