diff --git a/.travis.yml b/.travis.yml index 120e8b2..2bb01a0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,3 +14,4 @@ install: script: - vendor/bin/phpunit --coverage-text + - php examples/13-benchmark-throughput.php diff --git a/README.md b/README.md index 68286f9..81b9b2e 100644 --- a/README.md +++ b/README.md @@ -13,11 +13,11 @@ as [Streams](https://github.com/reactphp/stream). **Table of contents** +* [Quickstart example](#quickstart-example) * [Processes](#processes) * [EventEmitter Events](#eventemitter-events) * [Methods](#methods) * [Stream Properties](#stream-properties) -* [Usage](#usage) * [Prepending Commands with `exec`](#prepending-commands-with-exec) * [Sigchild Compatibility](#sigchild-compatibility) * [Command Chaining](#command-chaining) @@ -25,6 +25,27 @@ as [Streams](https://github.com/reactphp/stream). * [Tests](#tests) * [License](#license) +## Quickstart example + +```php +$loop = React\EventLoop\Factory::create(); + +$process = new React\ChildProcess\Process('echo foo'); +$process->start($loop); + +$process->stdout->on('data', function ($chunk) { + echo $chunk; +}); + +$process->on('exit', function($exitCode, $termSignal) { + echo 'Process exited with code ' . $exitCode . PHP_EOL; +}); + +$loop->run(); +``` + +See also the [examples](examples). + ## Processes ### EventEmitter Events @@ -81,26 +102,6 @@ $process->stdin->close(); For more details, see the [`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface). -## Usage -```php - $loop = React\EventLoop\Factory::create(); - - $process = new React\ChildProcess\Process('echo foo'); - - $process->on('exit', function($exitCode, $termSignal) { - // ... - }); - - $loop->addTimer(0.001, function($timer) use ($process) { - $process->start($timer->getLoop()); - - $process->stdout->on('data', function($output) { - // ... - }); - }); - - $loop->run(); -``` ### Prepending Commands with `exec` Symfony pull request [#5759](https://github.com/symfony/symfony/issues/5759) diff --git a/examples/01-stdio.php b/examples/01-stdio.php new file mode 100644 index 0000000..217e7f6 --- /dev/null +++ b/examples/01-stdio.php @@ -0,0 +1,32 @@ +start($loop); + +$process->stdout->on('data', function ($chunk) { + echo $chunk; +}); + +$process->on('exit', function ($code) { + echo 'EXIT with code ' . $code . PHP_EOL; +}); + +// periodically send something to stream +$periodic = $loop->addPeriodicTimer(0.2, function () use ($process) { + $process->stdin->write('hello'); +}); + +// stop sending after a few seconds +$loop->addTimer(2.0, function () use ($periodic, $loop, $process) { + $loop->cancelTimer($periodic); + $process->stdin->end(); +}); + +$loop->run(); diff --git a/examples/02-race.php b/examples/02-race.php new file mode 100644 index 0000000..5e4062c --- /dev/null +++ b/examples/02-race.php @@ -0,0 +1,24 @@ +start($loop); + +$second = new Process('sleep 1; echo hallo'); +$second->start($loop); + +$first->stdout->on('data', function ($chunk) { + echo $chunk; +}); + +$second->stdout->on('data', function ($chunk) { + echo $chunk; +}); + +$loop->run(); diff --git a/examples/03-stdout-stderr.php b/examples/03-stdout-stderr.php new file mode 100644 index 0000000..37fe837 --- /dev/null +++ b/examples/03-stdout-stderr.php @@ -0,0 +1,25 @@ +&2;sleep 1;echo error;sleep 1;nope'); +$process->start($loop); + +$process->stdout->on('data', function ($chunk) { + echo '(' . $chunk . ')'; +}); + +$process->stderr->on('data', function ($chunk) { + echo '[' . $chunk . ']'; +}); + +$process->on('exit', function ($code) { + echo 'EXIT with code ' . $code . PHP_EOL; +}); + +$loop->run(); diff --git a/examples/11-benchmark-read.php b/examples/11-benchmark-read.php new file mode 100644 index 0000000..996a153 --- /dev/null +++ b/examples/11-benchmark-read.php @@ -0,0 +1,48 @@ +pause(); +$info->write('Counts number of chunks/bytes received from process STDOUT' . PHP_EOL); +$info->write('Command: ' . $cmd . PHP_EOL); +if (extension_loaded('xdebug')) { + $info->write('NOTICE: The "xdebug" extension is loaded, this has a major impact on performance.' . PHP_EOL); +} + +$process = new Process($cmd); +$process->start($loop); +$start = microtime(true); + +$chunks = 0; +$bytes = 0; +$process->stdout->on('data', function ($chunk) use (&$chunks, &$bytes) { + ++$chunks; + $bytes += strlen($chunk); +}); + +// print stream position once stream closes +$process->on('exit', function () use (&$chunks, &$bytes, $start, $info) { + $t = microtime(true) - $start; + + $info->write('read ' . $chunks . ' chunks with ' . $bytes . ' byte(s) in ' . round($t, 3) . ' second(s) => ' . round($bytes / 1024 / 1024 / $t, 1) . ' MiB/s' . PHP_EOL); + $info->write('peak memory usage of ' . round(memory_get_peak_usage(true) / 1024 / 1024, 1) . ' MiB' . PHP_EOL); +}); + +// report any other output/errors +$process->stdout->on('error', array($info, 'write')); +$process->stderr->on('data', 'printf'); +$process->stdout->on('error', 'printf'); + +$loop->run(); diff --git a/examples/12-benchmark-write.php b/examples/12-benchmark-write.php new file mode 100644 index 0000000..4f88e5e --- /dev/null +++ b/examples/12-benchmark-write.php @@ -0,0 +1,45 @@ +pause(); +$info->write('Pipes data to process STDIN' . PHP_EOL); +if (extension_loaded('xdebug')) { + $info->write('NOTICE: The "xdebug" extension is loaded, this has a major impact on performance.' . PHP_EOL); +} + +$process = new Process('dd of=/dev/zero'); +$process->start($loop); + +// 10000 * 100 KB => 1 GB +$i = 10000; +$chunk = str_repeat("\0", 100 * 1000); +$write = function () use ($chunk, $process, &$i, &$write) { + do { + --$i; + $continue = $process->stdin->write($chunk); + } while ($i && $continue); + + if ($i > 0) { + // buffer full => wait for drain to continue + $process->stdin->once('drain', $write); + } else { + $process->stdin->end(); + } +}; +$write(); + +// report any other output/errors +$process->stdout->on('data', 'printf'); +$process->stdout->on('error', 'printf'); +$process->stderr->on('data', 'printf'); +$process->stdout->on('error', 'printf'); + +$loop->run(); diff --git a/examples/13-benchmark-throughput.php b/examples/13-benchmark-throughput.php new file mode 100644 index 0000000..a4c936c --- /dev/null +++ b/examples/13-benchmark-throughput.php @@ -0,0 +1,60 @@ +pause(); +$info->write('Pipes data through process STDIN and reads STDOUT again' . PHP_EOL); +if (extension_loaded('xdebug')) { + $info->write('NOTICE: The "xdebug" extension is loaded, this has a major impact on performance.' . PHP_EOL); +} + +$process = new Process('cat'); +$process->start($loop); +$start = microtime(true); + +$chunks = 0; +$bytes = 0; +$process->stdout->on('data', function ($chunk) use (&$chunks, &$bytes) { + ++$chunks; + $bytes += strlen($chunk); +}); + +// 10000 * 100 KB => 1 GB +$i = 10000; +$chunk = str_repeat("\0", 100 * 1000); +$write = function () use ($chunk, $process, &$i, &$write) { + do { + --$i; + $continue = $process->stdin->write($chunk); + } while ($i && $continue); + + if ($i > 0) { + // buffer full => wait for drain to continue + $process->stdin->once('drain', $write); + } else { + $process->stdin->end(); + } +}; +$write(); + +// print stream position once process exits +$process->on('exit', function () use (&$chunks, &$bytes, $start, $info) { + $t = microtime(true) - $start; + + $info->write('read ' . $chunks . ' chunks with ' . $bytes . ' byte(s) in ' . round($t, 3) . ' second(s) => ' . round($bytes / 1024 / 1024 / $t, 1) . ' MiB/s' . PHP_EOL); + $info->write('peak memory usage of ' . round(memory_get_peak_usage(true) / 1024 / 1024, 1) . ' MiB' . PHP_EOL); +}); + +// report any other output/errors +$process->stdout->on('error', 'printf'); +$process->stderr->on('data', 'printf'); +$process->stdout->on('error', 'printf'); + +$loop->run();