├── .gitignore ├── README.md ├── bin ├── clockwork-cli └── compile ├── composer.json ├── screenshots ├── clockwork-cli-details.png └── clockwork-cli-trace.png └── src ├── Clockwork └── Cli │ ├── Application.php │ ├── Colors.php │ ├── Laravel │ └── Command.php │ ├── LogFileScanner.php │ └── Output │ ├── Base.php │ ├── Details.php │ └── ShortLine.php └── bootstrap.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | composer.lock 3 | vendor 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Clockwork-cli 2 | ============= 3 | 4 | **[Clockwork](http://github.com/itsgoingd/clockwork)** - is a tool for debugging and profiling PHP applications. 5 | 6 | **[Clockwork-chrome](http://github.com/itsgoingd/clockwork-chrome)** - is a Chrome extension for Clockwork. 7 | 8 | **[Clockwork-firebug](https://github.com/sidorovich/clockwork-firebug)** - it's Clockwork extension for Firebug. 9 | 10 | **This project** is a command-line interface for Clockwork. 11 | 12 | ## Installation 13 | 14 | * Install clockwork-cli via composer for your project: 15 | ``` 16 | composer require ptrofimov/clockwork-cli:* 17 | ``` 18 | * Or install clockwork-cli globally: 19 | ``` 20 | composer global require ptrofimov/clockwork-cli:* 21 | ``` 22 | * You could also register Artisan command 23 | ``` 24 | Artisan::add(new Clockwork\Cli\Laravel\Command); 25 | ``` 26 | 27 | ## Usage 28 | 29 | * Run script to monitor clockwork logs for current project: 30 | ``` 31 | ./bin/clockwork-cli 32 | ``` 33 | * Or you can monitor logs for many projects: 34 | ``` 35 | ./bin/clockwork-cli /www/* 36 | ``` 37 | * You will see updating list of HTTP requests. The first character is a hotkey. 38 | * Press the hotkey to view details of HTTP request. 39 | * Press Backspace to show requests for last 10 minutes 40 | * Press Escape to exit 41 | 42 | ## Screenshots 43 | 44 | ![Clockwork Cli Trace](https://raw.githubusercontent.com/ptrofimov/clockwork-cli/master/screenshots/clockwork-cli-trace.png) 45 | 46 | ![Clockwork Cli Details](https://raw.githubusercontent.com/ptrofimov/clockwork-cli/master/screenshots/clockwork-cli-details.png) 47 | 48 | ## License 49 | 50 | Copyright (c) 2014 Petr Trofimov 51 | 52 | MIT License 53 | 54 | Permission is hereby granted, free of charge, to any person obtaining 55 | a copy of this software and associated documentation files (the 56 | "Software"), to deal in the Software without restriction, including 57 | without limitation the rights to use, copy, modify, merge, publish, 58 | distribute, sublicense, and/or sell copies of the Software, and to 59 | permit persons to whom the Software is furnished to do so, subject to 60 | the following conditions: 61 | 62 | The above copyright notice and this permission notice shall be 63 | included in all copies or substantial portions of the Software. 64 | 65 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 66 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 67 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 68 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 69 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 70 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 71 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 72 | -------------------------------------------------------------------------------- /bin/clockwork-cli: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 1 ? array_slice($argv, 1) : array(getcwd())); 8 | 9 | $application->run(); 10 | -------------------------------------------------------------------------------- /bin/compile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | setSignatureAlgorithm(Phar::SHA1); 12 | 13 | $stub = <<<'EOF' 14 | #!/usr/bin/env php 15 | startBuffering(); 24 | 25 | $content = file_get_contents(__DIR__ . '/clockwork-cli'); 26 | $content = preg_replace('{^#!/usr/bin/env php\s*}', '', $content); 27 | $p->addFromString('bin/clockwork-cli', $content); 28 | 29 | $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(__DIR__ . '/../src')); 30 | while ($it->valid()) { 31 | if (!$it->isDot()) { 32 | $p->addFromString( 33 | 'src/' . $it->getSubPathName(), 34 | file_get_contents($it->key()) 35 | ); 36 | } 37 | 38 | $it->next(); 39 | } 40 | 41 | $p->setStub($stub); 42 | $p->stopBuffering(); 43 | $p->compressFiles(Phar::GZ); 44 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ptrofimov/clockwork-cli", 3 | "description": "Command-line interface for Clockwork, the tool for debugging and profiling PHP applications", 4 | "keywords": ["clockwork", "cli", "laravel", "debugging", "profiling", "logging"], 5 | "license": "MIT", 6 | "homepage": "http://github.com/ptrofimov/clockwork-cli", 7 | "authors": [ 8 | { 9 | "name": "Petr Trofimov", 10 | "email": "petrofimov@yandex.ru" 11 | } 12 | ], 13 | "support": { 14 | "email": "petrofimov@yandex.ru", 15 | "source": "https://github.com/ptrofimov/clockwork-cli", 16 | "issues": "https://github.com/ptrofimov/clockwork-cli/issues" 17 | }, 18 | "require": { 19 | "php": ">=5.3", 20 | "itsgoingd/clockwork": "1.*" 21 | 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "Clockwork\\Cli\\": "src/Clockwork/Cli" 26 | } 27 | }, 28 | "bin": ["bin/clockwork-cli"] 29 | } 30 | -------------------------------------------------------------------------------- /screenshots/clockwork-cli-details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ptrofimov/clockwork-cli/d519f0dad01a90165595a0791fc2fe6e3527c9a2/screenshots/clockwork-cli-details.png -------------------------------------------------------------------------------- /screenshots/clockwork-cli-trace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ptrofimov/clockwork-cli/d519f0dad01a90165595a0791fc2fe6e3527c9a2/screenshots/clockwork-cli-trace.png -------------------------------------------------------------------------------- /src/Clockwork/Cli/Application.php: -------------------------------------------------------------------------------- 1 | fileScanner = new LogFileScanner($dirs); 23 | $this->shortLine = new ShortLine; 24 | $this->details = new Details; 25 | $this->colors = new Colors; 26 | if (function_exists('pcntl_signal')) { 27 | declare(ticks = 1); 28 | pcntl_signal(SIGINT, [$this, 'stop']); 29 | } 30 | } 31 | 32 | public function stop() 33 | { 34 | system('stty echo icanon'); 35 | exit; 36 | } 37 | 38 | public function run() 39 | { 40 | $hotKey = 'a'; 41 | $links = array(); 42 | $since = microtime(true); 43 | system('stty -echo -icanon min 0 time 0'); 44 | $this->printUsageLine(); 45 | while (true) { 46 | foreach ($this->fileScanner->getNewFiles($since) as $file) { 47 | $log = json_decode(file_get_contents($file['file']), true); 48 | echo $this->colors->colorize("{yellow}$hotKey{default} "); 49 | $this->shortLine->output($log); 50 | $links[$hotKey] = $file['file']; 51 | $hotKey = $hotKey == 'z' ? 'a' : chr(ord($hotKey) + 1); 52 | $since = $file['time']; 53 | } 54 | usleep($this->updateInterval * 1000000); 55 | $input = str_split(fread(STDIN, 100)); 56 | while ($char = array_shift($input)) { 57 | if (ord($char) == 127) { // Backspace 58 | $since = microtime(true) - 600; 59 | echo 'Requests for last 10 minutes', PHP_EOL; 60 | } elseif (ord($char) == 27 && empty($input)) { // Escape 61 | $this->stop(); 62 | } elseif (isset($links[$char])) { // Hotkey [a-z] 63 | $this->details->output(json_decode(file_get_contents($links[$char]), true)); 64 | } 65 | } 66 | } 67 | } 68 | 69 | private function printUsageLine() 70 | { 71 | echo $this->colors->colorize( 72 | '{yellow}F{default}irst symbol in line - show details, ' 73 | . '{yellow}Backspace{default} - show requests for last 10 minutes, ' 74 | . '{yellow}Escape{default} - exit' 75 | . PHP_EOL 76 | ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Clockwork/Cli/Colors.php: -------------------------------------------------------------------------------- 1 | "\033[0;30m", 9 | 'blue' => "\033[0;34m", 10 | 'green' => "\033[0;32m", 11 | 'cyan' => "\033[0;36m", 12 | 'red' => "\033[0;31m", 13 | 'purple' => "\033[0;35m", 14 | 'brown' => "\033[0;33m", 15 | 'light gray' => "\033[0;37m", 16 | 'dark gray' => "\033[1;30m", 17 | 'light blue' => "\033[1;34m", 18 | 'light green' => "\033[1;32m", 19 | 'light cyan' => "\033[1;36m", 20 | 'light red' => "\033[1;31m", 21 | 'light purple' => "\033[1;35m", 22 | 'yellow' => "\033[1;33m", 23 | 'white' => "\033[1;37m", 24 | 'default' => "\033[0m", 25 | ); 26 | /** @var array */ 27 | private $colorCodes = array(); 28 | 29 | public function __construct() 30 | { 31 | foreach ($this->colors as $name => $code) { 32 | $this->colorCodes[] = "{{$name}}"; 33 | } 34 | } 35 | 36 | /** @return array */ 37 | public function colors() 38 | { 39 | return $this->colors; 40 | } 41 | 42 | /** @return string */ 43 | public function colorize($string) 44 | { 45 | return str_replace($this->colorCodes, $this->colors, $string); 46 | } 47 | 48 | /** @return array */ 49 | public function example() 50 | { 51 | $example = array(); 52 | foreach ($this->colors as $name => $code) { 53 | $example[] = $this->colorize("{{$name}}$name{default}"); 54 | } 55 | 56 | return $example; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Clockwork/Cli/Laravel/Command.php: -------------------------------------------------------------------------------- 1 | argument('dirs')); 32 | 33 | $application->run(); 34 | } 35 | 36 | /** 37 | * Get the console command arguments. 38 | * 39 | * @return array 40 | */ 41 | protected function getArguments() 42 | { 43 | return array( 44 | array('dirs', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'List of directories', [getcwd()]), 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Clockwork/Cli/LogFileScanner.php: -------------------------------------------------------------------------------- 1 | dirs = $dirs; 12 | } 13 | 14 | public function getNewFiles($since) 15 | { 16 | $files = array(); 17 | foreach ($this->dirs as $dir) { 18 | $dir = $this->getClockworkDirectory($dir); 19 | if (is_dir($dir)) foreach (new \DirectoryIterator($dir) as $fileInfo) { 20 | if ($fileInfo->isFile() && $fileInfo->getExtension() == 'json') { 21 | $timestamp = (float) $fileInfo->getFilename(); 22 | if ($timestamp > $since) { 23 | $files[] = array('file' => $fileInfo->getPathname(), 'time' => $timestamp); 24 | } 25 | } 26 | } 27 | } 28 | usort($files, function ($a, $b) { 29 | return $a['time'] - $b['time']; 30 | }); 31 | 32 | return $files; 33 | } 34 | 35 | private function getClockworkDirectory($dir) 36 | { 37 | if (file_exists($dir.'/app/storage/clockwork')) { 38 | return $dir.'/app/storage/clockwork'; 39 | } else { 40 | return $dir.'/storage/clockwork'; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Clockwork/Cli/Output/Base.php: -------------------------------------------------------------------------------- 1 | colors = new Colors; 14 | } 15 | 16 | protected function color($string) 17 | { 18 | return $this->colors->colorize($string); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Clockwork/Cli/Output/Details.php: -------------------------------------------------------------------------------- 1 | outputHeaders($log); 9 | $this->outputQueries($log); 10 | $this->outputTimeline($log); 11 | $this->outputGeneral($log); 12 | echo PHP_EOL; 13 | } 14 | 15 | private function outputHeaders(array $log) 16 | { 17 | echo PHP_EOL; 18 | echo $this->color('{yellow}REQUEST HEADERS{default}'); 19 | echo PHP_EOL; 20 | foreach ($log['headers'] as $name => $header) { 21 | echo $this->color("{light blue}" . ucwords($name) . ":{default} " . implode(';', $header) . "\n"); 22 | } 23 | } 24 | 25 | private function outputQueries(array $log) 26 | { 27 | $keywords = array('SELECT', 'FROM', 'WHERE', 'GROUP BY', 'ORDER BY', 'INSERT', 'INTO', 'VALUES', 'UPDATE', 'SET', 'HAVING', 'IN', 'IS', 'NULL', 'ASC', 'DESC'); 28 | echo PHP_EOL; 29 | echo $this->color('{yellow}DATABASE QUERIES{default} '); 30 | echo sprintf("(%d, %.3f)", count($log['databaseQueries']), $log['databaseDuration'] / 1000); 31 | echo PHP_EOL; 32 | foreach ($log['databaseQueries'] as $query) { 33 | echo sprintf('%7.3f ', $query['duration'] / 1000); 34 | echo $this->color(preg_replace('/\b(' . implode('|', $keywords) . ')\b/i', '{light blue}$0{default}', $query['query'])); 35 | echo PHP_EOL; 36 | } 37 | } 38 | 39 | private function outputTimeline(array $log) 40 | { 41 | $time = array(); 42 | foreach ($log['timelineData'] as $item) { 43 | $time[] = $item['start']; 44 | $time[] = $item['end']; 45 | } 46 | $min = min($time); 47 | $max = max($time); 48 | $step = ($max - $min) / 10; 49 | 50 | echo PHP_EOL; 51 | echo $this->color('{yellow}TIMELINE{default}'); 52 | echo PHP_EOL; 53 | foreach ($log['timelineData'] as $item) { 54 | echo sprintf('%7.3f ', $item['duration'] / 1000); 55 | for ($i = $min; $i < $max - $step; $i += $step) { 56 | echo $i + $step < $item['start'] || $i >= $item['end'] ? '.' : '#'; 57 | } 58 | echo ' ', trim($item['description'], '.'); 59 | echo PHP_EOL; 60 | } 61 | } 62 | 63 | private function outputGeneral(array $log) 64 | { 65 | echo PHP_EOL; 66 | echo $this->color('{yellow}GENERAL{default}'); 67 | echo PHP_EOL; 68 | echo $this->color("{light blue}ID:{default} $log[id]"), PHP_EOL; 69 | echo $this->color("{light blue}Time:{default} " . date('Y-m-d H:i:s', $log['time'])), PHP_EOL; 70 | echo $this->color("{light blue}Duration:{default} " . sprintf('%.3f', $log['responseDuration'] / 1000)), PHP_EOL; 71 | echo $this->color("{light blue}Method:{default} $log[method]"), PHP_EOL; 72 | echo $this->color("{light blue}URI:{default} $log[uri]"), PHP_EOL; 73 | echo $this->color("{light blue}Controller:{default} $log[controller]"), PHP_EOL; 74 | echo $this->color("{light blue}Response status:{default} $log[responseStatus]"), PHP_EOL; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Clockwork/Cli/Output/ShortLine.php: -------------------------------------------------------------------------------- 1 | color(sprintf( 9 | '%s %5.3f %s %s %s {dark gray}%s{default}' . PHP_EOL, 10 | date('H:i:s', $log['time']), 11 | $log['responseDuration'] / 1000, 12 | $log['responseStatus'] >= 300 ? "{light red}$log[responseStatus]{default}" : "{light green}$log[responseStatus]{default}", 13 | $log['method'], 14 | $log['uri'], 15 | $log['headers']['host'][0] 16 | )); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/bootstrap.php: -------------------------------------------------------------------------------- 1 |