Changeset View
Changeset View
Standalone View
Standalone View
src/filesystem/PhutilProcessQuery.php
- This file was added.
| <?php | |||||
| final class PhutilProcessQuery | |||||
| extends Phobject { | |||||
| private $isOverseer; | |||||
| private $instances; | |||||
| public function withIsOverseer($is_overseer) { | |||||
| $this->isOverseer = $is_overseer; | |||||
| return $this; | |||||
| } | |||||
| public function withInstances(array $instances) { | |||||
| $this->instances = $instances; | |||||
| return $this; | |||||
| } | |||||
| public function execute() { | |||||
| if (phutil_is_windows()) { | |||||
| throw new Exception( | |||||
| pht( | |||||
| 'Querying system processes is not currently supported on '. | |||||
| 'Windows.')); | |||||
| } | |||||
| // TODO: See T12827. This formulation likely does not work properly on | |||||
| // Solaris. | |||||
| list($processes) = execx('ps -o pid,command -a -x -w -w -w'); | |||||
| $processes = phutil_split_lines($processes, false); | |||||
| $refs = array(); | |||||
| foreach ($processes as $process) { | |||||
| $parts = preg_split('/\s+/', trim($process), 2); | |||||
| list($pid, $command) = $parts; | |||||
| $ref = id(new PhutilProcessRef()) | |||||
| ->setPID((int)$pid); | |||||
| $argv = $this->getArgv($pid, $command); | |||||
| $ref->setArgv($argv); | |||||
| // If this is an overseer and the command has a "-l" ("Label") argument, | |||||
| // the argument contains the "PHABRICATOR_INSTANCE" value for the daemon. | |||||
| // Parse it out and annotate the process. | |||||
| $instance = null; | |||||
| if ($ref->getIsOverseer()) { | |||||
| $matches = null; | |||||
| if (preg_match('/-l (\S+)/', $command, $matches)) { | |||||
| $instance = $matches[1]; | |||||
| } | |||||
| } | |||||
| $ref->setInstance($instance); | |||||
| $refs[] = $ref; | |||||
| } | |||||
| if ($this->isOverseer !== null) { | |||||
| foreach ($refs as $key => $ref) { | |||||
| if ($ref->getIsOverseer() !== $this->isOverseer) { | |||||
| unset($refs[$key]); | |||||
| } | |||||
| } | |||||
| } | |||||
| if ($this->instances) { | |||||
| $instances_map = array_fuse($this->instances); | |||||
| foreach ($refs as $key => $ref) { | |||||
| if (!isset($instances_map[$ref->getInstance()])) { | |||||
| unset($refs[$key]); | |||||
| } | |||||
| } | |||||
| } | |||||
| return array_values($refs); | |||||
| } | |||||
| private function getArgv($pid, $command) { | |||||
| // In the output of "ps", arguments in process titles are not escaped, so | |||||
| // we can not distinguish between the processes created by running these | |||||
| // commands by looking only at the output of "ps": | |||||
| // | |||||
| // echo 'a b' | |||||
| // echo a b | |||||
| // | |||||
| // Both commands will have the same process title in the output of "ps". | |||||
| // This means we may split the command incorrectly in the general case, | |||||
| // and this misparsing may be important if the process binary resides in | |||||
| // a directory with spaces in its path and we're trying to identify which | |||||
| // binary a process is running. | |||||
| // On Ubuntu, and likely most other Linux systems, we can get a raw | |||||
| // command line from "/proc" with arguments delimited by "\0". | |||||
| // On macOS, there's no "/proc" and we don't currently have a robust way | |||||
| // to split the process command in a way that parses spaces properly, so | |||||
| // fall back to a best effort based on the output of "ps". This is almost | |||||
| // always correct, since it is uncommon to put binaries under paths with | |||||
| // spaces in them. | |||||
| $proc_cmdline = sprintf('/proc/%d/cmdline', $pid); | |||||
| try { | |||||
| $argv = Filesystem::readFile($proc_cmdline); | |||||
| $argv = explode("\0", $argv); | |||||
| // The output itself is terminated with "\0", so remove the final empty | |||||
| // argument. | |||||
| if (last($argv) === '') { | |||||
| array_pop($argv); | |||||
| } | |||||
| return $argv; | |||||
| } catch (Exception $ex) { | |||||
| // If we fail to read "/proc", fall through to less reliable methods. | |||||
| } | |||||
| // If we haven't found a better source, just split the "ps" output on | |||||
| // spaces. | |||||
| return preg_split('/\s+/', $command); | |||||
| } | |||||
| } | |||||