mirror of
https://github.com/OPSnet/Gazelle.git
synced 2026-01-16 18:04:34 -05:00
217 lines
7.5 KiB
PHP
217 lines
7.5 KiB
PHP
<?php
|
|
|
|
namespace Gazelle;
|
|
|
|
use Gazelle\Util\Time;
|
|
|
|
class SiteInfo extends Base {
|
|
public function gitBranch(): string {
|
|
return trim((string)shell_exec(BIN_GIT . ' rev-parse --abbrev-ref HEAD'));
|
|
}
|
|
|
|
public function gitHash(): string {
|
|
return trim((string)shell_exec(BIN_GIT . ' rev-parse HEAD'));
|
|
}
|
|
|
|
public function gitHashRemote(): string {
|
|
return trim((string)shell_exec(BIN_GIT . ' rev-parse origin/' . $this->gitBranch()));
|
|
}
|
|
|
|
public function phpinfo(): string {
|
|
ob_start();
|
|
phpinfo();
|
|
$p = (string)ob_get_contents();
|
|
ob_end_clean();
|
|
return substr($p, (int)strpos($p, '<body>') + 6, (int)strpos($p, '</body>'));
|
|
}
|
|
|
|
public function uptime(): array {
|
|
$in = fopen('/proc/uptime', 'r');
|
|
if ($in === false) {
|
|
[$uptime, $idletime] = [0, 0];
|
|
} else {
|
|
[$uptime, $idletime] = array_map('floatval', explode(' ', trim((string)fgets($in))));
|
|
fclose($in);
|
|
}
|
|
$ncpu = 0;
|
|
$in = fopen('/proc/cpuinfo', 'r');
|
|
if ($in !== false) {
|
|
while (($line = fgets($in)) !== false) {
|
|
if (preg_match('/^processor\s+:\s+\d+/', $line)) {
|
|
++$ncpu;
|
|
}
|
|
}
|
|
fclose($in);
|
|
}
|
|
$now = time();
|
|
return [
|
|
'uptime' => Time::diff($now - (int)$uptime),
|
|
'idletime' => Time::diff($now - (int)($idletime / $ncpu), 2, true, false, true),
|
|
];
|
|
}
|
|
|
|
public function phinx(): array {
|
|
return [
|
|
'version' => explode(' ', (string)shell_exec(BIN_PHINX . ' --version'))[5],
|
|
'migration' => array_filter(
|
|
json_decode(
|
|
(string)shell_exec(BIN_PHINX . ' status -c ' . PHINX_MYSQL . ' --format=json|tail -n 1'),
|
|
true
|
|
)['migrations'],
|
|
fn($v) => count($v) > 0
|
|
)
|
|
];
|
|
}
|
|
|
|
public function composerVersion(): string {
|
|
return trim((string)shell_exec(BIN_COMPOSER . ' --version 2>/dev/null'));
|
|
}
|
|
|
|
public function composerPackages(): array {
|
|
$packages = [];
|
|
$root = realpath(__DIR__ . '/../');
|
|
|
|
$composer = json_decode((string)file_get_contents((string)realpath("$root/composer.json")), true);
|
|
foreach ($composer['require'] as $name => $version) {
|
|
if ($name != 'php') {
|
|
$packages[$name] = [
|
|
'name' => $name,
|
|
'require' => $version,
|
|
'installed' => null,
|
|
];
|
|
}
|
|
}
|
|
|
|
$lock = json_decode((string)file_get_contents((string)realpath("$root/composer.lock")), true);
|
|
foreach ($lock['packages'] as $p) {
|
|
if (isset($packages[$p['name']])) {
|
|
if ($p['name'] == $packages[$p['name']]['name']) {
|
|
$packages[$p['name']]['installed'] = $p['version'];
|
|
}
|
|
}
|
|
}
|
|
|
|
$info = json_decode((string)shell_exec(BIN_COMPOSER . " info -d $root --format=json 2>/dev/null"), true);
|
|
foreach ($info['installed'] as $p) {
|
|
if (!isset($packages[$p['name']])) {
|
|
$packages[$p['name']] = [
|
|
'name' => $p['name'],
|
|
'installed' => $p['version'],
|
|
'require' => '-',
|
|
];
|
|
}
|
|
}
|
|
ksort($packages);
|
|
return $packages;
|
|
}
|
|
|
|
public function tableExists(string $tableName): bool {
|
|
return (bool)self::$db->scalar("
|
|
SELECT 1
|
|
FROM information_schema.tables t
|
|
WHERE t.table_schema = ?
|
|
AND t.table_name = ?
|
|
", MYSQL_DB, $tableName
|
|
);
|
|
}
|
|
|
|
public function tablesWithoutPK(): array {
|
|
self::$db->prepared_query("
|
|
SELECT table_name
|
|
FROM information_schema.tables
|
|
WHERE table_name NOT IN (
|
|
SELECT DISTINCT TABLE_NAME
|
|
FROM information_schema.statistics
|
|
WHERE INDEX_NAME = 'PRIMARY' AND TABLE_SCHEMA = ?
|
|
) AND TABLE_SCHEMA = ?
|
|
ORDER BY TABLE_NAME
|
|
", MYSQL_DB, MYSQL_DB
|
|
);
|
|
return self::$db->collect(0);
|
|
}
|
|
|
|
/**
|
|
* List of tables that have multiple foreign keys on the same referenced table.
|
|
* This can occur due to the use of online schema changers.
|
|
* Dropping a redundant key is as simple as
|
|
* ALTER TABLE table_name DROP CONSTRAINT constraint_name
|
|
*/
|
|
public function tablesWithDuplicateForeignKeys(): array {
|
|
self::$db->prepared_query("
|
|
SELECT kcu.table_name,
|
|
kcu.column_name,
|
|
kcu.referenced_table_name,
|
|
kcu.referenced_column_name,
|
|
kcu.constraint_name
|
|
FROM information_schema.key_column_usage kcu
|
|
INNER JOIN (
|
|
SELECT table_name,
|
|
column_name,
|
|
referenced_table_name,
|
|
referenced_column_name
|
|
FROM information_schema.key_column_usage
|
|
WHERE referenced_table_schema = ?
|
|
GROUP BY table_name, column_name, referenced_table_name, referenced_column_name
|
|
HAVING count(*) > 1
|
|
) DUP USING (table_name, column_name, referenced_table_name, referenced_column_name)
|
|
WHERE kcu.referenced_table_schema = ?
|
|
ORDER BY 1, 2, 3, 4, 5;
|
|
", MYSQL_DB, MYSQL_DB
|
|
);
|
|
return self::$db->to_array(false, MYSQLI_ASSOC);
|
|
}
|
|
|
|
public function tableRowsRead(string $tableName): array {
|
|
self::$db->prepared_query("
|
|
SELECT ROWS_READ, ROWS_CHANGED, ROWS_CHANGED_X_INDEXES
|
|
FROM information_schema.table_statistics
|
|
WHERE TABLE_SCHEMA = ?
|
|
AND TABLE_NAME = ?
|
|
", MYSQL_DB, $tableName
|
|
);
|
|
return self::$db->to_array(false, MYSQLI_ASSOC);
|
|
}
|
|
|
|
public function indexRowsRead(string $tableName): array {
|
|
self::$db->prepared_query("
|
|
SELECT s.INDEX_NAME AS index_name,
|
|
coalesce(si.ROWS_READ, 0) AS rows_read,
|
|
group_concat(
|
|
concat(s.column_name, ' {', s.cardinality, '}')
|
|
ORDER BY s.seq_in_index
|
|
SEPARATOR ', '
|
|
) AS column_list
|
|
FROM information_schema.statistics s
|
|
LEFT JOIN information_schema.index_statistics si USING (TABLE_SCHEMA, TABLE_NAME, INDEX_NAME)
|
|
WHERE s.TABLE_SCHEMA = ?
|
|
AND s.TABLE_NAME = ?
|
|
GROUP BY index_name,
|
|
rows_read
|
|
ORDER BY s.TABLE_NAME,
|
|
s.INDEX_NAME = 'PRIMARY' DESC,
|
|
coalesce(si.ROWS_READ, 0) DESC,
|
|
s.INDEX_NAME
|
|
", MYSQL_DB, $tableName
|
|
);
|
|
return self::$db->to_array(false, MYSQLI_ASSOC);
|
|
}
|
|
|
|
public function tableStats(string $tableName): array {
|
|
return self::$db->rowAssoc("
|
|
SELECT t.TABLE_ROWS,
|
|
t.AVG_ROW_LENGTH,
|
|
t.DATA_LENGTH,
|
|
t.INDEX_LENGTH,
|
|
t.DATA_FREE,
|
|
ts.ROWS_READ,
|
|
ts.ROWS_CHANGED,
|
|
ts.ROWS_CHANGED_X_INDEXES
|
|
FROM information_schema.tables t
|
|
INNER JOIN information_schema.table_statistics ts USING (TABLE_SCHEMA, TABLE_NAME)
|
|
WHERE t.TABLE_SCHEMA = ?
|
|
AND t.TABLE_NAME = ?
|
|
", MYSQL_DB, $tableName
|
|
);
|
|
}
|
|
}
|