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