Seb35 has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/309548

Change subject: Added a performance test
......................................................................

Added a performance test

Code:
* Changed private properties/methods to protected
* Optimised a bit arrayMerge

Bugs:
* Fixed a bug in the declaration of a hook in MW < 1.25

Performance tests:
* Added a subdirectory tests/perfs
* Added a subclass MediaWikiFarmTestPerfs which inherits
  MediaWikiFarm with some counters on some methods
* Derived index.php from www/index.php with a switch between
  farm installation and classic installation to test performance
  in both cases
* These web entry points are only accessible on localhost, and this
  index.php must be called instead of www/index.php when testing perfs
* The main program testperfs.php is only accessible via CLI and is
  responsible of calling X times the web entry point and of computing
  resulting means
* To simulate the classic installation, a LocalSettings.php with exact
  same parameters as the farm is created

Performance next steps:
* Given the strategy used in MediaWikiFarm for configuration (loading
  global settings, then global arrays, then wfLoadSkin, then require_once)
  is proven to be slower than LocalSettings.php (90 to 110µs added, out
  of the 239µs added by MediaWikiFarm), the next step is to create a
  LocalSettings.php in cache directory -- reusing code written here.

Change-Id: I74737330cb808d5a2e6897babe0cab94a81a886d
---
M .gitignore
M src/MediaWikiFarm.php
A tests/perfs/MediaWikiFarmTestPerfs.php
A tests/perfs/index.php
A tests/perfs/main.php
A tests/perfs/perfs.php
6 files changed, 391 insertions(+), 17 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/MediaWikiFarm 
refs/changes/48/309548/1

diff --git a/.gitignore b/.gitignore
index be65261..67b3c9f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,4 @@
 /tests/phpunit/data/config/varwikiversions.php
 /tests/phpunit/data/config/deployments.php
 /tests/phpunit/data/config/testdeploymentsfarmversions.php
+/tests/perfs/results
diff --git a/src/MediaWikiFarm.php b/src/MediaWikiFarm.php
index 7f2223d..7c12a71 100644
--- a/src/MediaWikiFarm.php
+++ b/src/MediaWikiFarm.php
@@ -37,25 +37,25 @@
         * ---------- */
 
        /** @var string Entry point script. */
-       private $entryPoint = '';
+       protected $entryPoint = '';
 
        /** @var string Farm code directory. */
-       private $farmDir = '';
+       protected $farmDir = '';
 
        /** @var string Farm configuration directory. */
-       private $configDir = '';
+       protected $configDir = '';
 
        /** @var string|null MediaWiki code directory, where each subdirectory 
is a MediaWiki installation. */
-       private $codeDir = null;
+       protected $codeDir = null;
 
        /** @var string|false MediaWiki cache directory. */
-       private $cacheDir = '/tmp/mw-cache';
+       protected $cacheDir = '/tmp/mw-cache';
 
        /** @var array Configuration for this farm. */
-       private $farmConfig = array();
+       protected $farmConfig = array();
 
        /** @var string[] Variables related to the current request. */
