Extension:TemplateProfiler
Jump to navigation
Jump to search
![]() | This extension requires patches to core MediaWiki code . Extensions implemented using patches may be disabled by or interfere with upgrades and security patches. If a suitable alternative without a patch is available, we recommend you use that extension instead. |
TemplateProfiler Release status: stable |
|
---|---|
Implementation | Page action |
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ć |
Latest version | 0.9 (2009-06-21) |
MediaWiki | 1.14. X |
License | GPL 3 |
Download | see below |
Translate the TemplateProfiler extension if it is available at translatewiki.net | |
Check usage and version matrix. |
Contents
Purpose[edit]
Profiling templates to make your Wiki faster. Requires minimal hacking of the parser.
Usage[edit]
add ?action=profile to the page url
Installation[edit]
- Open your
includes/parser/Parser.php
file. - Find the line that contains
# SUBST
in thebraceSubstitution
function. - 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');
Code[edit]
<?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]]";
}
}
Css[edit]
/* ### 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;
}