├── php-strace ├── scripts ├── phpunit └── version ├── .gitignore ├── readme.files └── php-strace.png ├── src ├── PhpStrace │ ├── Version.php │ ├── Exception.php │ ├── ExitException.php │ ├── Requirement.php │ ├── Observer.php │ ├── Requirement │ │ ├── Collection.php │ │ └── Result.php │ ├── Observerable.php │ ├── Linux.php │ ├── Root.php │ ├── CommandLine │ │ └── Execute │ │ │ └── Result.php │ ├── FileOutput.php │ ├── CommandLine.php │ ├── ProcessStatus.php │ ├── Strace.php │ └── Runner.php └── Bootstrap.php ├── puppet ├── modules │ ├── project │ │ ├── manifests │ │ │ ├── apt.pp │ │ │ ├── ant.pp │ │ │ ├── strace.pp │ │ │ ├── init.pp │ │ │ ├── bash.pp │ │ │ ├── php.pp │ │ │ └── nginx.pp │ │ └── files │ │ │ └── nginx │ │ │ └── sites-enabled │ │ │ └── default │ └── composer │ │ └── manifests │ │ └── init.pp └── manifests │ └── vm.box.pp ├── php-strace.php ├── tests ├── public │ ├── segfault.php │ └── index.php └── src │ └── PhpStrace │ ├── RootTest.php │ ├── FileOutputTest.php │ ├── Prerequirement │ └── ResultTest.php │ ├── LinuxTest.php │ ├── CommandLine │ └── Execute │ │ └── ResultTest.php │ ├── CommandLineTest.php │ ├── StraceTest.php │ ├── RunnerTest.php │ └── ProcessStatusTest.php ├── composer.json ├── phpunit.xml ├── Vagrantfile ├── LICENSE └── README.md /php-strace: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run($argv); 5 | -------------------------------------------------------------------------------- /src/PhpStrace/Exception.php: -------------------------------------------------------------------------------- 1 | installed, 5 | require => Class["project::apt"] 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /puppet/modules/project/manifests/strace.pp: -------------------------------------------------------------------------------- 1 | class project::strace { 2 | 3 | package { "strace": 4 | ensure => installed, 5 | require => Class["project::apt"] 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /scripts/version: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | root, 5 | group => root, 6 | mode => 644, 7 | content => 'export PS1=\'\[\e[1;32m\][\u@\h \W]\$\[\e[0m\] \'; cd /vagrant' 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "repositories": [ 3 | { 4 | "type": "composer", 5 | "url": "http://packages.zendframework.com/" 6 | } 7 | ], 8 | "require": { 9 | "phpunit/phpunit": "4.5.*", 10 | "zendframework/zend-console": "2.3.*" 11 | } 12 | } -------------------------------------------------------------------------------- /src/PhpStrace/Requirement/Collection.php: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | php-strace 8 | 9 | 10 | 11 | segfault me!

'; 14 | ?> 15 | 16 | -------------------------------------------------------------------------------- /puppet/modules/project/manifests/php.pp: -------------------------------------------------------------------------------- 1 | class project::php { 2 | 3 | package { "php5-cli": 4 | require => Class["project::apt"], 5 | ensure => installed, 6 | } 7 | 8 | package { "php5-fpm": 9 | require => Class["project::apt"], 10 | ensure => installed, 11 | } 12 | 13 | service { "php5-fpm": 14 | require => Package["php5-fpm"], 15 | ensure => running, 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/PhpStrace/Observerable.php: -------------------------------------------------------------------------------- 1 | "present", 7 | } 8 | 9 | File { owner => 0, group => 0, mode => 0644 } 10 | 11 | file { '/etc/motd': 12 | content => "Welcome to your Vagrant-built virtual machine! 13 | Managed by Puppet.\n" 14 | } 15 | 16 | class { 'project': } 17 | class { 'composer': } 18 | 19 | 20 | } 21 | 22 | include vm 23 | -------------------------------------------------------------------------------- /tests/src/PhpStrace/RootTest.php: -------------------------------------------------------------------------------- 1 | checkRequirements(); 13 | $this->assertFalse($result->getSucess()); 14 | $this->assertEquals('root access required. please execute this script as root.', $result->getErrorMessage()); 15 | } 16 | } -------------------------------------------------------------------------------- /puppet/modules/project/files/nginx/sites-enabled/default: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80 default; 3 | server_name localhost; 4 | 5 | access_log /tmp/nginx.access.log; 6 | error_log /tmp/nginx.error.log debug; 7 | 8 | root /vagrant/tests/public; 9 | 10 | index index.php; 11 | 12 | location ~ \.php$ { 13 | fastcgi_pass unix:/var/run/php5-fpm.sock; 14 | include fastcgi_params; 15 | } 16 | 17 | if (!-e $request_filename) { 18 | rewrite ^.*$ /index.php last; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /puppet/modules/project/manifests/nginx.pp: -------------------------------------------------------------------------------- 1 | 2 | class project::nginx { 3 | 4 | package { "nginx": 5 | ensure => installed, 6 | require => Class["project::apt"] 7 | } 8 | 9 | file { "/etc/nginx/sites-enabled/default": 10 | require => Package["nginx"], 11 | owner => root, 12 | group => root, 13 | mode => 644, 14 | source => "puppet:///modules/project/nginx/sites-enabled/default", 15 | notify => Service["nginx"], 16 | } 17 | 18 | service { "nginx": 19 | require => Package["nginx"], 20 | ensure => running, 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /tests/src/PhpStrace/FileOutputTest.php: -------------------------------------------------------------------------------- 1 | setTime(1354537347); 12 | 13 | $cl = new \PhpStrace\CommandLine(); 14 | 15 | $fo->notify($cl, array('text' => 'test')); 16 | 17 | fseek($tmpFile, 0); 18 | $this->assertEquals('2012-12-03 04:22:27 - test' . PHP_EOL, fread($tmpFile, 64)); 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /tests/src/PhpStrace/Prerequirement/ResultTest.php: -------------------------------------------------------------------------------- 1 | setSucess(true); 11 | $this->assertTrue($result->getSucess()); 12 | } 13 | 14 | public function testSetGetMessage () 15 | { 16 | $result = new \PhpStrace\Requirement\Result(); 17 | $result->setErrorMessage($msg = 'something failed'); 18 | $this->assertEquals($msg, $result->getErrorMessage()); 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /src/Bootstrap.php: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | tests/ 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/PhpStrace/Linux.php: -------------------------------------------------------------------------------- 1 | os = $os; 19 | } 20 | 21 | /** 22 | * @return string 23 | */ 24 | public function getOS () 25 | { 26 | return $this->os; 27 | } 28 | 29 | /** 30 | * @return Requirement\Result 31 | */ 32 | public function checkRequirements () 33 | { 34 | $result = new Requirement\Result(); 35 | if ($this->getOS() == 'Linux') { 36 | $result->setSucess(true); 37 | } else { 38 | $result->setErrorMessage('this program works only with linux'); 39 | } 40 | 41 | return $result; 42 | } 43 | } -------------------------------------------------------------------------------- /src/PhpStrace/Root.php: -------------------------------------------------------------------------------- 1 | setSucess(true); 19 | } else { 20 | $result->setSucess(false); 21 | $result->setErrorMessage('posix extension missing. please install php posix extension'); 22 | return $result; 23 | } 24 | 25 | if (posix_getuid() == 0) { 26 | $result->setSucess(true); 27 | } else { 28 | $result->setSucess(false); 29 | $result->setErrorMessage('root access required. please execute this script as root.'); 30 | 31 | } 32 | 33 | return $result; 34 | } 35 | } -------------------------------------------------------------------------------- /tests/src/PhpStrace/LinuxTest.php: -------------------------------------------------------------------------------- 1 | setOS($os = 'Windows'); 11 | $this->assertEquals($os, $linux->getOS()); 12 | } 13 | 14 | public function testcheckRequirementsSuccess () 15 | { 16 | $linux = new \PhpStrace\Linux(); 17 | 18 | $result = $linux->checkRequirements(); 19 | $this->assertTrue($result->getSucess()); 20 | } 21 | 22 | public function testcheckRequirementsFailure () 23 | { 24 | $linux = new \PhpStrace\Linux(); 25 | $linux->setOS('Windows'); 26 | 27 | $result = $linux->checkRequirements(); 28 | $this->assertFalse($result->getSucess()); 29 | $this->assertEquals('this program works only with linux', $result->getErrorMessage()); 30 | } 31 | } -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | 3 | # Every Vagrant virtual environment requires a box to build off of. 4 | config.vm.box = "mex_v5" 5 | config.vm.box_url = "http://dl.dropbox.com/u/32252351/mex_v5.box" 6 | 7 | config.vm.provider :virtualbox do |vb| 8 | vb.customize ["modifyvm", :id, "--memory", "512"] 9 | vb.customize ["modifyvm", :id, "--nestedpaging", "off"] 10 | # vb.gui = true 11 | end 12 | 13 | config.vm.synced_folder ".", "/vagrant" 14 | 15 | #Forward a port from the guest to the host, which allows for outside computers to access the VM, whereas host only networking does not. 16 | config.vm.network :forwarded_port, guest: 80, host: 8080 #php5-fpm 17 | 18 | # Puppet provision 19 | config.vm.provision :puppet, :module_path => "puppet/modules" do |puppet| 20 | puppet.manifests_path = "puppet/manifests" 21 | puppet.manifest_file = "vm.box.pp" 22 | end 23 | 24 | end 25 | -------------------------------------------------------------------------------- /src/PhpStrace/Requirement/Result.php: -------------------------------------------------------------------------------- 1 | errorMessage = (string) $errorMessage; 24 | } 25 | 26 | /** 27 | * @return string 28 | */ 29 | public function getErrorMessage () 30 | { 31 | return $this->errorMessage; 32 | } 33 | 34 | /** 35 | * @param $sucess 36 | */ 37 | public function setSucess ($sucess) 38 | { 39 | $this->sucess = (boolean) $sucess; 40 | } 41 | 42 | /** 43 | * @return bool 44 | */ 45 | public function getSucess () 46 | { 47 | return $this->sucess; 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /tests/src/PhpStrace/CommandLine/Execute/ResultTest.php: -------------------------------------------------------------------------------- 1 | setOutput($output = array( 11 | 'abc', 12 | 'def' 13 | )); 14 | $this->assertEquals($output, $result->getOutput()); 15 | } 16 | 17 | public function testSetGetReturnVar () 18 | { 19 | $result = new \PhpStrace\CommandLine\Execute\Result(); 20 | $result->setReturnVar($var = 1); 21 | $this->assertEquals($var, $result->getReturnVar()); 22 | } 23 | 24 | public function testConstruct () 25 | { 26 | $result = new \PhpStrace\CommandLine\Execute\Result($returnVar = 111, $output = array( 27 | 'test' 28 | )); 29 | 30 | $this->assertEquals($returnVar, $result->getReturnVar()); 31 | $this->assertEquals($output, $result->getOutput()); 32 | } 33 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012 Markus Perl - http://www.github.com/markus-perl 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /puppet/modules/composer/manifests/init.pp: -------------------------------------------------------------------------------- 1 | class composer { 2 | 3 | $downloadUrl = "http://getcomposer.org/composer.phar" 4 | $targetDir = "/opt/composer" 5 | 6 | file { $targetDir: 7 | ensure => directory, 8 | owner => "root", 9 | group => "root", 10 | mode => 0755, 11 | } 12 | 13 | exec { "composer download": 14 | command => "/usr/bin/wget ${downloadUrl}", 15 | cwd => $targetDir, 16 | require => File[$targetDir], 17 | unless => "/usr/bin/test -f composer.phar" 18 | } 19 | 20 | file { "${targetDir}/composer.phar": 21 | require => [Exec["composer download"]], 22 | ensure => "file", 23 | owner => "root", 24 | group => "root", 25 | mode => 0555, 26 | } 27 | 28 | file { "/usr/bin/composer": 29 | ensure => 'link', 30 | target => "${targetDir}/composer.phar", 31 | require => [File["${targetDir}/composer.phar"]], 32 | } 33 | 34 | exec { "composer install": 35 | environment => "COMPOSER_HOME=/home/vagrant", 36 | command => "/usr/bin/composer install", 37 | cwd => '/vagrant', 38 | require => [File["/usr/bin/composer"], Class["project::php"]], 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /src/PhpStrace/CommandLine/Execute/Result.php: -------------------------------------------------------------------------------- 1 | setReturnVar($returnVar); 25 | $this->setOutput($output); 26 | } 27 | 28 | /** 29 | * @param array $output 30 | */ 31 | public function setOutput (array $output) 32 | { 33 | foreach ($output as $line) { 34 | if (false === is_string($line)) { 35 | throw new \Exception('output array elements must be of string. ' . gettype($line) . ' given.'); 36 | } 37 | } 38 | 39 | $this->output = $output; 40 | } 41 | 42 | /** 43 | * @return array 44 | */ 45 | public function getOutput () 46 | { 47 | return $this->output; 48 | } 49 | 50 | /** 51 | * @param int $returnVar 52 | */ 53 | public function setReturnVar ($returnVar) 54 | { 55 | if ($returnVar !== null && false === is_int($returnVar)) { 56 | throw new \Exception('returnVar must be of type int or null. ' . gettype($returnVar) . ' given.'); 57 | } 58 | 59 | $this->returnVar = $returnVar; 60 | } 61 | 62 | /** 63 | * @return int 64 | */ 65 | public function getReturnVar () 66 | { 67 | return $this->returnVar; 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | php-strace 2 | ========== 3 | 4 | php-strace helps to track down segfaults in running php processes. It starts a new strace instance for every 5 | running php5-cgi or php-fpm process to monitor whether a segfault happened. 6 | If a segfault occurs, it will display the strace output of the faulty process. 7 | 8 |

9 | php-strace workflow 10 |

11 | 12 | 13 | Requirements 14 | ------------ 15 | 16 | * Linux 17 | * PHP 5.3.3 or later 18 | * strace installed 19 | * root access 20 | 21 | 22 | Installation and Downloads 23 | -------------------------- 24 | 25 | Download latest version and extract it to any folder 26 | 27 | * [php-strace-0.3.tar.gz](https://dl.dropboxusercontent.com/u/32252351/github/php-strace-0.3.tar.gz) 28 | * [php-strace-0.2.tar.gz](https://dl.dropbox.com/u/32252351/github/php-strace-0.2.tar.gz) 29 | 30 | 31 | Usage 32 | ---------------------- 33 | 34 | $ sudo ./php-strace 35 | 36 | 37 | Commandline options 38 | ------------------- 39 | 40 | Usage: ./php-strace [ options ] 41 | -h|--help show this help 42 | -l|--lines output the last N lines of a stacktrace. Default: 100 43 | --process-name name of running php processes. Default: autodetect 44 | --live search while running for new upcoming pid's 45 | 46 | 47 | Development 48 | ---------- 49 | 50 | * Checkout repository 51 | * Install vagrant and then run 52 | 53 | $ vagrant up 54 | $ vagrant ssh 55 | $ ./php-strace 56 | 57 | 58 | Testing 59 | ------- 60 | 61 | To run the tests ssh to your vagrant machine and enter: 62 | 63 | $ /vagrant/scripts/phpunit 64 | 65 | 66 | Contact 67 | ------- 68 | * Github: [http://www.github.com/markus-perl/php-strace](http://www.github.com/markus-perl/php-strace) 69 | * E-Mail: markus open-mmx.de 70 | -------------------------------------------------------------------------------- /tests/src/PhpStrace/CommandLineTest.php: -------------------------------------------------------------------------------- 1 | setStdout($tmpFile); 13 | $cl->stdout('test'); 14 | 15 | fseek($tmpFile, 0); 16 | $this->assertEquals('test', fread($tmpFile, 4)); 17 | } 18 | 19 | public function testStdErr () 20 | { 21 | $cl = new \PhpStrace\CommandLine(); 22 | 23 | $tmpFile = tmpfile(); 24 | $cl->setStderr($tmpFile); 25 | $cl->stderr('test'); 26 | 27 | fseek($tmpFile, 0); 28 | $this->assertEquals('test', fread($tmpFile, 4)); 29 | } 30 | 31 | public function testIsToolInstalledTrue () 32 | { 33 | $cl = new \PhpStrace\CommandLine(); 34 | $this->assertTrue($cl->isToolInstalled('ls')); 35 | } 36 | 37 | public function testIsToolInstalledFalse () 38 | { 39 | $cl = new \PhpStrace\CommandLine(); 40 | $this->assertFalse($cl->isToolInstalled('foobar')); 41 | } 42 | 43 | public function testExectute () 44 | { 45 | $cl = new \PhpStrace\CommandLine(); 46 | $result = $cl->execute('/vagrant/php-strace -h'); 47 | 48 | $this->assertEquals(0, $result->getReturnVar()); 49 | 50 | $output = $result->getOutput(); 51 | $this->assertContains('http://www.github.com/markus-perl/php-strace', $output[0]); 52 | } 53 | 54 | public function testAttachObserver () 55 | { 56 | $cl = new \PhpStrace\CommandLine(); 57 | 58 | $tmpFile = tmpfile(); 59 | $cl->setStdout($tmpFile); 60 | $cl->setStderr($tmpFile); 61 | 62 | $observer = $this->getMock('\PhpStrace\FileOutput', array(), array('/tmp/file')); 63 | $observer->expects($this->exactly(2))->method('notify')->with($cl, array('text' => 'test')); 64 | $cl->attachObserver($observer, 'stdout'); 65 | $cl->attachObserver($observer, 'stderr'); 66 | $cl->stdout('test'); 67 | $cl->stderr('test'); 68 | } 69 | } -------------------------------------------------------------------------------- /src/PhpStrace/FileOutput.php: -------------------------------------------------------------------------------- 1 | time = (int) $time; 28 | } 29 | 30 | /** 31 | * @return int 32 | */ 33 | public function getTime () 34 | { 35 | if (null === $this->time) { 36 | return time(); 37 | } 38 | 39 | return $this->time; 40 | } 41 | 42 | /** 43 | * @return string 44 | */ 45 | public function getFilePath () 46 | { 47 | return $this->filePath; 48 | } 49 | 50 | /** 51 | * @param string|resource $filePath 52 | */ 53 | public function __construct ($filePath = null) 54 | { 55 | if (is_string($filePath)) { 56 | $this->filePath = $filePath; 57 | } elseif (is_resource($filePath)) { 58 | $this->fileHandle = $filePath; 59 | } 60 | 61 | if (false === is_resource($this->fileHandle)) { 62 | 63 | if (null === $this->filePath) { 64 | throw new Exception('path to logfile not set'); 65 | } 66 | 67 | $this->fileHandle = @fopen($this->filePath, 'a'); 68 | 69 | if (false == $this->fileHandle) { 70 | throw new ExitException('cannot open file ' . $this->filePath . ' for writing.'); 71 | } 72 | } 73 | } 74 | 75 | public function notify (Observerable $observerable, $data = array()) 76 | { 77 | if (isset($data['text'])) { 78 | $formatted = sprintf('%s - %s' . PHP_EOL, date('Y-m-d H:i:s', $this->getTime()), $data['text']); 79 | 80 | $writeResult = fwrite($this->fileHandle, $formatted); 81 | } 82 | } 83 | 84 | public function __destruct () 85 | { 86 | if (is_resource($this->fileHandle)) { 87 | fclose($this->fileHandle); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/PhpStrace/CommandLine.php: -------------------------------------------------------------------------------- 1 | stderr = $stderr; 29 | } 30 | 31 | /** 32 | * @param resource $stdout 33 | */ 34 | public function setStdout ($stdout) 35 | { 36 | $this->stdout = $stdout; 37 | } 38 | 39 | /** 40 | * @param Observer $observer 41 | * @param string $eventType 42 | */ 43 | public function attachObserver (Observer $observer, $eventType) 44 | { 45 | if (false === isset($this->observers[$eventType])) { 46 | $this->observers[$eventType] = array(); 47 | } 48 | 49 | $this->observers[$eventType][] = $observer; 50 | } 51 | 52 | /** 53 | * @param string $eventType 54 | * @param array $data 55 | */ 56 | public function fireEvent ($eventType, $data = array()) 57 | { 58 | foreach ($this->getObservers($eventType) as $observer) { 59 | $observer->notify($this, $data); 60 | } 61 | } 62 | 63 | /** 64 | * @param string $eventType 65 | * @return array 66 | */ 67 | public function getObservers ($eventType) 68 | { 69 | if (isset($this->observers[$eventType])) { 70 | return $this->observers[$eventType]; 71 | } 72 | return array(); 73 | } 74 | 75 | /** 76 | * Checks if a specified command line tool 77 | * is installed 78 | * 79 | * @param string $cmd 80 | * @return boolean 81 | */ 82 | public function isToolInstalled ($cmd) 83 | { 84 | if ($this->execute($cmd)->getReturnVar() == 0) { 85 | return true; 86 | } 87 | 88 | return false; 89 | } 90 | 91 | /** 92 | * @param $cmd 93 | * @return Result 94 | */ 95 | public function execute ($cmd) 96 | { 97 | //redirect everything to stdin 98 | $cmd .= ' 2>&1'; 99 | 100 | exec($cmd, $output, $returnVar); 101 | return new Result($returnVar, $output); 102 | } 103 | 104 | /** 105 | * @param $text 106 | */ 107 | public function stdout ($text) 108 | { 109 | if (null == $this->stdout) { 110 | $this->stdout = fopen('php://stdout', 'w'); 111 | } 112 | 113 | fwrite($this->stdout, $text . PHP_EOL); 114 | $this->fireEvent('stdout', array( 115 | 'text' => $text 116 | )); 117 | } 118 | 119 | /** 120 | * @param $text 121 | */ 122 | public function stderr ($text) 123 | { 124 | if (null == $this->stderr) { 125 | $this->stderr = fopen('php://stderr', 'w'); 126 | } 127 | 128 | fwrite($this->stderr, $text . PHP_EOL); 129 | 130 | $this->fireEvent('stderr', array( 131 | 'text' => $text 132 | )); 133 | } 134 | 135 | /** 136 | * 137 | */ 138 | public function __destruct () 139 | { 140 | if (is_resource($this->stdout)) { 141 | fclose($this->stdout); 142 | } 143 | 144 | if (is_resource($this->stderr)) { 145 | fclose($this->stderr); 146 | } 147 | } 148 | 149 | 150 | } -------------------------------------------------------------------------------- /src/PhpStrace/ProcessStatus.php: -------------------------------------------------------------------------------- 1 | cmd = (string) $cmd; 34 | } 35 | 36 | public function getCmd () 37 | { 38 | return $this->cmd; 39 | } 40 | 41 | /** 42 | * @param string $scriptName 43 | */ 44 | public function setScriptName ($scriptName) 45 | { 46 | $this->scriptName = (string) $scriptName; 47 | } 48 | 49 | /** 50 | * @return string 51 | */ 52 | public function getScriptName () 53 | { 54 | return $this->scriptName; 55 | } 56 | 57 | /** 58 | * @param string $processName 59 | */ 60 | public function setProcessName ($processName) 61 | { 62 | $this->processName = (string) $processName; 63 | } 64 | 65 | /** 66 | * @return string 67 | */ 68 | public function getProcessName () 69 | { 70 | if ($this->processName == self::PROCESS_NAME_AUTODETECT) { 71 | if ($this->isProcessRunning('php5-cgi')) { 72 | $this->setProcessName('php5-cgi'); 73 | } else { 74 | $this->setProcessName('php-fpm'); 75 | } 76 | } 77 | 78 | return $this->processName; 79 | } 80 | 81 | /** 82 | * @param CommandLine $commandLine 83 | */ 84 | public function __construct (CommandLine $commandLine, $scriptName = null) 85 | { 86 | $this->commandLine = $commandLine; 87 | 88 | if ($scriptName) { 89 | $this->scriptName = $scriptName; 90 | } 91 | } 92 | 93 | /** 94 | * @return Requirement\Result 95 | */ 96 | public function checkRequirements () 97 | { 98 | $result = new Requirement\Result(); 99 | if ($this->commandLine->isToolInstalled($this->cmd)) { 100 | $result->setSucess(true); 101 | } else { 102 | $result->setErrorMessage('command line tool "ps" ist not installed. Please install "ps".'); 103 | } 104 | 105 | return $result; 106 | } 107 | 108 | 109 | /** 110 | * @return array 111 | * @throws Exception 112 | */ 113 | public function fetchPhpProcessIds () 114 | { 115 | $result = $this->commandLine->execute($this->cmd . ' xa'); 116 | 117 | $pids = array(); 118 | foreach ($result->getOutput() as $line) { 119 | if (mb_substr_count($line, $this->getProcessName()) && mb_substr_count($line, $this->getScriptName()) == 0) { 120 | preg_match('/[0-9]+/', $line, $matches); 121 | if (false === isset($matches[0]) || false === is_numeric($matches[0]) || $matches[0] < 1) { 122 | throw new Exception('faild to fetch pids'); 123 | } 124 | 125 | $pids[] = (int) $matches[0]; 126 | } 127 | } 128 | 129 | return $pids; 130 | } 131 | 132 | /** 133 | * @param $name 134 | * @return bool 135 | */ 136 | public function isProcessRunning ($name) 137 | { 138 | $result = $this->commandLine->execute($this->cmd . ' xa'); 139 | foreach ($result->getOutput() as $line) { 140 | if (mb_substr_count($line, $name)) { 141 | return true; 142 | } 143 | } 144 | 145 | return false; 146 | } 147 | 148 | 149 | } 150 | -------------------------------------------------------------------------------- /src/PhpStrace/Strace.php: -------------------------------------------------------------------------------- 1 | cmd = (string) $cmd; 34 | } 35 | 36 | public function getCmd () 37 | { 38 | return $this->cmd; 39 | } 40 | 41 | /** 42 | * @param int $numLines 43 | */ 44 | public function setLines ($numLines) 45 | { 46 | $numLines = (int) $numLines; 47 | 48 | if ($numLines < 1 || $numLines > 10000) { 49 | throw new Exception('invalid line count'); 50 | } 51 | 52 | $this->lines = $numLines; 53 | } 54 | 55 | /** 56 | * @return int 57 | */ 58 | public function getLines () 59 | { 60 | return $this->lines; 61 | } 62 | 63 | /** 64 | * @param int $lineLength 65 | */ 66 | public function setLineLength ($lineLength) 67 | { 68 | $lineLength = (int) $lineLength; 69 | 70 | //max 1MB 71 | if ($lineLength < 1 || $lineLength > 1 * 1024 * 1024) { 72 | throw new Exception('invalid line length'); 73 | } 74 | 75 | $this->lineLength = $lineLength; 76 | } 77 | 78 | /** 79 | * @return int 80 | */ 81 | public function getLineLength () 82 | { 83 | return $this->lineLength; 84 | } 85 | 86 | 87 | /** 88 | * @param CommandLine $commandLine 89 | */ 90 | public function __construct (CommandLine $commandLine) 91 | { 92 | $this->commandLine = $commandLine; 93 | } 94 | 95 | /** 96 | * @return Requirement\Result 97 | */ 98 | public function checkRequirements () 99 | { 100 | $commandLine = new CommandLine(); 101 | 102 | $result = new Requirement\Result(); 103 | if ($commandLine->isToolInstalled($this->cmd . ' -h')) { 104 | $result->setSucess(true); 105 | } else { 106 | $result->setErrorMessage('command line tool "strace" ist not installed. Please install "strace".'); 107 | } 108 | 109 | if ($result->getSucess()) { 110 | if (false === $commandLine->isToolInstalled('tail --help')) { 111 | $result->setSucess(false); 112 | $result->setErrorMessage('command line tool "tail" is not installed. please install "tail".'); 113 | } 114 | } 115 | 116 | if ($result->getSucess()) { 117 | if (false === function_exists('pcntl_fork')) { 118 | $result->setSucess(false); 119 | $result->setErrorMessage('PCNTL extension not found. Please install PHP PCNTL extension. If the extension is installed, check your php.ini if there are some pcntl functions disabled (https://github.com/markus-perl/php-strace/issues/1).'); 120 | } 121 | } 122 | 123 | return $result; 124 | } 125 | 126 | 127 | /** 128 | * @param int $phpPid 129 | * @param bool testing 130 | * @return bool isChild 131 | */ 132 | public function watch ($phpPid, $testing = false) 133 | { 134 | $pid = null; 135 | if ($testing == false) { 136 | $pid = pcntl_fork(); 137 | } 138 | 139 | if ($pid === -1) { 140 | throw new Exception('could not create fork for pid ' . $pid); 141 | } 142 | 143 | if ($pid && $testing == false) { 144 | pcntl_waitpid($pid, $status, WNOHANG); 145 | return $pid; 146 | } else { 147 | // child process runs what is here 148 | $this->commandLine->stdout('starting strace on pid ' . $phpPid . '.'); 149 | 150 | $result = $this->commandLine->execute($this->cmd . ' -q -s ' . $this->getLineLength() . ' -p ' . escapeshellarg($phpPid) . ' 2>&1 | tail -' . $this->getLines()); 151 | 152 | $this->commandLine->stdout('pid ' . $phpPid . ' finished running with exit code ' . $result->getReturnVar() . '.'); 153 | 154 | if ($result->getReturnVar() != 0 || substr_count(implode(' ', $result->getOutput()), self::SIGSEGV)) { 155 | foreach ($result->getOutput() as $line) { 156 | $this->commandLine->stdout($line); 157 | } 158 | } 159 | 160 | return false; 161 | } 162 | } 163 | 164 | } 165 | -------------------------------------------------------------------------------- /tests/src/PhpStrace/StraceTest.php: -------------------------------------------------------------------------------- 1 | getMock('\PhpStrace\CommandLine'); 10 | $strace = new \PhpStrace\Strace($commandLine); 11 | $strace->setLines($lines = 555); 12 | 13 | $this->assertEquals($lines, $strace->getLines()); 14 | } 15 | 16 | public function testSetGetLineLength () 17 | { 18 | $commandLine = $this->getMock('\PhpStrace\CommandLine'); 19 | $strace = new \PhpStrace\Strace($commandLine); 20 | $strace->setLineLength($length = 1024); 21 | 22 | $this->assertEquals(1024, $strace->getLineLength()); 23 | } 24 | 25 | public function testSetGetCmd () 26 | { 27 | $commandLine = $this->getMock('\PhpStrace\CommandLine'); 28 | $strace = new \PhpStrace\Strace($commandLine); 29 | $strace->setCmd($cmd = 'ps'); 30 | 31 | $this->assertEquals($cmd, $strace->getCmd()); 32 | } 33 | 34 | public function testcheckRequirementsSuccess () 35 | { 36 | $commandLine = $this->getMock('\PhpStrace\CommandLine'); 37 | $strace = new \PhpStrace\Strace($commandLine); 38 | 39 | $result = $strace->checkRequirements(); 40 | 41 | $this->assertTrue($result->getSucess()); 42 | } 43 | 44 | public function testcheckRequirementsFailure () 45 | { 46 | $commandLine = $this->getMock('\PhpStrace\CommandLine'); 47 | $strace = new \PhpStrace\Strace($commandLine); 48 | $strace->setCmd('invalidCmd'); 49 | 50 | $result = $strace->checkRequirements(); 51 | 52 | $this->assertFalse($result->getSucess()); 53 | $this->assertEquals('command line tool "strace" ist not installed. Please install "strace".', $result->getErrorMessage()); 54 | } 55 | 56 | public function testWatch () 57 | { 58 | $commandLine = $this->getMock('\PhpStrace\CommandLine', array( 59 | 'stdout', 60 | 'execute' 61 | )); 62 | $commandLine->expects($this->any())->method('stdout'); 63 | $commandLine->expects($this->any())->method('execute')->will($this->returnValue(new \PhpStrace\CommandLine\Execute\Result)); 64 | 65 | $strace = new \PhpStrace\Strace($commandLine); 66 | 67 | //stop child 68 | if (false === $strace->watch(123456)) { 69 | exit; 70 | } 71 | } 72 | 73 | public function testWatchOutputExitCode1 () 74 | { 75 | $result = new \PhpStrace\CommandLine\Execute\Result; 76 | $result->setReturnVar(1); 77 | $result->setOutput(array('error message')); 78 | 79 | $commandLine = $this->getMock('\PhpStrace\CommandLine', array( 80 | 'stdout', 81 | 'execute' 82 | )); 83 | $commandLine->expects($this->exactly(3))->method('stdout'); 84 | $commandLine->expects($this->any())->method('execute')->will($this->returnValue($result)); 85 | $commandLine->expects($this->at(3))->method('stdout')->with('error message'); 86 | 87 | $strace = new \PhpStrace\Strace($commandLine); 88 | 89 | $strace->watch(123456, true); 90 | } 91 | 92 | public function testWatchOutputExitCode0 () 93 | { 94 | $result = new \PhpStrace\CommandLine\Execute\Result; 95 | $result->setReturnVar(0); 96 | $result->setOutput(array('some output')); 97 | 98 | $commandLine = $this->getMock('\PhpStrace\CommandLine', array( 99 | 'stdout', 100 | 'execute' 101 | )); 102 | $commandLine->expects($this->exactly(2))->method('stdout'); 103 | $commandLine->expects($this->any())->method('execute')->will($this->returnValue($result)); 104 | 105 | $strace = new \PhpStrace\Strace($commandLine); 106 | 107 | $strace->watch(123456, true); 108 | } 109 | 110 | public function testWatchOutputSegfault () 111 | { 112 | $result = new \PhpStrace\CommandLine\Execute\Result; 113 | $result->setReturnVar(0); 114 | $result->setOutput(array( 115 | 'some output', 116 | \PhpStrace\Strace::SIGSEGV 117 | )); 118 | 119 | $commandLine = $this->getMock('\PhpStrace\CommandLine', array( 120 | 'stdout', 121 | 'execute' 122 | )); 123 | $commandLine->expects($this->exactly(4))->method('stdout'); 124 | $commandLine->expects($this->any())->method('execute')->will($this->returnValue($result)); 125 | $commandLine->expects($this->at(3))->method('stdout')->with('some output'); 126 | $commandLine->expects($this->at(4))->method('stdout')->with(\PhpStrace\Strace::SIGSEGV); 127 | 128 | 129 | $strace = new \PhpStrace\Strace($commandLine); 130 | 131 | $strace->watch(123456, true); 132 | } 133 | } -------------------------------------------------------------------------------- /tests/src/PhpStrace/RunnerTest.php: -------------------------------------------------------------------------------- 1 | bootstrap(); 12 | 13 | $this->assertEquals('32M', ini_get('memory_limit')); 14 | $this->assertEquals('-1', ini_get('max_execution_time')); 15 | } 16 | 17 | public function testShowWelcomeMessage () 18 | { 19 | $runner = new \PhpStrace\Runner(); 20 | $commandLine = $this->getMock('\PhpStrace\CommandLine', array('stdout')); 21 | 22 | $msg = 'php-strace ' . \PhpStrace\Version::ID . ' by Markus Perl (http://www.github.com/markus-perl/php-strace)'; 23 | $commandLine->expects($this->at(0))->method('stdout')->with($msg); 24 | $commandLine->expects($this->at(1))->method('stdout')->with(''); 25 | 26 | $runner->setCommandLine($commandLine); 27 | $runner->showWelcomeMessage(); 28 | } 29 | 30 | /** 31 | * @expectedException \Zend\Console\Exception\RuntimeException 32 | */ 33 | public function testParseGetOptHelp () 34 | { 35 | $argv = array( 36 | 'php-strace', 37 | '-h' 38 | ); 39 | 40 | $runner = new \PhpStrace\Runner(); 41 | $runner->parseGetOpt($argv); 42 | } 43 | 44 | public function testParseGetOptLines () 45 | { 46 | $argv = array( 47 | 'php-strace', 48 | '-l', 49 | '200' 50 | ); 51 | 52 | $runner = new \PhpStrace\Runner(); 53 | $runner->parseGetOpt($argv); 54 | $this->assertEquals(200, $runner->getStrace()->getLines()); 55 | } 56 | 57 | public function testParseGetOptLineLength () 58 | { 59 | $argv = array( 60 | 'php-strace', 61 | '--line-length', 62 | '300' 63 | ); 64 | 65 | $runner = new \PhpStrace\Runner(); 66 | $runner->parseGetOpt($argv); 67 | $this->assertEquals(300, $runner->getStrace()->getLineLength()); 68 | } 69 | 70 | public function testParseGetOptProcessName () 71 | { 72 | $argv = array( 73 | 'php-strace', 74 | '--process-name', 75 | 'php54-cgi' 76 | ); 77 | 78 | $runner = new \PhpStrace\Runner(); 79 | $runner->parseGetOpt($argv); 80 | $this->assertEquals('php54-cgi', $runner->getProcessStatus()->getProcessName()); 81 | } 82 | 83 | public function testParseGetOptLive() 84 | { 85 | $argv = array( 86 | 'php-strace', 87 | '--live', 88 | ); 89 | 90 | $runner = new \PhpStrace\Runner(); 91 | $runner->parseGetOpt($argv); 92 | $this->assertTrue($runner->getLive()); 93 | } 94 | 95 | /** 96 | * @expectedException \PhpStrace\ExitException 97 | * @expectedExceptionMessage cannot open file /foo/bar for writing. 98 | */ 99 | public function testParseGetOptOutputInvalidPath() 100 | { 101 | $argv = array( 102 | 'php-strace', 103 | '-o', 104 | '/foo/bar' 105 | ); 106 | 107 | $runner = new \PhpStrace\Runner(); 108 | $runner->parseGetOpt($argv); 109 | } 110 | 111 | /** 112 | */ 113 | public function testParseGetOptOutputValidPath() 114 | { 115 | $argv = array( 116 | 'php-strace', 117 | '-o', 118 | $path = '/tmp/php-strace.phpunit.log' 119 | ); 120 | 121 | $runner = new \PhpStrace\Runner(); 122 | $runner->parseGetOpt($argv); 123 | 124 | $observers = $runner->getCommandLine()->getObservers('stdout'); 125 | $this->assertInstanceOf('\PhpStrace\FileOutput', $observers[0]); 126 | $this->assertEquals($path, $observers[0]->getFilePath()); 127 | } 128 | 129 | 130 | 131 | public function testSetGetCommandLine () 132 | { 133 | $runner = new \PhpStrace\Runner(); 134 | $this->assertInstanceOf('\PhpStrace\CommandLine', $runner->getCommandLine()); 135 | 136 | $commandLine = new \PhpStrace\CommandLine(); 137 | $runner->setCommandLine($commandLine); 138 | 139 | $this->assertEquals($commandLine, $runner->getCommandLine()); 140 | } 141 | 142 | public function testGetProcessStatus () 143 | { 144 | $runner = new \PhpStrace\Runner(); 145 | $this->assertInstanceOf('\PhpStrace\ProcessStatus', $runner->getProcessStatus()); 146 | } 147 | 148 | public function testGetStrace () 149 | { 150 | $runner = new \PhpStrace\Runner(); 151 | $this->assertInstanceOf('\PhpStrace\Strace', $runner->getStrace()); 152 | } 153 | 154 | /** 155 | * @expectedException PhpStrace\ExitException 156 | */ 157 | public function testcheckRequirements () 158 | { 159 | $runner = new \PhpStrace\Runner(); 160 | 161 | $commandLine = $this->getMock('\PhpStrace\CommandLine', array('stderr')); 162 | $commandLine->expects($this->at(0))->method('stderr')->with('The following Requirements did not met:'); 163 | $commandLine->expects($this->at(1))->method('stderr')->with('root access required. please execute this script as root.'); 164 | $runner->setCommandLine($commandLine); 165 | 166 | $runner->checkRequirements(); 167 | } 168 | 169 | 170 | public function testSetGetLive () 171 | { 172 | $runner = new \PhpStrace\Runner(); 173 | $runner->setLive(true); 174 | $this->assertTrue($runner->getLive()); 175 | } 176 | 177 | } -------------------------------------------------------------------------------- /tests/src/PhpStrace/ProcessStatusTest.php: -------------------------------------------------------------------------------- 1 | getMock('\PhpStrace\CommandLine'); 10 | $ps = new \PhpStrace\ProcessStatus($commandLine); 11 | $ps->setScriptName($name = 'php-strace'); 12 | $this->assertEquals($name, $ps->getScriptName()); 13 | } 14 | 15 | public function testSetGetProcessName () 16 | { 17 | $commandLine = $this->getMock('\PhpStrace\CommandLine'); 18 | $ps = new \PhpStrace\ProcessStatus($commandLine); 19 | $ps->setProcessName($name = 'php53-cgi'); 20 | $this->assertEquals($name, $ps->getProcessName()); 21 | } 22 | 23 | 24 | public function testSetGetCmd () 25 | { 26 | $commandLine = $this->getMock('\PhpStrace\CommandLine'); 27 | $ps = new \PhpStrace\ProcessStatus($commandLine); 28 | $ps->setCmd($cmd = 'ps2'); 29 | $this->assertEquals($cmd, $ps->getCmd()); 30 | } 31 | 32 | public function testcheckRequirementsSuccess () 33 | { 34 | $commandLine = $this->getMock('\PhpStrace\CommandLine', array('isToolInstalled')); 35 | $commandLine->expects($this->once())->method('isToolInstalled')->will($this->returnValue(true)); 36 | $strace = new \PhpStrace\ProcessStatus($commandLine); 37 | $result = $strace->checkRequirements(); 38 | $this->assertTrue($result->getSucess()); 39 | } 40 | 41 | public function testcheckRequirementsFailure () 42 | { 43 | $commandLine = $this->getMock('\PhpStrace\CommandLine', array('isToolInstalled')); 44 | $commandLine->expects($this->once())->method('isToolInstalled')->will($this->returnValue(false)); 45 | $strace = new \PhpStrace\ProcessStatus($commandLine); 46 | $result = $strace->checkRequirements(); 47 | $this->assertFalse($result->getSucess()); 48 | $this->assertEquals('command line tool "ps" ist not installed. Please install "ps".', $result->getErrorMessage()); 49 | } 50 | 51 | public function testFetchPhpProcessIdsSuccess () 52 | { 53 | $result = new \PhpStrace\CommandLine\Execute\Result(0, array( 54 | '3460 ? S 0:01 sshd: vagrant@pts/1', 55 | '12192 ? Ss 0:00 /usr/bin/php5-cgi', 56 | ' 12193 ? S 0:00 /usr/bin/php5-cgi' 57 | )); 58 | 59 | 60 | $commandLine = $this->getMock('\PhpStrace\CommandLine', array('execute')); 61 | $commandLine->expects($this->once())->method('execute')->will($this->returnValue($result)); 62 | $ps = new \PhpStrace\ProcessStatus($commandLine); 63 | $ps->setProcessName('php5-cgi'); 64 | $result = $ps->fetchPhpProcessIds(); 65 | 66 | $this->assertEquals(array( 67 | 12192, 68 | 12193 69 | ), $result); 70 | } 71 | 72 | /** 73 | * @expectedException \PhpStrace\Exception 74 | */ 75 | public function testFetchPhpProcessIdsFailure () 76 | { 77 | $result = new \PhpStrace\CommandLine\Execute\Result(0, array( 78 | ' ? Ss 0:00 /usr/bin/php5-cgi', 79 | ' ? S 0:00 /usr/bin/php5-cgi' 80 | )); 81 | 82 | 83 | $commandLine = $this->getMock('\PhpStrace\CommandLine', array('execute')); 84 | $commandLine->expects($this->once())->method('execute')->will($this->returnValue($result)); 85 | $ps = new \PhpStrace\ProcessStatus($commandLine); 86 | $ps->setProcessName('php5-cgi'); 87 | 88 | $ps->fetchPhpProcessIds(); 89 | } 90 | 91 | public function testIsProcessRunningPhp5Cgi () 92 | { 93 | $result = new \PhpStrace\CommandLine\Execute\Result(0, array( 94 | '2398 ? Ss 0:00 /usr/bin/php5-cgi', 95 | '25398 ? S 0:00 /usr/bin/php5-cgi' 96 | )); 97 | 98 | $commandLine = $this->getMock('\PhpStrace\CommandLine', array('execute')); 99 | $commandLine->expects($this->exactly(2))->method('execute')->will($this->returnValue($result)); 100 | $ps = new \PhpStrace\ProcessStatus($commandLine); 101 | $this->assertTrue($ps->isProcessRunning('php5-cgi')); 102 | $this->assertFalse($ps->isProcessRunning('php-fpm')); 103 | } 104 | 105 | public function testIsProcessRunningPhpFpm () 106 | { 107 | $result = new \PhpStrace\CommandLine\Execute\Result(0, array( 108 | '22398 ? S 0:08 php-fpm: pool www', 109 | '22399 ? S 0:08 php-fpm: pool www ' 110 | )); 111 | 112 | $commandLine = $this->getMock('\PhpStrace\CommandLine', array('execute')); 113 | $commandLine->expects($this->exactly(2))->method('execute')->will($this->returnValue($result)); 114 | $ps = new \PhpStrace\ProcessStatus($commandLine); 115 | $this->assertFalse($ps->isProcessRunning('php5-cgi')); 116 | $this->assertTrue($ps->isProcessRunning('php-fpm')); 117 | } 118 | 119 | } -------------------------------------------------------------------------------- /src/PhpStrace/Runner.php: -------------------------------------------------------------------------------- 1 | live = (boolean) $live; 41 | } 42 | 43 | /** 44 | * @return boolean 45 | */ 46 | public function getLive () 47 | { 48 | return $this->live; 49 | } 50 | 51 | /** 52 | * @param string $scriptName 53 | */ 54 | public function setScriptName ($scriptName) 55 | { 56 | $this->scriptName = (string) $scriptName; 57 | } 58 | 59 | /** 60 | * @return string 61 | */ 62 | public function getScriptName () 63 | { 64 | return $this->scriptName; 65 | } 66 | 67 | /** 68 | * 69 | */ 70 | public function run (array $argv) 71 | { 72 | try { 73 | $this->setScriptName($argv[0]); 74 | $this->showWelcomeMessage(); 75 | $this->parseGetOpt($argv); 76 | $this->checkRequirements(); 77 | $this->bootstrap(); 78 | $this->watchPids(); 79 | } catch (Console\Exception\RuntimeException $e) { 80 | $this->getCommandLine()->stdout('php-strace starts a new strace instance for every running php5-cgi process and displays any segfault occurrence.'); 81 | $this->getCommandLine()->stdout($e->getUsageMessage()); 82 | } catch (ExitException $e) { 83 | if ($e->getMessage()) { 84 | $this->getCommandLine()->stdout($e->getMessage()); 85 | } 86 | exit($e->getCode()); 87 | } catch (\Exception $e) { 88 | $this->getCommandLine()->stderr('Line ' . $e->getLine() . ' ' . $e->getFile() . ' - ' . $e->getMessage()); 89 | } 90 | } 91 | 92 | /** 93 | * 94 | */ 95 | public function bootstrap () 96 | { 97 | if ('' == ini_get('date.timezone')) { 98 | date_default_timezone_set('Europe/Berlin'); 99 | } 100 | ini_set('memory_limit', '32M'); 101 | set_time_limit(-1); 102 | } 103 | 104 | /** 105 | * 106 | */ 107 | public function showWelcomeMessage () 108 | { 109 | $this->getCommandLine()->stdout('php-strace ' . Version::ID . ' by Markus Perl (http://www.github.com/markus-perl/php-strace)'); 110 | $this->getCommandLine()->stdout(''); 111 | } 112 | 113 | /** 114 | * @param array $argv 115 | * @throws \Zend\Console\Exception\RuntimeException 116 | */ 117 | public function parseGetOpt (array $argv) 118 | { 119 | 120 | $rules = array( 121 | 'h|help' => 'show this help', 122 | 'l|lines=i' => 'output the last N lines of a stacktrace. Default: 100', 123 | 'line-length=i' => 'maximum length of a line. Default 512', 124 | 'process-name=s' => 'name of running php processes. Default: autodetect', 125 | 'live' => 'search while running for new upcoming pid\'s', 126 | 'o|output=s' => 'output log to file' 127 | ); 128 | 129 | $opts = new Console\Getopt($rules, $argv); 130 | 131 | $opts->parse(); 132 | 133 | if ($opts->help) { 134 | throw new Console\Exception\RuntimeException('', $opts->getUsageMessage()); 135 | } 136 | 137 | if ($opts->getOption('lines')) { 138 | $this->getStrace()->setLines(min(1000, max(1, $opts->getOption('lines')))); 139 | } 140 | 141 | if ($opts->getOption('line-length')) { 142 | $this->getStrace()->setLineLength(min(1 * 1024 * 1024, max(10, $opts->getOption('line-length')))); 143 | } 144 | 145 | if ($opts->getOption('process-name')) { 146 | $this->getProcessStatus()->setProcessName($opts->getOption('process-name')); 147 | } 148 | 149 | if ($opts->getOption('output')) { 150 | $fileOutput = new FileOutput($opts->getOption('output')); 151 | $this->getCommandLine()->attachObserver($fileOutput, 'stdout'); 152 | $this->getCommandLine()->attachObserver($fileOutput, 'stderr'); 153 | } 154 | 155 | if ($opts->getOption('live')) { 156 | $this->setLive(true); 157 | } 158 | } 159 | 160 | /** 161 | * @return CommandLine 162 | */ 163 | public function getCommandLine () 164 | { 165 | if (null === $this->commandLine) { 166 | $this->commandLine = new CommandLine(); 167 | } 168 | 169 | return $this->commandLine; 170 | } 171 | 172 | /** 173 | * @param CommandLine $commandLine 174 | */ 175 | public function setCommandLine (CommandLine $commandLine = null) 176 | { 177 | $this->commandLine = $commandLine; 178 | } 179 | 180 | /** 181 | * @return ProcessStatus 182 | */ 183 | public function getProcessStatus () 184 | { 185 | if (null === $this->processStatus) { 186 | $this->processStatus = new ProcessStatus($this->getCommandLine(), $this->getScriptName()); 187 | } 188 | 189 | return $this->processStatus; 190 | } 191 | 192 | /** 193 | * @return Strace 194 | */ 195 | public function getStrace () 196 | { 197 | if (null === $this->strace) { 198 | $this->strace = new Strace($this->getCommandLine()); 199 | } 200 | 201 | return $this->strace; 202 | } 203 | 204 | /** 205 | * 206 | */ 207 | public function checkRequirements () 208 | { 209 | $collection = new Requirement\Collection; 210 | $collection->add(new Linux()); 211 | $collection->add(new Root()); 212 | $collection->add($this->getProcessStatus()); 213 | $collection->add($this->getStrace()); 214 | 215 | $errorMessages = array(); 216 | /* @var Requirement $Requirement */ 217 | foreach ($collection as $Requirement) { 218 | $result = $Requirement->checkRequirements(); 219 | 220 | if (false === $result->getSucess()) { 221 | $errorMessages[] = $result->getErrorMessage(); 222 | } 223 | } 224 | 225 | if (count($errorMessages)) { 226 | $this->getCommandLine()->stderr('The following Requirements did not met:'); 227 | foreach ($errorMessages as $message) { 228 | $this->getCommandLine()->stderr($message); 229 | } 230 | 231 | throw new ExitException('Requirements not met', 1); 232 | } 233 | } 234 | 235 | /** 236 | * @return array 237 | */ 238 | private function fetchPids () 239 | { 240 | $pids = $this->getProcessStatus()->fetchPhpProcessIds(); 241 | 242 | if (0 == count($pids)) { 243 | $this->getCommandLine()->stderr('No running php processes found'); 244 | throw new ExitException('no running processes', 1); 245 | } 246 | 247 | return $pids; 248 | } 249 | 250 | private function startStrace ($pid) 251 | { 252 | $pidWatching = $this->getStrace()->watch($pid); 253 | if (false == $pidWatching) { 254 | throw new ExitException('', 0); 255 | } 256 | 257 | return $pidWatching; 258 | } 259 | 260 | /** 261 | * @param array $pids 262 | */ 263 | public function watchPids () 264 | { 265 | $pids = $this->fetchPids(); 266 | 267 | $pidsWatching = array(); 268 | foreach ($pids as $pid) { 269 | $pidWatching = $this->startStrace($pid); 270 | $pidsWatching[] = $pidWatching; 271 | } 272 | 273 | usleep(100000); 274 | $this->getCommandLine()->stdout('Startup completed. If any segfault will happen, you will see it here. Press ctrl+c to exit.'); 275 | 276 | while (count($pidsWatching)) { 277 | $pid = pcntl_wait($status, WNOHANG); 278 | if ($pid) { 279 | $pidsWatching = array_diff($pidsWatching, array($pid)); 280 | $this->getCommandLine()->stdout('child with pid ' . $pid . ' terminated'); 281 | } 282 | 283 | if ($this->getLive()) { 284 | $currentPids = $this->fetchPids(); 285 | $diff = array_diff($currentPids, $pids); 286 | if (count($diff)) { 287 | foreach ($diff as $pid) { 288 | $pidWatching = $this->startStrace($pid); 289 | $pidsWatching[] = $pidWatching; 290 | } 291 | $pids = $currentPids; 292 | } 293 | } 294 | 295 | sleep(1); 296 | } 297 | } 298 | 299 | } --------------------------------------------------------------------------------