A global counter example introduced here applys Shared Memory to store a common counter value for Linux processes, which tend to get incremental amounts randomly. Moreover, we assume HTTP requests require this counter, and then focus on the behavior of child processes forked by Apache daemon.
As experimental proof, the simulation model in another Linux Host sends many HTTP messages to operational host, so as to force Apache daemon urgently fork new child processes for serving mass requests.
All codes here are not complicated, so you can easily understand even though you are still students in school. To benefit your learning, we will provide you download link to a zip file thus you can get all source codes for future usage.
Estimated reading time: 11 minutes
EXPLORE THIS ARTICLE
TABLE OF CONTENTS
BONUS
Source Code Download
We have released it under the MIT license, so feel free to use it in your own project or your school homework.
Download Guideline
- Prepare HTTP server such as XAMPP or WAMP in your windows environment.
- Prepare HTTP server such as Apache on Linux. For someone without Linux, it still works to build Linux virtual machine on Windows by freeware like Oracle VM VirtualBox.
- Download and unzip into a folder that HTTP server can access.
SECTION 1
Shared Memory Basics
How Shared Memory plays the role of bridging data communication between Linux processes is the basics we have written about here. Based on Apache Multi-Processing Modules(MPMs), Apache child processes read and write Shared Memory for serving HTTP requests.
Shared Memory in Linux
Shared Memory stores variables shared between two or more Linux processes. However, why do we have to share storage for data communication?
In Linux, each process has own address space, and can not get data from other process, unless it leverages IPC (Inter-Process Communication) mechanism. Most commonly used IPC’s are Shared Memory and Message Queue. For the latter one, we have an article discussing the topic, How to Pass HTTP Request into Queue using PHP.
In general, developers implement Shared Memory with Semaphore. Semaphore is a mechanism to support mutual exclusion, which prevents race conditions among concurrent reading from or writing to resources, like Shared Memory.
How Process Access Shared Memory
From the view of HTTP, browsers from everywhere send requests to host. In Linux host, Apache previously fork child processes listening requests. Once each process is assigned a job by Apache daemon, it executes the script, for example, token.php, and reply to browser with results.
For HTTP global counter example, when token.php is running in one process, it asks Linux kernel to create Shared Memory segment or uses an already created Shared Memory segment. Actually, it locks Shared Memory by using Semaphore at the beginning to prevent other processes from access to data. Until reading and increasing the counter value, processes unlock Shared Memory eventually.
SECTION 2
Counter In Shared Memory
The features of Linux Shared Memory fulfill the solution of HTTP global counter. The counter value will incrementally changes because of randomly accessing by Apache child processes.
HTTP Global Counter
HTTP global counter means that browsers from everywhere make requests to a single counter system through Apache child processes. If each request has not been divided into 4 actions as above diagram, race conditions or saying collisions will likely occur among requests.
<?php
// Convert a path name and a project identifier to a System V IPC key
$key = ftok(dirname(__FILE__) . '/token.php', 'S');
// Creating a shared memory segment with a key, we need to use an integer value.
$shmId = shm_attach($key);
$var_key = 99;
// Obtain a resource semaphore - lock feature. There is nothing wrong if we
// use the same key that is used to obtain a resource shared memory
$semId = sem_get($key);
echo sprintf("%06d", get_cnt());
/* get counter value */
function get_cnt() {
global $shmId, $var_key, $semId;
// Put a lock. There's a caveat. If another process will encounter this lock,
// it will wait until the lock is removed
sem_acquire($semId);
// Check if we have the requested variables.
if (shm_has_var($shmId, $var_key)) {
// If so, read the data
$val= shm_get_var($shmId, $var_key);
$val+= 1;
} else {
// If the data was not there.
$val= 1;
}
shm_put_var($shmId, $var_key, $val);
// Releases the lock.
sem_release($semId);
return $val;
}
?>
At first, ftok()
generate System V IPC keys for both $shmId
and $semId
, that is, Shared Memory and Semaphore use the identical key or saying resource. Then shm_attach()
attaches to Shared Memory if already existing; otherwise, create a new one and attach to it. Similiarly, sem_get()
create Semaphore related to Shared Memory in the same way.
The $var_key
is an integer and acts as the address in Shared Memory. When Semaphore locking is done by sem_acquire()
, shm_has_var($shmId, $var_key)
check whether this address contains any value. If an item is found there, add one to it and write it back. Finally, sem_release()
release the Semaphore lock.
Monitoring Contents in Shared Memory
For each Apache child process, at the time of job ending, all variables in its address space are cleaned, but Shared Memory still exists. As a common area in Linux, Shared Memory stores counter value for later usage. Here provides a tool to monitor not only change of contents, and also the number of Apache child processes, from which we will know whether the HTTP status is busy or idle.
Issus command lines using tools.php like the following.
eric@hr5:~/a0007$ php tools.php status
shared memory=>56 apache2 child processes=>14
shared memory=>56 apache2 child processes=>14
shared memory=>57 apache2 child processes=>15
^C
eric@hr5:~/a0007$ php tools.php reset
shm removed
sem removed
<?php
$key = ftok(dirname(__FILE__) . '/token.php', 'S');
// Creating a shared memory segment with a key, we need to use an integer value.
$shmId = shm_attach($key);
$var_key = 99;
$semId = sem_get($key);
$func = $argv[1];
switch ($func) {
default:
echo "INVALID REQUEST" . PHP_EOL;
break;
case "reset":
if(!shm_remove($shmId)) echo "remove shm failed\n"; else echo "shm removed\n";
if(!sem_remove($semId)) echo "remove sem failed\n"; else echo "sem removed\n";
break;
case "status":
while(1) {
$cnt= chk_cnt();
unset($out);
exec("ps -ef | grep www-data | wc -l", $out);
echo "shared memory=>{$cnt} apache2 child processes {$out[0]}\n";
sleep(1);
}
break;
}
/* check counter value */
function chk_cnt() {
global $shmId, $var_key, $semId;
// Put a lock. There's a caveat. If another process will encounter this lock,
// it will wait until the lock is removed
sem_acquire($semId);
// Check if we have the requested variables.
if (shm_has_var($shmId, $var_key)) {
// If so, read the data
$val= shm_get_var($shmId, $var_key);
} else
$val = 0;
// Releases the lock.
sem_release($semId);
return $val;
}
?>
From the codes above, you can find the similar way like that in token.php to read Shared Memory with Semaphore lock. In addition, shell commands ps -ef | grep www-data | wc -l
inspects how many Apache child processes are working. If going to reset counter value due to any reason, just remove it by shm_remove
and sem_remove
. When next HTTP request comes, Shared Memory will be created again.
SECTION 3
Simulation Model
The folder simcli containing simulation module can be moved to the benchmarking host to be used for generating a lot of HTTP requests to the operational host. We will introduce what a single client does, and how mass clients are working concurrently.
A Simulated Client
Assume benchmarking host is hr1 and operational host is hr5. A simulated client can be running in hr1 by issuing command lines with simcli.php. The second argument representing URL to hr5. The log file recvlog/1 records 100 requesting results.
joe@hr1:~/simcli$ php simcli.php 1 http://hr5:90/wpp/exams/a0007/token.php
joe@hr1:~/simcli$ vi recvlog/1
<?php
error_reporting(E_ERROR & ~E_NOTICE);
$url = $argv[2];
$timeout = 30; // seconds
$reqcnt = 0;
$sendfp = fopen('sendlog/' . $argv[1], 'a');
$recvfp = fopen('recvlog/' . $argv[1], 'a');
while($reqcnt < 100) {
fwrite($sendfp, sprintf("%s\n", 'send'));
$t1 = microtime(true);
$r = usecurl(['msg'=>'dummy'], $url);
$t2 = microtime(true);
if (($t2-$t1) > $timeout )
fwrite($recvfp, sprintf("%s %s\n", 'timeout', sprintf("%.4f", $t2-$t1) ) );
else
fwrite($recvfp, sprintf("%s %s %s\n", 'elapsed', sprintf("%.4f", $t2-$t1), $r ) );
$reqcnt += 1;
}
fclose($sendfp);
fclose($recvfp);
function usecurl($post_data, $url){
...
?>
Mass Simulated Clients
To raise mass concurrent simulated browsers, issuing command mgmt.php can start and manage, for instance, 1000 simulated clients by specifying the second argument. The third argument is URL. Because each simulated client iterates 100 times, totally simulation model make 1000×100 requests to test global counter in Shared Memory.
joe@hr1:~/simcli$ php mgmt.php start 1000 http://hr5:90/wpp/exams/a0007/token.php
Duration: 10:59:59 to 11:00:23
joe@hr1:~/simcli$ php mgmt.php stat
sendlog count: 100000
recvlog count: 100000
elapsed count: 100000
timeout count: 0
elapsed average: 0.1975
joe@hr1:~/simcli$ php mgmt.php stop
Furthermore, mgmt.php provide statistics to calculate the average requesting elapsed time. If you want to both stop running simcli.php and clear log files, use stop as first argument.
eric@hr5:~/a0007$ php tools.php status
shared memroy=>12799 apache2 child processes 42
shared memroy=>14879 apache2 child processes 70
shared memroy=>16963 apache2 child processes 77
shared memroy=>19220 apache2 child processes 77
shared memroy=>21261 apache2 child processes 79
shared memroy=>23219 apache2 child processes 91
shared memroy=>25421 apache2 child processes 107
shared memroy=>27480 apache2 child processes 139
shared memroy=>29645 apache2 child processes 154
shared memroy=>32050 apache2 child processes 154
Meanwhile, maybe you need to know what condition the operational host hr5 is in. So let us move there and open a tty terminal. From hr5, entering command line tools.php as mentioned in previous section will display real-time status about contents in Shared Memory, and also the number of Apache child processes.
<?php
error_reporting(E_ERROR & ~E_NOTICE);
$func=$argv[1]; // start | stop | stat
$maxcnt=(int)$argv[2]; // the number of client programs to be generated
/* $argv[3], for example,
http://hr5:90/wpp/exams/a0007/token.php
*/
$url = $argv[3];
switch ($func) {
default:
echo "INVALID ARGUMENT\n";
break;
case "start":
actionStart($maxcnt);
break;
case "stop":
actionStop();
break;
case "stat":
actionStat();
break;
}
function actionStart($n) {
global $url;
/* remove history data */
exec("rm sendlog/*");
exec("rm recvlog/*");
$err_cnt = 0;
$prev_pid = 0;
for ($i = 1; $i <= $n; $i++) {
$cmd1="php simcli.php {$i} {$url}";
exec($cmd1 . " > /dev/null &");
usleep(1000);
}
$beginning = date("H:i:s");
while(1) {
unset($out);
$chk1="ps aux | grep 'php simcli.php' | grep -v grep | awk '{ print $2 }' ";
exec($chk1, $out);
if(count($out) == 0) break;
usleep(1000);
}
echo "Duration: {$beginning} to " . date("H:i:s") . "\n";
}
function actionStop() {
unset($out);
$chk1="ps aux | grep 'php simcli.php' | grep -v grep | awk '{ print $2 }' ";
exec($chk1, $out);
foreach($out as $pid) {
exec('kill ' . $pid);
echo "killing " . $pid . PHP_EOL;
}
exec("rm sendlog/*");
exec("rm recvlog/*");
}
function actionStat() {
$folder='sendlog/';
$files = glob($folder . '*', GLOB_MARK);
$cnt = 0;
foreach ($files as $file) {
unset($out);
exec("cat " . $file . " | wc -l", $out);
if (isset($out[0])) $cnt += (int)$out[0];
}
echo 'sendlog count: ' . $cnt . PHP_EOL;
$folder='recvlog/';
$files = glob($folder . '*', GLOB_MARK);
$stat = (float)0;
$cnt = 0;
$elapsed_cnt = 0;
$timeout_cnt = 0;
foreach ($files as $file) {
$fp = fopen($file, "r");
while(!feof($fp)) {
$ln_data = fgets($fp);
$ary = explode(' ', $ln_data);
if($ary[0] == 'elapsed') {
$stat += (float)$ary[1];
$elapsed_cnt += 1;
}
if($ary[0] == 'timeout') {
$timeout_cnt += 1;
}
if($ln_data != "\n") $cnt += 1;
}
$cnt -= 1;
}
echo 'recvlog count: ' . $cnt . PHP_EOL;
echo 'elapsed count: ' . $elapsed_cnt . PHP_EOL;
echo 'timeout count: ' . $timeout_cnt . PHP_EOL;
echo 'elapsed average: ' . sprintf("%.4f", $stat / $elapsed_cnt) . PHP_EOL;
}
?>
In benchmarking host hr1, mgmt.php starts multiple simcli.php processes to be running in background by Linux shell commands like ps aux | grep 'php simcli.php' | grep -v grep | awk '{ print $2 }'
. Moreover, it can keep checking all processes until they stopped.
Using arguments stop will both kill all processes and clear history logs. A simple calculation by argument stat can show statistics for counting and average.
FINAL
Conclusion
Although codes in the example are few, they involve several critical topics. It is noticed that codes for simulation model is similiar to that in How to Pass HTTP Request into Queue using PHP. Thank you for reading, and we have suggested more helpful articles here. If you want to share anything, please feel free to comment below. Good luck and happy coding!