Skip to content

Instantly share code, notes, and snippets.

@greevex
Last active August 29, 2015 14:27
Show Gist options
  • Save greevex/0e050b8bf2358b121618 to your computer and use it in GitHub Desktop.
Save greevex/0e050b8bf2358b121618 to your computer and use it in GitHub Desktop.
Server RAM test
#!/usr/bin/env php
<?php
namespace hardcore;
abstract class appBase
{
private $input;
abstract protected function handle();
public function run()
{
error_log('Starting application...');
$this->handle();
error_log('Ending application...');
}
protected function getInput($name)
{
if($this->input === null) {
$this->input = $this->importArgs($GLOBALS['argv']);
}
return isset($this->input[$name]) ? $this->input[$name] : null;
}
/**
* Function to parse php arguments
*
* @param array $argv
* @return array
*/
private function importArgs($argv) {
array_shift($argv);
$out = array();
foreach ($argv as $arg){
if (substr($arg,0,2) == '--'){
$eqPos = strpos($arg,'=');
if ($eqPos === false){
$key = substr($arg,2);
$out[$key] = isset($out[$key]) ? $out[$key] : true;
} else {
$key = substr($arg,2,$eqPos-2);
$out[$key] = substr($arg,$eqPos+1);
}
} elseif (substr($arg,0,1) == '-'){
if (substr($arg,2,1) == '='){
$key = substr($arg,1,1);
$out[$key] = substr($arg,3);
} else {
$chars = str_split(substr($arg,1));
foreach ($chars as $char){
$key = $char;
$out[$key] = isset($out[$key]) ? $out[$key] : true;
}
}
} elseif (substr($arg,0,1) == ']'){
list($key, $value) = explode('=', $arg);
$out[$key] = $value;
} else {
$out[] = $arg;
}
}
return $out;
}
}
class hardcore
extends appBase
{
/**
* Handle function
*
* @return mixed
*/
protected function handle()
{
if($this->getInput('help')) {
echo <<<TEXT
=======
PARAMS:
--max-tests=... (integer) Count of internal iterations (each iteration cost some RAM)
--max-threads=... (integer) Count of process threads (forks)
TEXT;
return;
}
for(;;) {
$this->threadPool()->refresh();
while($this->threadPool()->getPoolCount() < $this->threadPool()->getMaxThreads()) {
$this->threadPool()->add(function() {
$this->process();
});
}
sleep(1);
}
}
protected function process()
{
$maxTests = (int)$this->getInput('max-tests');
for(;;) {
$strings = [];
for ($i = 0; $i < $maxTests; $i++) {
$strings[] = str_repeat(md5(mt_rand(0,5)) . md5(mt_rand(6,10)) . md5(mt_rand(11,15)) . md5(mt_rand(15,20)) . md5(mt_rand(21,25)), 128);
usleep(250);
}
unset($strings);
}
}
/**
* @return threadPool
*/
protected function threadPool()
{
static $threadPool;
if(!isset($threadPool)) {
$threadPool = new threadPool();
$queue = 0;
$threads = (int)$this->getInput('max-threads');
if(!$threads) {
$threads = 100;
}
error_log("Instantiating threadPool with queue: {$queue} and threads: {$threads}");
$threadPool->setMaxQueue($queue);
$threadPool->setMaxThreads($threads);
}
return $threadPool;
}
}
class thread
{
/**
* Status code
* Function is not callable
*/
const FUNCTION_NOT_CALLABLE = 10;
/**
* Status code
* Couldn't fork
*/
const COULD_NOT_FORK = 15;
/**
* Status code
* Fork ready
*/
const FORK_READY = -50;
/**
* Possible errors
*
* @var array
*/
private $errors = [
self::FUNCTION_NOT_CALLABLE => 'You must specify a valid function name that can be called from the current scope.',
self::COULD_NOT_FORK => 'pcntl_fork() returned a status of -1. No new process was created',
];
/**
* callback for the function that should
* run as a separate thread
*
* @var callable
*/
protected $runnable;
/**
* holds the current process id
*
* @var integer
*/
private $pid;
/**
* checks if threading is supported by the current
* PHP configuration
*
* @return boolean
*/
public static function available()
{
$required_functions = array(
'pcntl_fork',
);
foreach( $required_functions as $function ) {
if ( !function_exists( $function ) ) {
return false;
}
}
return true;
}
/**
* class constructor - you can pass
* the callback function as an argument
*
* @param callable $callable
*/
public function __construct($callable = null)
{
if($callable !== null) {
$this->setRunnable($callable);
}
}
/**
* Set callback
*
* @param callable $runnable
*/
public function setRunnable($runnable)
{
$this->runnable = $runnable;
}
/**
* Get callback
*
* @return callable
*/
public function getRunnable()
{
return $this->runnable;
}
/**
* returns the process id (pid) of the simulated thread
*
* @return int pid
*/
public function getPid()
{
return $this->pid;
}
/**
* checks if the child thread is alive
*
* @return boolean
*/
public function isAlive()
{
if(!isset($this->pid)) {
return false;
}
$pid = pcntl_waitpid( $this->pid, $status, WNOHANG );
return ( $pid === 0 );
}
/**
* starts the thread, all the parameters are
* passed to the callback function
*
* @return thread
* @throws \Exception
*/
public function start()
{
$pid = @ pcntl_fork();
if( $pid == -1 ) {
throw new \Exception( $this->getError( self::COULD_NOT_FORK ), self::COULD_NOT_FORK );
}
if( $pid ) {
$this->pid = $pid;
}
else {
$arguments = func_get_args();
pcntl_signal(SIGTERM, array(__CLASS__, 'signalHandler'));
register_shutdown_function(array(__CLASS__, 'signalHandler'));
pcntl_signal_dispatch();
call_user_func_array($this->runnable, $arguments);
exit(0);
}
return $this;
}
/**
* attempts to stop the thread
* returns true on success and false otherwise
*
* @param integer $_signal - SIGKILL/SIGTERM
* @param boolean $_wait
*/
public function stop( $_signal = SIGKILL, $_wait = false )
{
$isAlive = (int)$this->isAlive();
error_log("Stopping process {$this->pid}, alive:{$isAlive}");
if($isAlive) {
posix_kill( $this->pid, $_signal );
if( $_wait ) {
pcntl_waitpid( $this->pid, $status = 0 );
}
}
}
/**
* alias of stop();
*
* @param int $_signal
* @param bool $_wait
*
* @return bool
*/
public function kill( $_signal = SIGKILL, $_wait = false )
{
error_log("Killing process with pid {$this->pid}...");
for($i = 0; $i < 10; $i++) {
posix_kill( $this->pid, $_signal );
usleep(10000);
}
if( $_wait ) {
error_log("Waiting process [pid {$this->pid}]...");
pcntl_waitpid( $this->pid, $status = 0 );
}
}
/**
* gets the error's message based on
* its id
*
* @param integer $_code
* @return string
*/
public function getError( $_code )
{
if ( isset( $this->errors[$_code] ) ) {
return $this->errors[$_code];
}
else {
return 'No such error code ' . $_code . '! Quit inventing errors!!!';
}
}
/**
* signal handler
*
* @param integer $_signal
*/
public static function signalHandler($_signal = SIGTERM)
{
switch($_signal) {
case SIGTERM:
exit(0);
break;
}
}
}
/**
* Thread Pool class
*
* Organize thread pool with queue
*/
class threadPool
{
/**
* Max treads in pool
*
* @var int
*/
private $max_threads = 5;
/**
* Max queue length
*
* @var int
*/
private $max_queue = 5;
/**
* Pool queue array
*
* @var array
*/
private $queue = array();
/**
* Pool array
*
* @var thread[]
*/
private $pool = array();
/**
* Result array
*
* @var array
*/
private $result = array();
/**
* Check if has alive threads
*
* @return bool
*/
public function hasAlive()
{
$this->refresh();
return (count($this->pool) > 0 || count($this->queue) > 0);
}
/**
* Wait for alive threads
*
* @return bool
*/
public function waitAll()
{
while($this->hasAlive()) {
$this->refresh();
usleep(100000);
}
return true;
}
/**
* Refresh threads in pool
*
* @return bool
*/
public function refresh()
{
/**
* @note kill thread if thread is not alive
*/
foreach($this->pool as $thread_key => $thread) {
if(!$thread->isAlive()) {
$thread->kill();
unset($this->pool[$thread_key]);
}
}
/**
* @note add threads in pool from queue
*/
while(count($this->queue) > 0 && count($this->pool) < $this->max_threads) {
list($thread, $params) = array_shift($this->queue);
/** @var thread $thread */
/** @noinspection ExceptionsAnnotatingAndHandlingInspection */
$this->pool[] = $thread->start($params);
}
return true;
}
/**
* Get pool count
*
* @return int
*/
public function getPoolCount()
{
return is_array($this->pool) ? count($this->pool) : 0;
}
/**
* Add thread with arguments in pool
*
* @param callable $thread
* @param array|null $arguments
* @return bool
*/
public function add($thread, $arguments = null)
{
if(count($this->pool) >= $this->max_threads && count($this->queue) >= $this->max_queue) {
return false;
}
$this->queue[] = array(new thread($thread), $arguments);
$this->refresh();
return true;
}
/**
* Set max queue length
*
* @param $max_queue
* @return mixed
*/
public function setMaxQueue($max_queue)
{
return $this->max_queue = $max_queue;
}
/**
* Get max queue length
*
* @return int
*/
public function getMaxQueue()
{
return $this->max_queue;
}
/**
* Set max threads length
*
* @param int $max_threads
* @return mixed
*/
public function setMaxThreads($max_threads)
{
return $this->max_threads = $max_threads;
}
/**
* Get max threads value
*
* @return int max threads
*/
public function getMaxThreads()
{
return $this->max_threads;
}
/**
* Get thread pool
*
* @return array pool
*/
public function getPool()
{
return $this->pool;
}
/**
* Get threads work results
*
* @return array threads work results
*/
public function getResults()
{
$result = $this->result;
$this->result = array();
return $result;
}
}
$app = new hardcore();
$app->run();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment