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
);
}
添加评论
点赞收藏
点踩分享查看原文
评论
?
参与讨论