|
|
/*
|
|
|
Copyright (c) 2010, Yahoo! Inc. All rights reserved.
|
|
|
Code licensed under the BSD License:
|
|
|
http://developer.yahoo.com/yui/license.html
|
|
|
version: 2.8.2r1
|
|
|
*/
|
|
|
(function() {
|
|
|
|
|
|
/**
|
|
|
* The ProfilerViewer module provides a graphical display for viewing
|
|
|
* the output of the YUI Profiler <http://developer.yahoo.com/yui/profiler>.
|
|
|
* @module profilerviewer
|
|
|
* @requires yahoo, dom, event, element, profiler, yuiloader
|
|
|
*/
|
|
|
|
|
|
/**
|
|
|
* A widget to view YUI Profiler output.
|
|
|
* @namespace YAHOO.widget
|
|
|
* @class ProfilerViewer
|
|
|
* @extends YAHOO.util.Element
|
|
|
* @constructor
|
|
|
* @param {HTMLElement | String | Object} el(optional) The html
|
|
|
* element into which the ProfileViewer should be rendered.
|
|
|
* An element will be created if none provided.
|
|
|
* @param {Object} attr (optional) A key map of the ProfilerViewer's
|
|
|
* initial attributes. Ignored if first arg is an attributes object.
|
|
|
*/
|
|
|
YAHOO.widget.ProfilerViewer = function(el, attr) {
|
|
|
attr = attr || {};
|
|
|
if (arguments.length == 1 && !YAHOO.lang.isString(el) && !el.nodeName) {
|
|
|
attr = el;
|
|
|
el = attr.element || null;
|
|
|
}
|
|
|
if (!el && !attr.element) {
|
|
|
el = this._createProfilerViewerElement();
|
|
|
}
|
|
|
|
|
|
YAHOO.widget.ProfilerViewer.superclass.constructor.call(this, el, attr);
|
|
|
|
|
|
this._init();
|
|
|
|
|
|
};
|
|
|
|
|
|
YAHOO.extend(YAHOO.widget.ProfilerViewer, YAHOO.util.Element);
|
|
|
|
|
|
// Static members of YAHOO.widget.ProfilerViewer:
|
|
|
YAHOO.lang.augmentObject(YAHOO.widget.ProfilerViewer, {
|
|
|
/**
|
|
|
* Classname for ProfilerViewer containing element.
|
|
|
* @static
|
|
|
* @property CLASS
|
|
|
* @type string
|
|
|
* @public
|
|
|
* @default "yui-pv"
|
|
|
*/
|
|
|
CLASS: 'yui-pv',
|
|
|
|
|
|
/**
|
|
|
* Classname for ProfilerViewer button dashboard.
|
|
|
* @static
|
|
|
* @property CLASS_DASHBOARD
|
|
|
* @type string
|
|
|
* @public
|
|
|
* @default "yui-pv-dashboard"
|
|
|
*/
|
|
|
CLASS_DASHBOARD: 'yui-pv-dashboard',
|
|
|
|
|
|
/**
|
|
|
* Classname for the "refresh data" button.
|
|
|
* @static
|
|
|
* @property CLASS_REFRESH
|
|
|
* @type string
|
|
|
* @public
|
|
|
* @default "yui-pv-refresh"
|
|
|
*/
|
|
|
CLASS_REFRESH: 'yui-pv-refresh',
|
|
|
|
|
|
/**
|
|
|
* Classname for busy indicator in the dashboard.
|
|
|
* @static
|
|
|
* @property CLASS_BUSY
|
|
|
* @type string
|
|
|
* @public
|
|
|
* @default "yui-pv-busy"
|
|
|
*/
|
|
|
CLASS_BUSY: 'yui-pv-busy',
|
|
|
|
|
|
/**
|
|
|
* Classname for element containing the chart and chart
|
|
|
* legend elements.
|
|
|
* @static
|
|
|
* @property CLASS_CHART_CONTAINER
|
|
|
* @type string
|
|
|
* @public
|
|
|
* @default "yui-pv-chartcontainer"
|
|
|
*/
|
|
|
CLASS_CHART_CONTAINER: 'yui-pv-chartcontainer',
|
|
|
|
|
|
/**
|
|
|
* Classname for element containing the chart.
|
|
|
* @static
|
|
|
* @property CLASS_CHART
|
|
|
* @type string
|
|
|
* @public
|
|
|
* @default "yui-pv-chart"
|
|
|
*/
|
|
|
CLASS_CHART: 'yui-pv-chart',
|
|
|
|
|
|
/**
|
|
|
* Classname for element containing the chart's legend.
|
|
|
* @static
|
|
|
* @property CLASS_CHART_LEGEND
|
|
|
* @type string
|
|
|
* @public
|
|
|
* @default "yui-pv-chartlegend"
|
|
|
*/
|
|
|
CLASS_CHART_LEGEND: 'yui-pv-chartlegend',
|
|
|
|
|
|
/**
|
|
|
* Classname for element containing the datatable.
|
|
|
* @static
|
|
|
* @property CLASS_TABLE
|
|
|
* @type string
|
|
|
* @public
|
|
|
* @default "yui-pv-table"
|
|
|
*/
|
|
|
CLASS_TABLE: 'yui-pv-table',
|
|
|
|
|
|
/**
|
|
|
* Strings used in the UI.
|
|
|
* @static
|
|
|
* @property STRINGS
|
|
|
* @object
|
|
|
* @public
|
|
|
* @default English language strings for UI.
|
|
|
*/
|
|
|
STRINGS: {
|
|
|
title: "YUI Profiler (beta)",
|
|
|
buttons: {
|
|
|
viewprofiler: "View Profiler Data",
|
|
|
hideprofiler: "Hide Profiler Report",
|
|
|
showchart: "Show Chart",
|
|
|
hidechart: "Hide Chart",
|
|
|
refreshdata: "Refresh Data"
|
|
|
},
|
|
|
colHeads: {
|
|
|
//key: [column head label, width in pixels]
|
|
|
fn: ["Function/Method", null], //must auto-size
|
|
|
calls: ["Calls", 40],
|
|
|
avg: ["Average", 80],
|
|
|
min: ["Shortest", 70],
|
|
|
max: ["Longest", 70],
|
|
|
total: ["Total Time", 70],
|
|
|
pct: ["Percent", 70]
|
|
|
},
|
|
|
millisecondsAbbrev: "ms",
|
|
|
initMessage: "initialiazing chart...",
|
|
|
installFlashMessage: "Unable to load Flash content. The YUI Charts Control requires Flash Player 9.0.45 or higher. You can download the latest version of Flash Player from the <a href='http://www.adobe.com/go/getflashplayer'>Adobe Flash Player Download Center</a>."
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* Function used to format numbers in milliseconds
|
|
|
* for chart; must be publicly accessible, per Charts spec.
|
|
|
* @static
|
|
|
* @property timeAxisLabelFunction
|
|
|
* @type function
|
|
|
* @private
|
|
|
*/
|
|
|
timeAxisLabelFunction: function(n) {
|
|
|
var a = (n === Math.floor(n)) ? n : (Math.round(n*1000))/1000;
|
|
|
return (a + " " + YAHOO.widget.ProfilerViewer.STRINGS.millisecondsAbbrev);
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* Function used to format percent numbers for chart; must
|
|
|
* be publicly accessible, per Charts spec.
|
|
|
* @static
|
|
|
* @property percentAxisLabelFunction
|
|
|
* @type function
|
|
|
* @private
|
|
|
*/
|
|
|
percentAxisLabelFunction: function(n) {
|
|
|
var a = (n === Math.floor(n)) ? n : (Math.round(n*100))/100;
|
|
|
return (a + "%");
|
|
|
}
|
|
|
|
|
|
|
|
|
},true);
|
|
|
|
|
|
|
|
|
//
|
|
|
// STANDARD SHORTCUTS
|
|
|
//
|
|
|
var Dom = YAHOO.util.Dom;
|
|
|
var Event = YAHOO.util.Event;
|
|
|
var Profiler = YAHOO.tool.Profiler;
|
|
|
var PV = YAHOO.widget.ProfilerViewer;
|
|
|
var proto = PV.prototype;
|
|
|
|
|
|
|
|
|
//
|
|
|
// PUBLIC METHODS
|
|
|
//
|
|
|
|
|
|
/**
|
|
|
* Refreshes the data displayed in the ProfilerViewer. When called,
|
|
|
* this will invoke a refresh of the DataTable and (if displayed)
|
|
|
* the Chart.
|
|
|
* @method refreshData
|
|
|
* @return void
|
|
|
* @public
|
|
|
*/
|
|
|
proto.refreshData = function() {
|
|
|
this.fireEvent("dataRefreshEvent");
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Returns the element containing the console's header.
|
|
|
* @method getHeadEl
|
|
|
* @return HTMLElement
|
|
|
* @public
|
|
|
*/
|
|
|
proto.getHeadEl = function() {
|
|
|
return (this._headEl) ? Dom.get(this._headEl) : false;
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Returns the element containing the console's body, including
|
|
|
* the chart and the datatable..
|
|
|
* @method getBodyEl
|
|
|
* @return HTMLElement
|
|
|
* @public
|
|
|
*/
|
|
|
proto.getBodyEl = function() {
|
|
|
return (this._bodyEl) ? Dom.get(this._bodyEl) : false;
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Returns the element containing the console's chart.
|
|
|
* @method getChartEl
|
|
|
* @return HTMLElement
|
|
|
* @public
|
|
|
*/
|
|
|
proto.getChartEl = function() {
|
|
|
return (this._chartEl) ? Dom.get(this._chartEl) : false;
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Returns the element containing the console's dataTable.
|
|
|
* @method getTableEl
|
|
|
* @return HTMLElement
|
|
|
* @public
|
|
|
*/
|
|
|
proto.getTableEl = function() {
|
|
|
return (this._tableEl) ? Dom.get(this._tableEl) : false;
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Returns the element containing the console's DataTable
|
|
|
* instance.
|
|
|
* @method getDataTable
|
|
|
* @return YAHOO.widget.DataTable
|
|
|
* @public
|
|
|
*/
|
|
|
proto.getDataTable = function() {
|
|
|
return this._dataTable;
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Returns the element containing the console's Chart instance.
|
|
|
* @method getChart
|
|
|
* @return YAHOO.widget.BarChart
|
|
|
* @public
|
|
|
*/
|
|
|
proto.getChart = function() {
|
|
|
return this._chart;
|
|
|
};
|
|
|
|
|
|
|
|
|
//
|
|
|
// PRIVATE PROPERTIES
|
|
|
//
|
|
|
proto._rendered = false;
|
|
|
proto._headEl = null;
|
|
|
proto._bodyEl = null;
|
|
|
proto._toggleVisibleEl = null;
|
|
|
proto._busyEl = null;
|
|
|
proto._busy = false;
|
|
|
|
|
|
proto._tableEl = null;
|
|
|
proto._dataTable = null;
|
|
|
|
|
|
proto._chartEl = null;
|
|
|
proto._chartLegendEl = null;
|
|
|
proto._chartElHeight = 250;
|
|
|
proto._chart = null;
|
|
|
proto._chartInitialized = false;
|
|
|
|
|
|
//
|
|
|
// PRIVATE METHODS
|
|
|
//
|
|
|
|
|
|
proto._init = function() {
|
|
|
/**
|
|
|
* CUSTOM EVENTS
|
|
|
**/
|
|
|
|
|
|
/**
|
|
|
* Fired when a data refresh is requested. No arguments are passed
|
|
|
* with this event.
|
|
|
*
|
|
|
* @event refreshDataEvent
|
|
|
*/
|
|
|
this.createEvent("dataRefreshEvent");
|
|
|
|
|
|
/**
|
|
|
* Fired when the viewer canvas first renders. No arguments are passed
|
|
|
* with this event.
|
|
|
*
|
|
|
* @event renderEvent
|
|
|
*/
|
|
|
this.createEvent("renderEvent");
|
|
|
|
|
|
this.on("dataRefreshEvent", this._refreshDataTable, this, true);
|
|
|
|
|
|
this._initLauncherDOM();
|
|
|
|
|
|
if(this.get("showChart")) {
|
|
|
this.on("sortedByChange", this._refreshChart);
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* If no element is passed in, create it as the first element
|
|
|
* in the document.
|
|
|
* @method _createProfilerViewerElement
|
|
|
* @return HTMLElement
|
|
|
* @private
|
|
|
*/
|
|
|
proto._createProfilerViewerElement = function() {
|
|
|
|
|
|
var el = document.createElement("div");
|
|
|
document.body.insertBefore(el, document.body.firstChild);
|
|
|
Dom.addClass(el, this.SKIN_CLASS);
|
|
|
Dom.addClass(el, PV.CLASS);
|
|
|
return el;
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Provides a readable name for the ProfilerViewer instance.
|
|
|
* @method toString
|
|
|
* @return String
|
|
|
* @private
|
|
|
*/
|
|
|
proto.toString = function() {
|
|
|
return "ProfilerViewer " + (this.get('id') || this.get('tagName'));
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Toggles visibility of the viewer canvas.
|
|
|
* @method _toggleVisible
|
|
|
* @return void
|
|
|
* @private
|
|
|
*/
|
|
|
proto._toggleVisible = function() {
|
|
|
|
|
|
var newVis = (this.get("visible")) ? false : true;
|
|
|
this.set("visible", newVis);
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Shows the viewer canvas.
|
|
|
* @method show
|
|
|
* @return void
|
|
|
* @private
|
|
|
*/
|
|
|
proto._show = function() {
|
|
|
if(!this._busy) {
|
|
|
this._setBusyState(true);
|
|
|
if(!this._rendered) {
|
|
|
var loader = new YAHOO.util.YUILoader();
|
|
|
if (this.get("base")) {
|
|
|
loader.base = this.get("base");
|
|
|
}
|
|
|
|
|
|
var modules = ["datatable"];
|
|
|
if(this.get("showChart")) {
|
|
|
modules.push("charts");
|
|
|
}
|
|
|
|
|
|
loader.insert({ require: modules,
|
|
|
onSuccess: function() {
|
|
|
this._render();
|
|
|
},
|
|
|
scope: this});
|
|
|
} else {
|
|
|
var el = this.get("element");
|
|
|
Dom.removeClass(el, "yui-pv-minimized");
|
|
|
this._toggleVisibleEl.innerHTML = PV.STRINGS.buttons.hideprofiler;
|
|
|
|
|
|
//The Flash Charts component can't be set to display:none,
|
|
|
//and even after positioning it offscreen the screen
|
|
|
//may fail to repaint in some browsers. Adding an empty
|
|
|
//style rule to the console body can help force a repaint:
|
|
|
Dom.addClass(el, "yui-pv-null");
|
|
|
Dom.removeClass(el, "yui-pv-null");
|
|
|
|
|
|
//Always refresh data when changing to visible:
|
|
|
this.refreshData();
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Hides the viewer canvas.
|
|
|
* @method hide
|
|
|
* @return void
|
|
|
* @private
|
|
|
*/
|
|
|
proto._hide = function() {
|
|
|
this._toggleVisibleEl.innerHTML = PV.STRINGS.buttons.viewprofiler;
|
|
|
Dom.addClass(this.get("element"), "yui-pv-minimized");
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Render the viewer canvas
|
|
|
* @method _render
|
|
|
* @return void
|
|
|
* @private
|
|
|
*/
|
|
|
proto._render = function() {
|
|
|
|
|
|
Dom.removeClass(this.get("element"), "yui-pv-minimized");
|
|
|
|
|
|
this._initViewerDOM();
|
|
|
this._initDataTable();
|
|
|
if(this.get("showChart")) {
|
|
|
this._initChartDOM();
|
|
|
this._initChart();
|
|
|
}
|
|
|
this._rendered = true;
|
|
|
this._toggleVisibleEl.innerHTML = PV.STRINGS.buttons.hideprofiler;
|
|
|
|
|
|
this.fireEvent("renderEvent");
|
|
|
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Set up the DOM structure for the ProfilerViewer launcher.
|
|
|
* @method _initLauncherDOM
|
|
|
* @private
|
|
|
*/
|
|
|
proto._initLauncherDOM = function() {
|
|
|
|
|
|
var el = this.get("element");
|
|
|
Dom.addClass(el, PV.CLASS);
|
|
|
Dom.addClass(el, "yui-pv-minimized");
|
|
|
|
|
|
this._headEl = document.createElement("div");
|
|
|
Dom.addClass(this._headEl, "hd");
|
|
|
|
|
|
var s = PV.STRINGS.buttons;
|
|
|
var b = (this.get("visible")) ? s.hideprofiler : s.viewprofiler;
|
|
|
|
|
|
this._toggleVisibleEl = this._createButton(b, this._headEl);
|
|
|
|
|
|
this._refreshEl = this._createButton(s.refreshdata, this._headEl);
|
|
|
Dom.addClass(this._refreshEl, PV.CLASS_REFRESH);
|
|
|
|
|
|
this._busyEl = document.createElement("span");
|
|
|
this._headEl.appendChild(this._busyEl);
|
|
|
|
|
|
var title = document.createElement("h4");
|
|
|
title.innerHTML = PV.STRINGS.title;
|
|
|
this._headEl.appendChild(title);
|
|
|
|
|
|
el.appendChild(this._headEl);
|
|
|
|
|
|
Event.on(this._toggleVisibleEl, "click", this._toggleVisible, this, true);
|
|
|
Event.on(this._refreshEl, "click", function() {
|
|
|
if(!this._busy) {
|
|
|
this._setBusyState(true);
|
|
|
this.fireEvent("dataRefreshEvent");
|
|
|
}
|
|
|
}, this, true);
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Set up the DOM structure for the ProfilerViewer canvas,
|
|
|
* including the holder for the DataTable.
|
|
|
* @method _initViewerDOM
|
|
|
* @private
|
|
|
*/
|
|
|
proto._initViewerDOM = function() {
|
|
|
|
|
|
var el = this.get("element");
|
|
|
this._bodyEl = document.createElement("div");
|
|
|
Dom.addClass(this._bodyEl, "bd");
|
|
|
this._tableEl = document.createElement("div");
|
|
|
Dom.addClass(this._tableEl, PV.CLASS_TABLE);
|
|
|
this._bodyEl.appendChild(this._tableEl);
|
|
|
el.appendChild(this._bodyEl);
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Set up the DOM structure for the ProfilerViewer canvas.
|
|
|
* @method _initChartDOM
|
|
|
* @private
|
|
|
*/
|
|
|
proto._initChartDOM = function() {
|
|
|
|
|
|
this._chartContainer = document.createElement("div");
|
|
|
Dom.addClass(this._chartContainer, PV.CLASS_CHART_CONTAINER);
|
|
|
|
|
|
var chl = document.createElement("div");
|
|
|
Dom.addClass(chl, PV.CLASS_CHART_LEGEND);
|
|
|
|
|
|
var chw = document.createElement("div");
|
|
|
|
|
|
this._chartLegendEl = document.createElement("dl");
|
|
|
this._chartLegendEl.innerHTML = "<dd>" + PV.STRINGS.initMessage + "</dd>";
|
|
|
|
|
|
this._chartEl = document.createElement("div");
|
|
|
Dom.addClass(this._chartEl, PV.CLASS_CHART);
|
|
|
|
|
|
var msg = document.createElement("p");
|
|
|
msg.innerHTML = PV.STRINGS.installFlashMessage;
|
|
|
this._chartEl.appendChild(msg);
|
|
|
|
|
|
this._chartContainer.appendChild(chl);
|
|
|
chl.appendChild(chw);
|
|
|
chw.appendChild(this._chartLegendEl);
|
|
|
this._chartContainer.appendChild(this._chartEl);
|
|
|
this._bodyEl.insertBefore(this._chartContainer,this._tableEl);
|
|
|
};
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Create anchor elements for use as buttons. Args: label
|
|
|
* is text to appear on the face of the button, parentEl
|
|
|
* is the el to which the anchor will be attached, position
|
|
|
* is true for inserting as the first node and false for
|
|
|
* inserting as the last node of the parentEl.
|
|
|
* @method _createButton
|
|
|
* @private
|
|
|
*/
|
|
|
proto._createButton = function(label, parentEl, position) {
|
|
|
var b = document.createElement("a");
|
|
|
b.innerHTML = b.title = label;
|
|
|
if(parentEl) {
|
|
|
if(!position) {
|
|
|
parentEl.appendChild(b);
|
|
|
} else {
|
|
|
parentEl.insertBefore(b, parentEl.firstChild);
|
|
|
}
|
|
|
}
|
|
|
return b;
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Set's console busy state.
|
|
|
* @method _setBusyState
|
|
|
* @private
|
|
|
**/
|
|
|
proto._setBusyState = function(b) {
|
|
|
if(b) {
|
|
|
Dom.addClass(this._busyEl, PV.CLASS_BUSY);
|
|
|
this._busy = true;
|
|
|
} else {
|
|
|
Dom.removeClass(this._busyEl, PV.CLASS_BUSY);
|
|
|
this._busy = false;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Generages a sorting function based on current sortedBy
|
|
|
* values.
|
|
|
* @method _createProfilerViewerElement
|
|
|
* @private
|
|
|
**/
|
|
|
proto._genSortFunction = function(key, dir) {
|
|
|
var by = key;
|
|
|
var direction = dir;
|
|
|
return function(a, b) {
|
|
|
if (direction == YAHOO.widget.DataTable.CLASS_ASC) {
|
|
|
return a[by] - b[by];
|
|
|
} else {
|
|
|
return ((a[by] - b[by]) * -1);
|
|
|
}
|
|
|
};
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Utility function for array sums.
|
|
|
* @method _arraySum
|
|
|
* @private
|
|
|
**/
|
|
|
var _arraySum = function(arr){
|
|
|
var ct = 0;
|
|
|
for(var i = 0; i < arr.length; ct+=arr[i++]){}
|
|
|
return ct;
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Retrieves data from Profiler, filtering and sorting as needed
|
|
|
* based on current widget state. Adds calculated percentage
|
|
|
* column and function name to data returned by Profiler.
|
|
|
* @method _getProfilerData
|
|
|
* @private
|
|
|
**/
|
|
|
proto._getProfilerData = function() {
|
|
|
|
|
|
var obj = Profiler.getFullReport();
|
|
|
var arr = [];
|
|
|
var totalTime = 0;
|
|
|
for (name in obj) {
|
|
|
if (YAHOO.lang.hasOwnProperty(obj, name)) {
|
|
|
var r = obj[name];
|
|
|
var o = {};
|
|
|
o.fn = name; //add function name to record
|
|
|
o.points = r.points.slice(); //copy live array
|
|
|
o.calls = r.calls;
|
|
|
o.min = r.min;
|
|
|
o.max = r.max;
|
|
|
o.avg = r.avg;
|
|
|
o.total = _arraySum(o.points);
|
|
|
o.points = r.points;
|
|
|
var f = this.get("filter");
|
|
|
if((!f) || (f(o))) {
|
|
|
arr.push(o);
|
|
|
totalTime += o.total;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
//add calculated percentage column
|
|
|
for (var i = 0, j = arr.length; i < j; i++) {
|
|
|
arr[i].pct = (totalTime) ? (arr[i].total * 100) / totalTime : 0;
|
|
|
}
|
|
|
|
|
|
var sortedBy = this.get("sortedBy");
|
|
|
var key = sortedBy.key;
|
|
|
var dir = sortedBy.dir;
|
|
|
|
|
|
arr.sort(this._genSortFunction(key, dir));
|
|
|
|
|
|
|
|
|
return arr;
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Set up the DataTable.
|
|
|
* @method _initDataTable
|
|
|
* @private
|
|
|
*/
|
|
|
proto._initDataTable = function() {
|
|
|
|
|
|
var self = this;
|
|
|
|
|
|
//Set up the JS Function DataSource, pulling data from
|
|
|
//the Profiler.
|
|
|
this._dataSource = new YAHOO.util.DataSource(
|
|
|
function() {
|
|
|
return self._getProfilerData.call(self);
|
|
|
},
|
|
|
{
|
|
|
responseType: YAHOO.util.DataSource.TYPE_JSARRAY,
|
|
|
maxCacheEntries: 0
|
|
|
}
|
|
|
);
|
|
|
var ds = this._dataSource;
|
|
|
|
|
|
ds.responseSchema =
|
|
|
{
|
|
|
fields: [ "fn", "avg", "calls", "max", "min", "total", "pct", "points"]
|
|
|
};
|
|
|
|
|
|
//Set up the DataTable.
|
|
|
var formatTimeValue = function(elCell, oRecord, oColumn, oData) {
|
|
|
var a = (oData === Math.floor(oData)) ? oData : (Math.round(oData*1000))/1000;
|
|
|
elCell.innerHTML = a + " " + PV.STRINGS.millisecondsAbbrev;
|
|
|
};
|
|
|
|
|
|
var formatPercent = function(elCell, oRecord, oColumn, oData) {
|
|
|
var a = (oData === Math.floor(oData)) ? oData : (Math.round(oData*100))/100;
|
|
|
elCell.innerHTML = a + "%";
|
|
|
};
|
|
|
|
|
|
var a = YAHOO.widget.DataTable.CLASS_ASC;
|
|
|
var d = YAHOO.widget.DataTable.CLASS_DESC;
|
|
|
var c = PV.STRINGS.colHeads;
|
|
|
var f = formatTimeValue;
|
|
|
|
|
|
var cols = [
|
|
|
{key:"fn", sortable:true, label: c.fn[0],
|
|
|
sortOptions: {defaultDir:a},
|
|
|
resizeable: (YAHOO.util.DragDrop) ? true : false,
|
|
|
minWidth:c.fn[1]},
|
|
|
{key:"calls", sortable:true, label: c.calls[0],
|
|
|
sortOptions: {defaultDir:d},
|
|
|
width:c.calls[1]},
|
|
|
{key:"avg", sortable:true, label: c.avg[0],
|
|
|
sortOptions: {defaultDir:d},
|
|
|
formatter:f,
|
|
|
width:c.avg[1]},
|
|
|
{key:"min", sortable:true, label: c.min[0],
|
|
|
sortOptions: {defaultDir:a},
|
|
|
formatter:f,
|
|
|
width:c.min[1]},
|
|
|
{key:"max", sortable:true, label: c.max[0],
|
|
|
sortOptions: {defaultDir:d},
|
|
|
formatter:f,
|
|
|
width:c.max[1]},
|
|
|
{key:"total", sortable:true, label: c.total[0],
|
|
|
sortOptions: {defaultDir:d},
|
|
|
formatter:f,
|
|
|
width:c.total[1]},
|
|
|
{key:"pct", sortable:true, label: c.pct[0],
|
|
|
sortOptions: {defaultDir:d},
|
|
|
formatter:formatPercent,
|
|
|
width:c.pct[1]}
|
|
|
];
|
|
|
|
|
|
this._dataTable = new YAHOO.widget.DataTable(this._tableEl, cols, ds, {
|
|
|
scrollable:true,
|
|
|
height:this.get("tableHeight"),
|
|
|
initialRequest:null,
|
|
|
sortedBy: {
|
|
|
key: "total",
|
|
|
dir: YAHOO.widget.DataTable.CLASS_DESC
|
|
|
}
|
|
|
});
|
|
|
var dt = this._dataTable;
|
|
|
|
|
|
//Wire up DataTable events to drive the rest of the UI.
|
|
|
dt.subscribe("sortedByChange", this._sortedByChange, this, true);
|
|
|
dt.subscribe("renderEvent", this._dataTableRenderHandler, this, true);
|
|
|
dt.subscribe("initEvent", this._dataTableRenderHandler, this, true);
|
|
|
Event.on(this._tableEl.getElementsByTagName("th"), "click", this._thClickHandler, this, true);
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Proxy the sort event in DataTable into the ProfilerViewer
|
|
|
* attribute.
|
|
|
* @method _sortedByChange
|
|
|
* @private
|
|
|
**/
|
|
|
proto._sortedByChange = function(o) {
|
|
|
if(o.newValue && o.newValue.key) {
|
|
|
this.set("sortedBy", {key: o.newValue.key, dir:o.newValue.dir});
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Proxy the render event in DataTable into the ProfilerViewer
|
|
|
* attribute.
|
|
|
* @method _dataTableRenderHandler
|
|
|
* @private
|
|
|
**/
|
|
|
proto._dataTableRenderHandler = function(o) {
|
|
|
this._setBusyState(false);
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Event handler for clicks on the DataTable's sortable column
|
|
|
* heads.
|
|
|
* @method _thClickHandler
|
|
|
* @private
|
|
|
**/
|
|
|
proto._thClickHandler = function(o) {
|
|
|
this._setBusyState(true);
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Refresh DataTable, getting new data from Profiler.
|
|
|
* @method _refreshDataTable
|
|
|
* @private
|
|
|
**/
|
|
|
proto._refreshDataTable = function(args) {
|
|
|
var dt = this._dataTable;
|
|
|
dt.getDataSource().sendRequest("", dt.onDataReturnInitializeTable, dt);
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Refresh chart, getting new data from table.
|
|
|
* @method _refreshChart
|
|
|
* @private
|
|
|
**/
|
|
|
proto._refreshChart = function() {
|
|
|
|
|
|
switch (this.get("sortedBy").key) {
|
|
|
case "fn":
|
|
|
/*Keep the same data on the chart, but force update to
|
|
|
reflect new sort order on function/method name: */
|
|
|
this._chart.set("dataSource", this._chart.get("dataSource"));
|
|
|
/*no further action necessary; chart redraws*/
|
|
|
return;
|
|
|
case "calls":
|
|
|
/*Null out the xAxis formatting before redrawing chart.*/
|
|
|
this._chart.set("xAxis", this._chartAxisDefinitionPlain);
|
|
|
break;
|
|
|
case "pct":
|
|
|
this._chart.set("xAxis", this._chartAxisDefinitionPercent);
|
|
|
break;
|
|
|
default:
|
|
|
/*Set the default xAxis; redraw legend; set the new series definition.*/
|
|
|
this._chart.set("xAxis", this._chartAxisDefinitionTime);
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
this._drawChartLegend();
|
|
|
this._chart.set("series", this._getSeriesDef(this.get("sortedBy").key));
|
|
|
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Get data for the Chart from DataTable recordset
|
|
|
* @method _getChartData
|
|
|
* @private
|
|
|
*/
|
|
|
proto._getChartData = function() {
|
|
|
//var records = this._getProfilerData();
|
|
|
var records = this._dataTable.getRecordSet().getRecords(0, this.get("maxChartFunctions"));
|
|
|
var arr = [];
|
|
|
for (var i = 0, j = records.length; i<j; i++) {
|
|
|
arr.push(records[i].getData());
|
|
|
}
|
|
|
return arr;
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Build series definition based on current configuration attributes.
|
|
|
* @method _getSeriesDef
|
|
|
* @private
|
|
|
*/
|
|
|
proto._getSeriesDef = function(field) {
|
|
|
var sd = this.get("chartSeriesDefinitions")[field];
|
|
|
var arr = [];
|
|
|
for(var i = 0, j = sd.group.length; i<j; i++) {
|
|
|
var c = this.get("chartSeriesDefinitions")[sd.group[i]];
|
|
|
arr.push(
|
|
|
{displayName:c.displayName,
|
|
|
xField:c.xField,
|
|
|
style: {color:c.style.color, size:c.style.size}
|
|
|
}
|
|
|
);
|
|
|
}
|
|
|
|
|
|
return arr;
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Set up the Chart.
|
|
|
* @method _initChart
|
|
|
* @private
|
|
|
*/
|
|
|
proto._initChart = function() {
|
|
|
|
|
|
this._sizeChartCanvas();
|
|
|
|
|
|
YAHOO.widget.Chart.SWFURL = this.get("swfUrl");
|
|
|
|
|
|
var self = this;
|
|
|
|
|
|
//Create DataSource based on records currently displayed
|
|
|
//at the top of the sort list in the DataTable.
|
|
|
var ds = new YAHOO.util.DataSource(
|
|
|
//force the jsfunction DataSource to run in the scope of
|
|
|
//the ProfilerViewer, not in the YAHOO.util.DataSource scope:
|
|
|
function() {
|
|
|
return self._getChartData.call(self);
|
|
|
},
|
|
|
{
|
|
|
responseType: YAHOO.util.DataSource.TYPE_JSARRAY,
|
|
|
maxCacheEntries: 0
|
|
|
}
|
|
|
);
|
|
|
|
|
|
ds.responseSchema =
|
|
|
{
|
|
|
fields: [ "fn", "avg", "calls", "max", "min", "total", "pct" ]
|
|
|
};
|
|
|
|
|
|
ds.subscribe('responseEvent', this._sizeChartCanvas, this, true);
|
|
|
|
|
|
//Set up the chart itself.
|
|
|
this._chartAxisDefinitionTime = new YAHOO.widget.NumericAxis();
|
|
|
this._chartAxisDefinitionTime.labelFunction = "YAHOO.widget.ProfilerViewer.timeAxisLabelFunction";
|
|
|
|
|
|
this._chartAxisDefinitionPercent = new YAHOO.widget.NumericAxis();
|
|
|
this._chartAxisDefinitionPercent.labelFunction = "YAHOO.widget.ProfilerViewer.percentAxisLabelFunction";
|
|
|
|
|
|
this._chartAxisDefinitionPlain = new YAHOO.widget.NumericAxis();
|
|
|
|
|
|
this._chart = new YAHOO.widget.BarChart( this._chartEl, ds,
|
|
|
{
|
|
|
yField: "fn",
|
|
|
series: this._getSeriesDef(this.get("sortedBy").key),
|
|
|
style: this.get("chartStyle"),
|
|
|
xAxis: this._chartAxisDefinitionTime
|
|
|
} );
|
|
|
|
|
|
this._drawChartLegend();
|
|
|
this._chartInitialized = true;
|
|
|
this._dataTable.unsubscribe("initEvent", this._initChart, this);
|
|
|
this._dataTable.subscribe("initEvent", this._refreshChart, this, true);
|
|
|
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Set up the Chart's legend
|
|
|
* @method _drawChartLegend
|
|
|
* @private
|
|
|
**/
|
|
|
proto._drawChartLegend = function() {
|
|
|
var seriesDefs = this.get("chartSeriesDefinitions");
|
|
|
var currentDef = seriesDefs[this.get("sortedBy").key];
|
|
|
var l = this._chartLegendEl;
|
|
|
l.innerHTML = "";
|
|
|
for(var i = 0, j = currentDef.group.length; i<j; i++) {
|
|
|
var c = seriesDefs[currentDef.group[i]];
|
|
|
var dt = document.createElement("dt");
|
|
|
Dom.setStyle(dt, "backgroundColor", "#" + c.style.color);
|
|
|
var dd = document.createElement("dd");
|
|
|
dd.innerHTML = c.displayName;
|
|
|
l.appendChild(dt);
|
|
|
l.appendChild(dd);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Resize the chart's canvas if based on number of records
|
|
|
* returned from the chart's datasource.
|
|
|
* @method _sizeChartCanvas
|
|
|
* @private
|
|
|
**/
|
|
|
proto._sizeChartCanvas = function(o) {
|
|
|
var bars = (o) ? o.response.length : this.get("maxChartFunctions");
|
|
|
var s = (bars * 36) + 34;
|
|
|
if (s != parseInt(this._chartElHeight, 10)) {
|
|
|
this._chartElHeight = s;
|
|
|
Dom.setStyle(this._chartEl, "height", s + "px");
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* setAttributeConfigs TabView specific properties.
|
|
|
* @method initAttributes
|
|
|
* @param {Object} attr Hash of initial attributes
|
|
|
* @method initAttributes
|
|
|
* @private
|
|
|
*/
|
|
|
proto.initAttributes = function(attr) {
|
|
|
YAHOO.widget.ProfilerViewer.superclass.initAttributes.call(this, attr);
|
|
|
/**
|
|
|
* The YUI Loader base path from which to pull YUI files needed
|
|
|
* in the rendering of the ProfilerViewer canvas. Passed directly
|
|
|
* to YUI Loader. Leave blank to draw files from
|
|
|
* yui.yahooapis.com.
|
|
|
* @attribute base
|
|
|
* @type string
|
|
|
* @default ""
|
|
|
*/
|
|
|
this.setAttributeConfig('base', {
|
|
|
value: attr.base
|
|
|
});
|
|
|
|
|
|
/**
|
|
|
* The height of the DataTable. The table will scroll
|
|
|
* vertically if the content overflows the specified
|
|
|
* height.
|
|
|
* @attribute tableHeight
|
|
|
* @type string
|
|
|
* @default "15em"
|
|
|
*/
|
|
|
this.setAttributeConfig('tableHeight', {
|
|
|
value: attr.tableHeight || "15em",
|
|
|
method: function(s) {
|
|
|
if(this._dataTable) {
|
|
|
this._dataTable.set("height", s);
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
|
|
|
/**
|
|
|
* The default column key to sort by. Valid keys are: fn, calls,
|
|
|
* avg, min, max, total. Valid dir values are:
|
|
|
* YAHOO.widget.DataTable.CLASS_ASC and
|
|
|
* YAHOO.widget.DataTable.CLASS_DESC (or their
|
|
|
* string equivalents).
|
|
|
* @attribute sortedBy
|
|
|
* @type string
|
|
|
* @default {key:"total", dir:"yui-dt-desc"}
|
|
|
*/
|
|
|
this.setAttributeConfig('sortedBy', {
|
|
|
value: attr.sortedBy || {key:"total", dir:"yui-dt-desc"}
|
|
|
});
|
|
|
|
|
|
/**
|
|
|
* A filter function to use in selecting functions that will
|
|
|
* appear in the ProfilerViewer report. The function is passed
|
|
|
* a function report object and should return a boolean indicating
|
|
|
* whether that function should be included in the ProfilerViewer
|
|
|
* display. The argument is structured as follows:
|
|
|
*
|
|
|
* {
|
|
|
* fn: <str function name>,
|
|
|
* calls : <n number of calls>,
|
|
|
* avg : <n average call duration>,
|
|
|
* max: <n duration of longest call>,
|
|
|
* min: <n duration of shortest call>,
|
|
|
* total: <n total time of all calls>
|
|
|
* points : <array time in ms of each call>
|
|
|
* }
|
|
|
*
|
|
|
* For example, you would use the follwing filter function to
|
|
|
* return only functions that have been called at least once:
|
|
|
*
|
|
|
* function(o) {
|
|
|
* return (o.calls > 0);
|
|
|
* }
|
|
|
*
|
|
|
* @attribute filter
|
|
|
* @type function
|
|
|
* @default null
|
|
|
*/
|
|
|
this.setAttributeConfig('filter', {
|
|
|
value: attr.filter || null,
|
|
|
validator: YAHOO.lang.isFunction
|
|
|
});
|
|
|
|
|
|
/**
|
|
|
* The path to the YUI Charts swf file; must be a full URI
|
|
|
* or a path relative to the page being profiled. Changes at runtime
|
|
|
* not supported; pass this value in at instantiation.
|
|
|
* @attribute swfUrl
|
|
|
* @type string
|
|
|
* @default "http://yui.yahooapis.com/2.5.0/build/charts/assets/charts.swf"
|
|
|
*/
|
|
|
this.setAttributeConfig('swfUrl', {
|
|
|
value: attr.swfUrl || "http://yui.yahooapis.com/2.5.0/build/charts/assets/charts.swf"
|
|
|
});
|
|
|
|
|
|
/**
|
|
|
* The maximum number of functions to profile in the chart. The
|
|
|
* greater the number of functions, the greater the height of the
|
|
|
* chart canvas.
|
|
|
* height.
|
|
|
* @attribute maxChartFunctions
|
|
|
* @type int
|
|
|
* @default 6
|
|
|
*/
|
|
|
this.setAttributeConfig('maxChartFunctions', {
|
|
|
value: attr.maxChartFunctions || 6,
|
|
|
method: function(s) {
|
|
|
if(this._rendered) {
|
|
|
this._sizeChartCanvas();
|
|
|
}
|
|
|
},
|
|
|
validator: YAHOO.lang.isNumber
|
|
|
});
|
|
|
|
|
|
/**
|
|
|
* The style object that defines the chart's visual presentation.
|
|
|
* Conforms to the style attribute passed to the Charts Control
|
|
|
* constructor. See Charts Control User's Guide for more information
|
|
|
* on how to format this object.
|
|
|
* @attribute chartStyle
|
|
|
* @type obj
|
|
|
* @default See JS source for default definitions.
|
|
|
*/
|
|
|
this.setAttributeConfig('chartStyle', {
|
|
|
value: attr.chartStyle || {
|
|
|
font:
|
|
|
{
|
|
|
name: "Arial",
|
|
|
color: 0xeeee5c,
|
|
|
size: 12
|
|
|
},
|
|
|
background:
|
|
|
{
|
|
|
color: "6e6e63"
|
|
|
}
|
|
|
},
|
|
|
method: function() {
|
|
|
if(this._rendered && this.get("showChart")) {
|
|
|
this._refreshChart();
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
|
|
|
/**
|
|
|
* The series definition information to use when charting
|
|
|
* specific fields on the chart. displayName, xField,
|
|
|
* and style members are used to construct the series
|
|
|
* definition; the "group" member is the array of fields
|
|
|
* that should be charted when the table is sorted by a
|
|
|
* given field.
|
|
|
* @attribute chartSeriesDefinitions
|
|
|
* @type obj
|
|
|
* @default See JS source for full default definitions.
|
|
|
*/
|
|
|
this.setAttributeConfig('chartSeriesDefinitions', {
|
|
|
value: attr.chartSeriesDefinitions || {
|
|
|
total: {
|
|
|
displayName: PV.STRINGS.colHeads.total[0],
|
|
|
xField: "total",
|
|
|
style: {color:"4d95dd", size:20},
|
|
|
group: ["total"]
|
|
|
},
|
|
|
calls: {
|
|
|
displayName: PV.STRINGS.colHeads.calls[0],
|
|
|
xField: "calls",
|
|
|
style: {color:"edff9f", size:20},
|
|
|
group: ["calls"]
|
|
|
},
|
|
|
avg: {
|
|
|
displayName: PV.STRINGS.colHeads.avg[0],
|
|
|
xField: "avg",
|
|
|
style: {color:"209daf", size:9},
|
|
|
group: ["avg", "min", "max"]
|
|
|
},
|
|
|
min: {
|
|
|
displayName: PV.STRINGS.colHeads.min[0],
|
|
|
xField: "min",
|
|
|
style: {color:"b6ecf4", size:9},
|
|
|
group: ["avg", "min", "max"]
|
|
|
},
|
|
|
max: {
|
|
|
displayName: PV.STRINGS.colHeads.max[0],
|
|
|
xField: "max",
|
|
|
style: {color:"29c7de", size:9},
|
|
|
group: ["avg", "min", "max"]
|
|
|
},
|
|
|
pct: {
|
|
|
displayName: PV.STRINGS.colHeads.pct[0],
|
|
|
xField: "pct",
|
|
|
style: {color:"C96EDB", size:20},
|
|
|
group: ["pct"]
|
|
|
}
|
|
|
},
|
|
|
method: function() {
|
|
|
if(this._rendered && this.get("showChart")) {
|
|
|
this._refreshChart();
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
|
|
|
/**
|
|
|
* The default visibility setting for the viewer canvas. If true,
|
|
|
* the viewer will load all necessary files and render itself
|
|
|
* immediately upon instantiation; otherwise, the viewer will
|
|
|
* load only minimal resources until the user toggles visibility
|
|
|
* via the UI.
|
|
|
* @attribute visible
|
|
|
* @type boolean
|
|
|
* @default false
|
|
|
*/
|
|
|
this.setAttributeConfig('visible', {
|
|
|
value: attr.visible || false,
|
|
|
validator: YAHOO.lang.isBoolean,
|
|
|
method: function(b) {
|
|
|
if(b) {
|
|
|
this._show();
|
|
|
} else {
|
|
|
if (this._rendered) {
|
|
|
this._hide();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
|
|
|
/**
|
|
|
* The default visibility setting for the chart.
|
|
|
* @attribute showChart
|
|
|
* @type boolean
|
|
|
* @default true
|
|
|
*/
|
|
|
this.setAttributeConfig('showChart', {
|
|
|
value: attr.showChart || true,
|
|
|
validator: YAHOO.lang.isBoolean,
|
|
|
writeOnce: true
|
|
|
|
|
|
});
|
|
|
|
|
|
YAHOO.widget.ProfilerViewer.superclass.initAttributes.call(this, attr);
|
|
|
|
|
|
};
|
|
|
|
|
|
})();
|
|
|
YAHOO.register("profilerviewer", YAHOO.widget.ProfilerViewer, {version: "2.8.2r1", build: "7"});
|
|
|
|