mirror of
https://github.com/OpenXE-org/OpenXE.git
synced 2024-12-27 07:00:29 +01:00
658 lines
20 KiB
PHP
658 lines
20 KiB
PHP
<?php
|
|
|
|
namespace Xentral\Modules\SystemHealth\Service;
|
|
|
|
use Xentral\Components\Database\Database;
|
|
use Xentral\Modules\SystemHealth\Exception\InvalidArgumentException;
|
|
use Xentral\Modules\SystemHealth\Exception\RuntimeException;
|
|
use Xentral\Modules\SystemHealth\Gateway\SystemHealthGateway;
|
|
use Xentral\Modules\SystemNotification\Service\NotificationService;
|
|
|
|
final class SystemHealthService implements SystemHealthServiceInterface
|
|
{
|
|
|
|
/** @var Database $db */
|
|
private $db;
|
|
|
|
/** @var SystemHealthGateway $gateway */
|
|
private $gateway;
|
|
|
|
/** @var NotificationService $notficationService */
|
|
private $notficationService;
|
|
|
|
/**
|
|
* @param Database $database
|
|
* @param SystemHealthGateway $gateway
|
|
* @param NotificationService $notificationService
|
|
*/
|
|
public function __construct(
|
|
Database $database,
|
|
SystemHealthGateway $gateway,
|
|
NotificationService $notificationService
|
|
) {
|
|
$this->db = $database;
|
|
$this->gateway = $gateway;
|
|
$this->notficationService = $notificationService;
|
|
}
|
|
|
|
/**
|
|
* Create a SystemHealth
|
|
*
|
|
* @param int $systemHealthCategoryId
|
|
* @param string $name
|
|
* @param string $description
|
|
* @param string $status
|
|
* @param bool $resetAble
|
|
*
|
|
* @throws InvalidArgumentException|RuntimeException
|
|
*
|
|
* @return int|false Created SystemHealth-ID
|
|
*/
|
|
public function create(
|
|
$systemHealthCategoryId,
|
|
$name,
|
|
$description = '',
|
|
$status = '',
|
|
$resetAble = false
|
|
) {
|
|
if (empty($name)) {
|
|
throw new RuntimeException('name must not empty.');
|
|
}
|
|
|
|
if (!$this->gateway->getCategoryById($systemHealthCategoryId)) {
|
|
throw new RuntimeException('Category not found.');
|
|
}
|
|
|
|
if ($this->db->fetchValue(
|
|
'SELECT `id`
|
|
FROM `systemhealth`
|
|
WHERE `name` = :name AND `systemhealth_category_id` = :systemhealth_category_id',
|
|
[
|
|
'name' => $name,
|
|
'systemhealth_category_id' => $systemHealthCategoryId,
|
|
]
|
|
)) {
|
|
throw new RuntimeException(sprintf('name %s allready exists.', $name));
|
|
}
|
|
|
|
$this->db->perform(
|
|
'INSERT INTO `systemhealth`
|
|
(`id`, `systemhealth_category_id`, `name`, `description`, `status`, `created_at`, `resetable`)
|
|
VALUES (NULL, :systemhealth_category_id, :name, :description, :status, NOW(), :resetable)',
|
|
[
|
|
'systemhealth_category_id' => $systemHealthCategoryId,
|
|
'name' => $name,
|
|
'description' => $description,
|
|
'status' => $status,
|
|
'resetable' => $resetAble,
|
|
]
|
|
);
|
|
$insertId = (int)$this->db->lastInsertId();
|
|
if ($insertId === 0) {
|
|
throw new RuntimeException('Notification message could not be created.');
|
|
}
|
|
|
|
return $insertId;
|
|
}
|
|
|
|
/**
|
|
* @param int $systemHealthId
|
|
* @param string $description
|
|
* @param string $status
|
|
* @param bool $resetAble
|
|
*/
|
|
public function update($systemHealthId, $description, $status, $resetAble)
|
|
{
|
|
$systemHealth = $this->gateway->getById($systemHealthId);
|
|
if (empty($systemHealth)) {
|
|
throw new RuntimeException('SystemHealthentry not found.');
|
|
}
|
|
|
|
if (!empty($status) && $status !== 'ok') {
|
|
$customStatus = (string)$this->gateway->getCustomStatusLvlFromId($systemHealthId);
|
|
if (!empty($customStatus)) {
|
|
$status = $customStatus;
|
|
}
|
|
}
|
|
|
|
$this->db->perform(
|
|
'UPDATE `systemhealth`
|
|
SET `description` = :description, `status` = :status, `resetable` = :resetable
|
|
WHERE `id` = :systemhealth_id',
|
|
[
|
|
'description' => $description,
|
|
'status' => $status,
|
|
'resetable' => $resetAble,
|
|
'systemhealth_id' => $systemHealthId,
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param int $systemHealthId
|
|
* @param int $userId
|
|
*/
|
|
public function deleteSystemHealthItemNotificationSetting($systemHealthId, $userId = 0)
|
|
{
|
|
$systemHealth = $this->gateway->getById($systemHealthId);
|
|
if (empty($systemHealth)) {
|
|
throw new RuntimeException('SystemHealthentry not found.');
|
|
}
|
|
$notificationItem = $this->gateway->getItemNotificationByUserIdSystemHealthId($systemHealthId, $userId);
|
|
if (empty($notificationItem)) {
|
|
return;
|
|
}
|
|
$this->db->perform(
|
|
'DELETE FROM `systemhealth_notification_item` WHERE `id` = :id',
|
|
['id' => $notificationItem['id']]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param int $systemHealthId
|
|
* @param int $userId
|
|
*/
|
|
public function createSystemHealthItemNotificationSetting($systemHealthId, $userId = 0)
|
|
{
|
|
$systemHealth = $this->gateway->getById($systemHealthId);
|
|
if (empty($systemHealth)) {
|
|
throw new RuntimeException('SystemHealthentry not found.');
|
|
}
|
|
$systemHealthStatus = !empty($systemHealth['custom_status']) ?
|
|
$systemHealth['custom_status'] :
|
|
$systemHealth['status'];
|
|
$notificationItem = $this->gateway->getItemNotificationByUserIdSystemHealthId($systemHealthId, $userId);
|
|
if (empty($notificationItem)) {
|
|
$this->db->perform(
|
|
'INSERT INTO `systemhealth_notification_item` (`systemhealth_id`, `user_id`, `status`)
|
|
VALUES (:systemhealth_id, :user_id, :status)',
|
|
[
|
|
'systemhealth_id' => (int)$systemHealthId,
|
|
'user_id' => (int)$userId,
|
|
'status' => $systemHealthStatus,
|
|
]
|
|
);
|
|
|
|
$insertId = (int)$this->db->lastInsertId();
|
|
if ($insertId === 0) {
|
|
throw new RuntimeException('SystemHealthCategory could not be created.');
|
|
}
|
|
|
|
return;
|
|
}
|
|
if ($systemHealthStatus === $notificationItem['status']) {
|
|
return;
|
|
}
|
|
$this->db->perform(
|
|
'UPDATE `systemhealth_notification_item` SET `status` = :status WHERE `id` = :id',
|
|
['status' => $systemHealthStatus, 'id' => $notificationItem['id']]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param int $systemHealthId
|
|
*/
|
|
public function resetStatus($systemHealthId)
|
|
{
|
|
$systemHealth = $this->gateway->getById($systemHealthId);
|
|
if (empty($systemHealth)) {
|
|
throw new RuntimeException('SystemHealthentry not found.');
|
|
}
|
|
if (empty($systemHealth['resetable'])) {
|
|
throw new RuntimeException('SystemHealthentry is not resetable.');
|
|
}
|
|
$this->db->fetchAffected(
|
|
"UPDATE `systemhealth`
|
|
SET `status` = '',
|
|
`message` = '',
|
|
`lastupdate` = NOW(),
|
|
`last_reset` = NOW()
|
|
WHERE `id` = :id LIMIT 1",
|
|
[
|
|
'id' => (int)$systemHealthId,
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param int $systemHealthId
|
|
* @param string $status
|
|
* @param string $message
|
|
*
|
|
* @throws RuntimeException
|
|
* @return bool
|
|
*
|
|
*/
|
|
public function setStatus($systemHealthId, $status, $message = '')
|
|
{
|
|
$systemHealth = $this->gateway->getById($systemHealthId);
|
|
if (empty($systemHealth)) {
|
|
throw new RuntimeException('SystemHealthentry not found.');
|
|
}
|
|
|
|
if (!empty($status) && $status !== 'ok') {
|
|
$customStatus = (string)$this->gateway->getCustomStatusLvlFromId($systemHealthId);
|
|
if (!empty($customStatus)) {
|
|
$status = $customStatus;
|
|
}
|
|
}
|
|
|
|
$numRows = (int)$this->db->fetchAffected(
|
|
'UPDATE `systemhealth`
|
|
SET `status` = :status,
|
|
`message` = :message,
|
|
`lastupdate` = NOW()
|
|
WHERE `id` = :id LIMIT 1',
|
|
[
|
|
'id' => (int)$systemHealthId,
|
|
'status' => $status,
|
|
'message' => $message,
|
|
]
|
|
);
|
|
|
|
$return = $numRows === 1;
|
|
if (!$return) {
|
|
return false;
|
|
}
|
|
|
|
if ($status === $systemHealth['status']) {
|
|
return true;
|
|
}
|
|
|
|
$notifications = $this->gateway->getUserNotificationsByIdAndStatus($systemHealthId, $status);
|
|
|
|
if (empty($notifications)) {
|
|
return true;
|
|
}
|
|
|
|
foreach ($notifications as $notification) {
|
|
$this->notficationService->create(
|
|
$notification['user_id'],
|
|
$status,
|
|
$systemHealth['description'],
|
|
strip_tags($message),
|
|
$status === 'error',
|
|
[],
|
|
['systemhealth', $notification['name']]
|
|
);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Create a SystemHealthCategory
|
|
*
|
|
* @param string $name
|
|
* @param string $description
|
|
*
|
|
* @throws InvalidArgumentException|RuntimeException
|
|
*
|
|
* @return int|false Created SystemHealthCategory-ID
|
|
*/
|
|
public function createCategory($name, $description = '')
|
|
{
|
|
if (empty($name)) {
|
|
throw new RuntimeException('name must not empty.');
|
|
}
|
|
|
|
if ($this->db->fetchValue(
|
|
'SELECT sc.id FROM `systemhealth_category` AS `sc` WHERE sc.`name` = :name',
|
|
['name' => (string)$name]
|
|
)) {
|
|
throw new RuntimeException(sprintf('name %s allready exists.', $name));
|
|
}
|
|
|
|
$this->db->perform(
|
|
'INSERT INTO `systemhealth_category` (`id`, `name`, `description`, `created_at`)
|
|
VALUES (NULL, :name, :description, NOW())',
|
|
[
|
|
'name' => (string)$name,
|
|
'description' => $description,
|
|
]
|
|
);
|
|
$insertId = (int)$this->db->lastInsertId();
|
|
if ($insertId === 0) {
|
|
throw new RuntimeException('SystemHealthCategory could not be created.');
|
|
}
|
|
|
|
return $insertId;
|
|
}
|
|
|
|
/**
|
|
* @param int $systemHealthId
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function delete($systemHealthId)
|
|
{
|
|
$numRows = (int)$this->db->fetchAffected(
|
|
'DELETE FROM `systemhealth` WHERE `id` = :id LIMIT 1',
|
|
['id' => (int)$systemHealthId]
|
|
);
|
|
|
|
return $numRows === 1;
|
|
}
|
|
|
|
/**
|
|
* @param int $SystemHealthCategoryId
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function deleteCategory($SystemHealthCategoryId)
|
|
{
|
|
if ($this->db->fetchValue(
|
|
'SELECT sh.id FROM `systemhealth` AS `sh` WHERE sh.systemhealth_category_id = :id LIMIT 1',
|
|
['id' => (int)$SystemHealthCategoryId]
|
|
)) {
|
|
throw new RuntimeException('Category is not empty.');
|
|
}
|
|
|
|
$numRows = (int)$this->db->fetchAffected(
|
|
'DELETE FROM `systemhealth_category` WHERE `id` = :id LIMIT 1',
|
|
['id' => (int)$SystemHealthCategoryId]
|
|
);
|
|
|
|
return $numRows === 1;
|
|
}
|
|
|
|
/**
|
|
* @param int $systemHealthId
|
|
* @param string $status
|
|
* @param string $doctype
|
|
* @param int $doctypeId
|
|
* @param string $message
|
|
*
|
|
* @return int
|
|
*/
|
|
public function createEvent($systemHealthId, $status, $doctype, $doctypeId, $message = '')
|
|
{
|
|
$systemHealth = $this->gateway->getById($systemHealthId);
|
|
if (empty($systemHealth)) {
|
|
throw new RuntimeException('Item is not empty.');
|
|
}
|
|
|
|
$this->db->perform(
|
|
'INSERT INTO `systemhealth_event`
|
|
(`id`, `systemhealth_id`, `created_at`, `doctype`, `doctype_id`, `status`, `message`)
|
|
VALUES (NULL, :systemhealth_id, NOW(), :doctype, :doctype_id, :status, :message)',
|
|
[
|
|
'systemhealth_id' => (int)$systemHealthId,
|
|
'doctype' => $doctype,
|
|
'doctype_id' => (int)$doctypeId,
|
|
'status' => $status,
|
|
'message' => (string)$message,
|
|
]
|
|
);
|
|
$insertId = (int)$this->db->lastInsertId();
|
|
if ($insertId === 0) {
|
|
throw new RuntimeException('SystemHealthEvent could not be created.');
|
|
}
|
|
|
|
return $insertId;
|
|
}
|
|
|
|
|
|
/**
|
|
* @param string $dir
|
|
*
|
|
* @return false|float
|
|
*/
|
|
public function getDiskFree($dir = '')
|
|
{
|
|
if (!function_exists('disk_free_space')) {
|
|
throw new RuntimeException('function disk_free_space not found');
|
|
}
|
|
|
|
if (empty($dir)) {
|
|
return disk_free_space(dirname(__DIR__, 4));
|
|
}
|
|
|
|
if (!is_dir($dir)) {
|
|
throw new InvalidArgumentException(sprintf('%s is no valid directory', $dir));
|
|
}
|
|
|
|
return disk_free_space($dir);
|
|
}
|
|
|
|
/**
|
|
* @param string $database
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getTableSizes($database)
|
|
{
|
|
if (!is_string($database) || empty($database) || strpos($database, '`')) {
|
|
throw new InvalidArgumentException(sprintf('%s is no valid database', $database));
|
|
}
|
|
|
|
return $this->db->fetchPairs(
|
|
'SELECT table_name AS `Table`, ROUND((data_length + index_length) / 1024) AS `KB`
|
|
FROM information_schema.TABLES
|
|
WHERE table_schema = :database AND NOT ISNULL(data_length)
|
|
ORDER BY data_length + index_length DESC',
|
|
['database' => $database]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param string $database
|
|
*
|
|
* @return int
|
|
*/
|
|
public function getDbSize($database)
|
|
{
|
|
$sizes = $this->getTableSizes($database);
|
|
$ret = 0;
|
|
foreach ($sizes as $size) {
|
|
$ret += (int)$size;
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* @return array
|
|
*/
|
|
public function getSystemLoad()
|
|
{
|
|
if (!function_exists('exec')) {
|
|
throw new RuntimeException('function exec not found');
|
|
}
|
|
@exec('cat /proc/loadavg 2>/dev/null ', $output, $return);
|
|
|
|
if (empty($output[0])) {
|
|
return [null, null, null, null, null];
|
|
}
|
|
$output = reset($output);
|
|
while (strpos($output, ' ') !== false) {
|
|
$output = str_replace(' ', ' ', $output);
|
|
}
|
|
$output = explode(' ', $output);
|
|
$runable = null;
|
|
$threads = null;
|
|
if (!empty($output[3]) && strpos($output[3], '/') !== false) {
|
|
[$runable, $threads] = explode('/', $output[3]);
|
|
}
|
|
|
|
return [
|
|
'act' => $output[0],
|
|
'5min' => isset($output[1]) ? $output[1] : null,
|
|
'15min' => isset($output[2]) ? $output[2] : null,
|
|
'runable' => $runable,
|
|
'threads' => $threads,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array
|
|
*/
|
|
public function getMemoryUsage()
|
|
{
|
|
if (!function_exists('exec')) {
|
|
throw new RuntimeException('function exec not found');
|
|
}
|
|
@exec('free -t -w 2>/dev/null ', $output, $return);
|
|
$ret = [];
|
|
if (empty($output[1])) {
|
|
return [];
|
|
}
|
|
|
|
foreach ($output as $key => $row) {
|
|
if ($key > 0) {
|
|
$pos = strpos($row, ':');
|
|
$name = substr($row, 0, $pos);
|
|
$row = substr($row, $pos + 1);
|
|
while (strpos($row, ' ') !== false) {
|
|
$row = str_replace(' ', ' ', $row);
|
|
}
|
|
$mems = explode(' ', trim($row));
|
|
|
|
if ($key === 1) {
|
|
$type = 'memory';
|
|
} elseif ($key === count($output) - 1) {
|
|
$type = 'sum';
|
|
} elseif (count($output) === 4) {
|
|
$type = 'swap';
|
|
} else {
|
|
continue;
|
|
}
|
|
$ret[$type] = [
|
|
'name' => $name,
|
|
'sum' => $mems[0],
|
|
'used' => empty($mems[1]) ? 0 : $mems[1],
|
|
'free' => empty($mems[2]) ? 0 : $mems[2],
|
|
];
|
|
}
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* @param string $dir
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getPartions($dir = '')
|
|
{
|
|
if (!function_exists('exec')) {
|
|
throw new RuntimeException('function exec not found');
|
|
}
|
|
$command = 'df -B1024 -text4 -T --total 2>/dev/null';
|
|
@exec($command, $out, $return);
|
|
$ret = [];
|
|
$total = [];
|
|
$matches = [];
|
|
if (!empty($out) && count($out) > 1) {
|
|
$totalKey = count($out) - 1;
|
|
unset($out[0]);
|
|
foreach ($out as $key => $line) {
|
|
$line = str_replace(["\r", "\t", ' '], ' ', $line);
|
|
while (strpos($line, ' ') !== false) {
|
|
$line = str_replace(' ', ' ', $line);
|
|
}
|
|
$cols = explode(' ', $line);
|
|
if (count($cols) < 7) {
|
|
continue;
|
|
}
|
|
$mountPoint = $cols[6];
|
|
if ($key === $totalKey) {
|
|
$total = ['total' => $cols[2], 'used' => $cols[3], 'free' => $cols[4]];
|
|
}
|
|
|
|
if ($dir !== '' && strpos($dir, $mountPoint) !== 0) {
|
|
unset($out[$key]);
|
|
continue;
|
|
}
|
|
|
|
$ret[] = ['total' => $cols[2], 'used' => $cols[3], 'free' => $cols[4], 'mount' => $cols[6]];
|
|
$matches[] = strlen($cols[6]);
|
|
}
|
|
}
|
|
|
|
if (empty($ret)) {
|
|
return $total;
|
|
}
|
|
if (count($ret) === 1) {
|
|
return reset($ret);
|
|
}
|
|
|
|
array_multisort($matches, SORT_DESC, $ret);
|
|
|
|
return reset($ret);
|
|
}
|
|
|
|
/**
|
|
* @param string $dir
|
|
* @param string[] $excludeDirs
|
|
*
|
|
* @return int
|
|
*/
|
|
public function getUsedSpace($dir, $excludeDirs = [])
|
|
{
|
|
if (empty($dir) || !is_dir($dir)) {
|
|
throw new InvalidArgumentException(sprintf('%s is no valid directory', $dir));
|
|
}
|
|
|
|
if (!function_exists('exec')) {
|
|
throw new RuntimeException('function exec not found');
|
|
}
|
|
|
|
if (!empty($excludeDirs)) {
|
|
foreach ($excludeDirs as $key => $excludeDir) {
|
|
$excludeDir = trim($excludeDir);
|
|
if (strpos($excludeDir, '..') !== false
|
|
|| strpos($excludeDir, "\n") !== false
|
|
|| strpos($excludeDir, "\r") !== false
|
|
|| strpos($excludeDir, "\t") !== false
|
|
|| strpos($excludeDir, ' ') !== false
|
|
) {
|
|
unset($excludeDirs[$key]);
|
|
} else {
|
|
$excludeDirs[$key] = $excludeDir;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (empty($excludeDirs)) {
|
|
$command = sprintf(
|
|
'cd %s && du -d0 --block-size=1024 2>/dev/null',
|
|
$dir
|
|
);
|
|
} else {
|
|
$command = sprintf(
|
|
'cd %s && du -d0 --block-size=1024 --exclude=%s 2>/dev/null',
|
|
$dir,
|
|
implode(' --exclude=', $excludeDirs)
|
|
);
|
|
}
|
|
|
|
|
|
$userdata = @exec($command, $out, $return);
|
|
if (empty($userdata)) {
|
|
throw new RuntimeException('failed to get Space');
|
|
}
|
|
|
|
$pos = strpos($userdata, '.');
|
|
if ($pos === false) {
|
|
$pos = strpos($userdata, 'total');
|
|
}
|
|
if ($pos === false) {
|
|
$pos = strpos($userdata, 'insgesamt');
|
|
}
|
|
if ($pos === false && !empty($out)) {
|
|
$userdata = trim(reset($out));
|
|
if (is_numeric($userdata)) {
|
|
$pos = strlen($userdata);
|
|
}
|
|
}
|
|
|
|
if ((int)$pos <= 0) {
|
|
throw new RuntimeException('failed to get Space');
|
|
}
|
|
|
|
return (int)trim(substr($userdata, 0, $pos));
|
|
}
|
|
}
|