Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge remote branch 'origin/sweetandsour' into develop-perf

Conflicts:
	lib/app/addons/ac/output-adapter.common.js
	lib/app/autoload/dispatch.common.js
	lib/app/autoload/logger.common.js
	lib/app/autoload/perf.server.js
	lib/index.js
	lib/tests/autoload/app/autoload/dispatch-tests.common.js
  • Loading branch information...
commit fde66fd4d80678935fe8e7ed98b8a3537e5bb186 2 parents 9ccd9cf + d36eed7
@bthaeler authored
View
1  docs/dev_guide/topics/index.rst
@@ -23,3 +23,4 @@ Table of Contents
mojito_using_contexts
mojito_npm
mojito_resource_store
+ mojito_debugger
View
330 docs/dev_guide/topics/mojito_debugger.rst
@@ -0,0 +1,330 @@
+
+
+========
+Debugger
+========
+
+Mojito has an API used for collecting debug data/logs/perfomance data. Debug data is only collected when a application specified query param is set.
+The debug data is then displayable using some general purpose mojits, or you can create your own custom mojits to display the debugging data.
+
+
+Setup
+#####
+
+To start using the mojito debugger, you need to change your app configuration and enable debugging.
+By default, the debugger interface is turned off.
+To enable, update your application.json file with a **debug** section.
+
+Example
+
+.. code-block:: javascript
+
+ [
+ {
+ "settings": [ "master" ],
+ "specs": {
+ ...
+ },
+ "debug": {
+ "queryParam": "my_debug",
+ "debugMojit": "@DebugFrame.index",
+ "debugPath": "/debug_path",
+ "modules": {
+ },
+ "debugAllowed": "mojito-debug-reqest-validate.handler" (optional)
+ }
+
+* queryParam : (required)
+ A query param that provides a list of debug flags to turn on.
+ When this query param is not present, the debug API does not collect any data.
+
+ Example: ?my_debug=help
+* debugMojit : (required)
+ Name of mojit to redirect request to. If using the debug mojits from the npm module ``mojito-debug-view``,
+ then set this to **@DebugFrame.index**. If you want to build your own debug UI, set this to the mojit that
+ will render your app, and display the debug mojits.
+
+ Example: @DebugFrame.index
+* debugPath : (required)
+ Name of a path that is NOT used by the application. This path will be used internal to redirect
+ the request. This needs to not conflict with any other path the application uses.
+
+ Example: /debug_path
+* modules: (optional)
+ A list of user defined flags. Each flag is associated with a mojit to render the data.
+ See :ref:`Custom debug data <custom-debug>` for more information.
+* debugAllowed: (optional)
+ This is the name of a YUI module and a function inside it (module_name.function). This function
+ takes a single argument that is the request object. It should return true of false if this request is by a valid
+ user of the debugging system. Invalid request will return error. Valid request will redirect
+ to the debugMojit.
+
+Once your application has the debugger enabled, you will need to install the UI. You can either use the
+interface provided by the npm module ``mojito-debug-view``, or you can create your own.
+See :ref:`Custom UI <custom-ui>` for more information on building your own UI.
+See :ref:`Using mojito-debug-view <mojito-debug-view>` for more informatnio on installing a general purpose UI.
+
+
+Using
+#####
+
+
+To use the debugger you need to append the debug query param, you configured in application.json, to the url of the page you want to debug.
+
+Example: ``http://something.com/page?my_debug=flags,...``
+
+The value of the query param is a comma seperated list of debug flags to turn on. Each flag is associated with a specific piece
+of debug data. If using the ``mojito-debug-view`` UI, then the special flag **help** is available. This will bring up a help
+mojit that lists all the available debug flags.
+
+
+Pre-Defined Debugging Flags
+===========================
+
+The core mojito system has been instrimented with this debuggign system. The following flags are available.
+
+
+* log.AC_Logger
+ All log lines logged to the ``ac.logger`` are viewable from this flag.
+
+* log.AC
+ The AC object is logged under this flag. This provides an easy way to see the request object, and app config, context, and anything else
+ attached to the AC object.
+
+* log.Render
+ This is a log of the data that was used to render templates in your application.
+
+* prof.dispatch
+ This is a profiling breakdown showing when the different mojits in your application started rending, and when the ac.done was called for each.
+
+* prof.PerfMark
+ If you manually enable perf markers, this is profiling view is available. It will show you when the different perf markers were set.
+
+
+
+
+User Defined Debugging Flags
+############################
+
+Users can define there own flags and record there own debuggig data. Users will need to use the `MojitoDebugAPI <../../api/classes/MojitoDebugAPI.html>`_
+API to log there own data. There are three basic options available for logging your own data.
+
+
+Logs
+====
+
+The log API allow you to log both simple strings, or objects.
+You can either log data to the general purpose log flag, or you can log data to your own custom flag.
+
+Example
+
+.. code-block:: javascript
+
+ req.debug.log("Some log data");
+ req.debug.logOn("my_flag", "Some log data");
+
+Timing
+======
+
+The profiling API allows you to mark the start and end of specific events in your code. Events
+can have multiple ends. This allows you to mark different aspects of a single event. All events
+logged under the same flag are displayed together in a single waterfall graph.
+
+Example
+
+.. code-block:: javascript
+
+ req.debug.profOpen("my_flag", "event 1", "call to something");
+ req.debug.profClose("my_flag", "event 1", "DNS lookup done");
+ req.debug.profClose("my_flag", "event 1", "Req sent");
+
+
+General Purpose
+===============
+
+You can also log general purpose data. This system allows you to attach any data your want to
+and object, and then build your own mojit to render this data.
+
+Example
+
+.. code-block:: javascript
+
+ req.debug.on("my_flag", function (data) {
+ data.some = 'thing';
+ });
+
+See :ref:`Custom debug data <custom-debug>` for information on building your own custom mojti to display this data.
+
+
+.. _mojito-debug-view:
+
+Using ``mojito-debug-view`` module
+##################################
+
+To use the debugging system you will need a UI. The esasiet way to get started is to use the ``mojito-debug-view``
+package. This package provides mojits for logs, profiling data, and a help interface.
+
+Installing
+==========
+
+To install you need to add the following to your application package.json file.
+
+.. code-block:: javascript
+
+ "dependencies": {
+ ...
+ "mojito-debug-view": "0.0.4-13"
+ },
+
+Run "npm install"
+
+
+.. _custom-debug:
+
+User defined mojits
+===================
+
+User defined debug data, collected with the ``debug.on``, can be rendered by creating a custom
+mojit. This mojit will be passed the object created with the ``debug.on`` calls. This custom
+debug mojit needs to also be registred with the debug system.
+
+
+Registering mojit
+-----------------
+
+To register your own custom debug mojits, you will need to add the mojits to the debug
+section of the application.json file.
+
+Example
+
+
+Example
+
+.. code-block:: javascript
+
+ [
+ {
+ "settings": [ "master" ],
+ "specs": {
+ ...
+ },
+ "debug": {
+ ...
+ "modules": {
+ "my_flag": {
+ "title": "Bar",
+ "description": "Test debug of Bar",
+ "type": "DebugBar"
+ }
+ },
+
+* my_flag
+ The keys used in the modules object are the name of the debug flag. This must match the flag
+ used int he ``debug.on`` calls, and also what is passed to the debug query param ``?my_debug=my_flag``.
+
+* title
+ This is the name of the mojit. It will appear in the help page and in the title bar of the mojit.
+
+* description
+ This appears in the help page.
+
+* type
+ This is the mojit that will be rendered to display that data.
+
+
+Render mojit
+------------
+
+The mojit you registered to display your debug data, can use the api call ``debug.get("my_flag")`` to get
+the data your stored with the ``debug.on`` calls. You can use the ac addon ``mojito.debug.api`` to get
+easy access to the debug api.
+
+Example:
+
+.. code-block:: javascript
+
+ YUI.add('DebugBar', function(Y, NAME) {
+
+ Y.namespace('mojito.controllers')[NAME] = {
+
+ init: function(config) {
+ this.config = config;
+ },
+
+ index: function(ac) {
+ ac.models.DebugBarModelFoo.getData(function(err, data) {
+ var my_data = ac.debug.get("my_flag");
+ ac.done("My data: " + my_data.some);
+ });
+ }
+
+ };
+
+ }, '0.0.1', {requires: ['mojito', 'mojito.debug.api']});
+
+
+.. _custom-ui:
+
+Creating Custom UI
+##################
+
+
+If you don't want to use the ``mojito-debug-view`` package, you can define your own UI. You register your UI
+in the application.json file, set ``debugMojit`` to the name of your own top level UI mojit. This mojit will
+need to do several things.
+
+#. Parse the debug query param and call ``debug.addFlag(flag)`` for each flag set.
+
+#. Render the real page.
+
+#. For each flag set, get the debug data by calling ``debug.get(flag)`` and render that data.
+
+
+Example:
+
+.. code-block:: javascript
+
+ YUI.add('MyUI', function(Y, NAME) {
+ Y.namespace('mojito.controllers')[NAME] = {
+ init: function(config) {
+ this.config = config;
+ },
+ index: function(ac) {
+ var debug = ac._adapter.debug,
+ currentUrl = ac._adapter.req.url,
+ newUrl = currentUrl.slice(ac.app.config.debug.debugPath.length),
+ mojitoRoute = ac.url.find(newUrl),
+ call = mojitoRoute.call.split('.'),
+ route = Y.clone(ac.app.config.specs[call[0]]);
+
+ route.action = call[1];
+
+ Y.each(debug.parseDebugParameters(ac.params.url()[ac.app.config.debug.queryParam]), function (flag) {
+ debug.addFlag(flag);
+ });
+
+ ac.http.setHeader('Content-type', 'text/html');
+
+ ac.http.getRequest().url = newUrl;
+ ac.composite.execute({
+ children: {
+ 'application': route
+ }
+ }, function (data, meta) {
+ var res;
+
+ res = data.application + "<P>";
+ Y.each(debug.parseDebugParameters(ac.params.url()[ac.app.config.debug.queryParam]), function (flag) {
+ var data = debug.get(flag);
+
+ if (data)
+ res += JSON.stringify(data) + "<P>";
+ });
+ ac.done(res);
+ });
+ }
+ };
+ }, '0.0.1', {requires: ['mojito', 'mojito-url-addon']});
+
+
+
View
4 lib/app/addons/ac/output-adapter.common.js
@@ -59,6 +59,10 @@ YUI.add('mojito-output-adapter-addon', function(Y, NAME) {
// perf = Y.mojito.perf.timeline('mojito', 'ac.done',
// 'time to execute ac.done process', this.command);
+// if (adapter.debug) {
+// adapter.debug.profClose("dispatch", instance.instanceId, 'done');
+// }
+
// if (Y.Lang.isString(meta)) {
// // If the meta string is a serializer set it
// if (serializer[meta]) {
View
389 lib/app/autoload/debug-api.common.js
@@ -0,0 +1,389 @@
+/*
+ * Copyright (c) 2011-2012, Yahoo! Inc. All rights reserved.
+ * Copyrights licensed under the New BSD License.
+ * See the accompanying LICENSE file for terms.
+ */
+
+
+/*jslint anon:true, sloppy:true, regexp: true, nomen:true*/
+/*global YUI*/
+
+
+/**
+ * To use the debugger interface, users must include a debug section into there application.json file.
+ *
+ * Example:
+ * <pre>
+ * [
+ * {
+ * "settings": [ "master" ],
+ * "specs": {
+ * ...
+ * },
+ * "debug": {
+ * "queryParam": "my_debug", (required)
+ * "debugMojit": "@DebugFrame.index", (required)
+ * "debugPath": "/debug_path", (required)
+ * "modules": { (optional)
+ * },
+ * "debugAllowed": "mojito-debug-reqest-validate.handler" (optional)
+ * }
+ * </pre>
+ *
+ * <ul>
+ * <li>queryParam : A query param that provides a list of debug flags to turn on. Example: ?my_debug=help</li>
+ * <li>debugMojit : Name of mojit to redirect request to. Example: @DebugFrame.index</li>
+ * <li>debugPath : Name of a path that is NOT used by the application. This path will be used internal to redirect
+ * the request. This needs to not conflict with any other path the application uses.</li>
+ * <li>modules: A list of debug and flag and modules to use for user defined debug data.<P>
+ * Example:
+ * <pre>
+ "bar": {
+ "title": "Bar",
+ "description": "Test debug of Bar",
+ "type": "DebugBar"
+ }
+ * </pre>
+ * The DebugBar mojit will be used to render all data collected using the debug.on API calls.
+ * To acess the debug data use the the debug.get({flag}) call.
+ * The flag is bar.</li>
+ * <li>debugAllowed: This is the name of a YUI module and a function inside it (module_name.function). This function
+ * is of the format function (req). It should return true of false if this request is by a valid
+ * user of the debugging system. Invalid request will return error. Valid request will redirect
+ * to the debugMojit.</li>
+ * </ul>
+ *
+ * To generate the UI for the debug system, you can use the mojito-debug-view NPM module, or write your own.
+ * @module MojitoDebug
+ */
+YUI.add('mojito-debug-api', function(Y, NAME) {
+ /**
+ * Interface to add debugging data to a request. This api is available from three sources:
+ * <ol>
+ * <li>The request object: req.debug</li>
+ * <li>The adapter object: adapter.debug</li>
+ * <li>If using the mojito-debug-view NPM module, there is an ac addon ('mojito.debug.api') that provides the api though the ac: ac.debug</li>
+ * </ol>
+ * @class MojitoDebugAPI
+ * @constructor
+ * @param {Object} Y
+ * @param {String} queryparam Query param of comma seperated list of debug flags to enable
+ * @param {Object} microtime microtime interface to use if possible
+ */
+ function DebugAPI(Y, req, queryParam, microtime) {
+ var store = {};
+ store.modules = {};
+ store.profile = {};
+ store.specialLogs = {};
+
+ this.Y = Y;
+ this.store = store;
+ this.microtime = microtime;
+
+ if (queryParam && req.query && req.query[queryParam]) {
+
+ Y.each(this.parseDebugParameters(req.query[queryParam]), function (flag) {
+ store.modules[flag] = {};
+ });
+
+ /**
+ * Check to see if a specific debug flag is set.
+ * @method flag
+ * @param {String} flag Flag to check
+ * @return {boolean}
+ */
+ this.flag = function (flag) {
+ if (store.modules[flag]) {
+ return true;
+ }
+ return false;
+ };
+
+ /**
+ * If flat is set, call provided function with handle to object to store debug data in. Viewable with /?my_debug={flag,...}
+ * @method on
+ * @param {String} flag Flag to check
+ * @param {Function} cb Funcion of the form function (data) {} where data will be per flag object to store debug data in.
+ *
+ * Example:
+ * <pre>
+ * debug.on("my_debug_flag", function (my_debug_data) {
+ * my_debug_data.something = 'some data';
+ * }
+ * </pre>
+ */
+ this.on = function (flag, f) {
+
+ if (store.modules[flag]) {
+ // catch any errors in instrumented code
+ try {
+ f(store.modules[flag]);
+ } catch (e) {
+ if (!store.instrumentationErrors) {
+ store.instrumentationErrors = [];
+ }
+ store.instrumentationErrors.push({flag: flag, error: e});
+ }
+ }
+ };
+
+ /**
+ * Log a string or an object to the generic debug log stream. Viewable with /?my_debug=log
+ * @method log
+ * @param {Object} content A string or an object to log. May also be a function, that when called returns a string or object.
+ * @param {Object} jsonTreeOptions An optional object for hints to displaying object. Valid values: depth
+ *
+ * Example:
+ * <pre>
+ * debug.log("Some log data");
+ * debug.log({some: 'data'});
+ * debug.log(function () {return "log data";});
+ * </pre>
+ */
+ this.log = function (content, jsonTreeOptions) {
+ this.logOn("", content, jsonTreeOptions);
+ };
+
+ /**
+ * Log a string or an object to the a specific log stream. Viewable with /?my_debug=Log.{logType}
+ * @method logOn
+ * @param {String} logType Name of log stream to log to
+ * @param {String} content A string or an object to log. May also be a function, that when called returns a string or object.
+ * @param {Object} jsonTreeOptions An optional object for hints to displaying object. Valid values: depth
+ *
+ * Example:
+ * <pre>
+ * debug.logOn("MyLogs", "Some log data");
+ * debug.logOn("MyLogs", {some: 'data'});
+ * debug.logOn("MyLogs", function () {return "log data";});
+ * </pre>
+ */
+ this.logOn = function (logType, content, jsonTreeOptions) {
+ var flag = logType === "" ? "log" : "log." + logType;
+
+ // Do this before the module check. That way we will know about special logs, but we
+ // may not keep there data
+ if (flag !== "log") {
+ store.specialLogs[logType] = true;
+ }
+
+ if (!store.modules[flag]) {
+ return;
+ }
+
+ if ('function' === typeof content) {
+ try {
+ content = content();
+ } catch (e) {
+ if (!store.instrumentationErrors) {
+ store.instrumentationErrors = [];
+ }
+ store.instrumentationErrors.push({flag: flag, error: e});
+ return;
+ }
+ }
+
+ if (!store.modules[flag].data) {
+ store.modules[flag].data = [];
+ }
+
+ store.modules[flag].data.push({content: content, jsonTreeOptions: jsonTreeOptions});
+ };
+
+ /**
+ * Start profling a section of code. All the nodes of a specific profType are collected together and display in a single waterfall display.
+ * Viewable with: /?my_debug=prof.{profType}
+ * @method profOpen
+ * @param {String} profType Name of profile.
+ * @param {String} node An id of the specific sequence to capture
+ * @param {String} d A discription of this node.
+ *
+ * Example:
+ * <pre>
+ * debug.profOpen("YUI_calls", "call 1", "call to something");
+ * debug.profClose("YUI_calls", "call 1", "DNS lookup done");
+ * debug.profClose("YUI_calls", "call 1", "Req sent");
+ * debug.profClose("YUI_calls", "call 1", "Result recieved");
+ * debug.profOpen("YUI_calls", "call 2", "call to something");
+ * debug.profClose("YUI_calls", "call 2", "DNS lookup done");
+ * ...
+ * </pre>
+ */
+ this.profOpen = function (profType, node, d) {
+ var flag = "prof." + profType;
+
+ store.profile[profType] = true;
+
+ if (!store.modules[flag]) {
+ return;
+ }
+
+ if (!store.modules[flag].data) {
+ store.modules[flag].data = {};
+ }
+ if (!store.modules[flag].events) {
+ store.modules[flag].events = [];
+ }
+
+ store.modules[flag].data[node] = {
+ desc: d,
+ start: this.timestamp(),
+ close: []
+ };
+ };
+
+ /**
+ * End a profiling section. There can be multipel profClose for a single profOpen. Each will appear in the the output.
+ * @method profClose
+ * @param {String} profType Name of profile.
+ * @param {String} node An id of the specific sequence to capture
+ * @param {String} type Type of close condition
+ */
+ this.profClose = function (profType, node, type) {
+ var flag = "prof." + profType;
+
+ if (!store.modules[flag]) {
+ return;
+ }
+
+ if (!store.modules[flag].data) {
+ store.modules[flag].data = {};
+ }
+
+ if (!store.modules[flag].data[node]) {
+ return;
+ }
+
+ store.modules[flag].data[node].close.push({
+ type: type,
+ time: this.timestamp()
+ });
+ };
+
+ /**
+ * Mark a event that occured in the application. Events will show up as vertical lines in the waterfall.
+ * They are usefull to mark important events accross all profiling sections of a waterfall.
+ * @method profEvent
+ * @param {String} profType Name of profile.
+ * @param {String} type Type of the event
+ *
+ * Example:
+ * <pre>
+ * debug.profEvent("MyProfile", "End of important event");
+ * </pre>
+ */
+ this.profEvent = function (profType, type) {
+ var flag = "prof." + profType;
+
+ if (!store.modules[flag]) {
+ return;
+ }
+
+ if (!store.modules[flag].events) {
+ store.modules[flag].events = [];
+ }
+
+ store.modules[flag].events.push({
+ type: type,
+ time: this.timestamp()
+ });
+ };
+ }
+ }
+
+ DebugAPI.prototype = {
+ on: function () {
+ },
+
+ addFlag: function (flag) {
+ if (!this.store.modules[flag]) {
+ this.store.modules[flag] = {};
+ }
+ },
+
+ /**
+ * Return a list of all flags that are set
+ * @method flags
+ * @return {Array} Array of flags that are set.
+ */
+ flags: function () {
+ return this.Y.Object.keys(this.store.modules);
+ },
+
+ flag: function () {
+ return false;
+ },
+
+ /**
+ * Get data associated with a flag.
+ * @method get
+ * @param {String} key flag to set data for
+ * @param {Object} value Default value to return if no data was collected for flag
+ * @return {Object} Object containing debug data
+ *
+ * Example:
+ * <pre>
+ * data = debug.get("my_debug_flag");
+ * use_data = data.something;
+ * </pre>
+ */
+ get: function (key, value) {
+ return this.store.modules[key] || value;
+ },
+
+ getSpecialLogs: function () {
+ return Object.keys(this.store.specialLogs);
+ },
+
+ getKnownProf: function() {
+ return Object.keys(this.store.profile);
+ },
+
+ getInstrumentationErrors: function () {
+ return this.store.instrumentationErrors;
+ },
+
+ parseDebugParameters: function (parameters) {
+ var list = [],
+ Y = this.Y;
+
+ if (parameters && !this.Y.Lang.isArray(parameters)) {
+ parameters = [parameters];
+ }
+
+ Y.each(parameters, function (item) {
+ var items = item.split(',');
+ Y.each(items, function (item) {
+ if (item !== "") {
+ list.push(item);
+ }
+ });
+ });
+
+ return list;
+ },
+
+ timestamp: function () {
+ return this.microtime ? this.microtime.now() : new Date().getTime();
+ },
+
+ log: function () {
+ },
+
+ logOn: function () {
+ },
+
+ profOpen: function () {
+ },
+
+ profClose: function () {
+ },
+
+ profEvent: function () {
+ }
+ };
+
+ Y.namespace('mojito').DebugAPI = DebugAPI;
+
+}, '0.1.0', { requires: [
+]});
View
12 lib/app/autoload/dispatch.common.js
@@ -52,6 +52,10 @@ YUI.add('mojito-dispatcher', function(Y, NAME) {
'dispatch:expandInstance',
'gather details about mojit', command);
+ if (adapter.debug) {
+ adapter.debug.profOpen("dispatch", command.instance.instanceId, (command.instance.id || '@' + command.instance.type) + ':' + (command.action || (command.instance.action || 'index')));
+ }
+
store.validateContext(command.context);
store.expandInstance(command.instance, command.context,
@@ -65,6 +69,10 @@ YUI.add('mojito-dispatcher', function(Y, NAME) {
perf.done(); // closing 'dispatch:expandInstance' timeline
+ if (adapter.debug) {
+ adapter.debug.profClose("dispatch", command.instance.instanceId, 'startup done');
+ }
+
// We replace the given instance with the expanded instance.
command.instance = instance;
@@ -98,6 +106,10 @@ YUI.add('mojito-dispatcher', function(Y, NAME) {
perf.done(); // closing the 'ac:ctor' timeline
+ if (adapter.debug) {
+ adapter.debug.profClose("dispatch", command.instance.instanceId, 'startup instance done');
+ }
+
});
}
View
11 lib/app/autoload/logger.common.js
@@ -86,6 +86,7 @@ YUI.add('mojito-logger', function(Y, NAME) {
// this._opts = Y.merge(defaults, opts);
// this._buffer = [];
+// this.debug = null;
// if (id) {
// this._id = id;
@@ -155,6 +156,12 @@ YUI.add('mojito-logger', function(Y, NAME) {
// return;
// }
+// if (this.debug) {
+// this.debug.logOn("AC_Logger", function () {
+// return now + ": " + source + ": " + level + ": " + msg;
+// });
+// }
+
// if (this._opts.buffer) {
// this._buffer.push([msg, level, source, now]);
// // auto-flush buffer if breaking max buffer size
@@ -192,6 +199,10 @@ YUI.add('mojito-logger', function(Y, NAME) {
// this._opts.writer = w;
// },
+// setDebug: function(d) {
+// this.debug = d;
+// },
+
// setPublisher: function(p) {
// console.log('publisher set: ' + p.toString());
View
16 lib/app/autoload/perf.server.js
@@ -26,6 +26,7 @@ YUI.add('mojito-perf', function (Y, NAME) {
requestId = 0,
colorRed = '\u001b[31m',
colorReset = '\u001b[0m',
+ debug = null,
getgo,
microtime;
@@ -166,6 +167,9 @@ YUI.add('mojito-perf', function (Y, NAME) {
s.label = label;
s.id = id;
s.time = timestamp();
+ if (debug) {
+ debug.profOpen("PerfMark", group + ":" + label, msg);
+ }
return s;
}
@@ -278,6 +282,10 @@ YUI.add('mojito-perf', function (Y, NAME) {
perf = timeline('mojito', 'request', 'the whole request', id);
+ if (req.debug) {
+ req.debug.profOpen("PerfMark", id, 'the whole request');
+ }
+
// hooking into the res.end called from output-handler.server.js
// to be able to flush perf metrics only for mojito requests.
// static requests and other type of requests will be ignored.
@@ -293,13 +301,21 @@ YUI.add('mojito-perf', function (Y, NAME) {
req = null;
res = null;
}
+ if (req.debug) {
+ req.debug.profClose("PerfMark", id, 'req end');
+ }
};
}
+ function setDebug(d) {
+ debug = d;
+ }
+
if (config) {
Y.mojito.perf.idFromCommand = idFromCommand;
Y.mojito.perf.instrumentMojitoRequest = instrumentMojitoRequest;
Y.mojito.perf.dump = dump;
+ Y.mojito.perf.setDebug = setDebug;
// overriding the default definitions if needed
if (config.timeline) {
View
10 lib/app/autoload/view-renderer.common.js
@@ -42,6 +42,16 @@ YUI.add('mojito-view-renderer', function(Y) {
* later. (streaming)
*/
render: function(data, mojitType, tmpl, adapter, meta, more) {
+ if (adapter.debug) {
+ adapter.debug.logOn('Render', function () {
+ var ret = {};
+ ret.mojit_view_id = (data && data.mojit_view_id);
+ ret.data = data;
+ ret.mojitType = mojitType;
+ ret.tmpl = tmpl;
+ return ret;
+ });
+ }
this._renderer.render(data, mojitType, tmpl, adapter, meta, more);
}
};
View
106 lib/app/middleware/mojito-debug.js
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2011-2012, Yahoo! Inc. All rights reserved.
+ * Copyrights licensed under the New BSD License.
+ * See the accompanying LICENSE file for terms.
+ */
+
+
+/*jslint anon:true, sloppy:true, nomen:true*/
+
+
+function Debug(Y, store) {
+ this.Y = Y;
+ this._store = store;
+ try {
+ this.microtime = require('microtime');
+ } catch (e) {
+ Y.log('microtime not found. Recorded times will not have' +
+ ' microsecond accuracy', 'warn', "debug");
+ }
+}
+
+Debug.prototype = {
+ handle: function() {
+ var self = this,
+ queryParam = null,
+ debugPath = null,
+ debugMojit = null,
+ debugAllowed = null,
+ debugAllowHandler = null,
+ appConfig = self._store.getAppConfig(null),
+ s;
+
+ // see if debugger is turned on
+ if (appConfig.debug) {
+ queryParam = appConfig.debug.queryParam;
+ debugPath = appConfig.debug.debugPath;
+ debugMojit = appConfig.debug.debugMojit;
+ debugAllowed = appConfig.debug.debugAllowed;
+ if (debugAllowed) {
+ // debugAllowHandler = require(debugAllowed);
+ s = debugAllowed.split(/\./);
+ self.Y.applyConfig({ useSync: true });
+ debugAllowHandler = self.Y.use(s[0]).namespace(s[0])[s[1]];
+ self.Y.applyConfig({ useSync: false });
+ }
+ }
+
+ return function(req, res, next) {
+ var debug = new self.Y.mojito.DebugAPI(self.Y, req, queryParam, self.microtime),
+ liburl,
+ call = [];
+
+ req.debug = debug;
+
+ if (!debugMojit || !queryParam || !debugPath) {
+ next();
+ return;
+ }
+
+ if (req.url.match(new RegExp("^" + debugPath + ".*$"))) {
+ // debug is in route -> reroute to page not found.
+ // This prevents the debug mojit from being called directly by the user.
+ req.url = null;
+ console.warn("Request attempting to access debugger route directly");
+ } else if (req.url.match(new RegExp("^.*?(.*&)?" + queryParam + "=.+$"))) {
+
+ // call application provided function to verify if this request shoudl be allowed or not
+ if (debugAllowHandler && !debugAllowHandler(req)) {
+ next();
+ return;
+ }
+
+ liburl = require('url');
+ call[0] = debugMojit.split('.');
+ call[1] = call[0].pop();
+ call[0] = call[0].join('.');
+
+ req.url = debugPath + req.url;
+
+ req.command = {
+ instance: {
+ },
+ action: call[1],
+ context: req.context,
+ params: {
+ route: [],
+ url: liburl.parse(req.url, true).query || {},
+ body: req.body || {},
+ file: {}
+ }
+ };
+ if (call[0][0] === '@') {
+ req.command.instance.type = call[0].slice(1);
+ } else {
+ req.command.instance.base = call[0];
+ }
+ }
+ next();
+ };
+ }
+};
+
+module.exports = function(config) {
+ var debug = new Debug(config.Y, config.store);
+ return debug.handle();
+};
View
5 lib/mojito.js
@@ -85,6 +85,7 @@ MojitoServer.MOJITO_MIDDLEWARE = [
'mojito-parser-cookies',
'mojito-contextualizer',
'mojito-handler-tunnel',
+ 'mojito-debug',
'mojito-router',
'mojito-handler-dispatcher'
];
@@ -318,6 +319,10 @@ MojitoServer.prototype._configureAppInstance = function(app, options) {
outputHandler.setLogger({
log: Y.log
});
+ outputHandler.setDebug(req.debug);
+ if (Y.mojito.perf.setDebug) {
+ Y.mojito.perf.setDebug(req.debug);
+ }
// if perf metrics are on, we should hook into
// the mojito request to flush metrics when
View
4 lib/output-handler.server.js
@@ -37,6 +37,10 @@ OutputHandler.prototype = {
this.logger = logger;
},
+ setDebug: function(debug) {
+ this.debug = debug;
+ },
+
flush: function(data, meta) {
this._readMeta(meta);
View
188 lib/tests/autoload/app/autoload/debug-api-tests.common.js
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2011-2012, Yahoo! Inc. All rights reserved.
+ * Copyrights licensed under the New BSD License.
+ * See the accompanying LICENSE file for terms.
+ */
+YUI.add('mojito-debug-api-tests', function(Y, NAME) {
+ var suite = new YUITest.TestSuite(NAME),
+ A = YUITest.Assert,
+ OA = YUITest.ObjectAssert,
+ AA = YUITest.ArrayAssert;
+
+ suite.add(new YUITest.TestCase({
+ name: 'Debug api',
+
+ 'debug interface off': function() {
+ var req = {
+ query: {
+ }
+ },
+ debug = new Y.mojito.DebugAPI(Y, req, "debug", null);
+
+ debug.on("bar", function(d) {
+ d.data = "abc";
+ });
+ debug.log("here");
+ debug.logOn("My", "there");
+ debug.profOpen("A", "b", "b desc");
+ debug.profClose("A", "b", "first close");
+ debug.profEvent("A", "event 1");
+
+ // AA.contains(debug.flags(), [], 'correct flags');
+ A.areEqual(0, debug.flags().length, 'no flags');
+ A.isUndefined(debug.get("bar"), "no bar data");
+ A.isUndefined(debug.get("log"), "no log data");
+ A.isUndefined(debug.get("log.My"), "no log.My data");
+ A.isUndefined(debug.get("prof.A"), "cno prof.A");
+ A.isFalse(debug.flag("bar"), "flag not set");
+ },
+
+ 'debug on flags off': function() {
+ var req = {
+ query: {
+ debug: "help"
+ }
+ },
+ debug = new Y.mojito.DebugAPI(Y, req, "debug", null);
+
+ debug.on("bar", function(d) {
+ d.data = "abc";
+ });
+ debug.log("here");
+ debug.logOn("My", "there");
+ debug.profOpen("A", "b", "b desc");
+ debug.profClose("A", "b", "first close");
+ debug.profEvent("A", "event 1");
+
+ AA.containsItems("help".split(/,/), debug.flags(), 'correct flags');
+ // A.areEqual(debug.flags().length, 0, 'no flags');
+ A.isUndefined(debug.get("bar"), "no bar data");
+ A.isUndefined(debug.get("log"), "no log data");
+ A.isUndefined(debug.get("log.My"), "no log.My data");
+ A.isUndefined(debug.get("prof.A"), "cno prof.A");
+ A.isFalse(debug.flag("bar"), "flag not set");
+ },
+
+ 'debug interface on': function() {
+ var req = {
+ query: {
+ debug: "bar,log,log.My,prof.A"
+ }
+ },
+ debug = new Y.mojito.DebugAPI(Y, req, "debug", null);
+
+ debug.on("bar", function(d) {
+ d.data = "abc";
+ });
+ debug.log("here");
+ debug.logOn("My", "there");
+ debug.profOpen("A", "b", "b desc");
+ debug.profClose("A", "b", "first close");
+ debug.profEvent("A", "event 1");
+
+ AA.containsItems("bar,log,log.My,prof.A".split(/,/), debug.flags(), 'correct flags');
+ A.areEqual("abc", debug.get("bar").data, "correct bar data");
+ A.areEqual(1, debug.get("log").data.length, "correct log data count");
+ OA.areEqual({content: "here", jsonTreeOptions: undefined}, debug.get("log").data[0], "correct log data");
+ A.areEqual(1, debug.get("log.My").data.length, "correct log.My data count");
+ OA.areEqual({content: "there", jsonTreeOptions: undefined}, debug.get("log.My").data[0], "correct log.My data");
+ // do prof data
+ A.areEqual(1, Y.Object.keys(debug.get("prof.A").data).length, "correct prof.A data count");
+ A.isNotUndefined(debug.get("prof.A").data['b'], "correct prof.A node data");
+ A.areEqual('b desc', debug.get("prof.A").data['b'].desc, "correct prof.A desc");
+ A.isNumber(debug.get("prof.A").data['b'].start, "correct prof.A start");
+ A.areEqual(1, debug.get("prof.A").data['b'].close.length, "correct prof.A.close data count");
+ A.areEqual('first close', debug.get("prof.A").data['b'].close[0].type, "correct prof.A.close type");
+ A.isNumber(debug.get("prof.A").data['b'].close[0].time, "correct prof.A.close time");
+ A.areEqual(1, debug.get("prof.A").events.length, "correct prof.A.events count");
+ A.areEqual('event 1', debug.get("prof.A").events[0].type, "correct prof.A.events type");
+ A.isNumber(debug.get("prof.A").events[0].time, "correct prof.A.events time");
+
+ AA.containsItems("My".split(/,/), debug.getSpecialLogs(), 'correct flags');
+ AA.containsItems("A".split(/,/), debug.getKnownProf(), 'correct flags');
+ A.isTrue(debug.flag("bar"), "flag set");
+ },
+
+ 'debug interface more data': function() {
+ var req = {
+ query: {
+ debug: "log,log.My,prof.A"
+ }
+ },
+ debug = new Y.mojito.DebugAPI(Y, req, "debug", null);
+
+ debug.log("here");
+ debug.log("here2");
+ debug.logOn("My", "there");
+ debug.logOn("My", "there2");
+ debug.profOpen("A", "b", "b desc");
+ debug.profClose("A", "b", "first close");
+ debug.profClose("A", "b", "second close");
+ debug.profEvent("A", "event 1");
+ debug.profEvent("A", "event 2");
+ debug.profOpen("A", "c", "c desc");
+ debug.profClose("A", "c", "first close");
+ debug.profClose("A", "c", "second close");
+
+ debug.profClose("A", "d", "close no open");
+
+ AA.containsItems("log,log.My,prof.A".split(/,/), debug.flags(), 'correct flags');
+ A.areEqual(2, debug.get("log").data.length, "correct log data count");
+ OA.areEqual({content: "here", jsonTreeOptions: undefined}, debug.get("log").data[0], "correct log data");
+ OA.areEqual({content: "here2", jsonTreeOptions: undefined}, debug.get("log").data[1], "correct log data");
+ A.areEqual(2, debug.get("log.My").data.length, "correct log.My data count");
+ OA.areEqual({content: "there", jsonTreeOptions: undefined}, debug.get("log.My").data[0], "correct log.My data");
+ OA.areEqual({content: "there2", jsonTreeOptions: undefined}, debug.get("log.My").data[1], "correct log.My data");
+ // do prof data
+ A.areEqual(2, Y.Object.keys(debug.get("prof.A").data).length, "correct prof.A data count");
+ A.isNotUndefined(debug.get("prof.A").data['b'], "correct prof.A node b data");
+ A.areEqual('b desc', debug.get("prof.A").data['b'].desc, "correct prof.A b desc");
+ A.isNumber(debug.get("prof.A").data['b'].start, "correct prof.A b start");
+ A.areEqual(2, debug.get("prof.A").data['b'].close.length, "correct prof.A.close b data count");
+ A.areEqual('first close', debug.get("prof.A").data['b'].close[0].type, "correct prof.A.close b.0 type");
+ A.isNumber(debug.get("prof.A").data['b'].close[0].time, "correct prof.A.close b.0 time");
+ A.areEqual('second close', debug.get("prof.A").data['b'].close[1].type, "correct prof.A.close b.1 type");
+ A.isNumber(debug.get("prof.A").data['b'].close[1].time, "correct prof.A.close b.1 time");
+
+ A.areEqual(2, debug.get("prof.A").events.length, "correct prof.A.events count");
+ A.areEqual('event 1', debug.get("prof.A").events[0].type, "correct prof.A.events type");
+ A.isNumber(debug.get("prof.A").events[0].time, "correct prof.A.events time");
+ A.areEqual('event 2', debug.get("prof.A").events[1].type, "correct prof.A.events type");
+ A.isNumber(debug.get("prof.A").events[1].time, "correct prof.A.events time");
+
+ A.isNotUndefined(debug.get("prof.A").data['c'], "correct prof.A node c data");
+ A.areEqual('c desc', debug.get("prof.A").data['c'].desc, "correct prof.A c desc");
+ A.isNumber(debug.get("prof.A").data['c'].start, "correct prof.A c start");
+ A.areEqual(2, debug.get("prof.A").data['c'].close.length, "correct prof.A.close c data count");
+ A.areEqual('first close', debug.get("prof.A").data['c'].close[0].type, "correct prof.A.c.0 close type");
+ A.isNumber(debug.get("prof.A").data['c'].close[0].time, "correct prof.A.close c.0 time");
+ A.areEqual('second close', debug.get("prof.A").data['c'].close[1].type, "correct prof.A.c.1 close type");
+ A.isNumber(debug.get("prof.A").data['c'].close[1].time, "correct prof.A.close c.1 time");
+
+ AA.containsItems("My".split(/,/), debug.getSpecialLogs(), 'correct flags');
+ AA.containsItems("A".split(/,/), debug.getKnownProf(), 'correct flags');
+ },
+
+ 'instrimentatin errors': function() {
+ var req = {
+ query: {
+ debug: "bar,log.My"
+ }
+ },
+ debug = new Y.mojito.DebugAPI(Y, req, "debug", null);
+
+ debug.on("bar", function(d) {
+ throw new Error("fail 1");
+ });
+ debug.logOn("My", function () {throw new Error("fail 2");});
+
+ AA.containsItems("bar,log.My".split(/,/), debug.flags(), 'correct flags');
+ A.isNotUndefined(debug.getInstrumentationErrors(), "error list");
+ A.areEqual(2, debug.getInstrumentationErrors().length, 'number of errors');
+ A.areEqual('bar', debug.getInstrumentationErrors()[0].flag, 'bar error');
+ A.areEqual('log.My', debug.getInstrumentationErrors()[1].flag, 'log.My error');
+ },
+ }));
+
+ YUITest.TestRunner.add(suite);
+}, '0.0.1', {requires: ['mojito-debug-api']});
View
76 lib/tests/autoload/app/autoload/dispatch-tests.common.js
@@ -291,6 +291,82 @@ YUI.add('mojito-dispatcher-tests', function(Y, NAME) {
res.dispatch(command, adapter);
A.isTrue(actionInvoked);
+ },
+
+ 'debugger on': function() {
+
+ var store = {
+ getAppConfig: function() {
+ return { yui: { dependencyCalculations: 'precomputed+ondemand' },
+ debug: { queryParam: 'debug', debugPath: '/debug', debugMojit: '@Debug.index' }
+ };
+ },
+ expandInstance: function(instance, context, cb) {
+ cb(null, {
+ type: instance.type,
+ id: 'xyz123',
+ instanceId: 'xyz123',
+ yui: {
+ config: {},
+ langs: [],
+ requires: [],
+ sorted: [],
+ sortedPaths: {}
+ }
+ });
+ }
+ };
+
+ var coreModules = [];
+
+ var logger = {
+ log: function(msg, lvl, src) {
+ // not testing this
+ }
+ };
+
+ var loaderCalled = 0;
+ var loader = {
+ load: function(paths, cb) {
+ loaderCalled++;
+ cb();
+ }
+ };
+
+ var dispatcher = Y.mojito.Dispatcher;
+
+ var res = dispatcher.init(store, coreModules, logger, loader);
+ A.areSame(dispatcher, res);
+
+ var realCC = Y.mojito.ControllerContext;
+ Y.mojito.ControllerContext = function(cfg) {};
+ Y.mojito.ControllerContext.prototype.invoke = function(command, adapter) {};
+
+ var command = {
+ action: 'index',
+ instance: {
+ type: 'M'
+ },
+ context: {
+ lang: 'klingon',
+ langs: 'klingon'
+ }
+ };
+ var adapter = {
+ debug: {
+ profOpen: function() {},
+ profClose: function() {}
+ }
+ };
+ try {
+ dispatcher.dispatch(command, adapter);
+ }
+ finally {
+ Y.mojito.ControllerContext = realCC;
+ }
+
+ // this is about all we can get at
+ A.areSame(0, loaderCalled);
}
}));
View
223 lib/tests/autoload/middleware/debug-tests.js
@@ -0,0 +1,223 @@
+/*
+ * Copyright (c) 2011-2012, Yahoo! Inc. All rights reserved.
+ * Copyrights licensed under the New BSD License.
+ * See the accompanying LICENSE file for terms.
+ */
+YUI.add('mojito-middleware-debug-tests', function(Y, NAME) {
+ var path = require('path'),
+ suite = new YUITest.TestSuite(NAME),
+ debug_interface = require(path.join(__dirname, '../../../app/middleware/mojito-debug')),
+ A = YUITest.Assert;
+
+ suite.add(new YUITest.TestCase({
+ name: 'Debug middlewar',
+
+ 'create handler no debug': function() {
+ var store = {
+ getRoutes: function() {
+ return routes;
+ },
+ getAppConfig: function() {
+ return {};
+ }
+ },
+ config = {
+ Y: Y,
+ store: store
+ },
+ handler = debug_interface(config),
+ req = {};
+
+ A.isNotUndefined(handler, 'middleware is null');
+
+ handler(req, {}, function() {A.pass("handle req success");});
+
+ A.isNotNull(req.debug, 'api is null');
+
+ req.debug.addFlag('foo');
+ req.debug.on('foo', function() {A.fail("debugger should be off");});
+ A.isUndefined(req.debug.getInstrumentationErrors(), 'debugger should be off');
+ A.isUndefined(req.command, 'tried to load debugger when it was off');
+ },
+
+ 'create handler with debug': function() {
+ var store = {
+ getRoutes: function() {
+ return routes;
+ },
+ getAppConfig: function() {
+ return {
+ debug: {
+ queryParam: 'debug',
+ debugPath: '/debug',
+ debugMojit: '@Debug'
+ }
+ };
+ }
+ },
+ config = {
+ Y: Y,
+ store: store
+ },
+ handler = debug_interface(config),
+ req = {
+ url: '/foo'
+ };
+
+ A.isNotUndefined(handler, 'middleware is null');
+
+ handler(req, {}, function() {A.pass("handle req success");});
+
+ A.isNotUndefined(req.debug, 'api is null');
+
+ req.debug.addFlag("foo");
+ req.debug.on("foo", function() {A.fail("debugger is on");});
+ A.isUndefined(req.debug.getInstrumentationErrors(), 'debug on without query param');
+ A.isUndefined(req.command, 'tried to load debugger when query param missing');
+ },
+
+ 'debug direct': function() {
+ var store = {
+ getRoutes: function() {
+ return routes;
+ },
+ getAppConfig: function() {
+ return {
+ debug: {
+ queryParam: 'debug',
+ debugPath: '/debug',
+ debugMojit: '@Debug.index'
+ }
+ };
+ }
+ },
+ config = {
+ Y: Y,
+ store: store
+ },
+ handler = debug_interface(config),
+ req = {
+ url: '/debug/foo'
+ };
+
+ A.isNotUndefined(handler, 'middleware is null');
+
+ handler(req, {}, function() {A.pass("handle req success");});
+
+ A.isNull(req.url, 'null request on direct call');
+ },
+
+ 'create handler with debug query param': function() {
+ var app_config = {
+ debug: {
+ queryParam: 'debug',
+ debugPath: '/debug',
+ debugMojit: '@Debug.index'
+ }
+ },
+ store = {
+ getRoutes: function() {
+ return routes;
+ },
+ getAppConfig: function() {
+ return app_config;
+ }
+ },
+ config = {
+ Y: Y,
+ store: store
+ },
+ handler = debug_interface(config),
+ req = {
+ url: '/foo?debug=help'
+ };
+
+ A.isNotUndefined(handler, 'middleware is null');
+
+ handler(req, {}, function() {A.pass("handle req success");});
+
+ A.isNotUndefined(req.debug, 'api is null');
+
+ req.debug.addFlag("foo");
+ req.debug.on("foo", function() {A.pass("debugger is on");});
+ A.isUndefined(req.debug.getInstrumentationErrors(), 'instrumentation error');
+ A.isNotUndefined(req.command, 'failed to detect query param');
+ A.areEqual('/debug/foo?debug=help', req.url, 'redirect url wrong');
+ A.areEqual('Debug', req.command.instance.type, 'wrong debug mojit type');
+ A.areEqual('index', req.command.action, 'wrong mojit action');
+
+ req = {
+ url: '/foo?debug=help'
+ };
+ app_config.debug.debugMojit = 'Debug.index2';
+ handler = debug_interface(config);
+ handler(req, {}, function() {A.pass("handle req success");});
+ A.isNotUndefined(req.command, 'failed to detect query param');
+ A.areEqual('Debug', req.command.instance.base, 'wrong debug mojit base');
+ A.areEqual('index2', req.command.action, 'wrong mojit action');
+ },
+
+ 'create handler with request validator': function() {
+ var app_config = {
+ debug: {
+ queryParam: 'debug',
+ debugPath: '/debug',
+ debugMojit: '@Debug.index',
+ debugAllowed: 'my_validate_module.handler'
+ }
+ },
+ store = {
+ getRoutes: function() {
+ return routes;
+ },
+ getAppConfig: function() {
+ return app_config;
+ }
+ },
+ config = {
+ Y: Y,
+ store: store
+ },
+ handler = debug_interface(config),
+ req = {
+ url: '/foo'
+ };
+
+ Y.add('my_validate_module', function(Y, NAME) {
+ function ValidateRequest(req) {
+ return false;
+ };
+ Y.namespace(NAME).handler = ValidateRequest;
+ });
+
+
+ A.isNotUndefined(handler, 'middleware is null');
+
+ handler(req, {}, function() {A.pass("handle req success");});
+
+ A.isNotUndefined(req.debug, 'api is null');
+
+ A.areEqual('/foo', req.url, 'redirect url wrong');
+
+ Y.add('my_validate_module2', function(Y, NAME) {
+ function ValidateRequest(req) {
+ return true;
+ };
+ Y.namespace(NAME).handler = ValidateRequest;
+ });
+
+ req = {
+ url: '/foo?debug=help'
+ };
+ app_config.debug.debugAllowed = 'my_validate_module2.handler';
+ handler = debug_interface(config);
+ handler(req, {}, function() {A.pass("handle req success");});
+
+ A.isNotUndefined(req.debug, 'api is null');
+
+ A.areEqual('/debug/foo?debug=help', req.url, 'redirect url wrong');
+ }
+ }));
+
+ YUITest.TestRunner.add(suite);
+}, '0.0.1', {requires: ['mojito-debug-api']});
Please sign in to comment.
Something went wrong with that request. Please try again.