1<?php
2/*
3** Zabbix
4** Copyright (C) 2001-2021 Zabbix SIA
5**
6** This program is free software; you can redistribute it and/or modify
7** it under the terms of the GNU General Public License as published by
8** the Free Software Foundation; either version 2 of the License, or
9** (at your option) any later version.
10**
11** This program is distributed in the hope that it will be useful,
12** but WITHOUT ANY WARRANTY; without even the implied warranty of
13** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14** GNU General Public License for more details.
15**
16** You should have received a copy of the GNU General Public License
17** along with this program; if not, write to the Free Software
18** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19**/
20
21
22/**
23 * Class calculates graph data and makes SVG graph.
24 */
25class CSvgGraphHelper {
26
27	/**
28	 * Calculate graph data and draw SVG graph based on given graph configuration.
29	 *
30	 * @param array  $options                     Options for graph.
31	 * @param array  $options['data_sets']        Graph data set options.
32	 * @param int    $options['data_source']      Data source of graph.
33	 * @param bool   $options['dashboard_time']   True if dashboard time is used.
34	 * @param array  $options['time_period']      Graph time period used.
35	 * @param array  $options['left_y_axis']      Options for graph left Y axis.
36	 * @param array  $options['right_y_axis']     Options for graph right Y axis.
37	 * @param array  $options['x_axis']           Options for graph X axis.
38	 * @param array  $options['legend']           Options for graph legend.
39	 * @param int    $options['legend_lines']     Number of lines in the legend.
40	 * @param array  $options['problems']         Graph problems options.
41	 * @param array  $options['overrides']        Graph override options.
42	 *
43	 * @return array
44	 */
45	public static function get(array $options = [], $width, $height) {
46		$metrics = [];
47		$errors = [];
48
49		// Find which metrics will be shown in graph and calculate time periods and display options.
50		self::getMetrics($metrics, $options['data_sets']);
51		// Apply overrides for previously selected $metrics.
52
53		$metrics = CMacrosResolverHelper::resolveItemNames($metrics);
54		$metrics = CArrayHelper::renameObjectsKeys($metrics, ['name_expanded' => 'name']);
55
56		self::applyOverrides($metrics, $options['overrides']);
57		// Apply time periods for each $metric, based on graph/dashboard time as well as metric level timeshifts.
58		self::getTimePeriods($metrics, $options['time_period']);
59		// Find what data source (history or trends) will be used for each metric.
60		self::getGraphDataSource($metrics, $errors, $options['data_source'], $width);
61		// Load Data for each metric.
62		self::getMetricsData($metrics, $width);
63		// Load aggregated Data for each dataset.
64		self::getMetricsAggregatedData($metrics);
65
66		// Legend single line height is 18. Value should be synchronized with $svg-legend-line-height in 'screen.scss'.
67		$legend_height = ($options['legend'] == SVG_GRAPH_LEGEND_TYPE_SHORT) ? $options['legend_lines'] * 18 : 0;
68
69		// Draw SVG graph.
70		$graph = (new CSvgGraph([
71			'time_period' => $options['time_period'],
72			'x_axis' => $options['x_axis'],
73			'left_y_axis' => $options['left_y_axis'],
74			'right_y_axis' => $options['right_y_axis']
75		]))
76			->setSize($width, $height - $legend_height)
77			->addMetrics($metrics);
78
79		// Get problems to display in graph.
80		if ($options['problems']['show_problems'] == SVG_GRAPH_PROBLEMS_SHOW) {
81			$options['problems']['itemids'] =
82				($options['problems']['graph_item_problems'] == SVG_GRAPH_SELECTED_ITEM_PROBLEMS)
83					? array_unique(zbx_objectValues($metrics, 'itemid'))
84					: null;
85
86			$problems = self::getProblems($options['problems'], $options['time_period']);
87			$graph->addProblems($problems);
88		}
89
90		if ($legend_height > 0) {
91			$labels = [];
92
93			foreach ($metrics as $metric) {
94				$labels[] = [
95					'name' => $metric['name'],
96					'color' => $metric['options']['color']
97				];
98			}
99
100			$legend = (new CSvgGraphLegend($labels))
101				->setAttribute('style', 'height: '.$legend_height.'px')
102				->toString();
103		}
104		else {
105			$legend = '';
106		}
107
108		// Draw graph.
109		$graph->draw();
110
111		// SBox available only for graphs without overriten relative time.
112		if ($options['dashboard_time']) {
113			$graph->addSBox();
114		}
115
116		// Add mouse following helper line.
117		$graph->addHelper();
118
119		return [
120			'svg' => $graph,
121			'legend' => $legend,
122			'data' => [
123				'dims' => [
124					'x' => $graph->getCanvasX(),
125					'y' => $graph->getCanvasY(),
126					'w' => $graph->getCanvasWidth(),
127					'h' => $graph->getCanvasHeight()
128				],
129				'spp' => (int) ($options['time_period']['time_to'] - $options['time_period']['time_from']) / $graph->getCanvasWidth()
130			],
131			'errors' => $errors
132		];
133	}
134
135	/**
136	 * Select aggregated data to show in graph for each metric.
137	 */
138	protected static function getMetricsAggregatedData(array &$metrics = []) {
139		$dataset_metrics = [];
140
141		foreach ($metrics as $metric_num => &$metric) {
142			if ($metric['options']['aggregate_function'] == GRAPH_AGGREGATE_NONE) {
143				continue;
144			}
145
146			$dataset_num = $metric['data_set'];
147
148			if ($metric['options']['aggregate_grouping'] == GRAPH_AGGREGATE_BY_ITEM) {
149				$name = $metric['hosts'][0]['name'].NAME_DELIMITER.$metric['name'];
150			}
151			else {
152				$name = 'Dataset #'.($dataset_num + 1);
153			}
154
155			$item = [
156				'itemid' => $metric['itemid'],
157				'value_type' => $metric['value_type'],
158				'source' => ($metric['source'] == SVG_GRAPH_DATA_SOURCE_HISTORY) ? 'history' : 'trends'
159			];
160
161			if (!array_key_exists($dataset_num, $dataset_metrics)) {
162				$metric = array_merge($metric, [
163					'name' => graph_item_aggr_fnc2str($metric['options']['aggregate_function']).'('.$name.')',
164					'items' => [],
165					'points' => []
166				]);
167				$metric['options']['aggregate_interval'] =
168					(int) timeUnitToSeconds($metric['options']['aggregate_interval'], true);
169
170				if ($metric['options']['aggregate_grouping'] == GRAPH_AGGREGATE_BY_DATASET) {
171					$dataset_metrics[$dataset_num] = $metric_num;
172				}
173
174				$metric['items'][] = $item;
175			}
176			else {
177				$metrics[$dataset_metrics[$dataset_num]]['items'][] = $item;
178				unset($metrics[$metric_num]);
179			}
180		}
181		unset($metric);
182
183		foreach ($metrics as &$metric) {
184			if ($metric['options']['aggregate_function'] == GRAPH_AGGREGATE_NONE) {
185				continue;
186			}
187
188			$result = Manager::History()->getGraphAggregationByInterval(
189				$metric['items'], $metric['time_period']['time_from'], $metric['time_period']['time_to'],
190				$metric['options']['aggregate_function'], $metric['options']['aggregate_interval']
191			);
192
193			$metric_points = [];
194
195			if ($result) {
196				foreach ($result as $itemid => $points) {
197					foreach ($points['data'] as $point) {
198						$metric_points[$point['tick']]['itemid'][] = $point['itemid'];
199						$metric_points[$point['tick']]['clock'][] = $point['clock'];
200
201						if (array_key_exists('count', $point)) {
202							$metric_points[$point['tick']]['count'][] = $point['count'];
203						}
204						if (array_key_exists('value', $point)) {
205							$metric_points[$point['tick']]['value'][] = $point['value'];
206						}
207					}
208				}
209				ksort($metric_points, SORT_NUMERIC);
210
211				switch ($metric['options']['aggregate_function']) {
212					case GRAPH_AGGREGATE_MIN:
213						foreach ($metric_points as $tick => $point) {
214							$metric['points'][] = ['clock' => $tick, 'value' => min($point['value'])];
215						}
216						break;
217					case GRAPH_AGGREGATE_MAX:
218						foreach ($metric_points as $tick => $point) {
219							$metric['points'][] = ['clock' => $tick, 'value' => max($point['value'])];
220						}
221						break;
222					case GRAPH_AGGREGATE_AVG:
223						foreach ($metric_points as $tick => $point) {
224							$metric['points'][] = [
225								'clock' => $tick,
226								'value' => CMathHelper::safeAvg($point['value'])
227							];
228						}
229						break;
230					case GRAPH_AGGREGATE_COUNT:
231						foreach ($metric_points as $tick => $point) {
232							$metric['points'][] = ['clock' => $tick, 'value' => array_sum($point['count'])];
233						}
234						break;
235					case GRAPH_AGGREGATE_SUM:
236						foreach ($metric_points as $tick => $point) {
237							$metric['points'][] = ['clock' => $tick, 'value' => array_sum($point['value'])];
238						}
239						break;
240					case GRAPH_AGGREGATE_FIRST:
241						foreach ($metric_points as $tick => $point) {
242							if ($point['clock']) {
243								$metric['points'][] = [
244									'clock' => $tick,
245									'value' => $point['value'][array_search(min($point['clock']), $point['clock'])]
246								];
247							}
248						}
249						break;
250					case GRAPH_AGGREGATE_LAST:
251						foreach ($metric_points as $tick => $point) {
252							if ($point['clock']) {
253								$metric['points'][] = [
254									'clock' => $tick,
255									'value' => $point['value'][array_search(max($point['clock']), $point['clock'])]
256								];
257							}
258						}
259						break;
260				}
261			}
262		}
263	}
264
265	/**
266	 * Select data to show in graph for each metric.
267	 */
268	protected static function getMetricsData(array &$metrics = [], $width) {
269		// To reduce number of requests, group metrics by time range.
270		$tr_groups = [];
271		foreach ($metrics as $metric_num => &$metric) {
272			if ($metric['options']['aggregate_function'] != GRAPH_AGGREGATE_NONE) {
273				continue;
274			}
275
276			$metric['name'] = $metric['hosts'][0]['name'].NAME_DELIMITER.$metric['name'];
277			$metric['points'] = [];
278
279			$key = $metric['time_period']['time_from'].$metric['time_period']['time_to'];
280			if (!array_key_exists($key, $tr_groups)) {
281				$tr_groups[$key] = [
282					'time' => [
283						'from' => $metric['time_period']['time_from'],
284						'to' => $metric['time_period']['time_to']
285					]
286				];
287			}
288
289			$tr_groups[$key]['items'][$metric_num] = [
290				'itemid' => $metric['itemid'],
291				'value_type' => $metric['value_type'],
292				'source' => ($metric['source'] == SVG_GRAPH_DATA_SOURCE_HISTORY) ? 'history' : 'trends'
293			];
294		}
295		unset($metric);
296
297		// Request data.
298		foreach ($tr_groups as $tr_group) {
299			$results = Manager::History()->getGraphAggregationByWidth($tr_group['items'], $tr_group['time']['from'],
300				$tr_group['time']['to'], $width
301			);
302
303			if ($results) {
304				foreach ($tr_group['items'] as $metric_num => $item) {
305					$metric = &$metrics[$metric_num];
306
307					// Collect and sort data points.
308					if (array_key_exists($item['itemid'], $results)) {
309						$points = [];
310						foreach ($results[$item['itemid']]['data'] as $point) {
311							$points[] = ['clock' => $point['clock'], 'value' => $point['avg']];
312						}
313						usort($points, [__CLASS__, 'sortByClock']);
314						$metric['points'] = $points;
315
316						unset($metric['source'], $metric['history'], $metric['trends']);
317					}
318				}
319				unset($metric);
320			}
321		}
322	}
323
324	/**
325	 * Calculate what data source must be used for each metric.
326	 */
327	protected static function getGraphDataSource(array &$metrics = [], array &$errors = [], $data_source, $width) {
328		/**
329		 * If data source is not specified, calculate it automatically. Otherwise, set given $data_source to each
330		 * $metric.
331		 */
332		if ($data_source == SVG_GRAPH_DATA_SOURCE_AUTO) {
333			/**
334			 * First, if global configuration setting "Override item history period" is enabled, override globally
335			 * specified "Data storage period" value to each metric's custom history storage duration, converting it
336			 * to seconds. If "Override item history period" is disabled, item level field 'history' will be used
337			 * later but now we are just storing the field name 'history' in array $to_resolve.
338			 *
339			 * Do the same with trends.
340			 */
341			$to_resolve = [];
342
343			if (CHousekeepingHelper::get(CHousekeepingHelper::HK_HISTORY_GLOBAL)) {
344				foreach ($metrics as &$metric) {
345					$metric['history'] = timeUnitToSeconds(CHousekeepingHelper::get(CHousekeepingHelper::HK_HISTORY));
346				}
347				unset($metric);
348			}
349			else {
350				$to_resolve[] = 'history';
351			}
352
353			if (CHousekeepingHelper::get(CHousekeepingHelper::HK_TRENDS_GLOBAL)) {
354				foreach ($metrics as &$metric) {
355					$metric['trends'] = timeUnitToSeconds(CHousekeepingHelper::get(CHousekeepingHelper::HK_TRENDS));
356				}
357				unset($metric);
358			}
359			else {
360				$to_resolve[] = 'trends';
361			}
362
363			// If no global history and trend override enabled, resolve 'history' and/or 'trends' values for given $metric.
364			if ($to_resolve) {
365				$metrics = CMacrosResolverHelper::resolveTimeUnitMacros($metrics, $to_resolve);
366				$simple_interval_parser = new CSimpleIntervalParser();
367
368				foreach ($metrics as $num => &$metric) {
369					// Convert its values to seconds.
370					if (!CHousekeepingHelper::get(CHousekeepingHelper::HK_HISTORY_GLOBAL)) {
371						if ($simple_interval_parser->parse($metric['history']) != CParser::PARSE_SUCCESS) {
372							$errors[] = _s('Incorrect value for field "%1$s": %2$s.', 'history',
373								_('invalid history storage period')
374							);
375							unset($metrics[$num]);
376						}
377						else {
378							$metric['history'] = timeUnitToSeconds($metric['history']);
379						}
380					}
381
382					if (!CHousekeepingHelper::get(CHousekeepingHelper::HK_TRENDS_GLOBAL)) {
383						if ($simple_interval_parser->parse($metric['trends']) != CParser::PARSE_SUCCESS) {
384							$errors[] = _s('Incorrect value for field "%1$s": %2$s.', 'trends',
385								_('invalid trend storage period')
386							);
387							unset($metrics[$num]);
388						}
389						else {
390							$metric['trends'] = timeUnitToSeconds($metric['trends']);
391						}
392					}
393				}
394				unset($metric);
395			}
396
397			foreach ($metrics as &$metric) {
398				/**
399				 * History as a data source is used in 2 cases:
400				 * 1) if trends are disabled (set to 0) either for particular $metric item or globally;
401				 * 2) if period for requested data is newer than the period of keeping history for particular $metric
402				 *	  item.
403				 *
404				 * Use trends otherwise.
405				 */
406				$history = $metric['history'];
407				$trends = $metric['trends'];
408				$time_from = $metric['time_period']['time_from'];
409				$period = $metric['time_period']['time_to'] - $time_from;
410
411				$metric['source'] = ($trends == 0 || (time() - $history < $time_from
412						&& $period / $width <= ZBX_MAX_TREND_DIFF / ZBX_GRAPH_MAX_SKIP_CELL))
413					? SVG_GRAPH_DATA_SOURCE_HISTORY
414					: SVG_GRAPH_DATA_SOURCE_TRENDS;
415			}
416			unset($metric);
417		}
418		else {
419			foreach ($metrics as &$metric) {
420				$metric['source'] = $data_source;
421			}
422			unset($metric);
423		}
424	}
425
426	/**
427	 * Find problems at given time period that matches specified problem options.
428	 */
429	protected static function getProblems(array $problem_options, array $time_period) {
430		$options = [
431			'output' => ['objectid', 'name', 'severity', 'clock', 'r_eventid'],
432			'select_acknowledges' => ['action'],
433			'problem_time_from' => $time_period['time_from'],
434			'problem_time_till' => $time_period['time_to'],
435			'preservekeys' => true
436		];
437
438		// Find triggers involved.
439		if ($problem_options['problemhosts'] !== '') {
440			$options['hostids'] = array_keys(API::Host()->get([
441				'output' => [],
442				'searchWildcardsEnabled' => true,
443				'searchByAny' => true,
444				'search' => [
445					'name' => self::processPattern($problem_options['problemhosts'])
446				],
447				'preservekeys' => true
448			]));
449
450			// Return if no hosts found.
451			if (!$options['hostids']) {
452				return [];
453			}
454		}
455
456		$options['objectids'] = array_keys(API::Trigger()->get([
457			'output' => [],
458			'hostids' => array_key_exists('hostids', $options) ? $options['hostids'] : null,
459			'itemids' => $problem_options['itemids'],
460			'monitored' => true,
461			'preservekeys' => true
462		]));
463
464		unset($options['hostids']);
465
466		// Return if no triggers found.
467		if (!$options['objectids']) {
468			return [];
469		}
470
471		// Add severity filter.
472		$filter_severities = implode(',', $problem_options['severities']);
473		$all_severities = implode(',', range(TRIGGER_SEVERITY_NOT_CLASSIFIED, TRIGGER_SEVERITY_COUNT - 1));
474
475		if ($filter_severities !== '' && $filter_severities !== $all_severities) {
476			$options['severities'] = $problem_options['severities'];
477		}
478
479		// Add problem name filter.
480		if ($problem_options['problem_name'] !== '') {
481			$options['searchWildcardsEnabled'] = true;
482			$options['search']['name'] = $problem_options['problem_name'];
483		}
484
485		// Add tags filter.
486		if ($problem_options['tags']) {
487			$options['evaltype'] = $problem_options['evaltype'];
488			$options['tags'] = $problem_options['tags'];
489		}
490
491		$events = API::Event()->get($options);
492
493		// Find end time for each problem.
494		if ($events) {
495			$r_events = API::Event()->get([
496				'output' => ['clock'],
497				'eventids' => zbx_objectValues($events, 'r_eventid'),
498				'preservekeys' => true
499			]);
500		}
501
502		// Add recovery times for each problem event.
503		foreach ($events as &$event) {
504			$event['r_clock'] = array_key_exists($event['r_eventid'], $r_events)
505				? $r_events[$event['r_eventid']]['clock']
506				: 0;
507		}
508		unset($event);
509
510		CArrayHelper::sort($events, [['field' => 'clock', 'order' => ZBX_SORT_DOWN]]);
511
512		return $events;
513	}
514
515	/**
516	 * Select metrics from given data set options. Apply data set options to each selected metric.
517	 */
518	protected static function getMetrics(array &$metrics, array $data_sets) {
519		$max_metrics = SVG_GRAPH_MAX_NUMBER_OF_METRICS;
520
521		foreach ($data_sets as $index => $data_set) {
522			if (!$data_set['hosts'] || !$data_set['items']) {
523				continue;
524			}
525
526			if ($max_metrics == 0) {
527				break;
528			}
529
530			// Find hosts.
531			$hosts = API::Host()->get([
532				'output' => [],
533				'search' => [
534					'name' => self::processPattern($data_set['hosts'])
535				],
536				'searchWildcardsEnabled' => true,
537				'searchByAny' => true,
538				'preservekeys' => true
539			]);
540
541			if ($hosts) {
542				$items = API::Item()->get([
543					'output' => [
544						'itemid', 'hostid', 'name', 'history', 'trends', 'units', 'value_type', 'valuemapid', 'key_'
545					],
546					'selectHosts' => ['hostid', 'name'],
547					'hostids' => array_keys($hosts),
548					'webitems' => true,
549					'filter' => [
550						'value_type' => [ITEM_VALUE_TYPE_UINT64, ITEM_VALUE_TYPE_FLOAT]
551					],
552					'search' => [
553						'name' => self::processPattern($data_set['items'])
554					],
555					'searchWildcardsEnabled' => true,
556					'searchByAny' => true,
557					'sortfield' => 'name',
558					'sortorder' => ZBX_SORT_UP,
559					'limit' => $max_metrics
560				]);
561
562				if (!$items) {
563					continue;
564				}
565
566				unset($data_set['hosts'], $data_set['items']);
567
568				// The bigger transparency level, the less visible the metric is.
569				$data_set['transparency'] = 10 - (int) $data_set['transparency'];
570
571				$data_set['timeshift'] = ($data_set['timeshift'] !== '')
572					? (int) timeUnitToSeconds($data_set['timeshift'])
573					: 0;
574
575				$colors = getColorVariations('#'.$data_set['color'], count($items));
576
577				foreach ($items as $item) {
578					$data_set['color'] = array_shift($colors);
579					$metrics[] = $item + ['data_set' => $index, 'options' => $data_set];
580					$max_metrics--;
581				}
582			}
583		}
584	}
585
586	/**
587	 * Apply overrides for each pattern matchig metric.
588	 */
589	protected static function applyOverrides(array &$metrics = [], array $overrides = []) {
590		foreach ($overrides as $override) {
591			if ($override['hosts'] === '' || $override['items'] === '') {
592				continue;
593			}
594
595			// Convert timeshift to seconds.
596			if (array_key_exists('timeshift', $override)) {
597				$override['timeshift'] = ($override['timeshift'] !== '')
598					? (int) timeUnitToSeconds($override['timeshift'])
599					: 0;
600			}
601
602			$hosts_patterns = self::processPattern($override['hosts']);
603			$items_patterns = self::processPattern($override['items']);
604
605			unset($override['hosts'], $override['items']);
606
607			$metrics_matched = [];
608
609			foreach ($metrics as $metric_num => $metric) {
610				// If '*' used, apply options to all metrics.
611				$host_matches = ($hosts_patterns === null);
612				$item_matches = ($items_patterns === null);
613
614				/**
615				 * Find if host and item names matches one of given patterns.
616				 *
617				 * It currently checks if at least one of host pattern and at least one of item pattern matches,
618				 * without checking relation between matching host and item.
619				 */
620				if ($hosts_patterns !== null) {
621					for ($hosts_pattern = reset($hosts_patterns); !$host_matches && $hosts_pattern !== false;
622							$hosts_pattern = next($hosts_patterns)) {
623						$pattern = '/^'.str_replace('\*', '.*', preg_quote($hosts_pattern, '/')).'$/i';
624						$host_matches = (bool) preg_match($pattern, $metric['hosts'][0]['name']);
625					}
626				}
627
628				if ($items_patterns !== null && $host_matches) {
629					for ($items_pattern = reset($items_patterns); !$item_matches && $items_pattern !== false;
630							$items_pattern = next($items_patterns)) {
631						$pattern = '/^'.str_replace('\*', '.*', preg_quote($items_pattern, '/')).'$/i';
632						$item_matches = (bool) preg_match($pattern, $metric['name']);
633					}
634				}
635
636				/**
637				 * We need to know total amount of matched metrics to calculate variations of colors. That's why we
638				 * first collect matching metrics and than override existing metric options.
639				 */
640				if ($host_matches && $item_matches) {
641					$metrics_matched[] = $metric_num;
642				}
643			}
644
645			// Apply override options to matching metrics.
646			if ($metrics_matched) {
647				$colors = (array_key_exists('color', $override) && $override['color'] !== '')
648					? getColorVariations('#'.$override['color'], count($metrics_matched))
649					: null;
650
651				if (array_key_exists('transparency', $override)) {
652					// The bigger transparency level, the less visible the metric is.
653					$override['transparency'] = 10 - (int) $override['transparency'];
654				}
655
656				foreach ($metrics_matched as $metric_num) {
657					$metric = &$metrics[$metric_num];
658
659					if ($colors !== null) {
660						$override['color'] = array_shift($colors);
661					}
662					$metric['options'] = $override + $metric['options'];
663
664					// Fix missing options if draw type has changed.
665					switch ($metric['options']['type']) {
666						case SVG_GRAPH_TYPE_POINTS:
667							if (!array_key_exists('pointsize', $metric['options'])) {
668								$metric['options']['pointsize'] = SVG_GRAPH_DEFAULT_POINTSIZE;
669							}
670							break;
671
672						case SVG_GRAPH_TYPE_LINE:
673						case SVG_GRAPH_TYPE_STAIRCASE:
674							if (!array_key_exists('fill', $metric['options'])) {
675								$metric['options']['fill'] = SVG_GRAPH_DEFAULT_FILL;
676							}
677							if (!array_key_exists('missingdatafunc', $metric['options'])) {
678								$metric['options']['missingdatafunc'] = SVG_GRAPH_MISSING_DATA_NONE;
679							}
680							if (!array_key_exists('width', $metric['options'])) {
681								$metric['options']['width'] = SVG_GRAPH_DEFAULT_WIDTH;
682							}
683							break;
684					}
685				}
686				unset($metric);
687			}
688		}
689	}
690
691	/**
692	 * Apply time period for each metric.
693	 */
694	protected static function getTimePeriods(array &$metrics = [], array $options) {
695		foreach ($metrics as &$metric) {
696			$metric['time_period'] = $options;
697
698			if ($metric['options']['timeshift'] != 0) {
699				$metric['time_period']['time_from'] += $metric['options']['timeshift'];
700				$metric['time_period']['time_to'] += $metric['options']['timeshift'];
701			}
702		}
703		unset($metric);
704	}
705
706	/**
707	 * Prepare an array to be used for hosts/items filtering.
708	 *
709	 * @param array  $patterns  Array of strings containing hosts/items patterns.
710	 *
711	 * @return array|mixed  Returns array of patterns.
712	 *                      Returns NULL if array contains '*' (so any possible host/item search matches).
713	 */
714	protected static function processPattern(array $patterns) {
715		return in_array('*', $patterns) ? null : $patterns;
716	}
717
718	/*
719	 * Sort data points by clock field.
720	 * Do not use this function directly. It serves as value_compare_func function for usort.
721	 */
722	protected static function sortByClock($a, $b) {
723		if ($a['clock'] == $b['clock']) {
724			return 0;
725		}
726
727		return ($a['clock'] < $b['clock']) ? -1 : 1;
728	}
729}
730