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.

534 lines
16 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.
**/
/**
* Common methods for "charts.view" and "charts.view.json" actions.
*/
abstract class CControllerCharts extends CController {
// Number of subfilter values per row.
const SUBFILTERS_VALUES_PER_ROW = 100;
// Number of tag value rows allowed to be included in subfilter.
const SUBFILTERS_TAG_VALUE_ROWS = 20;
/**
* Fetches all host graphs based on hostid and graph name or item name used in graph.
*
* @param array $hostids Limit returned graphs to these hosts.
* @param string $name Graphs or items in them should contain this string in their name.
*
* @return array
*/
protected function getHostGraphs(array $hostids, string $name): array {
$graphs = API::Graph()->get([
'output' => ['graphid', 'name'],
'hostids' => $hostids,
'search' => $name !== '' ? ['name' => $name] : null,
'selectItems' => ['itemid'],
'preservekeys' => true
]);
$graph_items = [];
foreach ($graphs as $graph) {
foreach ($graph['items'] as $item) {
$graph_items[$item['itemid']] = $item;
}
}
$filter_items = [];
if ($name !== '') {
$filter_items = API::Item()->get([
'output' => ['itemid'],
'hostids' => $hostids,
'search' => ['name' => $name],
'preservekeys' => true
]);
if ($filter_items) {
$graphs += API::Graph()->get([
'output' => ['graphid', 'name'],
'hostids' => $hostids,
'itemids' => array_keys($filter_items),
'selectItems' => ['itemid'],
'preservekeys' => true
]);
}
}
$items = [];
if ($graph_items || $filter_items) {
$items = API::Item()->get([
'output' => ['itemid', 'name'],
'hostids' => $hostids,
'itemids' => array_keys($graph_items + $filter_items),
'selectTags' => ['tag', 'value'],
'preservekeys' => true
]);
}
return $this->addTagsToGraphs($graphs, $items);
}
private function addTagsToGraphs(array $graphs, array $items): array {
foreach ($graphs as &$graph) {
$graph['tags'] = [];
$tags = [];
foreach ($graph['items'] as $item) {
if (!array_key_exists($item['itemid'], $items)) {
continue;
}
foreach ($items[$item['itemid']]['tags'] as $tag) {
$tags[$tag['tag']][$tag['value']] = true;
}
}
foreach ($tags as $tag_name => $tag) {
foreach (array_keys($tag) as $tag_value) {
$graph['tags'][] = [
'tag' => $tag_name,
'value' => $tag_value
];
}
}
}
unset($graph);
return $graphs;
}
/**
* Fetches all host graphs based on hostid and graph name or item name used in graph.
*
* @param array $hostids Limit returned graphs to these hosts.
* @param string $name Graphs or items in them should contain this string in their name.
*
* @return array
*/
protected function getSimpleGraphs(array $hostids, string $name): array {
return API::Item()->get([
'output' => ['itemid', 'name'],
'selectTags' => ['tag', 'value'],
// TODO VM: filter by tags
'hostids' => $hostids,
'search' => $name !== '' ? ['name' => $name] : null,
'filter' => ['value_type' => [ITEM_VALUE_TYPE_UINT64, ITEM_VALUE_TYPE_FLOAT]],
'preservekeys' => true
]);
}
/**
* Prepares graph display details (graph dimensions, URL and sBox-ing flag).
*
* @param array $graphids Graph IDs for which details need to be prepared.
*
* @return array
*/
protected function getCharts(array $graphs): array {
$charts = [];
foreach ($graphs as $graph) {
if (array_key_exists('graphid', $graph)) {
$chart = [
'chartid' => 'graph_'.$graph['graphid'],
'graphid' => $graph['graphid'],
'dimensions' => getGraphDims($graph['graphid'])
];
if (in_array($chart['dimensions']['graphtype'], [GRAPH_TYPE_PIE, GRAPH_TYPE_EXPLODED])) {
$chart['sbox'] = false;
$chart['src'] = 'chart6.php';
}
else {
$chart['sbox'] = true;
$chart['src'] = 'chart2.php';
}
}
else {
$chart = [
'chartid' => 'item_'.$graph['itemid'],
'itemid' => $graph['itemid'],
'dimensions' => getGraphDims(),
'sbox' => true,
'src' => 'chart.php'
];
}
$charts[] = $chart;
}
return $charts;
}
/**
* Prepare subfilter fields from filter.
*
* @param array $filter
* @param array $filter['subfilter_tagnames'] Selected tagname subfilter parameters.
* @param array $filter['subfilter_tags'] Selected tags subfilter parameters.
*
* @return array
*/
protected static function getSubfilterFields(array $filter): array {
$subfilters = [];
$subfilter_keys = ['subfilter_tagnames', 'subfilter_tags'];
foreach ($subfilter_keys as $key) {
if (!array_key_exists($key, $filter)) {
continue;
}
if ($key === 'subfilter_tags') {
$tmp_tags = [];
foreach ($filter[$key] as $tag => $tag_values) {
$tmp_tags[urldecode($tag)] = array_flip($tag_values);
}
$subfilters[$key] = $tmp_tags;
unset($tmp_tags);
}
else {
$subfilters[$key] = array_flip($filter[$key]);
}
}
return CArrayHelper::renameKeys($subfilters, [
'subfilter_tagnames' => 'tagnames',
'subfilter_tags' => 'tags'
]);
}
/**
* Find what subfilters are available based on hosts selected using the main filter.
*
* @param array $graphs [IN/OUT] Result of host/simple graphs matching primary filter.
* @param string $graphs[]['graphid'] [IN] Host graph graphid.
* @param array $graphs[]['tags'] [IN] Item tags array.
* @param string $graphs[]['tags'][]['tag'] [IN] Tag name.
* @param string $graphs[]['tags'][]['value'] [IN] Tag value.
* @param array $graphs[]['matching_subfilters'] [OUT] Flag for each of subfilter group showing either graph fits
* its subfilter requirements.
* @param bool $graphs[]['has_data'] [OUT] Flag either item has data.
* @param array $subfilters Selected subfilters.
*
* @return array
*/
protected static function getSubfilters(array &$graphs, array $subfilters): array {
$subfilter_options = self::getSubfilterOptions($graphs, $subfilters);
$subfilters = self::clearSubfilters($subfilters, $subfilter_options);
$graphs = self::getGraphMatchings($graphs, $subfilters);
/*
* Calculate how many additional items would match the filtering results after selecting each of provided host
* subfilters. So item MUST match all subfilters except the tested one.
*/
foreach ($graphs as $graph) {
// Calculate the counters of tag existence subfilter options.
$graph_tagnames = [];
$selected_tagnames =
array_intersect(array_keys($subfilters['tagnames']), array_column($graph['tags'], 'tag'));
foreach ($graph['tags'] as $tag) {
if (array_key_exists($tag['tag'], $graph_tagnames)) {
continue;
}
$graph_tagnames[$tag['tag']] = [];
if ($selected_tagnames && !in_array($tag['tag'], $selected_tagnames)) {
continue;
}
$graph_matches = true;
foreach ($graph['matching_subfilters'] as $filter_name => $match) {
if ($filter_name === 'tagnames') {
continue;
}
if (!$match) {
$graph_matches = false;
break;
}
}
if ($graph_matches) {
$subfilter_options['tagnames'][$tag['tag']]['count']++;
}
}
// Calculate the same for the tag/value pair subfilter options.
$selected_tagvalues = [];
if ($subfilters['tags']) {
foreach ($graph['tags'] as $tag) {
if (array_key_exists($tag['tag'], $subfilters['tags'])
&& array_key_exists($tag['value'], $subfilters['tags'][$tag['tag']])) {
$selected_tagvalues[$tag['tag']][$tag['value']] = [];
}
}
}
foreach ($graph['tags'] as $tag) {
if ($selected_tagvalues) {
if (!(array_key_exists($tag['tag'], $selected_tagvalues)
&& array_key_exists($tag['value'], $selected_tagvalues[$tag['tag']]))) {
continue;
}
}
$graph_matches = true;
foreach ($graph['matching_subfilters'] as $filter_name => $match) {
if ($filter_name === 'tags') {
continue;
}
if (!$match) {
$graph_matches = false;
break;
}
}
if ($graph_matches) {
$subfilter_options['tags'][$tag['tag']][$tag['value']]['count']++;
}
}
}
return $subfilter_options;
}
/**
* Collect available options of subfilter from existing items and hosts selected by primary filter.
* All currently selected options will be included as well, regardless their presence in the retrieved data.
*
* @param array $graphs Host/Simple graphs selected by primary filter.
* @param array $graphs[]['tags'] Item tags.
* @param array $graphs[]['tags'][]['tag'] Item tag name.
* @param array $graphs[]['tags'][]['value'] Item tag value.
* @param array $subfilter
* @param array $subfilter['tagnames'] Selected subfilter names.
* @param array $subfilter['tags'] Selected subfilter tags.
*
* @return array
*/
protected static function getSubfilterOptions(array $graphs, array $subfilter): array {
$subfilter_options = [
'tagnames' => [],
'tags' => []
];
// First, add currently selected options, regardless their presence in the retrieved data.
foreach (array_keys($subfilter['tagnames']) as $tagname) {
$subfilter_options['tagnames'][$tagname] = [
'name' => $tagname,
'selected' => true,
'count' => 0
];
}
foreach ($subfilter['tags'] as $tag => $values) {
foreach (array_keys($values) as $value) {
$subfilter_options['tags'][$tag][$value] = [
'name' => $value,
'selected' => true,
'count' => 0
];
}
}
// Second, add options represented by the selected data.
foreach ($graphs as $graph) {
foreach ($graph['tags'] as $tag) {
if (!array_key_exists($tag['tag'], $subfilter_options['tagnames'])) {
$subfilter_options['tagnames'][$tag['tag']] = [
'name' => $tag['tag'],
'selected' => array_key_exists($tag['tag'], $subfilter['tagnames']),
'count' => 0
];
}
$subfilter_options['tags'][$tag['tag']][$tag['value']] = [
'name' => $tag['value'],
'selected' => (array_key_exists($tag['tag'], $subfilter['tags'])
&& array_key_exists($tag['value'], $subfilter['tags'][$tag['tag']])
),
'count' => 0
];
}
}
// Sort subfilters by values.
CArrayHelper::sort($subfilter_options['tagnames'], ['name']);
uksort($subfilter_options['tags'], 'strnatcmp');
array_walk($subfilter_options['tags'], function (&$tag_values) {
CArrayHelper::sort($tag_values, ['name']);
});
return $subfilter_options;
}
protected static function clearSubfilters(array $subfilter, array $subfilter_options): array {
foreach (array_keys($subfilter['tagnames']) as $tagname) {
if (!array_key_exists($tagname, $subfilter_options['tagnames'])) {
unset($subfilter['tagnames'][$tagname]);
}
}
foreach ($subfilter['tags'] as $tag => $values) {
if (!array_key_exists($tag, $subfilter_options['tags'])) {
unset($subfilter['tags'][$tag]);
continue;
}
foreach (array_keys($values) as $value) {
if (!array_key_exists($value, $subfilter_options['tags'][$tag])) {
unset($subfilter['tags'][$tag][$value]);
}
}
}
return $subfilter;
}
/**
* Calculate which items retrieved using the primary filter matches selected subfilter options. Results are added to
* the array stored with 'matching_subfilters' key for each retrieved item. Additionally 'has_data' flag is added to
* each of retrieved item to indicate either particular item has data.
*
* @param array $graphs
* @param string $graphs[]['graphid'] Item hostid.
* @param string $graphs[]['itemid'] Item itemid.
* @param array $graphs[]['tags'] Items tags.
* @param array $graphs[]['tags'][]['tag'] Items tag name.
* @param array $graphs[]['tags'][]['value'] Items tag value.
* @param array $subfilter
* @param array $subfilter['tagnames'] Selected subfilter tagnames.
* @param array $subfilter['tags'] Selected subfilter tags.
*
* @return array
*/
protected static function getGraphMatchings(array $graphs, array $subfilter): array {
foreach ($graphs as &$graph) {
$match_tagnames = (!$subfilter['tagnames']
|| (bool) array_intersect_key($subfilter['tagnames'], array_flip(array_column($graph['tags'], 'tag')))
);
if ($subfilter['tags']) {
$match_tags = false;
foreach ($graph['tags'] as $tag) {
if (array_key_exists($tag['tag'], $subfilter['tags'])
&& array_key_exists($tag['value'], $subfilter['tags'][$tag['tag']])) {
$match_tags = true;
break;
}
}
}
else {
$match_tags = true;
}
$graph['matching_subfilters'] = [
'tagnames' => $match_tagnames,
'tags' => $match_tags
];
}
unset($graph);
return $graphs;
}
/**
* Unset items not matching selected subfilters.
*
* @param array $graphs
* @param array $graphs['matching_subfilters'] Contains flags either items matches all selected subfilters.
*
* @return array
*/
protected static function applySubfilters(array $graphs): array {
return array_filter($graphs, function ($graph) {
return array_sum($graph['matching_subfilters']) == count($graph['matching_subfilters']);
});
}
/**
* Make subset of most severe subfilters to reduce the space used by subfilter.
*
* @param array $subfilters
* @param string $subfilters[<subfilter option>]['name'] Option name.
* @param bool $subfilters[<subfilter option>]['selected'] Flag indicating if option is selected.
*
* @return array
*/
public static function getMostSevereSubfilters(array $subfilters): array {
if (self::SUBFILTERS_VALUES_PER_ROW >= count($subfilters)) {
return $subfilters;
}
// All selected subfilters always must be included.
$most_severe = array_filter($subfilters, function ($elmnt) {
return $elmnt['selected'];
});
// Add first non-selected subfilter values in case if limit is not exceeded.
$remaining = self::SUBFILTERS_VALUES_PER_ROW - count($most_severe);
if ($remaining > 0) {
$subfilters = array_diff_key($subfilters, $most_severe);
$most_severe += array_slice($subfilters, 0, $remaining, true);
}
CArrayHelper::sort($most_severe, ['name']);
return $most_severe;
}
/**
* Make subset of most severe tag value subfilters to reduce the space used by subfilter.
*
* @param array $tags
* @param bool $tags[<tagname>][<tagvalue>]['selected'] Flag indicating if tag value is selected.
*
* @return array
*/
public static function getMostSevereTagValueSubfilters(array $tags): array {
if (self::SUBFILTERS_TAG_VALUE_ROWS >= count($tags)) {
return $tags;
}
// All selected subfilters always must be included.
$most_severe = array_filter($tags, function ($tag) {
return (bool) array_sum(array_column($tag, 'selected'));
});
// Add first non-selected subfilter values in case if limit is not exceeded.
$remaining = self::SUBFILTERS_TAG_VALUE_ROWS - count($most_severe);
if ($remaining > 0) {
$tags = array_diff_key($tags, $most_severe);
$most_severe += array_slice($tags, 0, $remaining, true);
}
uksort($most_severe, 'strnatcmp');
return $most_severe;
}
}