Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 137
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 Wikimedia\ObjectCache\BagOStuff;
27
28require_once __DIR__ . '/Maintenance.php';
29
30/**
31 * Maintenance script that  makes several 'set', 'incr' and 'get' requests
32 * on every memcached server and shows a report.
33 *
34 * @ingroup Maintenance
35 */
36class McTest extends Maintenance {
37    public function __construct() {
38        parent::__construct();
39        $this->addDescription(
40            "Makes several operation requests on every cache server and shows a report.\n" .
41            "This tests both per-key and batched *Multi() methods as well as WRITE_BACKGROUND.\n" .
42            "\"IB\" means \"immediate blocking\" and \"DB\" means \"deferred blocking.\""
43        );
44        $this->addOption( 'cache', 'Use servers from this $wgObjectCaches store', true, true );
45        $this->addOption( 'class', 'Override the store "class" parameter', false, true );
46        $this->addOption( 'i', 'Number of iterations', false, true );
47        $this->addArg( 'server[:port]', 'Cache server to test, with optional port', false );
48    }
49
50    public function execute() {
51        $config = $this->getConfig();
52        $objectCaches = $config->get( MainConfigNames::ObjectCaches );
53
54        $cacheType = $this->getOption( 'cache', $config->get( MainConfigNames::MainCacheType ) );
55        $iterations = $this->getOption( 'i', 100 );
56        $classOverride = $this->getOption( 'class' );
57        $server = $this->getArg( 0 );
58
59        if ( !isset( $objectCaches[$cacheType] ) ) {
60            $this->fatalError( "No configured '$cacheType' cache" );
61        }
62
63        if ( $classOverride !== null ) {
64            if ( !is_subclass_of( $classOverride, BagOStuff::class ) ) {
65                $this->fatalError( "Invalid class '$classOverride' for cache" );
66            }
67            $class = $classOverride;
68        } else {
69            $class = $objectCaches[$cacheType]['class'];
70        }
71
72        if ( $server !== null ) {
73            $servers = [ $server ];
74        } else {
75            // Note that some caches, like apcu, do not have a server list
76            $servers = $objectCaches[$cacheType]['servers'] ?? [ null ];
77        }
78
79        // Use longest server string for output alignment
80        $maxSrvLen = max( array_map( 'strlen', $servers ) );
81
82        $this->output( "Warming up connections to cache servers..." );
83        /** @var BagOStuff[] $cacheByServer */
84        $cacheByServer = [];
85        foreach ( $servers as $server ) {
86            $conf = $objectCaches[$cacheType];
87            if ( $server !== null ) {
88                $conf['servers'] = [ $server ];
89                $host = $server;
90            } else {
91                $host = 'localhost';
92            }
93            $cacheByServer[$host] = new $class( $conf );
94            $cacheByServer[$host]->get( 'key' );
95        }
96        $this->output( "done\n" );
97        $this->output( "Single and batched operation profiling/test results:\n" );
98
99        $valueByKey = [];
100        for ( $i = 1; $i <= $iterations; $i++ ) {
101            $valueByKey["test$i"] = 'S' . str_pad( (string)$i, 2048 );
102        }
103
104        foreach ( $cacheByServer as $host => $mcc ) {
105            $this->output( str_pad( $host, $maxSrvLen ) . "\n" );
106            $this->benchmarkSingleKeyOps( $mcc, $valueByKey );
107            $this->benchmarkMultiKeyOpsImmediateBlocking( $mcc, $valueByKey );
108            $this->benchmarkMultiKeyOpsDeferredBlocking( $mcc, $valueByKey );
109        }
110    }
111
112    /**
113     * @param BagOStuff $mcc
114     * @param array $valueByKey
115     */
116    private function benchmarkSingleKeyOps( BagOStuff $mcc, array $valueByKey ) {
117        $add = 0;
118        $set = 0;
119        $incr = 0;
120        $get = 0;
121        $delete = 0;
122
123        $keys = array_keys( $valueByKey );
124        $count = count( $valueByKey );
125
126        // Clear out any old values
127        $mcc->deleteMulti( $keys );
128
129        $time_start = microtime( true );
130        foreach ( $valueByKey as $key => $value ) {
131            if ( $mcc->add( $key, $value ) ) {
132                $add++;
133            }
134        }
135        $addMs = intval( 1e3 * ( microtime( true ) - $time_start ) );
136
137        $time_start = microtime( true );
138        foreach ( $valueByKey as $key => $value ) {
139            if ( $mcc->set( $key, $value ) ) {
140                $set++;
141            }
142        }
143        $setMs = intval( 1e3 * ( microtime( true ) - $time_start ) );
144
145        $time_start = microtime( true );
146        foreach ( $valueByKey as $key => $value ) {
147            if ( $mcc->get( $key ) === $value ) {
148                $get++;
149            }
150        }
151        $getMs = intval( 1e3 * ( microtime( true ) - $time_start ) );
152
153        $time_start = microtime( true );
154        foreach ( $keys as $key ) {
155            if ( $mcc->delete( $key ) ) {
156                $delete++;
157            }
158        }
159        $delMs = intval( 1e3 * ( microtime( true ) - $time_start ) );
160
161        $time_start = microtime( true );
162        foreach ( $keys as $index => $key ) {
163            if ( $mcc->incrWithInit( $key, $mcc::TTL_INDEFINITE, $index ) === $index ) {
164                $incr++;
165            }
166        }
167        $incrMs = intval( 1e3 * ( microtime( true ) - $time_start ) );
168
169        $this->output(
170            " add: $add/$count {$addMs}ms   " .
171            "set: $set/$count {$setMs}ms   " .
172            "get: $get/$count ({$getMs}ms)   " .
173            "delete: $delete/$count ({$delMs}ms)    " .
174            "incr: $incr/$count ({$incrMs}ms)\n"
175        );
176    }
177
178    /**
179     * @param BagOStuff $mcc
180     * @param array $valueByKey
181     */
182    private function benchmarkMultiKeyOpsImmediateBlocking( BagOStuff $mcc, array $valueByKey ) {
183        $keys = array_keys( $valueByKey );
184        $iterations = count( $valueByKey );
185
186        $time_start = microtime( true );
187        $mSetOk = $mcc->setMulti( $valueByKey ) ? '✓' : '✗';
188        $mSetMs = intval( 1e3 * ( microtime( true ) - $time_start ) );
189
190        $time_start = microtime( true );
191        $found = $mcc->getMulti( $keys );
192        $mGetMs = intval( 1e3 * ( microtime( true ) - $time_start ) );
193        $mGetOk = 0;
194        foreach ( $found as $key => $value ) {
195            $mGetOk += ( $value === $valueByKey[$key] );
196        }
197
198        $time_start = microtime( true );
199        $mChangeTTLOk = $mcc->changeTTLMulti( $keys, 3600 ) ? '✓' : '✗';
200        $mChangeTTTMs = intval( 1e3 * ( microtime( true ) - $time_start ) );
201
202        $time_start = microtime( true );
203        $mDelOk = $mcc->deleteMulti( $keys ) ? '✓' : '✗';
204        $mDelMs = intval( 1e3 * ( microtime( true ) - $time_start ) );
205
206        $this->output(
207            " setMulti (IB): $mSetOk {$mSetMs}ms   " .
208            "getMulti (IB): $mGetOk/$iterations {$mGetMs}ms   " .
209            "changeTTLMulti (IB): $mChangeTTLOk {$mChangeTTTMs}ms   " .
210            "deleteMulti (IB): $mDelOk {$mDelMs}ms\n"
211        );
212    }
213
214    /**
215     * @param BagOStuff $mcc
216     * @param array $valueByKey
217     */
218    private function benchmarkMultiKeyOpsDeferredBlocking( BagOStuff $mcc, array $valueByKey ) {
219        $keys = array_keys( $valueByKey );
220        $iterations = count( $valueByKey );
221        $flags = $mcc::WRITE_BACKGROUND;
222
223        $time_start = microtime( true );
224        $mSetOk = $mcc->setMulti( $valueByKey, 0, $flags ) ? '✓' : '✗';
225        $mSetMs = intval( 1e3 * ( microtime( true ) - $time_start ) );
226
227        $time_start = microtime( true );
228        $found = $mcc->getMulti( $keys );
229        $mGetMs = intval( 1e3 * ( microtime( true ) - $time_start ) );
230        $mGetOk = 0;
231        foreach ( $found as $key => $value ) {
232            $mGetOk += ( $value === $valueByKey[$key] );
233        }
234
235        $time_start = microtime( true );
236        $mChangeTTLOk = $mcc->changeTTLMulti( $keys, 3600, $flags ) ? '✓' : '✗';
237        $mChangeTTTMs = intval( 1e3 * ( microtime( true ) - $time_start ) );
238
239        $time_start = microtime( true );
240        $mDelOk = $mcc->deleteMulti( $keys, $flags ) ? '✓' : '✗';
241        $mDelMs = intval( 1e3 * ( microtime( true ) - $time_start ) );
242
243        $this->output(
244            " setMulti (DB): $mSetOk {$mSetMs}ms   " .
245            "getMulti (DB): $mGetOk/$iterations {$mGetMs}ms   " .
246            "changeTTLMulti (DB): $mChangeTTLOk {$mChangeTTTMs}ms   " .
247            "deleteMulti (DB): $mDelOk {$mDelMs}ms\n"
248        );
249    }
250}
251
252$maintClass = McTest::class;
253require_once RUN_MAINTENANCE_IF_MAIN;