├── 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 |
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 | }
--------------------------------------------------------------------------------