OpenXE/classes/Modules/SystemHealth/Service/SystemHealthService.php
2021-05-21 08:49:41 +02:00

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));
}
}