-       private $variables = array(
+       protected $variables = array(
                '$SERVER' => '',
                '$SUFFIX' => '',
                '$WIKIID' => '',
@@ -64,7 +64,7 @@
        );
 
        /** @var array Configuration parameters for this wiki. */
-       private $configuration = array(
+       protected $configuration = array(
                'general' => array(),
                'settings' => array(),
                'arrays' => array(),
@@ -74,7 +74,7 @@
        );
 
        /** @var array Errors */
-       private $errors = array();
+       protected $errors = array();
 
 
 
@@ -219,7 +219,7 @@
 
                try {
                        # Initialise object
-                       $wgMediaWikiFarm = new self( $host, 
$wgMediaWikiFarmConfigDir, $wgMediaWikiFarmCodeDir, $wgMediaWikiFarmCacheDir, 
$entryPoint );
+                       $wgMediaWikiFarm = new static( $host, 
$wgMediaWikiFarmConfigDir, $wgMediaWikiFarmCodeDir, $wgMediaWikiFarmCacheDir, 
$entryPoint );
 
                        # Check existence
                        $exists = $wgMediaWikiFarm->checkExistence();
@@ -395,7 +395,7 @@
                        $GLOBALS['wgAutoloadClasses']['MediaWikiFarm'] = 
'src/MediaWikiFarm.php';
                        
$GLOBALS['wgAutoloadClasses']['MWFConfigurationException'] = 
'src/MediaWikiFarm.php';
                        $GLOBALS['wgMessagesDirs']['MediaWikiFarm'] = array( 
'i18n' );
-                       $GLOBALS['wgHooks']['UnitTestsList'] = array( 
'MediaWikiFarm::onUnitTestsList' );
+                       $GLOBALS['wgHooks']['UnitTestsList'][] = array( 
'MediaWikiFarm::onUnitTestsList' );
                        // @codeCoverageIgnoreEnd
                }
 
@@ -792,7 +792,7 @@
         * @param string $version The new version, should be the version found 
in the 'expected version' file.
         * @return void
         */
-       private function updateVersion( $version ) {
+       protected function updateVersion( $version ) {
 
                # Check a deployment file is wanted
                if( !array_key_exists( '$DEPLOYMENTS', $this->variables ) )
@@ -1514,7 +1514,7 @@
         * @param string|null $directory Name of the parent directory; null for 
default cache directory
         * @return void
         */
-       private function cacheFile( $array, $filename, $directory = null ) {
+       protected function cacheFile( $array, $filename, $directory = null ) {
 
                if( is_null( $directory ) )
                        $directory = $this->cacheDir;
@@ -1568,13 +1568,16 @@
         *
         * @return array
         */
-       static function arrayMerge( /* ... */ ) {
-               $out = array();
+       static function arrayMerge( $array1 /* ... */ ) {
+               $out = $array1;
+               if ( is_null( $out ) ) {
+                       $out = array();
+               }
                $argsCount = func_num_args();
-               for ( $i = 0; $i < $argsCount; $i++ ) {
+               for ( $i = 1; $i < $argsCount; $i++ ) {
                        $array = func_get_arg( $i );
                        if ( is_null( $array ) ) {
-                               $array = array();
+                               continue;
                        }
                        foreach ( $array as $key => $value ) {
                                if( array_key_exists( $key, $out ) && 
is_string( $key ) && is_array( $out[$key] ) && is_array( $value ) ) {
diff --git a/tests/perfs/MediaWikiFarmTestPerfs.php 
b/tests/perfs/MediaWikiFarmTestPerfs.php
new file mode 100644
index 0000000..e89a7ba
--- /dev/null
+++ b/tests/perfs/MediaWikiFarmTestPerfs.php
@@ -0,0 +1,219 @@
+<?php
+
+if( $_SERVER['REMOTE_ADDR'] != '127.0.0.1' && $_SERVER['REMOTE_ADDR'] != '::1' 
) {
+       exit;
+}
+
+class MediaWikiFarmTestPerfs extends MediaWikiFarm {
+
+       /** @var float Beginning of time count. */
+       protected static $time0 = array();
+
+       /** @var float Resulting counters. */
+       protected static $counters = array();
+
+       /** @var float Entry point (bis). */
+       protected static $entryPoint2 = '';
+
+       /** @var float Entry point (bis). */
+       protected static $profile = 0;
+
+       /**
+        * To do A/B test with and without farm, this returns 0 or 1.
+        *
+        * It should return alternatively 0 and 1, the value is stored in a 
file.
+        *
+        * @param string $entryPoint The entry point we want the profile, e.g. 
'index.php'.
+        * @return int The profile, either 0 or 1.
+        */
+       static function getEntryPointProfile( $entryPoint ) {
+
+               if( !is_dir( dirname( __FILE__ ) . '/results' ) ) {
+                       mkdir( dirname( __FILE__ ) . '/results' );
+               }
+               if( !is_file( dirname( __FILE__ ) . 
"/results/profile-$entryPoint.php" ) ) {
+                       file_put_contents( dirname( __FILE__ ) . 
"/results/profile-$entryPoint.php", "<?php return 0;\n" );
+               }
+
+               self::$entryPoint2 = $entryPoint;
+               self::$profile = include dirname( __FILE__ ) . 
"/results/profile-$entryPoint.php";
+
+               $profile = (self::$profile+1)%2;
+               file_put_contents( dirname( __FILE__ ) . 
"/results/profile-$entryPoint.php", "<?php return $profile;\n" );
+
+               return self::$profile;
+       }
+
+       /**
+        * Start the counter.
+        *
+        * @param string $name Name of the counter.
+        * @return void.
+        */
+       static function startCounter( $name ) {
+
+               self::$time0[$name] = microtime( true );
+       }
+
+       /**
+        * Stop the counter.
+        *
+        * @param string $name Name of the counter.
+        * @return void.
+        */
+       static function stopCounter( $name ) {
+
+               $time = microtime( true );
+               self::$counters[$name] = $time - self::$time0[$name];
+       }
+
+       /**
+        * Write down results and select the next profile.
+        *
+        * @return void.
+        */
+       static function writeResults() {
+
+               global $IP;
+
+               $entryPoint = self::$entryPoint2;
+
+               if( !is_file( dirname( __FILE__ ) . 
"/results/measures-$entryPoint.php" ) ) {
+                       file_put_contents( dirname( __FILE__ ) . 
"/results/measures-$entryPoint.php", "<?php return array( 0 => array(), 1 => 
array() );\n" );
+               }
+
+               # Load existing state
+               $profile = self::$profile;
+               $measures = include dirname( __FILE__ ) . 
"/results/measures-$entryPoint.php";
+
+               # Update with current measure
+               $measures[$profile][] = self::$counters;
+
+               # Write results
+               file_put_contents( dirname( __FILE__ ) . 
"/results/measures-$entryPoint.php", '<?php return ' . var_export( $measures, 
true ) . ";\n" );
+
+               if( !is_file( dirname( __FILE__ ) . '/results/metadata.php' ) 
&& $profile == 0 ) {
+                       $server = $GLOBALS['wgMediaWikiFarm']->getVariable( 
'$SERVER' );
+                       file_put_contents( dirname( __FILE__ ) . 
'/results/metadata.php', "<?php return array( 'IP' => '$IP', 'server' => 
'$server' );\n" );
+
+                       $localSettings = "<?php\n";
+                       $localSettings .= "\n# Start 
counter\nMediaWikiFarmTestPerfs::startCounter( 'config' );\n";
+                       $localSettings .= "\n# General settings\n";
+                       foreach( $GLOBALS['wgMediaWikiFarm']->getConfiguration( 
'settings' ) as $setting => $value ) {
+                               $localSettings .= "\$$setting = " . var_export( 
$value, true ) . ";\n";
+                       }
+                       foreach( $GLOBALS['wgMediaWikiFarm']->getConfiguration( 
'arrays' ) as $setting => $value ) {
+                               $localSettings .= self::writeArrayAssignment( 
$value, "\$$setting" );
+                       }
+                       $localSettings .= "\n# Skins\n";
+                       foreach( $GLOBALS['wgMediaWikiFarm']->getConfiguration( 
'skins' ) as $skin => $loading ) {
+                               if( $loading == 'wfLoadSkin' ) {
+                                       $localSettings .= "wfLoadSkin( '$skin' 
);\n";
+                               }
+                               elseif( $loading == 'require_once' ) {
+                                       $localSettings .= "require_once 
\"\$IP/skins/$skin/$skin.php\";\n";
+                               }
+                       }
+                       $localSettings .= "\n# Extensions\n";
+                       foreach( $GLOBALS['wgMediaWikiFarm']->getConfiguration( 
'extensions' ) as $extension => $loading ) {
+                               if( $loading == 'wfLoadExtension' ) {
+                                       $localSettings .= "wfLoadExtension( 
'$extension' );\n";
+                               }
+                               elseif( $loading == 'require_once' ) {
+                                       $localSettings .= "require_once 
\"\$IP/extensions/$extension/$extension.php\";\n";
+                               }
+                       }
+                       $localSettings .= "\n# Included files\n";
+                       foreach( $GLOBALS['wgMediaWikiFarm']->getConfiguration( 
'execFiles' ) as $execFile ) {
+                               $localSettings .= "include '$execFile';\n";
+                       }
+                       $localSettings .= "\n# Stop 
counter\nMediaWikiFarmTestPerfs::stopCounter( 'config' 
);\nMediaWikiFarmTestPerfs::writeResults();\n";
+                       file_put_contents( dirname( __FILE__ ) . 
'/results/LocalSettings.php', $localSettings );
+
+                       #if( !is_file( $IP . '/LocalSettings.php' ) ) {
+                       copy( dirname( __FILE__ ) . 
'/results/LocalSettings.php', $IP . '/LocalSettings.php' );
+                       #}
+               }
+       }
+
+       static function writeArrayAssignment( $array, $prefix ) {
+
+               $result = '';
+               $isList = (count( array_diff( array_keys( $array ), range( 0, 
count( $array ) ) ) ) == 0);
+               foreach( $array as $key => $value ) {
+                       $newkey = '[' . var_export( $key, true ) . ']';
+                       if( $isList ) {
+                               $result .= $prefix . '[] = ' . var_export( 
$value, true ) . ";\n";
+                       } elseif( is_array( $value ) ) {
+                               $result .= self::writeArrayAssignment( $value, 
$prefix . $newkey );
+                       } else {
+                               $result .= $prefix . $newkey . ' = ' . 
var_export( $value, true ) . ";\n";
+                       }
+               }
+
+               return $result;
+       }
+
+
+
+       /*
+        * Overloaded methods
+        * ------------------ */
+
+       /**
+        * Return the file where must be loaded the configuration from.
+        *
+        * This function returns a file which starts and stops a counter and
+        * launch the original file.
+        *
+        * @mediawikifarm-const
+        * @mediawikifarm-idempotent
+        *
+        * @return string File where is loaded the configuration.
+        */
+       function getConfigFile() {
+
+               return $this->farmDir . '/tests/perfs/main.php';
+       }
+
+       function loadMediaWikiConfig() {
+
+               MediaWikiFarmTestPerfs::startCounter( 'compilation' );
+
+               parent::loadMediaWikiConfig();
+
+               MediaWikiFarmTestPerfs::stopCounter( 'compilation' );
+               MediaWikiFarmTestPerfs::startCounter( 
'loading-require_once-skins' );
+       }
+
+       function loadSkinsConfig() {
+
+               MediaWikiFarmTestPerfs::stopCounter( 
'loading-require_once-skins' );
+               MediaWikiFarmTestPerfs::startCounter( 'loading-wfLoadSkins' );
+
+               parent::loadSkinsConfig();
+
+               MediaWikiFarmTestPerfs::stopCounter( 'loading-wfLoadSkins' );
+               MediaWikiFarmTestPerfs::startCounter( 
'loading-require_once-extensions' );
+       }
+
+       function loadExtensionsConfig() {
+
+               MediaWikiFarmTestPerfs::stopCounter( 
'loading-require_once-extensions' );
+               MediaWikiFarmTestPerfs::startCounter( 
'loading-wfLoadExtensions' );
+
+               parent::loadExtensionsConfig();
+
+               MediaWikiFarmTestPerfs::stopCounter( 'loading-wfLoadExtensions' 
);
+               MediaWikiFarmTestPerfs::startCounter( 'loading-execFiles' );
+       }
+
+       function getMediaWikiConfig() {
+
+               MediaWikiFarmTestPerfs::startCounter( 
'loading-getMediaWikiConfig' );
+
+               parent::getMediaWikiConfig();
+
+               MediaWikiFarmTestPerfs::stopCounter( 
'loading-getMediaWikiConfig' );
+       }
+}
diff --git a/tests/perfs/index.php b/tests/perfs/index.php
new file mode 100644
index 0000000..efce0e4
--- /dev/null
+++ b/tests/perfs/index.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ * Entry point index.php in the context of a multiversion MediaWiki farm.
+ *
+ * @author Sébastien Beyou ~ Seb35 <[email protected]>
+ * @license GPL-3.0+ GNU General Public License v3.0 ou version ultérieure
+ * @license AGPL-3.0+ GNU Affero General Public License v3.0 ou version 
ultérieure
+ */
+// @codeCoverageIgnoreStart
+
+# Include library
+// @codingStandardsIgnoreStart MediaWiki.Usage.DirUsage.FunctionFound
+require_once dirname( dirname( dirname( __FILE__ ) ) ) . 
'/src/MediaWikiFarm.php';
+require_once dirname( __FILE__ ) . '/MediaWikiFarmTestPerfs.php';
+// @codingStandardsIgnoreEnd
+
+if( $_SERVER['REMOTE_ADDR'] != '127.0.0.1' && $_SERVER['REMOTE_ADDR'] != '::1' 
) {
+       exit;
+}
+
+switch( MediaWikiFarmTestPerfs::getEntryPointProfile( 'index.php' ) ) {
+
+       # Farm
+       case 0:
+
+               # Beginning of performance counter for the bootstrap part
+               MediaWikiFarmTestPerfs::startCounter( 'bootstrap' );
+
+               # Default MediaWikiFarm configuration
+               $wgMediaWikiFarmCodeDir = dirname( dirname( dirname( dirname( 
__FILE__ ) ) ) );
+               $wgMediaWikiFarmConfigDir = '/etc/mediawiki';
+               $wgMediaWikiFarmCacheDir = '/tmp/mw-cache';
+
+               # Check the entry point is installed in a multiversion 
MediaWiki farm or in the classical MediaWiki extensions directory
+               if( is_file( dirname( $wgMediaWikiFarmCodeDir ) . 
'/includes/DefaultSettings.php' ) ) exit;
+
+               # Override default MediaWikiFarm configuration
+               @include_once dirname( dirname( dirname( __FILE__ ) ) ) . 
'/config/MediaWikiFarmDirectories.php';
+
+               # Redirect to the requested version
+               if( MediaWikiFarmTestPerfs::load( 'index.php' ) == 200 ) {
+
+                       # End of performance counter for the bootstrap part
+                       MediaWikiFarmTestPerfs::stopCounter( 'bootstrap' );
+
+                       require 'index.php';
+               }
+
+               break;
+
+       # Classical LocalSettings.php in a classical installation
+       case 1:
+
+               $wgMediaWikiFarmMetadata = include_once dirname( __FILE__ ) . 
'/results/metadata.php';
+
+               chdir( $wgMediaWikiFarmMetadata['IP'] );
+               require 'index.php';
+}
+// @codeCoverageIgnoreEnd
diff --git a/tests/perfs/main.php b/tests/perfs/main.php
new file mode 100644
index 0000000..813c7f7
--- /dev/null
+++ b/tests/perfs/main.php
@@ -0,0 +1,23 @@
+<?php
+/**
+ * Main program, creating the MediaWikiFarm object, then loading MediaWiki 
configuration.
+ *
+ * @author Sébastien Beyou ~ Seb35 <[email protected]>
+ * @license GPL-3.0+ GNU General Public License v3.0 ou version ultérieure
+ * @license AGPL-3.0+ GNU Affero General Public License v3.0 ou version 
ultérieure
+ */
+// @codeCoverageIgnoreStart
+
+if( $_SERVER['REMOTE_ADDR'] != '127.0.0.1' && $_SERVER['REMOTE_ADDR'] != '::1' 
) {
+       exit;
+}
+
+MediaWikiFarmTestPerfs::startCounter( 'config' );
+
+require_once dirname( dirname( dirname( __FILE__ ) ) ) . '/src/main.php';
+
+MediaWikiFarmTestPerfs::stopCounter( 'loading-execFiles' );
+MediaWikiFarmTestPerfs::stopCounter( 'config' );
+MediaWikiFarmTestPerfs::writeResults();
+
+// @codeCoverageIgnoreEnd
diff --git a/tests/perfs/perfs.php b/tests/perfs/perfs.php
new file mode 100644
index 0000000..26ec182
--- /dev/null
+++ b/tests/perfs/perfs.php
@@ -0,0 +1,69 @@
+<?php
+
+if( PHP_SAPI != 'cli' && PHP_SAPI != 'phpdbg' ) {
+       exit;
+}
+
+$host = $argv[1];
+$sampleSize = count( $argv ) > 2 ? intval($argv[2]) : 100;
+$profiles = 2;
+
+if( is_file( dirname( __FILE__ ) . '/results/metadata.php' ) ) {
+       $deleted = @unlink( dirname( __FILE__ ) . '/results/metadata.php' );
+       if( !$deleted ) {
+               echo "Error: cannot delete previous measures.\n";
+               exit( 1 );
+       }
+}
+if( is_file( dirname( __FILE__ ) . '/results/LocalSettings.php' ) ) {
+       unlink( dirname( __FILE__ ) . '/results/LocalSettings.php' );
+}
+if( is_file( dirname( __FILE__ ) . '/results/measures-index.php.php' ) ) {
+       unlink( dirname( __FILE__ ) . '/results/measures-index.php.php' );
+}
+
+for( $i=0; $i<$sampleSize; $i++ ) {
+       if( $i > 0 && $i % 100 == 0 ) {
+               echo "\n";
+       }
+       for( $j=0; $j<$profiles; $j++ ) {
+               file_get_contents( $host );
+               usleep( rand( 3, 20 ) );
+       }
+       echo '.';
+}
+echo "\n";
+
+$statistics = include dirname( __FILE__ ) . '/results/measures-index.php.php';
+echo "sample size(farm) = " . count($statistics[0]) . "\n";
+echo "sample size(classical) = " . count($statistics[1]) . "\n";
+
+$mean = array();
+foreach( $statistics as $profile => $statisticsProfile ) {
+       $mean[$profile] = array();
+       foreach( $statisticsProfile as $unit ) {
+               foreach( $unit as $type => $value ) {
+                       if( !array_key_exists( $type, $mean[$profile] ) ) {
+                               $mean[$profile][$type] = 0;
+                       }
+                       $mean[$profile][$type] += $value;
+               }
+       }
+       foreach( $mean[$profile] as $type => $value ) {
+               $mean[$profile][$type] /= count( $statisticsProfile );
+               $mean[$profile][$type] *= 1000;
+       }
+}
+
+echo "bootstrap    config     total     total     config      total\n";
+echo "     farm      farm classical      farm difference difference\n";
+echo '    '.number_format($mean[0]['bootstrap'],3).' ';
+echo '    '.number_format($mean[0]['config'],3).' ';
+echo '    '.number_format($mean[1]['config'],3).' ';
+echo '    '.number_format($mean[0]['bootstrap']+$mean[0]['config'],3).' ';
+echo '     '.number_format($mean[0]['config']-$mean[1]['config'],3).' ';
+echo '     
'.number_format($mean[0]['bootstrap']+$mean[0]['config']-$mean[1]['config'],3).'
 ';
+echo "\n\n";
+var_dump($mean);
+
+unlink( dirname( __FILE__ ) . '/results/profile-index.php.php' );

-- 
To view, visit https://gerrit.wikimedia.org/r/309548
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I74737330cb808d5a2e6897babe0cab94a81a886d
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/MediaWikiFarm
Gerrit-Branch: master
Gerrit-Owner: Seb35 <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to