Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 134
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
McTest
0.00% covered (danger)
0.00%
0 / 134
0.00% covered (danger)
0.00%
0 / 5
992
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 37
0.00% covered (danger)
0.00%
0 / 1
90
 benchmarkSingleKeyOps
0.00% covered (danger)
0.00%
0 / 40
0.00% covered (danger)
0.00%
0 / 1
132
 benchmarkMultiKeyOpsImmediateBlocking
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
30
 benchmarkMultiKeyOpsDeferredBlocking
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
30
1<?php
2/**
3 * Makes several 'set', 'incr' and 'get' requests on every memcached
4 * server and shows a report.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 * http://www.gnu.org/copyleft/gpl.html
20 *
21 * @file
22 * @ingroup Maintenance
23 */
24
25use MediaWiki\MainConfigNames;
26use MediaWiki\Maintenance\Maintenance;
27use Wikimedia\ObjectCache\BagOStuff;
28
29// @codeCoverageIgnoreStart
30require_once __DIR__ . '/Maintenance.php';
31// @codeCoverageIgnoreEnd
32
33/**
34 * Maintenance script that  makes several 'set', 'incr' and 'get' requests
35 * on every memcached server and shows a report.
36 *
37 * @ingroup Maintenance
38 */
39class McTest extends Maintenance {
40    public function __construct() {
41        parent::__construct();
42        $this->addDescription(
43            "Makes several operation requests on every cache server and shows a report.\n" .
44            "This tests both per-key and batched *Multi() methods as well as WRITE_BACKGROUND.\n" .
45            "\"IB\" means \"immediate blocking\" and \"DB\" means \"deferred blocking.\""
46        );
47        $this->addOption( 'cache', 'Use servers from this $wgObjectCaches store', true, true );
48        $this->addOption( 'class', 'Override the store "class" parameter', false, true );
49        $this->addOption( 'i', 'Number of iterations', false, true );
50        $this->addArg( 'server[:port]', 'Cache server to test, with optional port', false );
51    }
52
53    public function execute() {
54        $config = $this->getConfig();
55        $objectCaches = $config->get( MainConfigNames::ObjectCaches );
56
57        $cacheType = $this->getOption( 'cache', $config->get( MainConfigNames::MainCacheType ) );
58        $iterations = $this->getOption( 'i', 100 );
59        $classOverride = $this->getOption( 'class' );
60        $server = $this->getArg( 0 );
61
62        if ( !isset( $objectCaches[$cacheType] ) ) {
63            $this->fatalError( "No configured '$cacheType' cache" );
64        }
65
66        if ( $classOverride !== null ) {
67            if ( !is_subclass_of( $classOverride, BagOStuff::class ) ) {
68                $this->fatalError( "Invalid class '$classOverride' for cache" );
69            }
70            $class = $classOverride;
71        } else {
72            $class = $objectCaches[$cacheType]['class'];
73        }
74
75        if ( $server !== null ) {
76            $servers = [ $server ];
77        } else {
78            // Note that some caches, like apcu, do not have a server list
79            $servers = $objectCaches[$cacheType]['servers'] ?? [ null ];
80        }
81
82        // Use longest server string for output alignment
83        $maxSrvLen = max( array_map( 'strlen', $servers ) );
84
85        $this->output( "Warming up connections to cache servers..." );
86        /** @var BagOStuff[] $cacheByServer */
87        $cacheByServer = [];
88        foreach ( $servers as $server ) {
89            $conf = $objectCaches[$cacheType];
90            if ( $server !== null ) {
91                $conf['servers'] = [ $server ];
92                $host = $server;
93            } else {
94                $host = 'localhost';
95            }
96            $cacheByServer[$host] = new $class( $conf );
97            $cacheByServer[$host]->get( 'key' );
98        }
99        $this->output( "done\n" );
100        $this->output( "Single and batched operation profiling/test results:\n" );
101
102        $valueByKey = [];
103        for ( $i = 1; $i <= $iterations; $i++ ) {
104            $valueByKey["test$i"] = 'S' . str_pad( (string)$i, 2048 );
105        }
106
107        foreach ( $cacheByServer as $host => $mcc ) {
108            $this->output( str_pad( $host, $maxSrvLen ) . "\n" );
109            $this->benchmarkSingleKeyOps( $mcc, $valueByKey );
110            $this->benchmarkMultiKeyOpsImmediateBlocking( $mcc, $valueByKey );
111            $this->benchmarkMultiKeyOpsDeferredBlocking( $mcc, $valueByKey );
112        }
113    }
114
115    private function benchmarkSingleKeyOps( BagOStuff $mcc, array $valueByKey ) {
116        $add = 0;
117        $set = 0;
118        $incr = 0;
119        $get = 0;
120        $delete = 0;
121
122        $keys = array_keys( $valueByKey );
123        $count = count( $valueByKey );
124
125        // Clear out any old values
126        $mcc->deleteMulti( $keys );
127
128        $time_start = microtime( true );
129        foreach ( $valueByKey as $key => $value ) {
130            if ( $mcc->add( $key, $value ) ) {
131                $add++;
132            }
133        }
134        $addMs = intval( 1e3 * ( microtime( true ) - $time_start ) );
135
136        $time_start = microtime( true );
137        foreach ( $valueByKey as $key => $value ) {
138            if ( $mcc->set( $key, $value ) ) {
139                $set++;
140            }
141        }
142        $setMs = intval( 1e3 * ( microtime( true ) - $time_start ) );
143
144        $time_start = microtime( true );
145        foreach ( $valueByKey as $key => $value ) {
146            if ( $mcc->get( $key ) === $value ) {
147                $get++;
148            }
149        }
150        $getMs = intval( 1e3 * ( microtime( true ) - $time_start ) );
151
152        $time_start = microtime( true );
153        foreach ( $keys as $key ) {
154            if ( $mcc->delete( $key ) ) {
155                $delete++;
156            }
157        }
158        $delMs = intval( 1e3 * ( microtime( true ) - $time_start ) );
159
160        $time_start = microtime( true );
161        foreach ( $keys as $index => $key ) {
162            if ( $mcc->incrWithInit( $key, $mcc::TTL_INDEFINITE, $index ) === $index ) {
163                $incr++;
164            }
165        }
166        $incrMs = intval( 1e3 * ( microtime( true ) - $time_start ) );
167
168        $this->output(
169            " add: $add/$count {$addMs}ms   " .
170            "set: $set/$count {$setMs}ms   " .
171            "get: $get/$count ({$getMs}ms)   " .
172            "delete: $delete/$count ({$delMs}ms)    " .
173            "incr: $incr/$count ({$incrMs}ms)\n"
174        );
175    }
176
177    private function benchmarkMultiKeyOpsImmediateBlocking( BagOStuff $mcc, array $valueByKey ) {
178        $keys = array_keys( $valueByKey );
179        $iterations = count( $valueByKey );
180
181        $time_start = microtime( true );
182        $mSetOk = $mcc->setMulti( $valueByKey ) ? '✓' : '✗';
183        $mSetMs = intval( 1e3 * ( microtime( true ) - $time_start ) );
184
185        $time_start = microtime( true );
186        $found = $mcc->getMulti( $keys );
187        $mGetMs = intval( 1e3 * ( microtime( true ) - $time_start ) );
188        $mGetOk = 0;
189        foreach ( $found as $key => $value ) {
190            $mGetOk += ( $value === $valueByKey[$key] );
191        }
192
193        $time_start = microtime( true );
194        $mChangeTTLOk = $mcc->changeTTLMulti( $keys, 3600 ) ? '✓' : '✗';
195        $mChangeTTTMs = intval( 1e3 * ( microtime( true ) - $time_start ) );
196
197        $time_start = microtime( true );
198        $mDelOk = $mcc->deleteMulti( $keys ) ? '✓' : '✗';
199        $mDelMs = intval( 1e3 * ( microtime( true ) - $time_start ) );
200
201        $this->output(
202            " setMulti (IB): $mSetOk {$mSetMs}ms   " .
203            "getMulti (IB): $mGetOk/$iterations {$mGetMs}ms   " .
204            "changeTTLMulti (IB): $mChangeTTLOk {$mChangeTTTMs}ms   " .
205            "deleteMulti (IB): $mDelOk {$mDelMs}ms\n"
206        );
207    }
208
209    private function benchmarkMultiKeyOpsDeferredBlocking( BagOStuff $mcc, array $valueByKey ) {
210        $keys = array_keys( $valueByKey );
211        $iterations = count( $valueByKey );
212        $flags = $mcc::WRITE_BACKGROUND;
213
214        $time_start = microtime( true );
215        $mSetOk = $mcc->setMulti( $valueByKey, 0, $flags ) ? '✓' : '✗';
216        $mSetMs = intval( 1e3 * ( microtime( true ) - $time_start ) );
217
218        $time_start = microtime( true );
219        $found = $mcc->getMulti( $keys );
220        $mGetMs = intval( 1e3 * ( microtime( true ) - $time_start ) );
221        $mGetOk = 0;
222        foreach ( $found as $key => $value ) {
223            $mGetOk += ( $value === $valueByKey[$key] );
224        }
225
226        $time_start = microtime( true );
227        $mChangeTTLOk = $mcc->changeTTLMulti( $keys, 3600, $flags ) ? '✓' : '✗';
228        $mChangeTTTMs = intval( 1e3 * ( microtime( true ) - $time_start ) );
229
230        $time_start = microtime( true );
231        $mDelOk = $mcc->deleteMulti( $keys, $flags ) ? '✓' : '✗';
232        $mDelMs = intval( 1e3 * ( microtime( true ) - $time_start ) );
233
234        $this->output(
235            " setMulti (DB): $mSetOk {$mSetMs}ms   " .
236            "getMulti (DB): $mGetOk/$iterations {$mGetMs}ms   " .
237            "changeTTLMulti (DB): $mChangeTTLOk {$mChangeTTTMs}ms   " .
238            "deleteMulti (DB): $mDelOk {$mDelMs}ms\n"
239        );
240    }
241}
242
243// @codeCoverageIgnoreStart
244$maintClass = McTest::class;
245require_once RUN_MAINTENANCE_IF_MAIN;
246// @codeCoverageIgnoreEnd