├── src ├── js │ ├── plugins │ │ ├── searchengine.js │ │ ├── files.js │ │ ├── calls.js │ │ ├── stack.js │ │ ├── metrics.js │ │ ├── duration.js │ │ ├── memory.js │ │ ├── groups.js │ │ └── inspector.js │ ├── lib │ │ ├── view │ │ │ ├── layout │ │ │ │ ├── sidebar.js │ │ │ │ ├── mainpanel.js │ │ │ │ ├── console.js │ │ │ │ └── layout.js │ │ │ ├── indicator │ │ │ │ ├── graph.js │ │ │ │ ├── gauge.js │ │ │ │ ├── histogram.js │ │ │ │ └── barchart.js │ │ │ ├── stack │ │ │ │ ├── backtrace.js │ │ │ │ └── tree.js │ │ │ ├── control │ │ │ │ └── togglebutton.js │ │ │ └── utils.js │ │ ├── helpers │ │ │ ├── tagrandcolor.js │ │ │ └── grader.js │ │ ├── model │ │ │ └── stack.js │ │ └── controller │ │ │ └── controller.js │ └── main.js ├── css │ ├── gstyle.css │ ├── consolas.css │ └── default.css ├── build.php └── built │ └── forp.min.js ├── doc ├── ui-tree.png ├── ui-calls.png ├── ui-files.png ├── ui-groups.png ├── ui-memory.png ├── ui-search.png ├── ui-duration.png ├── ui-groups-details.png ├── ui-consolas-groups.png └── ui-duration-details.png ├── .gitmodules ├── samples ├── php │ ├── basic │ │ ├── index.php │ │ └── common.php │ └── full │ │ ├── index.php │ │ └── vendor │ │ └── forp │ │ └── forp │ │ └── Forp.php └── free │ └── index.html ├── LICENSE └── README.md /src/js/plugins/searchengine.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /doc/ui-tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aterrien/forp-ui/HEAD/doc/ui-tree.png -------------------------------------------------------------------------------- /doc/ui-calls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aterrien/forp-ui/HEAD/doc/ui-calls.png -------------------------------------------------------------------------------- /doc/ui-files.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aterrien/forp-ui/HEAD/doc/ui-files.png -------------------------------------------------------------------------------- /doc/ui-groups.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aterrien/forp-ui/HEAD/doc/ui-groups.png -------------------------------------------------------------------------------- /doc/ui-memory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aterrien/forp-ui/HEAD/doc/ui-memory.png -------------------------------------------------------------------------------- /doc/ui-search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aterrien/forp-ui/HEAD/doc/ui-search.png -------------------------------------------------------------------------------- /doc/ui-duration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aterrien/forp-ui/HEAD/doc/ui-duration.png -------------------------------------------------------------------------------- /doc/ui-groups-details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aterrien/forp-ui/HEAD/doc/ui-groups-details.png -------------------------------------------------------------------------------- /doc/ui-consolas-groups.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aterrien/forp-ui/HEAD/doc/ui-consolas-groups.png -------------------------------------------------------------------------------- /doc/ui-duration-details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aterrien/forp-ui/HEAD/doc/ui-duration-details.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/tools/cssmin"] 2 | path = src/tools/cssmin 3 | url = https://github.com/natxet/CssMin.git 4 | [submodule "src/tools/jsmin"] 5 | path = src/tools/jsmin 6 | url = https://github.com/rgrove/jsmin-php.git 7 | [submodule "src/js/submodules/jmicro"] 8 | path = src/js/submodules/jmicro 9 | url = https://github.com/ichiriac/jmicro.git 10 | -------------------------------------------------------------------------------- /src/js/lib/view/layout/sidebar.js: -------------------------------------------------------------------------------- 1 | (function(forp, $){ 2 | /** 3 | * Sidebar Class 4 | * @param DOMElementWrapper parent 5 | */ 6 | forp.Sidebar = function(parent) 7 | { 8 | forp.Panel.call(this, "sidebar"); 9 | this.parent = parent; 10 | 11 | this.open = function() { 12 | this.$.addClass("w1of3"); 13 | return this; 14 | } 15 | 16 | this.close = function() { 17 | this.$.empty().removeClass("w1of3"); 18 | return this; 19 | } 20 | }; 21 | })(forp, jMicro); -------------------------------------------------------------------------------- /src/css/gstyle.css: -------------------------------------------------------------------------------- 1 | #forp { 2 | font-family: "Helvetica Neue", Helvetica, Nimbus, Arial, sans-serif; 3 | font-size : 13px; 4 | } 5 | #forp span.strong { 6 | font-weight: bold; 7 | } 8 | 9 | /* backtrace */ 10 | #forp div.backtrace-item{ 11 | background: #eee; 12 | } 13 | 14 | /* close */ 15 | #forp div.close:hover{ 16 | color: #555; 17 | } 18 | 19 | /* console */ 20 | #forp div.console{ 21 | background: #fff; 22 | } 23 | 24 | /* input */ 25 | #forp input[type=text]{ 26 | margin: 0px 5px 27 | } 28 | 29 | /* mainpanel */ 30 | #forp div.mainpanel { 31 | border-top: 1px solid #BBB; 32 | background: #fff; 33 | } 34 | 35 | /* nav */ 36 | #forp div.toggleBar{ 37 | margin-top: -4px 38 | } 39 | 40 | /* table */ 41 | #forp th, #forp td{ 42 | border: 1px solid #ddd; 43 | } -------------------------------------------------------------------------------- /samples/php/basic/index.php: -------------------------------------------------------------------------------- 1 | 6 | 7 | 21 | ")); 8 | this.ctx = this.$[0].getContext('2d'); 9 | this.drawn = false; 10 | 11 | this.conf = $.extend({ 12 | width: 100, 13 | xaxis: {length: 100, min: 0, max: 0}, 14 | yaxis: {length: 100, min: 0, max: 0}, 15 | mousemouve: null, 16 | val: function(i) { 17 | return this.datas[i]; 18 | }, 19 | color: function(i) { 20 | return '#999999'; 21 | } 22 | }, conf); 23 | 24 | this.datas = null; 25 | 26 | this.setDatas = function(datas) { 27 | this.datas = datas; 28 | return this; 29 | }; 30 | }; 31 | })(forp, jMicro); -------------------------------------------------------------------------------- /src/js/lib/view/layout/mainpanel.js: -------------------------------------------------------------------------------- 1 | (function(forp, $){ 2 | 3 | /** 4 | * MainPanel 5 | * @param Layout layout 6 | */ 7 | forp.MainPanel = function(layout) 8 | { 9 | var self = this; 10 | forp.Panel.call(this, "mainpanel"); 11 | 12 | this.console = null; 13 | this.layout = layout; 14 | 15 | this.getConsole = function() 16 | { 17 | if(!this.console) { 18 | this.console = (new forp.Console(this)).appendTo(this); 19 | } 20 | return this.console; 21 | }; 22 | 23 | this.open = function() { 24 | this.layout.size(); 25 | return this; 26 | }; 27 | 28 | this.close = function() { 29 | self.css( 30 | "height: 0px", 31 | function() { 32 | //self.closeButton.remove(); 33 | } 34 | ); 35 | return this; 36 | }; 37 | }; 38 | })(forp, jMicro); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Anthony Terrien 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /samples/free/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/js/plugins/files.js: -------------------------------------------------------------------------------- 1 | (function(forp, $) { 2 | 3 | forp.plugins.files = { 4 | 'nav': { 5 | 'label': 'files', 6 | 'display': function() { 7 | return forp.getController().getStack().includesCount > 0; 8 | }, 9 | 'enabled': false, 10 | 'open': function(e) { 11 | var controller = forp.getController(), 12 | datas = controller 13 | .getStack() 14 | .getIncludes(); 15 | 16 | var $table = $.table( 17 | ["file", "calls from"], ["file", "calls from"] 18 | ); 19 | for(var i in datas) { 20 | $table.line([ 21 | i, 22 | $.Gauge( 23 | datas[i].calls, 24 | controller.getStack().stack.length 25 | ) 26 | ]); 27 | } 28 | 29 | controller.getConsole().show($table); 30 | }, 31 | 'close': function() { 32 | forp.getController().getLayout().reduce(); 33 | } 34 | } 35 | }; 36 | 37 | })(forp, jMicro); -------------------------------------------------------------------------------- /src/js/plugins/calls.js: -------------------------------------------------------------------------------- 1 | (function(forp, $) { 2 | 3 | forp.plugins.calls = { 4 | 'nav': { 5 | 'label': "top @PHP-VAR-topCalls@ calls", 6 | 'display': true, 7 | 'enabled': false, 8 | 'open': function(e) { 9 | var controller = forp.getController(), 10 | datas = controller.getStack().getTopCalls(), 11 | $table = $.table( 12 | ["function", "calls", "ms", "Kb"], 13 | ["function", "calls", "ms", "Kb"] 14 | ); 15 | 16 | for(var i in datas) { 17 | $table.line([ 18 | datas[i].id, 19 | datas[i].calls, 20 | $.roundDiv(datas[i].getDuration(), 1000).toFixed(3) + '', 21 | $.roundDiv(datas[i].getMemory(), 1024).toFixed(3) + '' 22 | ]) 23 | .attr("data-ref", datas[i].id) 24 | .bind( 25 | "click", 26 | forp.getController().toggleDetails 27 | ); 28 | } 29 | 30 | controller.getConsole().show($table); 31 | }, 32 | 'close': function() { 33 | forp.getController().getLayout().reduce(); 34 | } 35 | } 36 | }; 37 | 38 | })(forp, jMicro); -------------------------------------------------------------------------------- /src/js/lib/view/stack/backtrace.js: -------------------------------------------------------------------------------- 1 | (function(forp, $){ 2 | 3 | /** 4 | * Backtrace Class 5 | * @param integer i Index 6 | * @param Object stack Callc stack array 7 | */ 8 | forp.Backtrace = function(i, stack) 9 | { 10 | var self = this, 11 | $container = $("
").class("backtrace"); 12 | forp.Decorator.call(this, $container); 13 | 14 | this.prependItem = function(entry, highlight) { 15 | return this.$.prepend( 16 | $("
") 17 | .class("backtrace-item " + (highlight ? " highlight" : "")) 18 | .append( 19 | $('') 20 | .class('strong') 21 | .text(entry.id) 22 | ) 23 | .append($('
')) 24 | .append($('').text($.Utils.trimPath(entry.filelineno))) 25 | .append($('
')) 26 | .append($('').text($.roundDiv(entry.usec, 1000).toFixed(3) + "ms ")) 27 | .append($('').text($.roundDiv(entry.bytes, 1024).toFixed(3) + "Kb")) 28 | ); 29 | }; 30 | 31 | var child = i; 32 | while(i != null) { 33 | this.prependItem(stack[i], child == i); 34 | i = stack[i].parent; 35 | if(i != null) { 36 | this.$.prepend($("
").class("arrow").text("▼")); 37 | } 38 | } 39 | }; 40 | })(forp, jMicro); -------------------------------------------------------------------------------- /src/js/lib/view/indicator/gauge.js: -------------------------------------------------------------------------------- 1 | (function(forp, $){ 2 | 3 | /** 4 | * Gauge Class 5 | * @param integer value 6 | * @param integer max 7 | * @param integer divider 8 | * @param string unit 9 | */ 10 | $.Gauge = function(value, max, divider, unit) 11 | { 12 | var percent = 0, text, displayedValue; 13 | 14 | displayedValue = $.roundDiv(value, (divider ? divider : 1)); 15 | 16 | if(value < 0) { 17 | displayedValue = Math.abs(displayedValue); 18 | } 19 | 20 | if(displayedValue % 1 !== 0) { 21 | displayedValue = displayedValue.toFixed(3); 22 | } 23 | displayedValue += (unit ? unit : ''); 24 | 25 | if(value > max) { 26 | text = "reached " + displayedValue; 27 | } else if(value < 0) { 28 | text = "won " + displayedValue; 29 | } else { 30 | text = displayedValue; 31 | percent = $.round(value * 100 / max); 32 | } 33 | 34 | return $('
') 35 | .addClass("gauge") 36 | .append( 37 | $("
") 38 | .class("text") 39 | .text(text) 40 | ) 41 | .append( 42 | $("
") 43 | .addClass("bar") 44 | .attr( 45 | "style", 46 | "width: " + percent.toFixed(0) + "%" 47 | ) 48 | ); 49 | }; 50 | })(forp, jMicro); -------------------------------------------------------------------------------- /samples/php/full/index.php: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 33 | 34 | 35 |
36 | 39 |
40 | 41 | 42 | 43 | send(); 52 | } -------------------------------------------------------------------------------- /src/js/lib/helpers/tagrandcolor.js: -------------------------------------------------------------------------------- 1 | (function(forp, $){ 2 | 3 | /** 4 | * TagRandColor Class 5 | * Provides predefined colors 6 | */ 7 | forp.TagRandColor = { 8 | i : 0, 9 | pocket : ["#f95", "#f59", "#59f", "#5e9", "#9e6", "#95f", 10 | "#e55", "#fe6", "#f6f", "#5e5", "#5ef", "#55f"], 11 | tagsColor : {}, 12 | provideFor : function(name) 13 | { 14 | if(!this.tagsColor[name]) { 15 | if(this.i < this.pocket.length) { 16 | this.tagsColor[name] = this.pocket[this.i]; 17 | this.i++; 18 | } else { 19 | this.tagsColor[name] = 'rgb(' + 20 | Math.round(Math.random() * 100 + 155) + ',' + 21 | Math.round(Math.random() * 100 + 155) + ',' + 22 | Math.round(Math.random() * 100 + 155) 23 | + ')'; 24 | } 25 | } 26 | return this.tagsColor[name]; 27 | }, 28 | provideElementFor : function(name) 29 | { 30 | return $("") 31 | .class("tag") 32 | .attr( 33 | 'style', 34 | 'background: ' + this.provideFor(name) 35 | ) 36 | .text(name) 37 | .bind( 38 | "click", 39 | function(){ 40 | //alert('to groups view'); 41 | } 42 | ); 43 | } 44 | }; 45 | 46 | })(forp, jMicro); -------------------------------------------------------------------------------- /src/js/lib/view/layout/console.js: -------------------------------------------------------------------------------- 1 | (function(forp, $){ 2 | /** 3 | * Console Class 4 | * @param DOMElementWrapper parent 5 | */ 6 | forp.Console = function(parent) 7 | { 8 | var self = this; 9 | forp.Panel.call(this, "console"); 10 | 11 | this.sidebar = null; 12 | this.parent = parent; 13 | 14 | this.open = function() { 15 | this.closeSidebar(); 16 | this.parent.open(); 17 | 18 | return this; 19 | }; 20 | 21 | this.show = function($content) { 22 | return this.open().$ 23 | .empty() 24 | .append($content); 25 | }; 26 | 27 | this.hasSidebar = function() { 28 | return (this.sidebar != null); 29 | }; 30 | 31 | 32 | this.showInSidebar = function($content) { 33 | return this.openSidebar().$ 34 | .empty() 35 | .append($content); 36 | }; 37 | 38 | this.getSidebar = function() { 39 | if(!this.sidebar) { 40 | this.sidebar = new forp.Sidebar(this.parent); 41 | this.parent 42 | .append(this.sidebar) 43 | .layout 44 | .size(); 45 | } 46 | return this.sidebar; 47 | }; 48 | 49 | this.openSidebar = function() { 50 | this.$.addClass("w2of3"); 51 | return this.getSidebar().open(); 52 | }; 53 | 54 | this.closeSidebar = function() { 55 | this.$.removeClass("w2of3"); 56 | if(this.sidebar) { 57 | this.sidebar.close(); 58 | } 59 | return this; 60 | }; 61 | }; 62 | })(forp, jMicro); -------------------------------------------------------------------------------- /src/js/lib/view/control/togglebutton.js: -------------------------------------------------------------------------------- 1 | (function(forp, $){ 2 | /** 3 | * ToggleButton Class 4 | * @param string label 5 | * @param function on On callback 6 | * @param mixed off Off callback function or false if off disabled 7 | * @param boolean triggerOn Fire click event if true 8 | */ 9 | forp.ToggleButton = function(label, on, off, triggerOn) 10 | { 11 | var self = this, 12 | $container = $("
"); 13 | forp.Decorator.call(this, $container); 14 | 15 | this.$ 16 | .text(typeof label == 'function' ? label() : label) 17 | .class("toggleOff") 18 | .bind( 19 | 'click', 20 | function(e) { 21 | if($(this).attr("data-state") == "on") { 22 | off && (off(e) !== false) 23 | && $(this) 24 | .class("toggleOff") 25 | .attr("data-state", "off"); 26 | } else { 27 | $(this) 28 | .parent() 29 | .find("div.toggleOn") 30 | .each( 31 | function() { 32 | $(this).class("toggleOff").attr("data-state", "off") 33 | } 34 | ); 35 | 36 | //self.parent && self.parent.clear && self.parent.clear(); 37 | on && (on(e) !== false) 38 | && $(this).class("toggleOn").attr("data-state", "on"); 39 | } 40 | } 41 | ); 42 | 43 | triggerOn 44 | && triggerOn() 45 | && this.$.trigger("click"); 46 | }; 47 | })(forp, jMicro); -------------------------------------------------------------------------------- /src/js/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * forp-ui 3 | * 4 | * Profile dump explorer. 5 | * 6 | * https://github.com/aterrien/forp-ui 7 | * 8 | * forp-ui is the perfect tool to treat the 9 | * call stack built by forp PHP profiler 10 | * (https://github.com/aterrien/forp-PHP-profiler). 11 | * 12 | * Example : 13 | * 14 | * 15 | * 42 | * 43 | * 44 | * Copyright (c) 2013 @aterrien, @ichiriac 45 | * 46 | * Under MIT and GPL licenses: 47 | * http://www.opensource.org/licenses/mit-license.php 48 | * http://www.gnu.org/licenses/gpl.html 49 | */ 50 | if(typeof forp == "undefined") {var forp = {};} 51 | if(typeof forp.plugins == "undefined") {forp.plugins = {};} 52 | 53 | /** 54 | * Main 55 | * 56 | * This is the forp-ui Facade. 57 | * 58 | * Adds some plugins for jMicro. 59 | */ 60 | (function(forp, $) { 61 | 62 | "use strict"; 63 | 64 | $.fn.forp = function(options) { 65 | options = $.extend( 66 | { 67 | stack: [], 68 | mode: "embedded" 69 | }, 70 | options 71 | ); 72 | 73 | (new forp.Controller({parent: this[0], mode: options.mode})) 74 | .setStack(options.stack) 75 | .run(); 76 | 77 | }; 78 | 79 | })(forp, jMicro); -------------------------------------------------------------------------------- /src/js/plugins/stack.js: -------------------------------------------------------------------------------- 1 | (function(forp, $) { 2 | 3 | forp.plugins.stack = { 4 | 'nav': { 5 | 'label': function() { 6 | return "stack (" + forp.getController().getStack().stack.length + ")"; 7 | }, 8 | 'display': true, 9 | 'enabled': false, 10 | 'open': function(e) { 11 | 12 | var controller = forp.getController(); 13 | 14 | if(!controller.tree) 15 | controller.tree = $.tree(controller.getStack().stack); 16 | 17 | controller 18 | .getConsole() 19 | .show( 20 | $("
") 21 | .attr("style", "margin-top: 10px;") 22 | .append( 23 | (new forp.ToggleButton( 24 | "collapse" 25 | ,function(e) { 26 | $("li.expanded") 27 | .each( 28 | function(e){ 29 | e.attr("class", "collapsed"); 30 | } 31 | ); 32 | } 33 | ,function(e) { 34 | $("li.collapsed[data-tree]") 35 | .each( 36 | function(e){ 37 | e.attr("class", "expanded"); 38 | } 39 | ); 40 | return true; 41 | } 42 | )).$.attr("style", "position: absolute; margin: 5px; right: 20px") 43 | ) 44 | .append( 45 | $("
").append(controller.tree) 46 | ) 47 | ); 48 | }, 49 | 'close': function() { 50 | forp.getController().getLayout().reduce(); 51 | } 52 | } 53 | }; 54 | 55 | })(forp, jMicro); -------------------------------------------------------------------------------- /src/js/lib/view/indicator/histogram.js: -------------------------------------------------------------------------------- 1 | (function(forp, $){ 2 | 3 | /** 4 | * Histogram Class 5 | */ 6 | forp.Histogram = function(conf) { 7 | forp.Graph.call(this, conf); 8 | this.tmp = null; 9 | 10 | this.restore = function() { 11 | if(this.tmp) { 12 | this.ctx.clearRect(0, 0, this.element.width, this.element.height); 13 | this.ctx.drawImage(this.tmp, 0, 0); 14 | } 15 | }; 16 | 17 | this.highlight = function(idx) { 18 | 19 | if(!this.tmp) { 20 | this.tmp = document.createElement('canvas'); 21 | this.tmp.width = this.element.width; 22 | this.tmp.height = this.element.height; 23 | this.tmp.style.width = '100%'; 24 | this.tmp.style.height = this.conf.yaxis.length + 'px'; 25 | 26 | var context = this.tmp.getContext('2d'); 27 | context.drawImage(this.element, 0, 0); 28 | } 29 | 30 | this.ctx.beginPath(); 31 | this.ctx.strokeStyle = '#4D90FE'; 32 | this.ctx.lineWidth = 3; 33 | this.ctx.moveTo(idx, this.conf.yaxis.length); 34 | this.ctx.lineTo( 35 | idx, 36 | this.conf.yaxis.length - 37 | ( 38 | (this.conf.val.call(this, idx) * this.conf.yaxis.length) / 39 | this.conf.yaxis.max 40 | ) 41 | ); 42 | this.ctx.closePath(); 43 | this.ctx.stroke(); 44 | }; 45 | 46 | this.draw = function() { 47 | if(!this.drawn) { 48 | this.drawn = true; 49 | 50 | var len = this.datas.length; 51 | this.element.width = len; 52 | this.element.height = this.conf.yaxis.length; 53 | this.element.style.width = '100%'; 54 | this.element.style.height = this.conf.yaxis.length + 'px'; 55 | this.element.style.marginBottom = '-3px'; 56 | this.element.style.backgroundColor = '#333'; 57 | 58 | this.ctx.beginPath(); 59 | for(var i = 0; i < len; i++) { 60 | this.ctx.strokeStyle = this.conf.color.call(this, i); 61 | this.ctx.lineWidth = 3; 62 | this.ctx.moveTo(i, this.conf.yaxis.length); 63 | this.ctx.lineTo( 64 | i, 65 | this.conf.yaxis.length - 66 | ( 67 | (this.conf.val.call(this, i) * this.conf.yaxis.length) / 68 | this.conf.yaxis.max 69 | ) 70 | ); 71 | } 72 | this.ctx.closePath(); 73 | this.ctx.stroke(); 74 | 75 | if(this.conf.mousemove) { 76 | var self = this; 77 | this.$.bind( 78 | 'mousemove', 79 | function(e) { 80 | self.conf.mousemove.call(self,e); 81 | } 82 | ) 83 | } 84 | } 85 | return this; 86 | }; 87 | }; 88 | })(forp, jMicro); -------------------------------------------------------------------------------- /src/js/lib/view/indicator/barchart.js: -------------------------------------------------------------------------------- 1 | (function(forp, $){ 2 | 3 | /** 4 | * BarChart Class 5 | */ 6 | forp.BarChart = function(conf) { 7 | forp.Graph.call(this, conf); 8 | this.tmp = null; 9 | 10 | this.restore = function() { 11 | if(this.tmp) { 12 | this.ctx.clearRect(0, 0, this.element.width, this.element.height); 13 | this.ctx.drawImage(this.tmp, 0, 0); 14 | } 15 | }; 16 | 17 | this.highlight = function(idx) { 18 | 19 | if(!this.tmp) { 20 | this.tmp = document.createElement('canvas'); 21 | this.tmp.width = this.element.width; 22 | this.tmp.height = this.element.height; 23 | this.tmp.style.width = '100%'; 24 | this.tmp.style.height = '100px'; 25 | 26 | var context = this.tmp.getContext('2d'); 27 | context.drawImage(this.element, 0, 0); 28 | } 29 | 30 | this.ctx.beginPath(); 31 | this.ctx.strokeStyle = '#4D90FE'; 32 | this.ctx.lineWidth = 60; 33 | this.ctx.moveTo(idx, this.conf.yaxis.length); 34 | this.ctx.lineTo( 35 | idx, 36 | this.conf.yaxis.length - 37 | ( 38 | (this.conf.val.call(this, idx) * 100) / 39 | this.conf.yaxis.max 40 | ) 41 | ); 42 | this.ctx.closePath(); 43 | this.ctx.stroke(); 44 | }; 45 | 46 | this.draw = function() { 47 | if(!this.drawn) { 48 | this.drawn = true; 49 | 50 | var len = this.datas.length; 51 | this.element.width = document.body.clientWidth*2; 52 | this.element.height = this.conf.yaxis.length; 53 | this.element.style.width = '100%'; 54 | this.element.style.height = this.conf.yaxis.length + 'px'; 55 | this.element.style.marginBottom = '-3px'; 56 | this.element.style.backgroundColor = '#333'; 57 | 58 | var x = 0; 59 | for(var i = 0; i < len; i++) { 60 | this.ctx.beginPath(); 61 | this.ctx.strokeStyle = this.conf.color.call(this, i); 62 | this.ctx.lineWidth = 50; 63 | this.ctx.moveTo( 64 | x, 25 65 | ); 66 | this.ctx.lineTo( 67 | x += ( 68 | (this.conf.val.call(this, i) * this.element.width) / 69 | this.conf.xaxis.max 70 | ) +2, 71 | 25 72 | ); 73 | this.ctx.closePath(); 74 | this.ctx.stroke(); 75 | } 76 | 77 | if(this.conf.mousemove) { 78 | var self = this; 79 | this.bind( 80 | 'mousemove', 81 | function(e) { 82 | self.conf.mousemove.call(self,e); 83 | } 84 | ) 85 | } 86 | } 87 | return this; 88 | }; 89 | }; 90 | })(forp, jMicro); -------------------------------------------------------------------------------- /src/js/plugins/metrics.js: -------------------------------------------------------------------------------- 1 | (function(forp, $) { 2 | 3 | forp.plugins.metrics = { 4 | 'nav': { 5 | 'label': 'metrics', 6 | 'display': true, 7 | 'enabled': function() { 8 | return (forp.getController().getStack().inspect == null); 9 | }, 10 | 'open': function(e) { 11 | 12 | // TODO Metrics API 13 | // @see http://www.sdmetrics.com/LoM.html 14 | // Cyclomatic complexity 15 | // Excessive class complexity 16 | // N-path complexity 17 | // Too many fields 18 | // Too many methods 19 | // x Ease of change 20 | 21 | var controller = forp.getController(), 22 | $table = $.table( 23 | ["metric", "type", "value", "grade"], 24 | ["metric", "type", "value", "grade"] 25 | ), 26 | duration = $.roundDiv(controller.getStack().getMainEntry().usec, 1000), 27 | memory = $.roundDiv(controller.getStack().getMainEntry().bytes, 1024); 28 | 29 | $table.line([ 30 | $('').class('strong').text('Real time (ms)'), 31 | "Performance", 32 | duration + '', 33 | controller.getGrader().getGradeWithTip("time", duration) 34 | ]); 35 | 36 | if(controller.getStack().utime != null) { 37 | var time = (controller.getStack().utime + controller.getStack().stime) / 1000; 38 | $table.line(["CPU time (ms)", "Performance", 39 | time + '', 40 | controller.getGrader().getGradeWithTip("time", time) 41 | ]); 42 | } 43 | 44 | $table.line(["Memory usage (Kb)", "Performance", 45 | memory + '', 46 | controller.getGrader().getGradeWithTip("memory", memory)]); 47 | $table.line(["Total includes", "Performance", 48 | controller.getStack().includesCount + '', 49 | controller.getGrader().getGradeWithTip("includes", controller.getStack().includesCount)]); 50 | $table.line(["Total calls", "Performance", 51 | controller.getStack().stack.length + '', 52 | controller.getGrader().getGradeWithTip("calls", controller.getStack().stack.length)]); 53 | $table.line(["Max nested level", "Nesting", 54 | controller.getStack().maxNestedLevel + '', 55 | controller.getGrader().getGradeWithTip("nesting", controller.getStack().maxNestedLevel)]); 56 | $table.line(["Avg nested level", "Nesting", 57 | controller.getStack().avgLevel.toFixed(2) + '', 58 | controller.getGrader().getGradeWithTip("nesting", controller.getStack().avgLevel)]); 59 | 60 | controller.getConsole().show($table); 61 | }, 62 | 'close': function() { 63 | return forp.getController().getLayout().reduce; 64 | } 65 | } 66 | }; 67 | })(forp, jMicro); -------------------------------------------------------------------------------- /src/js/plugins/duration.js: -------------------------------------------------------------------------------- 1 | (function(forp, $) { 2 | 3 | forp.plugins.duration = { 4 | 'nav': { 5 | 'label': "top @PHP-VAR-topCpu@ duration", 6 | 'display': true, 7 | 'enabled': false, 8 | 'open': function(e) { 9 | var controller = forp.getController(), 10 | datas = controller.getStack().getTopCpu(), 11 | $table = $.table( 12 | ["function", "self cost ms", "total cost ms", "calls"], 13 | ["function", "self cost ms", "total cost ms", "calls"] 14 | ); 15 | 16 | controller.getConsole().empty(); 17 | 18 | (controller.getStack().leaves.length > 100) 19 | && controller 20 | .getCpuHistogram() 21 | .appendTo(controller.getConsole()); 22 | 23 | for(var i in datas) { 24 | var id = controller.getStack().getEntryId(datas[i]); 25 | $table 26 | .line([ 27 | $('').class('strong').text(datas[i].id).append( 28 | $('').text("(" + datas[i].filelineno + ")") 29 | ).append( 30 | datas[i].caption ? 31 | $("
").text(datas[i].caption) : null 32 | ), 33 | $.roundDiv(datas[i].usec, 1000).toFixed(3) + '', 34 | $.roundDiv(controller.getStack().getFunctions()[id].getDuration(), 1000).toFixed(3) + '', 35 | controller.getStack().getFunctions()[id].calls 36 | ]) 37 | .attr('data-ref', datas[i].i) 38 | .bind( 39 | 'click', 40 | function() { 41 | forp.getController() 42 | .getConsole() 43 | .showInSidebar( 44 | (new forp.Backtrace( 45 | $(this).attr("data-ref"), 46 | forp.getController().getStack().stack 47 | )).$ 48 | ) 49 | .scrollBottom(); 50 | } 51 | ).bind( 52 | 'mouseover', 53 | function(){ 54 | controller 55 | .getCpuHistogram() 56 | .highlight( 57 | controller 58 | .getStack() 59 | .stack[$(this).attr('data-ref')].leaf); 60 | } 61 | ).bind( 62 | 'mouseout', 63 | function(){ 64 | controller.getCpuHistogram() 65 | .restore(); 66 | } 67 | ); 68 | } 69 | 70 | $table.appendTo(controller.getConsole().$); 71 | }, 72 | 'close': function() { 73 | forp.getController().getLayout().reduce(); 74 | } 75 | } 76 | }; 77 | 78 | })(forp, jMicro); -------------------------------------------------------------------------------- /src/js/plugins/memory.js: -------------------------------------------------------------------------------- 1 | (function(forp, $) { 2 | 3 | forp.plugins.memory = { 4 | 'nav': { 5 | 'label': 'top @PHP-VAR-topMemory@ memory', 6 | 'display': true, 7 | 'enabled': false, 8 | 'open': function(e) { 9 | var controller = forp.getController(), 10 | datas = controller 11 | .getStack() 12 | .getTopMemory(); 13 | 14 | controller.getConsole().empty(); 15 | 16 | (controller.getStack().leaves.length > 100) 17 | && controller 18 | .getMemoryHistogram() 19 | .appendTo(controller.getConsole()); 20 | 21 | var $table = $.table( 22 | ["function", "self cost Kb", "total cost Kb", "calls"], 23 | ["function", "self cost Kb", "total cost Kb", "calls"] 24 | ); 25 | for(var i in datas) { 26 | var id = controller.getStack().getEntryId(datas[i]); 27 | $table.line([ 28 | $('').class('strong').text(datas[i].id).append( 29 | $('').text("(" + datas[i].filelineno + ")") 30 | ).append( 31 | datas[i].caption ? 32 | $("
").text(datas[i].caption) : null 33 | ), 34 | $.roundDiv(datas[i].bytes, 1024).toFixed(3) + '', 35 | $.roundDiv(controller.getStack().getFunctions()[id].getMemory(), 1024).toFixed(3) + '', 36 | controller.getStack().getFunctions()[id].calls 37 | ]) 38 | .attr('data-ref', datas[i].i) 39 | .bind( 40 | 'click', 41 | function() { 42 | forp.getController() 43 | .getConsole() 44 | .showInSidebar( 45 | (new forp.Backtrace( 46 | $(this).attr("data-ref"), 47 | forp.getController().getStack().stack 48 | )).$ 49 | ) 50 | .scrollBottom(); 51 | } 52 | ).bind( 53 | 'mouseover', 54 | function(){ 55 | controller 56 | .getMemoryHistogram() 57 | .highlight( 58 | controller 59 | .getStack() 60 | .stack[$(this).attr('data-ref')].leaf 61 | ); 62 | } 63 | ).bind( 64 | 'mouseout', 65 | function(){ 66 | controller.memoryHistogram.restore(); 67 | } 68 | ); 69 | } 70 | 71 | $table.appendTo(controller.getConsole().$); 72 | }, 73 | 'close': function() { 74 | forp.getController().getLayout().reduce(); 75 | } 76 | } 77 | }; 78 | 79 | })(forp, jMicro); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction # 2 | 3 | forp-ui is a GUI utility that allows you to view and explore profiles dump. 4 | 5 | It is very easy to integrate forp-ui into an HTML page, it is written in JavaScript. 6 | 7 | # Basic features # 8 | 9 | - search engine 10 | - tree representation of the stack 11 | - top 20 duration 12 | - top 20 memory 13 | - top 20 calls 14 | - grouping of functions 15 | - metrics and quality grades 16 | - "called from" view 17 | - "backtrace" view 18 | - inspector 19 | 20 | # Integration into an HTML page and example # 21 | 22 | Download the minified version from the release branch of forp-ui on Github. 23 | Put it in the js directory of your project, then run forp-ui as in the example below. 24 | 25 | 26 | ``` 27 | 28 | 61 | ``` 62 | 63 | # Build # 64 | 65 | Use src/build.php to build src/built/forp.min.js file. 66 | 67 | ``` 68 | $ git submodule init 69 | $ git submodule update 70 | $ cd src 71 | $ php build.php 72 | ``` 73 | 74 | Options: 75 | 76 | --skin name : gstyle, consolas (default) 77 | 78 | 79 | # Communication with forp PHP profiler # 80 | 81 | forp-ui is the perfect tool to treat the forp PHP profiles dump (https://github.com/aterrien/forp). 82 | forp extension gives us PHP profiling datas, forp-ui helps you to refine it clientside. 83 | 84 | # Screenshots (example : Yii PHP framework) # 85 | 86 | ### tree representation of the stack ### 87 | 88 | ![tree](https://raw.github.com/aterrien/forp-ui/master/doc/ui-tree.png) 89 | 90 | ### top 20 duration ### 91 | 92 | ![duration](https://raw.github.com/aterrien/forp-ui/master/doc/ui-duration.png) 93 | 94 | Click on a stack entry displays backtrace in sidebar : 95 | 96 | ![duration details](https://raw.github.com/aterrien/forp-ui/master/doc/ui-duration-details.png) 97 | 98 | ### top 20 memory ### 99 | 100 | ![memory](https://raw.github.com/aterrien/forp-ui/master/doc/ui-memory.png) 101 | 102 | ### top 20 Calls ### 103 | 104 | ![calls](https://raw.github.com/aterrien/forp-ui/master/doc/ui-calls.png) 105 | 106 | ### grouping of functions ### 107 | 108 | This is the result of forp @ProfileGroup annotation. 109 | 110 | ![groups](https://raw.github.com/aterrien/forp-ui/master/doc/ui-groups.png) 111 | 112 | Click on a group entry displays "called from" block : 113 | 114 | ![groups details](https://raw.github.com/aterrien/forp-ui/master/doc/ui-groups-details.png) 115 | 116 | ### search engine ### 117 | 118 | ![search](https://raw.github.com/aterrien/forp-ui/master/doc/ui-search.png) 119 | 120 | 121 | ### metrics and quality grades 122 | 123 | # Samples # 124 | 125 | Samples are in the "samples" directory : 126 | - "free" : simple example free of programming language. 127 | - php : simple example of PHP profiling with forp PHP profiler. 128 | -------------------------------------------------------------------------------- /src/build.php: -------------------------------------------------------------------------------- 1 | array( 15 | 's:', 'skin:' 16 | ), 17 | 'nomin' => array( 18 | 'n', 'nomin' 19 | ) 20 | ); 21 | $php_var = array( 22 | '@PHP-VAR-topCalls@' => 20, 23 | '@PHP-VAR-topMemory@' => 20, 24 | '@PHP-VAR-topCpu@' => 20, 25 | '@PHP-VAR-maxStack@' => 30000 /*the max number of elements in the forp stack that forp ui will allow before displaying an error.*/ 26 | ); 27 | 28 | $shortOpts = ''; 29 | $longOpts = array(); 30 | foreach($opts as $opt) { 31 | $shortOpts .= $opt[0]; 32 | $longOpts[] = $opt[1]; 33 | } 34 | $options = getopt($shortOpts, $longOpts); 35 | foreach($options as $k=>$v) { 36 | switch($k) { 37 | case 's' : case 'skin' : 38 | $skin = $v; 39 | break; 40 | case 'n' : case 'nomin' : 41 | $nomin = true; 42 | break; 43 | default : 44 | $skin = 'gstyle'; 45 | $nomin = false; 46 | } 47 | } 48 | 49 | // Files 50 | $files = array( 51 | 'js' => array( 52 | 'js/submodules/jmicro/jmicro', //'dom', 53 | 'js/main', 54 | 'js/lib/view/utils', 55 | 56 | // Contoller 57 | 'js/lib/controller/controller', 58 | 59 | // Datas 60 | 'js/lib/model/stack', 61 | 62 | // Helpers 63 | 'js/lib/helpers/grader', 64 | 'js/lib/helpers/tagrandcolor', 65 | 66 | // UI 67 | 'js/lib/view/control/togglebutton', 68 | 'js/lib/view/indicator/graph', 69 | 'js/lib/view/indicator/gauge', 70 | 'js/lib/view/indicator/barchart', 71 | 'js/lib/view/indicator/histogram', 72 | 'js/lib/view/layout/layout', 73 | 'js/lib/view/layout/mainpanel', 74 | 'js/lib/view/layout/console', 75 | 'js/lib/view/layout/sidebar', 76 | 'js/lib/view/stack/backtrace', 77 | 'js/lib/view/stack/tree', 78 | 79 | // Plugins 80 | 'js/plugins/inspector', 81 | 'js/plugins/metrics', 82 | 'js/plugins/stack', 83 | 'js/plugins/duration', 84 | 'js/plugins/memory', 85 | 'js/plugins/calls', 86 | 'js/plugins/groups', 87 | 'js/plugins/files', 88 | 'js/plugins/searchengine', 89 | ), 90 | 'css' => array( 91 | 'css/default', 92 | 'css/' . $skin 93 | ) 94 | ); 95 | 96 | 97 | $path = dirname(__FILE__) . '/built/forp.min.js'; 98 | $target = fopen($path, 'w+'); 99 | try { 100 | $js = $css = ''; 101 | 102 | foreach($files['js'] as $file) { 103 | $js .= file_get_contents(dirname(__FILE__) . '/' . $file . '.js'); 104 | } 105 | 106 | foreach($files['css'] as $file) { 107 | $css .= file_get_contents(dirname(__FILE__) . '/' . $file . '.css'); 108 | } 109 | 110 | $js = str_replace(array_keys($php_var), array_values($php_var), $js ); 111 | $css = str_replace(array_keys($php_var), array_values($php_var), $css ); 112 | 113 | // Inject CSS 114 | fwrite( 115 | $target, 116 | str_replace( 117 | '%forp.css%', 118 | CssMin::minify($css), 119 | '/** forp-ui (c) 2013 Anthony Terrien **/' . 120 | ($nomin ? $js : JSMin::minify($js)) 121 | ) 122 | ); 123 | 124 | echo "File " . $path . " built\n"; 125 | } catch(Exception $ex ) { 126 | echo "Fatal error : " . $ex->getMessage() . "\n\n"; 127 | echo $ex->getTraceAsString(); 128 | 129 | } 130 | fclose($target); 131 | -------------------------------------------------------------------------------- /src/js/plugins/groups.js: -------------------------------------------------------------------------------- 1 | (function(forp, $) { 2 | 3 | forp.plugins.groups = { 4 | 'nav': { 5 | 'label': function() { 6 | return "groups (" + forp.getController().getStack().groupsCount + ")"; 7 | }, 8 | 'display': function() { 9 | return forp.getController().getStack().groupsCount > 0; 10 | }, 11 | 'enabled': false, 12 | 'open': function(e) { 13 | var self = forp.getController(), 14 | datas = self.getStack() 15 | .getGroups(); 16 | 17 | self.getConsole().empty().open(); 18 | 19 | self.getGroupsBarChart() 20 | .appendTo(self.getConsole()); 21 | 22 | var $table = $.table( 23 | ["group", "calls", "ms", "Kb"], 24 | ["group", "calls", "ms", "Kb"] 25 | ); 26 | 27 | for(var i in datas) { 28 | $table 29 | .append( 30 | $("") 31 | .append( 32 | $("") 33 | .attr("colspan", 4) 34 | .attr("style", "padding: 0px; height: 4px; background:" 35 | + forp.TagRandColor.provideFor(i)) 36 | ) 37 | ) 38 | .line([ 39 | 40 | datas[i].calls, 41 | $.roundDiv(datas[i].usec, 1000).toFixed(3) + '', 42 | $.roundDiv(datas[i].bytes, 1024).toFixed(3) + '' 43 | ]) 44 | .prepend( 45 | $("") 46 | .append( 47 | forp.TagRandColor.provideElementFor(i) 48 | ) 49 | .append( 50 | $("") 51 | .append( 52 | $("").class('strong').text(i) 53 | ) 54 | .append( 55 | $("").text(' (' + datas[i].refs.length + ' ' + (datas[i].refs.length>1 ? "entries" : "entry") + ')') 56 | ) 57 | ) 58 | ); 59 | 60 | for(var j in datas[i].refs) { 61 | $table.line([ 62 | datas[i].refs[j].id, 63 | $.Gauge( 64 | self.getStack().getFunctions()[datas[i].refs[j].id].calls, 65 | datas[i].calls 66 | ), 67 | $.Gauge( 68 | self.getStack().getFunctions()[datas[i].refs[j].id].getDuration(), 69 | datas[i].usec, 70 | 1000, 71 | 'ms' 72 | ), 73 | $.Gauge( 74 | self.getStack().getFunctions()[datas[i].refs[j].id].getMemory(), 75 | datas[i].bytes, 76 | 1024, 77 | 'Kb' 78 | ) 79 | ]) 80 | .attr("data-ref", datas[i].refs[j].id) 81 | .bind("click", self.toggleDetails); 82 | } 83 | } 84 | 85 | self.getConsole().show($table); 86 | }, 87 | 'close': function() { 88 | forp.getController().getLayout().reduce(); 89 | } 90 | } 91 | }; 92 | 93 | })(forp, jMicro); -------------------------------------------------------------------------------- /samples/php/basic/common.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 |
10 | '; 34 | 35 | /** 36 | * @ProfileGroup("Test") 37 | * @ProfileCaption("Hello #1 !") 38 | */ 39 | function test($i = 0){ echo 'User function with "Hello ' . $i . ' !" profile caption.
'; }; 40 | /** 41 | * @ProfileGroup("Foo Group") 42 | * @ProfileCaption("User function that calls another one") 43 | */ 44 | function test1(){test();}; 45 | // user class 46 | class Foo { 47 | 48 | public $mybool = true; 49 | 50 | public $myintvar = 27; 51 | 52 | public $mystringvar = "test"; 53 | 54 | public $myarray = array("assoc" => "test"); 55 | 56 | public $myobject = null; 57 | 58 | public function __construct() { 59 | $this->myobject = new stdClass(); 60 | } 61 | 62 | /** 63 | * @ProfileGroup("Foo","data") 64 | * @ProfileCaption("Caption of bar.") 65 | */ 66 | function bar() { test1(); } 67 | 68 | /** 69 | * @ProfileGroup("Foo Group") 70 | * @ProfileCaption("Caption of bar2 #1 #2.") 71 | */ 72 | function bar2($lambda, $object) { return test1(); } 73 | } 74 | // closure 75 | $lambda = 76 | /** 77 | * @ProfileGroup("Foo group") 78 | * @ProfileCaption("Closure") 79 | * @ProfileHighlight("1") 80 | * @ProfileAlias("HighlightTestClosure") 81 | */ 82 | function() { 83 | echo 'User function with "Foo group" group, HighlightTestClosure" alias, "Closure" caption and highlight.
'; 84 | test(); 85 | }; 86 | 87 | forp_inspect('lambda', $lambda); 88 | 89 | // calls 90 | for($i = 0; $i<1000; $i++) { 91 | test($i); 92 | // inspect stress 93 | // forp_inspect('i' . $i, $i); 94 | } 95 | 96 | for($i=0;$i<5;$i++){ test1(); } 97 | $lambda(); 98 | $lambda(); 99 | $foo = new Foo(); 100 | //sleep(1); 101 | $foo->bar(); 102 | $foo->bar2($lambda, $foo); 103 | forp_inspect('foo', $foo); 104 | 105 | /** 106 | * @ProfileAlias("Alloc") 107 | */ 108 | $alloc = function(){ 109 | $stdObject = new stdClass(); 110 | $stdObject->arr = array(); 111 | for($i = 0; $i<100; $i++) { 112 | $stdObject->arr[$i] = "test"; 113 | } 114 | return $stdObject; 115 | }; 116 | /** 117 | * @ProfileAlias("test alloc") 118 | */ 119 | $allocTest = function(){ 120 | global $alloc; 121 | $o = $alloc(); 122 | }; 123 | $allocTest(); 124 | 125 | // fibo 126 | $br = (php_sapi_name() == "cli")? "\n":"
\n"; 127 | 128 | /** 129 | * @ProfileGroup("Fibo Group") 130 | * @ProfileCaption("Caption of fibo, value #1 #1 #1 #1") 131 | * @param type $x 132 | * @return int 133 | */ 134 | function fibo( $x ) { 135 | if ( $x < 2) { 136 | return 1; 137 | } else { 138 | return fibo($x - 1) + fibo($x - 2); 139 | } 140 | } 141 | 142 | for( $i = 1; $i < 10; $i++) { 143 | printf( 144 | 'fibo(%1$s) = %2$s'.$br, 145 | $i, fibo($i) 146 | ); 147 | } 148 | ?> 149 | 150 | -------------------------------------------------------------------------------- /src/js/lib/view/layout/layout.js: -------------------------------------------------------------------------------- 1 | (function(forp, $){ 2 | 3 | /** 4 | * Layout 5 | * 6 | * - Layout #forp 7 | * - Navbar nav 8 | * - MainPanel .mainpanel 9 | * - Console .console 10 | * - Sidebar .sidebar 11 | */ 12 | forp.Layout = function($container, viewMode) 13 | { 14 | var self = this; 15 | forp.Decorator.call(this, $container); 16 | $container.attr("id", "forp"); 17 | 18 | this.mainpanel = null; 19 | this.nav = null; 20 | this.viewMode = viewMode; // fixed, embedded 21 | 22 | this.conf = { 23 | fixed : { 24 | size : function() { 25 | self.$.attr("style", ""); 26 | self.getConsole().$ 27 | .attr( 28 | "style", 29 | "height: " + (self.$.height()-45) + "px" 30 | ); 31 | 32 | if( self.getConsole() 33 | .hasSidebar() 34 | ) { 35 | self.getConsole() 36 | .getSidebar().$ 37 | .attr( 38 | "style", 39 | "height: " + (self.$.height()-45) + "px" 40 | ); 41 | } 42 | if(window.onresize == null) { 43 | window.onresize = function(e) { 44 | self.size(); 45 | } 46 | } 47 | }, 48 | reduce : function() { 49 | self.$.attr( 50 | "style", 51 | "height: 45px" 52 | ); 53 | } 54 | } 55 | , embedded : { 56 | size : function() { 57 | self.$.attr( 58 | "style", 59 | "height: 100%" 60 | ); 61 | self.getConsole().$ 62 | .attr( 63 | "style", 64 | "height: " + (self.$.height()-45) + "px" 65 | ); 66 | if( self.getConsole() 67 | .hasSidebar() 68 | ) { 69 | self.getConsole() 70 | .getSidebar().$ 71 | .attr( 72 | "style", 73 | "height: " + (self.$.height()-45) + "px" 74 | ); 75 | } 76 | }, 77 | reduce : function() { return false } 78 | } 79 | }; 80 | 81 | this.setViewMode = function(viewMode) 82 | { 83 | this.viewMode = viewMode; 84 | return this; 85 | }; 86 | 87 | this.getMainPanel = function() 88 | { 89 | if(!this.mainpanel) { 90 | this.mainpanel = (new forp.MainPanel(this)).appendTo(this); 91 | } 92 | return this.mainpanel; 93 | }; 94 | 95 | this.getNav = function() 96 | { 97 | if(!this.nav) { 98 | this.nav = (new forp.Nav()).appendTo(this); 99 | } 100 | return this.nav; 101 | }; 102 | 103 | this.getConsole = function() 104 | { 105 | return this.getMainPanel().getConsole(); 106 | }; 107 | 108 | this.open = function() 109 | { 110 | this.$.class("forp-" + this.viewMode); 111 | return this; 112 | }; 113 | 114 | this.size = function() { 115 | return (this.conf[this.viewMode].size() !== false); 116 | }; 117 | 118 | this.reduce = function() { 119 | return (self.conf[self.viewMode].reduce() !== false); 120 | }; 121 | 122 | this.compact = function(callback) 123 | { 124 | this.$ 125 | .attr("style", "") 126 | .empty() 127 | .class("forp-" + this.viewMode + "-compact"); 128 | 129 | this.nav = null; 130 | this.mainpanel = null; 131 | 132 | callback(); 133 | 134 | return this; 135 | }; 136 | }; 137 | })(forp, jMicro); -------------------------------------------------------------------------------- /src/css/consolas.css: -------------------------------------------------------------------------------- 1 | #forp { 2 | background: none; 3 | } 4 | 5 | #forp *{ 6 | font-family: Consolas, monaco, monospace; 7 | font-size : 11px; 8 | color: #eee; 9 | } 10 | 11 | /* backtrace */ 12 | #forp div.backtrace-item{ 13 | word-wrap: break-word; 14 | max-width: 90%; 15 | background: #777; 16 | padding: 4px 10px 5px 10px; 17 | } 18 | #forp div.backtrace div.highlight{ 19 | background: #333 !important; 20 | } 21 | #forp div.backtrace div.arrow { 22 | color: #ddd; 23 | } 24 | 25 | /* close */ 26 | #forp div.close:hover{ 27 | color: #eee; 28 | } 29 | 30 | /* console */ 31 | #forp div.console{} 32 | 33 | /* forp */ 34 | #forp.forp-fixed { 35 | height: 100%; 36 | background: #FFF; 37 | min-width: 1000px; 38 | top: 0px; 39 | left: 0px; 40 | } 41 | #forp.forp-fixed-compact { 42 | margin: 0px; 43 | position: fixed; 44 | top: 0px; 45 | right: 0px; 46 | bottom: auto; 47 | border-radius: 0; 48 | -moz-box-shadow: none; 49 | -webkit-box-shadow: none; 50 | box-shadow: none; 51 | width: 130px; 52 | } 53 | #forp.forp-fixed-compact nav{ 54 | height: 35px; 55 | text-align: right; 56 | padding: 5px 10px; 57 | background: none; 58 | background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0) 7%, #333 0%); 59 | background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0) 7%, #333 0%); 60 | background-image: -ms-linear-gradient(45deg, rgba(255, 255, 255, 0) 7%, #333 0%); 61 | background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0) 7%, #333 0%); 62 | background-image: linear-gradient(45deg, rgba(255, 255, 255, 0) 7%, #333 0%); 63 | border-radius: 0; 64 | } 65 | #forp.forp-fixed-compact div.summary > div { 66 | color: #fff !important; 67 | } 68 | #forp.forp-fixed-compact.grade-A { 69 | border-right: 4px solid #9E5 70 | } 71 | #forp.forp-fixed-compact.grade-B { 72 | border-right: 4px solid #EE5 73 | } 74 | #forp.forp-fixed-compact.grade-C { 75 | border-right: 4px solid #EB5 76 | } 77 | #forp.forp-fixed-compact.grade-D { 78 | border-right: 4px solid #E95 79 | } 80 | #forp.forp-fixed-compact.grade-E { 81 | border-right: 4px solid #E55 82 | } 83 | 84 | /* gauge */ 85 | #forp div.gauge{ 86 | background: #777 87 | } 88 | #forp div.gauge div.text{} 89 | #forp div.gauge div.bar{} 90 | 91 | /* input */ 92 | #forp input[type=text]{ 93 | border: 1px solid #333; 94 | background: #333; 95 | margin: 10px 5px; 96 | } 97 | #forp input[type=text]:focus{ 98 | background: #555; 99 | } 100 | 101 | /* inset */ 102 | #forp div.inset { 103 | -webkit-box-shadow: none; 104 | -moz-box-shadow: none; 105 | box-shadow: none; 106 | } 107 | 108 | /* mainpanel */ 109 | #forp div.mainpanel { 110 | background: #666; 111 | } 112 | 113 | /* nav */ 114 | #forp nav { 115 | background: #222; 116 | height: 45px; 117 | padding: 0px; 118 | } 119 | #forp.forp-fixed nav, #forp.forp-embedded nav{ 120 | height: 45px; 121 | } 122 | #forp div.toggleBar{ 123 | min-width: 1100px; 124 | } 125 | #forp div.toggleOn, #forp div.toggleOff{ 126 | height: 44px; 127 | padding: 15px 10px 5px 10px; 128 | min-width: 100px; 129 | background: none; 130 | border-radius: 0; 131 | margin: 0px; 132 | border-bottom: 4px solid #CCC; 133 | margin-right: 1px; 134 | } 135 | #forp div.toggleOn:hover, #forp div.toggleOff:hover{ 136 | border-bottom: 4px solid #FFF; 137 | } 138 | #forp div.toggleOn{ 139 | color: #EEE; 140 | border-bottom: 4px solid #9E5; 141 | background-image: -webkit-linear-gradient(-135deg, rgba(255, 255, 255, 0) 7%, #555 0%); 142 | background-image: -moz-linear-gradient(-135deg, rgba(255, 255, 255, 0) 7%, #555 0%); 143 | background-image: -ms-linear-gradient(-135deg, rgba(255, 255, 255, 0) 7%, #555 0%); 144 | background-image: -o-linear-gradient(-135deg, rgba(255, 255, 255, 0) 7%, #555 0%); 145 | background-image: linear-gradient(-135deg, rgba(255, 255, 255, 0) 7%, #555 0%); 146 | } 147 | #forp div.toggleOff{ 148 | color: #EEE; 149 | background-image: -webkit-linear-gradient(-135deg, rgba(255, 255, 255, 0) 5%, #333 0%); 150 | background-image: -moz-linear-gradient(-135deg, rgba(255, 255, 255, 0) 5%, #333 0%); 151 | background-image: -ms-linear-gradient(-135deg, rgba(255, 255, 255, 0) 5%, #333 0%); 152 | background-image: -o-linear-gradient(-135deg, rgba(255, 255, 255, 0) 5%, #333 0%); 153 | background-image: linear-gradient(-135deg, rgba(255, 255, 255, 0) 5%, #333 0%); 154 | } 155 | 156 | /* table */ 157 | #forp tr:nth-child(even) {background: #333} 158 | #forp tr:nth-child(odd) {background: #444} 159 | #forp tr:hover{ 160 | background: #777; 161 | } 162 | #forp th, #forp td { 163 | padding: 7px; 164 | border: none; 165 | } 166 | #forp th{ 167 | background: #999; 168 | } 169 | #forp th.reorder { 170 | cursor:pointer; 171 | } -------------------------------------------------------------------------------- /src/js/plugins/inspector.js: -------------------------------------------------------------------------------- 1 | (function(forp, $) { 2 | 3 | forp.plugins.inspector = { 4 | 'nav': { 5 | 'label': 'inspector', 6 | 'display': function() { 7 | return (forp.getController().getStack().inspect != null); 8 | }, 9 | 'enabled': function() { 10 | return (forp.getController().getStack().inspect != null); 11 | }, 12 | 'open': function(e) { 13 | 14 | var self = forp.getController(), 15 | $table = $.table(["var", "type"], ["var", "type"]), 16 | ivars = self.getStack().inspect; 17 | 18 | for(var ivar in ivars) { 19 | var info; 20 | if( 21 | typeof(ivars[ivar]) === "object" 22 | && ivars[ivar].properties 23 | ) { 24 | info = $('
'); 25 | for(var prop in ivars[ivar].properties) { 26 | info.append($('').text(prop)); 27 | } 28 | } else { 29 | info = $.Utils.htmlEntities(ivars[ivar].value); 30 | } 31 | 32 | $table 33 | .line( 34 | [ 35 | ivar, 36 | ivars[ivar].type 37 | ] 38 | ) 39 | .attr('data-ref', ivar) 40 | .bind( 41 | 'click', 42 | function() { 43 | 44 | var $ul = $("
    ").class("inspect"), 45 | ivar = $(this).attr('data-ref'), 46 | list = function(v, ul) { 47 | for(var entry in v) { 48 | var el = $("
  • "); 49 | if(typeof(v[entry]) == 'object') { 50 | ul.append( 51 | el.text(entry + ":") 52 | ); 53 | var sul = $("
      ").appendTo(el); 54 | 55 | if(v[entry].type) { 56 | switch(v[entry].type) { 57 | case "string" : 58 | case "int" : 59 | case "bool" : 60 | ul.append( 61 | el.text( 62 | entry + ": (" 63 | + v[entry].type 64 | + ") " 65 | + $.Utils.htmlEntities(v[entry].value) 66 | ) 67 | ); 68 | break; 69 | default: 70 | list(v[entry], sul); 71 | } 72 | 73 | } else { 74 | list(v[entry], sul); 75 | } 76 | } else { 77 | ul.append( 78 | el.text(entry + ": " + $.Utils.htmlEntities(v[entry])) 79 | ); 80 | } 81 | } 82 | }; 83 | 84 | list( 85 | forp.getController().getStack().inspect[ivar], 86 | $ul 87 | ); 88 | 89 | forp.getController() 90 | .getConsole() 91 | .showInSidebar($ul); 92 | } 93 | ); 94 | } 95 | 96 | self.getConsole().show($table); 97 | }, 98 | 'close': function() { 99 | forp.getController().getLayout().reduce(); 100 | } 101 | } 102 | }; 103 | 104 | })(forp, jMicro); -------------------------------------------------------------------------------- /src/js/lib/view/stack/tree.js: -------------------------------------------------------------------------------- 1 | (function(forp, $){ 2 | 3 | /** 4 | * Stack Tree Class 5 | * @param Object stack Call stack array 6 | */ 7 | $.tree = function(stack) 8 | { 9 | var self = this; 10 | 11 | /** 12 | * Generates a tree representation (UL) of the stack 13 | * 14 | * @param array entry Root entry 15 | * @param boolean recursive Says if we have to fetch it recursively 16 | * @return Object Wrapped UL 17 | */ 18 | this.treeList = function(entry, recursive) 19 | { 20 | var ul = $("
        ").addClass("l" + entry.level) 21 | , ex = $("
        ") 22 | .html(" ") 23 | .addClass("left expander") 24 | , gd = ($.Gauge( 25 | entry.usec, 26 | stack[entry.parent] ? stack[entry.parent].usec : entry.usec, 27 | 1000, 28 | 'ms' 29 | )).addClass("left") 30 | , gb = ($.Gauge( 31 | entry.bytes, 32 | stack[entry.parent] ? stack[entry.parent].bytes : entry.bytes, 33 | 1024, 34 | 'Kb' 35 | )).addClass("left") 36 | , li = $("
      • ").text(entry.id); 37 | 38 | 39 | if(entry.groups) { 40 | for(var g in entry.groups) { 41 | li.append(forp.TagRandColor.provideElementFor(entry.groups[g])); 42 | } 43 | } 44 | if(entry.caption) li.append($("").text(entry.caption)); 45 | 46 | li.append(ex).append(gd).append(gb).appendTo(ul); 47 | 48 | if(entry.childrenRefs) { 49 | 50 | li.removeClass("expanded").addClass("collapsed"); 51 | 52 | ex.bind( 53 | 'click' 54 | , function() { 55 | if(li.hasClass("expanded")) { 56 | li.removeClass("expanded").addClass("collapsed"); 57 | } else { 58 | li.removeClass("collapsed").addClass("expanded"); 59 | if(!li.attr("data-tree")) { 60 | for(var i in entry.childrenRefs) { 61 | self 62 | .treeList(stack[entry.childrenRefs[i]], true) 63 | .appendTo(li); 64 | } 65 | li.attr("data-tree", 1); 66 | } 67 | } 68 | } 69 | ); 70 | 71 | if(parseInt(entry.level) < 2) { 72 | li.removeClass("collapsed").addClass("expanded"); 73 | if(!li.attr("data-tree")) { 74 | li.attr("data-tree", 1); 75 | var loopCounter, 76 | lastId, 77 | loopMaxIter = 100; 78 | 79 | for(var i in entry.childrenRefs) { 80 | 81 | if(stack[entry.childrenRefs[i]].id == lastId) { 82 | loopCounter++; 83 | } else { 84 | loopCounter = 0; 85 | } 86 | lastId = stack[entry.childrenRefs[i]].id; 87 | 88 | // loop detected, max item reached 89 | if(loopCounter == loopMaxIter) { 90 | $("
          ") 91 | .append( 92 | $("
        • ") 93 | .append(ex) 94 | .append( 95 | $("
          ") 96 | .text(lastId + " : too many items to display (>" + loopMaxIter + ")") 97 | ) 98 | ) 99 | .appendTo(li); 100 | continue; 101 | } 102 | 103 | if(loopCounter < loopMaxIter) { 104 | this.treeList(stack[entry.childrenRefs[i]]) 105 | .appendTo(li); 106 | } 107 | } 108 | } 109 | } else { 110 | li.removeClass("expanded").addClass("collapsed"); 111 | } 112 | } 113 | 114 | return ul; 115 | }; 116 | 117 | return this.treeList(stack[0], true); 118 | }; 119 | })(forp, jMicro); -------------------------------------------------------------------------------- /samples/php/full/vendor/forp/forp/Forp.php: -------------------------------------------------------------------------------- 1 | false); 20 | 21 | /** 22 | * @param type $opts 23 | * @return \forp\Response 24 | */ 25 | public function setConf($opts) 26 | { 27 | $this->conf = array_merge($this->conf, $opts); 28 | return $this; 29 | } 30 | 31 | /** 32 | * @return \forp\ResponseCli|\forp\ResponseHttpHeaders|\forp\ResponseInlineXForpStack|\forp\ResponseInlineAsync|\forp\ResponseInline 33 | */ 34 | public function getResponse() 35 | { 36 | if(php_sapi_name() === "cli") { 37 | return new ResponseCli(); 38 | } 39 | 40 | // priority to send forp datas in HTTP headers 41 | // forp client is a browser extension 42 | if(isset($_SERVER) 43 | && isset($_SERVER['HTTP_X_FORP_VERSION']) 44 | ) { 45 | if(!empty($_SERVER['HTTP_ACCEPT']) 46 | && strpos(strtolower($_SERVER['HTTP_ACCEPT']), 'json') 47 | ) { 48 | // send forp datas in HTTP headers 49 | // XHR communication case 50 | return new ResponseHttpHeaders(); 51 | } else { 52 | // headers size limitation on Chrome 53 | return new ResponseInlineXForpStack(); 54 | } 55 | } 56 | 57 | // asynchrone JavaScript loading + inline JavaScript 58 | if($this->conf['async']) { 59 | return new ResponseInlineAsync(); 60 | } 61 | 62 | // JavaScript loading + inline JavaScript 63 | return new ResponseInline(); 64 | } 65 | 66 | /** 67 | * @return \forp\Response 68 | */ 69 | public function send() 70 | { 71 | $this->getResponse() 72 | ->send(forp_dump()); 73 | 74 | return $this; 75 | } 76 | } 77 | 78 | /** 79 | * IResponse 80 | */ 81 | interface IResponse 82 | { 83 | public function send(array $datas); 84 | } 85 | 86 | /** 87 | * ResponseCli 88 | */ 89 | class ResponseCli 90 | implements IResponse 91 | { 92 | /** 93 | * @param array $datas 94 | */ 95 | public function send(array $datas) 96 | { 97 | forp_print(); 98 | } 99 | } 100 | 101 | /** 102 | * ResponseInline 103 | */ 104 | class ResponseInline 105 | implements IResponse 106 | { 107 | /** 108 | * @param array $datas 109 | */ 110 | public function send(array $datas) 111 | { 112 | ?> 113 |
          114 | 115 | 123 | 139 | 140 |
          141 | 145 | 160 | 176 | "; 179 | } 180 | } 181 | 182 | /** 183 | * ResponseHttpHeaders 184 | */ 185 | class ResponseHttpHeaders 186 | implements IResponse 187 | { 188 | /** 189 | * @param array $datas 190 | * @throws \Exception 191 | */ 192 | public function send(array $datas) 193 | { 194 | $parts = explode( 195 | "\n", 196 | chunk_split(json_encode($datas), 5000, "\n") 197 | ); 198 | foreach($parts as $i=>$part) { 199 | header("X-Forp-Stack_" . $i . ":" . $part); 200 | if ($i > 99999) { 201 | throw new \Exception('Can\t exceed 99999 chunks.'); 202 | } 203 | } 204 | } 205 | } -------------------------------------------------------------------------------- /src/js/lib/helpers/grader.js: -------------------------------------------------------------------------------- 1 | (function(forp, $) { 2 | 3 | "use strict"; 4 | 5 | /** 6 | * Grader Class 7 | * 8 | * Provides grades, quality metrics 9 | */ 10 | forp.Grader = function() 11 | { 12 | this.grades = { 13 | time : { 14 | A : { 15 | min : 0, max : 100, tip : ["Very good job !", "The planet will reward you !", "You'll be the king at the coffee machine.", "Your servers thanks you."] 16 | }, 17 | B : { 18 | min : 100, max : 300, tip : ["Good job !"] 19 | }, 20 | C : { 21 | min : 300, max : 600, tip : ["You are close to job performance."] 22 | }, 23 | D : { 24 | min : 600, max : 1000, tip : ["You are under one second.", "Think cache."] 25 | }, 26 | E : { 27 | min : 1000, max : 2000, tip : ["At your own risk !"] 28 | } 29 | }, 30 | memory : { 31 | A : { 32 | min : 0, max : 2000, tip : ["Very good job !"] 33 | }, 34 | B : { 35 | min : 2000, max : 4000, tip : ["Good job !"] 36 | }, 37 | C : { 38 | min : 4000, max : 8000, tip : ["Respectable"] 39 | }, 40 | D : { 41 | min : 8000, max : 12000, tip : ["It seems that you load too much data."] 42 | }, 43 | E : { 44 | min : 12000, max : 20000, tip : ["It seems that you load too much data."] 45 | } 46 | }, 47 | includes : { 48 | A : { 49 | min : 0, max : 5, tip : ["Very good job !"] 50 | }, 51 | B : { 52 | min : 5, max : 20, tip : ["Good job !"] 53 | }, 54 | C : { 55 | min : 30, max : 60, tip : ["A builder script could do the rest."] 56 | }, 57 | D : { 58 | min : 60, max : 120, tip : ["A builder script could be your best friend on this."] 59 | }, 60 | E : { 61 | min : 120, max : 240, tip : ["At your own risk !", "A builder script could be your best friend on this."] 62 | } 63 | }, 64 | calls : { 65 | A : { 66 | min : 0, max : 2000, tip : ["Very good job !", "This is the 'Hello world' script ?"] 67 | }, 68 | B : { 69 | min : 2000, max : 4000, tip : ["Very good job !"] 70 | }, 71 | C : { 72 | min : 4000, max : 8000, tip : ["Respectable"] 73 | }, 74 | D : { 75 | min : 8000, max : 16000, tip : ["Has a bad impact on performance."] 76 | }, 77 | E : { 78 | min : 32000, max : 64000, tip : ["It's a joke ?", "At your own risk !", "Too many instructions."] 79 | } 80 | }, 81 | nesting : { 82 | E : { 83 | min : 0, max : 5, tip : ["This is the 'Hello world' script ?"] 84 | }, 85 | A : { 86 | min : 5, max : 10, tip : ["Good job !"] 87 | }, 88 | B : { 89 | min : 10, max : 15, tip : ["Respectable"] 90 | }, 91 | C : { 92 | min : 15, max : 20, tip : ["Respectable"] 93 | }, 94 | D : { 95 | min : 20, max : 30, tip : ["Perhaps, are you currently refactoring ?"] 96 | } 97 | } 98 | }; 99 | 100 | this.getGrade = function(gradeName, mesure) { 101 | for(var grade in this.grades[gradeName]) { 102 | if( mesure >= this.grades[gradeName][grade]['min'] 103 | && mesure <= this.grades[gradeName][grade]['max'] 104 | ) { 105 | return grade; 106 | } 107 | } 108 | return grade; 109 | }; 110 | 111 | this.getTip = function(gradeName, grade) { 112 | var i = Math.floor((Math.random() * this.grades[gradeName][grade]['tip'].length)); 113 | return this.grades[gradeName][grade]['tip'][i]; 114 | }; 115 | 116 | this.getClass = function(grade) { 117 | return "grade-" + grade; 118 | }; 119 | 120 | this.getGradeWithTip = function(gradeName, mesure) { 121 | var grade = this.getGrade(gradeName, mesure); 122 | return $('
          ') 123 | .append( 124 | $('
          ') 125 | .class(this.getClass(grade)) 126 | .text(grade) 127 | ) 128 | .append( 129 | $('') 130 | .text(this.getTip(gradeName, grade)) 131 | ); 132 | }; 133 | }; 134 | })(forp, jMicro); -------------------------------------------------------------------------------- /src/css/default.css: -------------------------------------------------------------------------------- 1 | /* reset CSS */ 2 | #forp *{ 3 | margin: 0; 4 | padding: 0; 5 | border: 0; 6 | font-family: sans-serif; 7 | font-size: 1em; 8 | font-weight: normal; 9 | font-style: normal; 10 | text-decoration: none; 11 | width: auto; 12 | height: auto; 13 | color: #222; 14 | line-height: 1.3; 15 | } 16 | #forp { 17 | position: relative; 18 | font-weight: 300; 19 | max-width: 300px; 20 | background: #fff; 21 | font-size : 12px; 22 | } 23 | #forp div.inset { 24 | height: 10px; position: absolute; 25 | top: 0px; left: 0px; right: 0px; 26 | -webkit-box-shadow: inset 0 3px 5px -2px #aaa; 27 | -moz-box-shadow: inset 0 3px 5px -2px #aaa; 28 | box-shadow: inset 0 3px 5px -2px #aaa; 29 | } 30 | #forp.forp-fixed { 31 | height: 70%; 32 | position: fixed; 33 | bottom: 0px; 34 | right: 0px; 35 | z-index: 2147483647; 36 | } 37 | #forp.forp-fixed-compact { 38 | position: fixed; 39 | bottom: 0px; 40 | right: 0px; 41 | z-index: 2147483647; 42 | width: 150px; 43 | opacity: .6; 44 | cursor: pointer; 45 | margin: 15px; 46 | border-radius: 8px; 47 | -moz-box-shadow: 0 0 8px #ccc; 48 | -webkit-box-shadow: 0 0 8px #ccc; 49 | box-shadow: 0 0 8px #ccc; 50 | } 51 | #forp.forp-fixed-compact:hover { 52 | opacity: 1; 53 | } 54 | #forp.forp-fixed-compact nav{ 55 | height: auto; 56 | padding: 10px; 57 | border-radius: 8px; 58 | } 59 | #forp.forp-fixed-compact nav div{ 60 | margin: 3px 0px; 61 | } 62 | #forp.forp-fixed, #forp.forp-embedded { 63 | min-width: 1000px; 64 | max-width: 100%; 65 | left: 0px; 66 | } 67 | #forp.forp-fixed a, #forp.forp-embedded a{ 68 | display: inline; 69 | } 70 | #forp.forp-fixed nav div, #forp.forp-embedded nav div{ 71 | float: left; 72 | } 73 | #forp.forp-fixed div.summary, #forp.forp-embedded div.summary{ 74 | display: none 75 | } 76 | 77 | /* backtrace */ 78 | #forp div.backtrace{ 79 | text-align: center; 80 | color: #aaa; 81 | margin: 30px auto; 82 | } 83 | #forp div.backtrace-item{ 84 | color: #333; 85 | margin: 0px 5px; 86 | padding: 4px 5px 5px 5px; 87 | border-radius: 8px; 88 | padding: 5px; 89 | display: inline-block; 90 | } 91 | #forp div.backtrace div.arrow{ 92 | margin: 5px; 93 | color: #999; 94 | } 95 | #forp div.backtrace div.highlight{ 96 | background: #4D90FE !important; 97 | color: #FFF !important; 98 | } 99 | #forp div.backtrace div.highlight span{ 100 | color: #FFF !important; 101 | } 102 | 103 | /* close */ 104 | #forp div.close{} 105 | #forp div.close:before{ 106 | content: "x"; 107 | } 108 | #forp div.close{ 109 | color: #ccc; 110 | position: absolute; 111 | top: 14px; 112 | right: 30px; 113 | } 114 | #forp div.close:hover{ 115 | cursor: pointer; 116 | } 117 | 118 | /* console */ 119 | #forp div.console{ 120 | position: relative; 121 | float: left; 122 | width: 100%; 123 | } 124 | 125 | /* gauge */ 126 | #forp div.gauge{ 127 | position: relative; 128 | background: #eee; 129 | color: #333; 130 | margin: 1px; 131 | width: 110px; 132 | text-align: right; 133 | } 134 | #forp div.gauge div.bar{ 135 | background: #59f; 136 | height: 18px; 137 | } 138 | #forp div.gauge div.text{ 139 | position: absolute; 140 | top: 0px; 141 | right: 4px; 142 | line-height: 1.8; 143 | font-size: .9em 144 | } 145 | #forp td div.gauge{ 146 | margin: 0px; 147 | } 148 | 149 | /* grade */ 150 | #forp div.grade, #forp div.grade-A, #forp div.grade-B, 151 | #forp div.grade-C, #forp div.grade-D, #forp div.grade-E { 152 | padding: 2px 5px; 153 | display: inline-block; 154 | border-radius: 3px; 155 | font-size: 0.8em; 156 | color: #FFF; 157 | margin-right: 10px 158 | } 159 | #forp div.grade-A { 160 | background-color: #9E5 161 | } 162 | #forp div.grade-B { 163 | background-color: #EE5 164 | } 165 | #forp div.grade-C { 166 | background-color: #EB5 167 | } 168 | #forp div.grade-D { 169 | background-color: #E95 170 | } 171 | #forp div.grade-E { 172 | background-color: #E55 173 | } 174 | 175 | /* inspector */ 176 | #forp ul.inspect{ 177 | padding: 10px 0px 0px 0px; 178 | } 179 | 180 | /* input */ 181 | #forp input[type=text]{ 182 | padding: 4px; 183 | border: 1px solid #777; 184 | border-radius: 3px; 185 | background: #fff; 186 | } 187 | 188 | /* nav */ 189 | #forp nav{ 190 | background: #fcfcfc; 191 | padding: 14px 0px 8px 4px; 192 | } 193 | #forp.forp-fixed nav, #forp.forp-embedded nav{ 194 | height: 44px; 195 | } 196 | #forp a{ 197 | white-space: nowrap; 198 | color: #FFF; 199 | } 200 | #forp div.toggleOn, #forp div.toggleOff{ 201 | margin: 0px 5px; 202 | padding: 4px; 203 | float: left; 204 | border-radius: 2px; 205 | } 206 | #forp div.toggleOn:hover, #forp div.toggleOff:hover{ 207 | cursor: pointer; 208 | } 209 | #forp div.toggleOn{ 210 | color: #FFF; 211 | background: #4D90FE; 212 | } 213 | #forp div.toggleOff{ 214 | color: #FFF; 215 | background: #555; 216 | } 217 | 218 | /* sidebar */ 219 | #forp div.sidebar{ 220 | float: right; 221 | } 222 | #forp div.w2of3 { 223 | width: 66% !important; 224 | } 225 | #forp div.w1of3 { 226 | width: 33% !important; 227 | } 228 | 229 | /* stack */ 230 | #forp ul{ 231 | list-style: none; 232 | padding-left: 20px; 233 | } 234 | #forp ul.l0{ 235 | padding-left: 10px; 236 | } 237 | #forp li{ 238 | line-height: 1.8 !important; 239 | margin: 2px 240 | } 241 | #forp li div.numeric{ 242 | min-width: 60px; 243 | margin: 0px 10px 244 | } 245 | #forp .indent{ 246 | padding-left: 30px 247 | } 248 | #forp li div.expander{ 249 | text-align:center; 250 | width: 20px; 251 | cursor: pointer; 252 | } 253 | #forp li.expanded div.expander:before{ 254 | content: "-"; 255 | } 256 | #forp li.collapsed div.expander:before{ 257 | content: "+"; 258 | } 259 | #forp li.collapsed ul{ 260 | display: none; 261 | } 262 | #forp .left{ 263 | float: left; 264 | } 265 | #forp .right{ 266 | float: right; 267 | } 268 | 269 | /* table */ 270 | #forp table{ 271 | font-weight: 300; 272 | width: 100%; 273 | border-collapse: collapse; 274 | } 275 | #forp th, #forp td{ 276 | padding: 5px; 277 | } 278 | #forp th{ 279 | text-align: center; 280 | font-weight: bold; 281 | color: #333; 282 | border: 1px solid #ddd; 283 | background: #eee; 284 | } 285 | #forp .w100{ 286 | width: 110px; 287 | } 288 | #forp td{ 289 | text-align: left; 290 | text-overflow: ellipsis; 291 | word-space: nowrap; 292 | overflow: hidden; 293 | vertical-align: top; 294 | } 295 | #forp tr{ 296 | color: #333; 297 | background: #fff; 298 | } 299 | #forp tr.sub{ 300 | background: #eee; 301 | } 302 | #forp tr:hover{ 303 | background: #EDF4FF; 304 | } 305 | #forp tr[data-ref]{ 306 | cursor: pointer; 307 | } 308 | #forp .numeric{ 309 | text-align: right; 310 | } 311 | 312 | /* tags */ 313 | #forp a.tag{ 314 | background: #EE0; 315 | color: #fff; 316 | font-size : 0.8em; 317 | padding: 2px 5px; 318 | margin: 0px 5px; 319 | } 320 | #forp a.tag, #forp a{ 321 | border-radius: 3px; 322 | } 323 | #forp div.panel { 324 | overflow: auto; 325 | overflow-x: hidden; 326 | } -------------------------------------------------------------------------------- /src/js/lib/view/utils.js: -------------------------------------------------------------------------------- 1 | (function(forp, $) { 2 | 3 | "use strict"; 4 | 5 | /** 6 | * ScrollBottom jMicro Plugin 7 | */ 8 | $.fn.scrollBottom = function() { 9 | return this.each(function() { 10 | this.scrollTop = $(this.firstChild).height(); 11 | }); 12 | }; 13 | 14 | /** 15 | * jMicro table() helper 16 | */ 17 | $.table = function(headers, reorder) { 18 | var $table = $(''); 19 | if(headers) { 20 | var $header = $(""); 21 | for(var i in headers) { 22 | if (reorder && ~reorder.indexOf(headers[i])) { 23 | $("'); 75 | for(var i in cols) { 76 | if(typeof cols[i] === "object") { 77 | $tr.append( 78 | $("
          ").text(headers[i]) 24 | .addClass("reorder") 25 | .on("click", $.fn.tableOrder) 26 | .appendTo($header); 27 | } else { 28 | $("").text(headers[i]).appendTo($header); 29 | } 30 | } 31 | $header.appendTo($table); 32 | } 33 | return $table; 34 | }; 35 | 36 | /** 37 | * jMicro tableOrder callback 38 | */ 39 | $.fn.tableOrder = function(e) { 40 | $(e.srcElement).data("order", $(e.srcElement).data("order") ? 0 : 1); 41 | var $tbl = $(e.srcElement).parent().parent(), 42 | index = e.srcElement.cellIndex, 43 | trs = [], 44 | order = $(e.srcElement).data("order"); 45 | 46 | for (var i = 1; i < $tbl[0].childNodes.length; i++) { 47 | trs.push($tbl[0].childNodes[i]); 48 | } 49 | 50 | trs.sort(function(a, b) { 51 | var aval = a.childNodes[index].innerHTML.replace(/<.*?>/g, ''), 52 | bval = b.childNodes[index].innerHTML.replace(/<.*?>/g, ''), 53 | regex = /^[\d\.]+$/; 54 | 55 | if (regex.test(aval) && regex.test(bval)) { 56 | return order ? (parseFloat(aval) - parseFloat(bval)): (parseFloat(bval) - parseFloat(aval)); 57 | } else if (aval < bval) { 58 | return order ? -1 : 1; 59 | } else if (aval > bval) { 60 | return order ? 1 : -1; 61 | } 62 | return 0; 63 | }); 64 | 65 | for (var i = 0; i < trs.length; i++) { 66 | $tbl.append(trs[i]); 67 | } 68 | }; 69 | 70 | /** 71 | * jMicro line() helper 72 | */ 73 | $.fn.line = function(cols) { 74 | var $tr = $('
          ") 79 | .append(cols[i]) 80 | ); 81 | } else if(isNaN(cols[i])) { 82 | $tr.append( 83 | $("") 84 | .html(cols[i]) 85 | ); 86 | } else { 87 | $tr.append( 88 | $("") 89 | .addClass("numeric w100") 90 | .html(cols[i]) 91 | ); 92 | } 93 | } 94 | $(this).append($tr); 95 | return $tr; 96 | }; 97 | 98 | /** 99 | * Utils, helpers 100 | */ 101 | $.Utils = { 102 | /** 103 | * TODO depth 104 | * @param path File path 105 | */ 106 | trimPath : function(path) 107 | { 108 | var pathSplit = path.split('/'); 109 | if(pathSplit.length > 3) { 110 | pathSplit = [pathSplit[pathSplit.length - 4], pathSplit[pathSplit.length - 3], pathSplit[pathSplit.length - 2], pathSplit[pathSplit.length - 1]]; 111 | path = pathSplit.join('/'); 112 | } 113 | return path; 114 | }, 115 | htmlEntities : function(str) { 116 | return String(str) 117 | .replace(/&/g, '&') 118 | .replace(//g, '>') 120 | .replace(/"/g, '"'); 121 | } 122 | }; 123 | 124 | /** 125 | * Sorted Fixed Array Class 126 | * @param callback compare 127 | * @param int size 128 | */ 129 | $.SortedFixedArray = function(compare, size) { 130 | this.stack = []; 131 | this.size = size; 132 | /** 133 | * Internal method insert 134 | * @param mixed entry 135 | * @param int i 136 | */ 137 | this.insert = function(entry, i) { 138 | for(var j = Math.min(this.size - 1, this.stack.length); j > i; j--) { 139 | this.stack[j] = this.stack[j - 1]; 140 | } 141 | this.stack[i] = entry; 142 | } 143 | /** 144 | * Evaluate and put a new entry in the stack 145 | * @param mixed entry 146 | */ 147 | this.put = function(entry) { 148 | if(this.stack.length) { 149 | // break if end entry is greatest 150 | if(this.stack.length == this.size) { 151 | if(!compare.call(this, entry, this.stack[this.size-1])) { 152 | return; 153 | } 154 | } 155 | // insert entry at the right place 156 | for(var i = 0; i < this.stack.length; i++) { 157 | if(compare.call(this, entry, this.stack[i])) { 158 | this.insert(entry, i); 159 | break; 160 | } 161 | } 162 | if( 163 | (i == this.stack.length) 164 | && (this.stack.length < this.size) 165 | ) { 166 | this.insert(entry, i); 167 | } 168 | } else { 169 | this.insert(entry, 0); 170 | } 171 | }; 172 | }; 173 | 174 | /** 175 | * @param string v 176 | * @return int 177 | */ 178 | $.round = function(v) 179 | { 180 | return (~~ (0.5 + (v * 1000))) / 1000; 181 | }; 182 | 183 | /** 184 | * @param string v 185 | * @param int d 186 | * @return int 187 | */ 188 | $.roundDiv = function(v, d) 189 | { 190 | return this.round(v / d); 191 | }; 192 | 193 | /** 194 | * inArray 195 | * @param needle 196 | * @param haystack 197 | */ 198 | $.inArray = function(needle, haystack) { 199 | var length = haystack.length; 200 | for(var i = 0; i < length; i++) { 201 | if(haystack[i] == needle) return true; 202 | } 203 | return false; 204 | }; 205 | 206 | /** 207 | * Helper for old jMicro portability 208 | */ 209 | forp.Decorator = function($container) 210 | { 211 | var self = this; 212 | this.$ = $container; 213 | this.element = $container[0]; 214 | 215 | this.unbind = function(event, fn) 216 | { 217 | self.$.unbind(event, fn); 218 | return this; 219 | }; 220 | 221 | this.appendTo = function(container) 222 | { 223 | this.$.appendTo(container.$); 224 | return this; 225 | }; 226 | 227 | 228 | this.append = function(element) 229 | { 230 | this.$.append(element.$); 231 | return this; 232 | }; 233 | 234 | this.empty = function() 235 | { 236 | this.$.empty(); 237 | return this; 238 | }; 239 | 240 | this.attr = function(k, v) 241 | { 242 | this.$.attr(k, v); 243 | return this; 244 | }; 245 | 246 | /*this.table = function(headers) { 247 | return (new forp.Table(headers)).appendTo(this); 248 | };*/ 249 | }; 250 | 251 | /** 252 | * Nav 253 | */ 254 | forp.Nav = function() 255 | { 256 | forp.Decorator.call(this, $("