UNPKG

@sap/cds

Version:

SAP Cloud Application Programming Model - CDS for Node.js

118 lines (107 loc) 4.2 kB
const cds = require ('../../index'), { decodeURIComponent } = cds.utils const LOG = cds.log('trace') if (!LOG._debug) module.exports = ()=>[]; else { module.exports = (o={}) => { // normalize options let { truncate:t = 111, skip = { BEGIN:1, COMMIT:2, ROLLBACK:3 } } = o || {} if (typeof t !== 'function') o.truncate = !t ? s=>s : s => s.length <= t ? s : s.slice(0,t)+' ...' if (typeof skip !== 'function') o.skip = !skip ? ()=>false : x => (x.event||x) in skip const _perf = e => !o.skip(e) && cds.context?.http?.req._perf // instrument framework services _instrument_cds_services (_perf) _instrument_better_sqlite (_perf) _instrument_sqlite (_perf) // the express middleware function return function cap_perf_logger (req, res, next) { let perf = req._perf || (req._perf = new PerfTrace) perf.log (req.method, decodeURIComponent(req.originalUrl)) res.on('finish', ()=> LOG.debug ('elapsed times:', perf.toString(o))) next() } } const { performance } = require ('perf_hooks') const { format } = require ('util') class PerfTrace extends Array { log (...details) { const e = { details, start:performance.now() } return this.push(e), e } done (e) { return e.stop = performance.now() } toString ({truncate}) { const t0 = this[0].start; if (!this[0].stop) this[0].stop = performance.now() return '\n'+ this.map (e => truncate (format ( (e.start - t0).toFixed(2).padStart(6), '→', (e.stop - t0).toFixed(2).padEnd(6), '=', (e.stop - e.start).toFixed(2).padStart(6), 'ms', '-', ...e.details)) ).join('\n') } } } function _instrument_cds_services (_get_perf) { const me = _instrument_cds_services; if (me.done) return; else me.done = true const { handle } = cds.Service.prototype cds.Service.prototype.handle = function (req) { const perf = _get_perf(req) if (perf) { const pe = perf.log (this.name, '-', req.event, req.path||'') var _done = () => perf.done(pe) } return handle.apply (this, arguments) .finally (_done) } } let sqlite function _instrument_sqlite (_get_perf) { const me = _instrument_sqlite; if (me.done) return; else me.done = true try { require.resolve('sqlite3') } catch { return } sqlite = require('sqlite3').Database.prototype for (let each of ['all', 'get', 'run', 'prepare']) _wrap(each,sqlite) function _wrap (op,sqlite) { const _super = sqlite[op] sqlite[op] = function (q, ..._) { const perf = _get_perf(q) //> q is a SQL command like BEGIN, COMMIT, ROLLBACK, SELECT ... if (perf) { const pe = perf.log ('sqlite3', '-', q) const callback = _[_.length-1]; _[_.length-1] = function(){ if (op === 'prepare') callback.apply (this, { all: _wrap('all',sqlite), get: _wrap('get',sqlite), run: _wrap('run',sqlite), }); else { perf.done(pe) callback.apply (this, arguments) } } } return _super.call (this, q, ..._) } } } function _instrument_better_sqlite (_get_perf) { const me = _instrument_better_sqlite; if (me.done) return; else me.done = true try { require.resolve('better-sqlite3') } catch { return } const sqlite = require('better-sqlite3').prototype for (let each of ['exec', 'prepare']) _wrap(each,sqlite) function _wrap (op,sqlite) { const _super = sqlite[op] sqlite[op] = function (q, ..._) { const perf = _get_perf(q) //> q is a SQL command like BEGIN, COMMIT, ROLLBACK, SELECT ... if (perf) { const pe = perf.log ('better-sqlite3', '-', q) try { const x = _super.call (this, q, ..._) if (op === 'prepare') return { all(..._){ try { return x.all(..._) } finally { perf.done(pe) }}, get(..._){ try { return x.get(..._) } finally { perf.done(pe) }}, run(..._){ try { return x.run(..._) } finally { perf.done(pe) }}, } else return perf.done(pe), x } catch(e) { perf.done(pe); throw e } } else return _super.call (this, q, ..._) } } }