Added Kohana v3.0.8
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* @package Kohana/Codebench
|
||||
* @category Tests
|
||||
* @author Geert De Deckere <geert@idoe.be>
|
||||
*/
|
||||
class Bench_ArrCallback extends Codebench {
|
||||
|
||||
public $description =
|
||||
'Parsing <em>command[param,param]</em> strings in <code>Arr::callback()</code>:
|
||||
http://github.com/shadowhand/kohana/commit/c3aaae849164bf92a486e29e736a265b350cb4da#L0R127';
|
||||
|
||||
public $loops = 10000;
|
||||
|
||||
public $subjects = array
|
||||
(
|
||||
// Valid callback strings
|
||||
'foo',
|
||||
'foo::bar',
|
||||
'foo[apple,orange]',
|
||||
'foo::bar[apple,orange]',
|
||||
'[apple,orange]', // no command, only params
|
||||
'foo[[apple],[orange]]', // params with brackets inside
|
||||
|
||||
// Invalid callback strings
|
||||
'foo[apple,orange', // no closing bracket
|
||||
);
|
||||
|
||||
public function bench_shadowhand($subject)
|
||||
{
|
||||
// The original regex we're trying to optimize
|
||||
if (preg_match('/([^\[]*+)\[(.*)\]/', $subject, $match))
|
||||
return $match;
|
||||
}
|
||||
|
||||
public function bench_geert_regex_1($subject)
|
||||
{
|
||||
// Added ^ and $ around the whole pattern
|
||||
if (preg_match('/^([^\[]*+)\[(.*)\]$/', $subject, $matches))
|
||||
return $matches;
|
||||
}
|
||||
|
||||
public function bench_geert_regex_2($subject)
|
||||
{
|
||||
// A rather experimental approach using \K which requires PCRE 7.2 ~ PHP 5.2.4
|
||||
// Note: $matches[0] = params, $matches[1] = command
|
||||
if (preg_match('/^([^\[]*+)\[\K.*(?=\]$)/', $subject, $matches))
|
||||
return $matches;
|
||||
}
|
||||
|
||||
public function bench_geert_str($subject)
|
||||
{
|
||||
// A native string function approach which beats all the regexes
|
||||
if (strpos($subject, '[') !== FALSE AND substr($subject, -1) === ']')
|
||||
return explode('[', substr($subject, 0, -1), 2);
|
||||
}
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* @package Kohana/Codebench
|
||||
* @category Tests
|
||||
* @author Geert De Deckere <geert@idoe.be>
|
||||
*/
|
||||
class Bench_AutoLinkEmails extends Codebench {
|
||||
|
||||
public $description =
|
||||
'Fixing <a href="http://dev.kohanaphp.com/issues/2772">#2772</a>, and comparing some possibilities.';
|
||||
|
||||
public $loops = 1000;
|
||||
|
||||
public $subjects = array
|
||||
(
|
||||
'<ul>
|
||||
<li>voorzitter@xxxx.com</li>
|
||||
<li>vicevoorzitter@xxxx.com</li>
|
||||
</ul>',
|
||||
);
|
||||
|
||||
// The original function, with str_replace replaced by preg_replace. Looks clean.
|
||||
public function bench_match_all_loop($subject)
|
||||
{
|
||||
if (preg_match_all('~\b(?<!href="mailto:|">|58;)(?!\.)[-+_a-z0-9.]++(?<!\.)@(?![-.])[-a-z0-9.]+(?<!\.)\.[a-z]{2,6}\b~i', $subject, $matches))
|
||||
{
|
||||
foreach ($matches[0] as $match)
|
||||
{
|
||||
$subject = preg_replace('!\b'.preg_quote($match).'\b!', HTML::mailto($match), $subject);
|
||||
}
|
||||
}
|
||||
|
||||
return $subject;
|
||||
}
|
||||
|
||||
// The "e" stands for "eval", hmm... Ugly and slow because it needs to reinterpret the PHP code upon each match.
|
||||
public function bench_replace_e($subject)
|
||||
{
|
||||
return preg_replace(
|
||||
'~\b(?<!href="mailto:|">|58;)(?!\.)[-+_a-z0-9.]++(?<!\.)@(?![-.])[-a-z0-9.]+(?<!\.)\.[a-z]{2,6}\b~ie',
|
||||
'HTML::mailto("$0")', // Yuck!
|
||||
$subject
|
||||
);
|
||||
}
|
||||
|
||||
// This one should be quite okay, it just requires an otherwise useless single-purpose callback.
|
||||
public function bench_replace_callback_external($subject)
|
||||
{
|
||||
return preg_replace_callback(
|
||||
'~\b(?<!href="mailto:|">|58;)(?!\.)[-+_a-z0-9.]++(?<!\.)@(?![-.])[-a-z0-9.]+(?<!\.)\.[a-z]{2,6}\b~i',
|
||||
array($this, '_callback_external'),
|
||||
$subject
|
||||
);
|
||||
}
|
||||
protected function _callback_external($matches)
|
||||
{
|
||||
return HTML::mailto($matches[0]);
|
||||
}
|
||||
|
||||
// This one clearly is the ugliest, the slowest and consumes a lot of memory!
|
||||
public function bench_replace_callback_internal($subject)
|
||||
{
|
||||
return preg_replace_callback(
|
||||
'~\b(?<!href="mailto:|">|58;)(?!\.)[-+_a-z0-9.]++(?<!\.)@(?![-.])[-a-z0-9.]+(?<!\.)\.[a-z]{2,6}\b~i',
|
||||
create_function('$matches', 'return HTML::mailto($matches[0]);'), // Yuck!
|
||||
$subject
|
||||
);
|
||||
}
|
||||
|
||||
}
|
186
includes/kohana/modules/codebench/classes/bench/datespan.php
Normal file
186
includes/kohana/modules/codebench/classes/bench/datespan.php
Normal file
@@ -0,0 +1,186 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* @package Kohana/Codebench
|
||||
* @category Tests
|
||||
* @author Woody Gilk <woody.gilk@kohanaphp.com>
|
||||
*/
|
||||
class Bench_DateSpan extends Codebench {
|
||||
|
||||
public $description =
|
||||
'Optimization for <code>Date::span()</code>.';
|
||||
|
||||
public $loops = 1000;
|
||||
|
||||
public $subjects = array();
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->subjects = array(
|
||||
time(),
|
||||
time() - Date::MONTH,
|
||||
time() - Date::YEAR,
|
||||
time() - Date::YEAR * 10,
|
||||
);
|
||||
}
|
||||
|
||||
// Original method
|
||||
public static function bench_span_original($remote, $local = NULL, $output = 'years,months,weeks,days,hours,minutes,seconds')
|
||||
{
|
||||
// Array with the output formats
|
||||
$output = preg_split('/[^a-z]+/', strtolower((string) $output));
|
||||
|
||||
// Invalid output
|
||||
if (empty($output))
|
||||
return FALSE;
|
||||
|
||||
// Make the output values into keys
|
||||
extract(array_flip($output), EXTR_SKIP);
|
||||
|
||||
if ($local === NULL)
|
||||
{
|
||||
// Calculate the span from the current time
|
||||
$local = time();
|
||||
}
|
||||
|
||||
// Calculate timespan (seconds)
|
||||
$timespan = abs($remote - $local);
|
||||
|
||||
if (isset($years))
|
||||
{
|
||||
$timespan -= Date::YEAR * ($years = (int) floor($timespan / Date::YEAR));
|
||||
}
|
||||
|
||||
if (isset($months))
|
||||
{
|
||||
$timespan -= Date::MONTH * ($months = (int) floor($timespan / Date::MONTH));
|
||||
}
|
||||
|
||||
if (isset($weeks))
|
||||
{
|
||||
$timespan -= Date::WEEK * ($weeks = (int) floor($timespan / Date::WEEK));
|
||||
}
|
||||
|
||||
if (isset($days))
|
||||
{
|
||||
$timespan -= Date::DAY * ($days = (int) floor($timespan / Date::DAY));
|
||||
}
|
||||
|
||||
if (isset($hours))
|
||||
{
|
||||
$timespan -= Date::HOUR * ($hours = (int) floor($timespan / Date::HOUR));
|
||||
}
|
||||
|
||||
if (isset($minutes))
|
||||
{
|
||||
$timespan -= Date::MINUTE * ($minutes = (int) floor($timespan / Date::MINUTE));
|
||||
}
|
||||
|
||||
// Seconds ago, 1
|
||||
if (isset($seconds))
|
||||
{
|
||||
$seconds = $timespan;
|
||||
}
|
||||
|
||||
// Remove the variables that cannot be accessed
|
||||
unset($timespan, $remote, $local);
|
||||
|
||||
// Deny access to these variables
|
||||
$deny = array_flip(array('deny', 'key', 'difference', 'output'));
|
||||
|
||||
// Return the difference
|
||||
$difference = array();
|
||||
foreach ($output as $key)
|
||||
{
|
||||
if (isset($$key) AND ! isset($deny[$key]))
|
||||
{
|
||||
// Add requested key to the output
|
||||
$difference[$key] = $$key;
|
||||
}
|
||||
}
|
||||
|
||||
// Invalid output formats string
|
||||
if (empty($difference))
|
||||
return FALSE;
|
||||
|
||||
// If only one output format was asked, don't put it in an array
|
||||
if (count($difference) === 1)
|
||||
return current($difference);
|
||||
|
||||
// Return array
|
||||
return $difference;
|
||||
}
|
||||
|
||||
// Using an array for the output
|
||||
public static function bench_span_use_array($remote, $local = NULL, $output = 'years,months,weeks,days,hours,minutes,seconds')
|
||||
{
|
||||
// Array with the output formats
|
||||
$output = preg_split('/[^a-z]+/', strtolower((string) $output));
|
||||
|
||||
// Invalid output
|
||||
if (empty($output))
|
||||
return FALSE;
|
||||
|
||||
// Convert the list of outputs to an associative array
|
||||
$output = array_combine($output, array_fill(0, count($output), 0));
|
||||
|
||||
// Make the output values into keys
|
||||
extract(array_flip($output), EXTR_SKIP);
|
||||
|
||||
if ($local === NULL)
|
||||
{
|
||||
// Calculate the span from the current time
|
||||
$local = time();
|
||||
}
|
||||
|
||||
// Calculate timespan (seconds)
|
||||
$timespan = abs($remote - $local);
|
||||
|
||||
if (isset($output['years']))
|
||||
{
|
||||
$timespan -= Date::YEAR * ($output['years'] = (int) floor($timespan / Date::YEAR));
|
||||
}
|
||||
|
||||
if (isset($output['months']))
|
||||
{
|
||||
$timespan -= Date::MONTH * ($output['months'] = (int) floor($timespan / Date::MONTH));
|
||||
}
|
||||
|
||||
if (isset($output['weeks']))
|
||||
{
|
||||
$timespan -= Date::WEEK * ($output['weeks'] = (int) floor($timespan / Date::WEEK));
|
||||
}
|
||||
|
||||
if (isset($output['days']))
|
||||
{
|
||||
$timespan -= Date::DAY * ($output['days'] = (int) floor($timespan / Date::DAY));
|
||||
}
|
||||
|
||||
if (isset($output['hours']))
|
||||
{
|
||||
$timespan -= Date::HOUR * ($output['hours'] = (int) floor($timespan / Date::HOUR));
|
||||
}
|
||||
|
||||
if (isset($output['minutes']))
|
||||
{
|
||||
$timespan -= Date::MINUTE * ($output['minutes'] = (int) floor($timespan / Date::MINUTE));
|
||||
}
|
||||
|
||||
// Seconds ago, 1
|
||||
if (isset($output['seconds']))
|
||||
{
|
||||
$output['seconds'] = $timespan;
|
||||
}
|
||||
|
||||
if (count($output) === 1)
|
||||
{
|
||||
// Only a single output was requested, return it
|
||||
return array_pop($output);
|
||||
}
|
||||
|
||||
// Return array
|
||||
return $output;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* @package Kohana/Codebench
|
||||
* @category Tests
|
||||
* @author Geert De Deckere <geert@idoe.be>
|
||||
*/
|
||||
class Bench_ExplodeLimit extends Codebench {
|
||||
|
||||
public $description =
|
||||
'Having a look at the effect of adding a limit to the <a href="http://php.net/explode">explode</a> function.<br />
|
||||
http://stackoverflow.com/questions/1308149/how-to-get-a-part-of-url-between-4th-and-5th-slashes';
|
||||
|
||||
public $loops = 10000;
|
||||
|
||||
public $subjects = array
|
||||
(
|
||||
'http://example.com/articles/123a/view',
|
||||
'http://example.com/articles/123a/view/x/x/x/x/x',
|
||||
'http://example.com/articles/123a/view/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x',
|
||||
);
|
||||
|
||||
public function bench_explode_without_limit($subject)
|
||||
{
|
||||
$parts = explode('/', $subject);
|
||||
return $parts[4];
|
||||
}
|
||||
|
||||
public function bench_explode_with_limit($subject)
|
||||
{
|
||||
$parts = explode('/', $subject, 6);
|
||||
return $parts[4];
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* @package Kohana/Codebench
|
||||
* @category Tests
|
||||
* @author Geert De Deckere <geert@idoe.be>
|
||||
*/
|
||||
class Bench_GruberURL extends Codebench {
|
||||
|
||||
public $description =
|
||||
'Optimization for http://daringfireball.net/2009/11/liberal_regex_for_matching_urls';
|
||||
|
||||
public $loops = 10000;
|
||||
|
||||
public $subjects = array
|
||||
(
|
||||
'http://foo.com/blah_blah',
|
||||
'http://foo.com/blah_blah/',
|
||||
'(Something like http://foo.com/blah_blah)',
|
||||
'http://foo.com/blah_blah_(wikipedia)',
|
||||
'(Something like http://foo.com/blah_blah_(wikipedia))',
|
||||
'http://foo.com/blah_blah.',
|
||||
'http://foo.com/blah_blah/.',
|
||||
'<http://foo.com/blah_blah>',
|
||||
'<http://foo.com/blah_blah/>',
|
||||
'http://foo.com/blah_blah,',
|
||||
'http://www.example.com/wpstyle/?p=364.',
|
||||
'http://✪df.ws/e7l',
|
||||
'rdar://1234',
|
||||
'rdar:/1234',
|
||||
'x-yojimbo-item://6303E4C1-xxxx-45A6-AB9D-3A908F59AE0E',
|
||||
'message://%3c330e7f8409726r6a4ba78dkf1fd71420c1bf6ff@mail.gmail.com%3e',
|
||||
'http://➡.ws/䨹',
|
||||
'www.➡.ws/䨹',
|
||||
'<tag>http://example.com</tag>',
|
||||
'Just a www.example.com link.',
|
||||
// To test the use of possessive quatifiers:
|
||||
'httpppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp',
|
||||
);
|
||||
|
||||
public function bench_daringfireball($subject)
|
||||
{
|
||||
// Original regex by John Gruber
|
||||
preg_match('~\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))~', $subject, $matches);
|
||||
return (empty($matches)) ? FALSE : $matches[0];
|
||||
}
|
||||
|
||||
public function bench_daringfireball_v2($subject)
|
||||
{
|
||||
// Removed outer capturing parentheses, made another pair non-capturing
|
||||
preg_match('~\b(?:[\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|(?:[^[:punct:]\s]|/))~', $subject, $matches);
|
||||
return (empty($matches)) ? FALSE : $matches[0];
|
||||
}
|
||||
|
||||
public function bench_daringfireball_v3($subject)
|
||||
{
|
||||
// Made quantifiers possessive where possible
|
||||
preg_match('~\b(?:[\w-]++://?+|www[.])[^\s()<>]+(?:\([\w\d]++\)|(?:[^[:punct:]\s]|/))~', $subject, $matches);
|
||||
return (empty($matches)) ? FALSE : $matches[0];
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* @package Kohana/Codebench
|
||||
* @category Tests
|
||||
* @author Geert De Deckere <geert@idoe.be>
|
||||
*/
|
||||
class Bench_LtrimDigits extends Codebench {
|
||||
|
||||
public $description = 'Chopping off leading digits: regex vs ltrim.';
|
||||
|
||||
public $loops = 100000;
|
||||
|
||||
public $subjects = array
|
||||
(
|
||||
'123digits',
|
||||
'no-digits',
|
||||
);
|
||||
|
||||
public function bench_regex($subject)
|
||||
{
|
||||
return preg_replace('/^\d+/', '', $subject);
|
||||
}
|
||||
|
||||
public function bench_ltrim($subject)
|
||||
{
|
||||
return ltrim($subject, '0..9');
|
||||
}
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* @package Kohana/Codebench
|
||||
* @category Tests
|
||||
* @author Geert De Deckere <geert@idoe.be>
|
||||
*/
|
||||
class Bench_MDDoBaseURL extends Codebench {
|
||||
|
||||
public $description =
|
||||
'Optimization for the <code>doBaseURL()</code> method of <code>Kohana_Kodoc_Markdown</code>
|
||||
for the Kohana Userguide.';
|
||||
|
||||
public $loops = 10000;
|
||||
|
||||
public $subjects = array
|
||||
(
|
||||
// Valid matches
|
||||
'[filesystem](about.filesystem)',
|
||||
'[filesystem](about.filesystem "Optional title")',
|
||||
'[same page link](#id)',
|
||||
'[object oriented](http://wikipedia.org/wiki/Object-Oriented_Programming)',
|
||||
|
||||
// Invalid matches
|
||||
'',
|
||||
'[filesystem](about.filesystem',
|
||||
);
|
||||
|
||||
public function bench_original($subject)
|
||||
{
|
||||
// The original regex contained a bug, which is fixed here for benchmarking purposes.
|
||||
// At the very start of the regex, (?!!) has been replace by (?<!!)
|
||||
return preg_replace_callback('~(?<!!)\[(.+?)\]\(([^#]\S*(?:\s*".+?")?)\)~', array($this, '_add_base_url_original'), $subject);
|
||||
}
|
||||
public function _add_base_url_original($matches)
|
||||
{
|
||||
if ($matches[2] AND strpos($matches[2], '://') === FALSE)
|
||||
{
|
||||
// Add the base url to the link URL
|
||||
$matches[2] = 'http://BASE/'.$matches[2];
|
||||
}
|
||||
|
||||
// Recreate the link
|
||||
return "[{$matches[1]}]({$matches[2]})";
|
||||
}
|
||||
|
||||
public function bench_optimized_callback($subject)
|
||||
{
|
||||
return preg_replace_callback('~(?<!!)\[(.+?)\]\((?!\w++://)([^#]\S*(?:\s*+".+?")?)\)~', array($this, '_add_base_url_optimized'), $subject);
|
||||
}
|
||||
public function _add_base_url_optimized($matches)
|
||||
{
|
||||
// Add the base url to the link URL
|
||||
$matches[2] = 'http://BASE/'.$matches[2];
|
||||
|
||||
// Recreate the link
|
||||
return "[{$matches[1]}]({$matches[2]})";
|
||||
}
|
||||
|
||||
public function bench_callback_gone($subject)
|
||||
{
|
||||
// All the optimized callback was doing now, is prepend some text to the URL.
|
||||
// We don't need a callback for that, and that should be clearly faster.
|
||||
return preg_replace('~(?<!!)(\[.+?\]\()(?!\w++://)([^#]\S*(?:\s*+".+?")?\))~', '$1http://BASE/$2', $subject);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* @package Kohana/Codebench
|
||||
* @category Tests
|
||||
* @author Geert De Deckere <geert@idoe.be>
|
||||
*/
|
||||
class Bench_MDDoImageURL extends Codebench {
|
||||
|
||||
public $description =
|
||||
'Optimization for the <code>doImageURL()</code> method of <code>Kohana_Kodoc_Markdown</code>
|
||||
for the Kohana Userguide.';
|
||||
|
||||
public $loops = 10000;
|
||||
|
||||
public $subjects = array
|
||||
(
|
||||
// Valid matches
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'![Alt text containing [square] brackets](img/install.png)',
|
||||
'![Empty src]()',
|
||||
|
||||
// Invalid matches
|
||||
';
|
||||
|
||||
public function bench_original($subject)
|
||||
{
|
||||
return preg_replace_callback('~!\[(.+?)\]\((\S*(?:\s*".+?")?)\)~', array($this, '_add_image_url_original'), $subject);
|
||||
}
|
||||
protected function _add_image_url_original($matches)
|
||||
{
|
||||
if ($matches[2] AND strpos($matches[2], '://') === FALSE)
|
||||
{
|
||||
// Add the base url to the link URL
|
||||
$matches[2] = 'http://BASE/'.$matches[2];
|
||||
}
|
||||
|
||||
// Recreate the link
|
||||
return "![{$matches[1]}]({$matches[2]})";
|
||||
}
|
||||
|
||||
public function bench_optimized_callback($subject)
|
||||
{
|
||||
// Moved the check for "://" to the regex, simplifying the callback function
|
||||
return preg_replace_callback('~!\[(.+?)\]\((?!\w++://)(\S*(?:\s*+".+?")?)\)~', array($this, '_add_image_url_optimized'), $subject);
|
||||
}
|
||||
protected function _add_image_url_optimized($matches)
|
||||
{
|
||||
// Add the base url to the link URL
|
||||
$matches[2] = 'http://BASE/'.$matches[2];
|
||||
|
||||
// Recreate the link
|
||||
return "![{$matches[1]}]({$matches[2]})";
|
||||
}
|
||||
|
||||
public function bench_callback_gone($subject)
|
||||
{
|
||||
// All the optimized callback was doing now, is prepend some text to the URL.
|
||||
// We don't need a callback for that, and that should be clearly faster.
|
||||
return preg_replace('~(!\[.+?\]\()(?!\w++://)(\S*(?:\s*+".+?")?\))~', '$1http://BASE/$2', $subject);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* @package Kohana/Codebench
|
||||
* @category Tests
|
||||
* @author Geert De Deckere <geert@idoe.be>
|
||||
*/
|
||||
class Bench_MDDoIncludeViews extends Codebench {
|
||||
|
||||
public $description =
|
||||
'Optimization for the <code>doIncludeViews()</code> method of <code>Kohana_Kodoc_Markdown</code>
|
||||
for the Kohana Userguide.';
|
||||
|
||||
public $loops = 10000;
|
||||
|
||||
public $subjects = array
|
||||
(
|
||||
// Valid matches
|
||||
'{{one}} two {{three}}',
|
||||
'{{userguide/examples/hello_world_error}}',
|
||||
|
||||
// Invalid matches
|
||||
'{}',
|
||||
'{{}}',
|
||||
'{{userguide/examples/hello_world_error}',
|
||||
'{{userguide/examples/hello_world_error }}',
|
||||
'{{userguide/examples/{{hello_world_error }}',
|
||||
);
|
||||
|
||||
public function bench_original($subject)
|
||||
{
|
||||
preg_match_all('/{{(\S+?)}}/m', $subject, $matches, PREG_SET_ORDER);
|
||||
return $matches;
|
||||
}
|
||||
|
||||
public function bench_possessive($subject)
|
||||
{
|
||||
// Using a possessive character class
|
||||
// Removed useless /m modifier
|
||||
preg_match_all('/{{([^\s{}]++)}}/', $subject, $matches, PREG_SET_ORDER);
|
||||
return $matches;
|
||||
}
|
||||
|
||||
public function bench_lookaround($subject)
|
||||
{
|
||||
// Using lookaround to move $mathes[1] into $matches[0]
|
||||
preg_match_all('/(?<={{)[^\s{}]++(?=}})/', $subject, $matches, PREG_SET_ORDER);
|
||||
return $matches;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* @package Kohana/Codebench
|
||||
* @category Tests
|
||||
* @author Geert De Deckere <geert@idoe.be>
|
||||
*/
|
||||
class Bench_StripNullBytes extends Codebench {
|
||||
|
||||
public $description =
|
||||
'String replacement comparisons related to <a href="http://dev.kohanaphp.com/issues/2676">#2676</a>.';
|
||||
|
||||
public $loops = 1000;
|
||||
|
||||
public $subjects = array
|
||||
(
|
||||
"\0",
|
||||
"\0\0\0\0\0\0\0\0\0\0",
|
||||
"bla\0bla\0bla\0bla\0bla\0bla\0bla\0bla\0bla\0bla",
|
||||
"blablablablablablablablablablablablablablablabla",
|
||||
);
|
||||
|
||||
public function bench_str_replace($subject)
|
||||
{
|
||||
return str_replace("\0", '', $subject);
|
||||
}
|
||||
|
||||
public function bench_strtr($subject)
|
||||
{
|
||||
return strtr($subject, array("\0" => ''));
|
||||
}
|
||||
|
||||
public function bench_preg_replace($subject)
|
||||
{
|
||||
return preg_replace('~\0+~', '', $subject);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,65 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* @package Kohana/Codebench
|
||||
* @category Tests
|
||||
* @author Geert De Deckere <geert@idoe.be>
|
||||
*/
|
||||
class Bench_Transliterate extends Codebench {
|
||||
|
||||
public $description =
|
||||
'Inspired by:
|
||||
http://forum.kohanaframework.org/comments.php?DiscussionID=6113';
|
||||
|
||||
public $loops = 10;
|
||||
|
||||
public $subjects = array
|
||||
(
|
||||
// ASCII
|
||||
'a', 'b', 'c', 'd', '1', '2', '3',
|
||||
|
||||
// Non-ASCII
|
||||
'à', 'ô', 'ď', 'ḟ', 'ë', 'š', 'ơ',
|
||||
'ß', 'ă', 'ř', 'ț', 'ň', 'ā', 'ķ',
|
||||
'ŝ', 'ỳ', 'ņ', 'ĺ', 'ħ', 'ṗ', 'ó',
|
||||
'ú', 'ě', 'é', 'ç', 'ẁ', 'ċ', 'õ',
|
||||
'ṡ', 'ø', 'ģ', 'ŧ', 'ș', 'ė', 'ĉ',
|
||||
'ś', 'î', 'ű', 'ć', 'ę', 'ŵ', 'ṫ',
|
||||
'ū', 'č', 'ö', 'è', 'ŷ', 'ą', 'ł',
|
||||
'ų', 'ů', 'ş', 'ğ', 'ļ', 'ƒ', 'ž',
|
||||
'ẃ', 'ḃ', 'å', 'ì', 'ï', 'ḋ', 'ť',
|
||||
'ŗ', 'ä', 'í', 'ŕ', 'ê', 'ü', 'ò',
|
||||
'ē', 'ñ', 'ń', 'ĥ', 'ĝ', 'đ', 'ĵ',
|
||||
'ÿ', 'ũ', 'ŭ', 'ư', 'ţ', 'ý', 'ő',
|
||||
'â', 'ľ', 'ẅ', 'ż', 'ī', 'ã', 'ġ',
|
||||
'ṁ', 'ō', 'ĩ', 'ù', 'į', 'ź', 'á',
|
||||
'û', 'þ', 'ð', 'æ', 'µ', 'ĕ', 'ı',
|
||||
'À', 'Ô', 'Ď', 'Ḟ', 'Ë', 'Š', 'Ơ',
|
||||
'Ă', 'Ř', 'Ț', 'Ň', 'Ā', 'Ķ', 'Ĕ',
|
||||
'Ŝ', 'Ỳ', 'Ņ', 'Ĺ', 'Ħ', 'Ṗ', 'Ó',
|
||||
'Ú', 'Ě', 'É', 'Ç', 'Ẁ', 'Ċ', 'Õ',
|
||||
'Ṡ', 'Ø', 'Ģ', 'Ŧ', 'Ș', 'Ė', 'Ĉ',
|
||||
'Ś', 'Î', 'Ű', 'Ć', 'Ę', 'Ŵ', 'Ṫ',
|
||||
'Ū', 'Č', 'Ö', 'È', 'Ŷ', 'Ą', 'Ł',
|
||||
'Ų', 'Ů', 'Ş', 'Ğ', 'Ļ', 'Ƒ', 'Ž',
|
||||
'Ẃ', 'Ḃ', 'Å', 'Ì', 'Ï', 'Ḋ', 'Ť',
|
||||
'Ŗ', 'Ä', 'Í', 'Ŕ', 'Ê', 'Ü', 'Ò',
|
||||
'Ē', 'Ñ', 'Ń', 'Ĥ', 'Ĝ', 'Đ', 'Ĵ',
|
||||
'Ÿ', 'Ũ', 'Ŭ', 'Ư', 'Ţ', 'Ý', 'Ő',
|
||||
'Â', 'Ľ', 'Ẅ', 'Ż', 'Ī', 'Ã', 'Ġ',
|
||||
'Ṁ', 'Ō', 'Ĩ', 'Ù', 'Į', 'Ź', 'Á',
|
||||
'Û', 'Þ', 'Ð', 'Æ', 'İ',
|
||||
);
|
||||
|
||||
public function bench_utf8($subject)
|
||||
{
|
||||
return UTF8::transliterate_to_ascii($subject);
|
||||
}
|
||||
|
||||
public function bench_iconv($subject)
|
||||
{
|
||||
// Note: need to suppress errors on iconv because some chars trigger the following notice:
|
||||
// "Detected an illegal character in input string"
|
||||
return preg_replace('~[^-a-z0-9]+~i', '', @iconv('UTF-8', 'ASCII//TRANSLIT', $subject));
|
||||
}
|
||||
|
||||
}
|
123
includes/kohana/modules/codebench/classes/bench/urlsite.php
Normal file
123
includes/kohana/modules/codebench/classes/bench/urlsite.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* @package Kohana/Codebench
|
||||
* @category Tests
|
||||
* @author Geert De Deckere <geert@idoe.be>
|
||||
*/
|
||||
class Bench_URLSite extends Codebench {
|
||||
|
||||
public $description = 'http://dev.kohanaframework.org/issues/3110';
|
||||
|
||||
public $loops = 1000;
|
||||
|
||||
public $subjects = array
|
||||
(
|
||||
'',
|
||||
'news',
|
||||
'news/',
|
||||
'/news/',
|
||||
'news/page/5',
|
||||
'news/page:5',
|
||||
'http://example.com/',
|
||||
'http://example.com/hello',
|
||||
'http://example.com:80/',
|
||||
'http://user:pass@example.com/',
|
||||
);
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
foreach ($this->subjects as $subject)
|
||||
{
|
||||
// Automatically create URIs with query string and/or fragment part appended
|
||||
$this->subjects[] = $subject.'?query=string';
|
||||
$this->subjects[] = $subject.'#fragment';
|
||||
$this->subjects[] = $subject.'?query=string#fragment';
|
||||
}
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function bench_original($uri)
|
||||
{
|
||||
// Get the path from the URI
|
||||
$path = trim(parse_url($uri, PHP_URL_PATH), '/');
|
||||
|
||||
if ($query = parse_url($uri, PHP_URL_QUERY))
|
||||
{
|
||||
$query = '?'.$query;
|
||||
}
|
||||
|
||||
if ($fragment = parse_url($uri, PHP_URL_FRAGMENT))
|
||||
{
|
||||
$fragment = '#'.$fragment;
|
||||
}
|
||||
|
||||
return $path.$query.$fragment;
|
||||
}
|
||||
|
||||
public function bench_explode($uri)
|
||||
{
|
||||
// Chop off possible scheme, host, port, user and pass parts
|
||||
$path = preg_replace('~^[-a-z0-9+.]++://[^/]++/?~', '', trim($uri, '/'));
|
||||
|
||||
$fragment = '';
|
||||
$explode = explode('#', $path, 2);
|
||||
if (isset($explode[1]))
|
||||
{
|
||||
$path = $explode[0];
|
||||
$fragment = '#'.$explode[1];
|
||||
}
|
||||
|
||||
$query = '';
|
||||
$explode = explode('?', $path, 2);
|
||||
if (isset($explode[1]))
|
||||
{
|
||||
$path = $explode[0];
|
||||
$query = '?'.$explode[1];
|
||||
}
|
||||
|
||||
return $path.$query.$fragment;
|
||||
}
|
||||
|
||||
public function bench_regex($uri)
|
||||
{
|
||||
preg_match('~^(?:[-a-z0-9+.]++://[^/]++/?)?([^?#]++)?(\?[^#]*+)?(#.*)?~', trim($uri, '/'), $matches);
|
||||
$path = Arr::get($matches, 1, '');
|
||||
$query = Arr::get($matches, 2, '');
|
||||
$fragment = Arr::get($matches, 3, '');
|
||||
|
||||
return $path.$query.$fragment;
|
||||
}
|
||||
|
||||
public function bench_regex_without_arrget($uri)
|
||||
{
|
||||
preg_match('~^(?:[-a-z0-9+.]++://[^/]++/?)?([^?#]++)?(\?[^#]*+)?(#.*)?~', trim($uri, '/'), $matches);
|
||||
$path = isset($matches[1]) ? $matches[1] : '';
|
||||
$query = isset($matches[2]) ? $matches[2] : '';
|
||||
$fragment = isset($matches[3]) ? $matches[3] : '';
|
||||
|
||||
return $path.$query.$fragment;
|
||||
}
|
||||
|
||||
// And then I thought, why do all the work of extracting the query and fragment parts and then reappending them?
|
||||
// Just leaving them alone should be fine, right? As a bonus we get a very nice speed boost.
|
||||
public function bench_less_is_more($uri)
|
||||
{
|
||||
// Chop off possible scheme, host, port, user and pass parts
|
||||
$path = preg_replace('~^[-a-z0-9+.]++://[^/]++/?~', '', trim($uri, '/'));
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
public function bench_less_is_more_with_strpos_optimization($uri)
|
||||
{
|
||||
if (strpos($uri, '://') !== FALSE)
|
||||
{
|
||||
// Chop off possible scheme, host, port, user and pass parts
|
||||
$uri = preg_replace('~^[-a-z0-9+.]++://[^/]++/?~', '', trim($uri, '/'));
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* @package Kohana/Codebench
|
||||
* @category Tests
|
||||
* @author Woody Gilk <woody.gilk@kohanaphp.com>
|
||||
*/
|
||||
class Bench_UserFuncArray extends Codebench {
|
||||
|
||||
public $description =
|
||||
'Testing the speed difference of using <code>call_user_func_array</code>
|
||||
compared to counting args and doing manual calls.';
|
||||
|
||||
public $loops = 100000;
|
||||
|
||||
public $subjects = array
|
||||
(
|
||||
// Argument sets
|
||||
array(),
|
||||
array('one'),
|
||||
array('one', 'two'),
|
||||
array('one', 'two', 'three'),
|
||||
);
|
||||
|
||||
public function bench_count_args($args)
|
||||
{
|
||||
$name = 'callme';
|
||||
switch (count($args))
|
||||
{
|
||||
case 1:
|
||||
$this->$name($args[0]);
|
||||
break;
|
||||
case 2:
|
||||
$this->$name($args[0], $args[1]);
|
||||
break;
|
||||
case 3:
|
||||
$this->$name($args[0], $args[1], $args[2]);
|
||||
break;
|
||||
case 4:
|
||||
$this->$name($args[0], $args[1], $args[2], $args[3]);
|
||||
break;
|
||||
default:
|
||||
call_user_func_array(array($this, $name), $args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function bench_direct_call($args)
|
||||
{
|
||||
$name = 'callme';
|
||||
call_user_func_array(array($this, $name), $args);
|
||||
}
|
||||
|
||||
protected function callme()
|
||||
{
|
||||
return count(func_get_args());
|
||||
}
|
||||
|
||||
}
|
116
includes/kohana/modules/codebench/classes/bench/validcolor.php
Normal file
116
includes/kohana/modules/codebench/classes/bench/validcolor.php
Normal file
@@ -0,0 +1,116 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* @package Kohana/Codebench
|
||||
* @category Tests
|
||||
* @author Geert De Deckere <geert@idoe.be>
|
||||
*/
|
||||
class Bench_ValidColor extends Codebench {
|
||||
|
||||
public $description =
|
||||
'Optimization for <code>Validate::color()</code>.
|
||||
See: http://forum.kohanaphp.com/comments.php?DiscussionID=2192.
|
||||
|
||||
Note that the methods with an <em>_invalid</em> suffix contain flawed regexes and should be
|
||||
completely discarded. I left them in here for educational purposes, and to remind myself
|
||||
to think harder and test more thoroughly. It can\'t be that I only found out so late in
|
||||
the game. For the regex explanation have a look at the forum topic mentioned earlier.';
|
||||
|
||||
public $loops = 10000;
|
||||
|
||||
public $subjects = array
|
||||
(
|
||||
// Valid colors
|
||||
'aaA',
|
||||
'123',
|
||||
'000000',
|
||||
'#123456',
|
||||
'#abcdef',
|
||||
|
||||
// Invalid colors
|
||||
'ggg',
|
||||
'1234',
|
||||
'#1234567',
|
||||
"#000\n",
|
||||
'}§è!çà%$z',
|
||||
);
|
||||
|
||||
// Note that I added the D modifier to corey's regexes. We need to match exactly
|
||||
// the same if we want the benchmarks to be of any value.
|
||||
public function bench_corey_regex_1_invalid($subject)
|
||||
{
|
||||
return (bool) preg_match('/^#?([0-9a-f]{1,2}){3}$/iD', $subject);
|
||||
}
|
||||
|
||||
public function bench_corey_regex_2($subject)
|
||||
{
|
||||
return (bool) preg_match('/^#?([0-9a-f]){3}(([0-9a-f]){3})?$/iD', $subject);
|
||||
}
|
||||
|
||||
// Optimized corey_regex_1
|
||||
// Using non-capturing parentheses and a possessive interval
|
||||
public function bench_geert_regex_1a_invalid($subject)
|
||||
{
|
||||
return (bool) preg_match('/^#?(?:[0-9a-f]{1,2}+){3}$/iD', $subject);
|
||||
}
|
||||
|
||||
// Optimized corey_regex_2
|
||||
// Removed useless parentheses, made the remaining ones non-capturing
|
||||
public function bench_geert_regex_2a($subject)
|
||||
{
|
||||
return (bool) preg_match('/^#?[0-9a-f]{3}(?:[0-9a-f]{3})?$/iD', $subject);
|
||||
}
|
||||
|
||||
// Optimized geert_regex_1a
|
||||
// Possessive "#"
|
||||
public function bench_geert_regex_1b_invalid($subject)
|
||||
{
|
||||
return (bool) preg_match('/^#?+(?:[0-9a-f]{1,2}+){3}$/iD', $subject);
|
||||
}
|
||||
|
||||
// Optimized geert_regex_2a
|
||||
// Possessive "#"
|
||||
public function bench_geert_regex_2b($subject)
|
||||
{
|
||||
return (bool) preg_match('/^#?+[0-9a-f]{3}(?:[0-9a-f]{3})?$/iD', $subject);
|
||||
}
|
||||
|
||||
// Using \z instead of $
|
||||
public function bench_salathe_regex_1($subject)
|
||||
{
|
||||
return (bool) preg_match('/^#?+[0-9a-f]{3}(?:[0-9a-f]{3})?\z/i', $subject);
|
||||
}
|
||||
|
||||
// Using \A instead of ^
|
||||
public function bench_salathe_regex_2($subject)
|
||||
{
|
||||
return (bool) preg_match('/\A#?+[0-9a-f]{3}(?:[0-9a-f]{3})?\z/i', $subject);
|
||||
}
|
||||
|
||||
// A solution without regex
|
||||
public function bench_geert_str($subject)
|
||||
{
|
||||
if ($subject[0] === '#')
|
||||
{
|
||||
$subject = substr($subject, 1);
|
||||
}
|
||||
|
||||
$strlen = strlen($subject);
|
||||
return (($strlen === 3 OR $strlen === 6) AND ctype_xdigit($subject));
|
||||
}
|
||||
|
||||
// An ugly, but fast, solution without regex
|
||||
public function bench_salathe_str($subject)
|
||||
{
|
||||
if ($subject[0] === '#')
|
||||
{
|
||||
$subject = substr($subject, 1);
|
||||
}
|
||||
|
||||
// TRUE if:
|
||||
// 1. $subject is 6 or 3 chars long
|
||||
// 2. $subject contains only hexadecimal digits
|
||||
return (((isset($subject[5]) AND ! isset($subject[6])) OR
|
||||
(isset($subject[2]) AND ! isset($subject[3])))
|
||||
AND ctype_xdigit($subject));
|
||||
}
|
||||
}
|
105
includes/kohana/modules/codebench/classes/bench/validurl.php
Normal file
105
includes/kohana/modules/codebench/classes/bench/validurl.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* @package Kohana/Codebench
|
||||
* @category Tests
|
||||
* @author Geert De Deckere <geert@idoe.be>
|
||||
*/
|
||||
class Bench_ValidURL extends Codebench {
|
||||
|
||||
public $description =
|
||||
'filter_var vs regex:
|
||||
http://dev.kohanaframework.org/issues/2847';
|
||||
|
||||
public $loops = 1000;
|
||||
|
||||
public $subjects = array
|
||||
(
|
||||
// Valid
|
||||
'http://google.com',
|
||||
'http://google.com/',
|
||||
'http://google.com/?q=abc',
|
||||
'http://google.com/#hash',
|
||||
'http://localhost',
|
||||
'http://hello-world.pl',
|
||||
'http://hello--world.pl',
|
||||
'http://h.e.l.l.0.pl',
|
||||
'http://server.tld/get/info',
|
||||
'http://127.0.0.1',
|
||||
'http://127.0.0.1:80',
|
||||
'http://user@127.0.0.1',
|
||||
'http://user:pass@127.0.0.1',
|
||||
'ftp://my.server.com',
|
||||
'rss+xml://rss.example.com',
|
||||
|
||||
// Invalid
|
||||
'http://google.2com',
|
||||
'http://google.com?q=abc',
|
||||
'http://google.com#hash',
|
||||
'http://hello-.pl',
|
||||
'http://hel.-lo.world.pl',
|
||||
'http://ww£.google.com',
|
||||
'http://127.0.0.1234',
|
||||
'http://127.0.0.1.1',
|
||||
'http://user:@127.0.0.1',
|
||||
"http://finalnewline.com\n",
|
||||
);
|
||||
|
||||
public function bench_filter_var($url)
|
||||
{
|
||||
return (bool) filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_HOST_REQUIRED);
|
||||
}
|
||||
|
||||
public function bench_regex($url)
|
||||
{
|
||||
// Based on http://www.apps.ietf.org/rfc/rfc1738.html#sec-5
|
||||
if ( ! preg_match(
|
||||
'~^
|
||||
|
||||
# scheme
|
||||
[-a-z0-9+.]++://
|
||||
|
||||
# username:password (optional)
|
||||
(?:
|
||||
[-a-z0-9$_.+!*\'(),;?&=%]++ # username
|
||||
(?::[-a-z0-9$_.+!*\'(),;?&=%]++)? # password (optional)
|
||||
@
|
||||
)?
|
||||
|
||||
(?:
|
||||
# ip address
|
||||
\d{1,3}+(?:\.\d{1,3}+){3}+
|
||||
|
||||
| # or
|
||||
|
||||
# hostname (captured)
|
||||
(
|
||||
(?!-)[-a-z0-9]{1,63}+(?<!-)
|
||||
(?:\.(?!-)[-a-z0-9]{1,63}+(?<!-)){0,126}+
|
||||
)
|
||||
)
|
||||
|
||||
# port (optional)
|
||||
(?::\d{1,5}+)?
|
||||
|
||||
# path (optional)
|
||||
(?:/.*)?
|
||||
|
||||
$~iDx', $url, $matches))
|
||||
return FALSE;
|
||||
|
||||
// We matched an IP address
|
||||
if ( ! isset($matches[1]))
|
||||
return TRUE;
|
||||
|
||||
// Check maximum length of the whole hostname
|
||||
// http://en.wikipedia.org/wiki/Domain_name#cite_note-0
|
||||
if (strlen($matches[1]) > 253)
|
||||
return FALSE;
|
||||
|
||||
// An extra check for the top level domain
|
||||
// It must start with a letter
|
||||
$tld = ltrim(substr($matches[1], (int) strrpos($matches[1], '.')), '.');
|
||||
return ctype_alpha($tld[0]);
|
||||
}
|
||||
|
||||
}
|
3
includes/kohana/modules/codebench/classes/codebench.php
Normal file
3
includes/kohana/modules/codebench/classes/codebench.php
Normal file
@@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
class Codebench extends Kohana_Codebench {}
|
@@ -0,0 +1,32 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* Codebench — A benchmarking module.
|
||||
*
|
||||
* @package Kohana/Codebench
|
||||
* @category Controllers
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2009 Kohana Team
|
||||
* @license http://kohanaphp.com/license.html
|
||||
*/
|
||||
class Controller_Codebench extends Kohana_Controller_Template {
|
||||
|
||||
// The codebench view
|
||||
public $template = 'codebench';
|
||||
|
||||
public function action_index($class)
|
||||
{
|
||||
// Convert submitted class name to URI segment
|
||||
if (isset($_POST['class']))
|
||||
$this->request->redirect('codebench/'.trim($_POST['class']));
|
||||
|
||||
// Pass the class name on to the view
|
||||
$this->template->class = (string) $class;
|
||||
|
||||
// Try to load the class, then run it
|
||||
if (Kohana::auto_load($class) === TRUE)
|
||||
{
|
||||
$codebench = new $class;
|
||||
$this->template->codebench = $codebench->run();
|
||||
}
|
||||
}
|
||||
}
|
217
includes/kohana/modules/codebench/classes/kohana/codebench.php
Normal file
217
includes/kohana/modules/codebench/classes/kohana/codebench.php
Normal file
@@ -0,0 +1,217 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* Codebench — A benchmarking module.
|
||||
*
|
||||
* @package Kohana/Codebench
|
||||
* @category Base
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2009 Kohana Team
|
||||
* @license http://kohanaphp.com/license.html
|
||||
*/
|
||||
abstract class Kohana_Codebench {
|
||||
|
||||
/**
|
||||
* @var string Some optional explanatory comments about the benchmark file.
|
||||
* HTML allowed. URLs will be converted to links automatically.
|
||||
*/
|
||||
public $description = '';
|
||||
|
||||
/**
|
||||
* @var integer How many times to execute each method per subject.
|
||||
*/
|
||||
public $loops = 1000;
|
||||
|
||||
/**
|
||||
* @var array The subjects to supply iteratively to your benchmark methods.
|
||||
*/
|
||||
public $subjects = array();
|
||||
|
||||
/**
|
||||
* @var array Grade letters with their maximum scores. Used to color the graphs.
|
||||
*/
|
||||
public $grades = array
|
||||
(
|
||||
125 => 'A',
|
||||
150 => 'B',
|
||||
200 => 'C',
|
||||
300 => 'D',
|
||||
500 => 'E',
|
||||
'default' => 'F',
|
||||
);
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// Set the maximum execution time
|
||||
set_time_limit(Kohana::config('codebench')->max_execution_time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs Codebench on the extending class.
|
||||
*
|
||||
* @return array benchmark output
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
// Array of all methods to loop over
|
||||
$methods = array_filter(get_class_methods($this), array($this, '_method_filter'));
|
||||
|
||||
// Make sure the benchmark runs at least once,
|
||||
// also if no subject data has been provided.
|
||||
if (empty($this->subjects))
|
||||
{
|
||||
$this->subjects = array('NULL' => NULL);
|
||||
}
|
||||
|
||||
// Initialize benchmark output
|
||||
$codebench = array
|
||||
(
|
||||
'class' => get_class($this),
|
||||
'description' => $this->description,
|
||||
'loops' => array
|
||||
(
|
||||
'base' => (int) $this->loops,
|
||||
'total' => (int) $this->loops * count($this->subjects) * count($methods),
|
||||
),
|
||||
'subjects' => $this->subjects,
|
||||
'benchmarks' => array(),
|
||||
);
|
||||
|
||||
// Benchmark each method
|
||||
foreach ($methods as $method)
|
||||
{
|
||||
// Initialize benchmark output for this method
|
||||
$codebench['benchmarks'][$method] = array('time' => 0, 'memory' => 0);
|
||||
|
||||
// Using Reflection because simply calling $this->$method($subject) in the loop below
|
||||
// results in buggy benchmark times correlating to the length of the method name.
|
||||
$reflection = new ReflectionMethod(get_class($this), $method);
|
||||
|
||||
// Benchmark each subject on each method
|
||||
foreach ($this->subjects as $subject_key => $subject)
|
||||
{
|
||||
// Prerun each method/subject combo before the actual benchmark loop.
|
||||
// This way relatively expensive initial processes won't be benchmarked, e.g. autoloading.
|
||||
// At the same time we capture the return here so we don't have to do that in the loop anymore.
|
||||
$return = $reflection->invoke($this, $subject);
|
||||
|
||||
// Start the timer for one subject
|
||||
$token = Profiler::start('codebench', $method.$subject_key);
|
||||
|
||||
// The heavy work
|
||||
for ($i = 0; $i < $this->loops; ++$i)
|
||||
{
|
||||
$reflection->invoke($this, $subject);
|
||||
}
|
||||
|
||||
// Stop and read the timer
|
||||
$benchmark = Profiler::total($token);
|
||||
|
||||
// Benchmark output specific to the current method and subject
|
||||
$codebench['benchmarks'][$method]['subjects'][$subject_key] = array
|
||||
(
|
||||
'return' => $return,
|
||||
'time' => $benchmark[0],
|
||||
'memory' => $benchmark[1],
|
||||
);
|
||||
|
||||
// Update method totals
|
||||
$codebench['benchmarks'][$method]['time'] += $benchmark[0];
|
||||
$codebench['benchmarks'][$method]['memory'] += $benchmark[1];
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the fastest and slowest benchmarks for both methods and subjects, time and memory,
|
||||
// these values will be overwritten using min() and max() later on.
|
||||
// The 999999999 values look like a hack, I know, but they work,
|
||||
// unless your method runs for more than 31 years or consumes over 1GB of memory.
|
||||
$fastest_method = $fastest_subject = array('time' => 999999999, 'memory' => 999999999);
|
||||
$slowest_method = $slowest_subject = array('time' => 0, 'memory' => 0);
|
||||
|
||||
// Find the fastest and slowest benchmarks, needed for the percentage calculations
|
||||
foreach ($methods as $method)
|
||||
{
|
||||
// Update the fastest and slowest method benchmarks
|
||||
$fastest_method['time'] = min($fastest_method['time'], $codebench['benchmarks'][$method]['time']);
|
||||
$fastest_method['memory'] = min($fastest_method['memory'], $codebench['benchmarks'][$method]['memory']);
|
||||
$slowest_method['time'] = max($slowest_method['time'], $codebench['benchmarks'][$method]['time']);
|
||||
$slowest_method['memory'] = max($slowest_method['memory'], $codebench['benchmarks'][$method]['memory']);
|
||||
|
||||
foreach ($this->subjects as $subject_key => $subject)
|
||||
{
|
||||
// Update the fastest and slowest subject benchmarks
|
||||
$fastest_subject['time'] = min($fastest_subject['time'], $codebench['benchmarks'][$method]['subjects'][$subject_key]['time']);
|
||||
$fastest_subject['memory'] = min($fastest_subject['memory'], $codebench['benchmarks'][$method]['subjects'][$subject_key]['memory']);
|
||||
$slowest_subject['time'] = max($slowest_subject['time'], $codebench['benchmarks'][$method]['subjects'][$subject_key]['time']);
|
||||
$slowest_subject['memory'] = max($slowest_subject['memory'], $codebench['benchmarks'][$method]['subjects'][$subject_key]['memory']);
|
||||
}
|
||||
}
|
||||
|
||||
// Percentage calculations for methods
|
||||
foreach ($codebench['benchmarks'] as & $method)
|
||||
{
|
||||
// Calculate percentage difference relative to fastest and slowest methods
|
||||
$method['percent']['fastest']['time'] = (empty($fastest_method['time'])) ? 0 : $method['time'] / $fastest_method['time'] * 100;
|
||||
$method['percent']['fastest']['memory'] = (empty($fastest_method['memory'])) ? 0 : $method['memory'] / $fastest_method['memory'] * 100;
|
||||
$method['percent']['slowest']['time'] = (empty($slowest_method['time'])) ? 0 : $method['time'] / $slowest_method['time'] * 100;
|
||||
$method['percent']['slowest']['memory'] = (empty($slowest_method['memory'])) ? 0 : $method['memory'] / $slowest_method['memory'] * 100;
|
||||
|
||||
// Assign a grade for time and memory to each method
|
||||
$method['grade']['time'] = $this->_grade($method['percent']['fastest']['time']);
|
||||
$method['grade']['memory'] = $this->_grade($method['percent']['fastest']['memory']);
|
||||
|
||||
// Percentage calculations for subjects
|
||||
foreach ($method['subjects'] as & $subject)
|
||||
{
|
||||
// Calculate percentage difference relative to fastest and slowest subjects for this method
|
||||
$subject['percent']['fastest']['time'] = (empty($fastest_subject['time'])) ? 0 : $subject['time'] / $fastest_subject['time'] * 100;
|
||||
$subject['percent']['fastest']['memory'] = (empty($fastest_subject['memory'])) ? 0 : $subject['memory'] / $fastest_subject['memory'] * 100;
|
||||
$subject['percent']['slowest']['time'] = (empty($slowest_subject['time'])) ? 0 : $subject['time'] / $slowest_subject['time'] * 100;
|
||||
$subject['percent']['slowest']['memory'] = (empty($slowest_subject['memory'])) ? 0 : $subject['memory'] / $slowest_subject['memory'] * 100;
|
||||
|
||||
// Assign a grade letter for time and memory to each subject
|
||||
$subject['grade']['time'] = $this->_grade($subject['percent']['fastest']['time']);
|
||||
$subject['grade']['memory'] = $this->_grade($subject['percent']['fastest']['memory']);
|
||||
}
|
||||
}
|
||||
|
||||
return $codebench;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for array_filter().
|
||||
* Filters out all methods not to benchmark.
|
||||
*
|
||||
* @param string method name
|
||||
* @return boolean
|
||||
*/
|
||||
protected function _method_filter($method)
|
||||
{
|
||||
// Only benchmark methods with the "bench" prefix
|
||||
return (substr($method, 0, 5) === 'bench');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the applicable grade letter for a score.
|
||||
*
|
||||
* @param integer|double score
|
||||
* @return string grade letter
|
||||
*/
|
||||
protected function _grade($score)
|
||||
{
|
||||
foreach ($this->grades as $max => $grade)
|
||||
{
|
||||
if ($max === 'default')
|
||||
continue;
|
||||
|
||||
if ($score <= $max)
|
||||
return $grade;
|
||||
}
|
||||
|
||||
return $this->grades['default'];
|
||||
}
|
||||
}
|
16
includes/kohana/modules/codebench/config/codebench.php
Normal file
16
includes/kohana/modules/codebench/config/codebench.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
|
||||
return array(
|
||||
|
||||
/**
|
||||
* The maximum execution time, in seconds. If set to zero, no time limit is imposed.
|
||||
* Note: http://php.net/manual/en/function.set-time-limit.php#84563
|
||||
*/
|
||||
'max_execution_time' => 0,
|
||||
|
||||
/**
|
||||
* Expand all benchmark details by default.
|
||||
*/
|
||||
'expand_all' => FALSE,
|
||||
|
||||
);
|
8
includes/kohana/modules/codebench/init.php
Normal file
8
includes/kohana/modules/codebench/init.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
// Catch-all route for Codebench classes to run
|
||||
Route::set('codebench', 'codebench(/<class>)')
|
||||
->defaults(array(
|
||||
'controller' => 'codebench',
|
||||
'action' => 'index',
|
||||
'class' => NULL));
|
258
includes/kohana/modules/codebench/views/codebench.php
Normal file
258
includes/kohana/modules/codebench/views/codebench.php
Normal file
@@ -0,0 +1,258 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* Codebench — A benchmarking module.
|
||||
*
|
||||
* @package Kohana
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2009 Kohana Team
|
||||
* @license http://kohanaphp.com/license.html
|
||||
*/
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
|
||||
<meta charset="utf-8" />
|
||||
<title><?php if ($class !== '') echo $class, ' · ' ?>Codebench</title>
|
||||
|
||||
<style>
|
||||
/* General styles*/
|
||||
body { position:relative; margin:1em 2em; font:12px monaco,monospace; }
|
||||
h1 { font-size:24px; letter-spacing:-0.05em; }
|
||||
h2 { font-size:18px; letter-spacing:-0.1em; }
|
||||
input, code { font:inherit; }
|
||||
code { background:#e5e5e5; }
|
||||
caption { display:none; }
|
||||
|
||||
/* Form */
|
||||
#runner { margin-bottom:2em; }
|
||||
#runner input[type="text"] { letter-spacing:-0.05em; }
|
||||
|
||||
/* Expand/Collapse all */
|
||||
#toggle_all { position:absolute; top:0; right:0; margin:0; padding:0 4px; background:#000; font-size:18px; color:#fff; cursor:pointer; -moz-border-radius:2px; -webkit-border-radius:2px; }
|
||||
|
||||
/* Benchmark main graphs */
|
||||
#bench { margin:2em 0; padding:0; list-style:none; }
|
||||
#bench > li { margin:6px 0; }
|
||||
#bench h2 { position:relative; margin:0; padding:2px; background:#ccc; border:1px solid #999; cursor:pointer; -moz-border-radius:3px; -webkit-border-radius:3px; }
|
||||
#bench h2 > span { display:block; min-width:1px; height:33px; background:#fff; -moz-border-radius:2px; -webkit-border-radius:2px; }
|
||||
#bench h2 .method { position:absolute; top:6px; left:8px; text-shadow:0 -1px 0 rgba(255,255,255,0.6); }
|
||||
#bench h2 .method:before { content:'▸ '; }
|
||||
#bench h2 .percent { position:absolute; top:6px; right:6px; padding:0 4px; background:#000; color:#fff; font-weight:normal; letter-spacing:0; -moz-border-radius:2px; -webkit-border-radius:2px; }
|
||||
#bench h2:hover .method { left:10px; }
|
||||
#bench h2.expanded { margin:12px 0 0; -moz-border-radius-bottomleft:0; -moz-border-radius-bottomright:0; -webkit-border-bottom-left-radius:0; -webkit-border-bottom-right-radius:0; }
|
||||
#bench h2.expanded .method:before { content:'▾ '; }
|
||||
|
||||
/* Colorization of the bars */
|
||||
#bench .grade-A { background:#3f0; }
|
||||
#bench .grade-B { background:#fc0; }
|
||||
#bench .grade-C { background:#f90; }
|
||||
#bench .grade-D { background:#f60; }
|
||||
#bench .grade-E { background:#f30; }
|
||||
#bench .grade-F { background:#f00; }
|
||||
|
||||
/* Benchmark details */
|
||||
#bench > li > div { display:none; margin:0 0 12px; padding:0 0 2px; background:#eee; border:1px solid #999; border-top:0; -moz-border-radius-bottomleft:3px; -moz-border-radius-bottomright:3px; -webkit-border-bottom-left-radius:3px; -webkit-border-bottom-right-radius:3px; }
|
||||
#bench > li > div table { width:100%; background:#eee; border-collapse:collapse; }
|
||||
#bench > li > div th { padding:6px; background:#ddd url(data:image/gif;base64,R0lGODlhAQASALMAAMfHx8TExM7Oztvb29jY2NbW1tPT09DQ0MrKygAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAAAAAAALAAAAAABABIAAAQNMAQAEBLiHGNKIcQwRAA7) repeat-x 0 1px; text-align:left; }
|
||||
#bench > li > div td { padding:6px; border-top:1px solid #ccc; vertical-align:top; }
|
||||
#bench .numeric { padding-left:18px; text-align:right; }
|
||||
#bench .numeric span { position:relative; display:block; height:16px; }
|
||||
#bench .numeric span span { position:absolute; top:0; right:0; min-width:1px; background:#ccc; -moz-border-radius:2px; -webkit-border-radius:2px; }
|
||||
#bench .numeric span span span { top:0; right:0; background:none; }
|
||||
#bench tbody tr:hover { background:#fff; }
|
||||
#bench tbody tr.highlight { background:#ffc; }
|
||||
|
||||
/* Footer */
|
||||
#footer { margin-top:2em; padding-top:1em; border-top:1px solid #ccc; color:#999; }
|
||||
#footer a { color:inherit; }
|
||||
|
||||
/* Misc text styles */
|
||||
.alert { padding:0 0.5em; background:#900; font-weight:normal; color:#fff; -moz-border-radius:3px; -webkit-border-radius:3px; }
|
||||
.quiet { color:#999; }
|
||||
.help { cursor:help; }
|
||||
</style>
|
||||
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// Insert "Toggle All" button
|
||||
var expand_all_text = '▸ Expand all';
|
||||
var collapse_all_text = '▾ Collapse all';
|
||||
$('#bench').before('<p id="toggle_all">'+expand_all_text+'</p>');
|
||||
|
||||
// Cache these selection operations
|
||||
var $runner = $('#runner');
|
||||
var $toggle_all = $('#toggle_all');
|
||||
var $bench_titles = $('#bench > li > h2');
|
||||
var $bench_rows = $('#bench > li > div > table > tbody > tr');
|
||||
|
||||
// Runner form
|
||||
$(':input:first', $runner).focus();
|
||||
$runner.submit(function() {
|
||||
$(':submit', this).attr('value', 'Running…').attr('disabled', 'disabled');
|
||||
$('.alert', this).remove();
|
||||
});
|
||||
|
||||
// Toggle details for all benchmarks
|
||||
$('#toggle_all').click(function() {
|
||||
if ($(this).data('expanded')) {
|
||||
$(this).data('expanded', false);
|
||||
$(this).text(expand_all_text);
|
||||
$bench_titles.removeClass('expanded').siblings().hide();
|
||||
}
|
||||
else {
|
||||
$(this).data('expanded', true);
|
||||
$(this).text(collapse_all_text);
|
||||
$bench_titles.addClass('expanded').siblings().show();
|
||||
}
|
||||
});
|
||||
|
||||
<?php if (Kohana::config('codebench')->expand_all) { ?>
|
||||
// Expand all benchmark details by default
|
||||
$toggle_all.click();
|
||||
<?php } ?>
|
||||
|
||||
// Toggle details for a single benchmark
|
||||
$bench_titles.click(function() {
|
||||
$(this).toggleClass('expanded').siblings().toggle();
|
||||
|
||||
// Counts of bench titles
|
||||
var total_bench_titles = $bench_titles.length;
|
||||
var expanded_bench_titles = $bench_titles.filter('.expanded').length;
|
||||
|
||||
// If no benchmark details are expanded, change "Collapse all" to "Expand all"
|
||||
if (expanded_bench_titles == 0 && $toggle_all.data('expanded')) {
|
||||
$toggle_all.click();
|
||||
}
|
||||
// If all benchmark details are expanded, change "Expand all" to "Collapse all"
|
||||
else if (expanded_bench_titles == total_bench_titles && ! $toggle_all.data('expanded')) {
|
||||
$toggle_all.click();
|
||||
}
|
||||
});
|
||||
|
||||
// Highlight clicked rows
|
||||
$bench_rows.click(function() {
|
||||
$(this).toggleClass('highlight');
|
||||
// Highlight doubleclicked rows globally
|
||||
}).dblclick(function() {
|
||||
var nth_row = $(this).parent().children().index(this) + 1;
|
||||
if ($(this).hasClass('highlight')) {
|
||||
$bench_rows.filter(':nth-child('+nth_row+')').removeClass('highlight');
|
||||
}
|
||||
else {
|
||||
$bench_rows.filter(':nth-child('+nth_row+')').addClass('highlight');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!--[if IE]><p class="alert">This page is not meant to be viewed in Internet Explorer. Get a better browser.</p><![endif]-->
|
||||
|
||||
<form id="runner" method="post" action="<?php echo URL::site('codebench') ?>">
|
||||
<h1>
|
||||
<input name="class" type="text" value="<?php echo ($class !== '') ? $class : 'Bench_' ?>" size="25" title="Name of the Codebench library to run" />
|
||||
<input type="submit" value="Run" />
|
||||
<?php if ( ! empty($class)) { ?>
|
||||
<?php if (empty($codebench)) { ?>
|
||||
<strong class="alert">Library not found</strong>
|
||||
<?php } elseif (empty($codebench['benchmarks'])) { ?>
|
||||
<strong class="alert">No methods found to benchmark</strong>
|
||||
<?php } ?>
|
||||
<?php } ?>
|
||||
</h1>
|
||||
</form>
|
||||
|
||||
<?php if ( ! empty($codebench)) { ?>
|
||||
|
||||
<?php if (empty($codebench['benchmarks'])) { ?>
|
||||
|
||||
<p>
|
||||
<strong>
|
||||
Remember to prefix the methods you want to benchmark with “bench”.<br />
|
||||
You might also want to overwrite <code>Codebench->method_filter()</code>.
|
||||
</strong>
|
||||
</p>
|
||||
|
||||
<?php } else { ?>
|
||||
|
||||
<ul id="bench">
|
||||
<?php foreach ($codebench['benchmarks'] as $method => $benchmark) { ?>
|
||||
<li>
|
||||
|
||||
<h2 title="<?php printf('%01.6f', $benchmark['time']) ?>s">
|
||||
<span class="grade-<?php echo $benchmark['grade']['time'] ?>" style="width:<?php echo $benchmark['percent']['slowest']['time'] ?>%">
|
||||
<span class="method"><?php echo $method ?></span>
|
||||
<span class="percent">+<?php echo (int) $benchmark['percent']['fastest']['time'] ?>%</span>
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
<div>
|
||||
<table>
|
||||
<caption>Benchmarks per subject for <?php echo $method ?></caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:50%">subject → return</th>
|
||||
<th class="numeric" style="width:25%" title="Total method memory"><?php echo Text::bytes($benchmark['memory'], 'MB', '%01.6f%s') ?></th>
|
||||
<th class="numeric" style="width:25%" title="Total method time"><?php printf('%01.6f', $benchmark['time']) ?>s</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
<?php foreach ($benchmark['subjects'] as $subject_key => $subject) { ?>
|
||||
<tr>
|
||||
<td>
|
||||
<strong class="help" title="(<?php echo gettype($codebench['subjects'][$subject_key]) ?>) <?php echo HTML::chars(var_export($codebench['subjects'][$subject_key], TRUE)) ?>">
|
||||
[<?php echo HTML::chars($subject_key) ?>] →
|
||||
</strong>
|
||||
<span class="quiet">(<?php echo gettype($subject['return']) ?>)</span>
|
||||
<?php echo HTML::chars(var_export($subject['return'], TRUE)) ?>
|
||||
</td>
|
||||
<td class="numeric">
|
||||
<span title="+<?php echo (int) $subject['percent']['fastest']['memory'] ?>% memory">
|
||||
<span style="width:<?php echo $subject['percent']['slowest']['memory'] ?>%">
|
||||
<span><?php echo Text::bytes($subject['memory'], 'MB', '%01.6f%s') ?></span>
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
<td class="numeric">
|
||||
<span title="+<?php echo (int) $subject['percent']['fastest']['time'] ?>% time">
|
||||
<span style="width:<?php echo $subject['percent']['slowest']['time'] ?>%">
|
||||
<span><?php printf('%01.6f', $subject['time']) ?>s</span>
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</li>
|
||||
<?php } ?>
|
||||
</ul>
|
||||
|
||||
<?php } ?>
|
||||
|
||||
<?php if ( ! empty($codebench['description'])) { ?>
|
||||
<?php echo Text::auto_p(Text::auto_link($codebench['description']), FALSE) ?>
|
||||
<?php } ?>
|
||||
|
||||
<?php // echo '<h2>Raw output:</h2>', Kohana::debug($codebench) ?>
|
||||
|
||||
<?php } ?>
|
||||
|
||||
<p id="footer">
|
||||
Page executed in <strong><?php echo round(microtime(TRUE) - KOHANA_START_TIME, 2) ?> s</strong>
|
||||
using <strong><?php echo Text::widont(Text::bytes(memory_get_usage(), 'MB')) ?></strong> of memory.<br />
|
||||
<a href="http://github.com/kohana/codebench">Codebench</a>, a <a href="http://kohanaframework.org/">Kohana</a> module
|
||||
by <a href="http://www.geertdedeckere.be/article/introducing-codebench">Geert De Deckere</a>.
|
||||
</p>
|
||||
|
||||
</body>
|
||||
</html>
|
Reference in New Issue
Block a user