Repository: MiniProfiler/node Branch: master Commit: 3d131ef909c7 Files: 56 Total size: 65.0 KB Directory structure: gitextract_fz3xrl6x/ ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .gitmodules ├── .npmignore ├── .travis.yml ├── README.md ├── lib/ │ ├── async-context.js │ ├── client-parser.js │ ├── middlewares/ │ │ ├── express.js │ │ ├── hapi.js │ │ └── koa.js │ ├── miniprofiler.js │ ├── storages/ │ │ ├── inmemory.js │ │ └── redis.js │ ├── ui.js │ └── utils.js ├── package.json └── tests/ ├── assets-test.js ├── basic-test.js ├── client-test.js ├── concurrent-async-test.js ├── custom-config-test.js ├── index.js ├── render-test.js ├── servers/ │ ├── async-provider.js │ ├── dummy-module.js │ ├── dummy-provider.js │ ├── express/ │ │ ├── async.js │ │ ├── custom-config.js │ │ ├── default.js │ │ ├── index.js │ │ ├── render.js │ │ ├── unauthorized.js │ │ └── unprofiled.js │ ├── hapi/ │ │ ├── async.js │ │ ├── custom-config.js │ │ ├── default.js │ │ ├── index.js │ │ ├── render.js │ │ ├── unauthorized.js │ │ └── unprofiled.js │ ├── index.js │ ├── koa/ │ │ ├── async.js │ │ ├── custom-config.js │ │ ├── default.js │ │ ├── index.js │ │ ├── render.js │ │ ├── unauthorized.js │ │ └── unprofiled.js │ └── views/ │ └── index.pug ├── share-test.js ├── step-test.js ├── timequery-test.js ├── unauthorized-test.js └── unprofiled-test.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .eslintignore ================================================ ui coverage ================================================ FILE: .eslintrc.json ================================================ { "extends": "eslint:recommended", "env": { "node": true, "es6": true, "mocha": true }, "rules": { "semi": [2, "always"], "require-yield": 0, "strict": ["error", "global"], "no-unused-vars": ["error", { "vars": "all", "args": "none" }], "quotes": ["error", "single", { "avoidEscape": true } ], "space-before-function-paren": ["error", "never"] } } ================================================ FILE: .gitignore ================================================ logs *.log npm-debug.log* pids *.pid *.seed *.rdb *.DS_STORE lib-cov coverage .nyc_output .grunt .lock-wscript build/Release node_modules jspm_packages .npm .node_repl_history ================================================ FILE: .gitmodules ================================================ [submodule "ui"] path = ui url = https://github.com/MiniProfiler/ui.git ================================================ FILE: .npmignore ================================================ coverage tests node_modules examples .travis.yml ================================================ FILE: .travis.yml ================================================ language: node_js node_js: - "8" - "stable" sudo: required services: - docker before_script: - npm run start-services - npm run lint - sleep 3 after_script: - npm run coverage - npm run check-coverage - npm run update-coveralls notifications: email: on_success: never on_failure: change ================================================ FILE: README.md ================================================ # MiniProfiler for Node.js Node.js implementation of Stack Exchange's MiniProfiler [![NPM](https://img.shields.io/npm/v/miniprofiler.svg)](https://www.npmjs.com/package/miniprofiler) [![Build](https://travis-ci.org/MiniProfiler/node.svg?branch=master)](https://travis-ci.org/MiniProfiler/node) [![Coverage](https://coveralls.io/repos/github/MiniProfiler/node/badge.svg?branch=master)](https://coveralls.io/github/MiniProfiler/node?branch=master) ![Dependencies](https://david-dm.org/MiniProfiler/node.svg) ![devDependencies](https://david-dm.org/MiniProfiler/node/dev-status.svg#info=devDependencies) ## Demonstration Visit [http://miniprofiler-demo.herokuapp.com](http://miniprofiler-demo.herokuapp.com) for a live demonstration. ## Installation ```bash $ npm install miniprofiler ``` You can hook up your application with any of the following packages are available on npm: | Name | About | Version | |-----------|-----------|-----------| | `miniprofiler-http` | Profile http(s) requests | [![NPM](https://img.shields.io/npm/v/miniprofiler-http.svg)](https://www.npmjs.com/package/miniprofiler-http) | | `miniprofiler-pg` | Profile [pg](https://www.npmjs.com/package/pg) queries | [![NPM](https://img.shields.io/npm/v/miniprofiler-pg.svg)](https://www.npmjs.com/package/miniprofiler-pg) | | `miniprofiler-redis`| Profile [redis](https://www.npmjs.com/package/redis) calls | [![NPM](https://img.shields.io/npm/v/miniprofiler-redis.svg)](https://www.npmjs.com/package/miniprofiler-redis) | ## Usage ### Simple usage with express.js `server.js` ```javascript var express = require('express') , miniprofiler = require('miniprofiler') , app = express(); app.set('view engine', 'pug'); app.use(miniprofiler.express()); app.get('/', function(req, res) { req.miniprofiler.step('Step 1', function() { req.miniprofiler.step('Step 2', function() { res.render('index'); }); }); }); app.listen(8080); ``` `index.pug` ```javascript doctype html html head title MiniProfiler Node.js Example body h1 Home Page | !{miniprofiler.include()} ``` When visiting `localhost:8080`, you should see this. ![](/examples/images/example0.png) ## API ### `miniprofiler.{framework}([options])` Replace `{framework}` with koa, express or hapi. This function returns a framework specific middleware that is responsible for initializing MiniProfiler on each request. #### `options` object properties | Property | Default | Description | |-----------|-----------|-------------| | enable | Always returns true | function(req, res) => boolean; this function is used to determine if the profiler should be enabled for the current request | | authorize | Always returns true | function(req, res) => boolean; this function is used to determine if the current request should be able to see the profiling results | ### `miniprofiler.{framework}.for([provider])` `provider` is a call for any of the supported providers listed [here](#installation). ### `miniprofiler.configure([options])` #### `options` object properties | Property | Default | Description | |-----------|-----------|-------------| | storage | InMemoryStorage({ max: 100, maxAge: 1000 \* 60 \* 60 }) | InMemoryStorage or RedisStorage; used to store or fetch a string JSON blob of profiling information | | ignoredPaths | [ ] | string array ; any request whose `url` property is in ignoredPaths will not be profiled | | trivialDurationThresholdMilliseconds | 2.5 | double ; any step lasting longer than this will be considered trivial, and hidden by default | | popupShowTimeWithChildren | false | boolean ; whether or not to include the "time with children" column | | popupRenderPosition | left | 'left', 'right', 'bottomLeft' or 'bottomRight' ; which side of the screen to display timings on | #### `options.storage` examples #### InMemoryStorage ``` miniprofiler.configure({ storage: miniprofiler.storage.InMemoryStorage({ lruCacheOptions }); }) ``` Refer to [lru-cache](https://www.npmjs.com/package/lru-cache) documentation for `lruCacheOptions`. #### RedisStorage ``` miniprofiler.configure({ storage: miniprofiler.storage.RedisStorage(client); }) ``` Where `client` is an instance of [redis.createClient](https://www.npmjs.com/package/redis). ================================================ FILE: lib/async-context.js ================================================ 'use strict'; const asyncHooks = require('async_hooks'); class AsyncContext { constructor() { this.map = new Map(); asyncHooks.createHook({ init: (id, _type, triggerId) => { if (this.map.has(triggerId)) this.map.set(id, this.map.get(triggerId)); }, destroy: (id) => this.map.delete(id) }).enable(); } get() { const id = asyncHooks.executionAsyncId(); if (this.map.has(id)) return this.map.get(id); } set(val) { this.map.set(asyncHooks.executionAsyncId(), val); } } module.exports = new AsyncContext(); ================================================ FILE: lib/client-parser.js ================================================ 'use strict'; var _ = require('./utils.js'); let insertInOrder = (array, timing) => { if (timing.Start <= 0) return; for(let key in array) { if (timing.Start <= array[key].Start) { return array.splice(key, 0, timing); } } return array.push(timing); }; module.exports = (postData) => { let preffix = 'clientPerformance[timing]['; let postDataTimings = { }; let clientTimings = [ ]; let navigationStart = 0; for(let postDataKey in postData) { if (postDataKey.startsWith(preffix)) { let key = postDataKey.substring(preffix.length, postDataKey.length - 1); if (key == 'navigationStart') navigationStart = parseInt(postData[postDataKey]); else postDataTimings[key] = parseInt(postData[postDataKey]); } } if (!navigationStart) return null; for(let key in postDataTimings) { if (key.endsWith('Start')) { let eventName = key.slice(0, -5); let eventStartTime = postDataTimings[`${eventName}Start`]; let eventEndTime = postDataTimings[`${eventName}End`]; let timing = { Name: _.toTitleCase(eventName), Start: eventStartTime - navigationStart, Duration: eventEndTime - eventStartTime }; if (!timing.Duration) { timing.Name = _.toTitleCase(`${eventName}Start`); timing.Duration = -1; } insertInOrder(clientTimings, timing); } else if (!key.endsWith('End')) { insertInOrder(clientTimings, { Name: _.toTitleCase(key), Start: postDataTimings[key] - navigationStart, Duration: -1 }); } } return { RedirectCount: parseInt(postData['clientPerformance[navigation][redirectCount]']), Timings: clientTimings }; }; ================================================ FILE: lib/middlewares/express.js ================================================ 'use strict'; const asyncContext = require('../async-context'); module.exports = { buildMiddleware: function(provider) { return function(req, res, next) { provider.handler(req, res, next); }; }, mainMiddleware: function(enable, authorize, handleRequest, cls) { return function(req, res, next) { handleRequest(enable, authorize, req, res).then((handled) => { res.locals.miniprofiler = req.miniprofiler; asyncContext.set(req.miniprofiler); Object.defineProperty(req, 'miniprofiler', { get: () => asyncContext.get() }); var render = res.render; res.render = function() { var renderArguments = arguments; req.miniprofiler.step(`Render: ${arguments[0]}`, function() { render.apply(res, renderArguments); }); }; if (!handled) next(); }).catch(next); }; } }; ================================================ FILE: lib/middlewares/hapi.js ================================================ 'use strict'; const asyncContext = require('../async-context'); module.exports = { buildMiddleware: function(provider) { var plugin = { register: (server, options, next) => { server.ext('onRequest', function(request, reply) { provider.handler(request.raw.req, request.raw.res, () => { return reply.continue(); }); }); next(); } }; plugin.register.attributes = { name: `miniprofiler-hapi-${provider.name}`, version: require('../../package.json').version }; return plugin; }, mainMiddleware: function(enable, authorize, handleRequest) { var plugin = { register: (server, options, next) => { server.ext('onRequest', function(request, reply) { handleRequest(enable, authorize, request.raw.req, request.raw.res).then((handled) => { asyncContext.set(request.raw.req.miniprofiler); Object.defineProperty(request.app, 'miniprofiler', { get: () => asyncContext.get() }); Object.defineProperty(request.raw.req, 'miniprofiler', { get: () => asyncContext.get() }); if (!handled) reply.continue(); }); }); next(); } }; plugin.register.attributes = { name: 'miniprofiler-hapi', version: require('../../package.json').version }; //That's a bad monkey patch, didn't like it, needs refactor... plugin.vision = (server) => { var view = server._replier._decorations['view']; server._replier._decorations['view'] = function(template, context, options) { var viewArguments = arguments; this.request.raw.req.miniprofiler.step(`Render: ${template}`, () => { return view.apply(this, viewArguments); }); }; }; return plugin; } }; ================================================ FILE: lib/middlewares/koa.js ================================================ 'use strict'; const asyncContext = require('../async-context'); module.exports = { buildMiddleware: function(provider) { return function *(next) { yield new Promise((resolve, reject) => { provider.handler(this.req, this.res, resolve); }); yield next; }; }, mainMiddleware: function(enable, authorize, handleRequest) { return function *(next) { var handled = yield handleRequest(enable, authorize, this.req, this.res); asyncContext.set(this.req.miniprofiler); Object.defineProperty(this.state, 'miniprofiler', { get: () => asyncContext.get() }); Object.defineProperty(this.req, 'miniprofiler', { get: () => asyncContext.get() }); if (this.render) { var render = this.render; this.render = function() { return new Promise((resolve, reject) => { var renderArguments = arguments; this.req.miniprofiler.step(`Render: ${arguments[0]}`, function() { render.apply(this, renderArguments); resolve(); }); }); }; } if (!handled) yield next; }; } }; ================================================ FILE: lib/miniprofiler.js ================================================ 'use strict'; /* * MiniProfiler implementation for node.js. * * Apache License, Version 2.0 * * Kevin Montrose, 2013 @kevin-montrose * Matt Jibson, 2013 @mjibsonF * Guilherme Oenning, 2016 @goenning */ var _ = require('./utils.js'); var qs = require('querystring'); var url = require('url'); var ui = require('./ui.js'); var clientParser = require('./client-parser.js'); const hostname = require('os').hostname; var ignoredPaths = []; var trivialDurationThresholdMilliseconds = 2.5; var popupShowTimeWithChildren = false; var popupRenderPosition = 'left'; var resourcePath = '/mini-profiler-resources/'; exports.storage = { InMemoryStorage: require('./storages/inmemory.js'), RedisStorage: require('./storages/redis.js') }; var storage = new exports.storage.InMemoryStorage({ max: 100, maxAge: 1000 * 60 * 60 }); exports.configure = configure; configure(); for (let framework of ['koa', 'express', 'hapi']) { let func = require(`./middlewares/${framework}.js`); exports[framework] = function(options) { options = options || {}; if (!options.enable) options.enable = () => { return true; }; if (!options.authorize) options.authorize = () => { return true; }; return func.mainMiddleware(options.enable, options.authorize, handleRequest); }; exports[framework].for = func.buildMiddleware; } var version = require('../package.json').version; var contentTypes = { css: 'text/css', js: 'text/javascript', tmpl: 'text/html; charset=utf-8' }; function getPath(req) { return url.parse(req.url).path; } function handleRequest(enable, authorize, req, res) { return new Promise((resolve, reject) => { var enabled = enable(req, res); var authorized = authorize(req, res); var requestPath = url.parse(req.url).pathname; for (let ignoredPath of ignoredPaths) { if (requestPath.startsWith(ignoredPath)) { enabled = false; break; } } if (!requestPath.startsWith(resourcePath)) { var extension = startProfiling(req, enabled, authorized); if (enabled) { res.on('finish', () => { stopProfiling(extension, req); }); res.setHeader('X-MiniProfiler-Ids', `["${extension.id}"]`); } return resolve(false); } if (!authorized) { res.writeHead(401, { 'Content-Type': 'text/plain; charset=utf-8' }); res.end(''); return resolve(true); } if (!enabled) { res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' }); res.end('MiniProfiler is disabled'); return resolve(true); } var segments = _.compact(requestPath.split('/')); var lastPathSegment = segments[segments.length - 1]; var handler = (lastPathSegment == 'results') ? results : assets; handler(req, res, lastPathSegment, (result) => { res.writeHead(result.status, { 'Content-Type': result.type }); res.end(result.body); resolve(true); }); }); } function assets(req, res, lastPathSegment, done) { ui.readFile(lastPathSegment, function(err, data) { if (err) { done({ type: 'text/plain; charset=utf-8', status: 404, body: 'Resource unavailable.' }); } else { var rs = lastPathSegment.split('.'); res.setHeader('Cache-Control', 'public, max-age=31557600'); done({ type: contentTypes[rs[rs.length - 1]], status: 200, body: data }); } }); } function results(req, res, lastPathSegment, done) { var proc = function(post, done) { var query = url.parse(req.url, true).query; var id = post.id || query.id; var popup = post.popup || query.popup; var timing = (req.method === 'POST') ? clientParser(post) : null; storage.get(id, (err, data) => { if (!data) { done({ type: 'text/plain; charset=utf-8', status: 404, body: `Id '${id}' not found.` }); return; } var json = JSON.parse(data); if (timing) { json.ClientTimings = timing; data = JSON.stringify(json); storage.set(id, data); } if (popup == '1') { done({ type: 'application/json', status: 200, body: data }); return; } done({ type: 'text/html; charset=utf-8', status: 200, body: ui.share({ name: json.Name, duration: json.DurationMilliseconds, path: resourcePath, json: data, includes: include(id), version: version }) }); }); }; var body = ''; req.on('data', function(data) { body += data; }); req.on('end', function() { var post = qs.parse(body); proc(post, done); }); } function include(id) { return ui.partial({ path: resourcePath, position: popupRenderPosition, showChildren: popupShowTimeWithChildren, trivialMilliseconds: trivialDurationThresholdMilliseconds, version: version, currentId: id, ids: id, showTrivial: true, maxTracesToShow: 15, showControls: true, authorized: true, toggleShortcut: '', startHidden: false }); } /* * Setup profiling. This function may only be called once, subsequent calls are ignored. * * This must be called before the first call to startProfiling. * * options is an optional object, which can have the following fields: * - storage: InMemoryStorage or RedisStorage; used to store or fetch a string JSON blob of profiling information * - ignoredPaths: string array ; any request whose `url` property is in ignoredPaths will not be profiled * - trivialDurationThresholdMilliseconds: double ; any step lasting longer than this will be considered trivial, and hidden by default * - popupShowTimeWithChildren: boolean ; whether or not to include the "time with children" column * - popupRenderPosition: 'left', 'right', 'bottomLeft', 'bottomRight' ; which side of the screen to display timings on * - resourcePath: string ; if your site root is in a subdirectory, specify here, e.g., /siteroot */ function configure(options) { options = options || {}; ignoredPaths = options.ignoredPaths || ignoredPaths; trivialDurationThresholdMilliseconds = options.trivialDurationThresholdMilliseconds || trivialDurationThresholdMilliseconds; popupShowTimeWithChildren = options.popupShowTimeWithChildren || popupShowTimeWithChildren; popupRenderPosition = options.popupRenderPosition || popupRenderPosition; storage = options.storage || storage; resourcePath = `${options.resourcePath ? options.resourcePath.replace(/\/$/, '') : ''}${resourcePath}`; } /* * Begins profiling the given request. */ function startProfiling(request, enabled, authorized) { var currentRequestExtension = { enabled: enabled, authorized: authorized }; if (enabled) { var path = getPath(request); currentRequestExtension.id = _.uuid(); currentRequestExtension.startDate = Date.now(); currentRequestExtension.startTime = process.hrtime(); currentRequestExtension.stopTime = null; currentRequestExtension.stepGraph = makeStep(path, currentRequestExtension.startTime, null); currentRequestExtension.customTimings = {}; } currentRequestExtension.timeQuery = function() { var args = Array.prototype.slice.call(arguments, enabled ? 0 : 3); if (enabled) { args.unshift(currentRequestExtension); timeQuery.apply(this, args); } else { arguments[2].apply(this, args); } }; currentRequestExtension.startTimeQuery = function(type, query) { return startTimeQuery.call(this, currentRequestExtension, type, query); }; currentRequestExtension.stopTimeQuery = function(timing) { return stopTimeQuery.call(this, timing); }; currentRequestExtension.step = function(name, call) { if (enabled) { step(name, request, call); } else { call(); } }; currentRequestExtension.include = function() { return enabled && authorized ? include(currentRequestExtension.id) : ''; }; request.miniprofiler = currentRequestExtension; return currentRequestExtension; } /* * Stops profiling the given request. */ function stopProfiling(extension, request) { var time = process.hrtime(); extension.stopTime = time; extension.stepGraph.stopTime = time; var json = describePerformance(extension, request); storage.set(extension.id, JSON.stringify(json)); } /* * Wraps an invokation of `call` in a step named `name`. * * You should only use this method directly in cases when calls to addProfiling won't suffice. */ function step(name, request, call) { var time = process.hrtime(); var extension = request.miniprofiler; var newStep = makeStep(name, time, extension.stepGraph); extension.stepGraph.steps.push(newStep); extension.stepGraph = newStep; var result; if (call.length) { result = call(() => { unstep(name, request); }); } else { try { result = call(); } finally { unstep(name, request); } } return result; } /* * Called to time a query, like to SQL or Redis, that completes with a callback * * `type` can be any string, it is used to group query types in timings. * `query` is a string representing the query, this is what is recorded as having run. * * `executeFunction` is invoked with any additional parameters following it. * * Any function passed as a parameter to `executeFunction` will be instrumented to detect * when the query has completed. Implicitly, any execution of a callback is considered * to have ended the query. */ function timeQuery(extension, type, query, executeFunction) { var timing = startTimeQuery(extension, type, query); var params = Array.prototype.slice.call(arguments, 4); for (var i = 0; i < params.length; i++) { if (_.isFunction(params[i])) { var param = params[i]; params[i] = function() { extension.stopTimeQuery(timing); var ret = param.apply(this, arguments); return ret; }; } } var ret = executeFunction.apply(this, params); return ret; } function stopTimeQuery(timing) { timing.stopTime = process.hrtime(); } function startTimeQuery(extension, type, query) { var time = process.hrtime(); var startDate = Date.now(); extension.stepGraph.customTimings[type] = extension.stepGraph.customTimings[type] || []; var customTiming = { id: _.uuid(), executeType: type, commandString: _.escape(query), startTime: time, startDate: startDate, callStack: new Error().stack }; extension.stepGraph.customTimings[type].push(customTiming); return customTiming; } function unstep(name, request) { var time = process.hrtime(); var extension = request.miniprofiler; extension.stepGraph.stopTime = time; // step back up extension.stepGraph = extension.stepGraph.parent; } function describePerformance(root, request) { var ret = {}; ret.Id = root.id; ret.Name = getPath(request); ret.Started = root.startDate; ret.MachineName = hostname(); ret.Root = describeTimings(root.stepGraph, root.stepGraph); ret.ClientTimings = null; ret.DurationMilliseconds = ret.Root.DurationMilliseconds; return ret; } function diff(start, stop) { var deltaSecs = stop[0] - start[0]; var deltaNanoSecs = stop[1] - start[1]; var elapsedMs = deltaSecs * 1000 + deltaNanoSecs / 1000000; return elapsedMs; } function callStack(stack) { var sp = stack.split('\n'); var ret = []; for (var i = 2; i < sp.length; i++) { var st = sp[i].trim().split(' '); ret.push(st[1]); } return ret.join(' '); } function describeTimings(timing, root) { var id = _.uuid(); var name = timing.name; var elapsedMs = diff(timing.startTime, timing.stopTime); var sinceRootMs = diff(root.startTime, timing.startTime); var customTimings = describeCustomTimings(timing.customTimings, root); var children = []; for (var i = 0; i < timing.steps.length; i++) { var step = timing.steps[i]; children.push(describeTimings(step, root)); } return { Id: id, Name: name, DurationMilliseconds: elapsedMs, StartMilliseconds: sinceRootMs, Children: children, CustomTimings: customTimings }; } function describeCustomTimings(customTimings, root) { var ret = {}; for (var prop in customTimings) { var arr = customTimings[prop]; var retArr = []; for (var i = 0; i < arr.length; i++) { var timing = {}; timing.Id = arr[i].id; timing.ExecuteType = arr[i].executeType; timing.CommandString = arr[i].commandString; timing.StartMilliseconds = diff(root.startTime, arr[i].startTime); timing.DurationMilliseconds = diff(arr[i].startTime, arr[i].stopTime); timing.StackTraceSnippet = callStack(arr[i].callStack); retArr.push(timing); } ret[prop] = retArr; } return ret; } function makeStep(name, time, parent) { return { name: name, startTime: time, stopTime: null, parent: parent, steps: [], customTimings: {} }; } ================================================ FILE: lib/storages/inmemory.js ================================================ 'use strict'; var LRU = require('lru-cache'); let miniprofilerHashKey = '_miniprofiler_'; function InMemoryStorage(options) { this.key = function(id) { return `${miniprofilerHashKey}${id}`; }; this.cache = LRU(options); this.get = function(id, callback) { var data = this.cache.get(this.key(id)); if (data) callback(null, data); else callback(new Error(`Id '${id}' not found.`)); }; this.set = function(id, json) { this.cache.set(this.key(id), json); }; } module.exports = InMemoryStorage; ================================================ FILE: lib/storages/redis.js ================================================ 'use strict'; let miniprofilerHashKey = '_miniprofiler_'; function RedisStorage(redisClient, maxAge) { this.maxAge = maxAge || 3600; this.key = function(id) { return `${miniprofilerHashKey}${id}`; }; this.get = function(id, callback) { redisClient.get(this.key(id), callback); }; this.set = function(id, json) { let key = this.key(id); redisClient.set(key, json, (err, data) => { redisClient.expire(key, this.maxAge); }); }; } module.exports = RedisStorage; ================================================ FILE: lib/ui.js ================================================ 'use strict'; const fileStore = { }; const fs = require('fs'); const path = require('path'); const _ = require('./utils.js'); const includesDir = path.join(__dirname, '../ui'); const readFile = (name, callback) => { if (fileStore[name]) { callback(null, fileStore[name]); } else { fs.readFile(path.join(includesDir, name), 'utf-8', (err, text) => { fileStore[name] = text; callback(err, text); }); } }; const templates = { partial: _.template(fs.readFileSync(path.join(includesDir, 'include.partial.html')).toString()), share: _.template(fs.readFileSync(path.join(includesDir, 'share.html')).toString()) }; const partial = (options) => templates.partial(options); const share = (options) => templates.share(options); module.exports = { readFile, partial, share }; ================================================ FILE: lib/utils.js ================================================ 'use strict'; const isFunction = (obj) => { return typeof obj == 'function' || false; }; const tagsToReplace = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '`': '`' }; const escape = (str) => { return str.replace(/[&<>]/g, function(tag) { return tagsToReplace[tag] || tag; }); }; const compact = (arr) => { return arr.filter((v) => v); }; const uuid = () => { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); return v.toString(16); }); }; const toTitleCase = (str) => { return str.replace(/ /g,'').split(/(?=[A-Z])/).join(' ').replace(/^.| ./g, (m) => { return m.toUpperCase(); }); }; const template = (content) => { return (options) => { return content.replace(/{(.+?)}/g, (match, key) => { return options[key]; }); }; }; module.exports = { isFunction, escape, compact, uuid, toTitleCase, template }; ================================================ FILE: package.json ================================================ { "name": "miniprofiler", "version": "2.0.0", "description": "A simple but effective mini-profiler.", "main": "lib/miniprofiler.js", "scripts": { "start-services": "docker run -d -p 6060:6379 redis", "lint": "eslint .", "test": "mocha tests/ -c", "coverage": "istanbul cover ./node_modules/mocha/bin/_mocha -- tests/ -R spec", "check-coverage": "istanbul check-coverage --statements 95 --branches 95 --functions 95 --lines 95", "update-coveralls": "cat coverage/lcov.info | node ./node_modules/coveralls/bin/coveralls.js" }, "repository": { "type": "git", "url": "https://github.com/MiniProfiler/node.git" }, "bugs": { "url": "http://github.com/MiniProfiler/node/issues" }, "author": "Guilherme Oenning (http://goenning.net/)", "contributors": [ "Matt Jibson (https://mattjibson.com/)", "Kevin Montrose" ], "license": "Apache-2.0", "readmeFilename": "README.md", "dependencies": { "lru-cache": "^4.0.1" }, "tags": [ "profiler", "performance", "profiling", "timing", "web profiling" ], "devDependencies": { "chai": "^3.5.0", "coveralls": "^2.11.11", "docker-ip": "^2.0.1", "eslint": "^6.6.0", "express": "^4.13.4", "hapi": "^13.5.0", "istanbul": "^0.4.3", "koa": "^1.2.1", "koa-route": "^2.4.2", "koa-views": "^4.1.0", "debug": "^2.6.1", "mocha": "^2.5.3", "pug": "^2.0.0-beta2", "redis": "^3.1.1", "request": "^2.73.0", "vision": "^4.1.0" } } ================================================ FILE: tests/assets-test.js ================================================ 'use strict'; var expect = require('chai').expect; var fs = require('fs'); module.exports = function(server) { describe('Assets Tests', function() { before(server.setUp.bind(null, 'default')); after(server.tearDown); var files = [ 'includes.css', 'includes.css', 'includes.tmpl', 'includes.js' ]; files.forEach((file) => { it(`Should return ${file} file`, function(done) { server.get(`/mini-profiler-resources/${file}`, (err, response, body) => { fs.readFile(`./ui/${file}`, 'utf-8', (err, content) => { expect(body).to.be.equal(content); done(); }); }); }); }); it('Unknown file should return 404', function(done) { server.get('/mini-profiler-resources/unknown.js', (err, response, body) => { expect(response.statusCode).to.be.equal(404); expect(body).to.be.equal('Resource unavailable.'); expect(response.headers['content-type']).to.be.equal('text/plain; charset=utf-8'); done(); }); }); }); }; ================================================ FILE: tests/basic-test.js ================================================ 'use strict'; var expect = require('chai').expect; var pkg = require('../package.json'); module.exports = function(server) { describe('Basic Tests', function() { before(server.setUp.bind(null, 'default')); after(server.tearDown); it('Profiled routes should always return Profiler ID', function(done) { server.get('/', (err, response) => { expect(response.headers).to.include.keys('x-miniprofiler-ids'); done(); }); }); it('Index page should include MiniProfiler javascript', function(done) { server.get('/', (err, response, body) => { var ids = JSON.parse(response.headers['x-miniprofiler-ids']); expect(ids).to.have.lengthOf(1); expect(body.trim()).to.be.equal(``); done(); }); }); it('Should return url parameters on results response', function(done) { server.get('/?key1=value1&key2=value2', (err, response, body) => { var ids = JSON.parse(response.headers['x-miniprofiler-ids']); expect(ids).to.have.lengthOf(1); server.get(`/mini-profiler-resources/results?id=${ids[0]}&popup=1`, (err, response, body) => { var result = JSON.parse(body); expect(result.Id).to.equal(ids[0]); expect(result.Name).to.equal('/?key1=value1&key2=value2'); expect(result.Root.Children).to.be.empty; done(); }); }); }); }); }; ================================================ FILE: tests/client-test.js ================================================ 'use strict'; var expect = require('chai').expect; module.exports = function(server) { describe('Client Timing Tests', function() { before(server.setUp.bind(null, 'default')); after(server.tearDown); it('should return client timing data that is sent on POST', function(done) { server.get('/', (err, response) => { var ids = JSON.parse(response.headers['x-miniprofiler-ids']); server.post('/mini-profiler-resources/results', { id: ids[0], popup: 1, clientPerformance: { navigation: { redirectCount: 0 }, timing: { navigationStart: 1000, responseEnd: 1014, loadEventStart: 1080, requestStart: 1001, secureConnectionStart: 0, loadEventEnd: 1112, responseStart: 1002, 'First Paint Time':1200 } } }, (err, response, body) => { var data = JSON.parse(body); expect(data.ClientTimings).to.be.deep.equal({ RedirectCount: 0, Timings: [{ Name: 'Request Start', Start: 1, Duration: -1 },{ Name: 'Response', Start: 2, Duration: 12 },{ Name: 'Load Event', Start: 80, Duration: 32 },{ Name: 'First Paint Time', Start: 200, Duration: -1 }] }); done(); }); }); }); it('should not return client timing when data is not sent via POST', function(done) { server.get('/', (err, response) => { var ids = JSON.parse(response.headers['x-miniprofiler-ids']); server.post('/mini-profiler-resources/results', { id: ids[0], popup: 1 }, (err, response, body) => { var data = JSON.parse(body); expect(data.ClientTimings).to.be.null; done(); }); }); }); }); }; ================================================ FILE: tests/concurrent-async-test.js ================================================ 'use strict'; var expect = require('chai').expect; module.exports = function(server) { describe('Concurrent Async Requests', function() { before(server.setUp.bind(null, 'async')); after(server.tearDown); it('Each profile runs on its own context', function(done) { let countDone = 0; const partialDone = () => { if (++countDone === 2) done(); }; server.get('/', (err, response) => { var ids = JSON.parse(response.headers['x-miniprofiler-ids']); expect(ids).to.have.lengthOf(1); server.post('/mini-profiler-resources/results/', { id: ids[0], popup: 1 }, (err, response, body) => { var result = JSON.parse(body); expect(result.Root.CustomTimings.async).to.have.lengthOf(2); partialDone(); }); }); server.get('/?once=true', (err, response) => { var ids = JSON.parse(response.headers['x-miniprofiler-ids']); expect(ids).to.have.lengthOf(1); server.post('/mini-profiler-resources/results/', { id: ids[0], popup: 1 }, (err, response, body) => { var result = JSON.parse(body); expect(result.Root.CustomTimings.async).to.have.lengthOf(1); partialDone(); }); }); }); }); }; ================================================ FILE: tests/custom-config-test.js ================================================ 'use strict'; var expect = require('chai').expect; var pkg = require('../package.json'); module.exports = function(server) { describe('Custom Configuration Tests', function() { this.timeout(5000); before(server.setUp.bind(null, 'custom-config')); after(server.tearDown); it('should include MiniProfiler javascript with custom settings', function(done) { server.get('/', (err, response, body) => { var ids = JSON.parse(response.headers['x-miniprofiler-ids']); expect(ids).to.have.lengthOf(1); expect(body.trim()).to.be.equal(``); done(); }); }); it('should get/set timing from redis storage', function(done) { server.get('/?key=value', (err, response, body) => { var ids = JSON.parse(response.headers['x-miniprofiler-ids']); expect(ids).to.have.lengthOf(1); server.post('/mini-profiler-resources/results', { id: ids[0], popup: 1 }, (err, response, body) => { var result = JSON.parse(body); expect(result.Id).to.equal(ids[0]); expect(result.Name).to.equal('/?key=value'); expect(result.Root.Children).to.be.empty; done(); }); }); }); it('should not get timing about from ignored paths', function(done) { server.get('/hidden', (err, response, body) => { expect(response.headers).to.not.include.keys('x-miniprofiler-ids'); done(); }); }); }); }; ================================================ FILE: tests/index.js ================================================ 'use strict'; var fs = require('fs'); var servers = require('./servers'); var testCases = fs.readdirSync('./tests').filter((file) => file.endsWith('-test.js')); for (var server of servers) { describe(`[${server.framework}]`, function() { for (var testCase of testCases) { require(`./${testCase}`)(server); } }); } ================================================ FILE: tests/render-test.js ================================================ 'use strict'; var expect = require('chai').expect; module.exports = function(server) { describe('Render Tests', function() { before(server.setUp.bind(null, 'render')); after(server.tearDown); it('Should add render step', function(done) { server.get('/', (err, response) => { var ids = JSON.parse(response.headers['x-miniprofiler-ids']); expect(ids).to.have.lengthOf(1); server.post('/mini-profiler-resources/results', { id: ids[0], popup: 1 }, (err, response, body) => { var result = JSON.parse(body); expect(result.Id).to.equal(ids[0]); expect(result.Name).to.equal('/'); expect(result.Root.Children).to.have.lengthOf(1); expect(result.Root.Children[0].Name).to.equal('Render: index'); expect(result.Root.Children[0].Children).to.be.empty; done(); }); }); }); it('Should add render step inside another step', function(done) { server.get('/inside-step', (err, response) => { var ids = JSON.parse(response.headers['x-miniprofiler-ids']); expect(ids).to.have.lengthOf(1); server.post('/mini-profiler-resources/results', { id: ids[0], popup: 1 }, (err, response, body) => { var result = JSON.parse(body); expect(result.Id).to.equal(ids[0]); expect(result.Name).to.equal('/inside-step'); expect(result.Root.Children).to.have.lengthOf(1); expect(result.Root.Children[0].Name).to.equal('Step 1'); expect(result.Root.Children[0].Children).to.have.lengthOf(1); expect(result.Root.Children[0].CustomTimings).to.have.property('custom'); expect(result.Root.Children[0].CustomTimings.custom).to.have.lengthOf(1); expect(result.Root.Children[0].Children[0].Name).to.be.equal('Render: index'); expect(result.Root.Children[0].Children[0].Children).to.be.empty; done(); }); }); }); }); }; ================================================ FILE: tests/servers/async-provider.js ================================================ 'use strict'; module.exports = function(obj) { return { name: 'dummy-async', handler: function(req, res, next) { obj.asyncFn = function() { const timing = req.miniprofiler.startTimeQuery('async', 'dummy call'); return new Promise(resolve => { setTimeout(() => { req.miniprofiler.stopTimeQuery(timing); resolve(); }, 25); }); }; next(); } }; }; ================================================ FILE: tests/servers/dummy-module.js ================================================ 'use strict'; module.exports = { asyncFn: () => Promise.resolve() }; ================================================ FILE: tests/servers/dummy-provider.js ================================================ 'use strict'; module.exports = function() { return { name: 'dummy', handler: function(req, res, next) { next(); } }; }; ================================================ FILE: tests/servers/express/async.js ================================================ 'use strict'; var miniprofiler = require('../../../lib/miniprofiler.js'); var dummyModule = require('../dummy-module'); var express = require('express'); var app = express(); app.use(miniprofiler.express()); app.use(miniprofiler.express.for(require('../async-provider.js')(dummyModule))); app.get('/', (req, res) => { dummyModule.asyncFn().then(() => { Promise.resolve(req.query.once ? undefined : dummyModule.asyncFn()) .then(() => res.send(res.locals.miniprofiler.include())); }); }); module.exports = app; ================================================ FILE: tests/servers/express/custom-config.js ================================================ 'use strict'; var miniprofiler = require('../../../lib/miniprofiler.js'); var express = require('express'); var ip = require('docker-ip'); var redis = require('redis'); var client = redis.createClient(6060, ip()); var app = express(); miniprofiler.configure({ popupRenderPosition: 'right', storage: new miniprofiler.storage.RedisStorage(client), ignoredPaths: [ '/hidden' ] }); app.use(miniprofiler.express()); app.get('/', (req, res) => { res.send(res.locals.miniprofiler.include()); }); app.get('/hidden', (req, res) => { res.send('This won\'t be profiled.'); }); module.exports = app; ================================================ FILE: tests/servers/express/default.js ================================================ 'use strict'; var miniprofiler = require('../../../lib/miniprofiler.js'); var express = require('express'); var app = express(); app.use(miniprofiler.express()); app.use(miniprofiler.express.for(require('../dummy-provider.js')())); app.get('/', (req, res) => { res.send(res.locals.miniprofiler.include()); }); app.get('/step', (req, res) => { req.miniprofiler.step('Step', () => { res.send(res.locals.miniprofiler.include()); }); }); app.get('/step-two', (req, res) => { req.miniprofiler.step('Step 1', () => { req.miniprofiler.step('Step 2', () => { res.send(res.locals.miniprofiler.include()); }); }); }); app.get('/step-parallel', (req, res) => { var count = 0; var finish = () => { if (++count == 2) res.send(res.locals.miniprofiler.include()); }; req.miniprofiler.step('Step 1', finish); req.miniprofiler.step('Step 2', finish); }); app.get('/js-sleep', function(req, res) { req.miniprofiler.timeQuery('custom', 'Sleeping...', setTimeout, function() { res.send(res.locals.miniprofiler.include()); }, 50); }); app.get('/js-sleep-start-stop', function(req, res) { var timing = req.miniprofiler.startTimeQuery('custom', 'Sleeping...'); setTimeout(function() { req.miniprofiler.stopTimeQuery(timing); res.send(res.locals.miniprofiler.include()); }, 50); }); module.exports = app; ================================================ FILE: tests/servers/express/index.js ================================================ 'use strict'; var server; module.exports = { start: function(name, port, done) { var app = require(`./${name}.js`); server = app.listen(port, done); }, stop: function(done) { server.close(done); } }; ================================================ FILE: tests/servers/express/render.js ================================================ 'use strict'; var miniprofiler = require('../../../lib/miniprofiler.js'); var express = require('express'); var app = express(); app.use(miniprofiler.express()); app.set('view engine', 'pug'); app.set('views', './tests/servers/views'); app.get('/', (req, res) => { res.render('index', { title: 'Hey', message: 'Hello there!' }); }); app.get('/inside-step', (req, res) => { req.miniprofiler.step('Step 1', (unstep) => { req.miniprofiler.timeQuery('custom', 'Sleeping...', setTimeout, function() { res.render('index', { title: 'Hey', message: 'Hello there!' }); unstep(); }, 50); }); }); module.exports = app; ================================================ FILE: tests/servers/express/unauthorized.js ================================================ 'use strict'; var miniprofiler = require('../../../lib/miniprofiler.js'); var express = require('express'); var app = express(); var options = { authorize: (req) => { return false; } }; app.use(miniprofiler.express(options)); app.get('/', (req, res) => { res.send(res.locals.miniprofiler.include()); }); module.exports = app; ================================================ FILE: tests/servers/express/unprofiled.js ================================================ 'use strict'; var miniprofiler = require('../../../lib/miniprofiler.js'); var express = require('express'); var app = express(); var options = { enable: (req) => { return false; } }; app.use(miniprofiler.express(options)); app.get('/', (req, res) => { req.miniprofiler.timeQuery('custom', 'Sleeping...', setTimeout, function() { req.miniprofiler.step('Step 1', () => { res.send(res.locals.miniprofiler.include()); }); }, 50); }); module.exports = app; ================================================ FILE: tests/servers/hapi/async.js ================================================ 'use strict'; var miniprofiler = require('../../../lib/miniprofiler.js'); var dummyModule = require('../dummy-module'); const Hapi = require('hapi'); const server = new Hapi.Server(); server.connection({ port: 8083 }); server.register(miniprofiler.hapi(), (err) => { if (err) throw err; }); server.register(miniprofiler.hapi.for(require('../async-provider.js')(dummyModule)), (err) => { if (err) throw err; }); server.route({ method: 'GET', path:'/', handler: function(request, reply) { dummyModule.asyncFn().then(() => { Promise.resolve(request.query.once ? undefined : dummyModule.asyncFn()) .then(() => reply(request.app.miniprofiler.include())); }); } }); module.exports = server; ================================================ FILE: tests/servers/hapi/custom-config.js ================================================ 'use strict'; var miniprofiler = require('../../../lib/miniprofiler.js'); const Hapi = require('hapi'); var ip = require('docker-ip'); var redis = require('redis'); var client = redis.createClient(6060, ip()); const server = new Hapi.Server(); server.connection({ port: 8083 }); miniprofiler.configure({ popupRenderPosition: 'right', storage: new miniprofiler.storage.RedisStorage(client), ignoredPaths: [ '/hidden' ] }); server.register(miniprofiler.hapi(), (err) => { if (err) throw (err); }); server.route({ method: 'GET', path:'/', handler: function(request, reply) { return reply(request.app.miniprofiler.include()); } }); server.route({ method: 'GET', path:'/hidden', handler: function(request, reply) { return reply('This won\'t be profiled.'); } }); module.exports = server; ================================================ FILE: tests/servers/hapi/default.js ================================================ 'use strict'; var miniprofiler = require('../../../lib/miniprofiler.js'); const Hapi = require('hapi'); const server = new Hapi.Server(); server.connection({ port: 8083 }); server.register(miniprofiler.hapi(), (err) => { if (err) throw err; }); server.register(miniprofiler.hapi.for(require('../dummy-provider.js')()), (err) => { if (err) throw err; }); server.route({ method: 'GET', path:'/', handler: function(request, reply) { return reply(request.app.miniprofiler.include()); } }); server.route({ method: 'GET', path:'/step', handler: function(request, reply) { request.raw.req.miniprofiler.step('Step', () => { return reply(request.app.miniprofiler.include()); }); } }); server.route({ method: 'GET', path:'/step-two', handler: function(request, reply) { request.raw.req.miniprofiler.step('Step 1', () => { request.raw.req.miniprofiler.step('Step 2', () => { return reply(request.app.miniprofiler.include()); }); }); } }); server.route({ method: 'GET', path:'/step-parallel', handler: function(request, reply) { var count = 0; var finish = () => { if (++count == 2) return reply(request.app.miniprofiler.include()); }; request.raw.req.miniprofiler.step('Step 1', finish); request.raw.req.miniprofiler.step('Step 2', finish); } }); server.route({ method: 'GET', path:'/js-sleep', handler: function(request, reply) { request.raw.req.miniprofiler.timeQuery('custom', 'Sleeping...', setTimeout, () => { return reply(request.app.miniprofiler.include()); }, 50); } }); server.route({ method: 'GET', path:'/js-sleep-start-stop', handler: function(request, reply) { var timing = request.raw.req.miniprofiler.startTimeQuery('custom', 'Sleeping...'); setTimeout(function() { request.raw.req.miniprofiler.stopTimeQuery(timing); return reply(request.app.miniprofiler.include()); }, 50); } }); module.exports = server; ================================================ FILE: tests/servers/hapi/index.js ================================================ 'use strict'; var server; module.exports = { start: function(name, port, done) { server = require(`./${name}.js`); server.start(done); }, stop: function(done) { server.stop({ }, done); } }; ================================================ FILE: tests/servers/hapi/render.js ================================================ 'use strict'; var miniprofiler = require('../../../lib/miniprofiler.js'); const Hapi = require('hapi'); const vision = require('vision'); const server = new Hapi.Server(); server.connection({ port: 8083 }); server.register(miniprofiler.hapi(), (err) => { if (err) throw (err); }); server.register(vision, (err) => { if (err) throw (err); server.views({ engines: { pug: require('pug') }, path: './tests/servers/views' }); miniprofiler.hapi().vision(server); }); server.route({ method: 'GET', path:'/', handler: function(request, reply) { reply.view('index', { title: 'Hey', message: 'Hello there!' }); } }); server.route({ method: 'GET', path:'/inside-step', handler: function(request, reply) { request.app.miniprofiler.step('Step 1', (unstep) => { request.app.miniprofiler.timeQuery('custom', 'Sleeping...', setTimeout, function() { reply.view('index', { title: 'Hey', message: 'Hello there!' }); unstep(); }, 50); }); } }); module.exports = server; ================================================ FILE: tests/servers/hapi/unauthorized.js ================================================ 'use strict'; var miniprofiler = require('../../../lib/miniprofiler.js'); const Hapi = require('hapi'); const server = new Hapi.Server(); server.connection({ port: 8083 }); var options = { authorize: (req) => { return false; } }; server.register(miniprofiler.hapi(options), (err) => { if (err) throw (err); }); server.route({ method: 'GET', path:'/', handler: function(request, reply) { return reply(request.app.miniprofiler.include()); } }); module.exports = server; ================================================ FILE: tests/servers/hapi/unprofiled.js ================================================ 'use strict'; var miniprofiler = require('../../../lib/miniprofiler.js'); const Hapi = require('hapi'); const server = new Hapi.Server(); server.connection({ port: 8083 }); var options = { enable: (req) => { return false; } }; server.register(miniprofiler.hapi(options), (err) => { if (err) throw (err); }); server.route({ method: 'GET', path:'/', handler: function(request, reply) { request.raw.req.miniprofiler.timeQuery('custom', 'Sleeping...', setTimeout, () => { request.raw.req.miniprofiler.step('Step 1', () => { return reply(request.app.miniprofiler.include()); }); }, 50); } }); module.exports = server; ================================================ FILE: tests/servers/index.js ================================================ 'use strict'; var request = require('request'); var frameworks = { 'koa': { 'port': 8081 }, 'express': { 'port': 8082 }, 'hapi': { 'port': 8083 } }; var all = [ ]; for (let fw in frameworks) { var server = require(`./${fw}`); server.framework = fw; frameworks[fw].server = server; server.setUp = function(name, done) { Object.keys(require.cache).forEach((key) => { delete require.cache[key]; }); frameworks[fw].server.start(name, frameworks[fw].port, done); }; server.tearDown = function(done) { frameworks[fw].server.stop(done); }; server.get = (path, cb) => { request.get(`http://localhost:${frameworks[fw].port}${path}`, (err, response, body) => { cb(err, response, body); }); }; server.post = (path, params, cb) => { request.post({url: `http://localhost:${frameworks[fw].port}${path}`, form: params }, (err, response, body) => { cb(err, response, body); }); }; all.push(server); } module.exports = all; ================================================ FILE: tests/servers/koa/async.js ================================================ 'use strict'; var miniprofiler = require('../../../lib/miniprofiler.js'); var dummyModule = require('../dummy-module'); var koa = require('koa'); var route = require('koa-route'); var app = koa(); app.use(miniprofiler.koa()); app.use(miniprofiler.koa.for(require('../async-provider.js')(dummyModule))); app.use(route.get('/', function *(){ yield dummyModule.asyncFn().then(() => { return Promise.resolve(this.query.once ? undefined : dummyModule.asyncFn()) .then(() => { this.body = this.state.miniprofiler.include(); }); }); })); module.exports = app; ================================================ FILE: tests/servers/koa/custom-config.js ================================================ 'use strict'; var miniprofiler = require('../../../lib/miniprofiler.js'); var koa = require('koa'); var route = require('koa-route'); var app = koa(); var ip = require('docker-ip'); var redis = require('redis'); var client = redis.createClient(6060, ip()); miniprofiler.configure({ popupRenderPosition: 'right', storage: new miniprofiler.storage.RedisStorage(client), ignoredPaths: [ '/hidden' ] }); app.use(miniprofiler.koa()); app.use(route.get('/', function *(){ this.body = this.state.miniprofiler.include(); })); app.use(route.get('/hidden', function *(){ this.body = 'This won\'t be profiled.'; })); module.exports = app; ================================================ FILE: tests/servers/koa/default.js ================================================ 'use strict'; var miniprofiler = require('../../../lib/miniprofiler.js'); var koa = require('koa'); var route = require('koa-route'); var app = koa(); app.use(miniprofiler.koa()); app.use(miniprofiler.koa.for(require('../dummy-provider.js')())); app.use(route.get('/', function *(){ this.body = this.state.miniprofiler.include(); })); app.use(route.get('/step', function *(){ this.req.miniprofiler.step('Step', () => { this.body = this.state.miniprofiler.include(); }); })); app.use(route.get('/step-two', function *(){ this.req.miniprofiler.step('Step 1', () => { this.req.miniprofiler.step('Step 2', () => { this.body = this.state.miniprofiler.include(); }); }); })); app.use(route.get('/step-parallel', function *(){ var count = 0; var finish = () => { if (++count == 2) this.body = this.state.miniprofiler.include(); }; this.req.miniprofiler.step('Step 1', finish); this.req.miniprofiler.step('Step 2', finish); })); app.use(route.get('/js-sleep', function *(){ yield new Promise((resolve, reject) => { this.req.miniprofiler.timeQuery('custom', 'Sleeping...', setTimeout, () => { this.body = this.state.miniprofiler.include(); resolve(); }, 50); }); })); app.use(route.get('/js-sleep-start-stop', function *(){ yield new Promise((resolve, reject) => { var timing = this.req.miniprofiler.startTimeQuery('custom', 'Sleeping...'); setTimeout(() => { this.req.miniprofiler.stopTimeQuery(timing); this.body = this.state.miniprofiler.include(); resolve(); }, 50); }); })); module.exports = app; ================================================ FILE: tests/servers/koa/index.js ================================================ 'use strict'; var server; module.exports = { start: function(name, port, done) { var app = require(`./${name}.js`); server = app.listen(port, done); }, stop: function(done) { server.close(done); } }; ================================================ FILE: tests/servers/koa/render.js ================================================ 'use strict'; var miniprofiler = require('../../../lib/miniprofiler.js'); var koa = require('koa'); var route = require('koa-route'); var views = require('koa-views'); var app = koa(); app.use(views('./tests/servers/views', { extension: 'pug' })); app.use(miniprofiler.koa()); app.use(route.get('/', function *(){ yield this.render('index', { title: 'Hey', message: 'Hello there!' }); })); app.use(route.get('/inside-step', function *(){ yield new Promise((resolve, reject) => { this.req.miniprofiler.step('Step 1', (unstep) => { this.req.miniprofiler.timeQuery('custom', 'Sleeping...', setTimeout, () => { this.render('index', { title: 'Hey', message: 'Hello there!' }).then(() => { unstep(); resolve(); }); }, 50); }); }); })); module.exports = app; ================================================ FILE: tests/servers/koa/unauthorized.js ================================================ 'use strict'; var miniprofiler = require('../../../lib/miniprofiler.js'); var koa = require('koa'); var route = require('koa-route'); var app = koa(); var options = { authorize: (req) => { return false; } }; app.use(miniprofiler.koa(options)); app.use(route.get('/', function *(){ this.body = this.state.miniprofiler.include(); })); module.exports = app; ================================================ FILE: tests/servers/koa/unprofiled.js ================================================ 'use strict'; var miniprofiler = require('../../../lib/miniprofiler.js'); var koa = require('koa'); var route = require('koa-route'); var app = koa(); var options = { enable: (req) => { return false; } }; app.use(miniprofiler.koa(options)); app.use(route.get('/', function *(){ yield new Promise((resolve, reject) => { this.req.miniprofiler.timeQuery('custom', 'Sleeping...', setTimeout, () => { this.req.miniprofiler.step('Step 1', () => { this.body = this.state.miniprofiler.include(); resolve(); }); }, 50); }); })); module.exports = app; ================================================ FILE: tests/servers/views/index.pug ================================================ html head title= title body h1= message ================================================ FILE: tests/share-test.js ================================================ 'use strict'; var expect = require('chai').expect; module.exports = function(server) { describe('Share Tests', function() { before(server.setUp.bind(null, 'default')); after(server.tearDown); var expectOkResponse = (done) => (err, response, body) => { expect(response.statusCode).to.be.equal(200); expect(response.headers['content-type']).to.be.equal('text/html; charset=utf-8'); done(); }; var expectNotFoundResponse = (done) => (err, response, body) => { expect(response.statusCode).to.be.equal(404); expect(response.headers['content-type']).to.be.equal('text/plain; charset=utf-8'); done(); }; it('[GET] Valid profiled id should render share page', function(done) { server.get('/', (err, response) => { var ids = JSON.parse(response.headers['x-miniprofiler-ids']); server.get(`/mini-profiler-resources/results?id=${ids[0]}`, expectOkResponse(done)); }); }); it('[POST] Valid profiled id should render share page', function(done) { server.get('/', (err, response) => { var ids = JSON.parse(response.headers['x-miniprofiler-ids']); server.post('/mini-profiler-resources/results', { id: ids[0] }, expectOkResponse(done)); }); }); it('[GET] Invalid profiled id should render 404', function(done) { server.get('/mini-profiler-resources/results?id=123', expectNotFoundResponse(done)); }); it('[POST] Invalid profiled id should render 404', function(done) { server.post('/mini-profiler-resources/results', { id: 123 }, expectNotFoundResponse(done)); }); }); }; ================================================ FILE: tests/step-test.js ================================================ 'use strict'; var expect = require('chai').expect; module.exports = function(server) { describe('Step Tests', function() { before(server.setUp.bind(null, 'default')); after(server.tearDown); it('Index route should not profile any step', function(done) { server.get('/', (err, response) => { var ids = JSON.parse(response.headers['x-miniprofiler-ids']); expect(ids).to.have.lengthOf(1); server.post('/mini-profiler-resources/results', { id: ids[0], popup: 1 }, (err, response, body) => { var result = JSON.parse(body); expect(result.Id).to.equal(ids[0]); expect(result.Name).to.equal('/'); expect(result.Root.Children).to.be.empty; done(); }); }); }); it('/step route should profile one step', function(done) { server.get('/step', (err, response) => { var ids = JSON.parse(response.headers['x-miniprofiler-ids']); expect(ids).to.have.lengthOf(1); server.post('/mini-profiler-resources/results', { id: ids[0], popup: 1 }, (err, response, body) => { var result = JSON.parse(body); expect(result.Id).to.equal(ids[0]); expect(result.Name).to.equal('/step'); expect(result.Root.Children).to.have.lengthOf(1); expect(result.Root.Children[0].Name).to.equal('Step'); expect(result.Root.Children[0].Children).to.be.empty; done(); }); }); }); it('step-two route should profile two nested step', function(done) { server.get('/step-two', (err, response) => { var ids = JSON.parse(response.headers['x-miniprofiler-ids']); expect(ids).to.have.lengthOf(1); server.post('/mini-profiler-resources/results', { id: ids[0], popup: 1 }, (err, response, body) => { var result = JSON.parse(body); expect(result.Id).to.equal(ids[0]); expect(result.Name).to.equal('/step-two'); expect(result.Root.Children).to.have.lengthOf(1); expect(result.Root.Children[0].Name).to.equal('Step 1'); expect(result.Root.Children[0].Children).to.have.lengthOf(1); expect(result.Root.Children[0].Children[0].Name).to.equal('Step 2'); expect(result.Root.Children[0].Children[0].Children).to.be.empty; done(); }); }); }); it('/step-parallel route should profile two nested step', function(done) { server.get('/step-parallel', (err, response) => { var ids = JSON.parse(response.headers['x-miniprofiler-ids']); expect(ids).to.have.lengthOf(1); server.post('/mini-profiler-resources/results', { id: ids[0], popup: 1 }, (err, response, body) => { var result = JSON.parse(body); expect(result.Id).to.equal(ids[0]); expect(result.Name).to.equal('/step-parallel'); expect(result.Root.Children).to.have.lengthOf(2); expect(result.Root.Children[0].Name).to.equal('Step 1'); expect(result.Root.Children[0].Children).to.be.empty; expect(result.Root.Children[1].Name).to.equal('Step 2'); expect(result.Root.Children[1].Children).to.be.empty; done(); }); }); }); }); }; ================================================ FILE: tests/timequery-test.js ================================================ 'use strict'; var expect = require('chai').expect; module.exports = function(server) { describe('Time Query Tests', function() { before(server.setUp.bind(null, 'default')); after(server.tearDown); for(let url of ['/js-sleep', '/js-sleep-start-stop']) { it(`Custom timed query should be profiled for url '${url}'`, function(done) { server.get(url, (err, response) => { var ids = JSON.parse(response.headers['x-miniprofiler-ids']); expect(ids).to.have.lengthOf(1); server.post('/mini-profiler-resources/results/', { id: ids[0], popup: 1 }, (err, response, body) => { var result = JSON.parse(body); expect(result.Id).to.equal(ids[0]); expect(result.Name).to.equal(url); expect(result.DurationMilliseconds).to.be.above(40); expect(result.Root.Children).to.be.empty; expect(result.Root.CustomTimings).to.have.property('custom'); expect(result.Root.CustomTimings.custom).to.have.lengthOf(1); expect(result.Root.CustomTimings.custom[0].ExecuteType).to.be.equal('custom'); expect(result.Root.CustomTimings.custom[0].CommandString).to.be.equal('Sleeping...'); expect(result.Root.CustomTimings.custom[0].DurationMilliseconds).to.be.below(result.DurationMilliseconds); done(); }); }); }); } }); }; ================================================ FILE: tests/unauthorized-test.js ================================================ 'use strict'; var expect = require('chai').expect; module.exports = function(server) { describe('Unauthorized Tests', function() { before(server.setUp.bind(null, 'unauthorized')); after(server.tearDown); it('should return profile ID', function(done) { server.get('/', (err, response, body) => { expect(response.headers).to.include.keys('x-miniprofiler-ids'); expect(body).to.be.equal(''); done(); }); }); it('should not allow user to get timing information from ID', function(done) { server.get('/', (err, response) => { var ids = JSON.parse(response.headers['x-miniprofiler-ids']); server.post('/mini-profiler-resources/results', { id: ids[0], popup: 1 }, (err, response, body) => { expect(response.statusCode).to.be.equal(401); expect(body).to.be.equal(''); done(); }); }); }); }); }; ================================================ FILE: tests/unprofiled-test.js ================================================ 'use strict'; var expect = require('chai').expect; module.exports = function(server) { describe('Unprofiled Tests', function() { before(server.setUp.bind(null, 'unprofiled')); after(server.tearDown); it('should not return profile ID', function(done) { server.get('/', (err, response) => { expect(response.headers).to.not.include.keys('x-miniprofiler-ids'); done(); }); }); it('should not include asset', function(done) { server.get('/', (err, response, body) => { expect(body).to.be.equal(''); done(); }); }); var paths = [ '/', '/includes.css', '/results', '/results?id=2' ]; paths.forEach((path) => { it(`should not respond for '${path}'`, function(done) { server.get(`/mini-profiler-resources${path}`, (err, response, body) => { expect(response.statusCode).to.be.equal(404); expect(response.headers['content-type']).to.be.equal('text/plain; charset=utf-8'); expect(body).to.be.equal('MiniProfiler is disabled'); done(); }); }); }); }); };