['min_user_type' => USER_TYPE_ZABBIX_USER] ]; public function __construct() { // the parent::__construct() method should not be called. } /** * Get trend data. * * @param array $options * @param int $options['time_from'] * @param int $options['time_till'] * @param int $options['limit'] * @param string $options['order'] * * @return array|int trend data as array or false if error */ public function get($options = []) { $default_options = [ 'itemids' => null, // filter 'time_from' => null, 'time_till' => null, // output 'output' => API_OUTPUT_EXTEND, 'countOutput' => false, 'limit' => null ]; $options = zbx_array_merge($default_options, $options); $storage_items = []; $result = ($options['countOutput']) ? 0 : []; if ($options['itemids'] === null || $options['itemids']) { // Check if items have read permissions. $items = API::Item()->get([ 'output' => ['itemid', 'value_type'], 'itemids' => $options['itemids'], 'webitems' => true, 'filter' => ['value_type' => [ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_UINT64]] ]); foreach ($items as $item) { $history_source = CHistoryManager::getDataSourceType($item['value_type']); $storage_items[$history_source][$item['value_type']][$item['itemid']] = true; } } foreach ([ZBX_HISTORY_SOURCE_ELASTIC, ZBX_HISTORY_SOURCE_SQL] as $source) { if (array_key_exists($source, $storage_items)) { $options['itemids'] = $storage_items[$source]; switch ($source) { case ZBX_HISTORY_SOURCE_ELASTIC: $data = $this->getFromElasticsearch($options); break; default: if (CHousekeepingHelper::get(CHousekeepingHelper::HK_TRENDS_GLOBAL) == 1) { $hk_trends = timeUnitToSeconds(CHousekeepingHelper::get(CHousekeepingHelper::HK_TRENDS)); $options['time_from'] = max($options['time_from'], time() - $hk_trends + 1); } $data = $this->getFromSql($options); } if (is_array($result)) { $result = array_merge($result, $data); } else { $result += $data; } } } return is_array($result) ? $result : (string) $result; } /** * SQL specific implementation of get. * * @see CTrend::get */ private function getFromSql($options) { $sql_where = []; if ($options['time_from'] !== null) { $sql_where['clock_from'] = 't.clock>='.zbx_dbstr($options['time_from']); } if ($options['time_till'] !== null) { $sql_where['clock_till'] = 't.clock<='.zbx_dbstr($options['time_till']); } if (!$options['countOutput']) { $sql_limit = ($options['limit'] && zbx_ctype_digit($options['limit'])) ? $options['limit'] : null; $sql_fields = []; if (is_array($options['output'])) { foreach ($options['output'] as $field) { if ($this->hasField($field, 'trends') && $this->hasField($field, 'trends_uint')) { $sql_fields[] = 't.'.$field; } } } elseif ($options['output'] == API_OUTPUT_EXTEND) { $sql_fields[] = 't.*'; } // An empty field set or invalid output method (string). Select only "itemid" instead of everything. if (!$sql_fields) { $sql_fields[] = 't.itemid'; } $result = []; foreach ($options['itemids'] as $value_type => $items) { if ($sql_limit !== null && $sql_limit <= 0) { break; } $sql_from = ($value_type == ITEM_VALUE_TYPE_FLOAT) ? 'trends' : 'trends_uint'; $sql_where['itemid'] = dbConditionInt('t.itemid', array_keys($items)); $res = DBselect( 'SELECT '.implode(',', $sql_fields). ' FROM '.$sql_from.' t'. ' WHERE '.implode(' AND ', $sql_where), $sql_limit ); while ($row = DBfetch($res)) { $result[] = $row; } if ($sql_limit !== null) { $sql_limit -= count($result); } } $result = $this->unsetExtraFields($result, ['itemid'], $options['output']); } else { $result = 0; foreach ($options['itemids'] as $value_type => $items) { $sql_from = ($value_type == ITEM_VALUE_TYPE_FLOAT) ? 'trends' : 'trends_uint'; $sql_where['itemid'] = dbConditionInt('t.itemid', array_keys($items)); $res = DBselect( 'SELECT COUNT(*) AS rowscount'. ' FROM '.$sql_from.' t'. ' WHERE '.implode(' AND ', $sql_where) ); if ($row = DBfetch($res)) { $result += $row['rowscount']; } } } return $result; } /** * Elasticsearch specific implementation of get. * * @see CTrend::get */ private function getFromElasticsearch($options) { $query_must = []; $value_types = [ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_UINT64]; $query = [ 'aggs' => [ 'group_by_itemid' => [ 'terms' => [ 'field' => 'itemid' ], 'aggs' => [ 'group_by_clock' => [ 'date_histogram' => [ 'field' => 'clock', 'interval' => '1h', 'min_doc_count' => 1 ], 'aggs' => [ 'max_value' => [ 'max' => [ 'field' => 'value' ] ], 'avg_value' => [ 'avg' => [ 'field' => 'value' ] ], 'min_value' => [ 'min' => [ 'field' => 'value' ] ] ] ] ] ] ], 'size' => 0 ]; if ($options['time_from'] !== null) { $query_must[] = [ 'range' => [ 'clock' => [ 'gte' => $options['time_from'] ] ] ]; } if ($options['time_till'] !== null) { $query_must[] = [ 'range' => [ 'clock' => [ 'lte' => $options['time_till'] ] ] ]; } $limit = ($options['limit'] && zbx_ctype_digit($options['limit'])) ? $options['limit'] : null; $result = []; if ($options['countOutput']) { $result = 0; } foreach (CHistoryManager::getElasticsearchEndpoints($value_types) as $type => $endpoint) { if (!array_key_exists($type, $options['itemids'])) { continue; } $itemids = array_keys($options['itemids'][$type]); if (!$itemids) { continue; } $query['query']['bool']['must'] = [ 'terms' => [ 'itemid' => $itemids ] ] + $query_must; $query['aggs']['group_by_itemid']['terms']['size'] = count($itemids); $data = CElasticsearchHelper::query('POST', $endpoint, $query); foreach ($data['group_by_itemid']['buckets'] as $item) { if (!$options['countOutput']) { foreach ($item['group_by_clock']['buckets'] as $histogram) { if ($limit !== null) { // Limit is reached, no need to continue. if ($limit <= 0) { break 3; } $limit--; } $result[] = [ 'itemid' => $item['key'], // Field key_as_string is used to get seconds instead of milliseconds. 'clock' => $histogram['key_as_string'], 'num' => $histogram['doc_count'], 'min_value' => $histogram['min_value']['value'], 'avg_value' => $histogram['avg_value']['value'], 'max_value' => $histogram['max_value']['value'] ]; } } else { $result += count($item['group_by_clock']['buckets']); } } } return $result; } }