From e91ebc2a67be732e1986c9ae2105c47479205488 Mon Sep 17 00:00:00 2001 From: "Selvanathan, Sam" Date: Thu, 30 Apr 2015 17:37:11 -0700 Subject: [PATCH] added performance profiling support --- .jshintrc | 2 +- CHANGELOG.md | 4 ++++ README.md | 30 +++++++++++++++++++++++++++- lib/performance.js | 37 ++++++++++++++++++++++++++++++++++ lib/server.js | 17 ++++++++++++++++ package.json | 2 +- test/server.js | 49 ++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 lib/performance.js diff --git a/.jshintrc b/.jshintrc index 85cec1d..b3fe4de 100644 --- a/.jshintrc +++ b/.jshintrc @@ -35,7 +35,7 @@ "maxlen": 120, // Require capitalized names for constructor functions. - "newcap": true, + "newcap": false, // Enforce use of single quotation marks for strings. "quotmark": "single", diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c51ad8..82db97a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.3.0 (April 30, 2015) + +* added Performance profiling + ## 1.2.0 (April 25, 2015) * Generate semantic html by injecting script tag before end of html tag diff --git a/README.md b/README.md index d2f83a6..b35ff09 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ // create the view engine with `react-engine` var engine = require('react-engine').server.create({ reactRoutes: /* pass in the path to react-router routes optionally */ + performanceCollector: /* optional function to collect perf stats */ }); // set the engine @@ -50,7 +51,8 @@ "renderer": { "method": "create", "arguments": [{ - "reactRoutes": "path:" + "reactRoutes": "path:", + "performanceCollector": "require:" }] } } @@ -100,6 +102,32 @@ document.addEventListener('DOMContentLoaded', function onLoad() { }; ``` +### Performance Profiling + +Pass in a function to the `performanceCollector` property to collect the `stats` +object for every render. + +##### `stats` +The object that contains the stats info for each render by react-engine. +It has the below properties. +- `name` - Name of the template or the url in case of react router rendering. +- `startTime` - The start time of render. +- `endTime` - The completion time of render. +- `duration` - The duration taken to render (in milliseconds). + +```js +// example +function collector(stats) { + console.log(stats); +} + +var engine = require('react-engine').server.create({ + reactRoutes: './routes.jsx' + performanceCollector: collector +}); +``` + + ### Notes * On the client side, the state is exposed on the window object's property `__REACT_ENGINE__` * In development mode, views are automatically reloaded before render. So there is no need to restart the server for seeing the changes. diff --git a/lib/performance.js b/lib/performance.js new file mode 100644 index 0000000..77a60bd --- /dev/null +++ b/lib/performance.js @@ -0,0 +1,37 @@ +/*-------------------------------------------------------------------------------------------------------------------*\ +| Copyright (C) 2015 PayPal | +| | +| Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance | +| with the License. | +| | +| You may obtain a copy of the License at | +| | +| http://www.apache.org/licenses/LICENSE-2.0 | +| | +| Unless required by applicable law or agreed to in writing, software distributed under the License is distributed | +| on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for | +| the specific language governing permissions and limitations under the License. | +\*-------------------------------------------------------------------------------------------------------------------*/ + +'use strict'; + +module.exports = function Performance(name) { + + var startTime = Date.now(); + var time = process.hrtime(); + + return function end() { + + var diff = process.hrtime(time); + + // duration in milliseconds + var duration = diff[0] + (diff[1] / 1000000); + + return { + name: name, + startTime: startTime, + endTime: Date.now(), + duration: duration + }; + }; +}; diff --git a/lib/server.js b/lib/server.js index 3f92a61..5aabe94 100644 --- a/lib/server.js +++ b/lib/server.js @@ -16,8 +16,10 @@ 'use strict'; var util = require('./util'); +var assert = require('assert'); var Config = require('./config'); var format = require('util').format; +var Performance = require('./performance'); var omit = require('lodash-node/compat/object/omit'); var merge = require('lodash-node/compat/object/merge'); @@ -36,10 +38,21 @@ exports.create = function create(createOptions) { createOptions = createOptions || {}; + if (createOptions.performanceCollector) { + assert.equal(typeof createOptions.performanceCollector, + 'function', + '`performanceCollector` - should be a function'); + } + // the render implementation return function render(thing, options, callback) { var routes; + var perfInstance; + + if (createOptions.performanceCollector) { + perfInstance = Performance(thing); + } function done(err, html) { if (options.settings.env === 'development') { @@ -49,6 +62,10 @@ exports.create = function create(createOptions) { util.clearRequireCacheInDir(options.settings.views, options.settings['view engine']); } + if (createOptions.performanceCollector) { + createOptions.performanceCollector(perfInstance()); + } + callback(err, html); } diff --git a/package.json b/package.json index ea7fe8e..62fdea6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-engine", - "version": "1.2.0", + "version": "1.3.0", "description": "a composite render engine for express apps to render both plain react views and react-router views", "main": "index.js", "scripts": { diff --git a/test/server.js b/test/server.js index 2c2182b..ea8c11f 100644 --- a/test/server.js +++ b/test/server.js @@ -114,6 +114,55 @@ test('rendering a react view', function(t) { setup(options); }); +test('performance collector to be asserted to be a function', function(t) { + + function underTest1() { + renderer.create({ + performanceCollector: 'SOME_STRING_AND_NOT_FUNCTION' + }); + } + + function underTest2() { + renderer.create({ + performanceCollector: console.dir + }); + } + + t.throws(underTest1); + t.doesNotThrow(underTest2); + t.end(); +}); + +test('performance collector', function(t) { + + var recorder = []; + + function collector(stats) { + recorder.push(stats); + } + + var options = { + engine: renderer.create({ + performanceCollector: collector + }), + onSetup: function(done) { + inject('/profile', function(err, data) { + t.error(err); + t.strictEqual(typeof data, 'string'); + t.strictEqual(recorder.length, 1); + t.strictEqual(Object.keys(recorder[0]).length, 4); + t.strictEqual(recorder[0].name, path.resolve(__dirname, 'fixtures/views', 'profile.js')); + t.strictEqual(typeof recorder[0].startTime, 'number'); + t.strictEqual(typeof recorder[0].endTime, 'number'); + t.strictEqual(typeof recorder[0].duration, 'number'); + t.ok(recorder[0].endTime > recorder[0].startTime); + done(t); + }); + } + }; + setup(options); +}); + test('all views get cleared from require cache in dev mode', function(t) { var options = { engine: renderer.create(),