1<?php
2
3/*
4	Phoronix Test Suite
5	URLs: http://www.phoronix.com, http://www.phoronix-test-suite.com/
6	Copyright (C) 2015 - 2021, Phoronix Media
7	Copyright (C) 2015 - 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_stress_run_manager extends pts_test_run_manager
24{
25	private $multi_test_stress_start_time;
26	private $stress_tests_executed;
27	private $sensor_data_archived;
28	private $sensor_data_archived_units;
29	private $sensor_data_archived_identifiers;
30	private $loop_until_time;
31	private $thread_collection_dir;
32	private $sensors_to_monitor;
33	private $stress_subsystems_active;
34	private $stress_child_thread = false;
35	private $stress_logger;
36	private $stress_log_event_call = false;
37
38	public $save_result_file = false;
39	public $save_result_identifier = false;
40
41	public static function stress_run($to_run, $batch_mode = false)
42	{
43		if($batch_mode == false)
44		{
45			$batch_mode = array(
46				'UploadResults' => false,
47				'SaveResults' => false,
48				'PromptForTestDescription' => false,
49				'RunAllTestCombinations' => false,
50				'PromptSaveName' => false,
51				'PromptForTestIdentifier' => false,
52				'OpenBrowser' => false
53				);
54		}
55		$test_run_manager = new pts_stress_run_manager($batch_mode);
56
57		$tests_to_run_concurrently = 2;
58
59		echo PHP_EOL . pts_client::cli_just_bold('STRESS-RUN ENVIRONMENT VARIABLES:') . PHP_EOL;
60
61		if(($j = getenv('PTS_CONCURRENT_TEST_RUNS')) && is_numeric($j) && $j > 1)
62		{
63			$tests_to_run_concurrently = $j;
64			echo PHP_EOL . 'PTS_CONCURRENT_TEST_RUNS set; running ' . $tests_to_run_concurrently . ' tests concurrently.' . PHP_EOL . PHP_EOL;
65		}
66		else
67		{
68			echo PHP_EOL . pts_client::cli_just_bold('PTS_CONCURRENT_TEST_RUNS:') . ' Set the PTS_CONCURRENT_TEST_RUNS environment variable to specify how many tests should be run concurrently during the stress-run process. If not specified, defaults to 2.' . PHP_EOL . PHP_EOL;
69		}
70
71		// Run the actual tests
72		$total_loop_time = pts_client::read_env('TOTAL_LOOP_TIME');
73		if($total_loop_time == 'infinite')
74		{
75			$total_loop_time = 'infinite';
76			echo PHP_EOL . pts_client::cli_just_bold('TOTAL_LOOP_TIME') . ' set; running tests in an infinite loop until otherwise triggered' . PHP_EOL . PHP_EOL;
77		}
78		else if($total_loop_time && is_numeric($total_loop_time) && $total_loop_time > 1)
79		{
80			echo PHP_EOL . pts_client::cli_just_bold('TOTAL_LOOP_TIME') . ' set; running tests for ' . $total_loop_time . ' minutes' . PHP_EOL . PHP_EOL;
81		}
82		else
83		{
84			echo PHP_EOL . pts_client::cli_just_bold('TOTAL_LOOP_TIME:') . ' Set the TOTAL_LOOP_TIME environment variable if wishing to specify (in minutes) how long to run the stress-run process.' . PHP_EOL . PHP_EOL;
85			$total_loop_time = false;
86		}
87
88		if(($j = getenv('TEST_RESULTS_NAME')))
89		{
90			$test_run_manager->save_result_file = pts_test_run_manager::clean_save_name($j);
91			$test_run_manager->result_file = new pts_result_file($test_run_manager->save_result_file);
92			$test_run_manager->result_file->set_title($j . ' Stress-Run Monitoring');
93			echo PHP_EOL . pts_client::cli_just_bold('TEST_RESULTS_NAME') . ' set; saving the result sensor data as ' . $test_run_manager->save_result_file . '.' . PHP_EOL . PHP_EOL;
94
95			if(($j = getenv('TEST_RESULTS_IDENTIFIER')))
96			{
97				$test_run_manager->save_result_identifier = $j;
98				echo PHP_EOL . pts_client::cli_just_bold('TEST_RESULTS_IDENTIFIER') . ' set; test identifier is ' . $test_run_manager->save_result_identifier . '.' . PHP_EOL . PHP_EOL;
99			}
100			else
101			{
102				$test_run_manager->save_result_identifier = date('Y-m-d H:i:s');
103				echo PHP_EOL . pts_client::cli_just_bold('TEST_RESULTS_IDENTIFIER') . ' is not set; test identifier is ' . $test_run_manager->save_result_identifier . '.' . PHP_EOL . PHP_EOL;
104			}
105
106			$sys = new pts_result_file_system($test_run_manager->save_result_identifier, phodevi::system_hardware(true), phodevi::system_software(true), array(), pts_client::current_user(), null, date('Y-m-d H:i:s', pts_client::current_time()), PTS_VERSION, $test_run_manager->result_file);
107			$test_run_manager->result_file->add_system($sys);
108		}
109		else
110		{
111			echo PHP_EOL . pts_client::cli_just_bold('TEST_RESULTS_NAME') . ' is not set; set the TEST_RESULTS_NAME environment variable if wanting to save the sensor summary to a result file.' . PHP_EOL . PHP_EOL;
112		}
113		//pts_test_installer::standard_install($to_run);
114		/*
115		if(count($to_run) < $tests_to_run_concurrently)
116		{
117			echo PHP_EOL . 'More tests must be specified in order to run ' . $tests_to_run_concurrently . ' tests concurrently.';
118			return false;
119		}
120		*/
121
122		if($test_run_manager->initial_checks($to_run, 'SHORT') == false)
123		{
124			return false;
125		}
126
127		// Load the tests to run
128		if($test_run_manager->load_tests_to_run($to_run) == false)
129		{
130			return false;
131		}
132
133		//$test_run_manager->pre_execution_process();
134		$test_run_manager->multi_test_stress_run_execute($tests_to_run_concurrently, $total_loop_time);
135	}
136	public function multi_test_stress_run_execute($tests_to_run_concurrently = 3, $total_loop_time = false)
137	{
138		ini_set('memory_limit','8192M'); // XXX testing
139
140		$continue_test_flag = true;
141		pts_client::$display->test_run_process_start($this);
142		$this->allow_test_cache_share = false;
143		$this->disable_dynamic_run_count();
144		$this->multi_test_stress_run = $tests_to_run_concurrently;
145		$possible_tests_to_run = $this->get_tests_to_run();
146
147		// Cache test profiles to avoid unnecessary recreation for reading metadata from it
148		$test_profiles = array();
149		// Cache pid to test identifier mapping to avoid extra reads of the PID file just to get the identifier
150		$pid_files_to_test_identifier = array();
151
152		foreach($possible_tests_to_run as &$rr)
153		{
154			$test_profiles[$rr->test_profile->get_identifier()] = $rr->test_profile;
155		}
156		if(is_numeric($total_loop_time))
157		{
158			$total_loop_time = $total_loop_time * 60;
159		}
160		$this->loop_until_time = is_numeric($total_loop_time) && $total_loop_time > 1 ? time() + $total_loop_time : false;
161		$this->stress_tests_executed = array();
162		$this->multi_test_stress_start_time = time();
163		$this->thread_collection_dir = pts_client::create_temporary_directory('stress-threads');
164		$this->sensors_to_monitor = array();
165		$this->sensor_data_archived = array();
166		$this->sensor_data_archived_units = array();
167		$this->sensor_data_archived_identifiers = array();
168		$this->stress_logger = new pts_logger(null, 'phoronix-test-suite-stress-' . date('ymdHi') . '.log');
169		$this->stress_logger->log('Log Initialized');
170		putenv('FORCE_TIMES_TO_RUN=1');
171
172		// Determine how frequently to print reports / status updates
173		$time_report_counter = time();
174		if($total_loop_time == 'infinite')
175		{
176			$report_counter_frequency = 6 * 60;
177		}
178		else if($total_loop_time > (3 * 60 * 60))
179		{
180			$report_counter_frequency = 30 * 60;
181		}
182		else if($total_loop_time > (60 * 60))
183		{
184			$report_counter_frequency = 10 * 60;
185		}
186		else if($total_loop_time > (20 * 60))
187		{
188			$report_counter_frequency = 5 * 60;
189		}
190		else if($total_loop_time > (10 * 60))
191		{
192			$report_counter_frequency = 2 * 60;
193		}
194		else
195		{
196			$report_counter_frequency = 60;
197		}
198
199		// SIGTERM handling
200		if(function_exists('pcntl_signal'))
201		{
202			declare(ticks = 1);
203			pcntl_signal(SIGTERM, array($this, 'sig_handler'));
204			pcntl_signal(SIGHUP, array($this, 'sig_handler'));
205			pcntl_signal(SIGINT, array($this, 'sig_handler'));
206		}
207		else
208		{
209			$this->stress_print_and_log('PHP PCNTL support is needed if wishing to gracefully interrupt testing with signals.' . PHP_EOL);
210		}
211
212		// SENSOR SETUP WORK
213		$sensor_interval_frequency = is_numeric($total_loop_time) && $total_loop_time > 1 ? max($total_loop_time / 1000, 3) : 6;
214		$sensor_time_since_last_poll = time();
215		foreach(phodevi::supported_sensors(array('cpu_temp', 'cpu_usage', 'gpu_usage', 'gpu_temp', 'hdd_read_speed', 'hdd_write_speed', 'memory_usage', 'swap_usage', 'sys_temp')) as $sensor)
216		{
217			$supported_devices = call_user_func(array($sensor[2], 'get_supported_devices'));
218
219			if($supported_devices === NULL)
220			{
221				$supported_devices = array(null);
222			}
223
224			foreach($supported_devices as $device)
225			{
226				$sensor_object = new $sensor[2](0, $device);
227				if(phodevi::read_sensor($sensor_object) != -1)
228				{
229					array_push($this->sensors_to_monitor, $sensor_object);
230					$this->sensor_data_archived[phodevi::sensor_object_name($sensor_object)] = array();
231					$this->sensor_data_archived_units[phodevi::sensor_object_name($sensor_object)] = phodevi::read_sensor_object_unit($sensor_object);
232					$this->sensor_data_archived_identifiers[phodevi::sensor_object_name($sensor_object)] = phodevi::sensor_object_identifier($sensor_object);
233				}
234			}
235		}
236
237		$table = array();
238		foreach(phodevi::system_hardware(false) as $component => $value)
239		{
240			$table[] = array($component . ': ', $value);
241		}
242		foreach(phodevi::system_software(false) as $component => $value)
243		{
244			$table[] = array($component . ': ', $value);
245		}
246		$this->stress_print_and_log('SYSTEM INFORMATION: ' . PHP_EOL . phodevi::system_centralized_view() . PHP_EOL . PHP_EOL);
247		if(!function_exists('pcntl_waitpid'))
248		{
249			$this->stress_print_and_log('PHP PCNTL support is to run stress tests.' . PHP_EOL);
250			return false;
251
252		}
253		pts_module_manager::module_process('__pre_run_process', $this);
254
255		// BEGIN THE LOOP
256		while(!empty($possible_tests_to_run))
257		{
258			if($continue_test_flag == false)
259				break;
260
261			if(($time_report_counter + $report_counter_frequency) <= time() && count(pts_file_io::glob($this->thread_collection_dir . '*')) > 0)
262			{
263				// ISSUE STATUS REPORT
264				$this->stress_print_and_log($this->final_stress_report(false, $pid_files_to_test_identifier));
265				$time_report_counter = time();
266			}
267
268			$this->stress_subsystems_active = array();
269			$test_identifiers_active = array();
270
271			while(($waited_pid = pcntl_waitpid(-1, $status, WNOHANG)) > 0)
272			{
273				unset($pid_files_to_test_identifier[$waited_pid]);
274				pts_file_io::unlink($this->thread_collection_dir . $waited_pid);
275			}
276
277			foreach(pts_file_io::glob($this->thread_collection_dir . '*') as $pid_file)
278			{
279				$pid = basename($pid_file);
280				$waited_pid = pcntl_waitpid($pid, $status, WNOHANG);
281
282				if(!file_exists('/proc/' . $pid))
283				{
284					unlink($pid_file);
285					unset($pid_files_to_test_identifier[$pid]);
286					continue;
287				}
288
289				if(!isset($test_profiles[$pid_files_to_test_identifier[$pid]]))
290				{
291					continue;
292				}
293				$test = $test_profiles[$pid_files_to_test_identifier[$pid]];
294
295				// Count the number of tests per stress subsystems active
296				if(!isset($this->stress_subsystems_active[$test->get_test_hardware_type()]))
297				{
298					$this->stress_subsystems_active[$test->get_test_hardware_type()] = 1;
299				}
300				else
301				{
302					$this->stress_subsystems_active[$test->get_test_hardware_type()] += 1;
303				}
304
305				if(!in_array($test->get_identifier(), $test_identifiers_active))
306				{
307					$test_identifiers_active[] = $test->get_identifier();
308				}
309
310			}
311
312			if(!empty($possible_tests_to_run) && count(pts_file_io::glob($this->thread_collection_dir . '*')) < $this->multi_test_stress_run && (!$total_loop_time || $total_loop_time == 'infinite' || $this->loop_until_time > time()))
313			{
314				shuffle($possible_tests_to_run);
315
316				$test_to_run = false;
317				$test_run_index = -1;
318
319				if(getenv('DONT_BALANCE_TESTS_FOR_SUBSYSTEMS') == false)
320				{
321					// Try to pick a test for a hardware subsystem not yet being explicitly utilized
322					foreach($possible_tests_to_run as $i => $test)
323					{
324						$hw_subsystem_type = $test->test_profile->get_test_hardware_type();
325
326						if(!isset($this->stress_subsystems_active[$hw_subsystem_type]) && !$this->skip_test_check($test))
327						{
328							$test_run_index = $i;
329							$test_to_run = $test;
330							break;
331						}
332					}
333				}
334
335				if($test_run_index == -1 && getenv('DONT_TRY_TO_ENSURE_TESTS_ARE_UNIQUE') == false)
336				{
337					// Try to pick a test from a test profile not currently active
338					foreach($possible_tests_to_run as $i => $test)
339					{
340						if(!in_array($test->test_profile->get_identifier(), $test_identifiers_active) && !$this->skip_test_check($test))
341						{
342							$test_run_index = $i;
343							$test_to_run = $test;
344							break;
345						}
346					}
347				}
348
349				if($test_run_index == -1)
350				{
351					// Last resort, just randomly pick a true "random" test
352					$test_run_index = array_rand(array_keys($possible_tests_to_run));
353					$test_to_run = $possible_tests_to_run[$test_run_index];
354
355					if($this->skip_test_check($test_to_run))
356					{
357						continue;
358					}
359				}
360
361				$pid = pcntl_fork();
362				if($pid == -1)
363				{
364					$this->stress_print_and_log('Forking Failure.');
365				}
366				if($pid)
367				{
368					// parent
369					$test_identifier = $test_to_run->test_profile->get_identifier();
370					file_put_contents($this->thread_collection_dir . $pid, $test_identifier);
371					$pid_files_to_test_identifier[$pid] = $test_identifier;
372
373					if(!isset($this->stress_tests_executed[$test_identifier]))
374					{
375						$this->stress_tests_executed[$test_identifier] = 1;
376					}
377					else
378					{
379						$this->stress_tests_executed[$test_identifier]++;
380					}
381				}
382				else
383				{
384					// child
385					$this->stress_child_thread = true;
386					//echo PHP_EOL . pts_client::cli_colored_text('Starting: ', 'green', true) . $test_to_run->test_profile->get_identifier() . ($test_to_run->get_arguments_description() != null ? ' [' . $test_to_run->get_arguments_description()  . ']' : null) . PHP_EOL;
387					$continue_test_flag = $this->process_test_run_request($test_to_run);
388					//echo PHP_EOL . pts_client::cli_colored_text('Ended: ', 'red', true) . $test_to_run->test_profile->get_identifier() . ($test_to_run->get_arguments_description() != null ? ' [' . $test_to_run->get_arguments_description()  . ']' : null) . PHP_EOL;
389					pts_file_io::unlink($this->thread_collection_dir . getmypid());
390					unset($pid_files_to_test_identifier[getmypid()]);
391					//echo PHP_EOL;
392					exit(0);
393				}
394				if($total_loop_time == false)
395				{
396					unset($possible_tests_to_run[$test_run_index]);
397				}
398				else if($total_loop_time == 'infinite')
399				{
400					//$this->stress_print_and_log('Continuing to test indefinitely' . PHP_EOL);
401				}
402				else
403				{
404					if($this->loop_until_time > time())
405					{
406						$time_left = ceil(($this->loop_until_time - time()) / 60);
407					//	echo 'Continuing to test for ' . $time_left . ' more minutes' . PHP_EOL;
408					}
409				}
410			}
411
412			if(is_numeric($this->loop_until_time) && $this->loop_until_time < time())
413			{
414				// Time to Quit
415				$this->stress_print_and_log('TEST TIME EXPIRED; NO NEW TESTS WILL EXECUTE; CURRENT TESTS WILL FINISH' . PHP_EOL);
416				// This halt-testing touch will let tests exit early (i.e. between multiple run steps)
417				file_put_contents(PTS_USER_PATH . 'halt-testing', 'stress-run is done... This text really is not important, just checking for file presence.');
418				// Final report
419				$this->stress_print_and_log($this->final_stress_report(false, $pid_files_to_test_identifier));
420				break;
421			}
422
423			if($sensor_time_since_last_poll + $sensor_interval_frequency < time())
424			{
425				// Time to do a sensor reading
426				foreach($this->sensors_to_monitor as &$sensor_object)
427				{
428					$this->sensor_data_archived[phodevi::sensor_object_name($sensor_object)][] = phodevi::read_sensor($sensor_object);
429				}
430				$sensor_time_since_last_poll = time();
431			}
432		}
433
434		pts_module_manager::module_process('__post_run_process', $this);
435		putenv('FORCE_TIMES_TO_RUN');
436		pts_file_io::delete($this->thread_collection_dir, null, true);
437
438		foreach($this->get_tests_to_run() as $run_request)
439		{
440			// Remove cache shares
441			foreach(pts_file_io::glob($run_request->test_profile->get_install_dir() . 'cache-share-*.pt2so') as $cache_share_file)
442			{
443				unlink($cache_share_file);
444			}
445		}
446
447		// Wait for child processes to complete
448		//pcntl_waitpid(-1, $status);
449
450		// Restore default handlers
451		pcntl_signal(SIGTERM, SIG_DFL);
452		pcntl_signal(SIGINT, SIG_DFL);
453		pcntl_signal(SIGHUP, SIG_DFL);
454
455		return true;
456	}
457	public function is_interactive_mode()
458	{
459		return false;
460	}
461	protected function skip_test_check(&$test)
462	{
463		$hw_subsystem_type = $test->test_profile->get_test_hardware_type();
464		$subsystem_limit_check = getenv('LIMIT_STRESS_' . strtoupper($hw_subsystem_type) . '_TESTS_COUNT');
465
466		if(isset($this->stress_subsystems_active[$hw_subsystem_type]) && $subsystem_limit_check && $subsystem_limit_check <= $this->stress_subsystems_active[$hw_subsystem_type])
467		{
468			// e.g. LIMIT_STRESS_GRAPHICS_TESTS_COUNT=2, don't want more than that number per subsystem concurrently
469			return true;
470		}
471
472		return false;
473	}
474	public function action_on_stress_log_set($call)
475	{
476		if(is_callable($call))
477		{
478			$this->stress_log_event_call = $call;
479		}
480	}
481	protected function stress_print_and_log($msg)
482	{
483		if($this->stress_logger && $msg != null)
484		{
485			echo $msg;
486			$this->stress_logger->log($msg, false);
487		}
488		if($this->stress_log_event_call)
489		{
490			call_user_func($this->stress_log_event_call, $this->stress_logger->get_clean_log());
491		}
492	}
493	public function get_stress_log()
494	{
495		return $this->stress_logger->get_clean_log();
496	}
497	public function sig_handler($signo)
498	{
499		// Time to Quit
500		// This halt-testing touch will let tests exit early (i.e. between multiple run steps)
501		file_put_contents(PTS_USER_PATH . 'halt-testing', 'stress-run is done... This text really is not important, just checking for file presence.');
502
503		if($this->stress_child_thread == false)
504		{
505			$this->stress_print_and_log('SIGNAL RECEIVED; QUITTING...' . PHP_EOL);
506			// Final report
507			$this->stress_print_and_log($this->final_stress_report());
508		}
509		exit();
510	}
511	protected function final_stress_report($is_final = true, $pid_files_to_test_identifier = null)
512	{
513		if(!$is_final)
514		{
515			$report_buffer = PHP_EOL . '###### STRESS RUN INTERIM REPORT ####' . PHP_EOL;
516		}
517		else
518		{
519			$report_buffer = PHP_EOL . '###### SUMMARY REPORT ####' . PHP_EOL;
520		}
521
522		$report_buffer .= strtoupper(date('F j H:i T')) . PHP_EOL;
523		$report_buffer .= pts_client::cli_just_bold('START TIME: ') . date('F j H:i T', $this->multi_test_stress_start_time) . PHP_EOL;
524		$report_buffer .= pts_client::cli_just_bold('ELAPSED TIME: ') . pts_strings::format_time(time() - $this->multi_test_stress_start_time) . PHP_EOL;
525		if($this->loop_until_time > time())
526		{
527			$report_buffer .= pts_client::cli_just_bold('TIME REMAINING: ') . pts_strings::format_time($this->loop_until_time - time()) . PHP_EOL;
528		}
529		else
530		{
531			$report_buffer .= 'WAITING FOR CURRENT TEST RUN QUEUE TO FINISH.' . PHP_EOL;
532		}
533		$report_buffer .= pts_client::cli_just_bold('SYSTEM IP: ') . phodevi::read_property('network', 'ip') . PHP_EOL;
534		$report_buffer .= pts_client::cli_just_bold('HOSTNAME: ') . phodevi::read_property('system', 'hostname') . PHP_EOL;
535		$report_buffer .= pts_client::cli_just_bold('# OF CONCURRENT TESTS: ') . $this->multi_test_stress_run . PHP_EOL . PHP_EOL;
536
537		if(!$is_final)
538		{
539			$report_buffer .= 'TESTS CURRENTLY ACTIVE: ' . PHP_EOL;
540
541			$table = array();
542			foreach(pts_file_io::glob($this->thread_collection_dir . '*') as $pid_file)
543			{
544				$pid_file_basename = basename($pid_file);
545				$test = isset($pid_files_to_test_identifier[$pid_file_basename]) ? $pid_files_to_test_identifier[$pid_file_basename] : pts_file_io::file_get_contents($pid_file);
546				$table[] = array($test, '[PID: ' . basename($pid_file) . ']');
547			}
548			$report_buffer .= pts_user_io::display_text_table($table, '   - ', 2) . PHP_EOL;
549		}
550
551		$report_buffer .= PHP_EOL . pts_client::cli_just_bold('TESTS IN RUN QUEUE: ') . PHP_EOL . PHP_EOL;
552		$tiq = array();
553		foreach($this->get_tests_to_run() as $i => $test)
554		{
555			$bar = pts_client::cli_colored_text(strtoupper($test->test_profile->get_title()) . ' [' . $test->test_profile->get_identifier() . ']', 'blue', true);
556			if(!isset($tiq[$bar]))
557			{
558				$tiq[$bar] = array();
559			}
560
561			array_push($tiq[$bar], $test->get_arguments_description());
562		}
563		foreach($tiq as $test => $args)
564		{
565			$report_buffer .= $test;
566			foreach($args as $arg)
567			{
568				if(!empty($arg))
569				{
570					$report_buffer .= PHP_EOL . '     ' . $arg;
571				}
572			}
573			$report_buffer .= PHP_EOL;
574		}
575
576		if(!empty($this->stress_tests_executed))
577		{
578			$table = array(array(pts_client::cli_just_bold('TESTS EXECUTED'), pts_client::cli_just_bold('TIMES CALLED')));
579			ksort($this->stress_tests_executed);
580
581			foreach($this->stress_tests_executed as $test => $times)
582			{
583				$table[] = array(pts_client::cli_just_bold($test) . ': ', $times);
584			}
585			$report_buffer .= pts_user_io::display_text_table($table, '     ', 2) . PHP_EOL . PHP_EOL;
586		}
587
588		if($this->save_result_file)
589		{
590			$desc = $this->save_result_identifier . ': ' . $report_buffer;
591		}
592
593		$report_buffer .= PHP_EOL . pts_client::cli_just_bold('SYSTEM INFORMATION: ') . PHP_EOL;
594		$table = array();
595		foreach(phodevi::system_hardware(false) as $component => $value)
596		{
597			$table[] = array(pts_client::cli_just_bold($component . ': '), $value);
598		}
599		foreach(phodevi::system_software(false) as $component => $value)
600		{
601			$table[] = array(pts_client::cli_just_bold($component . ': '), $value);
602		}
603		$report_buffer .= pts_user_io::display_text_table($table, '     ', 1) . PHP_EOL . PHP_EOL;
604
605		$report_buffer .= pts_client::cli_just_bold('SENSOR DATA: ') . PHP_EOL;
606		$table = array(array(pts_client::cli_just_bold('SENSOR'), pts_client::cli_just_bold('MIN'), pts_client::cli_just_bold('AVG'), pts_client::cli_just_bold('MAX')));
607		foreach($this->sensor_data_archived as $sensor_name => &$sensor_data)
608		{
609			if(empty($sensor_data))
610				continue;
611
612			$max_val = max($sensor_data);
613
614			if($max_val > 0)
615			{
616				$table[] = array(pts_client::cli_just_bold($sensor_name . ': '),
617					pts_math::set_precision(min($sensor_data), 2),
618					pts_math::set_precision(pts_math::arithmetic_mean($sensor_data), 2),
619					pts_math::set_precision($max_val, 2),
620					$this->sensor_data_archived_units[$sensor_name]);
621
622				if($this->save_result_file)
623				{
624					$test_profile = new pts_test_profile();
625					$test_result = new pts_test_result($test_profile);
626
627					$test_result->test_profile->set_test_title($sensor_name . ' Monitor');
628					$test_result->test_profile->set_identifier(null);
629					$test_result->test_profile->set_version(null);
630					$test_result->test_profile->set_result_proportion(null);
631					$test_result->test_profile->set_display_format('LINE_GRAPH');
632					$test_result->test_profile->set_result_scale($this->sensor_data_archived_units[$sensor_name]);
633					$test_result->set_used_arguments_description('Phoronix Test Suite System Monitoring');
634					$test_result->set_used_arguments($this->sensor_data_archived_identifiers[$sensor_name]);
635					$test_result->test_result_buffer = new pts_test_result_buffer();
636					$test_result->test_result_buffer->add_test_result($this->save_result_identifier, implode(',', $sensor_data), implode(',', $sensor_data));
637					$this->result_file->add_result_return_object($test_result);
638				}
639			}
640		}
641		$report_buffer .= pts_user_io::display_text_table($table, '     ', 2) . PHP_EOL;
642		$report_buffer .= '######' . PHP_EOL;
643
644		if($this->save_result_file)
645		{
646			$this->result_file->append_description($this->save_result_identifier . ': ' . $report_buffer);
647			pts_client::save_test_result($this->save_result_file . '/composite.xml', $this->result_file->get_xml(), true, $this->save_result_identifier);
648			$report_buffer .= pts_client::cli_just_bold('Result saved: ' . $this->save_result_file) . PHP_EOL;
649		}
650
651		return $report_buffer;
652	}
653}
654
655?>
656