1<?php
2
3/*
4	Phoronix Test Suite
5	URLs: http://www.phoronix.com, http://www.phoronix-test-suite.com/
6	Copyright (C) 2009 - 2021, Phoronix Media
7	Copyright (C) 2009 - 2021, Michael Larabel
8
9	This program is free software; you can redistribute it and/or modify
10	it under the terms of the GNU General Public License as published by
11	the Free Software Foundation; either version 3 of the License, or
12	(at your option) any later version.
13
14	This program is distributed in the hope that it will be useful,
15	but WITHOUT ANY WARRANTY; without even the implied warranty of
16	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17	GNU General Public License for more details.
18
19	You should have received a copy of the GNU General Public License
20	along with this program. If not, see <http://www.gnu.org/licenses/>.
21*/
22
23class pts_test_result_buffer
24{
25	public $buffer_items;
26
27	// TODO XXX: ultimately revisit the test_result_buffer handling in the future to see if it's safe these days for map buffer_items keys by identifier
28	// likely some corner cases around renaming, sorting, etc still to be sorted out...
29	protected $buffer_contains;
30	protected $buffer_by_identifier;
31	protected $added_multi_sample_result = false;
32	protected $max_precision = 0;
33	protected $min_bi;
34	protected $min_value = 0;
35	protected $max_bi;
36	protected $max_value = 0;
37
38	public function __construct($buffer_items = array())
39	{
40		$this->buffer_items = $buffer_items;
41
42		if(!empty($buffer_items))
43		{
44			foreach($buffer_items as $i => &$buffer_item)
45			{
46				$this->buffer_contains[$buffer_item->get_result_identifier() . $buffer_item->get_result_value()] = 1;
47				$this->buffer_by_identifier[$buffer_item->get_result_identifier()] = $i;
48				$this->check_buffer_item_for_min_max($buffer_item);
49			}
50		}
51	}
52	public function add_buffer_item($buffer_item)
53	{
54		if(isset($this->buffer_by_identifier[$buffer_item->get_result_identifier()]) && $this->buffer_items[$this->buffer_by_identifier[$buffer_item->get_result_identifier()]]->get_result_value() == '')
55		{
56			// Overwrite the buffer item if there is a match but empty (incomplete) result
57			$this->remove($buffer_item->get_result_identifier());
58		}
59
60		if(!$this->buffer_contained($buffer_item))
61		{
62			$this->buffer_items[] = $buffer_item;
63			$this->buffer_by_identifier[$buffer_item->get_result_identifier()] = (count($this->buffer_items) - 1);
64			$this->buffer_contains[$buffer_item->get_result_identifier() . $buffer_item->get_result_value()] = 1;
65			$this->check_buffer_item_for_min_max($buffer_item);
66		}
67	}
68	public function add_test_result($identifier, $value, $raw_value = null, $json = null, $min_value = null, $max_value = null)
69	{
70		$buffer_item = new pts_test_result_buffer_item($identifier, $value, $raw_value, $json, $min_value, $max_value);
71		if(isset($this->buffer_by_identifier[$buffer_item->get_result_identifier()]) && $this->buffer_items[$this->buffer_by_identifier[$buffer_item->get_result_identifier()]]->get_result_value() == '')
72		{
73			// Overwrite the buffer item if there is a match but empty (incomplete) result
74			$this->remove($buffer_item->get_result_identifier());
75		}
76
77		$this->check_buffer_item_for_min_max($buffer_item);
78		$this->buffer_items[] = $buffer_item;
79
80		if(is_array($value))
81		{
82			$value = implode(':', $value);
83		}
84
85		if($this->added_multi_sample_result == false && $raw_value && !is_array($raw_value))
86		{
87			$this->added_multi_sample_result = strpos($raw_value, ':') !== false;
88		}
89
90		$this->buffer_contains[$identifier . $value] = 1;
91		$this->buffer_by_identifier[$identifier] = (count($this->buffer_items) - 1);
92	}
93	public function recalculate_buffer_items_min_max()
94	{
95		$this->min_value = 0;
96		$this->max_value = 0;
97
98		foreach($this->buffer_items as &$buffer_item)
99		{
100			$this->check_buffer_item_for_min_max($buffer_item);
101		}
102	}
103	protected function check_buffer_item_for_min_max(&$buffer_item)
104	{
105		$value = $buffer_item->get_result_value();
106		if(!is_numeric($value))
107		{
108			$values = !is_array($value) ? explode(',', $value) : $value;
109			$min_value = min($values);
110			$max_value = max($values);
111
112			if(!is_numeric($min_value))
113			{
114				return;
115			}
116		}
117		else
118		{
119			$min_value = $value;
120			$max_value = $value;
121		}
122		if($min_value < $this->min_value || $this->min_value == 0)
123		{
124			$this->min_value = $min_value;
125			$this->min_bi = $buffer_item;
126		}
127		if($max_value > $this->max_value)
128		{
129			$this->max_value = $max_value;
130			$this->max_bi = $buffer_item;
131		}
132
133		// Also check precision
134		$this->max_precision = max($this->max_precision, pts_math::get_precision($buffer_item->get_result_value()));
135	}
136	public function __clone()
137	{
138		foreach($this->buffer_items as $i => $v)
139		{
140			$this->buffer_items[$i] = clone $this->buffer_items[$i];
141		}
142	}
143	public function get_buffer_items()
144	{
145		return $this->buffer_items;
146	}
147	public function sort_buffer_items()
148	{
149		sort($this->buffer_items);
150	}
151	public function sort_buffer_values($asc = true)
152	{
153		usort($this->buffer_items, array('pts_test_result_buffer', 'buffer_value_comparison'));
154
155		if($asc == false)
156		{
157			$this->buffer_items = array_reverse($this->buffer_items);
158		}
159	}
160	public static function buffer_value_comparison($a, $b)
161	{
162		return strcmp($a->get_result_value(), $b->get_result_value());
163	}
164	public function find_buffer_item($identifier)
165	{
166		foreach($this->buffer_items as &$buf)
167		{
168			if($buf->get_result_identifier() == $identifier)
169			{
170				return $buf;
171			}
172		}
173
174		return false;
175	}
176	public function get_result_from_identifier($identifier)
177	{
178		foreach($this->buffer_items as &$buf)
179		{
180			if($buf->get_result_identifier() == $identifier)
181			{
182				return $buf->get_result_value();
183			}
184		}
185
186		return false;
187	}
188	public function buffer_contained(&$buffer_item)
189	{
190		return isset($this->buffer_contains[$buffer_item->get_result_identifier() . $buffer_item->get_result_value()]);
191	}
192	public function get_buffer_item($i)
193	{
194		return isset($this->buffer_items[$i]) ? $this->buffer_items[$i] : false;
195	}
196	public function detected_multi_sample_result()
197	{
198		return $this->added_multi_sample_result;
199	}
200	public function clear_outlier_results($value_below)
201	{
202		$cleared = false;
203		foreach($this->buffer_items as $key => &$buffer_item)
204		{
205			if($buffer_item->get_result_value() < $value_below)
206			{
207				$other_value += $buffer_item->get_result_value();
208				unset($this->buffer_items[$key]);
209				$cleared = true;
210			}
211		}
212
213		if($cleared)
214		{
215			$this->recalculate_buffer_items_min_max();
216		}
217	}
218	public function rename($from, $to)
219	{
220		if($from == 'PREFIX')
221		{
222			foreach($this->buffer_items as &$buffer_item)
223			{
224				$buffer_item->reset_result_identifier($to . ': ' . $buffer_item->get_result_identifier());
225			}
226		}
227		else if($from == null && count($this->buffer_items) == 1)
228		{
229			foreach($this->buffer_items as &$buffer_item)
230			{
231				$buffer_item->reset_result_identifier($to);
232			}
233			return true;
234		}
235		else
236		{
237			foreach($this->buffer_items as &$buffer_item)
238			{
239				if($buffer_item->get_result_identifier() == $from)
240				{
241					$buffer_item->reset_result_identifier($to);
242					return true;
243				}
244			}
245		}
246		return false;
247	}
248	public function reorder($new_order)
249	{
250		foreach($new_order as $identifier)
251		{
252			foreach($this->buffer_items as $i => &$buffer_item)
253			{
254				if($buffer_item->get_result_identifier() == $identifier)
255				{
256					$c = $buffer_item;
257					unset($this->buffer_items[$i]);
258					$this->buffer_items[] = $c;
259					break;
260				}
261			}
262		}
263	}
264	public function remove($remove)
265	{
266		$remove = pts_arrays::to_array($remove);
267		$removed = false;
268		foreach($this->buffer_items as $i => &$buffer_item)
269		{
270			if(in_array($buffer_item->get_result_identifier(), $remove))
271			{
272				unset($this->buffer_by_identifier[$this->buffer_items[$i]->get_result_identifier()]);
273				unset($this->buffer_contains[$this->buffer_items[$i]->get_result_identifier() . $this->buffer_items[$i]->get_result_value()]);
274				unset($this->buffer_items[$i]);
275				$removed = true;
276			}
277		}
278
279		if($removed)
280		{
281			$this->recalculate_buffer_items_min_max();
282		}
283
284		return $removed;
285	}
286	public function auto_shorten_buffer_identifiers($identifier_shorten_index = false)
287	{
288		// If there's a lot to plot, try to auto-shorten the identifiers
289		// e.g. if each identifier contains like 'GeForce 6800', 'GeForce GT 220', etc..
290		// then remove the 'GeForce' part of the name.
291
292		if($identifier_shorten_index == false)
293		{
294			$identifier_shorten_index = pts_render::evaluate_redundant_identifier_words($this->get_identifiers());
295		}
296
297		if(empty($identifier_shorten_index))
298		{
299			return false;
300		}
301
302		foreach($this->buffer_items as &$buffer_item)
303		{
304			$identifier = explode(' ', $buffer_item->get_result_identifier());
305			foreach($identifier_shorten_index as $pos => $value)
306			{
307				if($identifier[$pos] == $value)
308				{
309					unset($identifier[$pos]);
310				}
311			}
312			$buffer_item->reset_result_identifier(implode(' ', $identifier));
313		}
314
315		return true;
316	}
317	public function clear_iqr_outlier_results()
318	{
319		$is_multi_way = pts_render::multi_way_identifier_check($this->get_identifiers());
320		$cleared = false;
321
322		if($is_multi_way)
323		{
324			$group_values = array();
325			$group_keys = array();
326
327			foreach($this->buffer_items as $key => &$buffer_item)
328			{
329				$identifier_r = pts_strings::trim_explode(': ', $buffer_item->get_result_identifier());
330
331				if(!isset($group_values[$identifier_r[1]]))
332				{
333					$group_values[$identifier_r[1]] = array();
334					$group_keys[$identifier_r[1]] = array();
335				}
336
337				$group_values[$identifier_r[1]][] = $buffer_item->get_result_value();
338				$group_keys[$identifier_r[1]][] = $key;
339			}
340
341			foreach($group_values as $group_key => $values)
342			{
343				// From: http://www.mathwords.com/o/outlier.htm
344				$fqr = pts_math::first_quartile($values);
345				$tqr = pts_math::third_quartile($values);
346				$iqr_cut = ($tqr - $fqr) * 1.5;
347				$bottom_cut = $fqr - $iqr_cut;
348				$top_cut = $tqr + $iqr_cut;
349
350				foreach($group_keys[$group_key] as $key)
351				{
352					$value = $this->buffer_items[$key]->get_result_value();
353
354					if($value > $top_cut || $value < $bottom_cut)
355					{
356						unset($this->buffer_items[$key]);
357						$cleared = true;
358					}
359				}
360			}
361		}
362		else
363		{
364			// From: http://www.mathwords.com/o/outlier.htm
365			$values = $this->get_values();
366			$fqr = pts_math::first_quartile($values);
367			$tqr = pts_math::third_quartile($values);
368			$iqr_cut = ($tqr - $fqr) * 1.5;
369			$bottom_cut = $fqr - $iqr_cut;
370			$top_cut = $tqr + $iqr_cut;
371
372			foreach($this->buffer_items as $key => &$buffer_item)
373			{
374				$value = $buffer_item->get_result_value();
375
376				if($value > $top_cut || $value < $bottom_cut)
377				{
378					unset($this->buffer_items[$key]);
379					$cleared = true;
380				}
381			}
382		}
383
384		if($cleared)
385		{
386			$this->recalculate_buffer_items_min_max();
387		}
388	}
389	public function buffer_values_sort()
390	{
391		usort($this->buffer_items, array('pts_test_result_buffer_item', 'compare_value'));
392	}
393	public function buffer_values_reverse()
394	{
395		$this->buffer_items = array_reverse($this->buffer_items);
396	}
397	public function get_count()
398	{
399		return count($this->buffer_items);
400	}
401	public function get_identifiers()
402	{
403		$identifiers = array();
404
405		foreach($this->buffer_items as &$buffer_item)
406		{
407			$identifiers[] = $buffer_item->get_result_identifier();
408		}
409
410		return $identifiers;
411	}
412	public function get_total_value_sum()
413	{
414		$sum = 0;
415
416		foreach($this->buffer_items as &$buffer_item)
417		{
418			$v = $buffer_item->get_result_value();
419			if(is_numeric($v))
420			{
421				$sum += $v;
422			}
423		}
424
425		return $sum;
426	}
427	public function get_longest_identifier()
428	{
429		$identifier = null;
430		$length = 0;
431
432		foreach($this->buffer_items as &$buffer_item)
433		{
434			if(($l = strlen($buffer_item->get_result_identifier())) > $length)
435			{
436				$length = $l;
437				$identifier = $buffer_item->get_result_identifier();
438			}
439		}
440
441		return $identifier;
442	}
443	public function result_identifier_differences_only_numeric()
444	{
445		if(!isset($this->buffer_items[0]))
446		{
447			return false;
448		}
449
450		$first_result = trim(str_ireplace(array('SVN', 'Git', 'Dev'), '', pts_strings::remove_from_string($this->buffer_items[0]->get_result_identifier(), pts_strings::CHAR_NUMERIC | pts_strings::CHAR_DECIMAL | pts_strings::CHAR_DASH)));
451		for($i = 1;  $i < count($this->buffer_items); $i++)
452		{
453			$result = trim(str_ireplace(array('SVN', 'Git', 'Dev'), '', pts_strings::remove_from_string($this->buffer_items[$i]->get_result_identifier(), pts_strings::CHAR_NUMERIC | pts_strings::CHAR_DECIMAL | pts_strings::CHAR_DASH)));
454			if($result != $first_result)
455			{
456				return false;
457			}
458		}
459		return true;
460	}
461	public function get_max_precision()
462	{
463		return $this->max_precision;
464	}
465	public function reset_precision($precision)
466	{
467		foreach($this->buffer_items as &$buffer_item)
468		{
469			if(!is_numeric($buffer_item->get_result_value()))
470			{
471				continue;
472			}
473
474			$p = pts_math::set_precision($buffer_item->get_result_value(), $precision);
475			$buffer_item->reset_result_value($p, false);
476		}
477	}
478	public function reduce_precision()
479	{
480		$min_value = $this->get_min_value();
481		$max_value = $this->get_max_value();
482		if($min_value > 20 && ($max_value / $min_value) > 1.25)
483		{
484			$this->reset_precision(0);
485		}
486		else
487		{
488			$max_precision = $this->get_max_precision();
489			if($max_precision >= 1)
490			{
491				if($min_value > 10 && $max_precision > 1)
492				{
493					$max_precision = 2;
494				}
495				/*else if($max_precision > 3 && ($max_value / $min_value) > 1.3)
496				{
497					$max_precision = 3;
498				}*/
499				else if($max_precision > 3)
500				{
501					$max_precision = 3;
502				}
503
504				$this->reset_precision(($max_precision - 1));
505			}
506		}
507	}
508	public function get_min_value($return_identifier = false)
509	{
510		if($this->min_bi == null)
511		{
512			return null;
513		}
514		else if($return_identifier === 2)
515		{
516			return $this->min_bi;
517		}
518		else if($return_identifier)
519		{
520			return $this->min_bi->get_result_identifier();
521		}
522		else
523		{
524			return pts_math::set_precision($this->min_value, $this->get_max_precision());
525		}
526	}
527	public function get_max_value($return_identifier = false)
528	{
529		if($this->max_bi == null)
530		{
531			return null;
532		}
533		else if($return_identifier === 2)
534		{
535			return $this->max_bi;
536		}
537		else if($return_identifier)
538		{
539			return $this->max_bi->get_result_identifier();
540		}
541		else
542		{
543			return pts_math::set_precision($this->max_value, $this->get_max_precision());
544		}
545	}
546	public function has_run_with_multiple_samples()
547	{
548		foreach($this->buffer_items as &$buffer_item)
549		{
550			if($buffer_item->get_sample_count() > 1)
551			{
552				return true;
553			}
554		}
555
556		return false;
557	}
558	public function get_value_from_identifier($result_identifier)
559	{
560		foreach($this->buffer_items as &$buffer_item)
561		{
562			if($buffer_item->get_result_identifier() == $result_identifier)
563			{
564				return $buffer_item->get_result_value();
565			}
566		}
567
568		return false;
569	}
570	public function get_identifier_value_map()
571	{
572		$m = array();
573
574		foreach($this->buffer_items as &$buffer_item)
575		{
576			$m[$buffer_item->get_result_identifier()] = $buffer_item->get_result_value();
577		}
578
579		return $m;
580	}
581	public function get_map_by_identifier()
582	{
583		$m = array();
584
585		foreach($this->buffer_items as &$buffer_item)
586		{
587			$m[$buffer_item->get_result_identifier()] = &$buffer_item;
588		}
589
590		return $m;
591	}
592	public function buffer_values_to_percent()
593	{
594		$is_multi_way = pts_render::multi_way_identifier_check($this->get_identifiers());
595		if($is_multi_way)
596		{
597			$group_values = array();
598			foreach($this->buffer_items as &$buffer_item)
599			{
600				if(!is_numeric($buffer_item->get_result_value()))
601				{
602					continue;
603				}
604				$identifier_r = pts_strings::trim_explode(': ', $buffer_item->get_result_identifier());
605				if(!isset($group_values[$identifier_r[1]]))
606				{
607					$group_values[$identifier_r[1]] = 0;
608				}
609				$group_values[$identifier_r[1]] += $buffer_item->get_result_value();
610			}
611			foreach($this->buffer_items as &$buffer_item)
612			{
613				if(!is_numeric($buffer_item->get_result_value()))
614				{
615					continue;
616				}
617				$identifier_r = pts_strings::trim_explode(': ', $buffer_item->get_result_identifier());
618				$percent = pts_math::set_precision(($buffer_item->get_result_value() / $group_values[$identifier_r[1]] * 100), 3);
619				$buffer_item->reset_result_value($percent);
620			}
621		}
622		else
623		{
624			$total_value = array_sum($this->get_values());
625			foreach($this->buffer_items as &$buffer_item)
626			{
627				$percent = pts_math::set_precision(($buffer_item->get_result_value() / $total_value * 100), 3);
628				$buffer_item->reset_result_value($percent);
629			}
630		}
631	}
632	public function adjust_precision($precision = 'auto')
633	{
634		if($precision == 'auto')
635		{
636			// For very large results, little point in keeping the precision...
637			$min_value = $this->get_min_value();
638			$precision = -1;
639			if($min_value >= 100)
640			{
641				$precision = 0;
642			}
643			if($min_value >= 10)
644			{
645				$precision = 2;
646			}
647
648			$current_precision = $this->get_max_precision();
649			$precision = $precision == -1 ? $current_precision : min($precision, $current_precision);
650		}
651
652		if(is_numeric($precision))
653		{
654			foreach($this->buffer_items as &$buffer_item)
655			{
656				if(is_numeric(($val = $buffer_item->get_result_value())))
657				{
658					$buffer_item->reset_result_value(pts_math::set_precision($val, $precision), false);
659				}
660			}
661
662		}
663	}
664	public function get_values()
665	{
666		$values = array();
667
668		foreach($this->buffer_items as &$buffer_item)
669		{
670			$values[] = $buffer_item->get_result_value();
671		}
672
673		return $values;
674	}
675	public function get_median()
676	{
677		return pts_math::median($this->get_values());
678	}
679	public function get_values_as_string()
680	{
681		return implode(':', $this->get_values());
682	}
683}
684
685?>
686