Repository: sarfraznawaz2005/meter Branch: master Commit: d5505c850353 Files: 69 Total size: 135.8 KB Directory structure: gitextract_q4lycjzt/ ├── .editorconfig ├── .gitattributes ├── .gitignore ├── LICENSE.md ├── README.md ├── composer.json ├── phpunit.xml ├── src/ │ ├── Charts/ │ │ ├── Chart.php │ │ ├── CommandsTimeChart.php │ │ ├── ConnectionsChart.php │ │ ├── CpuChart.php │ │ ├── DiskSpaceChart.php │ │ ├── EventsTimeChart.php │ │ ├── OverAllTimesChart.php │ │ ├── QueriesTimeChart.php │ │ ├── RequestMemoryChart.php │ │ ├── RequestTimeChart.php │ │ ├── SchedulesTimeChart.php │ │ └── ServerMemoryChart.php │ ├── Config/ │ │ └── config.php │ ├── Console/ │ │ ├── PruneCommand.php │ │ ├── PublishCommand.php │ │ └── ServerMonitorCommand.php │ ├── Helpers.php │ ├── Http/ │ │ ├── Controllers/ │ │ │ ├── PagesController.php │ │ │ └── TablesController.php │ │ ├── Middleware/ │ │ │ └── BasicAuth.php │ │ └── routes.php │ ├── Meter.php │ ├── MeterServiceProvider.php │ ├── Migrations/ │ │ └── 2019_12_12_100000_create_meter_entries_table.php │ ├── Models/ │ │ └── MeterModel.php │ ├── Monitors/ │ │ ├── CommandMonitor.php │ │ ├── CpuMonitor.php │ │ ├── DiskSpaceMonitor.php │ │ ├── EventMonitor.php │ │ ├── HttpConnectionsMonitor.php │ │ ├── MemoryMonitor.php │ │ ├── Monitor.php │ │ ├── QueryMonitor.php │ │ ├── RequestMonitor.php │ │ └── ScheduleMonitor.php │ ├── Resources/ │ │ ├── Assets/ │ │ │ ├── meter.css │ │ │ └── meter.js │ │ └── Views/ │ │ ├── commands.blade.php │ │ ├── components/ │ │ │ ├── chart.blade.php │ │ │ └── table.blade.php │ │ ├── dashboard.blade.php │ │ ├── events.blade.php │ │ ├── layout/ │ │ │ ├── filters.blade.php │ │ │ ├── layout.blade.php │ │ │ ├── links.blade.php │ │ │ └── modal.blade.php │ │ ├── queries.blade.php │ │ ├── requests.blade.php │ │ ├── schedules.blade.php │ │ └── server.blade.php │ ├── Tables/ │ │ ├── CommandsTable.php │ │ ├── EventsTable.php │ │ ├── QueriesTable.php │ │ ├── RequestsTable.php │ │ ├── SchedulesTable.php │ │ └── Table.php │ ├── Traits/ │ │ ├── FetchesStackTrace.php │ │ ├── FormatsClosure.php │ │ └── RegistersMonitors.php │ └── Type.php └── tests/ ├── ConsoleTest.php └── MeterModelTest.php ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ ; This file is for unifying the coding style for different editors and IDEs. ; More information at http://editorconfig.org root = true [*] charset = utf-8 indent_size = 4 indent_style = space end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true [*.md] trim_trailing_whitespace = false ================================================ FILE: .gitattributes ================================================ # Path-based git attributes # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html # Ignore all test and documentation with "export-ignore". /.gitattributes export-ignore /.gitignore export-ignore /.travis.yml export-ignore /phpunit.xml.dist export-ignore /.scrutinizer.yml export-ignore /tests export-ignore /.editorconfig export-ignore ================================================ FILE: .gitignore ================================================ build composer.lock docs vendor coverage todo.txt push.sh ================================================ FILE: LICENSE.md ================================================ MIT License Copyright (c) Sarfraz Ahmed 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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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. ================================================ FILE: README.md ================================================ [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) [![Latest Version on Packagist][ico-version]][link-packagist] [![Total Downloads][ico-downloads]][link-downloads] # Laravel Meter Laravel Meter monitors application performance for different things such as requests, commands, queries, events, etc and presents results in tables/charts. Think of it like Laravel Telescope but for performance monitoring. ## Requirements ## - PHP >= 7.3 - Laravel 5.5+ | 6 ## Configuration Available options are: - METER_DB_CONNECTION ` Database connection to use for meter entries. Default: mysql (uses default connection) ` - METER_PATH ` Path to access meter UI. Default: meter ` - METER_USERNAME ` Username to access meter UI. Default: null ` - METER_PASSWORD ` Password to access meter UI. Default: null ` - ignore_matched_string ` It helps to ignore the matched string in the query. Ex:` ```php "ignore_matched_string" => [ "query" => [ "information_schema", ] ], ``` There are more options available, please check `config/meter.php` file for more details. ## Monitors ## - Requests - Queries - Commands - Events - Schedule - CPU Load - Disk Space - Server Memory - HTTP Connections ## Screenshot ## ![Main Window](https://github.com/sarfraznawaz2005/meter/blob/master/screenshot.png?raw=true) ## Installation ## Install via composer ``` composer require sarfraznawaz2005/meter ``` Publish package's config file by running below command: ```bash php artisan vendor:publish --provider="Sarfraznawaz2005\Meter\MeterServiceProvider" ``` It should publish `config/meter.php` config file and migration file. Now run `php artisan migrate` command to create `meter_entries` database table. ## Usage ## Enable Meter in `config/meter.php` and setup any other options as needed. Meter UI will be visible at `path` config you set. By default Meter monitors: - Requests - Queries - Commands - Events - Schedule To monitor server stuff: - CPU Load - Disk Space - Server Memory - HTTP Connections You should use `meter:servermonitor` command. Schedule it in Laravel's console kernel file accordingly: ```php // app/Console/Kernel.php protected function schedule(Schedule $schedule) { $schedule->command('meter:servermonitor')->hourly(); // or daily } ``` ## Authentication ## You can use `username` and `password` config options to setup basic HTTP authentication for Meter interface to be accessed. Once setup, user should provide correct username and password to access Meter page(s). Alternatively, you can use `middleware` config option to apply your own auth middleware, etc ## Ignoring Things ## You can use `ignore_paths` and `ignore_commands` config options to ignore desired pages or commands from being monitored by Meter. ## Data Pruning ## You need to periodically remove meter data otherwise your database will turn big quickly. To prune meter data, you can setup prune command in Kernel file: ```php $schedule->command('meter:prune')->daily(); // or $schedule->command('meter:prune --days=7')->daily(); ``` ## Contributing PRs are welcome. Thanks ## Security If you discover any security related issues, please email sarfraznawaz2005@gmail.com instead of using the issue tracker. ## Credits - [Sarfraz Ahmed][link-author] - [All Contributors][link-contributors] ## License Please see the [license file](LICENSE.md) for more information. [ico-version]: https://img.shields.io/packagist/v/sarfraznawaz2005/meter.svg?style=flat-square [ico-downloads]: https://img.shields.io/packagist/dt/sarfraznawaz2005/meter.svg?style=flat-square [link-packagist]: https://packagist.org/packages/sarfraznawaz2005/meter [link-downloads]: https://packagist.org/packages/sarfraznawaz2005/meter [link-author]: https://github.com/sarfraznawaz2005 [link-contributors]: https://github.com/sarfraznawaz2005/meter/graphs/contributors ================================================ FILE: composer.json ================================================ { "name": "sarfraznawaz2005/meter", "description": "laravel package to find performance bottlenecks in your laravel application.", "keywords": [ "laravel", "performance", "meter", "log", "request", "queries", "database", "monitor", "health" ], "homepage": "https://github.com/sarfraznawaz2005/meter", "license": "MIT", "type": "library", "authors": [ { "name": "Sarfraz Ahmed", "email": "sarfraznawaz2005@gmail.com", "role": "Developer" } ], "require": { "php": ">=7.3|>=8.0", "illuminate/support": "~5|~6|~7|~8|~9", "consoletvs/charts": "6.*", "balping/json-raw-encoder": "^1.0" }, "autoload": { "psr-4": { "Sarfraznawaz2005\\Meter\\": "src" }, "files": [ "src/Helpers.php" ] }, "autoload-dev": { "psr-4": { "Sarfraznawaz2005\\Meter\\Tests\\": "tests" } }, "scripts": { "test": "vendor/bin/phpunit", "test-coverage": "vendor/bin/phpunit --coverage-html coverage" }, "config": { "sort-packages": true }, "extra": { "laravel": { "providers": [ "Sarfraznawaz2005\\Meter\\MeterServiceProvider" ] } } } ================================================ FILE: phpunit.xml ================================================ tests src/ ================================================ FILE: src/Charts/Chart.php ================================================ setData($model); $this->setOptions(); $this->labels($this->getLabels()); $this->setDataSet(); } } ================================================ FILE: src/Charts/CommandsTimeChart.php ================================================ options([ 'responsive' => true, 'maintainAspectRatio' => false, 'title' => [ 'display' => true, 'text' => [ 'Min ' . round(collect($this->getValues())->pluck('y')->min()) . ' | ' . 'Avg ' . round(collect($this->getValues())->pluck('y')->average()) . ' | ' . 'Max ' . round(collect($this->getValues())->pluck('y')->max()) ], ], 'legend' => false, 'scales' => [ 'yAxes' => [[ 'ticks' => [ 'beginAtZero' => true, ], 'scaleLabel' => [ 'display' => true, 'labelString' => 'Command Time (ms)' ], ]], 'xAxes' => [[ 'display' => false, //'type' => 'time', 'time' => [ 'displayFormats' => ['hour' => 'MMM D hA'], ], 'ticks' => [ 'beginAtZero' => true, 'autoSkip' => true, 'autoSkipPadding' => 30, 'maxRotation' => 0, ], 'gridLines' => ['offsetGridLines' => true], 'offset' => true, ]] ], 'tooltips' => [ 'callbacks' => [ 'label' => new Raw('function(item, data) { return "Time: " + data.datasets[item.datasetIndex].data[item.index].y + " (Command: " + data.datasets[item.datasetIndex].data[item.index].x + ")"}') ] ], ], true); } /** * Sets data for chart. * * @param MeterModel $model * @return void */ protected function setData(MeterModel $model) { foreach ($model->type(Type::COMMAND)->filtered()->orderBy('id', 'asc')->get() as $item) { if (isset($item->content['time'])) { $this->data[(string)$item->created_at] = [ 'x' => $item->content['command'], 'y' => (int)$item->content['time'], ]; } } } /** * Gets labels for chart. * * @return mixed */ protected function getLabels(): array { return array_keys($this->data); } /** * Gets values for chart. * * @return mixed */ protected function getValues(): array { return array_values($this->data); } /** * Generates and returns chart * * @return void */ protected function setDataSet() { $type = config('meter.monitors.' . CommandMonitor::class . '.graph_type', 'bar'); $this->dataset('Command Time', $type, $this->getValues()) ->color('rgb(' . static::COLOR_RED . ')') ->options([ 'pointRadius' => 2, 'fill' => true, 'lineTension' => 0, 'borderWidth' => 1, //'minBarLength' => 50, 'barPercentage' => 0.9 ]) ->backgroundcolor('rgba(' . static::COLOR_RED . ', 0.6)'); } } ================================================ FILE: src/Charts/ConnectionsChart.php ================================================ options([ 'responsive' => true, 'maintainAspectRatio' => false, 'title' => [ 'display' => true, 'text' => [ 'Min ' . round(collect($this->getValues())->min()) . ' | ' . 'Avg ' . round(collect($this->getValues())->average()) . ' | ' . 'Max ' . round(collect($this->getValues())->max()) ], ], 'legend' => false, 'scales' => [ 'yAxes' => [[ 'ticks' => [ 'beginAtZero' => true ], ]], 'xAxes' => [[ 'display' => false, 'ticks' => [ 'beginAtZero' => true, 'autoSkip' => true, 'autoSkipPadding' => 30, 'maxRotation' => 0, ], 'gridLines' => ['offsetGridLines' => true], 'offset' => true, ]] ], ], true); } /** * Sets data for chart. * * @param MeterModel $model * @return void */ protected function setData(MeterModel $model) { foreach ($model->type(Type::CONNECTIONS)->filtered()->orderBy('id', 'asc')->get() as $item) { $this->data[(string)$item->created_at] = (int)$item->content['count']; } } /** * Gets labels for chart. * * @return mixed */ protected function getLabels(): array { return array_keys($this->data); } /** * Gets values for chart. * * @return mixed */ protected function getValues(): array { return array_values($this->data); } /** * Generates and returns chart * * @return void */ protected function setDataSet() { $type = config('meter.monitors.' . HttpConnectionsMonitor::class . '.graph_type', 'bar'); $this->dataset('Count', $type, $this->getValues()) ->color('rgb(' . static::COLOR_RED . ')') ->options([ 'pointRadius' => 2, 'fill' => true, 'lineTension' => 0, 'borderWidth' => 1, //'minBarLength' => 50, 'barPercentage' => 0.9 ]) ->backgroundcolor('rgba(' . static::COLOR_RED . ', 0.6)'); } } ================================================ FILE: src/Charts/CpuChart.php ================================================ options([ 'responsive' => true, 'maintainAspectRatio' => false, 'title' => [ 'display' => true, 'text' => [ 'Min ' . round(collect($this->getValues())->min()) . ' | ' . 'Avg ' . round(collect($this->getValues())->average()) . ' | ' . 'Max ' . round(collect($this->getValues())->max()) ], ], 'legend' => false, 'scales' => [ 'yAxes' => [[ 'ticks' => [ 'beginAtZero' => true ], ]], 'xAxes' => [[ 'display' => false, 'ticks' => [ 'beginAtZero' => true, 'autoSkip' => true, 'autoSkipPadding' => 30, 'maxRotation' => 0, ], 'gridLines' => ['offsetGridLines' => true], 'offset' => true, ]] ], ], true); } /** * Sets data for chart. * * @param MeterModel $model * @return void */ protected function setData(MeterModel $model) { foreach ($model->type(Type::CPU)->filtered()->orderBy('id', 'asc')->get() as $item) { $this->data[(string)$item->created_at] = (int)$item->content['percent']; } } /** * Gets labels for chart. * * @return mixed */ protected function getLabels(): array { return array_keys($this->data); } /** * Gets values for chart. * * @return mixed */ protected function getValues(): array { return array_values($this->data); } /** * Generates and returns chart * * @return void */ protected function setDataSet() { $type = config('meter.monitors.' . CpuMonitor::class . '.graph_type', 'bar'); $this->dataset('Percent', $type, $this->getValues()) ->color('rgb(' . static::COLOR_RED . ')') ->options([ 'pointRadius' => 2, 'fill' => true, 'lineTension' => 0, 'borderWidth' => 1, //'minBarLength' => 50, 'barPercentage' => 0.9 ]) ->backgroundcolor('rgba(' . static::COLOR_RED . ', 0.6)'); } } ================================================ FILE: src/Charts/DiskSpaceChart.php ================================================ options([ 'responsive' => true, 'maintainAspectRatio' => false, 'title' => [ 'display' => true, 'text' => [ 'Min ' . round(collect($this->getValues())->min()) . ' | ' . 'Avg ' . round(collect($this->getValues())->average()) . ' | ' . 'Max ' . round(collect($this->getValues())->max()) ], ], 'legend' => false, 'scales' => [ 'yAxes' => [[ 'ticks' => [ 'beginAtZero' => true ], ]], 'xAxes' => [[ 'display' => false, 'ticks' => [ 'beginAtZero' => true, 'autoSkip' => true, 'autoSkipPadding' => 30, 'maxRotation' => 0, ], 'gridLines' => ['offsetGridLines' => true], 'offset' => true, ]] ], ], true); } /** * Sets data for chart. * * @param MeterModel $model * @return void */ protected function setData(MeterModel $model) { foreach ($model->type(Type::DISK)->filtered()->orderBy('id', 'asc')->get() as $item) { $this->data[(string)$item->created_at] = (int)$item->content['percent']; } } /** * Gets labels for chart. * * @return mixed */ protected function getLabels(): array { return array_keys($this->data); } /** * Gets values for chart. * * @return mixed */ protected function getValues(): array { return array_values($this->data); } /** * Generates and returns chart * * @return void */ protected function setDataSet() { $type = config('meter.monitors.' . DiskSpaceMonitor::class . '.graph_type', 'bar'); $this->dataset('Percent', $type, $this->getValues()) ->color('rgb(' . static::COLOR_RED . ')') ->options([ 'pointRadius' => 2, 'fill' => true, 'lineTension' => 0, 'borderWidth' => 1, //'minBarLength' => 50, 'barPercentage' => 0.9 ]) ->backgroundcolor('rgba(' . static::COLOR_RED . ', 0.6)'); } } ================================================ FILE: src/Charts/EventsTimeChart.php ================================================ options([ 'responsive' => true, 'maintainAspectRatio' => false, 'title' => [ 'display' => true, 'text' => [ 'Min ' . round(collect($this->getValues())->pluck('y')->min()) . ' | ' . 'Avg ' . round(collect($this->getValues())->pluck('y')->average()) . ' | ' . 'Max ' . round(collect($this->getValues())->pluck('y')->max()) ], ], 'legend' => false, 'scales' => [ 'yAxes' => [[ 'ticks' => [ 'beginAtZero' => true, ], 'scaleLabel' => [ 'display' => true, 'labelString' => 'Event Time (ms)' ], ]], 'xAxes' => [[ 'display' => false, //'type' => 'time', 'time' => [ 'displayFormats' => ['hour' => 'MMM D hA'], ], 'ticks' => [ 'beginAtZero' => true, 'autoSkip' => true, 'autoSkipPadding' => 30, 'maxRotation' => 0, ], 'gridLines' => ['offsetGridLines' => true], 'offset' => true, ]] ], 'tooltips' => [ 'callbacks' => [ 'label' => new Raw('function(item, data) { return "Time: " + data.datasets[item.datasetIndex].data[item.index].y + " (Event: " + data.datasets[item.datasetIndex].data[item.index].x + ")"}') ] ], ], true); } /** * Sets data for chart. * * @param MeterModel $model * @return void */ protected function setData(MeterModel $model) { foreach ($model->type(Type::EVENT)->filtered()->orderBy('id', 'asc')->get() as $item) { if (isset($item->content['time'])) { $this->data[(string)$item->created_at] = [ 'x' => $item->content['name'], 'y' => (int)$item->content['time'], ]; } } } /** * Gets labels for chart. * * @return mixed */ protected function getLabels(): array { return array_keys($this->data); } /** * Gets values for chart. * * @return mixed */ protected function getValues(): array { return array_values($this->data); } /** * Generates and returns chart * * @return void */ protected function setDataSet() { $type = config('meter.monitors.' . EventMonitor::class . '.graph_type', 'bar'); $this->dataset('Event Time', $type, $this->getValues()) ->color('rgb(' . static::COLOR_RED . ')') ->options([ 'pointRadius' => 2, 'fill' => true, 'lineTension' => 0, 'borderWidth' => 1, //'minBarLength' => 50, 'barPercentage' => 0.9 ]) ->backgroundcolor('rgba(' . static::COLOR_RED . ', 0.6)'); } } ================================================ FILE: src/Charts/OverAllTimesChart.php ================================================ options([ 'responsive' => true, 'maintainAspectRatio' => false, 'scales' => [ 'yAxes' => [[ 'ticks' => [ 'beginAtZero' => true ], ]], 'xAxes' => [[ 'display' => false, ]] ], ], true); } /** * Sets data for chart. * * @param MeterModel $model * @return void */ protected function setData(MeterModel $model) { foreach ($model->filtered()->orderBy('id', 'asc')->get() as $item) { $time = 0; if (isset($item->content['duration'])) { $time = $item->content['duration']; } elseif (isset($item->content['time'])) { $time = $item->content['time']; } $this->data[(string)$item->created_at] = [ 'type' => $item->type, 'time' => $time ]; } } /** * Gets labels for chart. * * @return mixed */ protected function getLabels(): array { return array_keys($this->data); } /** * Gets values for chart. * * @return mixed */ protected function getValues(): array { return array_values($this->data); } /** * Generates and returns chart * * @return void */ protected function setDataSet() { if (config('meter.monitors.' . RequestMonitor::class . '.enabled', true)) { $this->dataset('Response Time', 'line', collect($this->getValues())->where('type', 'request')->pluck('time')) ->color('rgb(0, 123, 255)') ->options([ 'pointRadius' => 2, 'fill' => true, 'lineTension' => 0, 'borderWidth' => 1, 'barPercentage' => 0.9 ]) ->backgroundcolor('rgba(0, 123, 255, 0.6)'); } if (config('meter.monitors.' . QueryMonitor::class . '.enabled', true)) { $this->dataset('Query Time', 'line', collect($this->getValues())->where('type', 'query')->pluck('time')) ->color('rgb(40, 167, 69)') ->options([ 'pointRadius' => 2, 'fill' => true, 'lineTension' => 0, 'borderWidth' => 1, 'barPercentage' => 0.9 ]) ->backgroundcolor('rgba(40, 167, 69, 0.6)'); } if (config('meter.monitors.' . CommandMonitor::class . '.enabled', true)) { $this->dataset('Command Time', 'line', collect($this->getValues())->where('type', 'command')->pluck('time')) ->color('rgb(23, 162, 184)') ->options([ 'pointRadius' => 2, 'fill' => true, 'lineTension' => 0, 'borderWidth' => 1, 'barPercentage' => 0.9 ]) ->backgroundcolor('rgba(23, 162, 184, 0.6)'); } if (config('meter.monitors.' . EventMonitor::class . '.enabled', true)) { $this->dataset('Event Time', 'line', collect($this->getValues())->where('type', 'event')->pluck('time')) ->color('rgb(255, 193, 7)') ->options([ 'pointRadius' => 2, 'fill' => true, 'lineTension' => 0, 'borderWidth' => 1, 'barPercentage' => 0.9 ]) ->backgroundcolor('rgba(255, 193, 7, 0.6)'); } if (config('meter.monitors.' . ScheduleMonitor::class . '.enabled', true)) { $this->dataset('Schedule Time', 'line', collect($this->getValues())->where('type', 'schedule')->pluck('time')) ->color('rgb(220, 53, 69)') ->options([ 'pointRadius' => 2, 'fill' => true, 'lineTension' => 0, 'borderWidth' => 1, 'barPercentage' => 0.9 ]) ->backgroundcolor('rgba(220, 53, 69, 0.6)'); } } } ================================================ FILE: src/Charts/QueriesTimeChart.php ================================================ options([ 'responsive' => true, 'maintainAspectRatio' => false, 'title' => [ 'display' => true, 'text' => [ 'Min ' . round(collect($this->getValues())->pluck('y')->min()) . ' | ' . 'Avg ' . round(collect($this->getValues())->pluck('y')->average()) . ' | ' . 'Max ' . round(collect($this->getValues())->pluck('y')->max()) ], ], 'legend' => false, 'scales' => [ 'yAxes' => [[ 'ticks' => [ 'beginAtZero' => true ], 'scaleLabel' => [ 'display' => true, 'labelString' => 'Time (ms)' ] ]], 'xAxes' => [[ 'display' => false, //'type' => 'time', 'time' => [ 'displayFormats' => ['hour' => 'MMM D hA'], ], 'ticks' => [ 'beginAtZero' => true, 'autoSkip' => true, 'autoSkipPadding' => 30, 'maxRotation' => 0, ], 'gridLines' => ['offsetGridLines' => true], 'offset' => true, ]] ], 'tooltips' => [ 'callbacks' => [ 'label' => new Raw('function(item, data) { return "Time: " + data.datasets[item.datasetIndex].data[item.index].y + " (Query: " + data.datasets[item.datasetIndex].data[item.index].x + ")"}') ] ], ], true); } /** * Sets data for chart. * * @param MeterModel $model * @return void */ protected function setData(MeterModel $model) { foreach ($model->type(Type::QUERY)->filtered()->orderBy('id', 'asc')->get() as $item) { if (isset($item->content['time'])) { $this->data[(string)$item->created_at] = [ 'x' => Str::limit($item->content['sql'], 120), 'y' => (int)$item->content['time'], ]; } } } /** * Gets labels for chart. * * @return mixed */ protected function getLabels(): array { return array_keys($this->data); } /** * Gets values for chart. * * @return mixed */ protected function getValues(): array { return array_values($this->data); } /** * Generates and returns chart * * @return void */ protected function setDataSet() { $type = config('meter.monitors.' . QueryMonitor::class . '.graph_type', 'bar'); $this->dataset('Query Time', $type, $this->getValues()) ->color('rgb(' . static::COLOR_RED . ')') ->options([ 'pointRadius' => 2, 'fill' => true, 'lineTension' => 0, 'borderWidth' => 1, //'minBarLength' => 50, 'barPercentage' => 0.9 ]) ->backgroundcolor('rgba(' . static::COLOR_RED . ', 0.6)'); } } ================================================ FILE: src/Charts/RequestMemoryChart.php ================================================ options([ 'responsive' => true, 'maintainAspectRatio' => false, 'title' => [ 'display' => true, 'text' => [ 'Min ' . round(collect($this->getValues())->pluck('y')->min()) . ' | ' . 'Avg ' . round(collect($this->getValues())->pluck('y')->average()) . ' | ' . 'Max ' . round(collect($this->getValues())->pluck('y')->max()) ], ], 'legend' => false, 'scales' => [ 'yAxes' => [[ 'ticks' => [ 'beginAtZero' => true ], 'scaleLabel' => [ 'display' => true, 'labelString' => 'Memory (MB)' ] ]], 'xAxes' => [[ 'display' => false, //'type' => 'time', 'time' => [ 'displayFormats' => ['hour' => 'MMM D hA'], ], 'ticks' => [ 'beginAtZero' => true, 'autoSkip' => true, 'autoSkipPadding' => 30, 'maxRotation' => 0, ], 'gridLines' => ['offsetGridLines' => true], 'offset' => true, ]] ], 'tooltips' => [ 'callbacks' => [ 'label' => new Raw('function(item, data) { return "Memory: " + data.datasets[item.datasetIndex].data[item.index].y + " (Path: " + data.datasets[item.datasetIndex].data[item.index].x + ")"}') ] ], ], true); } /** * Sets data for chart. * * @param MeterModel $model * @return void */ protected function setData(MeterModel $model) { foreach ($model->type(Type::REQUEST)->filtered()->orderBy('id', 'asc')->get() as $item) { if (isset($item->content['memory'])) { $this->data[(string)$item->created_at] = [ 'x' => $item->content['uri'], 'y' => (int)$item->content['memory'], ]; } } } /** * Gets labels for chart. * * @return mixed */ protected function getLabels(): array { return array_keys($this->data); } /** * Gets values for chart. * * @return mixed */ protected function getValues(): array { return array_values($this->data); } /** * Generates and returns chart * * @return void */ protected function setDataSet() { $type = config('meter.monitors.' . RequestMonitor::class . '.graph_type', 'bar'); $this->dataset('Memory', $type, $this->getValues()) ->color('rgb(' . static::COLOR_RED . ')') ->options([ 'pointRadius' => 2, 'fill' => true, 'lineTension' => 0, 'borderWidth' => 1, //'minBarLength' => 50, 'barPercentage' => 0.9 ]) ->backgroundcolor('rgba(' . static::COLOR_RED . ', 0.6)'); } } ================================================ FILE: src/Charts/RequestTimeChart.php ================================================ options([ 'responsive' => true, 'maintainAspectRatio' => false, 'title' => [ 'display' => true, 'text' => [ 'Min ' . round(collect($this->getValues())->pluck('y')->min()) . ' | ' . 'Avg ' . round(collect($this->getValues())->pluck('y')->average()) . ' | ' . 'Max ' . round(collect($this->getValues())->pluck('y')->max()) ], ], 'legend' => false, 'scales' => [ 'yAxes' => [[ 'ticks' => [ 'beginAtZero' => true ], 'scaleLabel' => [ 'display' => true, 'labelString' => 'Response Time (ms)' ] ]], 'xAxes' => [[ 'display' => false, //'type' => 'time', 'time' => [ 'displayFormats' => ['hour' => 'MMM D hA'], ], 'ticks' => [ 'beginAtZero' => true, 'autoSkip' => true, 'autoSkipPadding' => 30, 'maxRotation' => 0, ], 'gridLines' => ['offsetGridLines' => true], 'offset' => true, ]] ], 'tooltips' => [ 'callbacks' => [ 'label' => new Raw('function(item, data) { return "Time: " + data.datasets[item.datasetIndex].data[item.index].y + " (Path: " + data.datasets[item.datasetIndex].data[item.index].x + ")"}') ] ], ], true); } /** * Sets data for chart. * * @param MeterModel $model * @return void */ protected function setData(MeterModel $model) { foreach ($model->type(Type::REQUEST)->filtered()->orderBy('id', 'asc')->get() as $item) { if (isset($item->content['duration'])) { $this->data[(string)$item->created_at] = [ 'x' => $item->content['uri'], 'y' => (int)$item->content['duration'], ]; } } } /** * Gets labels for chart. * * @return mixed */ protected function getLabels(): array { return array_keys($this->data); } /** * Gets values for chart. * * @return mixed */ protected function getValues(): array { return array_values($this->data); } /** * Generates and returns chart * * @return void */ protected function setDataSet() { $type = config('meter.monitors.' . RequestMonitor::class . '.graph_type', 'bar'); $this->dataset('Response Time', $type, $this->getValues()) ->color('rgb(' . static::COLOR_RED . ')') ->options([ 'pointRadius' => 2, 'fill' => true, 'lineTension' => 0, 'borderWidth' => 1, //'minBarLength' => 50, 'barPercentage' => 0.9 ]) ->backgroundcolor('rgba(' . static::COLOR_RED . ', 0.6)'); } } ================================================ FILE: src/Charts/SchedulesTimeChart.php ================================================ options([ 'responsive' => true, 'maintainAspectRatio' => false, 'title' => [ 'display' => true, 'text' => [ 'Min ' . round(collect($this->getValues())->pluck('y')->min()) . ' | ' . 'Avg ' . round(collect($this->getValues())->pluck('y')->average()) . ' | ' . 'Max ' . round(collect($this->getValues())->pluck('y')->max()) ], ], 'legend' => false, 'scales' => [ 'yAxes' => [[ 'ticks' => [ 'beginAtZero' => true, ], 'scaleLabel' => [ 'display' => true, 'labelString' => 'Command Time (ms)' ], ]], 'xAxes' => [[ 'display' => false, //'type' => 'time', 'time' => [ 'displayFormats' => ['hour' => 'MMM D hA'], ], 'ticks' => [ 'beginAtZero' => true, 'autoSkip' => true, 'autoSkipPadding' => 30, 'maxRotation' => 0, ], 'gridLines' => ['offsetGridLines' => true], 'offset' => true, ]] ], 'tooltips' => [ 'callbacks' => [ 'label' => new Raw('function(item, data) { return "Time: " + data.datasets[item.datasetIndex].data[item.index].y + " (Command: " + data.datasets[item.datasetIndex].data[item.index].x + ")"}') ] ], ], true); } /** * Sets data for chart. * * @param MeterModel $model * @return void */ protected function setData(MeterModel $model) { /* $this->data = $model->type(Type::SCHEDULE) ->filtered() ->groupBy('date') ->orderBy('date', 'ASC') ->get([ DB::raw('DATE(created_at) as date'), DB::raw('COUNT(*) as "total"') ]) ->pluck('total', 'date') ->toArray(); */ foreach ($model->type(Type::SCHEDULE)->filtered()->orderBy('id', 'asc')->get() as $item) { if (isset($item->content['time'])) { $this->data[(string)$item->created_at] = [ 'x' => $item->content['command'], 'y' => (int)$item->content['time'], ];; } } } /** * Gets labels for chart. * * @return mixed */ protected function getLabels(): array { return array_keys($this->data); } /** * Gets values for chart. * * @return mixed */ protected function getValues(): array { return array_values($this->data); } /** * Generates and returns chart * * @return void */ protected function setDataSet() { $type = config('meter.monitors.' . ScheduleMonitor::class . '.graph_type', 'bar'); $this->dataset('Command Time', $type, $this->getValues()) ->color('rgb(' . static::COLOR_RED . ')') ->options([ 'pointRadius' => 2, 'fill' => true, 'lineTension' => 0, 'borderWidth' => 1, //'minBarLength' => 50, 'barPercentage' => 0.9 ]) ->backgroundcolor('rgba(' . static::COLOR_RED . ', 0.6)'); } } ================================================ FILE: src/Charts/ServerMemoryChart.php ================================================ options([ 'responsive' => true, 'maintainAspectRatio' => false, 'title' => [ 'display' => true, 'text' => [ 'Min ' . round(collect($this->getValues())->min()) . ' | ' . 'Avg ' . round(collect($this->getValues())->average()) . ' | ' . 'Max ' . round(collect($this->getValues())->max()) ], ], 'legend' => false, 'scales' => [ 'yAxes' => [[ 'ticks' => [ 'beginAtZero' => true ], ]], 'xAxes' => [[ 'display' => false, 'ticks' => [ 'beginAtZero' => true, 'autoSkip' => true, 'autoSkipPadding' => 30, 'maxRotation' => 0, ], 'gridLines' => ['offsetGridLines' => true], 'offset' => true, ]] ], ], true); } /** * Sets data for chart. * * @param MeterModel $model * @return void */ protected function setData(MeterModel $model) { foreach ($model->type(Type::MEMORY)->filtered()->orderBy('id', 'asc')->get() as $item) { $this->data[(string)$item->created_at] = (int)$item->content['percent']; } } /** * Gets labels for chart. * * @return mixed */ protected function getLabels(): array { return array_keys($this->data); } /** * Gets values for chart. * * @return mixed */ protected function getValues(): array { return array_values($this->data); } /** * Generates and returns chart * * @return void */ protected function setDataSet() { $type = config('meter.monitors.' . MemoryMonitor::class . '.graph_type', 'bar'); $this->dataset('Percent', $type, $this->getValues()) ->color('rgb(' . static::COLOR_RED . ')') ->options([ 'pointRadius' => 2, 'fill' => true, 'lineTension' => 0, 'borderWidth' => 1, //'minBarLength' => 50, 'barPercentage' => 0.9 ]) ->backgroundcolor('rgba(' . static::COLOR_RED . ', 0.6)'); } } ================================================ FILE: src/Config/config.php ================================================ env('METER_ENABLED', false), #--------------------------------------------------------------------- /* * This is the subdomain where Meter will be accessible from. If the * setting is null, Meter will reside under the same domain as the * application. */ 'domain' => env('METER_DOMAIN', null), #--------------------------------------------------------------------- /* * URI path Meter will be accessible from. * * Username and password for basic http authentication to access * meter interface. */ 'path' => env('METER_PATH', 'meter'), 'username' => env('METER_USERNAME', 'meter'), 'password' => env('METER_PASSWORD', 'meter'), #--------------------------------------------------------------------- /* * These middleware will be assigned to every Meter route, giving you * the chance to add your own middleware to this list or change any of * the existing middleware. */ 'middleware' => [ // ], #--------------------------------------------------------------------- /* | The following array lists the URI paths and Artisan commands that will | not be watched by Meter. In addition to this list, some Laravel | commands like migrations and queue commands are always ignored. */ 'ignore_paths' => [ // ], 'ignore_commands' => [ // ], 'ignore_matched_string' => [ "query" => [ "information_schema", ] ], #--------------------------------------------------------------------- /* * The database connection Meter will use to store data. By default, it * will use the default database connection. */ 'storage' => [ 'database' => [ 'connection' => env('METER_DB_CONNECTION', 'mysql'), ], ], #--------------------------------------------------------------------- // Customize the monitors meter will use to show statiscs of. 'monitors' => [ Sarfraznawaz2005\Meter\Monitors\RequestMonitor::class => [ 'enabled' => env('METER_REQUEST_MONITOR', true), 'slow' => 3000, // considered slow if equal or over given time in ms 'graph_type' => env('METER_REQUEST_GRAPH_TYPE', 'bar'), // bar, line 'show_on_dashboard' => env('METER_REQUEST_ON_DASHBOARD', true) ], Sarfraznawaz2005\Meter\Monitors\QueryMonitor::class => [ 'enabled' => env('METER_QUERY_MONITOR', true), 'ignore_packages' => true, 'slow' => 500, // considered slow if equal or over given time in ms 'graph_type' => env('METER_QUERY_GRAPH_TYPE', 'bar'), // bar, line 'show_on_dashboard' => env('METER_QUERY_ON_DASHBOARD', true) ], Sarfraznawaz2005\Meter\Monitors\CommandMonitor::class => [ 'enabled' => env('METER_COMMAND_MONITOR', true), 'ignore' => [], 'graph_type' => env('METER_COMMAND_GRAPH_TYPE', 'bar'), // bar, line 'show_on_dashboard' => env('METER_COMMAND_ON_DASHBOARD', true) ], Sarfraznawaz2005\Meter\Monitors\EventMonitor::class => [ 'enabled' => env('METER_EVENT_MONITOR', true), 'ignore' => [], 'graph_type' => env('METER_EVENT_GRAPH_TYPE', 'bar'), // bar, line 'show_on_dashboard' => env('METER_EVENT_ON_DASHBOARD', true) ], Sarfraznawaz2005\Meter\Monitors\ScheduleMonitor::class => [ 'enabled' => env('METER_SCHEDULE_MONITOR', true), 'graph_type' => env('METER_SCHEDULE_GRAPH_TYPE', 'bar'), // bar, line 'show_on_dashboard' => env('METER_SCHEDULE_ON_DASHBOARD', true) ], ##################################################################### # below monitors are run via "meter:servermonitor" command if enabled ##################################################################### // monitors average CPU usage Sarfraznawaz2005\Meter\Monitors\CpuMonitor::class => [ 'enabled' => env('METER_CPU_MONITOR', false), 'graph_type' => env('METER_CPU_GRAPH_TYPE', 'bar'), // bar, line 'show_on_dashboard' => env('METER_CPU_ON_DASHBOARD', true) ], // monitors disk space usage Sarfraznawaz2005\Meter\Monitors\DiskSpaceMonitor::class => [ 'enabled' => env('METER_DISK_MONITOR', false), 'graph_type' => env('METER_DISK_GRAPH_TYPE', 'bar'), // bar, line 'show_on_dashboard' => env('METER_DISK_ON_DASHBOARD', true) ], // monitors server memory usage Sarfraznawaz2005\Meter\Monitors\MemoryMonitor::class => [ 'enabled' => env('METER_MEMORY_MONITOR', false), 'graph_type' => env('METER_MEMORY_GRAPH_TYPE', 'bar'), // bar, line 'show_on_dashboard' => env('METER_MEMOR_ON_DASHBOARD', true) ], // monitors active http connections count on port 80 Sarfraznawaz2005\Meter\Monitors\HttpConnectionsMonitor::class => [ 'enabled' => env('METER_HTTP_CONNECTIONS_MONITOR', false), 'port' => env('METER_HTTP_CONNECTIONS_PORT', 80), 'graph_type' => env('METER_HTTP_CONNECTIONS_GRAPH_TYPE', 'bar'), // bar, line 'show_on_dashboard' => env('METER_HTTP_CONNECTIONS_ON_DASHBOARD', true) ], ], // Graph/Table filters. Will be added between "Today" and "All". 'filters' => [ 'Yesterday' => 1, 'Week' => 7, 'Month' => 30, '3 Month' => 90, '6 Month' => 180, 'Year' => 365, ], ]; ================================================ FILE: src/Console/PruneCommand.php ================================================ whereDate('created_at', '<', now()->subDays($this->option('days')))->delete(); $this->info($deleteCount.' entries pruned.'); } } ================================================ FILE: src/Console/PublishCommand.php ================================================ call('vendor:publish', [ '--tag' => 'meter-config', '--force' => $this->option('force'), ]); $this->call('vendor:publish', [ '--tag' => 'meter-views', '--force' => true, ]); $this->call('vendor:publish', [ '--tag' => 'meter-assets', '--force' => true, ]); } } ================================================ FILE: src/Console/ServerMonitorCommand.php ================================================ make($monitor); $monitor->register($app); } } } } } ================================================ FILE: src/Helpers.php ================================================ is((array)$path) || request()->routeIs((array)$path)) { return $class; } return ''; } } if (!function_exists('meterWithHtmlTitle')) { function meterWithHtmlTitle($text, $title) { return "$text"; } } if (!function_exists('meterBadge')) { function meterBadge($text) { return "$text"; } } if (!function_exists('meterAutoBadge')) { function meterAutoBadge($text, $colorMap) { $color = array_keys(array_filter($colorMap))[0] ?? 'badge-secondary'; return "$text"; } } if (!function_exists('meterCenter')) { function meterCenter($text) { return "
$text
"; } } if (!function_exists('meterDetailsButton')) { function meterDetailsButton($details) { $details = htmlspecialchars(json_encode($details), ENT_QUOTES, 'UTF-8'); return <<< HTML HTML; } } if (!function_exists('meterGetSql')) { function meterGetSql(Builder $builder) { $addSlashes = str_replace('?', "'?'", $builder->toSql()); return vsprintf(str_replace('?', '%s', $addSlashes), $builder->getBindings()); } } if (!function_exists('meterFormatModel')) { function meterFormatModel(Model $model) { return get_class($model) . ':' . implode('_', Arr::wrap($model->getKey())); } } if (!function_exists('meterIgnoreEntry')) { function meterIgnoreEntry($key, $content) { return str_contains($content, $key); } } ================================================ FILE: src/Http/Controllers/PagesController.php ================================================ filtered()->first(); return view( 'meter::dashboard', compact( 'totals', 'requestTimeChart', 'requestMemoryChart', 'queriesTimeChart', 'commandsTimeChart', 'eventsTimeChart', 'schedulesTimeChart', 'cpuChart', 'diskSpaceChart', 'serverMemoryChart', 'connectionsChart' ) ); } public function queries(QueriesTimeChart $queriesTimeChart) { return view('meter::queries', compact('queriesTimeChart')); } public function requests( RequestTimeChart $timeChart, RequestMemoryChart $memoryChart ) { return view('meter::requests', compact('timeChart', 'memoryChart')); } public function commands(CommandsTimeChart $chart) { return view('meter::commands', compact('chart')); } public function events(EventsTimeChart $chart) { return view('meter::events', compact('chart')); } public function schedules(SchedulesTimeChart $chart) { return view('meter::schedules', compact('chart')); } public function serverStats( CpuChart $cpuChart, DiskSpaceChart $diskSpaceChart, ServerMemoryChart $serverMemoryChart, ConnectionsChart $connectionsChart ) { return view('meter::server', compact('cpuChart', 'diskSpaceChart', 'serverMemoryChart', 'connectionsChart')); } } ================================================ FILE: src/Http/Controllers/TablesController.php ================================================ getData(); } public function queriesTable(QueriesTable $table): array { return $table->getData(); } public function commandsTable(CommandsTable $table): array { return $table->getData(); } public function eventsTable(EventsTable $table): array { return $table->getData(); } public function schedulesTable(SchedulesTable $table): array { return $table->getData(); } } ================================================ FILE: src/Http/Middleware/BasicAuth.php ================================================ name('meter_home'); Route::get('requests', 'PagesController@requests')->name('meter_requests'); Route::get('queries', 'PagesController@queries')->name('meter_queries'); Route::get('commands', 'PagesController@commands')->name('meter_commands'); Route::get('events', 'PagesController@events')->name('meter_events'); Route::get('schedules', 'PagesController@schedules')->name('meter_schedules'); Route::get('server_stats', 'PagesController@serverStats')->name('meter_server_stats'); // tables Route::get('requests_table', 'TablesController@requestsTable')->name('meter_requests_table'); Route::get('queries_table', 'TablesController@queriesTable')->name('meter_queries_table'); Route::get('commands_table', 'TablesController@commandsTable')->name('meter_commands_table'); Route::get('events_table', 'TablesController@eventsTable')->name('meter_events_table'); Route::get('schedules_table', 'TablesController@schedulesTable')->name('meter_schedules_table'); ================================================ FILE: src/Meter.php ================================================ runningInConsole() && !in_array($_SERVER['argv'][1] ?? null, $ignoredCommands, true); } /** * Determine if the application is handling an approved request. * * @param $app * @return bool */ protected static function handlingApprovedRequest($app): bool { $ignoredPaths = array_merge([ config('meter.path') . '*', 'meter*', 'debugbar*', '_debugbar*', 'clockwork*', '_clockwork*', 'telescope*', 'vendor/meter*', 'horizon*', 'vendor/horizon*', 'nova-api*', ], config('meter.ignore_paths', [])); return !$app->runningInConsole() && !$app['request']->is($ignoredPaths); } /** * Start monitoring. * * @return void */ public static function startMonitoring() { static::$shouldMonitor = true; } /** * Stop monitoring. * * @return void */ public static function stopMonitoring() { static::$shouldMonitor = false; } /** * Determine if Meter is monitoring. * * @return bool */ public static function isMonitoring(): bool { return static::$shouldMonitor; } } ================================================ FILE: src/MeterServiceProvider.php ================================================ app->runningInConsole()) { $this->publishes([__DIR__ . '/Config/config.php' => config_path('meter.php')], 'meter-config'); $this->publishes([__DIR__ . '/Migrations' => database_path('migrations')], 'meter-migration'); $this->publishes([__DIR__ . '/Resources/Views' => resource_path('views/vendor/meter')], 'meter-views'); $this->publishes([__DIR__ . '/Resources/Assets' => public_path('vendor/meter')], 'meter-assets'); } if (!config('meter.enabled')) { return; } $this->registerDbConnection(); Meter::start($this->app); if (method_exists($router, 'aliasMiddleware')) { $router->aliasMiddleware('auth.basic_meter', BasicAuth::class); } else { $router->middleware('auth.basic_meter', BasicAuth::class); } Route::middlewareGroup('meter', array_merge(config('meter.middleware', []), ['web', 'auth.basic_meter'])); $this->loadViewsFrom(__DIR__ . '/Resources/Views', 'meter'); $this->loadMigrationsFrom(__DIR__ . '/Migrations'); Route::group([ 'domain' => config('meter.domain', null), 'namespace' => 'Sarfraznawaz2005\Meter\Http\Controllers', 'prefix' => config('meter.path'), 'middleware' => 'meter', ], function () { $this->loadRoutesFrom(__DIR__ . '/Http/routes.php'); }); } // see: https://github.com/sarfraznawaz2005/meter/issues/2 // see: https://github.com/laravel/framework/issues/25768 protected function registerDbConnection() { $defaultConnection = config('database.default'); config(['database.connections.meter' => config("database.connections.{$defaultConnection}")]); } /** * Register the application services. */ public function register() { $this->mergeConfigFrom(__DIR__ . '/Config/config.php', 'meter'); $this->commands([ PruneCommand::class, PublishCommand::class, ServerMonitorCommand::class, ]); $this->app->singleton('meter', static function () { return new Meter; }); } } ================================================ FILE: src/Migrations/2019_12_12_100000_create_meter_entries_table.php ================================================ create('meter_entries', static function (Blueprint $table) { $table->bigIncrements('id'); $table->string('type', 20); $table->enum('is_slow', ['No', 'Yes'])->default('No'); $table->longText('content'); $table->dateTime('created_at')->nullable(); $table->index(['type']); $table->index(['created_at']); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('meter_entries'); } } ================================================ FILE: src/Models/MeterModel.php ================================================ 'json', ]; const UPDATED_AT = null; function __construct() { $this->connection = config('meter.storage.database.connection'); } /** * Scope the query for the given type. * * @param Builder $query * @param string $type * @return Builder */ public function scopeType($query, $type): Builder { return $query->where('type', $type); } /** * Scope the query for the given filter. * * @param Builder $query * @return Builder */ public function scopeFiltered($query): Builder { if (request()->has('all')) { $builder = $query; } elseif (request()->has('days')) { // not using "WHERE DATE(created_at)" since created_at is index column. $date = now()->subDays(request()->days)->toDateString(); $builder = $query->whereRaw("created_at >= '$date 00:00:00'"); } elseif (request()->has('slow')) { $builder = $query->where('is_slow', 1); } else { // default today $builder = $query->whereRaw("created_at >= '" . now()->toDateString() . " 00:00:00'"); } return $builder; } } ================================================ FILE: src/Monitors/CommandMonitor.php ================================================ events->listen(CommandStarting::class, function () { $this->startTime = microtime(true); }); return $app->events->listen(CommandFinished::class, [$this, 'collect']); } /** * Collect entry data and save in $data variable. * * @param CommandFinished $event */ public function collect(CommandFinished $event) { if (!Meter::isMonitoring() || $this->shouldIgnore($event)) { return; } $content = [ 'time' => floor((microtime(true) - $this->startTime) * 1000), 'command' => $event->command ?? $event->input->getArguments()['command'] ?? 'default', 'exit_code' => $event->exitCode, 'arguments' => $event->input->getArguments(), 'options' => $event->input->getOptions(), ]; $this->record(Type::COMMAND, false, $content); } /** * Determine if the event should be ignored. * * @param mixed $event * @return bool */ private function shouldIgnore($event): bool { return in_array($event->command, array_merge($this->options['ignore'] ?? [], [ 'schedule:run', 'schedule:finish', 'package:discover', 'meter:prune', 'meter:publish', 'meter:servermonitor', ]), true); } } ================================================ FILE: src/Monitors/CpuMonitor.php ================================================ record(Type::CPU, false, ['percent' => round($cpuPercent)]); } catch (\Exception $e) { } } } ================================================ FILE: src/Monitors/DiskSpaceMonitor.php ================================================ record(Type::DISK, false, ['percent' => $percent]); } } ================================================ FILE: src/Monitors/EventMonitor.php ================================================ startTime = microtime(true); return $app->events->listen('*', function ($eventName, $payload) { $this->collect($eventName, $payload); }); } /** * Collect entry data and save in $data variable. * * @param $eventName * @param $payload * @throws \ReflectionException */ public function collect($eventName, $payload) { if (!Meter::isMonitoring() || $this->shouldIgnore($eventName)) { return; } $formattedPayload = $this->extractPayload($eventName, $payload); $content = [ 'time' => floor((microtime(true) - $this->startTime) * 1000), 'name' => $eventName, 'payload' => empty($formattedPayload) ? null : $formattedPayload, 'listeners' => $this->formatListeners($eventName), 'broadcast' => class_exists($eventName) ? in_array(ShouldBroadcast::class, (array)class_implements($eventName), true) : false, ]; $this->record(Type::EVENT, false, $content); } /** * Extract the payload and tags from the event. * * @param string $eventName * @param array $payload * @return array * @throws \ReflectionException */ protected function extractPayload($eventName, $payload): array { if (is_object($payload[0]) && isset($payload[0]) && class_exists($eventName)) { return $this->extractProperties($payload[0]); } return collect($payload)->map(static function ($value) { return is_object($value) ? [ 'class' => get_class($value), 'properties' => json_decode(json_encode($value), true), ] : $value; })->toArray(); } /** * Extract the properties for the given object in array form. * * The given array is ready for storage. * * @param mixed $target * @return array * @throws \ReflectionException */ protected function extractProperties($target): array { return collect((new ReflectionClass($target))->getProperties()) ->mapWithKeys(static function ($property) use ($target) { $property->setAccessible(true); if (($value = $property->getValue($target)) instanceof Model) { return [$property->getName() => meterFormatModel($value)]; } if (is_object($value)) { return [ $property->getName() => [ 'class' => get_class($value), 'properties' => json_decode(json_encode($value), true), ], ]; } return [$property->getName() => json_decode(json_encode($value), true)]; })->toArray(); } /** * Format list of event listeners. * * @param string $eventName * @return array */ protected function formatListeners($eventName): array { return collect(app('events')->getListeners($eventName)) ->map(function ($listener) { $listener = (new ReflectionFunction($listener))->getStaticVariables()['listener']; if (is_string($listener)) { return Str::contains($listener, '@') ? $listener : $listener . '@handle'; } if (is_array($listener)) { return get_class($listener[0]) . '@' . $listener[1]; } return $this->formatClosureListener($listener); })->reject(static function ($listener) { return Str::contains($listener, 'Laravel\\Telescope'); })->map(static function ($listener) { if (Str::contains($listener, '@')) { $queued = in_array(ShouldQueue::class, class_implements(explode('@', $listener)[0]), true); } return [ 'name' => $listener, 'queued' => $queued ?? false, ]; })->values()->toArray(); } /** * Determine if the event should be ignored. * * @param string $eventName * @return bool */ protected function shouldIgnore($eventName): bool { return $this->eventIsIgnored($eventName) || $this->eventIsFiredByTheFramework($eventName); } /** * Determine if the event was fired internally by Laravel. * * @param string $eventName * @return bool */ protected function eventIsFiredByTheFramework($eventName): bool { return Str::is( ['Illuminate\*', 'eloquent*', 'bootstrapped*', 'bootstrapping*', 'creating*', 'composing*'], $eventName ); } /** * Determine if the event is ignored manually. * * @param string $eventName * @return bool */ protected function eventIsIgnored($eventName): bool { return Str::is($this->options['ignore'] ?? [], $eventName); } } ================================================ FILE: src/Monitors/HttpConnectionsMonitor.php ================================================ &1"); if (trim($connections)) { $count = count(array_filter(explode("\n", $connections))); } $this->record(Type::CONNECTIONS, false, ['count' => $count]); } catch (\Exception $e) { } } } ================================================ FILE: src/Monitors/MemoryMonitor.php ================================================ record(Type::MEMORY, false, ['percent' => $percent]); } } catch (\Exception $e) { } } } ================================================ FILE: src/Monitors/Monitor.php ================================================ options = $options; $this->model = new MeterModel(); } /** * Listens to event(s) and performs actions. * * @param Application $app * @return void */ abstract public function register($app); /** * Saves collected data in DB. * * @param string $type * @param boolean $isSlow * @param array $content * @return mixed */ public function record($type, $isSlow, array $content) { Meter::stopMonitoring(); $result = true; $skip = false; if (count(config('meter.ignore_matched_string', [])) > 0) { foreach (config('meter.ignore_matched_string', []) as $queryType) { if (!is_array($queryType)) { continue; } if(count($queryType) == 0) { continue; } foreach ($queryType as $string) { if (in_array($string, $content)) { $skip = true; break; } } } } if ($skip === false) { $result = $this->model->create([ 'type' => $type, 'is_slow' => $isSlow ? '1' : '0', 'content' => $content, 'created_at' => now(), ]); } Meter::startMonitoring(); return $result; } } ================================================ FILE: src/Monitors/QueryMonitor.php ================================================ events->listen(QueryExecuted::class, [$this, 'collect']); } /** * Collect entry data and save in $data variable. * * @param QueryExecuted $event */ public function collect(QueryExecuted $event) { if (!Meter::isMonitoring()) { return; } $time = $event->time; $isSlow = isset($this->options['slow']) && $time >= $this->options['slow']; $caller = $this->getCallerFromStackTrace(); $content = [ 'connection' => $event->connectionName, 'sql' => $this->replaceBindings($event), 'time' => number_format($time, 2), 'file' => $caller['file'], 'line' => $caller['line'], ]; $this->record(Type::QUERY, $isSlow, $content); } /** * Replace the placeholders with the actual bindings. * * @param QueryExecuted $event * @return string */ public function replaceBindings($event): string { $sql = $event->sql; foreach ($this->formatBindings($event) as $key => $binding) { $regex = is_numeric($key) ? "/\?(?=(?:[^'\\\']*'[^'\\\']*')*[^'\\\']*$)/" : "/:{$key}(?=(?:[^'\\\']*'[^'\\\']*')*[^'\\\']*$)/"; if ($binding === null) { $binding = 'null'; } elseif (!is_int($binding) && !is_float($binding)) { $binding = $event->connection->getPdo()->quote($binding); } $sql = preg_replace($regex, $binding, $sql, 1); } return $sql; } /** * Format the given bindings to strings. * * @param QueryExecuted $event * @return array */ protected function formatBindings($event): array { return $event->connection->prepareBindings($event->bindings); } } ================================================ FILE: src/Monitors/RequestMonitor.php ================================================ events->listen(RequestHandled::class, [$this, 'collect']); } /** * Collect entry data and save in $data variable. * * @param RequestHandled $event */ public function collect(RequestHandled $event) { if (!Meter::isMonitoring()) { return; } $isSlow = false; $startTime = defined('LARAVEL_START') ? LARAVEL_START : $event->request->server('REQUEST_TIME_FLOAT'); $duration = $startTime ? floor((microtime(true) - $startTime) * 1000) : null; if (isset($this->options['slow']) && $duration) { $isSlow = $duration >= $this->options['slow']; } $content = [ 'uri' => str_replace($event->request->root(), '', $event->request->fullUrl()) ?: '/', 'method' => $event->request->method(), 'controller_action' => optional($event->request->route())->getActionName(), 'middleware' => array_values(optional($event->request->route())->gatherMiddleware() ?? []), 'response_status' => $event->response->getStatusCode(), 'duration' => $duration, 'memory' => round(memory_get_peak_usage(true) / 1024 / 1025, 1), 'ip' => $event->request->ip(), ]; $this->record(Type::REQUEST, $isSlow, $content); } } ================================================ FILE: src/Monitors/ScheduleMonitor.php ================================================ events->listen(CommandStarting::class, [$this, 'collect']); } /** * Collect entry data and save in $data variable. * * @param CommandStarting $event */ public function collect(CommandStarting $event) { if (($event->command !== 'schedule:run' && $event->command !== 'schedule:finish') || !Meter::isMonitoring()) { return; } $this->startTime = microtime(true); collect(app(Schedule::class)->events())->each(function ($event) { $event->then(function () use ($event) { $expression = CronExpression::factory($event->expression); $content = [ 'time' => floor((microtime(true) - $this->startTime) * 1000), 'command' => $event instanceof CallbackEvent ? 'Closure' : $this->fixupCommand($event->command), 'description' => $event->description, 'expression' => $event->expression, 'timezone' => $event->timezone, 'user' => $event->user, 'output' => $this->getEventOutput($event), 'next_run' => $expression->getNextRunDate(), ]; $this->record(Type::SCHEDULE, false, $content); }); }); } /** * Get the output for the scheduled event. * * @param Event $event * @return string|null */ protected function getEventOutput(Event $event) { if ($event->shouldAppendOutput || !$event->output || $event->output === $event->getDefaultOutput() || !file_exists($event->output)) { return ''; } return trim(file_get_contents($event->output)); } /** * Removes PHP path from command name. * * @param $command * @return string */ protected function fixupCommand($command) { $parts = explode(' ', $command); if (count($parts) > 2 && ($parts[1] === "'artisan'" || $parts[1] === '"artisan"')) { array_shift($parts); } $command = implode(' ', $parts); return trim(str_ireplace(["'artisan'", '"artisan"'], '', $command)); } } ================================================ FILE: src/Resources/Assets/meter.css ================================================ body, .wrapper { font-family: Nunito !important; background: #edf1f3; color: #333; font-size: .95rem; border-radius: unset !important; } .wrapper { margin-top: 4rem; } .logo { font-size: 25px; } .card-body { padding: 1.5rem 0.50rem 0.50rem 0.50rem; } .table td { vertical-align: middle !important; font-size: .90rem; } .navbar-nav .nav-item { margin-left: 5px; } .nav-tabs .nav-link { border: none; } .nav-item a { font-size: .95rem; } .table thead { background: #DAE0E5; color: #666; font-size: .90rem; } .table th:last-child { text-align: center; } .card { border: none; } .navbar-toggler { width: 47px; height: 34px; background-color: #d3d3d3; border: none; outline: none; } .navbar-toggler .line { width: 100%; float: left; height: 2px; background-color: #999; outline: none; margin-bottom: 5px; } table { table-layout: fixed; word-wrap: break-word; } table.dataTable { border-collapse: collapse !important; } .table-bordered td, .table-bordered th { border: none; border-bottom: 1px solid #eee !important; } .table td, .table th { padding: .50rem; } .badge { font-size: .90rem; min-width: 40px; color: #212529; } .badge-primary { background-color: #8ebef2; } .badge-secondary { background-color: #DAE0E5; } .badge-info { background-color: #c4e7ed; } .badge-success { background-color: #a2e5b1; } .badge-warning { background-color: #FFF382; } .badge-danger { background-color: #f4bcc1; } .dataTables_wrapper .dataTables_processing { background-color: #a2e5b1; color: #212529; position: absolute !important; top: 1% !important; } .table-hover tbody tr { background-color: #fff !important; } .table-hover tbody tr:hover { background-color: #edf1f3 !important; } .icon { color: #666; font-size: 1.2rem; } .icon:hover { color: #000; font-size: 1.2rem; } .modal-dialog { position: relative; display: table; overflow-y: auto; overflow-x: auto; width: auto; min-width: 300px; } .nav.nav-tabs { padding-bottom: 2px; border: none; } .tab-content, .main { border-radius: 10px; margin: 0 10px 50px 10px; } .section { border-radius: 10px; margin: 0 20px 25px 20px; background: #fff; padding: 1.5rem 0.5rem; box-shadow: 0 2.8px 2.2px rgba(0, 0, 0, 0.02), 0 6.7px 5.3px rgba(0, 0, 0, 0.028), 0 12.5px 10px rgba(0, 0, 0, 0.035), 0 22.3px 17.9px rgba(0, 0, 0, 0.042), 0 41.8px 33.4px rgba(0, 0, 0, 0.05), 0 100px 80px rgba(0, 0, 0, 0.07); } .shadow { box-shadow: 0 2.8px 2.2px rgba(0, 0, 0, 0.02), 0 6.7px 5.3px rgba(0, 0, 0, 0.028), 0 12.5px 10px rgba(0, 0, 0, 0.035), 0 22.3px 17.9px rgba(0, 0, 0, 0.042), 0 41.8px 33.4px rgba(0, 0, 0, 0.05), 0 100px 80px rgba(0, 0, 0, 0.07); } .meter_sql { font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: #c7254e; } .meter_sql .sql_keyword { color: #2953e1; text-transform: uppercase; } #detailsModal pre { font-family: Menlo, Monaco, Consolas, "Courier New", monospace; background: #eee; padding: 10px; border-radius: 5px; font-weight: bold; } pre.json .json_keyword { color: #c7254e; } pre.json .json_keyword_braces { color: #2953e1; } a.nav-link, a.nav-link:active, a.nav-link:visited { color: #2953e1; } a.nav-link:hover { color: #ff1493; } div.table-responsive-sm { background: #EDF1F3; padding: 20px 5px; border-radius: 10px; margin: 10px; } .totals a { text-decoration: none; } ================================================ FILE: src/Resources/Assets/meter.js ================================================ $(document).ready(function () { meterSetup(); $('.dataTable').on('draw.dt', meterSetup); // details modal $(document).on('click', '.btnDetails', function () { var details = ''; var detailsObject = $(this).data('details'); var $modal = $('#detailsModal'); for (var key in detailsObject) { if (detailsObject.hasOwnProperty(key) && key !== 'Details') { details += ''; } } details += '
' + key + '' + detailsObject[key] + '
'; $modal.find('.modal-body').html(details); $modal.modal('show'); return false; }); $('#detailsModal').on('shown.bs.modal', function () { meterHighlightSQL(); meterHighlightJson(); }); }); function meterSetup() { $('[data-toggle="popover"]').popover({ html: true, placement: 'top', trigger: 'hover' }); $('[data-toggle="tooltip"]').tooltip(); meterHighlightSQL(); } // creates DataTables function meterTable(tableSelector, url, length, columns, extraOptions, data) { length = length || 10; extraOptions = extraOptions || {}; data = data || {}; var options = { "serverSide": true, "processing": true, "responsive": true, "autoWidth": false, "ordering": false, "lengthChange": false, "pageLength": length, "ajax": { "url": url, "dataType": "json", "type": "GET", "data": data }, "columns": columns }; // merge options = $.extend({}, options, extraOptions); return $(tableSelector).DataTable(options); } // highlights SQL keywords function meterHighlightSQL() { var sqlReg = /\b(AND|AS|ASC|BETWEEN|BY|CASE|CURRENT_DATE|CURRENT_TIME|DELETE|DESC|DISTINCT|EACH|ELSE|ELSEIF|FALSE|FOR|FROM|GROUP|HAVING|IF|IN|INSERT|INTERVAL|INTO|IS|JOIN|KEY|KEYS|LEFT|LIKE|LIMIT|MATCH|NOT|NULL|ON|OPTION|OR|ORDER|OUT|OUTER|REPLACE|RIGHT|SELECT|SET|TABLE|THEN|TO|TRUE|UPDATE|VALUES|WHEN|WHERE|CREATE|ALTER|ALL|DATABASE|GRANT|PRIVILEGES|IDENTIFIED|FLUSH|INNER|COUNT)(?=[^\w])/ig; document.querySelectorAll("td .meter_sql").forEach(function(item) { item.innerHTML = item.innerHTML.replace(sqlReg,'$1'); }); } // highlights JSON keywords function meterHighlightJson() { var reg = /(.+?(?=: ))/g; document.querySelectorAll("td pre.json").forEach(function(item) { item.innerHTML = item.innerHTML.replace(/({|}|\[|\])/g,'$1'); item.innerHTML = item.innerHTML.replace(reg,'$1'); }); } ================================================ FILE: src/Resources/Views/commands.blade.php ================================================ @extends('meter::layout.layout') @section('content')
@component('meter::components.chart', ['chart' => $chart, 'title' => 'Command Times'])@endcomponent
@component('meter::components.table',[ 'url' => route('meter_commands_table'), 'columns' => ['Happened', 'Command', 'Time', 'Exit Code', 'More'] ]) @endcomponent
@endsection ================================================ FILE: src/Resources/Views/components/chart.blade.php ================================================
@if (isset($title))
{{$title}}
@endif
{!! $chart->container() !!}
@push('js') {!! $chart->script() !!} @endpush ================================================ FILE: src/Resources/Views/components/table.blade.php ================================================
@foreach($columns as $column) @endforeach
{{$column}}
@push('js') @endpush ================================================ FILE: src/Resources/Views/dashboard.blade.php ================================================ @extends('meter::layout.layout') @section('content')
@if (config('meter.monitors.' . Sarfraznawaz2005\Meter\Monitors\RequestMonitor::class . '.enabled', true)) @endif @if (config('meter.monitors.' . Sarfraznawaz2005\Meter\Monitors\QueryMonitor::class . '.enabled', true)) @endif @if (config('meter.monitors.' . Sarfraznawaz2005\Meter\Monitors\CommandMonitor::class . '.enabled', true)) @endif @if (config('meter.monitors.' . Sarfraznawaz2005\Meter\Monitors\EventMonitor::class . '.enabled', true)) @endif @if (config('meter.monitors.' . Sarfraznawaz2005\Meter\Monitors\ScheduleMonitor::class . '.enabled', true)) @endif
@if ( config('meter.monitors.' . Sarfraznawaz2005\Meter\Monitors\RequestMonitor::class . '.enabled', true) && config('meter.monitors.' . Sarfraznawaz2005\Meter\Monitors\RequestMonitor::class . '.show_on_dashboard', true) )
@component('meter::components.chart', ['chart' => $requestTimeChart, 'title' => 'Response Times'])@endcomponent
@component('meter::components.chart', ['chart' => $requestMemoryChart, 'title' => 'Request Memory Usage'])@endcomponent
@endif @if ( config('meter.monitors.' . Sarfraznawaz2005\Meter\Monitors\QueryMonitor::class . '.enabled', true) && config('meter.monitors.' . Sarfraznawaz2005\Meter\Monitors\QueryMonitor::class . '.show_on_dashboard', true) )
@component('meter::components.chart', ['chart' => $queriesTimeChart, 'title' => 'Query Times'])@endcomponent
@endif @if ( config('meter.monitors.' . Sarfraznawaz2005\Meter\Monitors\CommandMonitor::class . '.enabled', true) && config('meter.monitors.' . Sarfraznawaz2005\Meter\Monitors\CommandMonitor::class . '.show_on_dashboard', true) )
@component('meter::components.chart', ['chart' => $commandsTimeChart, 'title' => 'Command Times'])@endcomponent
@endif @if ( config('meter.monitors.' . Sarfraznawaz2005\Meter\Monitors\EventMonitor::class . '.enabled', true) && config('meter.monitors.' . Sarfraznawaz2005\Meter\Monitors\EventMonitor::class . '.show_on_dashboard', true) )
@component('meter::components.chart', ['chart' => $eventsTimeChart, 'title' => 'Event Times'])@endcomponent
@endif @if ( config('meter.monitors.' . Sarfraznawaz2005\Meter\Monitors\ScheduleMonitor::class . '.enabled', true) && config('meter.monitors.' . Sarfraznawaz2005\Meter\Monitors\ScheduleMonitor::class . '.show_on_dashboard', true) )
@component('meter::components.chart', ['chart' => $schedulesTimeChart, 'title' => 'Schedule Times'])@endcomponent
@endif @if ( config('meter.monitors.' . Sarfraznawaz2005\Meter\Monitors\CpuMonitor::class . '.enabled', true) && config('meter.monitors.' . Sarfraznawaz2005\Meter\Monitors\CpuMonitor::class . '.show_on_dashboard', true) )
@component('meter::components.chart', ['chart' => $cpuChart, 'title' => 'Server CPU Usage'])@endcomponent
@endif @if ( config('meter.monitors.' . Sarfraznawaz2005\Meter\Monitors\DiskSpaceMonitor::class . '.enabled', true) && config('meter.monitors.' . Sarfraznawaz2005\Meter\Monitors\DiskSpaceMonitor::class . '.show_on_dashboard', true) )
@component('meter::components.chart', ['chart' => $diskSpaceChart, 'title' => 'Server Disk Space Usage'])@endcomponent
@endif @if ( config('meter.monitors.' . Sarfraznawaz2005\Meter\Monitors\MemoryMonitor::class . '.enabled', true) && config('meter.monitors.' . Sarfraznawaz2005\Meter\Monitors\MemoryMonitor::class . '.show_on_dashboard', true) )
@component('meter::components.chart', ['chart' => $serverMemoryChart, 'title' => 'Server Memory Usage'])@endcomponent
@endif @if ( config('meter.monitors.' . Sarfraznawaz2005\Meter\Monitors\HttpConnectionsMonitor::class . '.enabled', true) && config('meter.monitors.' . Sarfraznawaz2005\Meter\Monitors\HttpConnectionsMonitor::class . '.show_on_dashboard', true) )
@component('meter::components.chart', ['chart' => $connectionsChart, 'title' => 'Server HTTP Connections Count'])@endcomponent
@endif
@endsection ================================================ FILE: src/Resources/Views/events.blade.php ================================================ @extends('meter::layout.layout') @section('content')
@component('meter::components.chart', ['chart' => $chart, 'title' => 'Event Times'])@endcomponent
@component('meter::components.table',[ 'url' => route('meter_events_table'), 'columns' => ['Happened', 'Event', 'Time', 'Listeners', 'More'] ]) @endcomponent
@endsection ================================================ FILE: src/Resources/Views/layout/filters.blade.php ================================================ Today @foreach(config('meter.filters', []) as $name => $days) {{$name}} @endforeach All @push('js') @endpush ================================================ FILE: src/Resources/Views/layout/layout.blade.php ================================================ Meter @stack('css') @include('meter::layout.links')
@yield('content')
@include('meter::layout.modal') @stack('js') ================================================ FILE: src/Resources/Views/layout/links.blade.php ================================================ ================================================ FILE: src/Resources/Views/layout/modal.blade.php ================================================ ================================================ FILE: src/Resources/Views/queries.blade.php ================================================ @extends('meter::layout.layout') @section('content')
@component('meter::components.chart', ['chart' => $queriesTimeChart, 'title' => 'Query Times'])@endcomponent
@component('meter::components.table',[ 'url' => route('meter_queries_table'), 'columns' => ['Happened', 'Query', 'Time', 'Slow', 'More'], 'columnDefs' => ['{"width": "10%", "targets": 0}', '{"width": "5%", "targets": -1}', '{"width": "10%", "targets": -2}', '{"width": "10%", "targets": -3}'] ]) @endcomponent
@endsection ================================================ FILE: src/Resources/Views/requests.blade.php ================================================ @extends('meter::layout.layout') @section('content')
@component('meter::components.chart', ['chart' => $timeChart, 'title' => 'Response Times'])@endcomponent @component('meter::components.chart', ['chart' => $memoryChart, 'title' => 'Memory Usage'])@endcomponent
@component('meter::components.table',[ 'url' => route('meter_requests_table'), 'columns' => ['Happened', 'Verb', 'Path', 'Status', 'Time', 'Memory', 'Slow', 'More'], ]) @endcomponent
@endsection ================================================ FILE: src/Resources/Views/schedules.blade.php ================================================ @extends('meter::layout.layout') @section('content')
@component('meter::components.chart', ['chart' => $chart, 'title' => 'Schedule Times'])@endcomponent
@component('meter::components.table',[ 'url' => route('meter_schedules_table'), 'columns' => ['Happened', 'Command', 'Expression', 'Time', 'More'] ]) @endcomponent
@endsection ================================================ FILE: src/Resources/Views/server.blade.php ================================================ @extends('meter::layout.layout') @section('content') @component('meter::components.chart', ['chart' => $cpuChart, 'title' => 'Server CPU Usage'])@endcomponent @component('meter::components.chart', ['chart' => $diskSpaceChart, 'title' => 'Server Disk Space Usage'])@endcomponent @component('meter::components.chart', ['chart' => $serverMemoryChart, 'title' => 'Server Memory Usage'])@endcomponent @component('meter::components.chart', ['chart' => $connectionsChart, 'title' => 'Server HTTP Connections Count'])@endcomponent @endsection ================================================ FILE: src/Tables/CommandsTable.php ================================================ columns(); } /** * Table Query * * @return Builder */ public function builder(): Builder { return (new MeterModel)->type(Type::COMMAND)->filtered()->orderBy('id', 'desc'); } /** * Transform data as we need. * * @param array $rows * @return array */ public function transform(array $rows): array { $transformed = []; foreach ($rows as $row) { $data['Happened'] = meterWithHtmlTitle(Carbon::parse($row['created_at'])->diffForHumans(), $row['created_at']); $data['Command'] = $row['content']['command']; $data['Time'] = $row['content']['time'] . ' ms'; $data['Exit Code'] = meterAutoBadge($row['content']['exit_code'], [ 'success' => $row['content']['exit_code'] === 0, 'warning' => $row['content']['exit_code'] !== 0, ]); // additional for details button $details['Arguments'] = '
' . json_encode($row['content']['arguments'], JSON_PRETTY_PRINT) . '
'; $details['Options'] = '
' . json_encode($row['content']['options'], JSON_PRETTY_PRINT) . '
'; $data['More'] = meterCenter(meterDetailsButton($details)); $transformed[] = $data; } return $transformed; } } ================================================ FILE: src/Tables/EventsTable.php ================================================ columns(); } /** * Table Query * * @return Builder */ public function builder(): Builder { return (new MeterModel)->type(Type::EVENT)->filtered()->orderBy('id', 'desc'); } /** * Transform data as we need. * * @param array $rows * @return array */ public function transform(array $rows): array { $transformed = []; foreach ($rows as $row) { $data['Happened'] = meterWithHtmlTitle(Carbon::parse($row['created_at'])->diffForHumans(), $row['created_at']); $data['Event'] = $row['content']['name']; $data['Time'] = $row['content']['time'] . ' ms'; $data['Listeners'] = count($row['content']['listeners']); // additional for details button $details['Listeners'] = '
' . json_encode($row['content']['listeners'], JSON_PRETTY_PRINT) . '
'; $details['Payload'] = '
' . json_encode($row['content']['payload'], JSON_PRETTY_PRINT) . '
'; $details['Broadcast'] = $row['content']['broadcast']; $data['More'] = meterCenter(meterDetailsButton($details)); $transformed[] = $data; } return $transformed; } } ================================================ FILE: src/Tables/QueriesTable.php ================================================ columns(); } /** * Table Query * * @return Builder */ public function builder(): Builder { return (new MeterModel)->type(Type::QUERY)->filtered()->orderBy('id', 'desc'); } /** * Transform data as we need. * * @param array $rows * @return array */ public function transform(array $rows): array { $transformed = []; foreach ($rows as $row) { $data['Happened'] = meterWithHtmlTitle(Carbon::parse($row['created_at'])->diffForHumans(), $row['created_at']); $data['Query'] = '
' . Str::limit($row['content']['sql'], 80) . '
'; $data['Time'] = $row['content']['time'] . ' ms'; $data['Slow'] = meterAutoBadge($row['is_slow'], [ 'secondary' => $row['is_slow'] === 'No', 'danger' => $row['is_slow'] === 'Yes' ]); // additional for details button $details['Query'] = '
' . $row['content']['sql'] . '
'; $details['Connection'] = $row['content']['connection']; $details['File'] = $row['content']['file']; $details['Line'] = $row['content']['line']; $data['More'] = meterCenter(meterDetailsButton($details)); $transformed[] = $data; } return $transformed; } } ================================================ FILE: src/Tables/RequestsTable.php ================================================ columns(); } /** * Table Query * * @return Builder */ public function builder(): Builder { return (new MeterModel)->type(Type::REQUEST)->filtered()->orderBy('id', 'desc'); } /** * Transform data as we need. * * @param array $rows * @return array */ public function transform(array $rows): array { $transformed = []; foreach ($rows as $row) { $data['Happened'] = meterWithHtmlTitle(Carbon::parse($row['created_at'])->diffForHumans(), $row['created_at']); $data['Verb'] = meterBadge($row['content']['method']); $data['Path'] = meterWithHtmlTitle($row['content']['uri'], $row['content']['controller_action']); $data['Status'] = meterAutoBadge($row['content']['response_status'], [ 'success' => $row['content']['response_status'] < 400, 'warning' => $row['content']['response_status'] >= 400 && $row['content']['response_status'] < 500, 'danger' => $row['content']['response_status'] >= 500, ]); $data['Time'] = $row['content']['duration'] . ' ms'; $data['Memory'] = $row['content']['memory'] . ' MB'; $data['Slow'] = meterAutoBadge($row['is_slow'], [ 'secondary' => $row['is_slow'] === 'No', 'danger' => $row['is_slow'] === 'Yes' ]); // additional for details button $details['Controller'] = $row['content']['controller_action']; $details['Middleware'] = $row['content']['middleware']; $details['IP'] = $row['content']['ip']; $data['More'] = meterCenter(meterDetailsButton($details)); $transformed[] = $data; } return $transformed; } } ================================================ FILE: src/Tables/SchedulesTable.php ================================================ columns(); } /** * Table Query * * @return Builder */ public function builder(): Builder { return (new MeterModel)->type(Type::SCHEDULE)->filtered()->orderBy('id', 'desc'); } /** * Transform data as we need. * * @param array $rows * @return array */ public function transform(array $rows): array { $transformed = []; foreach ($rows as $row) { $data['Happened'] = meterWithHtmlTitle(Carbon::parse($row['created_at'])->diffForHumans(), $row['created_at']); $data['Command'] = $row['content']['command']; $data['Expression'] = meterBadge($row['content']['expression']); $data['Time'] = $row['content']['time'] . ' ms'; // additional for details button $details['Description'] = $row['content']['description']; $details['Timezone'] = $row['content']['timezone']; $details['User'] = $row['content']['user']; $details['Output'] = $row['content']['output'] ? '
' . $row['content']['output'] . '
' : 'null'; $data['More'] = meterCenter(meterDetailsButton($details)); $transformed[] = $data; } return $transformed; } } ================================================ FILE: src/Tables/Table.php ================================================ columns(); $searchColumns = $this->searchColumns(); $builder = $this->builder(); $draw = request()->draw; $start = request()->start; $length = request()->length; $orderColumn = request()->order[0]['column'] ?? null; $dir = request()->order[0]['dir'] ?? null; $searchValue = trim(request()->search['value']); // sets the current page Paginator::currentPageResolver(static function () use ($start, $length) { return ($start / $length + 1); }); if ($searchValue) { $where = ''; foreach ($searchColumns as $searchColumn) { $where .= "`$searchColumn` like '%$searchValue%' OR "; } $where = rtrim($where, 'OR '); $builder->whereRaw("($where)"); } $builder->select($columns); if ($orderColumn) { $builder->orderBy($columns[$orderColumn], $dir); } $rows = $builder->paginate($length); $entries = $rows->toArray()['data']; if (method_exists($this, 'transform')) { $entries = $this->transform($entries); } return $entries ? [ 'draw' => $draw, 'recordsTotal' => $rows->total(), 'recordsFiltered' => $rows->total(), 'data' => $entries ] : [ 'draw' => 0, 'recordsTotal' => 0, 'recordsFiltered' => 0, 'data' => [], ]; } } ================================================ FILE: src/Traits/FetchesStackTrace.php ================================================ forget(0); return $trace->first(function ($frame) { if (!isset($frame['file'])) { return false; } return !Str::contains($frame['file'], base_path('vendor' . DIRECTORY_SEPARATOR . $this->ignoredVendorPath()) ); }); } /** * Choose the frame outside of either Telescope/Laravel or all packages. * * @return string|null */ protected function ignoredVendorPath() { if (!($this->options['ignore_packages'] ?? true)) { return 'laravel'; } } } ================================================ FILE: src/Traits/FormatsClosure.php ================================================ getFileName(), $listener->getStartLine(), $listener->getEndLine() ); } } ================================================ FILE: src/Traits/RegistersMonitors.php ================================================ $monitor) { if (is_string($key) && $monitor === false) { continue; } if (is_array($monitor) && !($monitor['enabled'] ?? true)) { continue; } $monitor = $app->make(is_string($key) ? $key : $monitor, [ 'options' => is_array($monitor) ? $monitor : [], ]); $class = get_class($monitor); // exclude server monitors if (in_array($class, $serverMonitors, true)) { unset($monitor); continue; } static::$monitors[] = $class; $monitor->register($app); } } /** * Returns all registered monitors. * * @return array */ protected static function getMonitors(): array { return static::$monitors; } } ================================================ FILE: src/Type.php ================================================ assertFalse(File::exists(public_path('vendor/meter/meter.css'))); Artisan::call('meter:publish'); $this->assertTrue(File::exists(public_path('vendor/meter/meter.css'))); } } ================================================ FILE: tests/MeterModelTest.php ================================================ create(['type' => Type::EVENT, 'No', 'test content']); $this->assertEquals('test content', $model->content); } }