You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
512 lines
14 KiB
512 lines
14 KiB
1 year ago
|
<?php declare(strict_types = 0);
|
||
|
/*
|
||
|
** Zabbix
|
||
|
** Copyright (C) 2001-2023 Zabbix SIA
|
||
|
**
|
||
|
** This program is free software; you can redistribute it and/or modify
|
||
|
** it under the terms of the GNU General Public License as published by
|
||
|
** the Free Software Foundation; either version 2 of the License, or
|
||
|
** (at your option) any later version.
|
||
|
**
|
||
|
** This program is distributed in the hope that it will be useful,
|
||
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
** GNU General Public License for more details.
|
||
|
**
|
||
|
** You should have received a copy of the GNU General Public License
|
||
|
** along with this program; if not, write to the Free Software
|
||
|
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||
|
**/
|
||
|
|
||
|
|
||
|
namespace Widgets\TopHosts\Actions;
|
||
|
|
||
|
use API,
|
||
|
CControllerDashboardWidgetView,
|
||
|
CControllerResponseData,
|
||
|
CHousekeepingHelper,
|
||
|
CMacrosResolverHelper,
|
||
|
CNumberParser,
|
||
|
CParser,
|
||
|
CSettingsHelper,
|
||
|
Manager;
|
||
|
|
||
|
use Widgets\TopHosts\Widget;
|
||
|
|
||
|
use Zabbix\Widgets\Fields\CWidgetFieldColumnsList;
|
||
|
|
||
|
class WidgetView extends CControllerDashboardWidgetView {
|
||
|
|
||
|
protected function init(): void {
|
||
|
parent::init();
|
||
|
|
||
|
$this->addValidationRules([
|
||
|
'dynamic_hostid' => 'db hosts.hostid'
|
||
|
]);
|
||
|
}
|
||
|
|
||
|
protected function doAction(): void {
|
||
|
$data = [
|
||
|
'name' => $this->getInput('name', $this->widget->getDefaultName()),
|
||
|
'user' => [
|
||
|
'debug_mode' => $this->getDebugMode()
|
||
|
]
|
||
|
];
|
||
|
|
||
|
// Editing template dashboard?
|
||
|
if ($this->isTemplateDashboard() && !$this->hasInput('dynamic_hostid')) {
|
||
|
$data['error'] = _('No data.');
|
||
|
}
|
||
|
else {
|
||
|
$data += $this->getData();
|
||
|
$data['error'] = null;
|
||
|
}
|
||
|
|
||
|
$this->setResponse(new CControllerResponseData($data));
|
||
|
}
|
||
|
|
||
|
private function getData(): array {
|
||
|
$configuration = $this->fields_values['columns'];
|
||
|
|
||
|
$groupids = !$this->isTemplateDashboard() && $this->fields_values['groupids']
|
||
|
? getSubGroups($this->fields_values['groupids'])
|
||
|
: null;
|
||
|
|
||
|
if ($this->isTemplateDashboard()) {
|
||
|
$hostids = [$this->getInput('dynamic_hostid')];
|
||
|
}
|
||
|
else {
|
||
|
$hostids = $this->fields_values['hostids'] ?: null;
|
||
|
}
|
||
|
|
||
|
if (array_key_exists('tags', $this->fields_values)) {
|
||
|
$hosts = API::Host()->get([
|
||
|
'output' => ['name'],
|
||
|
'groupids' => $groupids,
|
||
|
'hostids' => $hostids,
|
||
|
'evaltype' => $this->fields_values['evaltype'],
|
||
|
'tags' => $this->fields_values['tags'],
|
||
|
'monitored_hosts' => true,
|
||
|
'preservekeys' => true
|
||
|
]);
|
||
|
|
||
|
$hostids = array_keys($hosts);
|
||
|
}
|
||
|
else {
|
||
|
$hosts = null;
|
||
|
}
|
||
|
|
||
|
$time_now = time();
|
||
|
|
||
|
$master_column = $configuration[$this->fields_values['column']];
|
||
|
$master_items_only_numeric_allowed = self::isNumericOnlyColumn($master_column);
|
||
|
|
||
|
$master_items = self::getItems($master_column['item'], $master_items_only_numeric_allowed, $groupids, $hostids);
|
||
|
$master_item_values = self::getItemValues($master_items, $master_column, $time_now);
|
||
|
|
||
|
if (!$master_item_values) {
|
||
|
return [
|
||
|
'configuration' => $configuration,
|
||
|
'rows' => []
|
||
|
];
|
||
|
}
|
||
|
|
||
|
$master_items_only_numeric_present = $master_items_only_numeric_allowed && !array_filter($master_items,
|
||
|
static function(array $item): bool {
|
||
|
return !in_array($item['value_type'], [ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_UINT64]);
|
||
|
}
|
||
|
);
|
||
|
|
||
|
if ($this->fields_values['order'] == Widget::ORDER_TOP_N) {
|
||
|
if ($master_items_only_numeric_present) {
|
||
|
arsort($master_item_values, SORT_NUMERIC);
|
||
|
|
||
|
$master_items_min = end($master_item_values);
|
||
|
$master_items_max = reset($master_item_values);
|
||
|
}
|
||
|
else {
|
||
|
asort($master_item_values, SORT_NATURAL);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
if ($master_items_only_numeric_present) {
|
||
|
asort($master_item_values, SORT_NUMERIC);
|
||
|
|
||
|
$master_items_min = reset($master_item_values);
|
||
|
$master_items_max = end($master_item_values);
|
||
|
}
|
||
|
else {
|
||
|
arsort($master_item_values, SORT_NATURAL);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$show_lines = $this->isTemplateDashboard() ? 1 : $this->fields_values['show_lines'];
|
||
|
$master_item_values = array_slice($master_item_values, 0, $show_lines, true);
|
||
|
$master_items = array_intersect_key($master_items, $master_item_values);
|
||
|
|
||
|
$master_hostids = [];
|
||
|
|
||
|
foreach (array_keys($master_item_values) as $itemid) {
|
||
|
$master_hostids[$master_items[$itemid]['hostid']] = true;
|
||
|
}
|
||
|
|
||
|
$number_parser = new CNumberParser([
|
||
|
'with_size_suffix' => true,
|
||
|
'with_time_suffix' => true,
|
||
|
'is_binary_size' => false
|
||
|
]);
|
||
|
|
||
|
$number_parser_binary = new CNumberParser([
|
||
|
'with_size_suffix' => true,
|
||
|
'with_time_suffix' => true,
|
||
|
'is_binary_size' => true
|
||
|
]);
|
||
|
|
||
|
$item_values = [];
|
||
|
|
||
|
foreach ($configuration as $column_index => &$column) {
|
||
|
if ($column['data'] != CWidgetFieldColumnsList::DATA_ITEM_VALUE) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
$calc_extremes = $column['display'] == CWidgetFieldColumnsList::DISPLAY_BAR
|
||
|
|| $column['display'] == CWidgetFieldColumnsList::DISPLAY_INDICATORS;
|
||
|
|
||
|
if ($column_index == $this->fields_values['column']) {
|
||
|
$column_items = $master_items;
|
||
|
$column_item_values = $master_item_values;
|
||
|
}
|
||
|
else {
|
||
|
$numeric_only = self::isNumericOnlyColumn($column);
|
||
|
$column_items = !$calc_extremes || ($column['min'] !== '' && $column['max'] !== '')
|
||
|
? self::getItems($column['item'], $numeric_only, $groupids, array_keys($master_hostids))
|
||
|
: self::getItems($column['item'], $numeric_only, $groupids, $hostids);
|
||
|
|
||
|
$column_item_values = self::getItemValues($column_items, $column, $time_now);
|
||
|
}
|
||
|
|
||
|
if ($calc_extremes && ($column['min'] !== '' || $column['max'] !== '')) {
|
||
|
if ($column['min'] !== '') {
|
||
|
$number_parser_binary->parse($column['min']);
|
||
|
$column['min_binary'] = $number_parser_binary->calcValue();
|
||
|
|
||
|
$number_parser->parse($column['min']);
|
||
|
$column['min'] = $number_parser->calcValue();
|
||
|
}
|
||
|
|
||
|
if ($column['max'] !== '') {
|
||
|
$number_parser_binary->parse($column['max']);
|
||
|
$column['max_binary'] = $number_parser_binary->calcValue();
|
||
|
|
||
|
$number_parser->parse($column['max']);
|
||
|
$column['max'] = $number_parser->calcValue();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (array_key_exists('thresholds', $column)) {
|
||
|
foreach ($column['thresholds'] as &$threshold) {
|
||
|
$number_parser_binary->parse($threshold['threshold']);
|
||
|
$threshold['threshold_binary'] = $number_parser_binary->calcValue();
|
||
|
|
||
|
$number_parser->parse($threshold['threshold']);
|
||
|
$threshold['threshold'] = $number_parser->calcValue();
|
||
|
}
|
||
|
unset($threshold);
|
||
|
}
|
||
|
|
||
|
if ($column_index == $this->fields_values['column']) {
|
||
|
if ($calc_extremes) {
|
||
|
if ($column['min'] === '') {
|
||
|
$column['min'] = $master_items_min;
|
||
|
$column['min_binary'] = $column['min'];
|
||
|
}
|
||
|
|
||
|
if ($column['max'] === '') {
|
||
|
$column['max'] = $master_items_max;
|
||
|
$column['max_binary'] = $column['max'];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
if ($calc_extremes && $column_item_values) {
|
||
|
if ($column['min'] === '') {
|
||
|
$column['min'] = min($column_item_values);
|
||
|
$column['min_binary'] = $column['min'];
|
||
|
}
|
||
|
|
||
|
if ($column['max'] === '') {
|
||
|
$column['max'] = max($column_item_values);
|
||
|
$column['max_binary'] = $column['max'];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$item_values[$column_index] = [];
|
||
|
|
||
|
foreach ($column_item_values as $itemid => $column_item_value) {
|
||
|
if (array_key_exists($column_items[$itemid]['hostid'], $master_hostids)) {
|
||
|
$item_values[$column_index][$column_items[$itemid]['hostid']] = [
|
||
|
'value' => $column_item_value,
|
||
|
'item' => $column_items[$itemid],
|
||
|
'is_binary_units' => isBinaryUnits($column_items[$itemid]['units'])
|
||
|
];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
unset($column);
|
||
|
|
||
|
$text_columns = [];
|
||
|
|
||
|
foreach ($configuration as $column_index => $column) {
|
||
|
if ($column['data'] == CWidgetFieldColumnsList::DATA_TEXT) {
|
||
|
$text_columns[$column_index] = $column['text'];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$text_columns = CMacrosResolverHelper::resolveWidgetTopHostsTextColumns($text_columns, $master_items);
|
||
|
|
||
|
$hostid_to_itemid = array_column($master_items, 'itemid', 'hostid');
|
||
|
|
||
|
$rows = [];
|
||
|
|
||
|
foreach (array_keys($master_hostids) as $hostid) {
|
||
|
$row = [];
|
||
|
|
||
|
foreach ($configuration as $column_index => $column) {
|
||
|
switch ($column['data']) {
|
||
|
case CWidgetFieldColumnsList::DATA_HOST_NAME:
|
||
|
if ($hosts === null) {
|
||
|
$hosts = API::Host()->get([
|
||
|
'output' => ['name'],
|
||
|
'groupids' => $groupids,
|
||
|
'hostids' => array_keys($master_hostids),
|
||
|
'monitored_hosts' => true,
|
||
|
'preservekeys' => true
|
||
|
]);
|
||
|
}
|
||
|
|
||
|
$row[] = [
|
||
|
'value' => $hosts[$hostid]['name'],
|
||
|
'hostid' => $hostid
|
||
|
];
|
||
|
|
||
|
break;
|
||
|
|
||
|
case CWidgetFieldColumnsList::DATA_TEXT:
|
||
|
$row[] = [
|
||
|
'value' => $text_columns[$column_index][$hostid_to_itemid[$hostid]]
|
||
|
];
|
||
|
|
||
|
break;
|
||
|
|
||
|
case CWidgetFieldColumnsList::DATA_ITEM_VALUE:
|
||
|
$row[] = array_key_exists($hostid, $item_values[$column_index])
|
||
|
? [
|
||
|
'value' => $item_values[$column_index][$hostid]['value'],
|
||
|
'item' => $item_values[$column_index][$hostid]['item'],
|
||
|
'is_binary_units' => $item_values[$column_index][$hostid]['is_binary_units']
|
||
|
]
|
||
|
: null;
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$rows[] = $row;
|
||
|
}
|
||
|
|
||
|
return [
|
||
|
'configuration' => $configuration,
|
||
|
'rows' => $rows
|
||
|
];
|
||
|
}
|
||
|
|
||
|
private static function isNumericOnlyColumn(array $column): bool {
|
||
|
return $column['aggregate_function'] != AGGREGATE_NONE
|
||
|
|| $column['display'] != CWidgetFieldColumnsList::DISPLAY_AS_IS
|
||
|
|| array_key_exists('thresholds', $column);
|
||
|
}
|
||
|
|
||
|
private static function getItems(string $name, bool $numeric_only, ?array $groupids, ?array $hostids): array {
|
||
|
$items = API::Item()->get([
|
||
|
'output' => ['itemid', 'hostid', 'key_', 'history', 'trends', 'value_type', 'units'],
|
||
|
'selectValueMap' => ['mappings'],
|
||
|
'groupids' => $groupids,
|
||
|
'hostids' => $hostids,
|
||
|
'monitored' => true,
|
||
|
'webitems' => true,
|
||
|
'filter' => [
|
||
|
'name' => $name,
|
||
|
'status' => ITEM_STATUS_ACTIVE,
|
||
|
'value_type' => $numeric_only ? [ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_UINT64] : null
|
||
|
],
|
||
|
'sortfield' => 'key_',
|
||
|
'preservekeys' => true
|
||
|
]);
|
||
|
|
||
|
if ($items) {
|
||
|
$single_key = reset($items)['key_'];
|
||
|
|
||
|
$items = array_filter($items,
|
||
|
static function ($item) use ($single_key): bool {
|
||
|
return $item['key_'] === $single_key;
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
return $items;
|
||
|
}
|
||
|
|
||
|
private static function getItemValues(array $items, array $column, int $time_now): array {
|
||
|
static $history_period;
|
||
|
|
||
|
if ($history_period === null) {
|
||
|
$history_period = timeUnitToSeconds(CSettingsHelper::get(CSettingsHelper::HISTORY_PERIOD));
|
||
|
}
|
||
|
|
||
|
$timeshift = $column['timeshift'] !== '' ? timeUnitToSeconds($column['timeshift']) : 0;
|
||
|
|
||
|
$time_to = $time_now + $timeshift;
|
||
|
|
||
|
$time_from = $column['aggregate_function'] != AGGREGATE_NONE
|
||
|
? $time_to - timeUnitToSeconds($column['aggregate_interval'])
|
||
|
: $time_to - $history_period;
|
||
|
|
||
|
$function = $column['aggregate_function'] != AGGREGATE_NONE
|
||
|
? $column['aggregate_function']
|
||
|
: AGGREGATE_LAST;
|
||
|
|
||
|
$interval = $time_to;
|
||
|
|
||
|
self::addDataSource($items, $time_from, $time_now, $column['history']);
|
||
|
|
||
|
$result = [];
|
||
|
|
||
|
if ($column['aggregate_function'] == AGGREGATE_NONE) {
|
||
|
$items_by_source = ['history' => [], 'trends' => []];
|
||
|
|
||
|
foreach ($items as $itemid => $item) {
|
||
|
$items_by_source[$item['source']][$itemid] = $item;
|
||
|
}
|
||
|
|
||
|
if ($timeshift != 0) {
|
||
|
$values = [];
|
||
|
|
||
|
foreach ($items_by_source['history'] as $itemid => $item) {
|
||
|
$history = Manager::History()->getValueAt($item, $time_to, 0);
|
||
|
|
||
|
if (is_array($history)) {
|
||
|
$values[$itemid] = $history['value'];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
$values = Manager::History()->getLastValues($items_by_source['history'], 1, $history_period);
|
||
|
$values = array_column(array_column($values, 0), 'value', 'itemid');
|
||
|
}
|
||
|
|
||
|
$result += $values;
|
||
|
$items = $items_by_source['trends'];
|
||
|
}
|
||
|
|
||
|
$values = Manager::History()->getAggregationByInterval($items, $time_from, $time_to, $function, $interval);
|
||
|
$values = array_column(array_column(array_column($values, 'data'), 0),
|
||
|
$function == AGGREGATE_COUNT ? 'count' : 'value', 'itemid'
|
||
|
);
|
||
|
|
||
|
$result += $values;
|
||
|
|
||
|
return $result;
|
||
|
}
|
||
|
|
||
|
private static function addDataSource(array &$items, int $time_from, int $time_now, int $data_source): void {
|
||
|
if ($data_source == CWidgetFieldColumnsList::HISTORY_DATA_HISTORY
|
||
|
|| $data_source == CWidgetFieldColumnsList::HISTORY_DATA_TRENDS) {
|
||
|
foreach ($items as &$item) {
|
||
|
$item['source'] = $data_source == CWidgetFieldColumnsList::HISTORY_DATA_TRENDS
|
||
|
&& ($item['value_type'] == ITEM_VALUE_TYPE_FLOAT || $item['value_type'] == ITEM_VALUE_TYPE_UINT64)
|
||
|
? 'trends'
|
||
|
: 'history';
|
||
|
}
|
||
|
unset($item);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
static $hk_history_global, $global_history_time, $hk_trends_global, $global_trends_time;
|
||
|
|
||
|
if ($hk_history_global === null) {
|
||
|
$hk_history_global = CHousekeepingHelper::get(CHousekeepingHelper::HK_HISTORY_GLOBAL);
|
||
|
|
||
|
if ($hk_history_global) {
|
||
|
$global_history_time = timeUnitToSeconds(CHousekeepingHelper::get(CHousekeepingHelper::HK_HISTORY));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ($hk_trends_global === null) {
|
||
|
$hk_trends_global = CHousekeepingHelper::get(CHousekeepingHelper::HK_TRENDS_GLOBAL);
|
||
|
|
||
|
if ($hk_history_global) {
|
||
|
$global_trends_time = timeUnitToSeconds(CHousekeepingHelper::get(CHousekeepingHelper::HK_TRENDS));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ($hk_history_global) {
|
||
|
foreach ($items as &$item) {
|
||
|
$item['history'] = $global_history_time;
|
||
|
}
|
||
|
unset($item);
|
||
|
}
|
||
|
|
||
|
if ($hk_trends_global) {
|
||
|
foreach ($items as &$item) {
|
||
|
$item['trends'] = $global_trends_time;
|
||
|
}
|
||
|
unset($item);
|
||
|
}
|
||
|
|
||
|
if (!$hk_history_global || !$hk_trends_global) {
|
||
|
$items = CMacrosResolverHelper::resolveTimeUnitMacros($items,
|
||
|
array_merge($hk_history_global ? [] : ['history'], $hk_trends_global ? [] : ['trends'])
|
||
|
);
|
||
|
|
||
|
$processed_items = [];
|
||
|
|
||
|
foreach ($items as $itemid => $item) {
|
||
|
if (!$global_trends_time) {
|
||
|
$item['history'] = timeUnitToSeconds($item['history']);
|
||
|
|
||
|
if ($item['history'] === null) {
|
||
|
error(_s('Incorrect value for field "%1$s": %2$s.', 'history',
|
||
|
_('invalid history storage period')
|
||
|
));
|
||
|
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!$hk_trends_global) {
|
||
|
$item['trends'] = timeUnitToSeconds($item['trends']);
|
||
|
|
||
|
if ($item['trends'] === null) {
|
||
|
error(_s('Incorrect value for field "%1$s": %2$s.', 'trends',
|
||
|
_('invalid trend storage period')
|
||
|
));
|
||
|
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$processed_items[$itemid] = $item;
|
||
|
}
|
||
|
|
||
|
$items = $processed_items;
|
||
|
}
|
||
|
|
||
|
foreach ($items as &$item) {
|
||
|
$item['source'] = $item['trends'] == 0 || $time_now - $item['history'] <= $time_from ? 'history' : 'trends';
|
||
|
}
|
||
|
unset($item);
|
||
|
}
|
||
|
}
|