Run apcu test operations in PHP
| #!/bin/sh | |
| # Within 10 concurrent requests, do 1 round of get and set operations to the same collection of 100 keys with 1MB values | |
| ab -n 10 -c 10 'http://localhost/core/apcu_rw.php?keysCount=100&minSize=1000000&maxSize=1000000&iterations=10&mode=gas' | |
| # In one request, do a get and set operation to a collection of 100 keys with 1MB values, with profiling to /tmp/apcu_trace.log. | |
| # This can be run in another terminal while huge -n command similar to above runs in order to profile apcu under pressure. | |
| ab -n 1 -c 1 'http://localhost/core/apcu_rw.php?keysCount=100&minSize=1000000&maxSize=1000000&iterations=10&mode=gas&profile=1' | |
| # Drag/drop /tmp/apcu_trace.log (assumping php-excimer is installed) into https://www.speedscope.app/ | |
| # View apcu stats | |
| curl -a 'http://localhost/core/apcu_stats.php' | |
| # Flush apcu (if using bare metal, adjust accordingly if using docker) | |
| sudo service php8.5-fpm restart |
| <?php | |
| testAPCUMixedReadsAndWrites(); | |
| function testAPCUMixedReadsAndWrites() { | |
| $profiler = null; | |
| $xhprof_shutdown_fn = null; | |
| if ( ( $_GET['profile'] ?? null ) === '1' ) { | |
| if ( function_exists( 'tideways_xhprof_enable' ) ) { | |
| tideways_xhprof_enable( TIDEWAYS_XHPROF_FLAGS_CPU ); | |
| $xhprof_shutdown_fn = 'tideways_xhprof_disable'; | |
| } elseif ( function_exists( 'tideways_enable' ) ) { | |
| tideways_enable( TIDEWAYS_XHPROF_FLAGS_CPU ); | |
| $xhprof_shutdown_fn = 'tideways_disable'; | |
| } elseif ( function_exists( 'xhprof_enable' ) ) { | |
| xhprof_enable( XHPROF_FLAGS_CPU ); | |
| $xhprof_shutdown_fn = 'xhprof_disable'; | |
| } elseif ( class_exists( 'ExcimerProfiler' ) ) { | |
| $profiler = new ExcimerProfiler; | |
| $profiler->setPeriod( 0.001 ); // every 1ms | |
| $profiler->setEventType( EXCIMER_REAL ); | |
| $profiler->start(); | |
| // Results that can be dragged into https://www.speedscope.app/ | |
| register_shutdown_function( function () use ( $profiler ) { | |
| $profiler->stop(); | |
| file_put_contents( '/tmp/apcu_trace.log', $profiler->getLog()->formatCollapsed() ); | |
| } ); | |
| } | |
| } | |
| # What action to do with each key | |
| $mode = $_GET['mode'] ?? 'gos'; | |
| # Min random value size to use for keys | |
| $minBytes = intval( $_GET['minSize'] ?? 128 ); | |
| # Max random value size to use for keys | |
| $maxBytes = intval( $_GET['maxSize'] ?? 1024 * 1024 ); | |
| # Cardinality of the set of keys that might be choosen for use | |
| $keysCount = intval( $_GET['keysCount'] ?? 1000 ); | |
| # Cardinality of the subset of keys choosen for this request | |
| $keysPerRequest = intval( $_GET['keysPerRequest'] ?? $keysCount ); | |
| # How many times we cycle through the choosen keys for this request | |
| $iterations = intval( $_GET['iterations'] ?? 1 ); | |
| # Expiration for keys written | |
| $ttl = intval( $_GET['ttl'] ?? 3600 ); | |
| $referenceValue = null; | |
| $storeOK = 0; | |
| $storeFail = 0; | |
| $indexes = (array)array_rand( range( 1, $keysCount ), $keysPerRequest ); | |
| $start = hrtime( true ); | |
| foreach ( $indexes as $keyIndex ) { | |
| for ( $i = 0; $i < $iterations; ++$i ) { | |
| $copyKey = "global:testblobs:$keyIndex:$minBytes:$maxBytes"; | |
| if ( $mode === 'gos' ) { | |
| # Get, or, if not there, set | |
| #$copyValue = apcu_fetch( $copyKey ); | |
| $copyValue = fetch_key( $copyKey ); | |
| } elseif ( $mode === 'gas' ) { | |
| # Get and set | |
| #apcu_fetch( $copyKey ); | |
| fetch_key( $copyKey ); | |
| $copyValue = false; | |
| } elseif ( $mode === 'i' ) { | |
| # Init (manual set if not exists) | |
| #$copyValue = apcu_exists( $copyKey ); | |
| $copyValue = has_key( $copyKey ); | |
| } elseif ( $mode === 's' ) { | |
| # Set | |
| $copyValue = false; | |
| } elseif ( $mode === 'a' ) { | |
| # Add (internal set if not exists) | |
| $copyValue = false; | |
| } else { | |
| die( "Invalid apcu mode" ); | |
| } | |
| if ( $copyValue === false ) { | |
| $referenceValue = $referenceValue | |
| ?? str_repeat( hash('sha256', microtime() ), (int)ceil( $maxBytes / 64 ) ); | |
| $copyValue = substr( $referenceValue, 0, mt_rand( $minBytes, $maxBytes ) ); | |
| if ( $mode === 'a' ) { | |
| #$ok = apcu_add( $copyKey, $copyValue, $ttl ); | |
| $ok = add_key( $copyKey, $copyValue, $ttl ); | |
| } else { | |
| #$ok = apcu_store( $copyKey, $copyValue, $ttl ); | |
| $ok = store_key( $copyKey, $copyValue, $ttl ); | |
| } | |
| if ( $ok ) { | |
| ++$storeOK; | |
| } else { | |
| ++$storeFail; | |
| } | |
| } | |
| } | |
| } | |
| $stop = hrtime( true ); | |
| header( 'Content-Type: text/javascript' ); | |
| $data = [ | |
| 'store_ok' => $storeOK, | |
| 'store_fail' => $storeFail, | |
| 'ms_elapsed' => ( $stop - $start ) / 1e6 | |
| ]; | |
| if ( $xhprof_shutdown_fn !== null ) { | |
| $profile = $xhprof_shutdown_fn(); | |
| uasort( | |
| $profile, | |
| function ( $a, $b ) { | |
| return $b['wt'] <=> $a['wt']; | |
| } | |
| ); | |
| $data += [ 'profile' => $profile ]; | |
| } | |
| echo json_encode( $data, JSON_PRETTY_PRINT ); | |
| } | |
| # Wrapper so that excimer has a symbol to profile | |
| function has_key( $key ) { | |
| return apcu_exists( $key ); | |
| } | |
| # Wrapper so that excimer has a symbol to profile | |
| function fetch_key( $key ) { | |
| return apcu_fetch( $key ); | |
| } | |
| # Wrapper so that excimer has a symbol to profile | |
| function add_key( $key, $value, $ttl ) { | |
| return apcu_add( $key, $value, $ttl ); | |
| } | |
| # Wrapper so that excimer has a symbol to profile | |
| function store_key( $key, $value, $ttl ) { | |
| return apcu_store( $key, $value, $ttl ); | |
| } |
| <?php | |
| outputApcuJsonReport(); | |
| function outputApcuJsonReport() { | |
| $iter = new APCUIterator( | |
| null, | |
| APC_ITER_MEM_SIZE | APC_ITER_TTL | APC_ITER_NUM_HITS | APC_ITER_MTIME | APC_ITER_ATIME, | |
| 100, | |
| APC_LIST_ACTIVE | |
| ); | |
| $qps_kludged = false; | |
| $hit_qps_total = 0; | |
| $hit_qps_total = 0; | |
| $hit_bps_total = 0; | |
| $bytesOverall = 0; | |
| $statsByCollection = []; | |
| foreach ( $iter as $key => $info ) { | |
| $now = time(); | |
| $mtimeAgo = max( $now - $info['mtime'], 0 ); | |
| $atimeAgo = max( $now - $info['access_time'], 0 ); | |
| $qps_kludged = ( $qps_kludged || $mtimeAgo <= 0 ); | |
| $hitsPerSec = $info['num_hits'] / max( $mtimeAgo, 1 ); | |
| // Key came directly from BagOStuff::makeKey() or BagOStuff::makeGlobalKey() | |
| // and thus has the format of ":[:]..." | |
| $components = explode( ':', $key, 3 ); | |
| // Handle legacy callers that fail to use the key building methods | |
| $collection = $components[1] ?? $components[0]; | |
| // Replace dots because they are special in StatsD (T232907) | |
| $collection = strtr( $collection, '.', '_' ); | |
| if ( !isset( $statsByCollection[$collection] ) ) { | |
| $statsByCollection[$collection] = [ | |
| 'keys' => 0, | |
| 'keys_with_ttl' => 0, | |
| 'ttl_ave' => 0.0, | |
| 'bytes_ave' => 0.0, | |
| 'atime_ago_ave' => 0.0, | |
| 'mtime_ago_ave' => 0.0, | |
| 'hit_qps_total' => 0.0, | |
| 'hit_bps_total' => 0.0, | |
| '%bytes' => 0.0 | |
| ]; | |
| } | |
| $bytesOverall += $info['mem_size']; | |
| $statsByCollection[$collection]['keys'] += 1; | |
| $statsByCollection[$collection]['keys_with_ttl'] += ( $info['ttl'] ? 1 : 0 ); | |
| $statsByCollection[$collection]['ttl_ave'] += $info['ttl']; | |
| $statsByCollection[$collection]['bytes_ave'] += $info['mem_size']; | |
| $statsByCollection[$collection]['atime_ago_ave'] += $atimeAgo; | |
| $statsByCollection[$collection]['mtime_ago_ave'] += $mtimeAgo; | |
| $statsByCollection[$collection]['%bytes'] += $info['mem_size']; | |
| $statsByCollection[$collection]['hit_qps_total'] += $hitsPerSec; | |
| $statsByCollection[$collection]['hit_bps_total'] += $hitsPerSec * $info['mem_size']; | |
| $hit_qps_total += $hitsPerSec; | |
| $hit_bps_total += ( $hitsPerSec * $info['mem_size'] ); | |
| } | |
| uasort( | |
| $statsByCollection, | |
| function ( $a, $b ) { | |
| return $b['%bytes'] <=> $a['%bytes']; | |
| } | |
| ); | |
| foreach ( $statsByCollection as $collection => $stats ) { | |
| $statsByCollection[$collection]['ttl_ave'] /= ( $stats['keys_with_ttl'] ?: 1 ); | |
| $statsByCollection[$collection]['bytes_ave'] /= $stats['keys']; | |
| $statsByCollection[$collection]['atime_ago_ave'] /= $stats['keys']; | |
| $statsByCollection[$collection]['mtime_ago_ave'] /= $stats['keys']; | |
| $statsByCollection[$collection]['%bytes'] /= $bytesOverall; | |
| $statsByCollection[$collection]['%bytes'] *= 100; | |
| } | |
| $overallStats = apcu_cache_info( true ); | |
| $overallStats['hit_qps_total'] = $hit_qps_total; | |
| $overallStats['hit_bps_total'] = $hit_bps_total; | |
| $overallStats['qps_kludged'] = $qps_kludged; | |
| header('Content-Type: text/javascript'); | |
| print json_encode( | |
| [ 'overall' => $overallStats, 'breakdown' => $statsByCollection ], | |
| JSON_PRETTY_PRINT | |
| ); | |
| } |
评论
?
参与讨论