feat : add DatabaseService for PostgreSQL metrics
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
124
src/Service/DatabaseService.php
Normal file
124
src/Service/DatabaseService.php
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Service;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Connection;
|
||||||
|
|
||||||
|
final class DatabaseService
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly Connection $connection,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{connected: bool, name: string, size: string, tableCount: int, activeConnections: int, cacheHitRatio: float, largestTable: string}
|
||||||
|
*/
|
||||||
|
public function getDatabaseInfo(string $databaseName): array
|
||||||
|
{
|
||||||
|
$fallback = [
|
||||||
|
'connected' => false,
|
||||||
|
'name' => $databaseName,
|
||||||
|
'size' => '',
|
||||||
|
'tableCount' => 0,
|
||||||
|
'activeConnections' => 0,
|
||||||
|
'cacheHitRatio' => 0.0,
|
||||||
|
'largestTable' => '',
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check database exists
|
||||||
|
$exists = $this->connection->fetchOne(
|
||||||
|
'SELECT 1 FROM pg_database WHERE datname = :dbname',
|
||||||
|
['dbname' => $databaseName]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!$exists) {
|
||||||
|
return $fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Database size
|
||||||
|
$sizeBytes = (int) $this->connection->fetchOne(
|
||||||
|
'SELECT pg_database_size(:dbname)',
|
||||||
|
['dbname' => $databaseName]
|
||||||
|
);
|
||||||
|
$size = $this->formatBytes($sizeBytes);
|
||||||
|
|
||||||
|
// Table count
|
||||||
|
$tableCount = (int) $this->connection->fetchOne(
|
||||||
|
"SELECT count(*) FROM information_schema.tables WHERE table_schema = 'public' AND table_catalog = :dbname",
|
||||||
|
['dbname' => $databaseName]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Active connections
|
||||||
|
$activeConnections = (int) $this->connection->fetchOne(
|
||||||
|
'SELECT count(*) FROM pg_stat_activity WHERE datname = :dbname',
|
||||||
|
['dbname' => $databaseName]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Cache hit ratio
|
||||||
|
$cacheHitRatio = (float) ($this->connection->fetchOne(
|
||||||
|
'SELECT round(100.0 * sum(blks_hit) / nullif(sum(blks_hit + blks_read), 0), 2) FROM pg_stat_database WHERE datname = :dbname',
|
||||||
|
['dbname' => $databaseName]
|
||||||
|
) ?? 0);
|
||||||
|
|
||||||
|
// Largest table — requires querying the target database catalog
|
||||||
|
$largestTable = $this->fetchLargestTable($databaseName);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'connected' => true,
|
||||||
|
'name' => $databaseName,
|
||||||
|
'size' => $size,
|
||||||
|
'tableCount' => $tableCount,
|
||||||
|
'activeConnections' => $activeConnections,
|
||||||
|
'cacheHitRatio' => $cacheHitRatio,
|
||||||
|
'largestTable' => $largestTable,
|
||||||
|
];
|
||||||
|
} catch (\Throwable) {
|
||||||
|
return $fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function fetchLargestTable(string $databaseName): string
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$row = $this->connection->fetchAssociative(
|
||||||
|
"SELECT relname, pg_total_relation_size(c.oid) as total_size
|
||||||
|
FROM pg_catalog.pg_class c
|
||||||
|
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
||||||
|
WHERE n.nspname = 'public' AND c.relkind = 'r'
|
||||||
|
AND c.relowner = (SELECT oid FROM pg_roles WHERE rolname = current_user)
|
||||||
|
ORDER BY pg_total_relation_size(c.oid) DESC
|
||||||
|
LIMIT 1"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!$row) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $row['relname'] . ' (' . $this->formatBytes((int) $row['total_size']) . ')';
|
||||||
|
} catch (\Throwable) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatBytes(int $bytes): string
|
||||||
|
{
|
||||||
|
if ($bytes < 1024) {
|
||||||
|
return $bytes . ' B';
|
||||||
|
}
|
||||||
|
|
||||||
|
$units = ['KB', 'MB', 'GB', 'TB'];
|
||||||
|
$value = (float) $bytes;
|
||||||
|
|
||||||
|
foreach ($units as $unit) {
|
||||||
|
$value /= 1024;
|
||||||
|
if ($value < 1024) {
|
||||||
|
return round($value, 1) . ' ' . $unit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return round($value, 1) . ' TB';
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user