Extension:TemplateProfiler
From MediaWiki.org
|
Release status: stable |
|
|---|---|
| Implementation | page_action (invalid type) |
| Description | Profiles templates and parser function and shows results in a tree and aggregated results in a table. Requires minimal hacking of the parser. |
| Author(s) | Zoran Obradović |
| Last Version | 0.9 (2009-06-21) |
| MediaWiki | 1.14. X |
| License | GPL 3 |
| Download | see below |
|
check usage (experimental) |
|
Contents |
[edit] Purpose
Profiling templates to make your Wiki faster. Requires minimal hacking of the parser.
[edit] Usage
add ?action=profile to the page url
[edit] Installation
- Open your
includes/parser/Parser.phpfile. - Find the line that contains
# SUBSTin thebraceSubstitutionfunction. - Insert the following line before that line:
wfRunHooks( 'BeforeBraceSubstitution', array( &$parser, &$originalTitle, &$args) );
- Scroll down to the end of the function
braceSubstitution. Find the line that containswfProfileOut( __METHOD__ );a few lines before the end of the function. - Insert the following line before that line:
wfRunHooks( 'AfterBraceSubstitution', array( &$parser, &$originalTitle) );
- Save and close Parser.php
- Copy the code into a file named TemplateProfiler.php in your extensions directory.
- Copy the css to the end of your Mediawiki:Common.css
- Add the following line to the end of your LocalSettings.php file
require_once('extensions/TemplateProfiler.php');
[edit] Code
<?php $wgTemplateProfiler = new TemplateProfiler; class TemplateProfiler { var $active = false; # for hooks to know whether to collect data or not var $data = array(null); # for collecting profiling data var $outputTree = array(); # tree of profiling data, for output var $outputList = array(); # summary list, for output var $outputSubList = array(); # summary sub list, for output var $totalTime = 0; # total time, for use in output var $maxNetTime = 0; # maximum net time, for use in output # arguments: # profile_show : templates, all, or dump, defaults to templates # profile_min : smallest time in ms to include in the list, defaults to 10 # profile_hl : total, net or dump, defaults to net var $show = 'templates'; var $min = 10; var $hl = 'net'; function registerHook( $name ) { global $wgHooks; $wgHooks[ $name ][] = array( &$this , "hook_$name" ); } function __construct() { $this->registerHook('UnknownAction'); $this->registerHook('BeforeBraceSubstitution'); $this->registerHook('AfterBraceSubstitution'); } ################################ # # M A I N F U N C T I O N # ################################ function hook_UnknownAction($action, &$article) { # die ($action); if ($action!='profile') return true; global $wgRequest, $wgOut; # gather and fix parameters $this->min = (int) $wgRequest->getVal('profile_min','10'); if ($this->min < 0) $this->min = 0; switch ($this->show = $wgRequest->getText('profile_show')) { case 'all': case 'dump': break; default: $show='templates'; } switch ($this->hl = $wgRequest->getText('profile_hl')) { case 'dump': case 'total': break; default: $this->hl='net'; } #collect profiling data for this article $this->collectData($article); # if we're just dumping, return print_r if ($this->show=='dump') { $wgOut->addHTML("<pre>".print_r($this->data,true)."</pre>"); return false; } # otherwise, we need to extract data for display $this->processData(); # output the results $wgOut->addHTML('<div id="template-profiler">'); $this->outputHeader($article->getTitle()); $this->outputData(); $wgOut->addHTML('</div>'); return false; } ################################ # # D A T A C O L L E C T I O N # ################################ # profiles an article, by using hooks to collect times on braceSubstitution function collectData(&$article) { global $wgParser, $wgUser, $wgRequest; #get title for later use $title = $article->getTitle(); # prepare root node $this->data = array ( 'parent' => null, 'title' => ':'.$title->getFullText(), 'children' => array() ); # prepare the parser $parser =& $wgParser; $this->active = true; $options = ParserOptions::newFromUser($wgUser); #get text $text = $article->getContent(); #parse the page, recording times for the root node $this->data['start']= microtime(true); $parser->parse($article->getContent(), $title, $options, false); $this->data['end']= microtime(true); } function hook_BeforeBraceSubstitution(&$parser,$titleText,&$args) { # work only if active if (!$this->active) return true; $node = array ( 'parent' => &$this->data, 'start' => microtime(true), 'title' => $titleText, 'children' => array() ); $this->data['children'][] =& $node; $this->data =& $node; return true; } function hook_AfterBraceSubstitution(&$parser,$titleText) { if (!$this->active) return true; $end = microtime(true); $this->data['end']= $end; $this->data =& $this->data['parent']; return true; } ################################### # # D A T A P R O C E S S I N G # ################################### # process collected data, producing $this->outputList and $this->outputTree function processData(&$node=null) { if ($node===null) { $initial=true; $this->outputList=array(); $this->outputSubList=array(); $this->maxNetTime=0; $theNode =& $this->data; $this->totalTime = $this->data['end']-$this->data['start']; } else { $initial=false; $theNode =& $node; } $newNode = array ( 'title' => $theNode['title'], 'children' => array(), 'time' => $theNode['end'] - $theNode['start'], 'nettime' => $theNode['end'] - $theNode['start'] ); foreach ($theNode['children'] as &$child) { $childTime = $child['end'] - $child['start']; if ($this->show='all' || $this->isTemplate($child['title'])) { $childNode = $this->processData($child); if ($childTime >= $this->min/1000) { $newNode['children'][] = $childNode; $newNode['nettime'] -= $childTime; } else { $newNode['nettime'] -= $childTime; $newNode['children'][-1]['nettime']+=$childTime; $newNode['children'][-1]['time']+=$childTime; $newNode['children'][-1]['title']="{other}"; } list ($group,$item)=$this->splitTitle($child['title']); # print ("$group,$item," . $childTime - $childNode['nettime'] . "<br>"); $this->outputSubList[$group][$item]['time']+=$childTime; $this->outputSubList[$group][$item]['nettime']+=$childNode['nettime']; $this->outputSubList[$group][$item]['count']++; $this->outputList[$group]['time']+=$childTime; $this->outputList[$group]['nettime']+=$childNode['nettime']; $this->outputList[$group]['count']++; if ($childNode['nettime'] > $this->maxNetTime) $this->maxNetTime = $childNode['nettime']; } } if (!$initial) return $newNode; $this->outputTree=$newNode; return true; } # is the title a variable? function isVariable($titleText) { global $wgParser; return $wgParser->mVariables->matchStartToEnd($titleText); } # is the title a function? function isFunction($titleText) { global $wgParser; $parts = split(':',$titleText,2); return ( count($parts) == 2 && ( isset( $wgParser->mFunctionSynonyms[0][strtolower($parts[0])] ) || isset( $wgParser->mFunctionSynonyms[1][$parts[0]] ) ) ); } # is the title a template? function isTemplate($titleText) { return !($this->isVariable($titleText) || $this->isFunction($titleText) || !Title::newFromText($titleText)); } # split function splitTitle($titleText) { if ($this->isVariable($titleText)) return array ('variables',$titleText); if ($this->isFunction($titleText)) { return split(':',$titleText,2); } $title = Title::newFromText($titleText,NS_TEMPLATE); return array($title->getNsText(),$title->getText()); } ################################### # # O U T P U T # ################################### function outputHeader($title) { global $wgOut; $wgOut->addHTML ( 'show: <a href="' . $title->getFullUrl('action=profile&profile_min=0') . '">all</a>' .' · <a href="' . $title->getFullUrl('action=profile&profile_min=10') . '">over 10 ms</a>' .' · <a href="' . $title->getFullUrl('action=profile&profile_min=100') . '">over 100 ms</a>' ); } function outputData() { global $wgOut; if ($this->hl=='dump') { $wgOut->addHTML("<pre>".print_r(array('tree'=>$this->outputTree,'list'=>$this->outputList),true)."</pre>"); return; } $this->outputTreeData(); $this->outputListData(); } function outputTreeData() { global $wgOut; $text = $this->outputTreeNode($this->outputTree); $wgOut->addHTML('<ul>'); $wgOut->addWikiText($text); $wgOut->addHTML('</ul></div>'); return false; } function outputTreeNode(&$node) { $time = $node['time']; $nettime = $node['nettime']; $shade = (int)($nettime*1000); if ($shade>50) $shade=50; $shade = (50-$shade)/5; $shade=$shade*$shade; $shade = (int)(100-$shade); $color = "rgb($shade,$shade,0)"; $ret = '<li style="background:'.$color.'" class="profiler-treeitem">'; $ret .= '<span class="profiler-time">' . ($time<.001 ? (int)($time*10000)/10 : (int)($time*1000)) .'</span>'; $ret.= '<span class="profiler-nettime">' . ($nettime<.001 ? (int)($nettime*10000)/10 : (int)($nettime*1000)) .'</span>'; $ret.= '<span class="profiler-title">'; $ret.= $this->getTitle($node['title']); $ret.='</span>'; if (count($node['children'])) { $ret.='<ul>'; foreach ($node['children'] as &$child) { $ret.=$this->outputTreeNode(&$child,$minTime); } $ret.='</ul>'; } return $ret; } function compareNetTime(&$l,&$r) { if ($l['nettime']>$r['nettime']) return -1; elseif ($l['nettime']<$r['nettime']) return 1; else return 0; } function outputListData() { global $wgOut; $wgOut->addHTML("<table id=\"profiler-list\"><tr id=\"profiler-listheader\"><th>group / item</th><th>count</th><th>net time (ms)</th><th>total time (ms)</th></th></tr>"); uasort($this->outputList,array($this,'compareNetTime')); foreach ($this->outputList as $groupName=>$group) { if ($group['time']>=$this->min/1000) { $wgOut->addHTML("<tr class=\"profiler-listgroup\"><th>".($groupName?$groupName:'(main)').":</th><td>${group['count']}</td><td>" .(int)($group['nettime']*1000) ."</td><td>" .(int)($group['time']*1000) ."</td></tr>" ); uasort($this->outputSubList[$groupName],array($this,'compareNetTime')); foreach ($this->outputSubList[$groupName] as $itemName=>$item) { if ($item['time']>=$this->min/1000) { $wgOut->addHTML("<tr class=\"profiler-listitem\"><th>$itemName</th><td>${item['count']}</td><td>" .(int)($item['nettime']*1000) ."</td><td>" .(int)($item['time']*1000) ."</td></tr>"); } } } } $wgOut->addHTML("</table>"); return; } function getTitle($titleText) { if (!$this->IsTemplate($titleText)) return $titleText; $title = Title::newFromText($titleText,NS_TEMPLATE)->getFullText(); return "[[$title]]"; } }
[edit] Css
/* ### PROFILER ### */ #template-profiler, .box { margin:0 0 10px 0; padding:10px; background:#272727; color:white; } #template-profiler a { color:orange; } #template-profiler ul { margin:0; padding:0; } #profiler-list { background:black; width:100%; color:white; border-collapse:collapse; } #profiler-list th, #profiler-list td { border-top:dotted 1px #022; border-bottom:dotted 1px #022; } #profiler-list td { text-align:right; font-size:90%; } tr.profiler-listgroup td { color:red; background:#111; width:6em; text-align:right; color:white; } .profiler-listgroup th { text-align:left; background:#111; } .profiler-listitem th { text-align:left; font-weight:normal; padding-left:2em; } .profiler-treeitem { list-style-type:none; marker-offset:0px; padding:0px 0px 0px 24px; margin:0px -1px -1px 0px; border:dotted 1px #033; } .profiler-time { font-size:10px; float:left; height:100%; margin-left:-24px; width:24px; padding-right:6px; text-align:right; color:#8ff; } .profiler-nettime { font-size:10px; width:22px; float:right; text-align:right; padding-right:2px; color:#8ff; }