forked from laravel/framework
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
) * wip * wip * wip * wip * wip * wip * Create `AbstractDatabaseCommand` * wip * Change return type of connection count methods * Add `db:monitor` * Move dbal check * Formatting * Opt-in to expensive operations * Apply fixes from StyleCI * Ask for table * Rename variable * Change how getTableSize works * Make `--max` optional in `db:monitor` * wip * Standardise headings * make `db:monitor` use an argument for databases * Use option again * Move composer to abstract * Add composer * Apply fixes from StyleCI * Update src/Illuminate/Database/Console/MonitorCommand.php Co-authored-by: bastien-phi <bastien.philippe@soyhuce.fr> * formatting * Apply fixes from StyleCI Co-authored-by: Joe Dixon <hello@joedixon.co.uk> Co-authored-by: StyleCI Bot <bot@styleci.io> Co-authored-by: Dries Vints <dries@vints.io> Co-authored-by: bastien-phi <bastien.philippe@soyhuce.fr> Co-authored-by: Taylor Otwell <taylor@laravel.com>
- Loading branch information
1 parent
4fcb8f8
commit 3465d1e
Showing
7 changed files
with
851 additions
and
71 deletions.
There are no files selected for viewing
196 changes: 196 additions & 0 deletions
196
src/Illuminate/Database/Console/DatabaseInspectionCommand.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
<?php | ||
|
||
namespace Illuminate\Database\Console; | ||
|
||
use Doctrine\DBAL\Platforms\AbstractPlatform; | ||
use Illuminate\Console\Command; | ||
use Illuminate\Database\ConnectionInterface; | ||
use Illuminate\Database\MySqlConnection; | ||
use Illuminate\Database\PostgresConnection; | ||
use Illuminate\Database\SQLiteConnection; | ||
use Illuminate\Support\Arr; | ||
use Illuminate\Support\Composer; | ||
use Symfony\Component\Process\Exception\ProcessSignaledException; | ||
use Symfony\Component\Process\Exception\RuntimeException; | ||
use Symfony\Component\Process\Process; | ||
|
||
abstract class DatabaseInspectionCommand extends Command | ||
{ | ||
/** | ||
* The Composer instance. | ||
* | ||
* @var \Illuminate\Support\Composer | ||
*/ | ||
protected $composer; | ||
|
||
/** | ||
* Create a new command instance. | ||
* | ||
* @param \Illuminate\Support\Composer|null $composer | ||
* @return void | ||
*/ | ||
public function __construct(Composer $composer = null) | ||
{ | ||
parent::__construct(); | ||
|
||
$this->composer = $composer ?? $this->laravel->make(Composer::class); | ||
} | ||
|
||
/** | ||
* Get a human-readable platform name for the given platform. | ||
* | ||
* @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform | ||
* @param string $database | ||
* @return string | ||
*/ | ||
protected function getPlatformName(AbstractPlatform $platform, $database) | ||
{ | ||
return match (class_basename($platform)) { | ||
'MySQLPlatform' => 'MySQL <= 5', | ||
'MySQL57Platform' => 'MySQL 5.7', | ||
'MySQL80Platform' => 'MySQL 8', | ||
'PostgreSQL100Platform', 'PostgreSQLPlatform' => 'Postgres', | ||
'SqlitePlatform' => 'SQLite', | ||
'SQLServerPlatform' => 'SQL Server', | ||
'SQLServer2012Platform' => 'SQL Server 2012', | ||
default => $database, | ||
}; | ||
} | ||
|
||
/** | ||
* Get the size of a table in bytes. | ||
* | ||
* @param \Illuminate\Database\ConnectionInterface $connection | ||
* @param string $table | ||
* @return int|null | ||
*/ | ||
protected function getTableSize(ConnectionInterface $connection, string $table) | ||
{ | ||
return match (true) { | ||
$connection instanceof MySqlConnection => $this->getMySQLTableSize($connection, $table), | ||
$connection instanceof PostgresConnection => $this->getPostgresTableSize($connection, $table), | ||
$connection instanceof SQLiteConnection => $this->getSqliteTableSize($connection, $table), | ||
default => null, | ||
}; | ||
} | ||
|
||
/** | ||
* Get the size of a MySQL table in bytes. | ||
* | ||
* @param \Illuminate\Database\ConnectionInterface $connection | ||
* @param string $table | ||
* @return mixed | ||
*/ | ||
protected function getMySQLTableSize(ConnectionInterface $connection, string $table) | ||
{ | ||
return $connection->selectOne('SELECT (data_length + index_length) AS size FROM information_schema.TABLES WHERE table_schema = ? AND table_name = ?', [ | ||
$connection->getDatabaseName(), | ||
$table, | ||
])->size; | ||
} | ||
|
||
/** | ||
* Get the size of a Postgres table in bytes. | ||
* | ||
* @param \Illuminate\Database\ConnectionInterface $connection | ||
* @param string $table | ||
* @return mixed | ||
*/ | ||
protected function getPostgresTableSize(ConnectionInterface $connection, string $table) | ||
{ | ||
return $connection->selectOne('SELECT pg_total_relation_size(?) AS size;', [ | ||
$table, | ||
])->size; | ||
} | ||
|
||
/** | ||
* Get the size of a SQLite table in bytes. | ||
* | ||
* @param \Illuminate\Database\ConnectionInterface $connection | ||
* @param string $table | ||
* @return mixed | ||
*/ | ||
protected function getSqliteTableSize(ConnectionInterface $connection, string $table) | ||
{ | ||
return $connection->selectOne('SELECT SUM(pgsize) FROM dbstat WHERE name=?', [ | ||
$table, | ||
])->size; | ||
} | ||
|
||
/** | ||
* Get the number of open connections for a database. | ||
* | ||
* @param \Illuminate\Database\ConnectionInterface $connection | ||
* @return null | ||
*/ | ||
protected function getConnectionCount(ConnectionInterface $connection) | ||
{ | ||
return match (class_basename($connection)) { | ||
'MySqlConnection' => (int) $connection->selectOne($connection->raw('show status where variable_name = "threads_connected"'))->Value, | ||
'PostgresConnection' => (int) $connection->selectOne('select count(*) as connections from pg_stat_activity')->connections, | ||
'SqlServerConnection' => (int) $connection->selectOne('SELECT COUNT(*) connections FROM sys.dm_exec_sessions WHERE status = ?', ['running'])->connections, | ||
default => null, | ||
}; | ||
} | ||
|
||
/** | ||
* Get the connection configuration details for the given connection. | ||
* | ||
* @param string $database | ||
* @return array | ||
*/ | ||
protected function getConfigFromDatabase($database) | ||
{ | ||
$database ??= config('database.default'); | ||
|
||
return Arr::except(config('database.connections.'.$database), ['password']); | ||
} | ||
|
||
/** | ||
* Ensure the dependencies for the database commands are available. | ||
* | ||
* @return int|null | ||
*/ | ||
protected function ensureDependenciesExist() | ||
{ | ||
if (! interface_exists('Doctrine\DBAL\Driver')) { | ||
if (! $this->components->confirm('Displaying model information requires the Doctrine DBAL (doctrine/dbal) package. Would you like to install it?')) { | ||
return 1; | ||
} | ||
|
||
return $this->installDependencies(); | ||
} | ||
} | ||
|
||
/** | ||
* Install the command's dependencies. | ||
* | ||
* @return void | ||
* | ||
* @throws \Symfony\Component\Process\Exception\ProcessSignaledException | ||
*/ | ||
protected function installDependencies() | ||
{ | ||
$command = collect($this->composer->findComposer()) | ||
->push('require doctrine/dbal') | ||
->implode(' '); | ||
|
||
$process = Process::fromShellCommandline($command, null, null, null, null); | ||
|
||
if ('\\' !== DIRECTORY_SEPARATOR && file_exists('/dev/tty') && is_readable('/dev/tty')) { | ||
try { | ||
$process->setTty(true); | ||
} catch (RuntimeException $e) { | ||
$this->components->warn($e->getMessage()); | ||
} | ||
} | ||
|
||
try { | ||
$process->run(fn ($type, $line) => $this->output->write($line)); | ||
} catch (ProcessSignaledException $e) { | ||
if (extension_loaded('pcntl') && $e->getSignal() !== SIGINT) { | ||
throw $e; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
<?php | ||
|
||
namespace Illuminate\Database\Console; | ||
|
||
use Illuminate\Contracts\Events\Dispatcher; | ||
use Illuminate\Database\ConnectionResolverInterface; | ||
use Illuminate\Database\Events\DatabaseBusy; | ||
use Illuminate\Support\Composer; | ||
use Symfony\Component\Console\Attribute\AsCommand; | ||
|
||
#[AsCommand(name: 'db:monitor')] | ||
class MonitorCommand extends DatabaseInspectionCommand | ||
{ | ||
/** | ||
* The name and signature of the console command. | ||
* | ||
* @var string | ||
*/ | ||
protected $signature = 'db:monitor | ||
{--databases= : The database connections to monitor} | ||
{--max= : The maximum number of connections that can be open before an event is dispatched}'; | ||
|
||
/** | ||
* The name of the console command. | ||
* | ||
* This name is used to identify the command during lazy loading. | ||
* | ||
* @var string|null | ||
* | ||
* @deprecated | ||
*/ | ||
protected static $defaultName = 'db:monitor'; | ||
|
||
/** | ||
* The console command description. | ||
* | ||
* @var string | ||
*/ | ||
protected $description = 'Monitor the number of connections on the specified database'; | ||
|
||
/** | ||
* The connection resolver instance. | ||
* | ||
* @var \Illuminate\Database\ConnectionResolverInterface | ||
*/ | ||
protected $connection; | ||
|
||
/** | ||
* The events dispatcher instance. | ||
* | ||
* @var \Illuminate\Contracts\Events\Dispatcher | ||
*/ | ||
protected $events; | ||
|
||
/** | ||
* Create a new command instance. | ||
* | ||
* @param \Illuminate\Database\ConnectionResolverInterface $connection | ||
* @param \Illuminate\Contracts\Events\Dispatcher $events | ||
* @param \Illuminate\Support\Composer $composer | ||
*/ | ||
public function __construct(ConnectionResolverInterface $connection, Dispatcher $events, Composer $composer) | ||
{ | ||
parent::__construct($composer); | ||
|
||
$this->connection = $connection; | ||
$this->events = $events; | ||
} | ||
|
||
/** | ||
* Execute the console command. | ||
* | ||
* @return void | ||
*/ | ||
public function handle() | ||
{ | ||
$databases = $this->parseDatabases($this->option('databases')); | ||
|
||
$this->displayConnections($databases); | ||
|
||
if ($this->option('max')) { | ||
$this->dispatchEvents($databases); | ||
} | ||
} | ||
|
||
/** | ||
* Parse the database into an array of the connections. | ||
* | ||
* @param string $databases | ||
* @return \Illuminate\Support\Collection | ||
*/ | ||
protected function parseDatabases($databases) | ||
{ | ||
return collect(explode(',', $databases))->map(function ($database) { | ||
if (! $database) { | ||
$database = $this->laravel['config']['database.default']; | ||
} | ||
|
||
$maxConnections = $this->option('max'); | ||
|
||
return [ | ||
'database' => $database, | ||
'connections' => $connections = $this->getConnectionCount($this->connection->connection($database)), | ||
'status' => $maxConnections && $connections >= $maxConnections ? '<fg=yellow;options=bold>ALERT</>' : '<fg=green;options=bold>OK</>', | ||
]; | ||
}); | ||
} | ||
|
||
/** | ||
* Display the databases and their connection counts in the console. | ||
* | ||
* @param \Illuminate\Support\Collection $databases | ||
* @return void | ||
*/ | ||
protected function displayConnections($databases) | ||
{ | ||
$this->newLine(); | ||
|
||
$this->components->twoColumnDetail('<fg=gray>Database name</>', '<fg=gray>Connections</>'); | ||
|
||
$databases->each(function ($database) { | ||
$status = '['.$database['connections'].'] '.$database['status']; | ||
|
||
$this->components->twoColumnDetail($database['database'], $status); | ||
}); | ||
|
||
$this->newLine(); | ||
} | ||
|
||
/** | ||
* Dispatch the database monitoring events. | ||
* | ||
* @param \Illuminate\Support\Collection $databases | ||
* @return void | ||
*/ | ||
protected function dispatchEvents($databases) | ||
{ | ||
$databases->each(function ($database) { | ||
if ($database['status'] === '<fg=green;options=bold>OK</>') { | ||
return; | ||
} | ||
|
||
$this->events->dispatch( | ||
new DatabaseBusy( | ||
$database['database'], | ||
$database['connections'] | ||
) | ||
); | ||
}); | ||
} | ||
} |
Oops, something went wrong.